I broke up with neovim....vim is my best friend now
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
" https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
|
||||
let s:enabled = 0
|
||||
|
||||
let s:Markdown = vital#lsp#import('VS.Vim.Syntax.Markdown')
|
||||
let s:MarkupContent = vital#lsp#import('VS.LSP.MarkupContent')
|
||||
let s:FloatingWindow = vital#lsp#import('VS.Vim.Window.FloatingWindow')
|
||||
let s:Window = vital#lsp#import('VS.Vim.Window')
|
||||
let s:Buffer = vital#lsp#import('VS.Vim.Buffer')
|
||||
|
||||
function! lsp#internal#completion#documentation#_enable() abort
|
||||
" don't even bother registering if the feature is disabled
|
||||
if !g:lsp_completion_documentation_enabled | return | endif
|
||||
|
||||
if !s:FloatingWindow.is_available() | return | endif
|
||||
if !exists('##CompleteChanged') | return | endif
|
||||
|
||||
if s:enabled | return | endif
|
||||
let s:enabled = 1
|
||||
|
||||
let s:Dispose = lsp#callbag#pipe(
|
||||
\ lsp#callbag#merge(
|
||||
\ lsp#callbag#pipe(
|
||||
\ lsp#callbag#fromEvent('CompleteChanged'),
|
||||
\ lsp#callbag#filter({_->g:lsp_completion_documentation_enabled}),
|
||||
\ lsp#callbag#map({->copy(v:event)}),
|
||||
\ lsp#callbag#debounceTime(g:lsp_completion_documentation_delay),
|
||||
\ lsp#callbag#switchMap({event->
|
||||
\ lsp#callbag#pipe(
|
||||
\ s:resolve_completion(event),
|
||||
\ lsp#callbag#tap({managed_user_data->s:show_floating_window(event, managed_user_data)}),
|
||||
\ lsp#callbag#takeUntil(lsp#callbag#fromEvent('CompleteDone'))
|
||||
\ )
|
||||
\ })
|
||||
\ ),
|
||||
\ lsp#callbag#pipe(
|
||||
\ lsp#callbag#fromEvent('CompleteDone'),
|
||||
\ lsp#callbag#tap({_->s:close_floating_window(v:false)}),
|
||||
\ )
|
||||
\ ),
|
||||
\ lsp#callbag#subscribe(),
|
||||
\ )
|
||||
endfunction
|
||||
|
||||
function! s:resolve_completion(event) abort
|
||||
let l:managed_user_data = lsp#omni#get_managed_user_data_from_completed_item(a:event['completed_item'])
|
||||
if empty(l:managed_user_data)
|
||||
return lsp#callbag#of({})
|
||||
endif
|
||||
|
||||
let l:completion_item = l:managed_user_data['completion_item']
|
||||
|
||||
if has_key(l:completion_item, 'documentation')
|
||||
return lsp#callbag#of(l:managed_user_data)
|
||||
elseif lsp#capabilities#has_completion_resolve_provider(l:managed_user_data['server_name'])
|
||||
return lsp#callbag#pipe(
|
||||
\ lsp#request(l:managed_user_data['server_name'], {
|
||||
\ 'method': 'completionItem/resolve',
|
||||
\ 'params': l:completion_item,
|
||||
\ }),
|
||||
\ lsp#callbag#map({x->{
|
||||
\ 'server_name': l:managed_user_data['server_name'],
|
||||
\ 'completion_item': x['response']['result'],
|
||||
\ 'complete_position': l:managed_user_data['complete_position'],
|
||||
\ }})
|
||||
\ )
|
||||
else
|
||||
return lsp#callbag#of(l:managed_user_data)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:show_floating_window(event, managed_user_data) abort
|
||||
if empty(a:managed_user_data) || !pumvisible()
|
||||
call s:close_floating_window(v:true)
|
||||
return
|
||||
endif
|
||||
let l:completion_item = a:managed_user_data['completion_item']
|
||||
|
||||
let l:contents = []
|
||||
|
||||
" Add detail field if provided.
|
||||
if type(get(l:completion_item, 'detail', v:null)) == type('')
|
||||
if !empty(l:completion_item.detail)
|
||||
let l:detail = s:MarkupContent.normalize({
|
||||
\ 'language': &filetype,
|
||||
\ 'value': l:completion_item['detail'],
|
||||
\ }, {
|
||||
\ 'compact': !g:lsp_preview_fixup_conceal
|
||||
\ })
|
||||
let l:contents += [l:detail]
|
||||
endif
|
||||
endif
|
||||
|
||||
" Add documentation filed if provided.
|
||||
let l:documentation = s:MarkupContent.normalize(get(l:completion_item, 'documentation', ''), {
|
||||
\ 'compact': !g:lsp_preview_fixup_conceal
|
||||
\ })
|
||||
if !empty(l:documentation)
|
||||
let l:contents += [l:documentation]
|
||||
endif
|
||||
|
||||
" Ignore if contents is empty.
|
||||
if empty(l:contents)
|
||||
return s:close_floating_window(v:true)
|
||||
endif
|
||||
|
||||
" Update contents.
|
||||
let l:doc_win = s:get_doc_win()
|
||||
call deletebufline(l:doc_win.get_bufnr(), 1, '$')
|
||||
call setbufline(l:doc_win.get_bufnr(), 1, lsp#utils#_split_by_eol(join(l:contents, "\n\n")))
|
||||
|
||||
" Calculate layout.
|
||||
if g:lsp_float_max_width >= 1
|
||||
let l:maxwidth = g:lsp_float_max_width
|
||||
elseif g:lsp_float_max_width == 0
|
||||
let l:maxwidth = &columns
|
||||
else
|
||||
let l:maxwidth = float2nr(&columns * 0.4)
|
||||
endif
|
||||
let l:size = l:doc_win.get_size({
|
||||
\ 'maxwidth': l:maxwidth,
|
||||
\ 'maxheight': float2nr(&lines * 0.4),
|
||||
\ })
|
||||
let l:margin_right = &columns - 1 - (float2nr(a:event.col) + float2nr(a:event.width) + 1 + (a:event.scrollbar ? 1 : 0))
|
||||
let l:margin_left = float2nr(a:event.col) - 3
|
||||
if l:size.width < l:margin_right
|
||||
" do nothing
|
||||
elseif l:margin_left <= l:margin_right
|
||||
let l:size.width = l:margin_right
|
||||
else
|
||||
let l:size.width = l:margin_left
|
||||
endif
|
||||
let l:pos = s:compute_position(a:event, l:size)
|
||||
if empty(l:pos)
|
||||
call s:close_floating_window(v:true)
|
||||
return
|
||||
endif
|
||||
|
||||
" Show popupmenu and apply markdown syntax.
|
||||
call l:doc_win.open({
|
||||
\ 'row': l:pos[0] + 1,
|
||||
\ 'col': l:pos[1] + 1,
|
||||
\ 'width': l:size.width,
|
||||
\ 'height': l:size.height,
|
||||
\ 'border': v:true,
|
||||
\ 'topline': 1,
|
||||
\ })
|
||||
call s:Window.do(l:doc_win.get_winid(), { -> s:Markdown.apply() })
|
||||
endfunction
|
||||
|
||||
function! s:close_floating_window(force) abort
|
||||
" Ignore `CompleteDone` if it occurred by `complete()` because in this case, the popup menu will re-appear immediately.
|
||||
let l:ctx = {}
|
||||
function! l:ctx.callback(force) abort
|
||||
if !pumvisible() || a:force
|
||||
call s:get_doc_win().close()
|
||||
endif
|
||||
endfunction
|
||||
call timer_start(1, { -> l:ctx.callback(a:force) })
|
||||
endfunction
|
||||
|
||||
function! s:compute_position(event, size) abort
|
||||
let l:col_if_right = a:event.col + a:event.width + 1 + (a:event.scrollbar ? 1 : 0)
|
||||
let l:col_if_left = a:event.col - a:size.width - 2
|
||||
|
||||
if a:size.width >= (&columns - l:col_if_right)
|
||||
let l:col = l:col_if_left
|
||||
else
|
||||
let l:col = l:col_if_right
|
||||
endif
|
||||
|
||||
if l:col <= 0
|
||||
return []
|
||||
endif
|
||||
if &columns <= l:col + a:size.width
|
||||
return []
|
||||
endif
|
||||
|
||||
return [a:event.row, l:col]
|
||||
endfunction
|
||||
|
||||
function! s:get_doc_win() abort
|
||||
if exists('s:doc_win')
|
||||
return s:doc_win
|
||||
endif
|
||||
|
||||
let s:doc_win = s:FloatingWindow.new({
|
||||
\ 'on_opened': { -> execute('doautocmd <nomodeline> User lsp_float_opened') },
|
||||
\ 'on_closed': { -> execute('doautocmd <nomodeline> User lsp_float_closed') }
|
||||
\ })
|
||||
call s:doc_win.set_var('&wrap', 1)
|
||||
call s:doc_win.set_var('&conceallevel', 2)
|
||||
noautocmd silent let l:bufnr = s:Buffer.create()
|
||||
call s:doc_win.set_bufnr(l:bufnr)
|
||||
call setbufvar(s:doc_win.get_bufnr(), '&buftype', 'nofile')
|
||||
call setbufvar(s:doc_win.get_bufnr(), '&bufhidden', 'hide')
|
||||
call setbufvar(s:doc_win.get_bufnr(), '&buflisted', 0)
|
||||
call setbufvar(s:doc_win.get_bufnr(), '&swapfile', 0)
|
||||
return s:doc_win
|
||||
endfunction
|
||||
|
||||
function! lsp#internal#completion#documentation#_disable() abort
|
||||
if !s:enabled | return | endif
|
||||
if exists('s:Dispose')
|
||||
call s:Dispose()
|
||||
unlet s:Dispose
|
||||
endif
|
||||
endfunction
|
||||
@@ -0,0 +1,20 @@
|
||||
function! lsp#internal#diagnostics#_enable() abort
|
||||
" don't even bother registering if the feature is disabled
|
||||
if !g:lsp_diagnostics_enabled | return | endif
|
||||
|
||||
call lsp#internal#diagnostics#state#_enable() " Needs to be the first one to register
|
||||
call lsp#internal#diagnostics#echo#_enable()
|
||||
call lsp#internal#diagnostics#highlights#_enable()
|
||||
call lsp#internal#diagnostics#float#_enable()
|
||||
call lsp#internal#diagnostics#signs#_enable()
|
||||
call lsp#internal#diagnostics#virtual_text#_enable()
|
||||
endfunction
|
||||
|
||||
function! lsp#internal#diagnostics#_disable() abort
|
||||
call lsp#internal#diagnostics#echo#_disable()
|
||||
call lsp#internal#diagnostics#float#_disable()
|
||||
call lsp#internal#diagnostics#highlights#_disable()
|
||||
call lsp#internal#diagnostics#virtual_text#_disable()
|
||||
call lsp#internal#diagnostics#signs#_disable()
|
||||
call lsp#internal#diagnostics#state#_disable() " Needs to be the last one to unregister
|
||||
endfunction
|
||||
@@ -0,0 +1,40 @@
|
||||
" options = {
|
||||
" buffers: '1' " optional string, defaults to current buffer, '*' for all buffers
|
||||
" }
|
||||
function! lsp#internal#diagnostics#document_diagnostics_command#do(options) abort
|
||||
if !g:lsp_diagnostics_enabled
|
||||
call lsp#utils#error(':LspDocumentDiagnostics g:lsp_diagnostics_enabled must be enabled')
|
||||
return
|
||||
endif
|
||||
|
||||
let l:buffers = get(a:options, 'buffers', '')
|
||||
|
||||
let l:filtered_diagnostics = {}
|
||||
|
||||
if l:buffers ==# '*'
|
||||
let l:filtered_diagnostics = lsp#internal#diagnostics#state#_get_all_diagnostics_grouped_by_uri_and_server()
|
||||
else
|
||||
let l:uri = lsp#utils#get_buffer_uri()
|
||||
if !empty(l:uri)
|
||||
let l:filtered_diagnostics[l:uri] = lsp#internal#diagnostics#state#_get_all_diagnostics_grouped_by_server_for_uri(l:uri)
|
||||
endif
|
||||
endif
|
||||
|
||||
let l:result = []
|
||||
for [l:uri, l:value] in items(l:filtered_diagnostics)
|
||||
if lsp#internal#diagnostics#state#_is_enabled_for_buffer(bufnr(lsp#utils#uri_to_path(l:uri)))
|
||||
for l:diagnostics in values(l:value)
|
||||
let l:result += lsp#ui#vim#utils#diagnostics_to_loc_list({ 'response': l:diagnostics })
|
||||
endfor
|
||||
endif
|
||||
endfor
|
||||
|
||||
if empty(l:result)
|
||||
call lsp#utils#error('No diagnostics results')
|
||||
return
|
||||
else
|
||||
call setloclist(0, l:result)
|
||||
echo 'Retrieved diagnostics results'
|
||||
botright lopen
|
||||
endif
|
||||
endfunction
|
||||
@@ -0,0 +1,41 @@
|
||||
" internal state for whether it is enabled or not to avoid multiple subscriptions
|
||||
let s:enabled = 0
|
||||
|
||||
function! lsp#internal#diagnostics#echo#_enable() abort
|
||||
" don't even bother registering if the feature is disabled
|
||||
if !g:lsp_diagnostics_echo_cursor | return | endif
|
||||
|
||||
if s:enabled | return | endif
|
||||
let s:enabled = 1
|
||||
|
||||
let s:Dispose = lsp#callbag#pipe(
|
||||
\ lsp#callbag#fromEvent(['CursorMoved']),
|
||||
\ lsp#callbag#filter({_->g:lsp_diagnostics_echo_cursor}),
|
||||
\ lsp#callbag#debounceTime(g:lsp_diagnostics_echo_delay),
|
||||
\ lsp#callbag#map({_->{'bufnr': bufnr('%'), 'curpos': getcurpos()[0:2], 'changedtick': b:changedtick }}),
|
||||
\ lsp#callbag#distinctUntilChanged({a,b -> a['bufnr'] == b['bufnr'] && a['curpos'] == b['curpos'] && a['changedtick'] == b['changedtick']}),
|
||||
\ lsp#callbag#filter({_->mode() is# 'n'}),
|
||||
\ lsp#callbag#filter({_->getbufvar(bufnr('%'), '&buftype') !=# 'terminal' }),
|
||||
\ lsp#callbag#map({_->lsp#internal#diagnostics#under_cursor#get_diagnostic()}),
|
||||
\ lsp#callbag#subscribe({x->s:echo(x)}),
|
||||
\ )
|
||||
endfunction
|
||||
|
||||
function! lsp#internal#diagnostics#echo#_disable() abort
|
||||
if !s:enabled | return | endif
|
||||
if exists('s:Dispose')
|
||||
call s:Dispose()
|
||||
unlet s:Dispose
|
||||
endif
|
||||
let s:enabled = 0
|
||||
endfunction
|
||||
|
||||
function! s:echo(diagnostic) abort
|
||||
if !empty(a:diagnostic) && has_key(a:diagnostic, 'message')
|
||||
call lsp#utils#echo_with_truncation('LSP: '. substitute(a:diagnostic['message'], '\n\+', ' ', 'g'))
|
||||
let s:displaying_message = 1
|
||||
elseif get(s:, 'displaying_message', 0)
|
||||
call lsp#utils#echo_with_truncation('')
|
||||
let s:displaying_message = 0
|
||||
endif
|
||||
endfunction
|
||||
@@ -0,0 +1,26 @@
|
||||
" Return first error line or v:null if there are no errors
|
||||
" available.
|
||||
" options = {
|
||||
" 'bufnr': '', " optional
|
||||
" }
|
||||
function! lsp#internal#diagnostics#first_line#get_first_error_line(options) abort
|
||||
let l:bufnr = get(a:options, 'bufnr', bufnr('%'))
|
||||
|
||||
if !lsp#internal#diagnostics#state#_is_enabled_for_buffer(l:bufnr)
|
||||
return v:null
|
||||
endif
|
||||
|
||||
let l:uri = lsp#utils#get_buffer_uri(l:bufnr)
|
||||
let l:diagnostics_by_server = lsp#internal#diagnostics#state#_get_all_diagnostics_grouped_by_server_for_uri(l:uri)
|
||||
|
||||
let l:first_error_line = v:null
|
||||
for l:diagnostics_response in values(l:diagnostics_by_server)
|
||||
for l:item in lsp#utils#iteratable(l:diagnostics_response['params']['diagnostics'])
|
||||
let l:severity = get(l:item, 'severity', 1)
|
||||
if l:severity ==# 1 && (l:first_error_line ==# v:null || l:first_error_line ># l:item['range']['start']['line'])
|
||||
let l:first_error_line = l:item['range']['start']['line']
|
||||
endif
|
||||
endfor
|
||||
endfor
|
||||
return l:first_error_line ==# v:null ? v:null : l:first_error_line + 1
|
||||
endfunction
|
||||
@@ -0,0 +1,123 @@
|
||||
" internal state for whether it is enabled or not to avoid multiple subscriptions
|
||||
let s:enabled = 0
|
||||
|
||||
let s:Markdown = vital#lsp#import('VS.Vim.Syntax.Markdown')
|
||||
let s:MarkupContent = vital#lsp#import('VS.LSP.MarkupContent')
|
||||
let s:FloatingWindow = vital#lsp#import('VS.Vim.Window.FloatingWindow')
|
||||
let s:Window = vital#lsp#import('VS.Vim.Window')
|
||||
let s:Buffer = vital#lsp#import('VS.Vim.Buffer')
|
||||
|
||||
function! lsp#internal#diagnostics#float#_enable() abort
|
||||
" don't even bother registering if the feature is disabled
|
||||
if !lsp#ui#vim#output#float_supported() | return | endif
|
||||
if !g:lsp_diagnostics_float_cursor | return | endif
|
||||
|
||||
if s:enabled | return | endif
|
||||
let s:enabled = 1
|
||||
|
||||
let s:Dispose = lsp#callbag#pipe(
|
||||
\ lsp#callbag#merge(
|
||||
\ lsp#callbag#fromEvent(['CursorMoved', 'CursorHold']),
|
||||
\ lsp#callbag#pipe(
|
||||
\ lsp#callbag#fromEvent(['InsertEnter']),
|
||||
\ lsp#callbag#filter({_->!g:lsp_diagnostics_float_insert_mode_enabled}),
|
||||
\ lsp#callbag#tap({_->s:hide_float()}),
|
||||
\ )
|
||||
\ ),
|
||||
\ lsp#callbag#filter({_->g:lsp_diagnostics_float_cursor}),
|
||||
\ lsp#callbag#tap({_->s:hide_float()}),
|
||||
\ lsp#callbag#debounceTime(g:lsp_diagnostics_float_delay),
|
||||
\ lsp#callbag#map({_->{'bufnr': bufnr('%'), 'curpos': getcurpos()[0:2], 'changedtick': b:changedtick }}),
|
||||
\ lsp#callbag#distinctUntilChanged({a,b -> a['bufnr'] == b['bufnr'] && a['curpos'] == b['curpos'] && a['changedtick'] == b['changedtick']}),
|
||||
\ lsp#callbag#filter({_->mode() is# 'n'}),
|
||||
\ lsp#callbag#filter({_->getbufvar(bufnr('%'), '&buftype') !=# 'terminal' }),
|
||||
\ lsp#callbag#map({_->lsp#internal#diagnostics#under_cursor#get_diagnostic()}),
|
||||
\ lsp#callbag#subscribe({x->s:show_float(x)}),
|
||||
\ )
|
||||
endfunction
|
||||
|
||||
function! lsp#internal#diagnostics#float#_disable() abort
|
||||
if !s:enabled | return | endif
|
||||
if exists('s:Dispose')
|
||||
call s:Dispose()
|
||||
unlet s:Dispose
|
||||
endif
|
||||
let s:enabled = 0
|
||||
endfunction
|
||||
|
||||
function! s:show_float(diagnostic) abort
|
||||
let l:doc_win = s:get_doc_win()
|
||||
if !empty(a:diagnostic) && has_key(a:diagnostic, 'message')
|
||||
" Update contents.
|
||||
call deletebufline(l:doc_win.get_bufnr(), 1, '$')
|
||||
call setbufline(l:doc_win.get_bufnr(), 1, lsp#utils#_split_by_eol(a:diagnostic['message']))
|
||||
|
||||
" Compute size.
|
||||
if g:lsp_float_max_width >= 1
|
||||
let l:maxwidth = g:lsp_float_max_width
|
||||
elseif g:lsp_float_max_width == 0
|
||||
let l:maxwidth = &columns
|
||||
else
|
||||
let l:maxwidth = float2nr(&columns * 0.4)
|
||||
endif
|
||||
let l:size = l:doc_win.get_size({
|
||||
\ 'maxwidth': l:maxwidth,
|
||||
\ 'maxheight': float2nr(&lines * 0.4),
|
||||
\ })
|
||||
|
||||
" Compute position.
|
||||
let l:pos = s:compute_position(l:size)
|
||||
|
||||
" Open window.
|
||||
call l:doc_win.open({
|
||||
\ 'row': l:pos[0],
|
||||
\ 'col': l:pos[1],
|
||||
\ 'width': l:size.width,
|
||||
\ 'height': l:size.height,
|
||||
\ 'border': v:true,
|
||||
\ 'topline': 1,
|
||||
\ })
|
||||
else
|
||||
call s:hide_float()
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:hide_float() abort
|
||||
let l:doc_win = s:get_doc_win()
|
||||
call l:doc_win.close()
|
||||
endfunction
|
||||
|
||||
function! s:get_doc_win() abort
|
||||
if exists('s:doc_win')
|
||||
return s:doc_win
|
||||
endif
|
||||
|
||||
let s:doc_win = s:FloatingWindow.new({
|
||||
\ 'on_opened': { -> execute('doautocmd <nomodeline> User lsp_float_opened') },
|
||||
\ 'on_closed': { -> execute('doautocmd <nomodeline> User lsp_float_closed') }
|
||||
\ })
|
||||
call s:doc_win.set_var('&wrap', 1)
|
||||
call s:doc_win.set_var('&conceallevel', 2)
|
||||
noautocmd silent let l:bufnr = s:Buffer.create()
|
||||
call s:doc_win.set_bufnr(l:bufnr)
|
||||
call setbufvar(s:doc_win.get_bufnr(), '&buftype', 'nofile')
|
||||
call setbufvar(s:doc_win.get_bufnr(), '&bufhidden', 'hide')
|
||||
call setbufvar(s:doc_win.get_bufnr(), '&buflisted', 0)
|
||||
call setbufvar(s:doc_win.get_bufnr(), '&swapfile', 0)
|
||||
return s:doc_win
|
||||
endfunction
|
||||
|
||||
function! s:compute_position(size) abort
|
||||
let l:pos = screenpos(0, line('.'), col('.'))
|
||||
if l:pos.row == 0 && l:pos.col == 0
|
||||
let l:pos = {'curscol': screencol(), 'row': screenrow()}
|
||||
endif
|
||||
let l:pos = [l:pos.row + 1, l:pos.curscol + 1]
|
||||
if l:pos[0] + a:size.height > &lines
|
||||
let l:pos[0] = l:pos[0] - a:size.height - 3
|
||||
endif
|
||||
if l:pos[1] + a:size.width > &columns
|
||||
let l:pos[1] = l:pos[1] - a:size.width - 3
|
||||
endif
|
||||
return l:pos
|
||||
endfunction
|
||||
@@ -0,0 +1,207 @@
|
||||
" internal state for whether it is enabled or not to avoid multiple subscriptions
|
||||
let s:enabled = 0
|
||||
let s:namespace_id = '' " will be set when enabled
|
||||
|
||||
let s:severity_sign_names_mapping = {
|
||||
\ 1: 'LspError',
|
||||
\ 2: 'LspWarning',
|
||||
\ 3: 'LspInformation',
|
||||
\ 4: 'LspHint',
|
||||
\ }
|
||||
|
||||
if !hlexists('LspErrorHighlight')
|
||||
highlight link LspErrorHighlight Error
|
||||
endif
|
||||
|
||||
if !hlexists('LspWarningHighlight')
|
||||
highlight link LspWarningHighlight Todo
|
||||
endif
|
||||
|
||||
if !hlexists('LspInformationHighlight')
|
||||
highlight link LspInformationHighlight Normal
|
||||
endif
|
||||
|
||||
if !hlexists('LspHintHighlight')
|
||||
highlight link LspHintHighlight Normal
|
||||
endif
|
||||
|
||||
function! lsp#internal#diagnostics#highlights#_enable() abort
|
||||
" don't even bother registering if the feature is disabled
|
||||
if !lsp#utils#_has_highlights() | return | endif
|
||||
if !g:lsp_diagnostics_highlights_enabled | return | endif
|
||||
|
||||
if s:enabled | return | endif
|
||||
let s:enabled = 1
|
||||
|
||||
if empty(s:namespace_id)
|
||||
if has('nvim')
|
||||
let s:namespace_id = nvim_create_namespace('vim_lsp_diagnostics_highlights')
|
||||
else
|
||||
let s:namespace_id = 'vim_lsp_diagnostics_highlights'
|
||||
for l:severity in keys(s:severity_sign_names_mapping)
|
||||
let l:hl_group = s:severity_sign_names_mapping[l:severity] . 'Highlight'
|
||||
call prop_type_add(s:get_prop_type_name(l:severity),
|
||||
\ {'highlight': l:hl_group, 'combine': v:true, 'priority': lsp#internal#textprop#priority('diagnostics_highlight') })
|
||||
endfor
|
||||
endif
|
||||
endif
|
||||
|
||||
let s:Dispose = lsp#callbag#pipe(
|
||||
\ lsp#callbag#merge(
|
||||
\ lsp#callbag#pipe(
|
||||
\ lsp#stream(),
|
||||
\ lsp#callbag#filter({x->has_key(x, 'server') && has_key(x, 'response')
|
||||
\ && has_key(x['response'], 'method') && x['response']['method'] ==# '$/vimlsp/lsp_diagnostics_updated'
|
||||
\ && !lsp#client#is_error(x['response'])}),
|
||||
\ lsp#callbag#map({x->x['response']['params']}),
|
||||
\ ),
|
||||
\ lsp#callbag#pipe(
|
||||
\ lsp#callbag#fromEvent(['InsertEnter', 'InsertLeave']),
|
||||
\ lsp#callbag#filter({_->!g:lsp_diagnostics_highlights_insert_mode_enabled}),
|
||||
\ lsp#callbag#map({_->{ 'uri': lsp#utils#get_buffer_uri() }}),
|
||||
\ ),
|
||||
\ ),
|
||||
\ lsp#callbag#filter({_->g:lsp_diagnostics_highlights_enabled}),
|
||||
\ lsp#callbag#debounceTime(g:lsp_diagnostics_highlights_delay),
|
||||
\ lsp#callbag#tap({x->s:clear_highlights(x)}),
|
||||
\ lsp#callbag#tap({x->s:set_highlights(x)}),
|
||||
\ lsp#callbag#subscribe(),
|
||||
\ )
|
||||
endfunction
|
||||
|
||||
function! lsp#internal#diagnostics#highlights#_disable() abort
|
||||
if !s:enabled | return | endif
|
||||
if exists('s:Dispose')
|
||||
call s:Dispose()
|
||||
unlet s:Dispose
|
||||
endif
|
||||
call s:clear_all_highlights()
|
||||
let s:enabled = 0
|
||||
endfunction
|
||||
|
||||
function! s:get_prop_type_name(severity) abort
|
||||
return s:namespace_id . '_' . get(s:severity_sign_names_mapping, a:severity, 'LspError')
|
||||
endfunction
|
||||
|
||||
function! s:clear_all_highlights() abort
|
||||
for l:bufnr in range(1, bufnr('$'))
|
||||
if bufexists(l:bufnr) && bufloaded(l:bufnr)
|
||||
if has('nvim')
|
||||
call nvim_buf_clear_namespace(l:bufnr, s:namespace_id, 0, -1)
|
||||
else
|
||||
for l:severity in keys(s:severity_sign_names_mapping)
|
||||
try
|
||||
" TODO: need to check for valid range before calling prop_add
|
||||
" See https://github.com/prabirshrestha/vim-lsp/pull/721
|
||||
silent! call prop_remove({
|
||||
\ 'type': s:get_prop_type_name(l:severity),
|
||||
\ 'bufnr': l:bufnr,
|
||||
\ 'all': v:true })
|
||||
catch
|
||||
call lsp#log('diagnostics', 'clear_all_highlights', 'prop_remove', v:exception, v:throwpoint)
|
||||
endtry
|
||||
endfor
|
||||
endif
|
||||
endif
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! s:clear_highlights(params) abort
|
||||
" TODO: optimize by looking at params
|
||||
call s:clear_all_highlights()
|
||||
endfunction
|
||||
|
||||
function! s:set_highlights(params) abort
|
||||
" TODO: optimize by looking at params
|
||||
if !g:lsp_diagnostics_highlights_insert_mode_enabled
|
||||
if mode()[0] ==# 'i' | return | endif
|
||||
endif
|
||||
|
||||
for l:bufnr in range(1, bufnr('$'))
|
||||
if lsp#internal#diagnostics#state#_is_enabled_for_buffer(l:bufnr) && bufexists(l:bufnr) && bufloaded(l:bufnr)
|
||||
let l:uri = lsp#utils#get_buffer_uri(l:bufnr)
|
||||
for [l:server, l:diagnostics_response] in items(lsp#internal#diagnostics#state#_get_all_diagnostics_grouped_by_server_for_uri(l:uri))
|
||||
call s:place_highlights(l:server, l:diagnostics_response, l:bufnr)
|
||||
endfor
|
||||
endif
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! s:place_highlights(server, diagnostics_response, bufnr) abort
|
||||
" TODO: make diagnostics highlights same across vim and neovim
|
||||
for l:item in lsp#utils#iteratable(a:diagnostics_response['params']['diagnostics'])
|
||||
let [l:start_line, l:start_col] = lsp#utils#position#lsp_to_vim(a:bufnr, l:item['range']['start'])
|
||||
let [l:end_line, l:end_col] = lsp#utils#position#lsp_to_vim(a:bufnr, l:item['range']['end'])
|
||||
let l:severity = get(l:item, 'severity', 3)
|
||||
let l:hl_group = get(s:severity_sign_names_mapping, l:severity, 'LspError') . 'Highlight'
|
||||
if has('nvim')
|
||||
for l:line in range(l:start_line, l:end_line)
|
||||
if l:line == l:start_line
|
||||
let l:highlight_start_col = l:start_col
|
||||
else
|
||||
let l:highlight_start_col = 1
|
||||
endif
|
||||
|
||||
if l:line == l:end_line
|
||||
let l:highlight_end_col = l:end_col
|
||||
else
|
||||
" neovim treats -1 as end of line, special handle it later
|
||||
" when calling nvim_buf_add_higlight
|
||||
let l:highlight_end_col = -1
|
||||
endif
|
||||
|
||||
if l:start_line == l:end_line && l:highlight_start_col == l:highlight_end_col
|
||||
" higlighting same start col and end col on same line
|
||||
" doesn't work so use -1 for start col
|
||||
let l:highlight_start_col -= 1
|
||||
if l:highlight_start_col <= 0
|
||||
let l:highlight_start_col = 1
|
||||
endif
|
||||
endif
|
||||
|
||||
call nvim_buf_add_highlight(a:bufnr, s:namespace_id, l:hl_group,
|
||||
\ l:line - 1, l:highlight_start_col - 1, l:highlight_end_col == -1 ? -1 : l:highlight_end_col)
|
||||
endfor
|
||||
else
|
||||
if l:start_line == l:end_line
|
||||
try
|
||||
" TODO: need to check for valid range before calling prop_add
|
||||
" See https://github.com/prabirshrestha/vim-lsp/pull/721
|
||||
silent! call prop_add(l:start_line, l:start_col, {
|
||||
\ 'end_col': l:end_col,
|
||||
\ 'bufnr': a:bufnr,
|
||||
\ 'type': s:get_prop_type_name(l:severity),
|
||||
\ })
|
||||
catch
|
||||
call lsp#log('diagnostics', 'place_highlights', 'prop_add', v:exception, v:throwpoint)
|
||||
endtry
|
||||
else
|
||||
for l:line in range(l:start_line, l:end_line)
|
||||
if l:line == l:start_line
|
||||
let l:highlight_start_col = l:start_col
|
||||
else
|
||||
let l:highlight_start_col = 1
|
||||
endif
|
||||
|
||||
if l:line == l:end_line
|
||||
let l:highlight_end_col = l:end_col
|
||||
else
|
||||
let l:highlight_end_col = strlen(getbufline(a:bufnr, l:line, l:line)[0]) + 1
|
||||
endif
|
||||
|
||||
try
|
||||
" TODO: need to check for valid range before calling prop_add
|
||||
" See https://github.com/prabirshrestha/vim-lsp/pull/721
|
||||
silent! call prop_add(l:line, l:highlight_start_col, {
|
||||
\ 'end_col': l:highlight_end_col,
|
||||
\ 'bufnr': a:bufnr,
|
||||
\ 'type': s:get_prop_type_name(l:severity),
|
||||
\ })
|
||||
catch
|
||||
call lsp#log('diagnostics', 'place_highlights', 'prop_add', v:exception, v:throwpoint)
|
||||
endtry
|
||||
endfor
|
||||
endif
|
||||
endif
|
||||
endfor
|
||||
endfunction
|
||||
@@ -0,0 +1,199 @@
|
||||
function! s:severity_of(diagnostic) abort
|
||||
return get(a:diagnostic, 'severity', 1)
|
||||
endfunction
|
||||
|
||||
function! lsp#internal#diagnostics#movement#_next_error(...) abort
|
||||
let l:diagnostics = filter(s:get_all_buffer_diagnostics(),
|
||||
\ {_, diagnostic -> s:severity_of(diagnostic) ==# 1 })
|
||||
let l:options = lsp#utils#parse_command_options(a:000)
|
||||
call s:next_diagnostic(l:diagnostics, l:options)
|
||||
endfunction
|
||||
|
||||
function! lsp#internal#diagnostics#movement#_next_warning(...) abort
|
||||
let l:diagnostics = filter(s:get_all_buffer_diagnostics(),
|
||||
\ {_, diagnostic -> s:severity_of(diagnostic) ==# 2 })
|
||||
let l:options = lsp#utils#parse_command_options(a:000)
|
||||
call s:next_diagnostic(l:diagnostics, l:options)
|
||||
endfunction
|
||||
|
||||
function! lsp#internal#diagnostics#movement#_next_diagnostics(...) abort
|
||||
let l:options = lsp#utils#parse_command_options(a:000)
|
||||
call s:next_diagnostic(s:get_all_buffer_diagnostics(), l:options)
|
||||
endfunction
|
||||
|
||||
function! s:next_diagnostic(diagnostics, options) abort
|
||||
if !len(a:diagnostics)
|
||||
return
|
||||
endif
|
||||
call sort(a:diagnostics, 's:compare_diagnostics')
|
||||
|
||||
let l:wrap = 1
|
||||
if has_key(a:options, 'wrap')
|
||||
let l:wrap = a:options['wrap']
|
||||
endif
|
||||
|
||||
let l:view = winsaveview()
|
||||
let l:next_line = 0
|
||||
let l:next_col = 0
|
||||
for l:diagnostic in a:diagnostics
|
||||
let [l:line, l:col] = lsp#utils#position#lsp_to_vim('%', l:diagnostic['range']['start'])
|
||||
if l:line > l:view['lnum']
|
||||
\ || (l:line == l:view['lnum'] && l:col > l:view['col'] + 1)
|
||||
let l:next_line = l:line
|
||||
let l:next_col = l:col - 1
|
||||
break
|
||||
endif
|
||||
endfor
|
||||
|
||||
if l:next_line == 0
|
||||
if !l:wrap
|
||||
return
|
||||
endif
|
||||
" Wrap to start
|
||||
let [l:next_line, l:next_col] = lsp#utils#position#lsp_to_vim('%', a:diagnostics[0]['range']['start'])
|
||||
let l:next_col -= 1
|
||||
endif
|
||||
|
||||
let l:view['lnum'] = l:next_line
|
||||
let l:view['col'] = l:next_col
|
||||
let l:view['topline'] = 1
|
||||
let l:height = winheight(0)
|
||||
let l:totalnum = line('$')
|
||||
if l:totalnum > l:height
|
||||
let l:half = l:height / 2
|
||||
if l:totalnum - l:half < l:view['lnum']
|
||||
let l:view['topline'] = l:totalnum - l:height + 1
|
||||
else
|
||||
let l:view['topline'] = l:view['lnum'] - l:half
|
||||
endif
|
||||
endif
|
||||
call winrestview(l:view)
|
||||
endfunction
|
||||
|
||||
function! lsp#internal#diagnostics#movement#_previous_error(...) abort
|
||||
let l:diagnostics = filter(s:get_all_buffer_diagnostics(),
|
||||
\ {_, diagnostic -> s:severity_of(diagnostic) ==# 1 })
|
||||
let l:options = lsp#utils#parse_command_options(a:000)
|
||||
call s:previous_diagnostic(l:diagnostics, l:options)
|
||||
endfunction
|
||||
|
||||
function! lsp#internal#diagnostics#movement#_previous_warning(...) abort
|
||||
let l:options = lsp#utils#parse_command_options(a:000)
|
||||
let l:diagnostics = filter(s:get_all_buffer_diagnostics(),
|
||||
\ {_, diagnostic -> s:severity_of(diagnostic) ==# 2 })
|
||||
call s:previous_diagnostic(l:diagnostics, l:options)
|
||||
endfunction
|
||||
|
||||
function! lsp#internal#diagnostics#movement#_previous_diagnostics(...) abort
|
||||
let l:options = lsp#utils#parse_command_options(a:000)
|
||||
call s:previous_diagnostic(s:get_all_buffer_diagnostics(), l:options)
|
||||
endfunction
|
||||
|
||||
function! s:previous_diagnostic(diagnostics, options) abort
|
||||
if !len(a:diagnostics)
|
||||
return
|
||||
endif
|
||||
call sort(a:diagnostics, 's:compare_diagnostics')
|
||||
|
||||
let l:wrap = 1
|
||||
if has_key(a:options, 'wrap')
|
||||
let l:wrap = a:options['wrap']
|
||||
endif
|
||||
|
||||
let l:view = winsaveview()
|
||||
let l:next_line = 0
|
||||
let l:next_col = 0
|
||||
let l:index = len(a:diagnostics) - 1
|
||||
while l:index >= 0
|
||||
let [l:line, l:col] = lsp#utils#position#lsp_to_vim('%', a:diagnostics[l:index]['range']['start'])
|
||||
if l:line < l:view['lnum']
|
||||
\ || (l:line == l:view['lnum'] && l:col < l:view['col'])
|
||||
let l:next_line = l:line
|
||||
let l:next_col = l:col - 1
|
||||
break
|
||||
endif
|
||||
let l:index = l:index - 1
|
||||
endwhile
|
||||
|
||||
if l:next_line == 0
|
||||
if !l:wrap
|
||||
return
|
||||
endif
|
||||
" Wrap to end
|
||||
let [l:next_line, l:next_col] = lsp#utils#position#lsp_to_vim('%', a:diagnostics[-1]['range']['start'])
|
||||
let l:next_col -= 1
|
||||
endif
|
||||
|
||||
let l:view['lnum'] = l:next_line
|
||||
let l:view['col'] = l:next_col
|
||||
let l:view['topline'] = 1
|
||||
let l:height = winheight(0)
|
||||
let l:totalnum = line('$')
|
||||
if l:totalnum > l:height
|
||||
let l:half = l:height / 2
|
||||
if l:totalnum - l:half < l:view['lnum']
|
||||
let l:view['topline'] = l:totalnum - l:height + 1
|
||||
else
|
||||
let l:view['topline'] = l:view['lnum'] - l:half
|
||||
endif
|
||||
endif
|
||||
call winrestview(l:view)
|
||||
endfunction
|
||||
|
||||
function! s:get_diagnostics(uri) abort
|
||||
if has_key(s:diagnostics, a:uri)
|
||||
return [1, s:diagnostics[a:uri]]
|
||||
else
|
||||
if s:is_win
|
||||
" vim in windows always uses upper case for drive letter, so use lowercase in case lang server uses lowercase
|
||||
" https://github.com/theia-ide/typescript-language-server/issues/23
|
||||
let l:uri = substitute(a:uri, '^' . a:uri[:8], tolower(a:uri[:8]), '')
|
||||
if has_key(s:diagnostics, l:uri)
|
||||
return [1, s:diagnostics[l:uri]]
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
return [0, {}]
|
||||
endfunction
|
||||
|
||||
" Get diagnostics for the current buffer URI from all servers
|
||||
function! s:get_all_buffer_diagnostics(...) abort
|
||||
let l:server = get(a:000, 0, '')
|
||||
|
||||
let l:bufnr = bufnr('%')
|
||||
let l:uri = lsp#utils#get_buffer_uri(l:bufnr)
|
||||
|
||||
if !lsp#internal#diagnostics#state#_is_enabled_for_buffer(l:bufnr)
|
||||
return []
|
||||
endif
|
||||
|
||||
let l:diagnostics_by_server = lsp#internal#diagnostics#state#_get_all_diagnostics_grouped_by_server_for_uri(l:uri)
|
||||
let l:diagnostics = []
|
||||
if empty(l:server)
|
||||
for l:item in values(l:diagnostics_by_server)
|
||||
let l:diagnostics += lsp#utils#iteratable(l:item['params']['diagnostics'])
|
||||
endfor
|
||||
else
|
||||
if has_key(l:diagnostics_by_server, l:server)
|
||||
let l:diagnostics = lsp#utils#iteratable(l:diagnostics_by_server[l:server]['params']['diagnostics'])
|
||||
endif
|
||||
endif
|
||||
|
||||
return l:diagnostics
|
||||
endfunction
|
||||
|
||||
function! s:compare_diagnostics(d1, d2) abort
|
||||
let l:range1 = a:d1['range']
|
||||
let l:line1 = l:range1['start']['line'] + 1
|
||||
let l:col1 = l:range1['start']['character'] + 1
|
||||
let l:range2 = a:d2['range']
|
||||
let l:line2 = l:range2['start']['line'] + 1
|
||||
let l:col2 = l:range2['start']['character'] + 1
|
||||
|
||||
if l:line1 == l:line2
|
||||
return l:col1 == l:col2 ? 0 : l:col1 > l:col2 ? 1 : -1
|
||||
else
|
||||
return l:line1 > l:line2 ? 1 : -1
|
||||
endif
|
||||
endfunction
|
||||
" vim sw=4 ts=4 et
|
||||
@@ -0,0 +1,151 @@
|
||||
" internal state for whether it is enabled or not to avoid multiple subscriptions
|
||||
let s:enabled = 0
|
||||
let s:sign_group = 'vim_lsp'
|
||||
|
||||
let s:severity_sign_names_mapping = {
|
||||
\ 1: 'LspError',
|
||||
\ 2: 'LspWarning',
|
||||
\ 3: 'LspInformation',
|
||||
\ 4: 'LspHint',
|
||||
\ }
|
||||
|
||||
if !hlexists('LspErrorText')
|
||||
highlight link LspErrorText Error
|
||||
endif
|
||||
|
||||
if !hlexists('LspWarningText')
|
||||
highlight link LspWarningText Todo
|
||||
endif
|
||||
|
||||
if !hlexists('LspInformationText')
|
||||
highlight link LspInformationText Normal
|
||||
endif
|
||||
|
||||
if !hlexists('LspHintText')
|
||||
highlight link LspHintText Normal
|
||||
endif
|
||||
|
||||
function! lsp#internal#diagnostics#signs#_enable() abort
|
||||
" don't even bother registering if the feature is disabled
|
||||
if !lsp#utils#_has_signs() | return | endif
|
||||
if !g:lsp_diagnostics_signs_enabled | return | endif
|
||||
|
||||
if s:enabled | return | endif
|
||||
let s:enabled = 1
|
||||
|
||||
call s:define_sign('LspError', 'E>', g:lsp_diagnostics_signs_error)
|
||||
call s:define_sign('LspWarning', 'W>', g:lsp_diagnostics_signs_warning)
|
||||
call s:define_sign('LspInformation', 'I>', g:lsp_diagnostics_signs_information)
|
||||
call s:define_sign('LspHint', 'H>', g:lsp_diagnostics_signs_hint)
|
||||
|
||||
let s:Dispose = lsp#callbag#pipe(
|
||||
\ lsp#callbag#merge(
|
||||
\ lsp#callbag#pipe(
|
||||
\ lsp#stream(),
|
||||
\ lsp#callbag#filter({x->has_key(x, 'server') && has_key(x, 'response')
|
||||
\ && has_key(x['response'], 'method') && x['response']['method'] ==# '$/vimlsp/lsp_diagnostics_updated'
|
||||
\ && !lsp#client#is_error(x['response'])}),
|
||||
\ lsp#callbag#map({x->x['response']['params']}),
|
||||
\ ),
|
||||
\ lsp#callbag#pipe(
|
||||
\ lsp#callbag#fromEvent(['InsertEnter', 'InsertLeave']),
|
||||
\ lsp#callbag#filter({_->!g:lsp_diagnostics_signs_insert_mode_enabled}),
|
||||
\ lsp#callbag#map({_->{ 'uri': lsp#utils#get_buffer_uri() }}),
|
||||
\ ),
|
||||
\ ),
|
||||
\ lsp#callbag#filter({_->g:lsp_diagnostics_signs_enabled}),
|
||||
\ lsp#callbag#debounceTime(g:lsp_diagnostics_signs_delay),
|
||||
\ lsp#callbag#tap({x->s:clear_signs(x)}),
|
||||
\ lsp#callbag#tap({x->s:set_signs(x)}),
|
||||
\ lsp#callbag#subscribe(),
|
||||
\ )
|
||||
endfunction
|
||||
|
||||
function! lsp#internal#diagnostics#signs#_disable() abort
|
||||
if !s:enabled | return | endif
|
||||
if exists('s:Dispose')
|
||||
call s:Dispose()
|
||||
unlet s:Dispose
|
||||
endif
|
||||
call s:clear_all_signs()
|
||||
call s:undefine_signs()
|
||||
let s:enabled = 0
|
||||
endfunction
|
||||
|
||||
" Set default sign text to handle case when user provides empty dict
|
||||
function! s:define_sign(sign_name, sign_default_text, sign_options) abort
|
||||
let l:options = {
|
||||
\ 'text': get(a:sign_options, 'text', a:sign_default_text),
|
||||
\ 'texthl': a:sign_name . 'Text',
|
||||
\ 'linehl': a:sign_name . 'Line',
|
||||
\ }
|
||||
let l:sign_icon = get(a:sign_options, 'icon', '')
|
||||
if !empty(l:sign_icon)
|
||||
let l:options['icon'] = l:sign_icon
|
||||
endif
|
||||
call sign_define(a:sign_name, l:options)
|
||||
endfunction
|
||||
|
||||
function! s:undefine_signs() abort
|
||||
call sign_undefine('LspError')
|
||||
call sign_undefine('LspWarning')
|
||||
call sign_undefine('LspInformation')
|
||||
call sign_undefine('LspHint')
|
||||
endfunction
|
||||
|
||||
function! s:clear_all_signs() abort
|
||||
call sign_unplace(s:sign_group)
|
||||
endfunction
|
||||
|
||||
" params => {
|
||||
" server: '' " optional
|
||||
" uri: '' " optional
|
||||
" }
|
||||
function! s:clear_signs(params) abort
|
||||
" TODO: optimize by looking at params
|
||||
call s:clear_all_signs()
|
||||
endfunction
|
||||
|
||||
" params => {
|
||||
" server: '' " optional
|
||||
" uri: '' " optional
|
||||
" }
|
||||
function! s:set_signs(params) abort
|
||||
" TODO: optimize by looking at params
|
||||
if !g:lsp_diagnostics_signs_insert_mode_enabled
|
||||
if mode()[0] ==# 'i' | return | endif
|
||||
endif
|
||||
|
||||
for l:bufnr in range(1, bufnr('$'))
|
||||
if lsp#internal#diagnostics#state#_is_enabled_for_buffer(l:bufnr) && bufexists(l:bufnr) && bufloaded(l:bufnr)
|
||||
let l:uri = lsp#utils#get_buffer_uri(l:bufnr)
|
||||
for [l:server, l:diagnostics_response] in items(lsp#internal#diagnostics#state#_get_all_diagnostics_grouped_by_server_for_uri(l:uri))
|
||||
call s:place_signs(l:server, l:diagnostics_response, l:bufnr)
|
||||
endfor
|
||||
endif
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! s:place_signs(server, diagnostics_response, bufnr) abort
|
||||
for l:item in lsp#utils#iteratable(a:diagnostics_response['params']['diagnostics'])
|
||||
let l:line = lsp#utils#position#lsp_line_to_vim(a:bufnr, l:item['range']['start'])
|
||||
|
||||
" Some language servers report an unexpected EOF one line past the end
|
||||
" key 'linecount' may be missing.
|
||||
if has_key(getbufinfo(a:bufnr)[0], 'linecount')
|
||||
if l:line == getbufinfo(a:bufnr)[0].linecount + 1
|
||||
let l:line = l:line - 1
|
||||
endif
|
||||
endif
|
||||
|
||||
if has_key(l:item, 'severity') && !empty(l:item['severity'])
|
||||
let l:sign_name = get(s:severity_sign_names_mapping, l:item['severity'], 'LspError')
|
||||
let l:sign_priority = get(g:lsp_diagnostics_signs_priority_map, l:sign_name, g:lsp_diagnostics_signs_priority)
|
||||
let l:sign_priority = get(g:lsp_diagnostics_signs_priority_map,
|
||||
\ a:server . '_' . l:sign_name, l:sign_priority)
|
||||
" pass 0 and let vim generate sign id
|
||||
let l:sign_id = sign_place(0, s:sign_group, l:sign_name, a:bufnr,
|
||||
\{ 'lnum': l:line, 'priority': l:sign_priority })
|
||||
endif
|
||||
endfor
|
||||
endfunction
|
||||
@@ -0,0 +1,173 @@
|
||||
" https://microsoft.github.io/language-server-protocol/specifications/specification-current/#diagnostic
|
||||
"
|
||||
" Refer to https://github.com/microsoft/language-server-protocol/pull/1019 on normalization of urls.
|
||||
" {
|
||||
" 'normalized_uri': {
|
||||
" 'server_name': {
|
||||
" 'method': 'textDocument/publishDiagnostics',
|
||||
" 'params': {
|
||||
" 'uri': 'uri', " this uri is not normalized and is exactly what server returns
|
||||
" 'dignostics': [ " array can never be null but can be empty
|
||||
" https://microsoft.github.io/language-server-protocol/specifications/specification-current/#diagnostic
|
||||
" { range, message, severity?, code?, codeDesciption?, source?, tags?, relatedInformation?, data? }
|
||||
" ]
|
||||
" }
|
||||
" }
|
||||
" }
|
||||
" Note: Do not remove when buffer unloads or doesn't exist since some server
|
||||
" may send diagnsotics information regardless of textDocument/didOpen.
|
||||
" buffer state is removed when server exits.
|
||||
" TODO: reset buffer state when server initializes. ignoring for now for perf.
|
||||
let s:diagnostics_state = {}
|
||||
|
||||
" internal state for whether it is enabled or not to avoid multiple subscriptions
|
||||
let s:enabled = 0
|
||||
|
||||
let s:diagnostic_kinds = {
|
||||
\ 1: 'error',
|
||||
\ 2: 'warning',
|
||||
\ 3: 'information',
|
||||
\ 4: 'hint',
|
||||
\ }
|
||||
|
||||
function! lsp#internal#diagnostics#state#_enable() abort
|
||||
" don't even bother registering if the feature is disabled
|
||||
if !g:lsp_diagnostics_enabled | return | endif
|
||||
|
||||
if s:enabled | return | endif
|
||||
let s:enabled = 1
|
||||
|
||||
call lsp#internal#diagnostics#state#_reset()
|
||||
|
||||
let s:Dispose = lsp#callbag#pipe(
|
||||
\ lsp#callbag#merge(
|
||||
\ lsp#callbag#pipe(
|
||||
\ lsp#stream(),
|
||||
\ lsp#callbag#filter({x->has_key(x, 'server') && has_key(x, 'response')
|
||||
\ && get(x['response'], 'method', '') ==# 'textDocument/publishDiagnostics'}),
|
||||
\ lsp#callbag#tap({x->s:on_text_documentation_publish_diagnostics(x['server'], x['response'])}),
|
||||
\ ),
|
||||
\ lsp#callbag#pipe(
|
||||
\ lsp#stream(),
|
||||
\ lsp#callbag#filter({x->has_key(x, 'server') && has_key(x, 'response')
|
||||
\ && get(x['response'], 'method', '') ==# '$/vimlsp/lsp_server_exit' }),
|
||||
\ lsp#callbag#tap({x->s:on_exit(x['response'])}),
|
||||
\ ),
|
||||
\ ),
|
||||
\ lsp#callbag#subscribe(),
|
||||
\ )
|
||||
|
||||
call s:notify_diagnostics_update()
|
||||
endfunction
|
||||
|
||||
function! lsp#internal#diagnostics#state#_disable() abort
|
||||
if !s:enabled | return | endif
|
||||
if exists('s:Dispose')
|
||||
call s:Dispose()
|
||||
unlet s:Dispose
|
||||
endif
|
||||
call lsp#internal#diagnostics#state#_reset()
|
||||
call s:notify_diagnostics_update()
|
||||
let s:enabled = 0
|
||||
endfunction
|
||||
|
||||
function! lsp#internal#diagnostics#state#_reset() abort
|
||||
let s:diagnostics_state = {}
|
||||
let s:diagnostics_disabled_buffers = {}
|
||||
endfunction
|
||||
|
||||
" callers should always treat the return value as immutable
|
||||
" @return {
|
||||
" 'servername': response
|
||||
" }
|
||||
function! lsp#internal#diagnostics#state#_get_all_diagnostics_grouped_by_server_for_uri(uri) abort
|
||||
return get(s:diagnostics_state, lsp#utils#normalize_uri(a:uri), {})
|
||||
endfunction
|
||||
|
||||
" callers should always treat the return value as immutable.
|
||||
" callers should treat uri as normalized via lsp#utils#normalize_uri
|
||||
" @return {
|
||||
" 'normalized_uri': {
|
||||
" 'servername': response
|
||||
" }
|
||||
" }
|
||||
function! lsp#internal#diagnostics#state#_get_all_diagnostics_grouped_by_uri_and_server() abort
|
||||
return s:diagnostics_state
|
||||
endfunction
|
||||
|
||||
function! s:on_text_documentation_publish_diagnostics(server, response) abort
|
||||
if lsp#client#is_error(a:response) | return | endif
|
||||
let l:normalized_uri = lsp#utils#normalize_uri(a:response['params']['uri'])
|
||||
if !has_key(s:diagnostics_state, l:normalized_uri)
|
||||
let s:diagnostics_state[l:normalized_uri] = {}
|
||||
endif
|
||||
let s:diagnostics_state[l:normalized_uri][a:server] = a:response
|
||||
call s:notify_diagnostics_update(a:server, l:normalized_uri)
|
||||
endfunction
|
||||
|
||||
function! s:on_exit(response) abort
|
||||
let l:server = a:response['params']['server']
|
||||
let l:notify = 0
|
||||
for [l:key, l:value] in items(s:diagnostics_state)
|
||||
if has_key(l:value, l:server)
|
||||
let l:notify = 1
|
||||
call remove(l:value, l:server)
|
||||
endif
|
||||
endfor
|
||||
if l:notify | call s:notify_diagnostics_update(l:server) | endif
|
||||
endfunction
|
||||
|
||||
function! lsp#internal#diagnostics#state#_force_notify_buffer(buffer) abort
|
||||
" TODO: optimize buffer only
|
||||
call s:notify_diagnostics_update()
|
||||
endfunction
|
||||
|
||||
" call s:notify_diagnostics_update()
|
||||
" call s:notify_diagnostics_update('server')
|
||||
" call s:notify_diagnostics_update('server', 'uri')
|
||||
function! s:notify_diagnostics_update(...) abort
|
||||
let l:data = { 'server': '$vimlsp', 'response': { 'method': '$/vimlsp/lsp_diagnostics_updated', 'params': {} } }
|
||||
" if a:0 > 0 | let l:data['response']['params']['server'] = a:1 | endif
|
||||
" if a:0 > 1 | let l:data['response']['params']['uri'] = a:2 | endif
|
||||
call lsp#stream(1, l:data)
|
||||
doautocmd <nomodeline> User lsp_diagnostics_updated
|
||||
endfunction
|
||||
|
||||
function! lsp#internal#diagnostics#state#_enable_for_buffer(bufnr) abort
|
||||
if getbufvar(a:bufnr, 'lsp_diagnostics_enabled', 1) == 0
|
||||
call setbufvar(a:bufnr, 'lsp_diagnostics_enabled', 1)
|
||||
call s:notify_diagnostics_update()
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! lsp#internal#diagnostics#state#_disable_for_buffer(bufnr) abort
|
||||
if getbufvar(a:bufnr, 'lsp_diagnostics_enabled', 1) != 0
|
||||
call setbufvar(a:bufnr, 'lsp_diagnostics_enabled', 0)
|
||||
call s:notify_diagnostics_update()
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! lsp#internal#diagnostics#state#_is_enabled_for_buffer(bufnr) abort
|
||||
return getbufvar(a:bufnr, 'lsp_diagnostics_enabled', 1) == 1
|
||||
endfunction
|
||||
|
||||
" Return dict with diagnostic counts for the specified buffer
|
||||
" { 'error': 1, 'warning': 0, 'information': 0, 'hint': 0 }
|
||||
function! lsp#internal#diagnostics#state#_get_diagnostics_count_for_buffer(bufnr) abort
|
||||
let l:counts = {
|
||||
\ 'error': 0,
|
||||
\ 'warning': 0,
|
||||
\ 'information': 0,
|
||||
\ 'hint': 0,
|
||||
\ }
|
||||
if lsp#internal#diagnostics#state#_is_enabled_for_buffer(a:bufnr)
|
||||
let l:uri = lsp#utils#get_buffer_uri(a:bufnr)
|
||||
for [l:_, l:response] in items(lsp#internal#diagnostics#state#_get_all_diagnostics_grouped_by_server_for_uri(l:uri))
|
||||
for l:diagnostic in lsp#utils#iteratable(l:response['params']['diagnostics'])
|
||||
let l:key = get(s:diagnostic_kinds, get(l:diagnostic, 'severity', 1) , 'error')
|
||||
let l:counts[l:key] += 1
|
||||
endfor
|
||||
endfor
|
||||
end
|
||||
return l:counts
|
||||
endfunction
|
||||
@@ -0,0 +1,48 @@
|
||||
" Returns a diagnostic object, or empty dictionary if no diagnostics are
|
||||
" available.
|
||||
" options = {
|
||||
" 'server': '', " optional
|
||||
" }
|
||||
function! lsp#internal#diagnostics#under_cursor#get_diagnostic(...) abort
|
||||
let l:options = get(a:000, 0, {})
|
||||
let l:server = get(l:options, 'server', '')
|
||||
let l:bufnr = bufnr('%')
|
||||
|
||||
if !lsp#internal#diagnostics#state#_is_enabled_for_buffer(l:bufnr)
|
||||
return {}
|
||||
endif
|
||||
|
||||
let l:uri = lsp#utils#get_buffer_uri(l:bufnr)
|
||||
|
||||
let l:diagnostics_by_server = lsp#internal#diagnostics#state#_get_all_diagnostics_grouped_by_server_for_uri(l:uri)
|
||||
let l:diagnostics = []
|
||||
if empty(l:server)
|
||||
for l:item in values(l:diagnostics_by_server)
|
||||
let l:diagnostics += lsp#utils#iteratable(l:item['params']['diagnostics'])
|
||||
endfor
|
||||
else
|
||||
if has_key(l:diagnostics_by_server, l:server)
|
||||
let l:diagnostics = lsp#utils#iteratable(l:diagnostics_by_server[l:server]['params']['diagnostics'])
|
||||
endif
|
||||
endif
|
||||
|
||||
let l:line = line('.')
|
||||
let l:col = col('.')
|
||||
|
||||
let l:closest_diagnostic = {}
|
||||
let l:closest_distance = -1
|
||||
|
||||
for l:diagnostic in l:diagnostics
|
||||
let [l:start_line, l:start_col] = lsp#utils#position#lsp_to_vim('%', l:diagnostic['range']['start'])
|
||||
|
||||
if l:line == l:start_line
|
||||
let l:distance = abs(l:start_col - l:col)
|
||||
if l:closest_distance < 0 || l:distance < l:closest_distance
|
||||
let l:closest_diagnostic = l:diagnostic
|
||||
let l:closest_distance = l:distance
|
||||
endif
|
||||
endif
|
||||
endfor
|
||||
|
||||
return l:closest_diagnostic
|
||||
endfunction
|
||||
@@ -0,0 +1,191 @@
|
||||
" internal state for whether it is enabled or not to avoid multiple subscriptions
|
||||
let s:enabled = 0
|
||||
let s:namespace_id = '' " will be set when enabled
|
||||
let s:severity_sign_names_mapping = {
|
||||
\ 1: 'LspError',
|
||||
\ 2: 'LspWarning',
|
||||
\ 3: 'LspInformation',
|
||||
\ 4: 'LspHint',
|
||||
\ }
|
||||
|
||||
if !hlexists('LspErrorVirtualText')
|
||||
if !hlexists('LspErrorText')
|
||||
highlight link LspErrorVirtualText Error
|
||||
else
|
||||
highlight link LspErrorVirtualText LspErrorText
|
||||
endif
|
||||
endif
|
||||
|
||||
if !hlexists('LspWarningVirtualText')
|
||||
if !hlexists('LspWarningText')
|
||||
highlight link LspWarningVirtualText Todo
|
||||
else
|
||||
highlight link LspWarningVirtualText LspWarningText
|
||||
endif
|
||||
endif
|
||||
|
||||
if !hlexists('LspInformationVirtualText')
|
||||
if !hlexists('LspInformationText')
|
||||
highlight link LspInformationVirtualText Normal
|
||||
else
|
||||
highlight link LspInformationVirtualText LspInformationText
|
||||
endif
|
||||
endif
|
||||
|
||||
if !hlexists('LspHintVirtualText')
|
||||
if !hlexists('LspHintText')
|
||||
highlight link LspHintVirtualText Normal
|
||||
else
|
||||
highlight link LspHintVirtualText LspHintText
|
||||
endif
|
||||
endif
|
||||
|
||||
function! lsp#internal#diagnostics#virtual_text#_enable() abort
|
||||
" don't even bother registering if the feature is disabled
|
||||
if !lsp#utils#_has_nvim_virtual_text() && !lsp#utils#_has_vim_virtual_text() | return | endif
|
||||
if !g:lsp_diagnostics_virtual_text_enabled | return | endif
|
||||
|
||||
if s:enabled | return | endif
|
||||
let s:enabled = 1
|
||||
|
||||
if has('nvim')
|
||||
if empty(s:namespace_id)
|
||||
let s:namespace_id = nvim_create_namespace('vim_lsp_diagnostic_virtual_text')
|
||||
endif
|
||||
else
|
||||
if index(prop_type_list(), 'vim_lsp_LspError_virtual_text') ==# -1
|
||||
call prop_type_add('vim_lsp_LspError_virtual_text', { 'highlight': 'LspErrorVirtualText' })
|
||||
call prop_type_add('vim_lsp_LspWarning_virtual_text', { 'highlight': 'LspWarningVirtualText' })
|
||||
call prop_type_add('vim_lsp_LspInformation_virtual_text', { 'highlight': 'LspInformationVirtualText' })
|
||||
call prop_type_add('vim_lsp_LspHint_virtual_text', { 'highlight': 'LspHintVirtualText' })
|
||||
endif
|
||||
endif
|
||||
|
||||
let s:Dispose = lsp#callbag#pipe(
|
||||
\ lsp#callbag#merge(
|
||||
\ lsp#callbag#pipe(
|
||||
\ lsp#stream(),
|
||||
\ lsp#callbag#filter({x->has_key(x, 'server') && has_key(x, 'response')
|
||||
\ && has_key(x['response'], 'method') && x['response']['method'] ==# '$/vimlsp/lsp_diagnostics_updated'
|
||||
\ && !lsp#client#is_error(x['response'])}),
|
||||
\ lsp#callbag#map({x->x['response']['params']}),
|
||||
\ ),
|
||||
\ lsp#callbag#pipe(
|
||||
\ lsp#callbag#fromEvent(['InsertEnter', 'InsertLeave']),
|
||||
\ lsp#callbag#filter({_->!g:lsp_diagnostics_virtual_text_insert_mode_enabled}),
|
||||
\ lsp#callbag#map({_->{ 'uri': lsp#utils#get_buffer_uri() }}),
|
||||
\ ),
|
||||
\ ),
|
||||
\ lsp#callbag#filter({_->g:lsp_diagnostics_virtual_text_enabled}),
|
||||
\ lsp#callbag#debounceTime(g:lsp_diagnostics_virtual_text_delay),
|
||||
\ lsp#callbag#tap({x->s:clear_virtual_text(x)}),
|
||||
\ lsp#callbag#tap({x->s:set_virtual_text(x)}),
|
||||
\ lsp#callbag#subscribe(),
|
||||
\ )
|
||||
endfunction
|
||||
|
||||
function! lsp#internal#diagnostics#virtual_text#_disable() abort
|
||||
if !s:enabled | return | endif
|
||||
if exists('s:Dispose')
|
||||
call s:Dispose()
|
||||
unlet s:Dispose
|
||||
endif
|
||||
call s:clear_all_virtual_text()
|
||||
let s:enabled = 0
|
||||
endfunction
|
||||
|
||||
function! s:clear_all_virtual_text() abort
|
||||
if has('nvim')
|
||||
for l:bufnr in nvim_list_bufs()
|
||||
if bufexists(l:bufnr) && bufloaded(l:bufnr)
|
||||
call nvim_buf_clear_namespace(l:bufnr, s:namespace_id, 0, -1)
|
||||
endif
|
||||
endfor
|
||||
else
|
||||
let l:types = ['vim_lsp_LspError_virtual_text', 'vim_lsp_LspWarning_virtual_text', 'vim_lsp_LspInformation_virtual_text', 'vim_lsp_LspHint_virtual_text']
|
||||
for l:bufnr in map(copy(getbufinfo()), 'v:val.bufnr')
|
||||
if lsp#utils#_has_prop_remove_types()
|
||||
call prop_remove({'types': l:types, 'bufnr': l:bufnr, 'all': v:true})
|
||||
else
|
||||
for l:type in l:types
|
||||
call prop_remove({'type': l:type, 'bufnr': l:bufnr, 'all': v:true})
|
||||
endfor
|
||||
endif
|
||||
endfor
|
||||
endif
|
||||
endfunction
|
||||
|
||||
" params => {
|
||||
" server: '' " optional
|
||||
" uri: '' " optional
|
||||
" }
|
||||
function! s:clear_virtual_text(params) abort
|
||||
" TODO: optimize by looking at params
|
||||
call s:clear_all_virtual_text()
|
||||
endfunction
|
||||
|
||||
" params => {
|
||||
" server: '' " optional
|
||||
" uri: '' " optional
|
||||
" }
|
||||
function! s:set_virtual_text(params) abort
|
||||
" TODO: optimize by looking at params
|
||||
if !g:lsp_diagnostics_virtual_text_insert_mode_enabled
|
||||
if mode()[0] ==# 'i' | return | endif
|
||||
endif
|
||||
|
||||
if has('nvim')
|
||||
for l:bufnr in nvim_list_bufs()
|
||||
if lsp#internal#diagnostics#state#_is_enabled_for_buffer(l:bufnr) && bufexists(l:bufnr) && bufloaded(l:bufnr)
|
||||
let l:uri = lsp#utils#get_buffer_uri(l:bufnr)
|
||||
for [l:server, l:diagnostics_response] in items(lsp#internal#diagnostics#state#_get_all_diagnostics_grouped_by_server_for_uri(l:uri))
|
||||
call s:place_virtual_text(l:server, l:diagnostics_response, l:bufnr)
|
||||
endfor
|
||||
endif
|
||||
endfor
|
||||
else
|
||||
for l:bufnr in map(copy(getbufinfo()), 'v:val.bufnr')
|
||||
if lsp#internal#diagnostics#state#_is_enabled_for_buffer(l:bufnr) && bufexists(l:bufnr) && bufloaded(l:bufnr)
|
||||
let l:uri = lsp#utils#get_buffer_uri(l:bufnr)
|
||||
for [l:server, l:diagnostics_response] in items(lsp#internal#diagnostics#state#_get_all_diagnostics_grouped_by_server_for_uri(l:uri))
|
||||
call s:place_virtual_text(l:server, l:diagnostics_response, l:bufnr)
|
||||
endfor
|
||||
endif
|
||||
endfor
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:place_virtual_text(server, diagnostics_response, bufnr) abort
|
||||
for l:item in lsp#utils#iteratable(a:diagnostics_response['params']['diagnostics'])
|
||||
let l:line = lsp#utils#position#lsp_line_to_vim(a:bufnr, l:item['range']['start'])
|
||||
let l:name = get(s:severity_sign_names_mapping, get(l:item, 'severity', 3), 'LspError')
|
||||
let l:text = g:lsp_diagnostics_virtual_text_prefix . l:item['message']
|
||||
|
||||
" Some language servers report an unexpected EOF one line past the end
|
||||
if l:line == getbufinfo(a:bufnr)[0].linecount + 1
|
||||
let l:line = l:line - 1
|
||||
endif
|
||||
|
||||
if has('nvim')
|
||||
let l:hl_name = l:name . 'VirtualText'
|
||||
" need to do -1 for virtual text
|
||||
call nvim_buf_set_virtual_text(a:bufnr, s:namespace_id, l:line - 1,
|
||||
\ [[l:text, l:hl_name]], {})
|
||||
else
|
||||
" it's an error to add virtual text on lines that don't exist
|
||||
" anymore due to async processing, just skip such diagnostics
|
||||
if l:line <= getbufinfo(a:bufnr)[0].linecount
|
||||
let l:type = 'vim_lsp_' . l:name . '_virtual_text'
|
||||
call prop_remove({'all': v:true, 'type': l:type, 'bufnr': a:bufnr}, l:line)
|
||||
call prop_add(
|
||||
\ l:line, 0,
|
||||
\ {
|
||||
\ 'type': l:type, 'text': l:text, 'bufnr': a:bufnr,
|
||||
\ 'text_align': g:lsp_diagnostics_virtual_text_align,
|
||||
\ 'text_padding_left': g:lsp_diagnostics_virtual_text_padding_left,
|
||||
\ 'text_wrap': g:lsp_diagnostics_virtual_text_wrap,
|
||||
\ })
|
||||
endif
|
||||
endif
|
||||
endfor
|
||||
endfunction
|
||||
@@ -0,0 +1,131 @@
|
||||
" https://microsoft.github.io/language-server-protocol/specification#textDocument_codeAction
|
||||
|
||||
" internal state for whether it is enabled or not to avoid multiple subscriptions
|
||||
let s:enabled = 0
|
||||
|
||||
let s:sign_group = 'vim_lsp_document_code_action_signs'
|
||||
|
||||
if !hlexists('LspCodeActionText')
|
||||
highlight link LspCodeActionText Normal
|
||||
endif
|
||||
|
||||
function! lsp#internal#document_code_action#signs#_enable() abort
|
||||
if !lsp#utils#_has_signs() | return | endif
|
||||
" don't even bother registering if the feature is disabled
|
||||
if !g:lsp_document_code_action_signs_enabled | return | endif
|
||||
|
||||
if s:enabled | return | endif
|
||||
let s:enabled = 1
|
||||
|
||||
call s:define_sign('LspCodeAction', 'A>', g:lsp_document_code_action_signs_hint)
|
||||
|
||||
" Note:
|
||||
" - update CodeAction signs when CusorMoved or CursorHold
|
||||
" - clear signs when InsertEnter or BufLeave
|
||||
" - debounce code action requests
|
||||
" - automatically switch to latest code action request via switchMap()
|
||||
" - cancel code action request via takeUntil() when BufLeave
|
||||
let s:Dispose = lsp#callbag#pipe(
|
||||
\ lsp#callbag#merge(
|
||||
\ lsp#callbag#fromEvent(['CursorMoved', 'CursorHold']),
|
||||
\ lsp#callbag#pipe(
|
||||
\ lsp#callbag#fromEvent(['InsertEnter', 'BufLeave']),
|
||||
\ lsp#callbag#tap({_ -> s:clear_signs() }),
|
||||
\ )
|
||||
\ ),
|
||||
\ lsp#callbag#filter({_ -> g:lsp_document_code_action_signs_enabled }),
|
||||
\ lsp#callbag#debounceTime(g:lsp_document_code_action_signs_delay),
|
||||
\ lsp#callbag#map({_->{'bufnr': bufnr('%'), 'curpos': getcurpos()[0:2], 'changedtick': b:changedtick }}),
|
||||
\ lsp#callbag#distinctUntilChanged({a,b -> a['bufnr'] == b['bufnr'] && a['curpos'] == b['curpos'] && a['changedtick'] == b['changedtick']}),
|
||||
\ lsp#callbag#filter({_->mode() is# 'n' && getbufvar(bufnr('%'), '&buftype') !=# 'terminal' }),
|
||||
\ lsp#callbag#switchMap({_->
|
||||
\ lsp#callbag#pipe(
|
||||
\ s:send_request(),
|
||||
\ lsp#callbag#materialize(),
|
||||
\ lsp#callbag#filter({x->lsp#callbag#isNextNotification(x)}),
|
||||
\ lsp#callbag#map({x->x['value']}),
|
||||
\ lsp#callbag#takeUntil(
|
||||
\ lsp#callbag#fromEvent('BufLeave')
|
||||
\ )
|
||||
\ )
|
||||
\ }),
|
||||
\ lsp#callbag#subscribe({x->s:set_signs(x)}),
|
||||
\)
|
||||
endfunction
|
||||
|
||||
function! lsp#internal#document_code_action#signs#_disable() abort
|
||||
if !s:enabled | return | endif
|
||||
if exists('s:Dispose')
|
||||
call s:Dispose()
|
||||
unlet s:Dispose
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:send_request() abort
|
||||
let l:servers = filter(lsp#get_allowed_servers(), 'lsp#capabilities#has_code_action_provider(v:val)')
|
||||
|
||||
if empty(l:servers)
|
||||
return lsp#callbag#empty()
|
||||
endif
|
||||
|
||||
let l:range = lsp#utils#range#_get_current_line_range()
|
||||
return lsp#callbag#pipe(
|
||||
\ lsp#callbag#fromList(l:servers),
|
||||
\ lsp#callbag#flatMap({server->
|
||||
\ lsp#request(server, {
|
||||
\ 'method': 'textDocument/codeAction',
|
||||
\ 'params': {
|
||||
\ 'textDocument': lsp#get_text_document_identifier(),
|
||||
\ 'range': l:range,
|
||||
\ 'context': {
|
||||
\ 'diagnostics': [],
|
||||
\ 'only': ['', 'quickfix', 'refactor', 'refactor.extract', 'refactor.inline', 'refactor.rewrite'],
|
||||
\ }
|
||||
\ }
|
||||
\ })
|
||||
\ }),
|
||||
\ lsp#callbag#filter({x-> !lsp#client#is_error(x['response']) && !empty(x['response']['result'])}),
|
||||
\ lsp#callbag#take(1),
|
||||
\ )
|
||||
endfunction
|
||||
|
||||
function! s:clear_signs() abort
|
||||
call sign_unplace(s:sign_group)
|
||||
endfunction
|
||||
|
||||
function! s:set_signs(data) abort
|
||||
call s:clear_signs()
|
||||
|
||||
if lsp#client#is_error(a:data['response']) | return | endif
|
||||
|
||||
if empty(a:data['response']['result'])
|
||||
return
|
||||
endif
|
||||
|
||||
let l:bufnr = bufnr(lsp#utils#uri_to_path(a:data['request']['params']['textDocument']['uri']))
|
||||
call s:place_signs(a:data, l:bufnr)
|
||||
endfunction
|
||||
|
||||
" Set default sign text to handle case when user provides empty dict
|
||||
function! s:define_sign(sign_name, sign_default_text, sign_options) abort
|
||||
let l:options = {
|
||||
\ 'text': get(a:sign_options, 'text', a:sign_default_text),
|
||||
\ 'texthl': a:sign_name . 'Text',
|
||||
\ 'linehl': a:sign_name . 'Line',
|
||||
\ }
|
||||
let l:sign_icon = get(a:sign_options, 'icon', '')
|
||||
if !empty(l:sign_icon)
|
||||
let l:options['icon'] = l:sign_icon
|
||||
endif
|
||||
call sign_define(a:sign_name, l:options)
|
||||
endfunction
|
||||
|
||||
function! s:place_signs(data, bufnr) abort
|
||||
if !bufexists(a:bufnr) || !bufloaded(a:bufnr)
|
||||
return
|
||||
endif
|
||||
let l:sign_priority = g:lsp_document_code_action_signs_priority
|
||||
let l:line = lsp#utils#position#lsp_line_to_vim(a:bufnr, a:data['request']['params']['range']['start'])
|
||||
let l:sign_id = sign_place(0, s:sign_group, 'LspCodeAction', a:bufnr,
|
||||
\ { 'lnum': l:line, 'priority': l:sign_priority })
|
||||
endfunction
|
||||
@@ -0,0 +1,86 @@
|
||||
" options - {
|
||||
" bufnr: bufnr('%') " required
|
||||
" server - 'server_name' " optional
|
||||
" sync: 0 " optional, defaults to 0 (async)
|
||||
" }
|
||||
function! lsp#internal#document_formatting#format(options) abort
|
||||
let l:mode = mode()
|
||||
if l:mode =~# '[vV]' || l:mode ==# "\<C-V>"
|
||||
return lsp#internal#document_range_formatting#format(a:options)
|
||||
endif
|
||||
|
||||
if has_key(a:options, 'server')
|
||||
let l:servers = [a:options['server']]
|
||||
else
|
||||
let l:servers = filter(lsp#get_allowed_servers(), 'lsp#capabilities#has_document_formatting_provider(v:val)')
|
||||
endif
|
||||
|
||||
if len(l:servers) == 0
|
||||
let l:filetype = getbufvar(a:options['bufnr'], '&filetype')
|
||||
call lsp#utils#error('textDocument/formatting not supported for ' . l:filetype)
|
||||
return
|
||||
endif
|
||||
|
||||
" TODO: ask user to select server for formatting if there are multiple servers
|
||||
let l:server = l:servers[0]
|
||||
|
||||
redraw | echo 'Formatting Document ...'
|
||||
|
||||
call lsp#_new_command()
|
||||
|
||||
let l:request = {
|
||||
\ 'method': 'textDocument/formatting',
|
||||
\ 'params': {
|
||||
\ 'textDocument': lsp#get_text_document_identifier(a:options['bufnr']),
|
||||
\ 'options': {
|
||||
\ 'tabSize': lsp#utils#buffer#get_indent_size(a:options['bufnr']),
|
||||
\ 'insertSpaces': getbufvar(a:options['bufnr'], '&expandtab') ? v:true : v:false,
|
||||
\ }
|
||||
\ },
|
||||
\ 'bufnr': a:options['bufnr'],
|
||||
\ }
|
||||
|
||||
if get(a:options, 'sync', 0) == 1
|
||||
try
|
||||
let l:x = lsp#callbag#pipe(
|
||||
\ lsp#request(l:server, l:request),
|
||||
\ lsp#callbag#takeUntil(lsp#callbag#pipe(
|
||||
\ lsp#stream(),
|
||||
\ lsp#callbag#filter({x->has_key(x, 'command')}),
|
||||
\ )),
|
||||
\ lsp#callbag#toList(),
|
||||
\ ).wait({ 'sleep': get(a:options, 'sleep', 1), 'timeout': get(a:options, 'timeout', g:lsp_format_sync_timeout) })
|
||||
call s:format_next(l:x[0])
|
||||
call s:format_complete()
|
||||
catch
|
||||
call s:format_error(v:exception . ' ' . v:throwpoint)
|
||||
endtry
|
||||
else
|
||||
return lsp#callbag#pipe(
|
||||
\ lsp#request(l:server, l:request),
|
||||
\ lsp#callbag#takeUntil(lsp#callbag#pipe(
|
||||
\ lsp#stream(),
|
||||
\ lsp#callbag#filter({x->has_key(x, 'command')}),
|
||||
\ )),
|
||||
\ lsp#callbag#subscribe({
|
||||
\ 'next':{x->s:format_next(x)},
|
||||
\ 'error': {x->s:format_error(e)},
|
||||
\ 'complete': {->s:format_complete()},
|
||||
\ }),
|
||||
\ )
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:format_next(x) abort
|
||||
if lsp#client#is_error(a:x['response']) | return | endif
|
||||
call lsp#utils#text_edit#apply_text_edits(a:x['request']['params']['textDocument']['uri'], get(a:x['response'], 'result', ''))
|
||||
endfunction
|
||||
|
||||
function! s:format_error(e) abort
|
||||
call lsp#log('Formatting Document Failed', a:e)
|
||||
call lsp#utils#error('Formatting Document Failed.' . (type(a:e) == type('') ? a:e : ''))
|
||||
endfunction
|
||||
|
||||
function! s:format_complete() abort
|
||||
redraw | echo 'Formatting Document complete'
|
||||
endfunction
|
||||
@@ -0,0 +1,238 @@
|
||||
let s:use_vim_textprops = lsp#utils#_has_textprops() && !has('nvim')
|
||||
let s:prop_id = 11
|
||||
|
||||
function! lsp#internal#document_highlight#_enable() abort
|
||||
" don't event bother registering if the feature is disabled
|
||||
if !g:lsp_document_highlight_enabled | return | endif
|
||||
|
||||
" Highlight group for references
|
||||
if !hlexists('lspReference')
|
||||
highlight link lspReference CursorColumn
|
||||
endif
|
||||
|
||||
" Note:
|
||||
" - update highlight references when CusorMoved or CursorHold
|
||||
" - clear highlights when InsertEnter or BufLeave
|
||||
" - debounce highlight requests
|
||||
" - automatically switch to latest highlight request via switchMap()
|
||||
" - cancel highlight request via takeUntil() when BufLeave
|
||||
let s:Dispose = lsp#callbag#pipe(
|
||||
\ lsp#callbag#merge(
|
||||
\ lsp#callbag#fromEvent(['CursorMoved', 'CursorHold']),
|
||||
\ lsp#callbag#pipe(
|
||||
\ lsp#callbag#fromEvent(['InsertEnter', 'BufLeave']),
|
||||
\ lsp#callbag#tap({_ -> s:clear_highlights() }),
|
||||
\ )
|
||||
\ ),
|
||||
\ lsp#callbag#filter({_ -> g:lsp_document_highlight_enabled }),
|
||||
\ lsp#callbag#debounceTime(g:lsp_document_highlight_delay),
|
||||
\ lsp#callbag#map({_->{'bufnr': bufnr('%'), 'curpos': getcurpos()[0:2], 'changedtick': b:changedtick }}),
|
||||
\ lsp#callbag#distinctUntilChanged({a,b -> a['bufnr'] == b['bufnr'] && a['curpos'] == b['curpos'] && a['changedtick'] == b['changedtick']}),
|
||||
\ lsp#callbag#filter({_->mode() is# 'n' && getbufvar(bufnr('%'), '&buftype') !=# 'terminal' }),
|
||||
\ lsp#callbag#switchMap({_->
|
||||
\ lsp#callbag#pipe(
|
||||
\ s:send_highlight_request(),
|
||||
\ lsp#callbag#materialize(),
|
||||
\ lsp#callbag#filter({x->lsp#callbag#isNextNotification(x)}),
|
||||
\ lsp#callbag#map({x->x['value']}),
|
||||
\ lsp#callbag#takeUntil(
|
||||
\ lsp#callbag#fromEvent('BufLeave')
|
||||
\ )
|
||||
\ )
|
||||
\ }),
|
||||
\ lsp#callbag#filter({_->mode() is# 'n'}),
|
||||
\ lsp#callbag#subscribe({x->s:set_highlights(x)}),
|
||||
\)
|
||||
endfunction
|
||||
|
||||
function! lsp#internal#document_highlight#_disable() abort
|
||||
if exists('s:Dispose')
|
||||
call s:Dispose()
|
||||
unlet s:Dispose
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:send_highlight_request() abort
|
||||
let l:capability = 'lsp#capabilities#has_document_highlight_provider(v:val)'
|
||||
let l:servers = filter(lsp#get_allowed_servers(), l:capability)
|
||||
|
||||
if empty(l:servers)
|
||||
return lsp#callbag#empty()
|
||||
endif
|
||||
|
||||
return lsp#request(l:servers[0], {
|
||||
\ 'method': 'textDocument/documentHighlight',
|
||||
\ 'params': {
|
||||
\ 'textDocument': lsp#get_text_document_identifier(),
|
||||
\ 'position': lsp#get_position(),
|
||||
\ },
|
||||
\ })
|
||||
endfunction
|
||||
|
||||
function! s:set_highlights(data) abort
|
||||
let l:bufnr = bufnr('%')
|
||||
|
||||
call s:clear_highlights()
|
||||
|
||||
if mode() !=# 'n' | return | endif
|
||||
|
||||
if lsp#client#is_error(a:data['response']) | return | endif
|
||||
|
||||
" Get references from the response
|
||||
let l:reference_list = a:data['response']['result']
|
||||
if empty(l:reference_list)
|
||||
return
|
||||
endif
|
||||
|
||||
" Convert references to vim positions
|
||||
let l:position_list = []
|
||||
for l:reference in l:reference_list
|
||||
call extend(l:position_list, lsp#utils#range#lsp_to_vim(l:bufnr, l:reference['range']))
|
||||
endfor
|
||||
|
||||
call sort(l:position_list, function('s:compare_positions'))
|
||||
|
||||
" Ignore response if the cursor is not over a reference anymore
|
||||
if s:in_reference(l:position_list) == -1 | return | endif
|
||||
|
||||
" Store references
|
||||
if s:use_vim_textprops
|
||||
let b:lsp_reference_positions = l:position_list
|
||||
let b:lsp_reference_matches = []
|
||||
else
|
||||
let w:lsp_reference_positions = l:position_list
|
||||
let w:lsp_reference_matches = []
|
||||
endif
|
||||
|
||||
" Apply highlights to the buffer
|
||||
call s:init_reference_highlight(l:bufnr)
|
||||
if s:use_vim_textprops
|
||||
for l:position in l:position_list
|
||||
try
|
||||
" TODO: need to check for valid range before calling prop_add
|
||||
" See https://github.com/prabirshrestha/vim-lsp/pull/721
|
||||
silent! call prop_add(l:position[0], l:position[1], {
|
||||
\ 'id': s:prop_id,
|
||||
\ 'bufnr': l:bufnr,
|
||||
\ 'length': l:position[2],
|
||||
\ 'type': 'vim-lsp-reference-highlight'})
|
||||
call add(b:lsp_reference_matches, l:position[0])
|
||||
catch
|
||||
call lsp#log('document_highlight', 'set_highlights', v:exception, v:throwpoint)
|
||||
endtry
|
||||
endfor
|
||||
else
|
||||
for l:position in l:position_list
|
||||
let l:match = matchaddpos('lspReference', [l:position], -5)
|
||||
call add(w:lsp_reference_matches, l:match)
|
||||
endfor
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:clear_highlights() abort
|
||||
if s:use_vim_textprops
|
||||
if exists('b:lsp_reference_matches')
|
||||
let l:bufnr = bufnr('%')
|
||||
for l:line in b:lsp_reference_matches
|
||||
silent! call prop_remove(
|
||||
\ {'id': s:prop_id,
|
||||
\ 'bufnr': l:bufnr,
|
||||
\ 'all': v:true}, l:line)
|
||||
endfor
|
||||
unlet b:lsp_reference_matches
|
||||
unlet b:lsp_reference_positions
|
||||
endif
|
||||
else
|
||||
if exists('w:lsp_reference_matches')
|
||||
for l:match in w:lsp_reference_matches
|
||||
silent! call matchdelete(l:match)
|
||||
endfor
|
||||
unlet w:lsp_reference_matches
|
||||
unlet w:lsp_reference_positions
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
|
||||
" Compare two positions
|
||||
function! s:compare_positions(p1, p2) abort
|
||||
let l:line_1 = a:p1[0]
|
||||
let l:line_2 = a:p2[0]
|
||||
if l:line_1 != l:line_2
|
||||
return l:line_1 > l:line_2 ? 1 : -1
|
||||
endif
|
||||
let l:col_1 = a:p1[1]
|
||||
let l:col_2 = a:p2[1]
|
||||
return l:col_1 - l:col_2
|
||||
endfunction
|
||||
|
||||
" If the cursor is over a reference, return its index in
|
||||
" the array. Otherwise, return -1.
|
||||
function! s:in_reference(reference_list) abort
|
||||
let l:line = line('.')
|
||||
let l:column = col('.')
|
||||
let l:index = 0
|
||||
for l:position in a:reference_list
|
||||
if l:line == l:position[0] &&
|
||||
\ l:column >= l:position[1] &&
|
||||
\ l:column < l:position[1] + l:position[2]
|
||||
return l:index
|
||||
endif
|
||||
let l:index += 1
|
||||
endfor
|
||||
return -1
|
||||
endfunction
|
||||
|
||||
function! s:init_reference_highlight(buf) abort
|
||||
if s:use_vim_textprops
|
||||
let l:props = {
|
||||
\ 'bufnr': a:buf,
|
||||
\ 'highlight': 'lspReference',
|
||||
\ 'combine': v:true,
|
||||
\ 'priority': lsp#internal#textprop#priority('document_highlight')
|
||||
\ }
|
||||
if prop_type_get('vim-lsp-reference-highlight', { 'bufnr': a:buf }) == {}
|
||||
call prop_type_add('vim-lsp-reference-highlight', l:props)
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
|
||||
" Cyclically move between references by `offset` occurrences.
|
||||
function! lsp#internal#document_highlight#jump(offset) abort
|
||||
if s:use_vim_textprops && !exists('b:lsp_reference_positions') ||
|
||||
\ !s:use_vim_textprops && !exists('w:lsp_reference_positions')
|
||||
echohl WarningMsg
|
||||
echom 'References not available'
|
||||
echohl None
|
||||
return
|
||||
endif
|
||||
|
||||
" Get index of reference under cursor
|
||||
let l:index = s:use_vim_textprops ? s:in_reference(b:lsp_reference_positions) : s:in_reference(w:lsp_reference_positions)
|
||||
if l:index < 0
|
||||
return
|
||||
endif
|
||||
|
||||
let l:n = s:use_vim_textprops ? len(b:lsp_reference_positions) : len(w:lsp_reference_positions)
|
||||
let l:index += a:offset
|
||||
|
||||
" Show a message when reaching TOP/BOTTOM of the file
|
||||
if l:index < 0
|
||||
echohl WarningMsg
|
||||
echom 'search hit TOP, continuing at BOTTOM'
|
||||
echohl None
|
||||
elseif l:index >= (s:use_vim_textprops ? len(b:lsp_reference_positions) : len(w:lsp_reference_positions))
|
||||
echohl WarningMsg
|
||||
echom 'search hit BOTTOM, continuing at TOP'
|
||||
echohl None
|
||||
endif
|
||||
|
||||
" Wrap index
|
||||
if l:index < 0 || l:index >= (s:use_vim_textprops ? len(b:lsp_reference_positions) : len(w:lsp_reference_positions))
|
||||
let l:index = (l:index % l:n + l:n) % l:n
|
||||
endif
|
||||
|
||||
" Jump
|
||||
let l:target = (s:use_vim_textprops ? b:lsp_reference_positions : w:lsp_reference_positions)[l:index][0:1]
|
||||
normal! m`
|
||||
call cursor(l:target[0], l:target[1])
|
||||
endfunction
|
||||
@@ -0,0 +1,278 @@
|
||||
" https://microsoft.github.io/language-server-protocol/specification#textDocument_hover
|
||||
|
||||
let s:Markdown = vital#lsp#import('VS.Vim.Syntax.Markdown')
|
||||
let s:MarkupContent = vital#lsp#import('VS.LSP.MarkupContent')
|
||||
let s:FloatingWindow = vital#lsp#import('VS.Vim.Window.FloatingWindow')
|
||||
let s:Window = vital#lsp#import('VS.Vim.Window')
|
||||
let s:Buffer = vital#lsp#import('VS.Vim.Buffer')
|
||||
|
||||
" options - {
|
||||
" server - 'server_name' " optional
|
||||
" ui - 'float' | 'preview'
|
||||
" }
|
||||
function! lsp#internal#document_hover#under_cursor#do(options) abort
|
||||
let l:bufnr = bufnr('%')
|
||||
let l:ui = get(a:options, 'ui', g:lsp_hover_ui)
|
||||
if empty(l:ui)
|
||||
let l:ui = s:FloatingWindow.is_available() ? 'float' : 'preview'
|
||||
endif
|
||||
|
||||
if l:ui ==# 'float'
|
||||
let l:doc_win = s:get_doc_win()
|
||||
if l:doc_win.is_visible()
|
||||
if bufnr('%') ==# l:doc_win.get_bufnr()
|
||||
call s:close_floating_window()
|
||||
else
|
||||
call l:doc_win.enter()
|
||||
inoremap <buffer><silent> <Plug>(lsp-float-close) <ESC>:<C-u>call <SID>close_floating_window()<CR>
|
||||
nnoremap <buffer><silent> <Plug>(lsp-float-close) :<C-u>call <SID>close_floating_window()<CR>
|
||||
execute('doautocmd <nomodeline> User lsp_float_focused')
|
||||
if !hasmapto('<Plug>(lsp-float-close)')
|
||||
imap <silent> <buffer> <C-c> <Plug>(lsp-float-close)
|
||||
nmap <silent> <buffer> <C-c> <Plug>(lsp-float-close)
|
||||
endif
|
||||
endif
|
||||
return
|
||||
endif
|
||||
endif
|
||||
|
||||
if has_key(a:options, 'server')
|
||||
let l:servers = [a:options['server']]
|
||||
else
|
||||
let l:servers = filter(lsp#get_allowed_servers(), 'lsp#capabilities#has_hover_provider(v:val)')
|
||||
endif
|
||||
|
||||
if len(l:servers) == 0
|
||||
let l:filetype = getbufvar(l:bufnr, '&filetype')
|
||||
call lsp#utils#error('textDocument/hover not supported for ' . l:filetype)
|
||||
return
|
||||
endif
|
||||
|
||||
redraw | echo 'Retrieving hover ...'
|
||||
|
||||
call lsp#_new_command()
|
||||
|
||||
" TODO: ask user to select server for formatting if there are multiple servers
|
||||
let l:request = {
|
||||
\ 'method': 'textDocument/hover',
|
||||
\ 'params': {
|
||||
\ 'textDocument': lsp#get_text_document_identifier(),
|
||||
\ 'position': lsp#get_position(),
|
||||
\ },
|
||||
\ }
|
||||
call lsp#callbag#pipe(
|
||||
\ lsp#callbag#fromList(l:servers),
|
||||
\ lsp#callbag#flatMap({server->
|
||||
\ lsp#request(server, l:request)
|
||||
\ }),
|
||||
\ lsp#callbag#tap({x->s:show_hover(l:ui, x['server_name'], x['request'], x['response'])}),
|
||||
\ lsp#callbag#takeUntil(lsp#callbag#pipe(
|
||||
\ lsp#stream(),
|
||||
\ lsp#callbag#filter({x->has_key(x, 'command')}),
|
||||
\ )),
|
||||
\ lsp#callbag#subscribe(),
|
||||
\ )
|
||||
endfunction
|
||||
|
||||
function! lsp#internal#document_hover#under_cursor#getpreviewwinid() abort
|
||||
if exists('s:doc_win')
|
||||
return s:doc_win.get_winid()
|
||||
endif
|
||||
return v:null
|
||||
endfunction
|
||||
|
||||
function! s:show_hover(ui, server_name, request, response) abort
|
||||
if !has_key(a:response, 'result') || empty(a:response['result']) ||
|
||||
\ empty(a:response['result']['contents'])
|
||||
call lsp#utils#error('No hover information found in server - ' . a:server_name)
|
||||
return
|
||||
endif
|
||||
|
||||
echo ''
|
||||
|
||||
if s:FloatingWindow.is_available() && a:ui ==? 'float'
|
||||
call s:show_floating_window(a:server_name, a:request, a:response)
|
||||
else
|
||||
call s:show_preview_window(a:server_name, a:request, a:response)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:show_preview_window(server_name, request, response) abort
|
||||
let l:contents = s:get_contents(a:response['result']['contents'])
|
||||
|
||||
" Ignore if contents is empty.
|
||||
if empty(l:contents)
|
||||
call lsp#utils#error('Empty contents for LspHover')
|
||||
return
|
||||
endif
|
||||
|
||||
let l:lines = lsp#utils#_split_by_eol(join(l:contents, "\n\n"))
|
||||
let l:view = winsaveview()
|
||||
let l:alternate=@#
|
||||
silent! pclose
|
||||
sp LspHoverPreview
|
||||
execute 'resize '.min([len(l:lines), &previewheight])
|
||||
set previewwindow
|
||||
setlocal conceallevel=2
|
||||
setlocal bufhidden=hide
|
||||
setlocal nobuflisted
|
||||
setlocal buftype=nofile
|
||||
setlocal noswapfile
|
||||
%d _
|
||||
call setline(1, l:lines)
|
||||
call s:Window.do(win_getid(), {->s:Markdown.apply()})
|
||||
execute "normal \<c-w>p"
|
||||
call winrestview(l:view)
|
||||
let @#=l:alternate
|
||||
endfunction
|
||||
|
||||
function! s:show_floating_window(server_name, request, response) abort
|
||||
call s:close_floating_window()
|
||||
|
||||
let l:contents = s:get_contents(a:response['result']['contents'])
|
||||
|
||||
" Ignore if contents is empty.
|
||||
if empty(l:contents)
|
||||
return s:close_floating_window()
|
||||
endif
|
||||
|
||||
" Update contents.
|
||||
let l:doc_win = s:get_doc_win()
|
||||
silent! call deletebufline(l:doc_win.get_bufnr(), 1, '$')
|
||||
call setbufline(l:doc_win.get_bufnr(), 1, lsp#utils#_split_by_eol(join(l:contents, "\n\n")))
|
||||
|
||||
" Calculate layout.
|
||||
if g:lsp_float_max_width >= 1
|
||||
let l:maxwidth = g:lsp_float_max_width
|
||||
elseif g:lsp_float_max_width == 0
|
||||
let l:maxwidth = &columns
|
||||
else
|
||||
let l:maxwidth = float2nr(&columns * 0.4)
|
||||
endif
|
||||
let l:size = l:doc_win.get_size({
|
||||
\ 'maxwidth': l:maxwidth,
|
||||
\ 'maxheight': float2nr(&lines * 0.4),
|
||||
\ })
|
||||
let l:pos = s:compute_position(l:size)
|
||||
if empty(l:pos)
|
||||
call s:close_floating_window()
|
||||
return
|
||||
endif
|
||||
|
||||
execute printf('augroup vim_lsp_hover_close_on_move_%d', bufnr('%'))
|
||||
autocmd!
|
||||
execute printf('autocmd InsertEnter,BufLeave,CursorMoved <buffer> call s:close_floating_window_on_move(%s)', getcurpos())
|
||||
augroup END
|
||||
|
||||
" Show popupmenu and apply markdown syntax.
|
||||
call l:doc_win.open({
|
||||
\ 'row': l:pos[0],
|
||||
\ 'col': l:pos[1],
|
||||
\ 'width': l:size.width,
|
||||
\ 'height': l:size.height,
|
||||
\ 'border': v:true,
|
||||
\ })
|
||||
call s:Window.do(l:doc_win.get_winid(), { -> s:Markdown.apply() })
|
||||
|
||||
" Format contents to fit window
|
||||
call setbufvar(l:doc_win.get_bufnr(), '&textwidth', l:size.width)
|
||||
call s:Window.do(l:doc_win.get_winid(), { -> s:format_window() })
|
||||
endfunction
|
||||
|
||||
function! s:format_window() abort
|
||||
global/^/normal! gqgq
|
||||
endfunction
|
||||
|
||||
function! s:get_contents(contents) abort
|
||||
if type(a:contents) == type('')
|
||||
return [a:contents]
|
||||
elseif type(a:contents) == type([])
|
||||
let l:result = []
|
||||
for l:content in a:contents
|
||||
let l:result += s:get_contents(l:content)
|
||||
endfor
|
||||
return l:result
|
||||
elseif type(a:contents) == type({})
|
||||
if has_key(a:contents, 'value')
|
||||
if has_key(a:contents, 'kind')
|
||||
if a:contents['kind'] ==? 'markdown'
|
||||
let l:detail = s:MarkupContent.normalize(a:contents['value'], {
|
||||
\ 'compact': !g:lsp_preview_fixup_conceal
|
||||
\ })
|
||||
return [l:detail]
|
||||
else
|
||||
return [a:contents['value']]
|
||||
endif
|
||||
elseif has_key(a:contents, 'language')
|
||||
let l:detail = s:MarkupContent.normalize(a:contents, {
|
||||
\ 'compact': !g:lsp_preview_fixup_conceal
|
||||
\ })
|
||||
return [l:detail]
|
||||
else
|
||||
return ''
|
||||
endif
|
||||
else
|
||||
return ''
|
||||
endif
|
||||
else
|
||||
return ''
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:close_floating_window() abort
|
||||
call s:get_doc_win().close()
|
||||
endfunction
|
||||
|
||||
function! s:close_floating_window_on_move(curpos) abort
|
||||
if a:curpos != getcurpos() | call s:close_floating_window() | endif
|
||||
endf
|
||||
|
||||
function! s:on_opened() abort
|
||||
inoremap <buffer><silent> <Plug>(lsp-float-close) <ESC>:<C-u>call <SID>close_floating_window()<CR>
|
||||
nnoremap <buffer><silent> <Plug>(lsp-float-close) :<C-u>call <SID>close_floating_window()<CR>
|
||||
execute('doautocmd <nomodeline> User lsp_float_opened')
|
||||
if !hasmapto('<Plug>(lsp-float-close)')
|
||||
imap <silent> <buffer> <C-c> <Plug>(lsp-float-close)
|
||||
nmap <silent> <buffer> <C-c> <Plug>(lsp-float-close)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:on_closed() abort
|
||||
execute('doautocmd <nomodeline> User lsp_float_closed')
|
||||
endfunction
|
||||
|
||||
function! s:get_doc_win() abort
|
||||
if exists('s:doc_win')
|
||||
return s:doc_win
|
||||
endif
|
||||
|
||||
let s:doc_win = s:FloatingWindow.new({
|
||||
\ 'on_opened': function('s:on_opened'),
|
||||
\ 'on_closed': function('s:on_closed')
|
||||
\ })
|
||||
call s:doc_win.set_var('&wrap', 1)
|
||||
call s:doc_win.set_var('&conceallevel', 2)
|
||||
call s:doc_win.set_bufnr(s:Buffer.create())
|
||||
call setbufvar(s:doc_win.get_bufnr(), '&buftype', 'nofile')
|
||||
call setbufvar(s:doc_win.get_bufnr(), '&bufhidden', 'hide')
|
||||
call setbufvar(s:doc_win.get_bufnr(), '&buflisted', 0)
|
||||
call setbufvar(s:doc_win.get_bufnr(), '&swapfile', 0)
|
||||
return s:doc_win
|
||||
endfunction
|
||||
|
||||
function! s:compute_position(size) abort
|
||||
let l:pos = screenpos(0, line('.'), col('.'))
|
||||
if l:pos.row == 0 && l:pos.col == 0
|
||||
" workaround for float position
|
||||
let l:pos = {'curscol': wincol(), 'row': winline()}
|
||||
endif
|
||||
let l:pos = [l:pos.row + 1, l:pos.curscol + 1]
|
||||
if l:pos[0] + a:size.height > &lines
|
||||
let l:pos[0] = l:pos[0] - a:size.height - 3
|
||||
endif
|
||||
if l:pos[1] + a:size.width > &columns
|
||||
let l:pos[1] = l:pos[1] - a:size.width - 3
|
||||
endif
|
||||
return l:pos
|
||||
endfunction
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
" options - {
|
||||
" bufnr: bufnr('%') " required
|
||||
" type: '' " optional: defaults to visualmode(). overridden by opfunc
|
||||
" server - 'server_name' " optional
|
||||
" sync: 0 " optional, defaults to 0 (async)
|
||||
" }
|
||||
function! lsp#internal#document_range_formatting#format(options) abort
|
||||
if has_key(a:options, 'server')
|
||||
let l:servers = [a:options['server']]
|
||||
else
|
||||
let l:servers = filter(lsp#get_allowed_servers(), 'lsp#capabilities#has_document_range_formatting_provider(v:val)')
|
||||
endif
|
||||
|
||||
if len(l:servers) == 0
|
||||
let l:filetype = getbufvar(a:options['bufnr'], '&filetype')
|
||||
call lsp#utils#error('textDocument/rangeFormatting not supported for ' . l:filetype)
|
||||
return
|
||||
endif
|
||||
|
||||
" TODO: ask user to select server for formatting if there are multiple servers
|
||||
let l:server = l:servers[0]
|
||||
|
||||
redraw | echo 'Formatting Document Range ...'
|
||||
|
||||
call lsp#_new_command()
|
||||
|
||||
let [l:start_lnum, l:start_col, l:end_lnum, l:end_col] = s:get_selection_pos(get(a:options, 'type', visualmode()))
|
||||
let l:start_char = lsp#utils#to_char('%', l:start_lnum, l:start_col)
|
||||
let l:end_char = lsp#utils#to_char('%', l:end_lnum, l:end_col)
|
||||
|
||||
let l:request = {
|
||||
\ 'method': 'textDocument/rangeFormatting',
|
||||
\ 'params': {
|
||||
\ 'textDocument': lsp#get_text_document_identifier(a:options['bufnr']),
|
||||
\ 'range': {
|
||||
\ 'start': { 'line': l:start_lnum - 1, 'character': l:start_char },
|
||||
\ 'end': { 'line': l:end_lnum - 1, 'character': l:end_char },
|
||||
\ },
|
||||
\ 'options': {
|
||||
\ 'tabSize': lsp#utils#buffer#get_indent_size(a:options['bufnr']),
|
||||
\ 'insertSpaces': getbufvar(a:options['bufnr'], '&expandtab') ? v:true : v:false,
|
||||
\ }
|
||||
\ },
|
||||
\ 'bufnr': a:options['bufnr'],
|
||||
\ }
|
||||
|
||||
if get(a:options, 'sync', 0) == 1
|
||||
try
|
||||
let l:x = lsp#callbag#pipe(
|
||||
\ lsp#request(l:server, l:request),
|
||||
\ lsp#callbag#takeUntil(lsp#callbag#pipe(
|
||||
\ lsp#stream(),
|
||||
\ lsp#callbag#filter({x->has_key(x, 'command')}),
|
||||
\ )),
|
||||
\ lsp#callbag#toList(),
|
||||
\ ).wait({ 'sleep': get(a:options, 'sleep', 1), 'timeout': get(a:options, 'timeout', g:lsp_format_sync_timeout) })
|
||||
call s:format_next(l:x[0])
|
||||
call s:format_complete()
|
||||
catch
|
||||
call s:format_error(v:exception . ' ' . v:throwpoint)
|
||||
endtry
|
||||
else
|
||||
return lsp#callbag#pipe(
|
||||
\ lsp#request(l:server, l:request),
|
||||
\ lsp#callbag#takeUntil(lsp#callbag#pipe(
|
||||
\ lsp#stream(),
|
||||
\ lsp#callbag#filter({x->has_key(x, 'command')}),
|
||||
\ )),
|
||||
\ lsp#callbag#subscribe({
|
||||
\ 'next':{x->s:format_next(x)},
|
||||
\ 'error': {x->s:format_error(e)},
|
||||
\ 'complete': {->s:format_complete()},
|
||||
\ }),
|
||||
\ )
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:format_next(x) abort
|
||||
if lsp#client#is_error(a:x['response']) | return | endif
|
||||
call lsp#utils#text_edit#apply_text_edits(a:x['request']['params']['textDocument']['uri'], a:x['response']['result'])
|
||||
endfunction
|
||||
|
||||
function! s:format_error(e) abort
|
||||
call lsp#log('Formatting Document Range Failed', a:e)
|
||||
call lsp#utils#error('Formatting Document Range Failed.' . (type(a:e) == type('') ? a:e : ''))
|
||||
endfunction
|
||||
|
||||
function! s:format_complete() abort
|
||||
redraw | echo 'Formatting Document Range complete'
|
||||
endfunction
|
||||
|
||||
function! s:get_selection_pos(type) abort
|
||||
" TODO: support bufnr
|
||||
if a:type ==? 'v'
|
||||
let l:start_pos = getpos("'<")[1:2]
|
||||
let l:end_pos = getpos("'>")[1:2]
|
||||
" fix end_pos column (see :h getpos() and :h 'selection')
|
||||
let l:end_line = getline(l:end_pos[0])
|
||||
let l:offset = (&selection ==# 'inclusive' ? 1 : 2)
|
||||
let l:end_pos[1] = len(l:end_line[:l:end_pos[1]-l:offset])
|
||||
" edge case: single character selected with selection=exclusive
|
||||
if l:start_pos[0] == l:end_pos[0] && l:start_pos[1] > l:end_pos[1]
|
||||
let l:end_pos[1] = l:start_pos[1]
|
||||
endif
|
||||
elseif a:type ==? 'line'
|
||||
let l:start_pos = [line("'["), 1]
|
||||
let l:end_lnum = line("']")
|
||||
let l:end_pos = [line("']"), len(getline(l:end_lnum))]
|
||||
elseif a:type ==? 'char'
|
||||
let l:start_pos = getpos("'[")[1:2]
|
||||
let l:end_pos = getpos("']")[1:2]
|
||||
else
|
||||
let l:start_pos = [0, 0]
|
||||
let l:end_pos = [0, 0]
|
||||
endif
|
||||
|
||||
return l:start_pos + l:end_pos
|
||||
endfunction
|
||||
|
||||
function! lsp#internal#document_range_formatting#opfunc(type) abort
|
||||
call lsp#internal#document_range_formatting#format({
|
||||
\ 'type': a:type,
|
||||
\ 'bufnr': bufnr('%'),
|
||||
\ })
|
||||
endfunction
|
||||
@@ -0,0 +1,76 @@
|
||||
" https://microsoft.github.io/language-server-protocol/specification#textDocument_documentSymbol
|
||||
" options - {
|
||||
" bufnr: bufnr('%') " optional
|
||||
" server - 'server_name' " optional
|
||||
" }
|
||||
function! lsp#internal#document_symbol#search#do(options) abort
|
||||
let l:bufnr = get(a:options, 'bufnr', bufnr('%'))
|
||||
if has_key(a:options, 'server')
|
||||
let l:servers = [a:options['server']]
|
||||
else
|
||||
let l:servers = filter(lsp#get_allowed_servers(), 'lsp#capabilities#has_document_symbol_provider(v:val)')
|
||||
endif
|
||||
|
||||
if len(l:servers) == 0
|
||||
let l:filetype = getbufvar(l:bufnr, '&filetype')
|
||||
call lsp#utils#error('textDocument/documentSymbol not supported for ' . l:filetype)
|
||||
return
|
||||
endif
|
||||
|
||||
redraw | echo 'Retrieving document symbols ...'
|
||||
|
||||
call lsp#internal#ui#quickpick#open({
|
||||
\ 'items': [],
|
||||
\ 'busy': 1,
|
||||
\ 'input': '',
|
||||
\ 'key': 'text',
|
||||
\ 'on_accept': function('s:on_accept'),
|
||||
\ 'on_close': function('s:on_close'),
|
||||
\ })
|
||||
|
||||
let s:Dispose = lsp#callbag#pipe(
|
||||
\ lsp#callbag#fromList(l:servers),
|
||||
\ lsp#callbag#flatMap({server->
|
||||
\ lsp#callbag#pipe(
|
||||
\ lsp#request(server, {
|
||||
\ 'method': 'textDocument/documentSymbol',
|
||||
\ 'params': {
|
||||
\ 'textDocument': lsp#get_text_document_identifier(l:bufnr),
|
||||
\ },
|
||||
\ }),
|
||||
\ lsp#callbag#map({x->{'server': server, 'request': x['request'], 'response': x['response']}}),
|
||||
\ )
|
||||
\ }),
|
||||
\ lsp#callbag#scan({acc, curr->add(acc, curr)}, []),
|
||||
\ lsp#callbag#tap({x->s:update_ui_items(x)}),
|
||||
\ lsp#callbag#subscribe({
|
||||
\ 'complete':{->lsp#internal#ui#quickpick#busy(0)},
|
||||
\ 'error':{e->s:on_error(e)},
|
||||
\ }),
|
||||
\ )
|
||||
endfunction
|
||||
|
||||
function! s:update_ui_items(x) abort
|
||||
let l:items = []
|
||||
for l:i in a:x
|
||||
let l:items += lsp#ui#vim#utils#symbols_to_loc_list(l:i['server'], l:i)
|
||||
endfor
|
||||
call lsp#internal#ui#quickpick#items(l:items)
|
||||
endfunction
|
||||
|
||||
function! s:on_accept(data, ...) abort
|
||||
call lsp#internal#ui#quickpick#close()
|
||||
call lsp#utils#location#_open_vim_list_item(a:data['items'][0], '')
|
||||
endfunction
|
||||
|
||||
function! s:on_close(...) abort
|
||||
if exists('s:Dispose')
|
||||
call s:Dispose()
|
||||
unlet s:Dispose
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:on_error(e) abort
|
||||
call lsp#internal#ui#quickpick#close()
|
||||
call lsp#log('LspDocumentSymbolSearch error', a:e)
|
||||
endfunction
|
||||
127
dot_vim/plugged/vim-lsp/autoload/lsp/internal/inlay_hints.vim
Normal file
127
dot_vim/plugged/vim-lsp/autoload/lsp/internal/inlay_hints.vim
Normal file
@@ -0,0 +1,127 @@
|
||||
let s:use_vim_textprops = lsp#utils#_has_vim_virtual_text() && !has('nvim')
|
||||
|
||||
function! s:set_inlay_hints(data) abort
|
||||
let l:bufnr = bufnr('%')
|
||||
|
||||
call s:clear_inlay_hints()
|
||||
|
||||
if mode() !=# 'n' | return | endif
|
||||
|
||||
if lsp#client#is_error(a:data['response']) | return | endif
|
||||
|
||||
" Get hints from the response
|
||||
let l:hints = a:data['response']['result']
|
||||
if empty(l:hints)
|
||||
return
|
||||
endif
|
||||
|
||||
let l:not_curline = s:has_inlay_hints_mode('!curline')
|
||||
for l:hint in l:hints
|
||||
if l:not_curline && l:hint.position.line+1 ==# line('.')
|
||||
continue
|
||||
endif
|
||||
let l:label = ''
|
||||
if type(l:hint.label) ==# v:t_list
|
||||
let l:label = join(map(copy(l:hint.label), {_,v -> v.value}), '')
|
||||
else
|
||||
let l:label = l:hint.label
|
||||
endif
|
||||
let l:text = (get(l:hint, 'paddingLeft', v:false) ? ' ' : '') . l:label . (get(l:hint, 'paddingRight', v:false) ? ' ' : '')
|
||||
if !has_key(l:hint, 'kind') || l:hint.kind ==# 1
|
||||
call prop_add(l:hint.position.line+1, l:hint.position.character+1, {'type': 'vim_lsp_inlay_hint_type', 'text': l:text, 'bufnr': l:bufnr})
|
||||
elseif l:hint.kind ==# 2
|
||||
call prop_add(l:hint.position.line+1, l:hint.position.character+1, {'type': 'vim_lsp_inlay_hint_parameter', 'text': l:text, 'bufnr': l:bufnr})
|
||||
endif
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! s:init_inlay_hints() abort
|
||||
if index(prop_type_list(), 'vim_lsp_inlay_hint_type') ==# -1
|
||||
call prop_type_add('vim_lsp_inlay_hint_type', { 'highlight': 'lspInlayHintsType' })
|
||||
call prop_type_add('vim_lsp_inlay_hint_parameter', { 'highlight': 'lspInlayHintsParameter' })
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! lsp#internal#inlay_hints#_disable() abort
|
||||
if exists('s:Dispose')
|
||||
call s:Dispose()
|
||||
unlet s:Dispose
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:clear_inlay_hints() abort
|
||||
let l:bufnr = bufnr('%')
|
||||
call prop_remove({'type': 'vim_lsp_inlay_hint_type', 'bufnr': l:bufnr, 'all': v:true})
|
||||
call prop_remove({'type': 'vim_lsp_inlay_hint_parameter', 'bufnr': l:bufnr, 'all': v:true})
|
||||
endfunction
|
||||
|
||||
function! s:has_inlay_hints_mode(value) abort
|
||||
let l:m = get(g:, 'lsp_inlay_hints_mode', {})
|
||||
if type(l:m) != v:t_dict | return v:false | endif
|
||||
if mode() ==# 'i'
|
||||
let l:a = get(l:m, 'insert', [])
|
||||
elseif mode() ==# 'n'
|
||||
let l:a = get(l:m, 'normal', [])
|
||||
else
|
||||
return v:false
|
||||
endif
|
||||
if type(l:a) != v:t_list | return v:false | endif
|
||||
return index(l:a, a:value) != -1 ? v:true : v:false
|
||||
endfunction
|
||||
|
||||
function! s:send_inlay_hints_request() abort
|
||||
let l:capability = 'lsp#capabilities#has_inlay_hint_provider(v:val)'
|
||||
let l:servers = filter(lsp#get_allowed_servers(), l:capability)
|
||||
|
||||
if empty(l:servers)
|
||||
return lsp#callbag#empty()
|
||||
endif
|
||||
|
||||
if s:has_inlay_hints_mode('curline')
|
||||
let l:range = lsp#utils#range#get_range_curline()
|
||||
else
|
||||
let l:range = lsp#utils#range#get_range()
|
||||
endif
|
||||
return lsp#request(l:servers[0], {
|
||||
\ 'method': 'textDocument/inlayHint',
|
||||
\ 'params': {
|
||||
\ 'textDocument': lsp#get_text_document_identifier(),
|
||||
\ 'range': l:range,
|
||||
\ },
|
||||
\ })
|
||||
endfunction
|
||||
|
||||
function! lsp#internal#inlay_hints#_enable() abort
|
||||
if !s:use_vim_textprops | return | endif
|
||||
if !g:lsp_inlay_hints_enabled | return | endif
|
||||
|
||||
if !hlexists('lspInlayHintsType')
|
||||
highlight link lspInlayHintsType Label
|
||||
endif
|
||||
if !hlexists('lspInlayHintsParameter')
|
||||
highlight link lspInlayHintsParameter Todo
|
||||
endif
|
||||
|
||||
call s:init_inlay_hints()
|
||||
let s:Dispose = lsp#callbag#pipe(
|
||||
\ lsp#callbag#merge(
|
||||
\ lsp#callbag#fromEvent(['CursorMoved', 'CursorHold']),
|
||||
\ lsp#callbag#pipe(
|
||||
\ lsp#callbag#fromEvent(['InsertEnter', 'BufLeave']),
|
||||
\ lsp#callbag#tap({_ -> s:clear_inlay_hints() }),
|
||||
\ )
|
||||
\ ),
|
||||
\ lsp#callbag#filter({_ -> g:lsp_inlay_hints_enabled }),
|
||||
\ lsp#callbag#debounceTime(g:lsp_inlay_hints_delay),
|
||||
\ lsp#callbag#filter({_->getbufvar(bufnr('%'), '&buftype') !~# '^(help\|terminal\|prompt\|popup)$'}),
|
||||
\ lsp#callbag#switchMap({_->
|
||||
\ lsp#callbag#pipe(
|
||||
\ s:send_inlay_hints_request(),
|
||||
\ lsp#callbag#materialize(),
|
||||
\ lsp#callbag#filter({x->lsp#callbag#isNextNotification(x)}),
|
||||
\ lsp#callbag#map({x->x['value']})
|
||||
\ )
|
||||
\ }),
|
||||
\ lsp#callbag#subscribe({x->s:set_inlay_hints(x)}),
|
||||
\)
|
||||
endfunction
|
||||
411
dot_vim/plugged/vim-lsp/autoload/lsp/internal/semantic.vim
Normal file
411
dot_vim/plugged/vim-lsp/autoload/lsp/internal/semantic.vim
Normal file
@@ -0,0 +1,411 @@
|
||||
let s:use_vim_textprops = lsp#utils#_has_textprops() && !has('nvim')
|
||||
let s:use_nvim_highlight = lsp#utils#_has_nvim_buf_highlight()
|
||||
let s:textprop_cache = 'vim-lsp-semantic-cache'
|
||||
|
||||
if s:use_nvim_highlight
|
||||
let s:namespace_id = nvim_create_namespace('vim-lsp-semantic')
|
||||
endif
|
||||
|
||||
" Global functions {{{1
|
||||
function! lsp#internal#semantic#is_enabled() abort
|
||||
return g:lsp_semantic_enabled && (s:use_vim_textprops || s:use_nvim_highlight) ? v:true : v:false
|
||||
endfunction
|
||||
|
||||
function! lsp#internal#semantic#_enable() abort
|
||||
if !lsp#internal#semantic#is_enabled() | return | endif
|
||||
|
||||
augroup lsp#internal#semantic
|
||||
autocmd!
|
||||
au User lsp_buffer_enabled call s:on_lsp_buffer_enabled()
|
||||
augroup END
|
||||
|
||||
let l:events = [['User', 'lsp_buffer_enabled'], 'TextChanged', 'TextChangedI']
|
||||
if exists('##TextChangedP')
|
||||
call add(l:events, 'TextChangedP')
|
||||
endif
|
||||
let s:Dispose = lsp#callbag#pipe(
|
||||
\ lsp#callbag#fromEvent(l:events),
|
||||
\ lsp#callbag#filter({_->lsp#internal#semantic#is_enabled()}),
|
||||
\ lsp#callbag#debounceTime(g:lsp_semantic_delay),
|
||||
\ lsp#callbag#filter({_->index(['help', 'terminal', 'prompt', 'popup'], getbufvar(bufnr('%'), '&buftype')) ==# -1}),
|
||||
\ lsp#callbag#filter({_->!lsp#utils#is_large_window(win_getid())}),
|
||||
\ lsp#callbag#switchMap({_->
|
||||
\ lsp#callbag#pipe(
|
||||
\ s:semantic_request(),
|
||||
\ lsp#callbag#materialize(),
|
||||
\ lsp#callbag#filter({x->lsp#callbag#isNextNotification(x)}),
|
||||
\ lsp#callbag#map({x->x['value']})
|
||||
\ )
|
||||
\ }),
|
||||
\ lsp#callbag#subscribe({x->s:handle_semantic_request(x)})
|
||||
\ )
|
||||
endfunction
|
||||
|
||||
function! lsp#internal#semantic#_disable() abort
|
||||
augroup lsp#internal#semantic
|
||||
autocmd!
|
||||
augroup END
|
||||
|
||||
if exists('s:Dispose')
|
||||
call s:Dispose()
|
||||
unlet s:Dispose
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! lsp#internal#semantic#get_legend(server) abort
|
||||
if !lsp#capabilities#has_semantic_tokens(a:server)
|
||||
return {'tokenTypes': [], 'tokenModifiers': []}
|
||||
endif
|
||||
|
||||
let l:capabilities = lsp#get_server_capabilities(a:server)
|
||||
return l:capabilities['semanticTokensProvider']['legend']
|
||||
endfunction
|
||||
|
||||
function! lsp#internal#semantic#get_token_types() abort
|
||||
let l:capability = 'lsp#capabilities#has_semantic_tokens(v:val)'
|
||||
let l:servers = filter(lsp#get_allowed_servers(), l:capability)
|
||||
|
||||
if empty(l:servers)
|
||||
return []
|
||||
endif
|
||||
|
||||
let l:legend = lsp#internal#semantic#get_legend(l:servers[0])
|
||||
let l:token_types = l:legend['tokenTypes']
|
||||
call map(l:token_types, {_, type -> toupper(type[0]) . type[1:]})
|
||||
return l:token_types
|
||||
endfunction
|
||||
|
||||
function! lsp#internal#semantic#get_token_modifiers() abort
|
||||
let l:capability = 'lsp#capabilities#has_semantic_tokens(v:val)'
|
||||
let l:servers = filter(lsp#get_allowed_servers(), l:capability)
|
||||
|
||||
if empty(l:servers)
|
||||
return []
|
||||
endif
|
||||
|
||||
let l:legend = lsp#internal#semantic#get_legend(l:servers[0])
|
||||
let l:token_modifiers = l:legend['tokenModifiers']
|
||||
call map(l:token_modifiers, {_, modifier -> toupper(modifier[0]) . modifier[1:]})
|
||||
return l:token_modifiers
|
||||
endfunction
|
||||
|
||||
function! s:on_lsp_buffer_enabled() abort
|
||||
augroup lsp#internal#semantic
|
||||
if !exists('#BufUnload#<buffer>')
|
||||
execute 'au BufUnload <buffer> call setbufvar(' . bufnr() . ', ''lsp_semantic_previous_result_id'', '''')'
|
||||
endif
|
||||
augroup END
|
||||
endfunction
|
||||
|
||||
function! s:supports_full_semantic_request(server) abort
|
||||
if !lsp#capabilities#has_semantic_tokens(a:server)
|
||||
return v:false
|
||||
endif
|
||||
|
||||
let l:capabilities = lsp#get_server_capabilities(a:server)['semanticTokensProvider']
|
||||
if !has_key(l:capabilities, 'full')
|
||||
return v:false
|
||||
endif
|
||||
|
||||
if type(l:capabilities['full']) ==# v:t_dict
|
||||
return v:true
|
||||
endif
|
||||
|
||||
return l:capabilities['full']
|
||||
endfunction
|
||||
|
||||
function! s:supports_delta_semantic_request(server) abort
|
||||
if !lsp#capabilities#has_semantic_tokens(a:server)
|
||||
return v:false
|
||||
endif
|
||||
|
||||
let l:capabilities = lsp#get_server_capabilities(a:server)['semanticTokensProvider']
|
||||
if !has_key(l:capabilities, 'full')
|
||||
return v:false
|
||||
endif
|
||||
|
||||
if type(l:capabilities['full']) !=# v:t_dict
|
||||
return v:false
|
||||
endif
|
||||
|
||||
if !has_key(l:capabilities['full'], 'delta')
|
||||
return v:false
|
||||
endif
|
||||
|
||||
return l:capabilities['full']['delta']
|
||||
endfunction
|
||||
|
||||
function! s:get_server() abort
|
||||
let l:capability = 's:supports_delta_semantic_request(v:val)'
|
||||
let l:servers = filter(lsp#get_allowed_servers(), l:capability)
|
||||
if empty(l:servers)
|
||||
let l:capability = 's:supports_full_semantic_request(v:val)'
|
||||
let l:servers = filter(lsp#get_allowed_servers(), l:capability)
|
||||
endif
|
||||
if empty(l:servers)
|
||||
return ''
|
||||
endif
|
||||
return l:servers[0]
|
||||
endfunction
|
||||
|
||||
function! s:semantic_request() abort
|
||||
let l:server = s:get_server()
|
||||
if l:server ==# ''
|
||||
return lsp#callbag#empty()
|
||||
endif
|
||||
|
||||
if (s:supports_delta_semantic_request(l:server)
|
||||
\ && getbufvar(bufnr(), 'lsp_semantic_previous_result_id') !=# '')
|
||||
return s:delta_semantic_request(l:server)
|
||||
else
|
||||
return s:full_semantic_request(l:server)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:full_semantic_request(server) abort
|
||||
return lsp#request(a:server, {
|
||||
\ 'method': 'textDocument/semanticTokens/full',
|
||||
\ 'params': {
|
||||
\ 'textDocument': lsp#get_text_document_identifier()
|
||||
\ }})
|
||||
endfunction
|
||||
|
||||
function! s:delta_semantic_request(server) abort
|
||||
return lsp#request(a:server, {
|
||||
\ 'method': 'textDocument/semanticTokens/full/delta',
|
||||
\ 'params': {
|
||||
\ 'textDocument': lsp#get_text_document_identifier(),
|
||||
\ 'previousResultId': getbufvar(bufname(), 'lsp_semantic_previous_result_id', 0)
|
||||
\ }})
|
||||
endfunction
|
||||
|
||||
" Highlight helper functions {{{1
|
||||
function! s:handle_semantic_request(data) abort
|
||||
if lsp#client#is_error(a:data['response'])
|
||||
call lsp#log('Skipping semantic highlight: response is invalid')
|
||||
return
|
||||
endif
|
||||
|
||||
let l:server = a:data['server_name']
|
||||
let l:uri = a:data['request']['params']['textDocument']['uri']
|
||||
let l:path = lsp#utils#uri_to_path(l:uri)
|
||||
let l:bufnr = bufnr(l:path)
|
||||
|
||||
" Skip if the buffer doesn't exist. This might happen when a buffer is
|
||||
" opened and quickly deleted.
|
||||
if !bufloaded(l:bufnr) | return | endif
|
||||
|
||||
if has_key(a:data['response']['result'], 'data')
|
||||
call s:handle_semantic_tokens_response(l:server, l:bufnr, a:data['response']['result'])
|
||||
elseif has_key(a:data['response']['result'], 'edits')
|
||||
call s:handle_semantic_tokens_delta_response(l:server, l:bufnr, a:data['response']['result'])
|
||||
else
|
||||
" Don't update previous result ID if we could not update local copy
|
||||
call lsp#log('SemanticHighlight: unsupported semanticTokens method')
|
||||
return
|
||||
endif
|
||||
|
||||
if has_key(a:data['response']['result'], 'resultId')
|
||||
call setbufvar(l:bufnr, 'lsp_semantic_previous_result_id', a:data['response']['result']['resultId'])
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:handle_semantic_tokens_response(server, buf, result) abort
|
||||
let l:highlights = {}
|
||||
let l:legend = lsp#internal#semantic#get_legend(a:server)
|
||||
for l:token in s:decode_tokens(a:result['data'])
|
||||
let [l:key, l:value] = s:add_highlight(a:server, l:legend, a:buf, l:token)
|
||||
let l:highlights[l:key] = get(l:highlights, l:key, []) + l:value
|
||||
endfor
|
||||
call s:apply_highlights(a:server, a:buf, l:highlights)
|
||||
|
||||
call setbufvar(a:buf, 'lsp_semantic_local_data', a:result['data'])
|
||||
endfunction
|
||||
|
||||
function! s:startpos_compare(edit1, edit2) abort
|
||||
return a:edit1[0] == a:edit2[0] ? 0 : a:edit1[0] > a:edit2[0] ? -1 : 1
|
||||
endfunction
|
||||
|
||||
function! s:handle_semantic_tokens_delta_response(server, buf, result) abort
|
||||
" The locations given in the edit are all referenced to the state before
|
||||
" any are applied and sorting is not required from the server,
|
||||
" therefore the edits must be sorted before applying.
|
||||
let l:edits = a:result['edits']
|
||||
call sort(l:edits, function('s:startpos_compare'))
|
||||
|
||||
let l:localdata = getbufvar(a:buf, 'lsp_semantic_local_data')
|
||||
for l:edit in l:edits
|
||||
let l:insertdata = get(l:edit, 'data', [])
|
||||
let l:localdata = l:localdata[:l:edit['start'] - 1]
|
||||
\ + l:insertdata
|
||||
\ + l:localdata[l:edit['start'] + l:edit['deleteCount']:]
|
||||
endfor
|
||||
call setbufvar(a:buf, 'lsp_semantic_local_data', l:localdata)
|
||||
|
||||
let l:highlights = {}
|
||||
let l:legend = lsp#internal#semantic#get_legend(a:server)
|
||||
for l:token in s:decode_tokens(l:localdata)
|
||||
let [l:key, l:value] = s:add_highlight(a:server, l:legend, a:buf, l:token)
|
||||
let l:highlights[l:key] = get(l:highlights, l:key, []) + l:value
|
||||
endfor
|
||||
call s:apply_highlights(a:server, a:buf, l:highlights)
|
||||
endfunction
|
||||
|
||||
function! s:decode_tokens(data) abort
|
||||
let l:tokens = []
|
||||
|
||||
let l:i = 0
|
||||
let l:line = 0
|
||||
let l:char = 0
|
||||
while l:i < len(a:data)
|
||||
let l:line = l:line + a:data[l:i]
|
||||
if a:data[l:i] > 0
|
||||
let l:char = 0
|
||||
endif
|
||||
let l:char = l:char + a:data[l:i + 1]
|
||||
|
||||
call add(l:tokens, {
|
||||
\ 'pos': {'line': l:line, 'character': l:char},
|
||||
\ 'length': a:data[l:i + 2],
|
||||
\ 'token_idx': a:data[l:i + 3],
|
||||
\ 'token_modifiers': a:data[l:i + 4]
|
||||
\ })
|
||||
|
||||
let l:i = l:i + 5
|
||||
endwhile
|
||||
|
||||
return l:tokens
|
||||
endfunction
|
||||
|
||||
function! s:clear_highlights(server, buf) abort
|
||||
if s:use_vim_textprops
|
||||
let l:BeginsWith = {str, prefix -> str[0:len(prefix) - 1] ==# prefix}
|
||||
let l:IsSemanticTextprop = {_, textprop -> l:BeginsWith(textprop, s:textprop_type_prefix)}
|
||||
let l:textprop_types = prop_type_list()
|
||||
call filter(l:textprop_types, l:IsSemanticTextprop)
|
||||
for l:textprop_type in l:textprop_types
|
||||
silent! call prop_remove({'type': l:textprop_type, 'bufnr': a:buf, 'all': v:true})
|
||||
endfor
|
||||
elseif s:use_nvim_highlight
|
||||
call nvim_buf_clear_namespace(a:buf, s:namespace_id, 0, line('$'))
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:add_highlight(server, legend, buf, token) abort
|
||||
let l:startpos = lsp#utils#position#lsp_to_vim(a:buf, a:token['pos'])
|
||||
let l:endpos = a:token['pos']
|
||||
let l:endpos['character'] = l:endpos['character'] + a:token['length']
|
||||
let l:endpos = lsp#utils#position#lsp_to_vim(a:buf, l:endpos)
|
||||
|
||||
if s:use_vim_textprops
|
||||
let l:textprop_name = s:get_textprop_type(a:server, a:legend, a:token['token_idx'], a:token['token_modifiers'])
|
||||
return [l:textprop_name, [[l:startpos[0], l:startpos[1], l:endpos[0], l:endpos[1]]]]
|
||||
elseif s:use_nvim_highlight
|
||||
let l:char = a:token['pos']['character']
|
||||
let l:hl_name = s:get_hl_group(a:server, a:legend, a:token['token_idx'], a:token['token_modifiers'])
|
||||
return [l:hl_name, [[l:startpos[0] - 1, l:startpos[1] - 1, l:endpos[1] - 1]]]
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:apply_highlights(server, buf, highlights) abort
|
||||
call s:clear_highlights(a:server, a:buf)
|
||||
|
||||
if s:use_vim_textprops
|
||||
for [l:type, l:prop_list] in items(a:highlights)
|
||||
call prop_add_list({'type': l:type, 'bufnr': a:buf}, l:prop_list)
|
||||
endfor
|
||||
elseif s:use_nvim_highlight
|
||||
call lsp#log(a:highlights)
|
||||
for [l:hl_name, l:instances] in items(a:highlights)
|
||||
for l:instance in l:instances
|
||||
let [l:line, l:startcol, l:endcol] = l:instance
|
||||
try
|
||||
call nvim_buf_add_highlight(a:buf, s:namespace_id, l:hl_name, l:line, l:startcol, l:endcol)
|
||||
catch
|
||||
call lsp#log('SemanticHighlight: error while adding ' . l:hl_name . ' highlight on line ' . l:line)
|
||||
endtry
|
||||
endfor
|
||||
endfor
|
||||
end
|
||||
endfunction
|
||||
|
||||
let s:hl_group_prefix = 'LspSemantic'
|
||||
|
||||
let s:default_highlight_groups = {
|
||||
\ s:hl_group_prefix . 'Type': 'Type',
|
||||
\ s:hl_group_prefix . 'Class': 'Type',
|
||||
\ s:hl_group_prefix . 'Enum': 'Type',
|
||||
\ s:hl_group_prefix . 'Interface': 'TypeDef',
|
||||
\ s:hl_group_prefix . 'Struct': 'Type',
|
||||
\ s:hl_group_prefix . 'TypeParameter': 'Type',
|
||||
\ s:hl_group_prefix . 'Parameter': 'Identifier',
|
||||
\ s:hl_group_prefix . 'Variable': 'Identifier',
|
||||
\ s:hl_group_prefix . 'Property': 'Identifier',
|
||||
\ s:hl_group_prefix . 'EnumMember': 'Constant',
|
||||
\ s:hl_group_prefix . 'Event': 'Identifier',
|
||||
\ s:hl_group_prefix . 'Function': 'Function',
|
||||
\ s:hl_group_prefix . 'Method': 'Function',
|
||||
\ s:hl_group_prefix . 'Macro': 'Macro',
|
||||
\ s:hl_group_prefix . 'Keyword': 'Keyword',
|
||||
\ s:hl_group_prefix . 'Modifier': 'Type',
|
||||
\ s:hl_group_prefix . 'Comment': 'Comment',
|
||||
\ s:hl_group_prefix . 'String': 'String',
|
||||
\ s:hl_group_prefix . 'Number': 'Number',
|
||||
\ s:hl_group_prefix . 'Regexp': 'String',
|
||||
\ s:hl_group_prefix . 'Operator': 'Operator',
|
||||
\ s:hl_group_prefix . 'Decorator': 'Macro'
|
||||
\ }
|
||||
|
||||
function! s:get_hl_group(server, legend, token_idx, token_modifiers) abort
|
||||
" get highlight group name
|
||||
let l:Capitalise = {str -> toupper(str[0]) . str[1:]}
|
||||
let l:token_name = l:Capitalise(a:legend['tokenTypes'][a:token_idx])
|
||||
let l:token_modifiers = []
|
||||
for l:modifier_idx in range(len(a:legend['tokenModifiers']))
|
||||
" float2nr(pow(2, a)) is 1 << a
|
||||
if and(a:token_modifiers, float2nr(pow(2, l:modifier_idx)))
|
||||
let l:modifier_name = a:legend['tokenModifiers'][l:modifier_idx]
|
||||
call add(l:token_modifiers, l:Capitalise(l:modifier_name))
|
||||
endif
|
||||
endfor
|
||||
call sort(l:token_modifiers)
|
||||
let l:hl_group = s:hl_group_prefix
|
||||
\ . reduce(l:token_modifiers, {acc, val -> acc . val}, '')
|
||||
\ . l:token_name
|
||||
|
||||
" create the highlight group if it does not already exist
|
||||
if !hlexists(l:hl_group)
|
||||
if has_key(s:default_highlight_groups, l:hl_group)
|
||||
exec 'highlight link' l:hl_group s:default_highlight_groups[l:hl_group]
|
||||
else
|
||||
if a:token_modifiers != 0
|
||||
let l:base_hl_group = s:get_hl_group(a:server, a:legend, a:token_idx, 0)
|
||||
exec 'highlight link' l:hl_group l:base_hl_group
|
||||
else
|
||||
exec 'highlight link' l:hl_group 'Normal'
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
return l:hl_group
|
||||
endfunction
|
||||
|
||||
let s:textprop_type_prefix = 'vim-lsp-semantic-'
|
||||
|
||||
function! s:get_textprop_type(server, legend, token_idx, token_modifiers) abort
|
||||
" get textprop type name
|
||||
let l:textprop_type = s:textprop_type_prefix . a:server . '-' . a:token_idx . '-' . a:token_modifiers
|
||||
|
||||
" create the textprop type if it does not already exist
|
||||
if prop_type_get(l:textprop_type) ==# {}
|
||||
let l:hl_group = s:get_hl_group(a:server, a:legend, a:token_idx, a:token_modifiers)
|
||||
silent! call prop_type_add(l:textprop_type, {
|
||||
\ 'highlight': l:hl_group,
|
||||
\ 'combine': v:true,
|
||||
\ 'priority': lsp#internal#textprop#priority('semantic')})
|
||||
endif
|
||||
|
||||
return l:textprop_type
|
||||
endfunction
|
||||
|
||||
" vim: fdm=marker
|
||||
@@ -0,0 +1,74 @@
|
||||
let s:ErrorType = 1
|
||||
let s:WarningType = 2
|
||||
let s:InfoType = 3
|
||||
let s:LogType = 4
|
||||
|
||||
function! lsp#internal#show_message#_enable() abort
|
||||
if g:lsp_show_message_log_level ==# 'none' | return | endif
|
||||
let s:Dispose = lsp#callbag#pipe(
|
||||
\ lsp#stream(),
|
||||
\ lsp#callbag#filter({x->
|
||||
\ g:lsp_show_message_log_level !=# 'none' &&
|
||||
\ has_key(x, 'response') && has_key(x['response'], 'method')
|
||||
\ && x['response']['method'] ==# 'window/showMessage'
|
||||
\ }),
|
||||
\ lsp#callbag#tap({x->s:handle_show_message(x['server'], x['response']['params'])}),
|
||||
\ lsp#callbag#subscribe({ 'error': function('s:on_error') }),
|
||||
\ )
|
||||
endfunction
|
||||
|
||||
function! lsp#internal#show_message#_disable() abort
|
||||
if exists('s:Dispose')
|
||||
call s:Dispose()
|
||||
unlet s:Dispose
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:on_error(e) abort
|
||||
call lsp#log('lsp#internal#show_message error', a:e)
|
||||
if exists('s:Dispose')
|
||||
call s:Dispose()
|
||||
unlet s:Dispose
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:handle_show_message(server, params) abort
|
||||
let l:level = s:name_to_level(g:lsp_show_message_log_level)
|
||||
let l:type = a:params['type']
|
||||
if l:level < l:type
|
||||
return
|
||||
endif
|
||||
|
||||
let l:message = a:params['message']
|
||||
try
|
||||
if l:type == s:ErrorType
|
||||
echohl ErrorMsg
|
||||
elseif l:type == s:WarningType
|
||||
echohl WarningMsg
|
||||
endif
|
||||
echom printf('%s: %s: %s', a:server, s:type_to_name(l:type), l:message)
|
||||
finally
|
||||
echohl None
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
function! s:name_to_level(name) abort
|
||||
if a:name ==# 'none'
|
||||
return 0
|
||||
elseif a:name ==# 'error'
|
||||
return s:ErrorType
|
||||
elseif a:name ==# 'warn' || a:name ==# 'warning'
|
||||
return s:WarningType
|
||||
elseif a:name ==# 'info'
|
||||
return s:InfoType
|
||||
elseif a:name ==# 'log'
|
||||
return s:LogType
|
||||
else
|
||||
return 0
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:type_to_name(type) abort
|
||||
return get(['unknown', 'error', 'warning', 'info', 'log'], a:type, 'unknown')
|
||||
endfunction
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
function! lsp#internal#show_message_request#_enable() abort
|
||||
if !g:lsp_show_message_request_enabled | return | endif
|
||||
let s:Dispose = lsp#callbag#pipe(
|
||||
\ lsp#stream(),
|
||||
\ lsp#callbag#filter({x->
|
||||
\ g:lsp_show_message_request_enabled &&
|
||||
\ has_key(x, 'request') && !has_key(x, 'response') &&
|
||||
\ has_key(x['request'], 'method') && x['request']['method'] ==# 'window/showMessageRequest'
|
||||
\ }),
|
||||
\ lsp#callbag#map({x->s:show_message_request(x['server'], x['request'])}),
|
||||
\ lsp#callbag#map({x->s:send_message_response(x['server'], x['request'], x['action'])}),
|
||||
\ lsp#callbag#flatten(),
|
||||
\ lsp#callbag#materialize(),
|
||||
\ lsp#callbag#subscribe({ 'error': function('s:on_error') }),
|
||||
\ )
|
||||
endfunction
|
||||
|
||||
function! lsp#internal#show_message_request#_disable() abort
|
||||
if exists('s:Dispose')
|
||||
call s:Dispose()
|
||||
unlet s:Dispose
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:on_error(e) abort
|
||||
call lsp#log('lsp#internal#show_message_request error', a:e)
|
||||
if exists('s:Dispose')
|
||||
call s:Dispose()
|
||||
unlet s:Dispose
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:show_message_request(server_name, request) abort
|
||||
let l:params = a:request['params']
|
||||
|
||||
let l:selected_action = v:null
|
||||
|
||||
if has_key(l:params, 'actions') && !empty(l:params['actions'])
|
||||
let l:options = map(copy(l:params['actions']), {i, action ->
|
||||
\ printf('%d - [%s] %s', i + 1, a:server_name, action['title'])
|
||||
\ })
|
||||
let l:index = inputlist([l:params['message']] + l:options)
|
||||
if l:index > 0 && l:index <= len(l:index)
|
||||
let l:selected_action = l:params['actions'][l:index - 1]
|
||||
endif
|
||||
else
|
||||
echom l:params['message']
|
||||
endif
|
||||
|
||||
return { 'server': a:server_name, 'request': a:request, 'action': l:selected_action }
|
||||
endfunction
|
||||
|
||||
function! s:send_message_response(server_name, request, action) abort
|
||||
return lsp#request(a:server_name, {
|
||||
\ 'id': a:request['id'],
|
||||
\ 'result': a:action
|
||||
\})
|
||||
endfunction
|
||||
13
dot_vim/plugged/vim-lsp/autoload/lsp/internal/textprop.vim
Normal file
13
dot_vim/plugged/vim-lsp/autoload/lsp/internal/textprop.vim
Normal file
@@ -0,0 +1,13 @@
|
||||
" TODO: currently, quickpick is generated via vim-quickpick, 'quickpick' is
|
||||
" not used.
|
||||
let s:priorities = {
|
||||
\ 'quickpick': 1,
|
||||
\ 'folding': 2,
|
||||
\ 'semantic': 3,
|
||||
\ 'diagnostics_highlight': 4,
|
||||
\ 'document_highlight': 5,
|
||||
\}
|
||||
|
||||
function! lsp#internal#textprop#priority(name) abort
|
||||
return get(s:priorities, a:name, 0)
|
||||
endfunction
|
||||
@@ -0,0 +1,91 @@
|
||||
function! lsp#internal#type_hierarchy#show() abort
|
||||
let l:servers = filter(lsp#get_allowed_servers(), 'lsp#capabilities#has_type_hierarchy_provider(v:val)')
|
||||
let l:command_id = lsp#_new_command()
|
||||
|
||||
if len(l:servers) == 0
|
||||
return lsp#utils#error('Retrieving type hierarchy not supported for ' . &filetype)
|
||||
endif
|
||||
|
||||
let l:ctx = { 'counter': len(l:servers), 'list':[], 'last_command_id': l:command_id }
|
||||
" direction 0 children, 1 parent, 2 both
|
||||
for l:server in l:servers
|
||||
call lsp#send_request(l:server, {
|
||||
\ 'method': 'textDocument/typeHierarchy',
|
||||
\ 'params': {
|
||||
\ 'textDocument': lsp#get_text_document_identifier(),
|
||||
\ 'position': lsp#get_position(),
|
||||
\ 'direction': 2,
|
||||
\ 'resolve': 1,
|
||||
\ },
|
||||
\ 'on_notification': function('s:handle_type_hierarchy', [l:ctx, l:server, 'type hierarchy']),
|
||||
\ })
|
||||
endfor
|
||||
|
||||
echo 'Retrieving type hierarchy ...'
|
||||
endfunction
|
||||
|
||||
function! s:handle_type_hierarchy(ctx, server, type, data) abort "ctx = {counter, list, last_command_id}
|
||||
if a:ctx['last_command_id'] != lsp#_last_command()
|
||||
return
|
||||
endif
|
||||
|
||||
if lsp#client#is_error(a:data['response'])
|
||||
call lsp#utils#error('Failed to '. a:type . ' for ' . a:server . ': ' . lsp#client#error_message(a:data['response']))
|
||||
return
|
||||
endif
|
||||
|
||||
if empty(a:data['response']['result'])
|
||||
echo 'No type hierarchy found'
|
||||
return
|
||||
endif
|
||||
|
||||
" Create new buffer in a split
|
||||
let l:position = 'topleft'
|
||||
let l:orientation = 'new'
|
||||
exec l:position . ' ' . 10 . l:orientation
|
||||
|
||||
let l:provider = {
|
||||
\ 'root': a:data['response']['result'],
|
||||
\ 'root_state': 'expanded',
|
||||
\ 'bufnr': bufnr('%'),
|
||||
\ 'getChildren': function('s:get_children_for_tree_hierarchy'),
|
||||
\ 'getParent': function('s:get_parent_for_tree_hierarchy'),
|
||||
\ 'getTreeItem': function('s:get_treeitem_for_tree_hierarchy'),
|
||||
\ }
|
||||
|
||||
call lsp#utils#tree#new(l:provider)
|
||||
|
||||
echo 'Retrieved type hierarchy'
|
||||
endfunction
|
||||
|
||||
function! s:hierarchyitem_to_treeitem(hierarchyitem) abort
|
||||
return {
|
||||
\ 'id': a:hierarchyitem,
|
||||
\ 'label': a:hierarchyitem['name'],
|
||||
\ 'command': function('s:hierarchy_treeitem_command', [a:hierarchyitem]),
|
||||
\ 'collapsibleState': has_key(a:hierarchyitem, 'parents') && !empty(a:hierarchyitem['parents']) ? 'expanded' : 'none',
|
||||
\ }
|
||||
endfunction
|
||||
|
||||
function! s:hierarchy_treeitem_command(hierarchyitem) abort
|
||||
bwipeout
|
||||
call lsp#utils#tagstack#_update()
|
||||
call lsp#utils#location#_open_lsp_location(a:hierarchyitem)
|
||||
endfunction
|
||||
|
||||
function! s:get_children_for_tree_hierarchy(Callback, ...) dict abort
|
||||
if a:0 == 0
|
||||
call a:Callback('success', [l:self['root']])
|
||||
return
|
||||
else
|
||||
call a:Callback('success', a:1['parents'])
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:get_parent_for_tree_hierarchy(...) dict abort
|
||||
" TODO
|
||||
endfunction
|
||||
|
||||
function! s:get_treeitem_for_tree_hierarchy(Callback, object) dict abort
|
||||
call a:Callback('success', s:hierarchyitem_to_treeitem(a:object))
|
||||
endfunction
|
||||
@@ -0,0 +1,39 @@
|
||||
let s:Markdown = vital#lsp#import('VS.Vim.Syntax.Markdown')
|
||||
let s:Window = vital#lsp#import('VS.Vim.Window')
|
||||
|
||||
function! lsp#internal#ui#popupmenu#open(opt) abort
|
||||
let l:Callback = remove(a:opt, 'callback')
|
||||
let l:items = remove(a:opt, 'items')
|
||||
|
||||
let l:items_with_shortcuts= map(l:items, {
|
||||
\ idx, item -> ((idx < 9) ? '['.(idx+1).'] ' : '').item
|
||||
\ })
|
||||
|
||||
function! Filter(id, key) abort closure
|
||||
if a:key >= 1 && a:key <= len(l:items)
|
||||
call popup_close(a:id, a:key)
|
||||
elseif a:key ==# "\<C-j>"
|
||||
call win_execute(a:id, 'normal! j')
|
||||
elseif a:key ==# "\<C-k>"
|
||||
call win_execute(a:id, 'normal! k')
|
||||
else
|
||||
return popup_filter_menu(a:id, a:key)
|
||||
endif
|
||||
|
||||
return v:true
|
||||
endfunction
|
||||
|
||||
let l:popup_opt = extend({
|
||||
\ 'callback': funcref('s:callback', [l:Callback]),
|
||||
\ 'filter': funcref('Filter'),
|
||||
\ }, a:opt)
|
||||
|
||||
let l:winid = popup_menu(l:items_with_shortcuts, l:popup_opt)
|
||||
call s:Window.do(l:winid, { -> s:Markdown.apply() })
|
||||
execute('doautocmd <nomodeline> User lsp_float_opened')
|
||||
endfunction
|
||||
|
||||
function! s:callback(callback, id, selected) abort
|
||||
call a:callback(a:id, a:selected)
|
||||
execute('doautocmd <nomodeline> User lsp_float_closed')
|
||||
endfunction
|
||||
461
dot_vim/plugged/vim-lsp/autoload/lsp/internal/ui/quickpick.vim
Normal file
461
dot_vim/plugged/vim-lsp/autoload/lsp/internal/ui/quickpick.vim
Normal file
@@ -0,0 +1,461 @@
|
||||
" https://github.com/prabirshrestha/quickpick.vim#968f00787c1a118228aee869351e754bec555298
|
||||
" :QuickpickEmbed path=autoload/lsp/internal/ui/quickpick.vim namespace=lsp#internal#ui#quickpick prefix=lsp-quickpick
|
||||
|
||||
let s:has_timer = exists('*timer_start') && exists('*timer_stop')
|
||||
let s:has_matchfuzzy = exists('*matchfuzzy')
|
||||
let s:has_matchfuzzypos = exists('*matchfuzzypos')
|
||||
let s:has_proptype = exists('*prop_type_add') && exists('*prop_type_delete')
|
||||
|
||||
"
|
||||
" is_floating
|
||||
"
|
||||
if has('nvim')
|
||||
function! s:is_floating(winid) abort
|
||||
if !s:win_exists(a:winid)
|
||||
return 0
|
||||
endif
|
||||
let l:config = nvim_win_get_config(a:winid)
|
||||
return empty(l:config) || !empty(get(l:config, 'relative', ''))
|
||||
endfunction
|
||||
else
|
||||
function! s:is_floating(winid) abort
|
||||
return s:win_exists(a:winid) && win_id2win(a:winid) == 0
|
||||
endfunction
|
||||
endif
|
||||
|
||||
function! s:win_exists(winid) abort
|
||||
return winheight(a:winid) != -1
|
||||
endfunction
|
||||
|
||||
function! lsp#internal#ui#quickpick#open(opt) abort
|
||||
call lsp#internal#ui#quickpick#close() " hide existing picker if exists
|
||||
|
||||
" when key is empty, item is a string else it is a dict
|
||||
" fitems is filtered items and is the item that is filtered
|
||||
let s:state = extend({
|
||||
\ 'items': [],
|
||||
\ 'highlights': [],
|
||||
\ 'fitems': [],
|
||||
\ 'key': '',
|
||||
\ 'busy': 0,
|
||||
\ 'busyframes': ['-', '\', '|', '/'],
|
||||
\ 'filetype': 'lsp-quickpick',
|
||||
\ 'promptfiletype': 'lsp-quickpick-filter',
|
||||
\ 'input': '',
|
||||
\ 'maxheight': 10,
|
||||
\ 'debounce': 250,
|
||||
\ 'filter': 1,
|
||||
\ }, a:opt)
|
||||
|
||||
let s:inputecharpre = 0
|
||||
let s:state['busyframe'] = 0
|
||||
|
||||
let s:state['bufnr'] = bufnr('%')
|
||||
let s:state['winid'] = win_getid()
|
||||
let s:state['wininfo'] = getwininfo()
|
||||
|
||||
" create result buffer
|
||||
exe printf('keepalt botright 3new %s', s:state['filetype'])
|
||||
let s:state['resultsbufnr'] = bufnr('%')
|
||||
let s:state['resultswinid'] = win_getid()
|
||||
if s:has_proptype
|
||||
call prop_type_add('highlight', { 'highlight': 'Directory', 'bufnr': s:state['resultsbufnr'] })
|
||||
endif
|
||||
|
||||
" create prompt buffer
|
||||
exe printf('keepalt botright 1new %s', s:state['promptfiletype'])
|
||||
let s:state['promptbufnr'] = bufnr('%')
|
||||
let s:state['promptwinid'] = win_getid()
|
||||
|
||||
call win_gotoid(s:state['resultswinid'])
|
||||
call s:set_buffer_options()
|
||||
setlocal cursorline
|
||||
call s:update_items()
|
||||
exec printf('setlocal filetype=' . s:state['filetype'])
|
||||
call s:notify('open', { 'bufnr': s:state['bufnr'], 'winid': s:state['winid'] , 'resultsbufnr': s:state['resultsbufnr'], 'resultswinid': s:state['resultswinid'] })
|
||||
|
||||
call win_gotoid(s:state['promptwinid'])
|
||||
call s:set_buffer_options()
|
||||
call setline(1, s:state['input'])
|
||||
|
||||
" map keys
|
||||
inoremap <buffer><silent> <Plug>(lsp-quickpick-accept) <ESC>:<C-u>call <SID>on_accept()<CR>
|
||||
nnoremap <buffer><silent> <Plug>(lsp-quickpick-accept) :<C-u>call <SID>on_accept()<CR>
|
||||
|
||||
inoremap <buffer><silent> <Plug>(lsp-quickpick-close) <ESC>:<C-u>call lsp#internal#ui#quickpick#close()<CR>
|
||||
nnoremap <buffer><silent> <Plug>(lsp-quickpick-close) :<C-u>call lsp#internal#ui#quickpick#close()<CR>
|
||||
|
||||
inoremap <buffer><silent> <Plug>(lsp-quickpick-cancel) <ESC>:<C-u>call <SID>on_cancel()<CR>
|
||||
nnoremap <buffer><silent> <Plug>(lsp-quickpick-cancel) :<C-u>call <SID>on_cancel()<CR>
|
||||
|
||||
inoremap <buffer><silent> <Plug>(lsp-quickpick-move-next) <C-o>:<C-u>call <SID>on_move_next()<CR>
|
||||
nnoremap <buffer><silent> <Plug>(lsp-quickpick-move-next) :<C-u>call <SID>on_move_next()<CR>
|
||||
|
||||
inoremap <buffer><silent> <Plug>(lsp-quickpick-move-previous) <C-o>:<C-u>call <SID>on_move_previous()<CR>
|
||||
nnoremap <buffer><silent> <Plug>(lsp-quickpick-move-previous) :<C-u>call <SID>on_move_previous()<CR>
|
||||
|
||||
exec printf('setlocal filetype=' . s:state['promptfiletype'])
|
||||
|
||||
if !hasmapto('<Plug>(lsp-quickpick-accept)')
|
||||
imap <buffer><cr> <Plug>(lsp-quickpick-accept)
|
||||
nmap <buffer><cr> <Plug>(lsp-quickpick-accept)
|
||||
endif
|
||||
|
||||
if !hasmapto('<Plug>(lsp-quickpick-cancel)')
|
||||
imap <silent> <buffer> <C-c> <Plug>(lsp-quickpick-cancel)
|
||||
map <silent> <buffer> <C-c> <Plug>(lsp-quickpick-cancel)
|
||||
imap <silent> <buffer> <Esc> <Plug>(lsp-quickpick-cancel)
|
||||
map <silent> <buffer> <Esc> <Plug>(lsp-quickpick-cancel)
|
||||
endif
|
||||
|
||||
if !hasmapto('<Plug>(lsp-quickpick-move-next)')
|
||||
imap <silent> <buffer> <C-n> <Plug>(lsp-quickpick-move-next)
|
||||
nmap <silent> <buffer> <C-n> <Plug>(lsp-quickpick-move-next)
|
||||
imap <silent> <buffer> <C-j> <Plug>(lsp-quickpick-move-next)
|
||||
nmap <silent> <buffer> <C-j> <Plug>(lsp-quickpick-move-next)
|
||||
endif
|
||||
|
||||
if !hasmapto('<Plug>(lsp-quickpick-move-previous)')
|
||||
imap <silent> <buffer> <C-p> <Plug>(lsp-quickpick-move-previous)
|
||||
nmap <silent> <buffer> <C-p> <Plug>(lsp-quickpick-move-previous)
|
||||
imap <silent> <buffer> <C-k> <Plug>(lsp-quickpick-move-previous)
|
||||
nmap <silent> <buffer> <C-k> <Plug>(lsp-quickpick-move-previous)
|
||||
endif
|
||||
|
||||
call cursor(line('$'), 0)
|
||||
call feedkeys('i', 'n')
|
||||
|
||||
augroup lsp#internal#ui#quickpick
|
||||
autocmd!
|
||||
autocmd InsertCharPre <buffer> call s:on_insertcharpre()
|
||||
autocmd TextChangedI <buffer> call s:on_inputchanged()
|
||||
autocmd InsertEnter <buffer> call s:on_insertenter()
|
||||
autocmd InsertLeave <buffer> call s:on_insertleave()
|
||||
|
||||
if exists('##TextChangedP')
|
||||
autocmd TextChangedP <buffer> call s:on_inputchanged()
|
||||
endif
|
||||
augroup END
|
||||
|
||||
call s:notify_items()
|
||||
call s:notify_selection()
|
||||
call lsp#internal#ui#quickpick#busy(s:state['busy'])
|
||||
endfunction
|
||||
|
||||
function! s:set_buffer_options() abort
|
||||
" set buffer options
|
||||
abc <buffer>
|
||||
setlocal bufhidden=unload " unload buf when no longer displayed
|
||||
setlocal buftype=nofile " buffer is not related to any file<Paste>
|
||||
setlocal noswapfile " don't create swap file
|
||||
setlocal nowrap " don't soft-wrap
|
||||
setlocal nonumber " don't show line numbers
|
||||
setlocal nolist " don't use list mode (visible tabs etc)
|
||||
setlocal foldcolumn=0 " don't show a fold column at side
|
||||
setlocal foldlevel=99 " don't fold anything
|
||||
setlocal nospell " spell checking off
|
||||
setlocal nobuflisted " don't show up in the buffer list
|
||||
setlocal textwidth=0 " don't hardwarp (break long lines)
|
||||
setlocal nocursorline " highlight the line cursor is off
|
||||
setlocal nocursorcolumn " disable cursor column
|
||||
setlocal noundofile " don't enable undo
|
||||
setlocal winfixheight
|
||||
if exists('+colorcolumn') | setlocal colorcolumn=0 | endif
|
||||
if exists('+relativenumber') | setlocal norelativenumber | endif
|
||||
setlocal signcolumn=yes " for prompt
|
||||
endfunction
|
||||
|
||||
function! lsp#internal#ui#quickpick#close() abort
|
||||
if !exists('s:state')
|
||||
return
|
||||
endif
|
||||
|
||||
call lsp#internal#ui#quickpick#busy(0)
|
||||
|
||||
call win_gotoid(s:state['winid'])
|
||||
call s:notify('close', { 'bufnr': s:state['bufnr'], 'winid': s:state['winid'], 'resultsbufnr': s:state['resultsbufnr'], 'resultswinid': s:state['winid'] })
|
||||
|
||||
augroup lsp#internal#ui#quickpick
|
||||
autocmd!
|
||||
augroup END
|
||||
|
||||
exe 'silent! bunload! ' . s:state['promptbufnr']
|
||||
exe 'silent! bunload! ' . s:state['resultsbufnr']
|
||||
call s:restore_windows()
|
||||
|
||||
let s:inputecharpre = 0
|
||||
|
||||
unlet s:state
|
||||
endfunction
|
||||
|
||||
function! s:restore_windows() abort
|
||||
let [l:tabnr, l:_] = win_id2tabwin(s:state['winid'])
|
||||
if l:tabnr == 0
|
||||
return
|
||||
endif
|
||||
|
||||
let l:Resizable = {_, info ->
|
||||
\ info.tabnr == l:tabnr &&
|
||||
\ s:win_exists(info.winid) &&
|
||||
\ !s:is_floating(info.winid)
|
||||
\ }
|
||||
let l:wins_to_resize = sort(filter(s:state['wininfo'], l:Resizable), {l, r -> l.winnr - r.winnr})
|
||||
let l:open_winids_to_resize = map(filter(getwininfo(), l:Resizable), {_, info -> info.winid})
|
||||
|
||||
let l:resize_cmd = ''
|
||||
for l:info in l:wins_to_resize
|
||||
if index(l:open_winids_to_resize, l:info.winid) == -1
|
||||
return
|
||||
endif
|
||||
|
||||
let l:resize_cmd .= printf('%dresize %d | vert %dresize %d |', l:info.winnr, l:info.height, l:info.winnr, l:info.width)
|
||||
endfor
|
||||
|
||||
" winrestcmd repeats :resize commands twice after patch-8.2.2631.
|
||||
" To simulate this behavior, execute the :resize commands twice.
|
||||
" see https://github.com/vim/vim/issues/7988
|
||||
exe 'silent! ' . l:resize_cmd . l:resize_cmd
|
||||
endfunction
|
||||
|
||||
function! lsp#internal#ui#quickpick#items(items) abort
|
||||
let s:state['items'] = a:items
|
||||
call s:update_items()
|
||||
call s:notify_items()
|
||||
call s:notify_selection()
|
||||
endfunction
|
||||
|
||||
function! lsp#internal#ui#quickpick#busy(busy) abort
|
||||
let s:state['busy'] = a:busy
|
||||
if a:busy
|
||||
if !has_key(s:state, 'busytimer')
|
||||
let s:state['busyframe'] = 0
|
||||
let s:state['busytimer'] = timer_start(60, function('s:busy_tick'), { 'repeat': -1 })
|
||||
endif
|
||||
else
|
||||
if has_key(s:state, 'busytimer')
|
||||
call timer_stop(s:state['busytimer'])
|
||||
call remove(s:state, 'busytimer')
|
||||
redraw
|
||||
echohl None
|
||||
echo ''
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! lsp#internal#ui#quickpick#results_winid() abort
|
||||
if exists('s:state')
|
||||
return s:state['resultswinid']
|
||||
else
|
||||
return 0
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:busy_tick(...) abort
|
||||
let s:state['busyframe'] = s:state['busyframe'] + 1
|
||||
if s:state['busyframe'] >= len(s:state['busyframes'])
|
||||
let s:state['busyframe'] = 0
|
||||
endif
|
||||
redraw
|
||||
echohl Question | echon s:state['busyframes'][s:state['busyframe']]
|
||||
echohl None
|
||||
endfunction
|
||||
|
||||
function! s:update_items() abort
|
||||
call s:win_execute(s:state['resultswinid'], 'silent! %delete _')
|
||||
|
||||
let s:state['highlights'] = []
|
||||
|
||||
if s:state['filter'] " if filter is enabled
|
||||
if empty(s:trim(s:state['input']))
|
||||
let s:state['fitems'] = s:state['items']
|
||||
else
|
||||
if empty(s:state['key']) " item is string
|
||||
if s:has_matchfuzzypos
|
||||
let l:matchfuzzyresult = matchfuzzypos(s:state['items'], s:state['input'])
|
||||
let l:fitems = l:matchfuzzyresult[0]
|
||||
let l:highlights = l:matchfuzzyresult[1]
|
||||
let s:state['fitems'] = l:fitems
|
||||
let s:state['highlights'] = l:highlights
|
||||
elseif s:has_matchfuzzy
|
||||
let s:state['fitems'] = matchfuzzy(s:state['items'], s:state['input'])
|
||||
else
|
||||
let s:state['fitems'] = filter(copy(s:state['items']), 'stridx(toupper(v:val), toupper(s:state["input"])) >= 0')
|
||||
endif
|
||||
else " item is dict
|
||||
if s:has_matchfuzzypos
|
||||
" vim requires matchfuzzypos to have highlights.
|
||||
" matchfuzzy only patch doesn't support dict search
|
||||
let l:matchfuzzyresult = matchfuzzypos(s:state['items'], s:state['input'], { 'key': s:state['key'] })
|
||||
let l:fitems = l:matchfuzzyresult[0]
|
||||
let l:highlights = l:matchfuzzyresult[1]
|
||||
let s:state['fitems'] = l:fitems
|
||||
let s:state['highlights'] = l:highlights
|
||||
else
|
||||
let s:state['fitems'] = filter(copy(s:state['items']), 'stridx(toupper(v:val[s:state["key"]]), toupper(s:state["input"])) >= 0')
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
else " if filter is disabled
|
||||
let s:state['fitems'] = s:state['items']
|
||||
endif
|
||||
|
||||
|
||||
if empty(s:state['key']) " item is string
|
||||
let l:lines = s:state['fitems']
|
||||
else " item is dict
|
||||
let l:lines = map(copy(s:state['fitems']), 'v:val[s:state["key"]]')
|
||||
endif
|
||||
|
||||
call setbufline(s:state['resultsbufnr'], 1, l:lines)
|
||||
|
||||
if s:has_proptype && !empty(s:state['highlights'])
|
||||
let l:i = 0
|
||||
for l:line in s:state['highlights']
|
||||
for l:pos in l:line
|
||||
let l:cs = split(getbufline(s:state['resultsbufnr'], l:i + 1)[0], '\zs')
|
||||
let l:mpos = strlen(join(l:cs[: l:pos - 1], ''))
|
||||
let l:len = strlen(l:cs[l:pos])
|
||||
call prop_add(l:i + 1, l:mpos + 1, { 'length': l:len, 'type': 'highlight', 'bufnr': s:state['resultsbufnr'] })
|
||||
endfor
|
||||
let l:i += 1
|
||||
endfor
|
||||
endif
|
||||
|
||||
call s:win_execute(s:state['resultswinid'], printf('resize %d', min([len(s:state['fitems']), s:state['maxheight']])))
|
||||
call s:win_execute(s:state['promptwinid'], 'resize 1')
|
||||
endfunction
|
||||
|
||||
function! s:on_accept() abort
|
||||
if win_gotoid(s:state['resultswinid'])
|
||||
let l:index = line('.') - 1 " line is 1 index, list is 0 index
|
||||
let l:fitems = s:state['fitems']
|
||||
if l:index < 0 || len(l:fitems) <= l:index
|
||||
let l:items = []
|
||||
else
|
||||
let l:items = [l:fitems[l:index]]
|
||||
endif
|
||||
call win_gotoid(s:state['winid'])
|
||||
call s:notify('accept', { 'items': l:items })
|
||||
end
|
||||
endfunction
|
||||
|
||||
function! s:on_cancel() abort
|
||||
call win_gotoid(s:state['winid'])
|
||||
call s:notify('cancel', {})
|
||||
call lsp#internal#ui#quickpick#close()
|
||||
endfunction
|
||||
|
||||
function! s:on_move_next() abort
|
||||
let l:col = col('.')
|
||||
call s:win_execute(s:state['resultswinid'], 'normal! j')
|
||||
call s:notify_selection()
|
||||
endfunction
|
||||
|
||||
function! s:on_move_previous() abort
|
||||
let l:col = col('.')
|
||||
call s:win_execute(s:state['resultswinid'], 'normal! k')
|
||||
call s:notify_selection()
|
||||
endfunction
|
||||
|
||||
function! s:notify_items() abort
|
||||
" items could be huge, so don't send the items as part of data
|
||||
call s:notify('items', { 'bufnr': s:state['bufnr'], 'winid': s:state['winid'], 'resultsbufnr': s:state['resultsbufnr'], 'resultswinid': s:state['resultswinid'] })
|
||||
endfunction
|
||||
|
||||
function! s:notify_selection() abort
|
||||
let l:original_winid = win_getid()
|
||||
call win_gotoid(s:state['resultswinid'])
|
||||
let l:index = line('.') - 1 " line is 1 based, list is 0 based
|
||||
if l:index < 0 || ((l:index + 1) > len(s:state['fitems']))
|
||||
let l:items = []
|
||||
else
|
||||
let l:items = [s:state['fitems'][l:index]]
|
||||
endif
|
||||
let l:data = {
|
||||
\ 'bufnr': s:state['bufnr'],
|
||||
\ 'winid': s:state['winid'],
|
||||
\ 'resultsbufnr': s:state['resultsbufnr'],
|
||||
\ 'resultswinid': s:state['resultswinid'],
|
||||
\ 'items': l:items,
|
||||
\ }
|
||||
noautocmd call win_gotoid(s:state['winid'])
|
||||
call s:notify('selection', l:data)
|
||||
noautocmd call win_gotoid(l:original_winid)
|
||||
endfunction
|
||||
|
||||
function! s:on_inputchanged() abort
|
||||
if s:inputecharpre
|
||||
if s:has_timer && s:state['debounce'] > 0
|
||||
call s:debounce_onchange()
|
||||
else
|
||||
call s:notify_onchange()
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:on_insertcharpre() abort
|
||||
let s:inputecharpre = 1
|
||||
endfunction
|
||||
|
||||
function! s:on_insertenter() abort
|
||||
let s:inputecharpre = 0
|
||||
endfunction
|
||||
|
||||
function! s:on_insertleave() abort
|
||||
if s:has_timer && has_key(s:state, 'debounce_onchange_timer')
|
||||
call timer_stop(s:state['debounce_onchange_timer'])
|
||||
call remove(s:state, 'debounce_onchange_timer')
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:debounce_onchange() abort
|
||||
if has_key(s:state, 'debounce_onchange_timer')
|
||||
call timer_stop(s:state['debounce_onchange_timer'])
|
||||
call remove(s:state, 'debounce_onchange_timer')
|
||||
endif
|
||||
let s:state['debounce_onchange_timer'] = timer_start(s:state['debounce'], function('s:notify_onchange'))
|
||||
endfunction
|
||||
|
||||
function! s:notify_onchange(...) abort
|
||||
let s:state['input'] = getbufline(s:state['promptbufnr'], 1)[0]
|
||||
call s:notify('change', { 'input': s:state['input'] })
|
||||
if s:state['filter']
|
||||
call s:update_items()
|
||||
call s:notify_selection()
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:notify(name, data) abort
|
||||
if has_key(s:state, 'on_event') | call s:state['on_event'](a:data, a:name) | endif
|
||||
if has_key(s:state, 'on_' . a:name) | call s:state['on_' . a:name](a:data, a:name) | endif
|
||||
endfunction
|
||||
|
||||
if exists('*win_execute')
|
||||
function! s:win_execute(win_id, cmd) abort
|
||||
call win_execute(a:win_id, a:cmd)
|
||||
endfunction
|
||||
else
|
||||
function! s:win_execute(winid, cmd) abort
|
||||
let l:original_winid = win_getid()
|
||||
if l:original_winid == a:winid
|
||||
exec a:cmd
|
||||
else
|
||||
if win_gotoid(a:winid)
|
||||
exec a:cmd
|
||||
call win_gotoid(l:original_winid)
|
||||
end
|
||||
endif
|
||||
endfunction
|
||||
endif
|
||||
|
||||
if exists('*trim')
|
||||
function! s:trim(str) abort
|
||||
return trim(a:str)
|
||||
endfunction
|
||||
else
|
||||
function! s:trim(str) abort
|
||||
return substitute(a:str, '^\s*\|\s*$', '', 'g')
|
||||
endfunction
|
||||
endif
|
||||
|
||||
" vim: set sw=2 ts=2 sts=2 et tw=78 foldmarker={{{,}}} foldmethod=marker spell:
|
||||
@@ -0,0 +1,70 @@
|
||||
" https://microsoft.github.io/language-server-protocol/specifications/specification-current/#progress
|
||||
|
||||
let s:progress_ui = []
|
||||
let s:enabled = 0
|
||||
|
||||
function! lsp#internal#work_done_progress#_enable() abort
|
||||
if !g:lsp_work_done_progress_enabled | return | endif
|
||||
|
||||
if s:enabled | return | endif
|
||||
let s:enabled = 1
|
||||
let s:progress_ui = []
|
||||
|
||||
let s:Dispose = lsp#callbag#pipe(
|
||||
\ lsp#stream(),
|
||||
\ lsp#callbag#filter({x->has_key(x, 'response') && has_key(x['response'], 'method')
|
||||
\ && x['response']['method'] ==# '$/progress' && has_key(x['response'], 'params')
|
||||
\ && has_key(x['response']['params'], 'value') && type(x['response']['params']['value']) == type({})}),
|
||||
\ lsp#callbag#subscribe({'next': {x->s:handle_work_done_progress(x['server'], x['response'])}})
|
||||
\ )
|
||||
endfunction
|
||||
|
||||
function! s:handle_work_done_progress(server, response) abort
|
||||
let l:value = a:response['params']['value']
|
||||
let l:token = a:response['params']['token']
|
||||
let l:new = {
|
||||
\ 'server': a:server,
|
||||
\ 'token': l:token,
|
||||
\ 'title': '',
|
||||
\ 'message': '',
|
||||
\ }
|
||||
|
||||
if l:value['kind'] ==# 'end'
|
||||
let l:new['message'] = ''
|
||||
let l:new['percentage'] = 100
|
||||
call filter(s:progress_ui, {_, x->x['token'] !=# l:token || x['server'] !=# a:server})
|
||||
elseif l:value['kind'] ==# 'begin'
|
||||
let l:new['title'] = l:value['title']
|
||||
call filter(s:progress_ui, {_, x->x['token'] !=# l:token || x['server'] !=# a:server})
|
||||
call insert(s:progress_ui, l:new)
|
||||
elseif l:value['kind'] ==# 'report'
|
||||
let l:new['message'] = get(l:value, 'message', '')
|
||||
if has_key(l:value, 'percentage')
|
||||
" l:value['percentage'] is uinteger in specification.
|
||||
" But some implementation return float. (e.g. clangd11)
|
||||
" So we round it.
|
||||
let l:new['percentage'] = float2nr(l:value['percentage'] + 0.5)
|
||||
endif
|
||||
let l:idx = match(s:progress_ui, l:token)
|
||||
let l:new['title'] = s:progress_ui[l:idx]['title']
|
||||
call filter(s:progress_ui, {_, x->x['token'] !=# l:token || x['server'] !=# a:server})
|
||||
call insert(s:progress_ui, l:new)
|
||||
endif
|
||||
doautocmd <nomodeline> User lsp_progress_updated
|
||||
endfunction
|
||||
|
||||
function! lsp#internal#work_done_progress#_disable() abort
|
||||
if !s:enabled | return | endif
|
||||
|
||||
if exists('s:Dispose')
|
||||
call s:Dispose()
|
||||
unlet s:Dispose
|
||||
endif
|
||||
|
||||
let s:enabled = 0
|
||||
let s:progress_ui = []
|
||||
endfunction
|
||||
|
||||
function! lsp#internal#work_done_progress#get_progress() abort
|
||||
return s:progress_ui
|
||||
endfunction
|
||||
@@ -0,0 +1,94 @@
|
||||
" https://microsoft.github.io/language-server-protocol/specification#workspace_symbol
|
||||
" options - {
|
||||
" bufnr: bufnr('%') " optional
|
||||
" server - 'server_name' " optional
|
||||
" query: '' " optional
|
||||
" }
|
||||
function! lsp#internal#workspace_symbol#search#do(options) abort
|
||||
if has_key(a:options, 'server')
|
||||
let l:servers = [a:options['server']]
|
||||
else
|
||||
let l:servers = filter(lsp#get_allowed_servers(), 'lsp#capabilities#has_document_symbol_provider(v:val)')
|
||||
endif
|
||||
|
||||
if len(l:servers) == 0
|
||||
echom 'textDocument/workspaceSymbol not supported'
|
||||
call lsp#utils#error('textDocument/workspaceSymbol not supported')
|
||||
return
|
||||
endif
|
||||
|
||||
redraw | echo 'Retrieving workspace symbols ...'
|
||||
|
||||
let l:TextChangeSubject = lsp#callbag#makeSubject()
|
||||
|
||||
" use callbag debounce instead of quickpick debounce
|
||||
call lsp#internal#ui#quickpick#open({
|
||||
\ 'items': [],
|
||||
\ 'input': get(a:options, 'query', ''),
|
||||
\ 'key': 'text',
|
||||
\ 'debounce': 0,
|
||||
\ 'on_change': function('s:on_change', [l:TextChangeSubject]),
|
||||
\ 'on_accept': function('s:on_accept'),
|
||||
\ 'on_close': function('s:on_close'),
|
||||
\ })
|
||||
|
||||
let s:Dispose = lsp#callbag#pipe(
|
||||
\ l:TextChangeSubject,
|
||||
\ lsp#callbag#debounceTime(250),
|
||||
\ lsp#callbag#distinctUntilChanged(),
|
||||
\ lsp#callbag#switchMap({query->
|
||||
\ lsp#callbag#pipe(
|
||||
\ lsp#callbag#fromList(l:servers),
|
||||
\ lsp#callbag#tap({_->lsp#internal#ui#quickpick#busy(1)}),
|
||||
\ lsp#callbag#flatMap({server->
|
||||
\ lsp#callbag#pipe(
|
||||
\ lsp#request(server, {
|
||||
\ 'method': 'workspace/symbol',
|
||||
\ 'params': {
|
||||
\ 'query': query
|
||||
\ }
|
||||
\ }),
|
||||
\ lsp#callbag#map({x->{'server': server, 'request': x['request'], 'response': x['response']}}),
|
||||
\ )
|
||||
\ }),
|
||||
\ lsp#callbag#scan({acc, curr->add(acc, curr)}, []),
|
||||
\ lsp#callbag#tap({x->s:update_ui_items(x)}),
|
||||
\ lsp#callbag#tap({'complete': {->lsp#internal#ui#quickpick#busy(0)}}),
|
||||
\ )
|
||||
\ }),
|
||||
\ lsp#callbag#subscribe({
|
||||
\ 'error': {e->s:on_error(e)},
|
||||
\ }),
|
||||
\ )
|
||||
" Notify empty query. Some servers may not return results when query is empty
|
||||
call l:TextChangeSubject(1, '')
|
||||
endfunction
|
||||
|
||||
function! s:on_change(TextChangeSubject, data, ...) abort
|
||||
call a:TextChangeSubject(1, a:data['input'])
|
||||
endfunction
|
||||
|
||||
function! s:update_ui_items(x) abort
|
||||
let l:items = []
|
||||
for l:i in a:x
|
||||
let l:items += lsp#ui#vim#utils#symbols_to_loc_list(l:i['server'], l:i)
|
||||
endfor
|
||||
call lsp#internal#ui#quickpick#items(l:items)
|
||||
endfunction
|
||||
|
||||
function! s:on_accept(data, name) abort
|
||||
call lsp#internal#ui#quickpick#close()
|
||||
call lsp#utils#location#_open_vim_list_item(a:data['items'][0], '')
|
||||
endfunction
|
||||
|
||||
function! s:on_close(...) abort
|
||||
if exists('s:Dispose')
|
||||
call s:Dispose()
|
||||
unlet s:Dispose
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:on_error(e) abort
|
||||
call lsp#internal#ui#quickpick#close()
|
||||
call lsp#log('LspWorkspaceSymbolSearch error', a:e)
|
||||
endfunction
|
||||
Reference in New Issue
Block a user