I broke up with neovim....vim is my best friend now

This commit is contained in:
LinlyBoi
2023-04-30 08:14:07 +03:00
parent 0d185449c5
commit 4a4a6b1e81
5245 changed files with 468325 additions and 25 deletions

View File

@@ -0,0 +1,42 @@
function! lsp#utils#args#_parse(args, opt, remainder_key) abort
let l:result = {}
let l:is_opts = v:true
let l:remainder = []
for l:item in split(a:args, ' ')
if l:item[:1] !=# '--'
let l:is_opts = v:false
endif
if l:is_opts == v:false
call add(l:remainder, l:item)
continue
endif
let l:parts = split(l:item, '=')
let l:key = l:parts[0]
let l:value = get(l:parts, 1, '')
let l:key = l:key[2:]
if has_key(a:opt, l:key)
if has_key(a:opt[l:key], 'type')
let l:type = a:opt[l:key]['type']
if l:type == type(v:true)
if l:value ==# 'false' || l:value ==# '0' || l:value ==# ''
let l:value = 0
else
let l:value = 1
endif
elseif l:type ==# type(0)
let l:value = str2nr(l:value)
endif
endif
endif
let l:result[l:key] = l:value
endfor
if a:remainder_key != v:null
let l:result[a:remainder_key] = join(l:remainder)
endif
return l:result
endfunction

View File

@@ -0,0 +1,74 @@
let s:fixendofline_exists = exists('+fixendofline')
function! s:get_fixendofline(buf) abort
let l:eol = getbufvar(a:buf, '&endofline')
let l:binary = getbufvar(a:buf, '&binary')
if s:fixendofline_exists
let l:fixeol = getbufvar(a:buf, '&fixendofline')
if !l:binary
" When 'binary' is off and 'fixeol' is on, 'endofline' is not used
"
" When 'binary' is off and 'fixeol' is off, 'endofline' is used to
" remember the presence of a <EOL>
return l:fixeol || l:eol
else
" When 'binary' is on, the value of 'fixeol' doesn't matter
return l:eol
endif
else
" When 'binary' is off the value of 'endofline' is not used
"
" When 'binary' is on 'endofline' is used to remember the presence of
" a <EOL>
return !l:binary || l:eol
endif
endfunction
function! lsp#utils#buffer#_get_fixendofline(bufnr) abort
return s:get_fixendofline(a:bufnr)
endfunction
function! lsp#utils#buffer#_get_lines(buf) abort
let l:lines = getbufline(a:buf, 1, '$')
if s:get_fixendofline(a:buf)
let l:lines += ['']
endif
return l:lines
endfunction
" @params {location} = {
" 'uri': 'file://....',
" 'range': {
" 'start': { 'line': 1, 'character': 1 },
" 'end': { 'line': 1, 'character': 1 },
" }
" }
function! lsp#utils#buffer#_open_lsp_location(location) abort
let l:path = lsp#utils#uri_to_path(a:location['uri'])
let l:bufnr = bufnr(l:path)
let [l:start_line, l:start_col] = lsp#utils#position#lsp_to_vim(l:bufnr, a:location['range']['start'])
let [l:end_line, l:end_col] = lsp#utils#position#lsp_to_vim(l:bufnr, a:location['range']['end'])
normal! m'
if &modified && !&hidden
let l:cmd = l:bufnr !=# -1 ? 'sb ' . l:bufnr : 'split ' . fnameescape(l:path)
else
let l:cmd = l:bufnr !=# -1 ? 'b ' . l:bufnr : 'edit ' . fnameescape(l:path)
endif
execute l:cmd . ' | call cursor('.l:start_line.','.l:start_col.')'
normal! V
call setpos("'<", [l:bufnr, l:start_line, l:start_col])
call setpos("'>", [l:bufnr, l:end_line, l:end_col])
endfunction
function! lsp#utils#buffer#get_indent_size(bufnr) abort
let l:shiftwidth = getbufvar(a:bufnr, '&shiftwidth')
if getbufvar(a:bufnr, '&shiftwidth')
return l:shiftwidth
endif
return getbufvar(a:bufnr, '&tabstop')
endfunction

View File

@@ -0,0 +1,165 @@
" This is copied from https://github.com/natebosch/vim-lsc/blob/master/autoload/lsc/diff.vim
"
" Computes a simplistic diff between [old] and [new].
"
" Returns a dict with keys `range`, `rangeLength`, and `text` matching the LSP
" definition of `TextDocumentContentChangeEvent`.
"
" Finds a single change between the common prefix, and common postfix.
let s:has_lua = has('nvim-0.4.0') || (has('lua') && has('patch-8.2.0775'))
" lua array and neovim vim list index starts with 1 while vim lists starts with 0.
" starting patch-8.2.1066 vim lists array index was changed to start with 1.
let s:lua_array_start_index = has('nvim-0.4.0') || has('patch-8.2.1066')
function! s:init_lua() abort
lua <<EOF
-- Returns a zero-based index of the last line that is different between
-- old and new. If old and new are not zero indexed, pass offset to indicate
-- the index base.
function vimlsp_last_difference(old, new, offset, line_count)
for i = 0, line_count - 1 do
if old[#old - i + offset] ~= new[#new - i + offset] then
return -1 * i
end
end
return -1 * line_count
end
-- Returns a zero-based index of the first line that is different between
-- old and new. If old and new are not zero indexed, pass offset to indicate
-- the index base.
function vimlsp_first_difference(old, new, offset, line_count)
for i = 0, line_count - 1 do
if old[i + offset] ~= new[i + offset] then
return i
end
end
return line_count - 1
end
EOF
let s:lua = 1
endfunction
if s:has_lua && !exists('s:lua')
call s:init_lua()
endif
function! lsp#utils#diff#compute(old, new) abort
let [l:start_line, l:start_char] = s:FirstDifference(a:old, a:new)
let [l:end_line, l:end_char] =
\ s:LastDifference(a:old[l:start_line :], a:new[l:start_line :], l:start_char)
let l:text = s:ExtractText(a:new, l:start_line, l:start_char, l:end_line, l:end_char)
let l:length = s:Length(a:old, l:start_line, l:start_char, l:end_line, l:end_char)
let l:adj_end_line = len(a:old) + l:end_line
let l:adj_end_char = l:end_line == 0 ? 0 : strchars(a:old[l:end_line]) + l:end_char + 1
let l:result = { 'range': {'start': {'line': l:start_line, 'character': l:start_char},
\ 'end': {'line': l:adj_end_line, 'character': l:adj_end_char}},
\ 'text': l:text,
\ 'rangeLength': l:length,
\}
return l:result
endfunction
" Finds the line and character of the first different character between two
" list of Strings.
function! s:FirstDifference(old, new) abort
let l:line_count = min([len(a:old), len(a:new)])
if l:line_count == 0 | return [0, 0] | endif
if g:lsp_use_lua && s:has_lua
let l:eval = has('nvim') ? 'vim.api.nvim_eval' : 'vim.eval'
let l:i = luaeval('vimlsp_first_difference('
\.l:eval.'("a:old"),'.l:eval.'("a:new"),'.s:lua_array_start_index.','.l:line_count.')')
else
for l:i in range(l:line_count)
if a:old[l:i] !=# a:new[l:i] | break | endif
endfor
endif
if l:i >= l:line_count
return [l:line_count - 1, strchars(a:old[l:line_count - 1])]
endif
let l:old_line = a:old[l:i]
let l:new_line = a:new[l:i]
let l:length = min([strchars(l:old_line), strchars(l:new_line)])
let l:j = 0
while l:j < l:length
if strgetchar(l:old_line, l:j) != strgetchar(l:new_line, l:j) | break | endif
let l:j += 1
endwhile
return [l:i, l:j]
endfunction
function! s:LastDifference(old, new, start_char) abort
let l:line_count = min([len(a:old), len(a:new)])
if l:line_count == 0 | return [0, 0] | endif
if g:lsp_use_lua && s:has_lua
let l:eval = has('nvim') ? 'vim.api.nvim_eval' : 'vim.eval'
let l:i = luaeval('vimlsp_last_difference('
\.l:eval.'("a:old"),'.l:eval.'("a:new"),'.s:lua_array_start_index.','.l:line_count.')')
else
for l:i in range(-1, -1 * l:line_count, -1)
if a:old[l:i] !=# a:new[l:i] | break | endif
endfor
endif
if l:i <= -1 * l:line_count
let l:i = -1 * l:line_count
let l:old_line = strcharpart(a:old[l:i], a:start_char)
let l:new_line = strcharpart(a:new[l:i], a:start_char)
else
let l:old_line = a:old[l:i]
let l:new_line = a:new[l:i]
endif
let l:old_line_length = strchars(l:old_line)
let l:new_line_length = strchars(l:new_line)
let l:length = min([l:old_line_length, l:new_line_length])
let l:j = -1
while l:j >= -1 * l:length
if strgetchar(l:old_line, l:old_line_length + l:j) !=
\ strgetchar(l:new_line, l:new_line_length + l:j)
break
endif
let l:j -= 1
endwhile
return [l:i, l:j]
endfunction
function! s:ExtractText(lines, start_line, start_char, end_line, end_char) abort
if a:start_line == len(a:lines) + a:end_line
if a:end_line == 0 | return '' | endif
let l:line = a:lines[a:start_line]
let l:length = strchars(l:line) + a:end_char - a:start_char + 1
return strcharpart(l:line, a:start_char, l:length)
endif
let l:result = strcharpart(a:lines[a:start_line], a:start_char) . "\n"
for l:line in a:lines[a:start_line + 1:a:end_line - 1]
let l:result .= l:line . "\n"
endfor
if a:end_line != 0
let l:line = a:lines[a:end_line]
let l:length = strchars(l:line) + a:end_char + 1
let l:result .= strcharpart(l:line, 0, l:length)
endif
return l:result
endfunction
function! s:Length(lines, start_line, start_char, end_line, end_char) abort
let l:adj_end_line = len(a:lines) + a:end_line
if l:adj_end_line >= len(a:lines)
let l:adj_end_char = a:end_char - 1
else
let l:adj_end_char = strchars(a:lines[l:adj_end_line]) + a:end_char
endif
if a:start_line == l:adj_end_line
return l:adj_end_char - a:start_char + 1
endif
let l:result = strchars(a:lines[a:start_line]) - a:start_char + 1
let l:line = a:start_line + 1
while l:line < l:adj_end_line
let l:result += strchars(a:lines[l:line]) + 1
let l:line += 1
endwhile
let l:result += l:adj_end_char + 1
return l:result
endfunction

View File

@@ -0,0 +1,409 @@
" https://github.com/prabirshrestha/async.vim#2082d13bb195f3203d41a308b89417426a7deca1 (dirty)
" :AsyncEmbed path=./autoload/lsp/utils/job.vim namespace=lsp#utils#job
" Author: Prabir Shrestha <mail at prabir dot me>
" Website: https://github.com/prabirshrestha/async.vim
" License: The MIT License {{{
" The MIT License (MIT)
"
" Copyright (c) 2016 Prabir Shrestha
"
" Permission is hereby granted, free of charge, to any person obtaining a copy
" of this software and associated documentation files (the "Software"), to deal
" in the Software without restriction, including without limitation the rights
" to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
" copies of the Software, and to permit persons to whom the Software is
" furnished to do so, subject to the following conditions:
"
" The above copyright notice and this permission notice shall be included in all
" copies or substantial portions of the Software.
"
" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
" IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
" FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
" AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
" LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
" OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
" SOFTWARE.
" }}}
let s:save_cpo = &cpo
set cpo&vim
let s:jobidseq = 0
let s:jobs = {} " { job, opts, type: 'vimjob|nvimjob'}
let s:job_type_nvimjob = 'nvimjob'
let s:job_type_vimjob = 'vimjob'
let s:job_error_unsupported_job_type = -2 " unsupported job type
function! s:noop(...) abort
endfunction
function! s:job_supported_types() abort
let l:supported_types = []
if has('nvim')
let l:supported_types += [s:job_type_nvimjob]
endif
if !has('nvim') && has('job') && has('channel') && has('lambda')
let l:supported_types += [s:job_type_vimjob]
endif
return l:supported_types
endfunction
function! s:job_supports_type(type) abort
return index(s:job_supported_types(), a:type) >= 0
endfunction
function! s:out_cb(jobid, opts, job, data) abort
call a:opts.on_stdout(a:jobid, a:data, 'stdout')
endfunction
function! s:out_cb_array(jobid, opts, job, data) abort
call a:opts.on_stdout(a:jobid, split(a:data, "\n", 1), 'stdout')
endfunction
function! s:err_cb(jobid, opts, job, data) abort
call a:opts.on_stderr(a:jobid, a:data, 'stderr')
endfunction
function! s:err_cb_array(jobid, opts, job, data) abort
call a:opts.on_stderr(a:jobid, split(a:data, "\n", 1), 'stderr')
endfunction
function! s:exit_cb(jobid, opts, job, status) abort
if has_key(a:opts, 'on_exit')
call a:opts.on_exit(a:jobid, a:status, 'exit')
endif
if has_key(s:jobs, a:jobid)
call remove(s:jobs, a:jobid)
endif
endfunction
function! s:on_stdout(jobid, data, event) abort
let l:jobinfo = s:jobs[a:jobid]
call l:jobinfo.opts.on_stdout(a:jobid, a:data, a:event)
endfunction
function! s:on_stdout_string(jobid, data, event) abort
let l:jobinfo = s:jobs[a:jobid]
call l:jobinfo.opts.on_stdout(a:jobid, join(a:data, "\n"), a:event)
endfunction
function! s:on_stderr(jobid, data, event) abort
let l:jobinfo = s:jobs[a:jobid]
call l:jobinfo.opts.on_stderr(a:jobid, a:data, a:event)
endfunction
function! s:on_stderr_string(jobid, data, event) abort
let l:jobinfo = s:jobs[a:jobid]
call l:jobinfo.opts.on_stderr(a:jobid, join(a:data, "\n"), a:event)
endfunction
function! s:on_exit(jobid, status, event) abort
if has_key(s:jobs, a:jobid)
let l:jobinfo = s:jobs[a:jobid]
if has_key(l:jobinfo.opts, 'on_exit')
call l:jobinfo.opts.on_exit(a:jobid, a:status, a:event)
endif
if has_key(s:jobs, a:jobid)
call remove(s:jobs, a:jobid)
endif
endif
endfunction
function! s:job_start(cmd, opts) abort
let l:jobtypes = s:job_supported_types()
let l:jobtype = ''
if has_key(a:opts, 'type')
if type(a:opts.type) == type('')
if !s:job_supports_type(a:opts.type)
return s:job_error_unsupported_job_type
endif
let l:jobtype = a:opts.type
else
let l:jobtypes = a:opts.type
endif
endif
if empty(l:jobtype)
" find the best jobtype
for l:jobtype2 in l:jobtypes
if s:job_supports_type(l:jobtype2)
let l:jobtype = l:jobtype2
endif
endfor
endif
if l:jobtype ==? ''
return s:job_error_unsupported_job_type
endif
" options shared by both vim and neovim
let l:jobopt = {}
if has_key(a:opts, 'cwd')
let l:jobopt.cwd = a:opts.cwd
endif
if has_key(a:opts, 'env')
let l:jobopt.env = a:opts.env
endif
let l:normalize = get(a:opts, 'normalize', 'array') " array/string/raw
if l:jobtype == s:job_type_nvimjob
if l:normalize ==# 'string'
let l:jobopt['on_stdout'] = has_key(a:opts, 'on_stdout') ? function('s:on_stdout_string') : function('s:noop')
let l:jobopt['on_stderr'] = has_key(a:opts, 'on_stderr') ? function('s:on_stderr_string') : function('s:noop')
else " array or raw
let l:jobopt['on_stdout'] = has_key(a:opts, 'on_stdout') ? function('s:on_stdout') : function('s:noop')
let l:jobopt['on_stderr'] = has_key(a:opts, 'on_stderr') ? function('s:on_stderr') : function('s:noop')
endif
call extend(l:jobopt, { 'on_exit': function('s:on_exit') })
let l:job = jobstart(a:cmd, l:jobopt)
if l:job <= 0
return l:job
endif
let l:jobid = l:job " nvimjobid and internal jobid is same
let s:jobs[l:jobid] = {
\ 'type': s:job_type_nvimjob,
\ 'opts': a:opts,
\ }
let s:jobs[l:jobid].job = l:job
elseif l:jobtype == s:job_type_vimjob
let s:jobidseq = s:jobidseq + 1
let l:jobid = s:jobidseq
if l:normalize ==# 'array'
let l:jobopt['out_cb'] = has_key(a:opts, 'on_stdout') ? function('s:out_cb_array', [l:jobid, a:opts]) : function('s:noop')
let l:jobopt['err_cb'] = has_key(a:opts, 'on_stderr') ? function('s:err_cb_array', [l:jobid, a:opts]) : function('s:noop')
else " raw or string
let l:jobopt['out_cb'] = has_key(a:opts, 'on_stdout') ? function('s:out_cb', [l:jobid, a:opts]) : function('s:noop')
let l:jobopt['err_cb'] = has_key(a:opts, 'on_stderr') ? function('s:err_cb', [l:jobid, a:opts]) : function('s:noop')
endif
call extend(l:jobopt, {
\ 'exit_cb': function('s:exit_cb', [l:jobid, a:opts]),
\ 'mode': 'raw',
\ })
if has('patch-8.1.889')
let l:jobopt['noblock'] = 1
endif
let l:job = job_start(a:cmd, l:jobopt)
if job_status(l:job) !=? 'run'
return -1
endif
let s:jobs[l:jobid] = {
\ 'type': s:job_type_vimjob,
\ 'opts': a:opts,
\ 'job': l:job,
\ 'channel': job_getchannel(l:job),
\ 'buffer': ''
\ }
else
return s:job_error_unsupported_job_type
endif
return l:jobid
endfunction
function! s:job_stop(jobid) abort
if has_key(s:jobs, a:jobid)
let l:jobinfo = s:jobs[a:jobid]
if l:jobinfo.type == s:job_type_nvimjob
" See: vital-Whisky/System.Job
try
call jobstop(a:jobid)
catch /^Vim\%((\a\+)\)\=:E900/
" NOTE:
" Vim does not raise exception even the job has already closed so fail
" silently for 'E900: Invalid job id' exception
endtry
elseif l:jobinfo.type == s:job_type_vimjob
if type(s:jobs[a:jobid].job) == v:t_job
call job_stop(s:jobs[a:jobid].job)
elseif type(s:jobs[a:jobid].job) == v:t_channel
call ch_close(s:jobs[a:jobid].job)
endif
endif
endif
endfunction
function! s:job_send(jobid, data, opts) abort
let l:jobinfo = s:jobs[a:jobid]
let l:close_stdin = get(a:opts, 'close_stdin', 0)
if l:jobinfo.type == s:job_type_nvimjob
call jobsend(a:jobid, a:data)
if l:close_stdin
call chanclose(a:jobid, 'stdin')
endif
elseif l:jobinfo.type == s:job_type_vimjob
" There is no easy way to know when ch_sendraw() finishes writing data
" on a non-blocking channels -- has('patch-8.1.889') -- and because of
" this, we cannot safely call ch_close_in(). So when we find ourselves
" in this situation (i.e. noblock=1 and close stdin after send) we fall
" back to using s:flush_vim_sendraw() and wait for transmit buffer to be
" empty
"
" Ref: https://groups.google.com/d/topic/vim_dev/UNNulkqb60k/discussion
if has('patch-8.1.818') && (!has('patch-8.1.889') || !l:close_stdin)
call ch_sendraw(l:jobinfo.channel, a:data)
else
let l:jobinfo.buffer .= a:data
call s:flush_vim_sendraw(a:jobid, v:null)
endif
if l:close_stdin
while len(l:jobinfo.buffer) != 0
sleep 1m
endwhile
call ch_close_in(l:jobinfo.channel)
endif
endif
endfunction
function! s:flush_vim_sendraw(jobid, timer) abort
" https://github.com/vim/vim/issues/2548
" https://github.com/natebosch/vim-lsc/issues/67#issuecomment-357469091
let l:jobinfo = s:jobs[a:jobid]
sleep 1m
if len(l:jobinfo.buffer) <= 4096
call ch_sendraw(l:jobinfo.channel, l:jobinfo.buffer)
let l:jobinfo.buffer = ''
else
let l:to_send = l:jobinfo.buffer[:4095]
let l:jobinfo.buffer = l:jobinfo.buffer[4096:]
call ch_sendraw(l:jobinfo.channel, l:to_send)
call timer_start(1, function('s:flush_vim_sendraw', [a:jobid]))
endif
endfunction
function! s:job_wait_single(jobid, timeout, start) abort
if !has_key(s:jobs, a:jobid)
return -3
endif
let l:jobinfo = s:jobs[a:jobid]
if l:jobinfo.type == s:job_type_nvimjob
let l:timeout = a:timeout - reltimefloat(reltime(a:start)) * 1000
return jobwait([a:jobid], float2nr(l:timeout))[0]
elseif l:jobinfo.type == s:job_type_vimjob
let l:timeout = a:timeout / 1000.0
try
while l:timeout < 0 || reltimefloat(reltime(a:start)) < l:timeout
let l:info = job_info(l:jobinfo.job)
if l:info.status ==# 'dead'
return l:info.exitval
elseif l:info.status ==# 'fail'
return -3
endif
sleep 1m
endwhile
catch /^Vim:Interrupt$/
return -2
endtry
endif
return -1
endfunction
function! s:job_wait(jobids, timeout) abort
let l:start = reltime()
let l:exitcode = 0
let l:ret = []
for l:jobid in a:jobids
if l:exitcode != -2 " Not interrupted.
let l:exitcode = s:job_wait_single(l:jobid, a:timeout, l:start)
endif
let l:ret += [l:exitcode]
endfor
return l:ret
endfunction
function! s:job_pid(jobid) abort
if !has_key(s:jobs, a:jobid)
return 0
endif
let l:jobinfo = s:jobs[a:jobid]
if l:jobinfo.type == s:job_type_nvimjob
return jobpid(a:jobid)
elseif l:jobinfo.type == s:job_type_vimjob
let l:vimjobinfo = job_info(a:jobid)
if type(l:vimjobinfo) == type({}) && has_key(l:vimjobinfo, 'process')
return l:vimjobinfo['process']
endif
endif
return 0
endfunction
function! s:callback_cb(jobid, opts, ch, data) abort
if has_key(a:opts, 'on_stdout')
call a:opts.on_stdout(a:jobid, a:data, 'stdout')
endif
endfunction
function! s:callback_cb_array(jobid, opts, ch, data) abort
if has_key(a:opts, 'on_stdout')
call a:opts.on_stdout(a:jobid, split(a:data, "\n", 1), 'stdout')
endif
endfunction
function! s:close_cb(jobid, opts, ch) abort
if has_key(a:opts, 'on_exit')
call a:opts.on_exit(a:jobid, 'closed', 'exit')
endif
if has_key(s:jobs, a:jobid)
call remove(s:jobs, a:jobid)
endif
endfunction
" public apis {{{
function! lsp#utils#job#start(cmd, opts) abort
return s:job_start(a:cmd, a:opts)
endfunction
function! lsp#utils#job#stop(jobid) abort
call s:job_stop(a:jobid)
endfunction
function! lsp#utils#job#send(jobid, data, ...) abort
let l:opts = get(a:000, 0, {})
call s:job_send(a:jobid, a:data, l:opts)
endfunction
function! lsp#utils#job#wait(jobids, ...) abort
let l:timeout = get(a:000, 0, -1)
return s:job_wait(a:jobids, l:timeout)
endfunction
function! lsp#utils#job#pid(jobid) abort
return s:job_pid(a:jobid)
endfunction
function! lsp#utils#job#connect(addr, opts) abort
let s:jobidseq = s:jobidseq + 1
let l:jobid = s:jobidseq
let l:retry = 0
let l:normalize = get(a:opts, 'normalize', 'array') " array/string/raw
while l:retry < 5
let l:ch = ch_open(a:addr, {'waittime': 1000})
call ch_setoptions(l:ch, {
\ 'callback': function(l:normalize ==# 'array' ? 's:callback_cb_array' : 's:callback_cb', [l:jobid, a:opts]),
\ 'close_cb': function('s:close_cb', [l:jobid, a:opts]),
\ 'mode': 'raw',
\})
if ch_status(l:ch) ==# 'open'
break
endif
sleep 100m
let l:retry += 1
endwhile
let s:jobs[l:jobid] = {
\ 'type': s:job_type_vimjob,
\ 'opts': a:opts,
\ 'job': l:ch,
\ 'channel': l:ch,
\ 'buffer': ''
\}
return l:jobid
endfunction
" }}}
let &cpo = s:save_cpo
unlet s:save_cpo

View File

@@ -0,0 +1,135 @@
function! s:open_location(path, line, col, ...) abort
normal! m'
let l:mods = a:0 ? a:1 : ''
let l:buffer = bufnr(a:path)
if l:mods ==# '' && &modified && !&hidden && l:buffer != bufnr('%')
let l:mods = &splitbelow ? 'rightbelow' : 'leftabove'
endif
if l:mods ==# ''
if l:buffer == bufnr('%')
let l:cmd = ''
else
let l:cmd = (l:buffer !=# -1 ? 'b ' . l:buffer : 'edit ' . fnameescape(a:path)) . ' | '
endif
else
let l:cmd = l:mods . ' ' . (l:buffer !=# -1 ? 'sb ' . l:buffer : 'split ' . fnameescape(a:path)) . ' | '
endif
execute l:cmd . 'call cursor('.a:line.','.a:col.')'
endfunction
" @param location = {
" 'filename',
" 'lnum',
" 'col',
" }
function! lsp#utils#location#_open_vim_list_item(location, mods) abort
call s:open_location(a:location['filename'], a:location['lnum'], a:location['col'], a:mods)
endfunction
" @params {location} = {
" 'uri': 'file://....',
" 'range': {
" 'start': { 'line': 1, 'character': 1 },
" 'end': { 'line': 1, 'character': 1 },
" }
" }
function! lsp#utils#location#_open_lsp_location(location) abort
let l:path = lsp#utils#uri_to_path(a:location['uri'])
let l:bufnr = bufnr(l:path)
let [l:start_line, l:start_col] = lsp#utils#position#lsp_to_vim(l:bufnr, a:location['range']['start'])
let [l:end_line, l:end_col] = lsp#utils#position#lsp_to_vim(l:bufnr, a:location['range']['end'])
call s:open_location(l:path, l:start_line, l:start_col)
normal! V
call setpos("'<", [l:bufnr, l:start_line, l:start_col])
call setpos("'>", [l:bufnr, l:end_line, l:end_col])
endfunction
" @param loc = Location | LocationLink
" @param cache = {} empty dict
" @returns {
" 'filename',
" 'lnum',
" 'col',
" 'text',
" 'viewstart?',
" 'viewend?',
" }
function! s:lsp_location_item_to_vim(loc, cache) abort
if has_key(a:loc, 'targetUri') " LocationLink
let l:uri = a:loc['targetUri']
let l:range = a:loc['targetSelectionRange']
let l:use_link = 1
else " Location
let l:uri = a:loc['uri']
let l:range = a:loc['range']
let l:use_link = 0
endif
if !lsp#utils#is_file_uri(l:uri)
return v:null
endif
let l:path = lsp#utils#uri_to_path(l:uri)
let [l:line, l:col] = lsp#utils#position#lsp_to_vim(l:path, l:range['start'])
let l:index = l:line - 1
if has_key(a:cache, l:path)
let l:text = a:cache[l:path][l:index]
else
let l:contents = getbufline(l:path, 1, '$')
if !empty(l:contents)
let l:text = get(l:contents, l:index, '')
else
let l:contents = readfile(l:path)
let a:cache[l:path] = l:contents
let l:text = get(l:contents, l:index, '')
endif
endif
if l:use_link
" viewstart/end decremented to account for incrementing in _lsp_to_vim
return {
\ 'filename': l:path,
\ 'lnum': l:line,
\ 'col': l:col,
\ 'text': l:text,
\ 'viewstart': lsp#utils#position#lsp_to_vim(l:path, a:loc['targetRange']['start'])[0] - 1,
\ 'viewend': lsp#utils#position#lsp_to_vim(l:path, a:loc['targetRange']['end'])[0] - 1,
\ }
else
return {
\ 'filename': l:path,
\ 'lnum': l:line,
\ 'col': l:col,
\ 'text': l:text,
\ }
endif
endfunction
" @summary Use this to convert loc to vim list that is compatible with
" quickfix and locllist items
" @param loc = v:null | Location | Location[] | LocationLink
" @returns []
function! lsp#utils#location#_lsp_to_vim_list(loc) abort
let l:result = []
let l:cache = {}
if empty(a:loc) " v:null
return l:result
elseif type(a:loc) == type([]) " Location[]
for l:location in a:loc
let l:vim_loc = s:lsp_location_item_to_vim(l:location, l:cache)
if !empty(l:vim_loc) " https:// uri will return empty
call add(l:result, l:vim_loc)
endif
endfor
else " Location or LocationLink
let l:vim_loc = s:lsp_location_item_to_vim(a:loc, l:cache)
if !empty(l:vim_loc) " https:// uri will return empty
call add(l:result, l:vim_loc)
endif
endif
return l:result
endfunction

View File

@@ -0,0 +1,91 @@
" This function can be error prone if the caller forgets to use +1 to vim line
" so use lsp#utils#position#lsp_to_vim instead
" Convert a character-index (0-based) to byte-index (1-based)
" This function requires a buffer specifier (expr, see :help bufname()),
" a line number (lnum, 1-based), and a character-index (char, 0-based).
function! s:to_col(expr, lnum, char) abort
let l:lines = getbufline(a:expr, a:lnum)
if l:lines == []
if type(a:expr) != v:t_string || !filereadable(a:expr)
" invalid a:expr
return a:char + 1
endif
" a:expr is a file that is not yet loaded as a buffer
let l:lines = readfile(a:expr, '', a:lnum)
if l:lines == []
" when the file is empty. a:char should be 0 in the case
return a:char + 1
endif
endif
let l:linestr = l:lines[-1]
return strlen(strcharpart(l:linestr, 0, a:char)) + 1
endfunction
" The inverse version of `s:to_col`.
" Convert [lnum, col] to LSP's `Position`.
function! s:to_char(expr, lnum, col) abort
let l:lines = getbufline(a:expr, a:lnum)
if l:lines == []
if type(a:expr) != v:t_string || !filereadable(a:expr)
" invalid a:expr
return a:col - 1
endif
" a:expr is a file that is not yet loaded as a buffer
let l:lines = readfile(a:expr, '', a:lnum)
endif
let l:linestr = l:lines[-1]
return strchars(strpart(l:linestr, 0, a:col - 1))
endfunction
" @param expr = see :help bufname()
" @param position = {
" 'line': 1,
" 'character': 1
" }
" @returns [
" line,
" col
" ]
function! lsp#utils#position#lsp_to_vim(expr, position) abort
let l:line = lsp#utils#position#lsp_line_to_vim(a:expr, a:position)
let l:col = lsp#utils#position#lsp_character_to_vim(a:expr, a:position)
return [l:line, l:col]
endfunction
" @param expr = see :help bufname()
" @param position = {
" 'line': 1,
" 'character': 1
" }
" @returns
" line
function! lsp#utils#position#lsp_line_to_vim(expr, position) abort
return a:position['line'] + 1
endfunction
" @param expr = see :help bufname()
" @param position = {
" 'line': 1,
" 'character': 1
" }
" @returns
" line
function! lsp#utils#position#lsp_character_to_vim(expr, position) abort
let l:line = a:position['line'] + 1 " optimize function overhead by not calling lsp_line_to_vim
let l:char = a:position['character']
return s:to_col(a:expr, l:line, l:char)
endfunction
" @param expr = :help bufname()
" @param pos = [lnum, col]
" @returns {
" 'line': line,
" 'character': character
" }
function! lsp#utils#position#vim_to_lsp(expr, pos) abort
return {
\ 'line': a:pos[0] - 1,
\ 'character': s:to_char(a:expr, a:pos[0], a:pos[1])
\ }
endfunction

View File

@@ -0,0 +1,82 @@
"
" Returns recent visual-mode range.
"
function! lsp#utils#range#_get_recent_visual_range() abort
let l:start_pos = getpos("'<")[1 : 2]
let l:end_pos = getpos("'>")[1 : 2]
let l:end_pos[1] += 1 " To exclusive
" Fix line selection.
let l:end_line = getline(l:end_pos[0])
if l:end_pos[1] > strlen(l:end_line)
let l:end_pos[1] = strlen(l:end_line) + 1
endif
let l:range = {}
let l:range['start'] = lsp#utils#position#vim_to_lsp('%', l:start_pos)
let l:range['end'] = lsp#utils#position#vim_to_lsp('%', l:end_pos)
return l:range
endfunction
"
" Returns current line range.
"
function! lsp#utils#range#_get_current_line_range() abort
let l:pos = getpos('.')[1 : 2]
let l:range = {}
let l:range['start'] = lsp#utils#position#vim_to_lsp('%', l:pos)
let l:range['end'] = lsp#utils#position#vim_to_lsp('%', [l:pos[0], l:pos[1] + strlen(getline(l:pos[0])) + 1])
return l:range
endfunction
" Convert a LSP range to one or more vim match positions.
" If the range spans over multiple lines, break it down to multiple
" positions, one for each line.
" Return a list of positions.
function! lsp#utils#range#lsp_to_vim(bufnr, range) abort
let l:position = []
let [l:start_line, l:start_col] = lsp#utils#position#lsp_to_vim(a:bufnr, a:range['start'])
let [l:end_line, l:end_col] = lsp#utils#position#lsp_to_vim(a:bufnr, a:range['end'])
if l:end_line == l:start_line
let l:position = [[
\ l:start_line,
\ l:start_col,
\ l:end_col - l:start_col
\ ]]
else
" First line
let l:position = [[
\ l:start_line,
\ l:start_col,
\ 999
\ ]]
" Last line
call add(l:position, [
\ l:end_line,
\ 1,
\ l:end_col
\ ])
" Lines in the middle
let l:middle_lines = map(
\ range(l:start_line + 1, l:end_line - 1),
\ {_, l -> [l, 0, 999]}
\ )
call extend(l:position, l:middle_lines)
endif
return l:position
endfunction
function! lsp#utils#range#get_range() abort
let l:char = lsp#utils#to_char('%', line('$'), col('$'))
return {'start': {'line': 0, 'character': 0}, 'end': {'line': line('$')-1, 'character': l:char}}
endfunction
function! lsp#utils#range#get_range_curline() abort
let l:char = lsp#utils#to_char('%', line('.'), col('$'))
return {'start': {'line': line('.')-1, 'character': 0}, 'end': {'line': line('.')-1, 'character': l:char}}
endfunction

View File

@@ -0,0 +1,19 @@
function! s:next(steps, current_index, result) abort
if len(a:steps) == a:current_index
return
endif
let l:Step = a:steps[a:current_index]
let l:ctx = {
\ 'callback': function('s:callback', [a:steps, a:current_index]),
\ 'result': a:result
\ }
call call(l:Step, [l:ctx])
endfunction
function! s:callback(steps, current_index, ...) abort
call s:next(a:steps, a:current_index + 1, a:000)
endfunction
function! lsp#utils#step#start(steps) abort
call s:next(a:steps, 0, [])
endfunction

View File

@@ -0,0 +1,33 @@
if exists('*gettagstack') && exists('*settagstack')
function! lsp#utils#tagstack#_update() abort
let l:bufnr = bufnr('%')
let l:item = {'bufnr': l:bufnr, 'from': [l:bufnr, line('.'), col('.'), 0], 'tagname': expand('<cword>')}
let l:winid = win_getid()
let l:stack = gettagstack(l:winid)
if l:stack['length'] == l:stack['curidx']
" Replace the last items with item.
let l:action = 'r'
let l:stack['items'][l:stack['curidx']-1] = l:item
elseif l:stack['length'] > l:stack['curidx']
" Replace items after used items with item.
let l:action = 'r'
if l:stack['curidx'] > 1
let l:stack['items'] = add(l:stack['items'][:l:stack['curidx']-2], l:item)
else
let l:stack['items'] = [l:item]
endif
else
" Append item.
let l:action = 'a'
let l:stack['items'] = [l:item]
endif
let l:stack['curidx'] += 1
call settagstack(l:winid, l:stack, l:action)
endfunction
else
function! lsp#utils#tagstack#_update() abort
" do nothing
endfunction
endif

View File

@@ -0,0 +1,229 @@
function! lsp#utils#text_edit#get_range(text_edit) abort
if type(a:text_edit) != v:t_dict
return v:null
endif
let l:insert = get(a:text_edit, 'insert', v:null)
if type(l:insert) == v:t_dict
return l:insert
endif
return get(a:text_edit, 'range', v:null)
endfunction
function! lsp#utils#text_edit#apply_text_edits(uri, text_edits) abort
let l:current_bufname = bufname('%')
let l:target_bufname = lsp#utils#uri_to_path(a:uri)
let l:cursor_position = lsp#get_position()
call s:_switch(l:target_bufname)
for l:text_edit in s:_normalize(a:text_edits)
call s:_apply(bufnr(l:target_bufname), l:text_edit, l:cursor_position)
endfor
call s:_switch(l:current_bufname)
if bufnr(l:current_bufname) == bufnr(l:target_bufname)
call cursor(lsp#utils#position#lsp_to_vim('%', l:cursor_position))
endif
endfunction
" @summary Use this to convert textedit to vim list that is compatible with
" quickfix and locllist items
" @param uri = DocumentUri
" @param text_edit = TextEdit | TextEdit[]
" @returns []
function! lsp#utils#text_edit#_lsp_to_vim_list(uri, text_edit) abort
let l:result = []
let l:cache = {}
if type(a:text_edit) == type([]) " TextEdit[]
for l:text_edit in a:text_edit
let l:vim_loc = s:lsp_text_edit_item_to_vim(a:uri, l:text_edit, l:cache)
if !empty(l:vim_loc)
call add(l:result, l:vim_loc)
endif
endfor
else " TextEdit
let l:vim_loc = s:lsp_text_edit_item_to_vim(a:uri, a:text_edit, l:cache)
if !empty(l:vim_loc)
call add(l:result, l:vim_loc)
endif
endif
return l:result
endfunction
" @param uri = DocumentUri
" @param text_edit = TextEdit
" @param cache = {} empty dict
" @returns {
" 'filename',
" 'lnum',
" 'col',
" 'text',
" }
function! s:lsp_text_edit_item_to_vim(uri, text_edit, cache) abort
if !lsp#utils#is_file_uri(a:uri)
return v:null
endif
let l:path = lsp#utils#uri_to_path(a:uri)
let l:range = a:text_edit['range']
let [l:line, l:col] = lsp#utils#position#lsp_to_vim(l:path, l:range['start'])
let l:index = l:line - 1
if has_key(a:cache, l:path)
let l:text = a:cache[l:path][l:index]
else
let l:contents = getbufline(l:path, 1, '$')
if !empty(l:contents)
let l:text = get(l:contents, l:index, '')
else
let l:contents = readfile(l:path)
let a:cache[l:path] = l:contents
let l:text = get(l:contents, l:index, '')
endif
endif
return {
\ 'filename': l:path,
\ 'lnum': l:line,
\ 'col': l:col,
\ 'text': l:text
\ }
endfunction
"
" _apply
"
function! s:_apply(bufnr, text_edit, cursor_position) abort
" create before/after line.
let l:start_line = getline(a:text_edit['range']['start']['line'] + 1)
let l:end_line = getline(a:text_edit['range']['end']['line'] + 1)
let l:before_line = strcharpart(l:start_line, 0, a:text_edit['range']['start']['character'])
let l:after_line = strcharpart(l:end_line, a:text_edit['range']['end']['character'], strchars(l:end_line) - a:text_edit['range']['end']['character'])
" create new lines.
let l:new_lines = lsp#utils#_split_by_eol(a:text_edit['newText'])
let l:new_lines[0] = l:before_line . l:new_lines[0]
let l:new_lines[-1] = l:new_lines[-1] . l:after_line
" save length.
let l:new_lines_len = len(l:new_lines)
let l:range_len = (a:text_edit['range']['end']['line'] - a:text_edit['range']['start']['line']) + 1
" fixendofline
let l:buffer_length = len(getbufline(a:bufnr, '^', '$'))
let l:should_fixendofline = lsp#utils#buffer#_get_fixendofline(a:bufnr)
let l:should_fixendofline = l:should_fixendofline && l:new_lines[-1] ==# ''
let l:should_fixendofline = l:should_fixendofline && l:buffer_length <= a:text_edit['range']['end']['line']
let l:should_fixendofline = l:should_fixendofline && a:text_edit['range']['end']['character'] == 0
if l:should_fixendofline
call remove(l:new_lines, -1)
endif
" fix cursor pos
if a:text_edit['range']['end']['line'] < a:cursor_position['line']
" fix cursor line
let a:cursor_position['line'] += l:new_lines_len - l:range_len
elseif a:text_edit['range']['end']['line'] == a:cursor_position['line'] && a:text_edit['range']['end']['character'] <= a:cursor_position['character']
" fix cursor line and col
let a:cursor_position['line'] += l:new_lines_len - l:range_len
let l:end_character = strchars(l:new_lines[-1]) - strchars(l:after_line)
let l:end_offset = a:cursor_position['character'] - a:text_edit['range']['end']['character']
let a:cursor_position['character'] = l:end_character + l:end_offset
endif
" append or delete lines.
if l:new_lines_len > l:range_len
call append(a:text_edit['range']['start']['line'], repeat([''], l:new_lines_len - l:range_len))
elseif l:new_lines_len < l:range_len
let l:offset = l:range_len - l:new_lines_len
call s:delete(a:bufnr, a:text_edit['range']['start']['line'] + 1, a:text_edit['range']['start']['line'] + l:offset)
endif
" set lines.
call setline(a:text_edit['range']['start']['line'] + 1, l:new_lines)
endfunction
"
" _normalize
"
function! s:_normalize(text_edits) abort
let l:text_edits = type(a:text_edits) == type([]) ? a:text_edits : [a:text_edits]
let l:text_edits = filter(copy(l:text_edits), { _, text_edit -> type(text_edit) == type({}) })
let l:text_edits = s:_range(l:text_edits)
let l:text_edits = sort(copy(l:text_edits), function('s:_compare', [], {}))
let l:text_edits = s:_check(l:text_edits)
return reverse(l:text_edits)
endfunction
"
" _range
"
function! s:_range(text_edits) abort
for l:text_edit in a:text_edits
if l:text_edit.range.start.line > l:text_edit.range.end.line || (
\ l:text_edit.range.start.line == l:text_edit.range.end.line &&
\ l:text_edit.range.start.character > l:text_edit.range.end.character
\ )
let l:text_edit.range = { 'start': l:text_edit.range.end, 'end': l:text_edit.range.start }
endif
endfor
return a:text_edits
endfunction
"
" _check
"
" LSP Spec says `multiple text edits can not overlap those ranges`.
" This function check it. But does not throw error.
"
function! s:_check(text_edits) abort
if len(a:text_edits) > 1
let l:range = a:text_edits[0].range
for l:text_edit in a:text_edits[1 : -1]
if l:range.end.line > l:text_edit.range.start.line || (
\ l:range.end.line == l:text_edit.range.start.line &&
\ l:range.end.character > l:text_edit.range.start.character
\ )
call lsp#log('text_edit: range overlapped.')
endif
let l:range = l:text_edit.range
endfor
endif
return a:text_edits
endfunction
"
" _compare
"
function! s:_compare(text_edit1, text_edit2) abort
let l:diff = a:text_edit1.range.start.line - a:text_edit2.range.start.line
if l:diff == 0
return a:text_edit1.range.start.character - a:text_edit2.range.start.character
endif
return l:diff
endfunction
"
" _switch
"
function! s:_switch(path) abort
if bufnr(a:path) >= 0
execute printf('keepalt keepjumps %sbuffer!', bufnr(a:path))
else
execute printf('keepalt keepjumps edit! %s', fnameescape(a:path))
endif
endfunction
"
" delete
"
function! s:delete(bufnr, start, end) abort
if exists('*deletebufline')
call deletebufline(a:bufnr, a:start, a:end)
else
let l:foldenable = &foldenable
setlocal nofoldenable
execute printf('%s,%sdelete _', a:start, a:end)
let &foldenable = l:foldenable
endif
endfunction

View File

@@ -0,0 +1,295 @@
" This file is part of an installation of vim-yggdrasil, a vim/neovim tree viewer library.
" The source code of vim-yggdrasil is available at https://github.com/m-pilia/vim-yggdrasil
"
" vim-yggdrasil is free software, distributed under the MIT license.
" The full license is available at https://github.com/m-pilia/vim-yggdrasil/blob/master/LICENSE
"
" Yggdrasil version (git SHA-1): 043d0ab53dcdd0d91b7c7cd205791d64d4ed9624
"
" This installation was generated on 2020-03-15T14:47:27-0700 with the following vim command:
" :YggdrasilPlant -plugin_dir=./ -namespace=lsp/utils
scriptencoding utf-8
" Callback to retrieve the tree item representation of an object.
function! s:node_get_tree_item_cb(node, object, status, tree_item) abort
if a:status ==? 'success'
let l:new_node = s:node_new(a:node.tree, a:object, a:tree_item, a:node)
call add(a:node.children, l:new_node)
call s:tree_render(l:new_node.tree)
endif
endfunction
" Callback to retrieve the children objects of a node.
function! s:node_get_children_cb(node, status, childObjectList) abort
for l:childObject in a:childObjectList
let l:Callback = function('s:node_get_tree_item_cb', [a:node, l:childObject])
call a:node.tree.provider.getTreeItem(l:Callback, l:childObject)
endfor
endfunction
" Set the node to be collapsed or expanded.
"
" When {collapsed} evaluates to 0 the node is expanded, when it is 1 the node is
" collapsed, when it is equal to -1 the node is toggled (it is expanded if it
" was collapsed, and vice versa).
function! s:node_set_collapsed(collapsed) dict abort
let l:self.collapsed = a:collapsed < 0 ? !l:self.collapsed : !!a:collapsed
endfunction
" Given a funcref {Condition}, return a list of all nodes in the subtree of
" {node} for which {Condition} evaluates to v:true.
function! s:search_subtree(node, Condition) abort
if a:Condition(a:node)
return [a:node]
endif
if len(a:node.children) < 1
return []
endif
let l:result = []
for l:child in a:node.children
let l:result = l:result + s:search_subtree(l:child, a:Condition)
endfor
return l:result
endfunction
" Execute the action associated to a node
function! s:node_exec() dict abort
if has_key(l:self.tree_item, 'command')
call l:self.tree_item.command()
endif
endfunction
" Return the depth level of the node in the tree. The level is defined
" recursively: the root has depth 0, and each node has depth equal to the depth
" of its parent increased by 1.
function! s:node_level() dict abort
if l:self.parent == {}
return 0
endif
return 1 + l:self.parent.level()
endf
" Return the string representation of the node. The {level} argument represents
" the depth level of the node in the tree and it is passed for convenience, to
" simplify the implementation and to avoid re-computing the depth.
function! s:node_render(level) dict abort
let l:indent = repeat(' ', 2 * a:level)
let l:mark = '• '
if len(l:self.children) > 0 || l:self.lazy_open != v:false
let l:mark = l:self.collapsed ? '▸ ' : '▾ '
endif
let l:label = split(l:self.tree_item.label, "\n")
call extend(l:self.tree.index, map(range(len(l:label)), 'l:self'))
let l:repr = l:indent . l:mark . l:label[0]
\ . join(map(l:label[1:], {_, l -> "\n" . l:indent . ' ' . l}))
let l:lines = [l:repr]
if !l:self.collapsed
if l:self.lazy_open
let l:self.lazy_open = v:false
let l:Callback = function('s:node_get_children_cb', [l:self])
call l:self.tree.provider.getChildren(l:Callback, l:self.object)
endif
for l:child in l:self.children
call add(l:lines, l:child.render(a:level + 1))
endfor
endif
return join(l:lines, "\n")
endfunction
" Insert a new node in the tree, internally represented by a unique progressive
" integer identifier {id}. The node represents a certain {object} (children of
" {parent}) belonging to a given {tree}, having an associated action to be
" triggered on execution defined by the function object {exec}. If {collapsed}
" is true the node will be rendered as collapsed in the view. If {lazy_open} is
" true, the children of the node will be fetched when the node is expanded by
" the user.
function! s:node_new(tree, object, tree_item, parent) abort
let a:tree.maxid += 1
return {
\ 'id': a:tree.maxid,
\ 'tree': a:tree,
\ 'object': a:object,
\ 'tree_item': a:tree_item,
\ 'parent': a:parent,
\ 'collapsed': a:tree_item.collapsibleState ==? 'collapsed',
\ 'lazy_open': a:tree_item.collapsibleState !=? 'none',
\ 'children': [],
\ 'level': function('s:node_level'),
\ 'exec': function('s:node_exec'),
\ 'set_collapsed': function('s:node_set_collapsed'),
\ 'render': function('s:node_render'),
\ }
endfunction
" Callback that sets the root node of a given {tree}, creating a new node
" with a {tree_item} representation for the given {object}. If {status} is
" equal to 'success', the root node is set and the tree view is updated
" accordingly, otherwise nothing happens.
function! s:tree_set_root_cb(tree, object, status, tree_item) abort
if a:status ==? 'success'
let a:tree.maxid = -1
let a:tree.root = s:node_new(a:tree, a:object, a:tree_item, {})
call s:tree_render(a:tree)
endif
endfunction
" Return the node currently under the cursor from the given {tree}.
function! s:get_node_under_cursor(tree) abort
let l:index = min([line('.'), len(a:tree.index) - 1])
return a:tree.index[l:index]
endfunction
" Expand or collapse the node under cursor, and render the tree.
" Please refer to *s:node_set_collapsed()* for details about the
" arguments and behaviour.
function! s:tree_set_collapsed_under_cursor(collapsed) dict abort
let l:node = s:get_node_under_cursor(l:self)
call l:node.set_collapsed(a:collapsed)
call s:tree_render(l:self)
endfunction
" Run the action associated to the node currently under the cursor.
function! s:tree_exec_node_under_cursor() dict abort
call s:get_node_under_cursor(l:self).exec()
endfunction
" Render the {tree}. This will replace the content of the buffer with the
" tree view. Clear the index, setting it to a list containing a guard
" value for index 0 (line numbers are one-based).
function! s:tree_render(tree) abort
if &filetype !=# 'lsp-tree'
return
endif
let l:cursor = getpos('.')
let a:tree.index = [-1]
let l:text = a:tree.root.render(0)
setlocal modifiable
silent 1,$delete _
silent 0put=l:text
$d
setlocal nomodifiable
call setpos('.', l:cursor)
endfunction
" If {status} equals 'success', update all nodes of {tree} representing
" an {obect} with given {tree_item} representation.
function! s:node_update(tree, object, status, tree_item) abort
if a:status !=? 'success'
return
endif
for l:node in s:search_subtree(a:tree.root, {n -> n.object == a:object})
let l:node.tree_item = a:tree_item
let l:node.children = []
let l:node.lazy_open = a:tree_item.collapsibleState !=? 'none'
endfor
call s:tree_render(a:tree)
endfunction
" Update the view if nodes have changed. If called with no arguments,
" update the whole tree. If called with an {object} as argument, update
" all the subtrees of nodes corresponding to {object}.
function! s:tree_update(...) dict abort
if a:0 < 1
call l:self.provider.getChildren({status, obj ->
\ l:self.provider.getTreeItem(function('s:tree_set_root_cb', [l:self, obj[0]]), obj[0])})
else
call l:self.provider.getTreeItem(function('s:node_update', [l:self, a:1]), a:1)
endif
endfunction
" Destroy the tree view. Wipe out the buffer containing it.
function! s:tree_wipe() dict abort
execute 'bwipeout' . l:self.bufnr
endfunction
" Apply syntax to an lsp-tree buffer
function! s:filetype_syntax() abort
syntax clear
syntax match LspTreeMarkLeaf "•" contained
syntax match LspTreeMarkCollapsed "▸" contained
syntax match LspTreeMarkExpanded "▾" contained
syntax match LspTreeNode "\v^(\s|[▸▾•])*.*"
\ contains=LspTreeMarkLeaf,LspTreeMarkCollapsed,LspTreeMarkExpanded
highlight def link LspTreeMarkLeaf Type
highlight def link LspTreeMarkExpanded Type
highlight def link LspTreeMarkCollapsed Macro
endfunction
" Apply local settings to an lsp-tree buffer
function! s:filetype_settings() abort
setlocal bufhidden=wipe
setlocal buftype=nofile
setlocal foldcolumn=0
setlocal foldmethod=manual
setlocal nobuflisted
setlocal nofoldenable
setlocal nohlsearch
setlocal nolist
setlocal nomodifiable
setlocal nonumber
setlocal nospell
setlocal noswapfile
setlocal nowrap
nnoremap <silent> <buffer> <Plug>(lsp-tree-toggle-node)
\ :call b:lsp_tree.set_collapsed_under_cursor(-1)<cr>
nnoremap <silent> <buffer> <Plug>(lsp-tree-open-node)
\ :call b:lsp_tree.set_collapsed_under_cursor(v:false)<cr>
nnoremap <silent> <buffer> <Plug>(lsp-tree-close-node)
\ :call b:lsp_tree.set_collapsed_under_cursor(v:true)<cr>
nnoremap <silent> <buffer> <Plug>(lsp-tree-execute-node)
\ :call b:lsp_tree.exec_node_under_cursor()<cr>
nnoremap <silent> <buffer> <Plug>(lsp-tree-wipe-tree)
\ :call b:lsp_tree.wipe()<cr>
if !exists('g:lsp_tree_no_default_maps')
nmap <silent> <buffer> o <Plug>(lsp-tree-toggle-node)
nmap <silent> <buffer> <cr> <Plug>(lsp-tree-execute-node)
nmap <silent> <buffer> q <Plug>(lsp-tree-wipe-tree)
endif
endfunction
" Turns the current buffer into an lsp-tree tree view. Tree data is retrieved
" from the given {provider}, and the state of the tree is stored in a
" buffer-local variable called b:lsp_tree.
"
" The {bufnr} stores the buffer number of the view, {maxid} is the highest
" known internal identifier of the nodes. The {index} is a list that
" maps line numbers to nodes.
function! lsp#utils#tree#new(provider) abort
let b:lsp_tree = {
\ 'bufnr': bufnr('%'),
\ 'maxid': -1,
\ 'root': {},
\ 'index': [],
\ 'provider': a:provider,
\ 'set_collapsed_under_cursor': function('s:tree_set_collapsed_under_cursor'),
\ 'exec_node_under_cursor': function('s:tree_exec_node_under_cursor'),
\ 'update': function('s:tree_update'),
\ 'wipe': function('s:tree_wipe'),
\ }
augroup vim_lsp_tree
autocmd!
autocmd FileType lsp-tree call s:filetype_syntax() | call s:filetype_settings()
autocmd BufEnter <buffer> call s:tree_render(b:lsp_tree)
augroup END
setlocal filetype=lsp-tree
call b:lsp_tree.update()
endfunction

View File

@@ -0,0 +1,14 @@
function! lsp#utils#workspace_config#get_value(server_name, item) abort
try
let l:server_info = lsp#get_server_info(a:server_name)
let l:config = l:server_info['workspace_config']
for l:section in split(a:item['section'], '\.')
let l:config = l:config[l:section]
endfor
return l:config
catch
return v:null
endtry
endfunction

View File

@@ -0,0 +1,27 @@
" Applies WorkspaceEdit changes.
function! lsp#utils#workspace_edit#apply_workspace_edit(workspace_edit) abort
let l:loclist_items = []
if has_key(a:workspace_edit, 'documentChanges')
for l:text_document_edit in a:workspace_edit['documentChanges']
let l:loclist_items += s:_apply(l:text_document_edit['textDocument']['uri'], l:text_document_edit['edits'])
endfor
elseif has_key(a:workspace_edit, 'changes')
for [l:uri, l:text_edits] in items(a:workspace_edit['changes'])
let l:loclist_items += s:_apply(l:uri, l:text_edits)
endfor
endif
if g:lsp_show_workspace_edits
call setloclist(0, l:loclist_items, 'r')
execute 'lopen'
endif
endfunction
"
" _apply
"
function! s:_apply(uri, text_edits) abort
call lsp#utils#text_edit#apply_text_edits(a:uri, a:text_edits)
return lsp#utils#text_edit#_lsp_to_vim_list(a:uri, a:text_edits)
endfunction