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

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

View File

@@ -0,0 +1,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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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