mirror of
https://github.com/prabirshrestha/vim-lsp.git
synced 2025-12-14 20:35:59 +01:00
* refactor to use lsp#utils#position#_lsp_to_vim * remove lsp#utils#to_col in favor of lsp#utils#position#_lsp_to_vim * fix doc
229 lines
6.5 KiB
VimL
229 lines
6.5 KiB
VimL
" Global state
|
|
let s:last_req_id = 0
|
|
let s:pending = {}
|
|
|
|
" Highlight group for references
|
|
if !hlexists('lspReference')
|
|
highlight link lspReference CursorColumn
|
|
endif
|
|
|
|
" Convert a LSP range to one or more vim match positions.
|
|
" If the range spans over multiple lines, break it down to multiple
|
|
" positions, one for each line.
|
|
" Return a list of positions.
|
|
function! s:range_to_position(bufnr, range) abort
|
|
let l:position = []
|
|
|
|
let [l:start_line, l:start_col] = lsp#utils#position#_lsp_to_vim(a:bufnr, a:range['start'])
|
|
let [l:end_line, l:end_col] = lsp#utils#position#_lsp_to_vim(a:bufnr, a:range['end'])
|
|
if l:end_line == l:start_line
|
|
let l:position = [[
|
|
\ l:start_line,
|
|
\ l:start_col,
|
|
\ l:end_col - l:start_col
|
|
\ ]]
|
|
else
|
|
" First line
|
|
let l:position = [[
|
|
\ l:start_line,
|
|
\ l:start_col,
|
|
\ 999
|
|
\ ]]
|
|
|
|
" Last line
|
|
call add(l:position, [
|
|
\ l:end_line,
|
|
\ 1,
|
|
\ l:end_col
|
|
\ ])
|
|
|
|
" Lines in the middle
|
|
let l:middle_lines = map(
|
|
\ range(l:start_line + 1, l:end_line - 1),
|
|
\ {_, l -> [l, 0, 999]}
|
|
\ )
|
|
|
|
call extend(l:position, l:middle_lines)
|
|
endif
|
|
|
|
return l:position
|
|
endfunction
|
|
|
|
" 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
|
|
|
|
" Handle response from server.
|
|
function! s:handle_references(ctx, data) abort
|
|
" Sanity checks
|
|
if !has_key(s:pending, a:ctx['filetype']) ||
|
|
\ !s:pending[a:ctx['filetype']]
|
|
return
|
|
endif
|
|
let s:pending[a:ctx['filetype']] = v:false
|
|
if lsp#client#is_error(a:data['response'])
|
|
return
|
|
end
|
|
|
|
" More sanity checks
|
|
if a:ctx['bufnr'] != bufnr('%') || a:ctx['last_req_id'] != s:last_req_id
|
|
return
|
|
endif
|
|
|
|
" Remove existing highlights from the buffer
|
|
call lsp#ui#vim#references#clean_references()
|
|
|
|
" 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, s:range_to_position(a:ctx['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
|
|
" If the cursor has moved: send another request
|
|
if a:ctx['curpos'] != getcurpos()
|
|
call lsp#ui#vim#references#highlight(v:true)
|
|
endif
|
|
return
|
|
endif
|
|
|
|
" Store references
|
|
let w:lsp_reference_positions = l:position_list
|
|
let w:lsp_reference_matches = []
|
|
|
|
" Apply highlights to the buffer
|
|
if g:lsp_highlight_references_enabled
|
|
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
|
|
|
|
" Highlight references to the symbol under the cursor
|
|
function! lsp#ui#vim#references#highlight(force_refresh) abort
|
|
" No need to change the highlights if the cursor has not left
|
|
" the currently highlighted symbol.
|
|
if !a:force_refresh &&
|
|
\ exists('w:lsp_reference_positions') &&
|
|
\ s:in_reference(w:lsp_reference_positions) != -1
|
|
return
|
|
endif
|
|
|
|
" A request for this symbol has already been sent
|
|
if has_key(s:pending, &filetype) && s:pending[&filetype]
|
|
return
|
|
endif
|
|
|
|
" Check if any server provides document highlight
|
|
let l:capability = 'lsp#capabilities#has_document_highlight_provider(v:val)'
|
|
let l:servers = filter(lsp#get_whitelisted_servers(), l:capability)
|
|
|
|
if len(l:servers) == 0
|
|
return
|
|
endif
|
|
|
|
" Send a request
|
|
let s:pending[&filetype] = v:true
|
|
let s:last_req_id += 1
|
|
let l:ctx = {
|
|
\ 'last_req_id': s:last_req_id,
|
|
\ 'curpos': getcurpos(),
|
|
\ 'bufnr': bufnr('%'),
|
|
\ 'filetype': &filetype,
|
|
\ }
|
|
call lsp#send_request(l:servers[0], {
|
|
\ 'method': 'textDocument/documentHighlight',
|
|
\ 'params': {
|
|
\ 'textDocument': lsp#get_text_document_identifier(),
|
|
\ 'position': lsp#get_position(),
|
|
\ },
|
|
\ 'on_notification': function('s:handle_references', [l:ctx]),
|
|
\ })
|
|
endfunction
|
|
|
|
" Remove all reference highlights from the buffer
|
|
function! lsp#ui#vim#references#clean_references() abort
|
|
let s:pending[&filetype] = v:false
|
|
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
|
|
endfunction
|
|
|
|
" Cyclically move between references by `offset` occurrences.
|
|
function! lsp#ui#vim#references#jump(offset) abort
|
|
if !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:in_reference(w:lsp_reference_positions)
|
|
if l:index < 0
|
|
return
|
|
endif
|
|
|
|
let l:n = 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 >= 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 >= len(w:lsp_reference_positions)
|
|
let l:index = (l:index % l:n + l:n) % l:n
|
|
endif
|
|
|
|
" Jump
|
|
let l:target = w:lsp_reference_positions[l:index][0:1]
|
|
normal! m`
|
|
call cursor(l:target[0], l:target[1])
|
|
endfunction
|