I broke up with neovim....vim is my best friend now
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user