Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
178 changes: 150 additions & 28 deletions autoload/ale/references.vim
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
let g:ale_default_navigation = get(g:, 'ale_default_navigation', 'buffer')
let g:ale_references_show_contents = get(g:, 'ale_references_show_contents', 1)
let g:ale_references_use_fzf = get(g:, 'ale_references_use_fzf', 0)

let s:references_map = {}

Expand Down Expand Up @@ -67,48 +69,160 @@ function! ale#references#HandleTSServerResponse(conn_id, response) abort
endfunction

function! ale#references#FormatLSPResponseItem(response_item, options) abort
let l:line_text = ''

let l:line= a:response_item.range.start.line
let l:col = a:response_item.range.start.character
let l:filename = ale#util#ToResource(a:response_item.uri)

if get(a:options, 'show_contents') == 1
try
let l:line_text = substitute(readfile(l:filename)[l:line], '^\s*\(.\{-}\)\s*$', '\1', '')
catch
" This happens in tests
endtry
endif

if get(a:options, 'use_fzf') == 1
let l:filename = ale#util#ToResource(a:response_item.uri)
let l:nline = a:response_item.range.start.line + 1
let l:ncol = a:response_item.range.start.character + 1

" grep-style output (filename:line:col:text) so that fzf can properly
" show matches and previews using ':' as delimiter
return l:filename . ':' . l:nline . ":" . l:ncol . ":" . l:line_text
endif

if get(a:options, 'open_in') is# 'quickfix'
return {
\ 'filename': ale#util#ToResource(a:response_item.uri),
\ 'filename': l:filename,
\ 'lnum': a:response_item.range.start.line + 1,
\ 'col': a:response_item.range.start.character + 1,
\ 'text': l:line_text,
\}
else
return {
\ 'filename': ale#util#ToResource(a:response_item.uri),
\ 'line': a:response_item.range.start.line + 1,
\ 'column': a:response_item.range.start.character + 1,
\ 'filename': l:filename,
\ 'line': l:line + 1,
\ 'column': l:col + 1,
\ 'match': l:line_text,
\}
endif
endfunction

function! ale#references#HandleLSPResponse(conn_id, response) abort
if has_key(a:response, 'id')
\&& has_key(s:references_map, a:response.id)
let l:options = remove(s:references_map, a:response.id)

" The result can be a Dictionary item, a List of the same, or null.
let l:result = get(a:response, 'result', [])
let l:item_list = []

if type(l:result) is v:t_list
for l:response_item in l:result
call add(l:item_list,
\ ale#references#FormatLSPResponseItem(l:response_item, l:options)
\)
endfor
function! ale#references#ShowInFzf(item_list, options) abort
let name = "LSP References"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the l: variable prefix for local variables to be consistent with the rest of the code.

let capname = "References"
let items = copy(a:item_list)
let cwd = getcwd() " no-custom-checks
let sep = has('win32') ? '\' : '/'

function! s:relative_paths(line) closure
return substitute(a:line, '^' . cwd . sep, '', '')
endfunction

if get(a:options, 'use_relative_paths')
let items = map(filter(items, 'len(v:val)'), 's:relative_paths(v:val)')
endif

let start_query = ''
let fzf_options = {
\ 'source': items,
\ 'options': ['--prompt', name.'> ', '--query', start_query,
\ '--multi', '--bind', 'alt-a:select-all,alt-d:deselect-all',
\ '--delimiter', ':', '--preview-window', '+{2}/2']
\}

call add(fzf_options['options'], '--highlight-line') " this only works for more recent fzf versions (TODO: handle version check?)

" wrap with #with_preview and #fzfwrap before adding the sinklist,
" otherwise --expect options are not added
let opts_with_preview = fzf#vim#with_preview(fzf_options)
let bang = 0 " TODO: handle bang
let wrapped = fzf#wrap(name, opts_with_preview, bang)

call remove(wrapped, 'sink*') " remove the default sinklist to add in our custom sinklist

function! wrapped.sinklist(lines) closure
if len(a:lines) <2
return
endif
let cmd = a:lines[0]
function! s:references_to_qf(line) closure
" mimics ag_to_qf in fzf.vim
let parts = matchlist(a:line, '\(.\{-}\)\s*:\s*\(\d\+\)\%(\s*:\s*\(\d\+\)\)\?\%(\s*:\(.*\)\)\?')
let filename = &acd ? fnamemodify(parts[1], ':p') : parts[1]
return {'filename': filename, 'lnum': parts[2], 'col': parts[3], 'text': parts[4]}
endfunction

if empty(l:item_list)
call ale#util#Execute('echom ''No references found.''')
else
if get(l:options, 'open_in') is# 'quickfix'
call setqflist([], 'r')
call setqflist(l:item_list, 'a')
call ale#util#Execute('cc 1')
else
call ale#preview#ShowSelection(l:item_list, l:options)
let references = map(filter(a:lines[1:], 'len(v:val)'), 's:references_to_qf(v:val)')

if empty(references)
return
endif

if get(a:options, 'open_in') is# 'quickfix'
call setqflist([], 'r')
call setqflist(references, 'a')
echomsg 'a:lines[1]=' . string(a:lines[1:])
call ale#util#Execute('cc 1')
endif

function! s:action(key, file)
" copied from fzf.vim
let default_action = {
\ 'ctrl-t': 'tab split',
\ 'ctrl-x': 'split',
\ 'ctrl-v': 'vsplit' }

let fzf_actions = get(g:, 'fzf_action', default_action)
let Cmd = get(fzf_actions, a:key, 'edit')

let cursor_cmd = escape("call cursor(" . a:file['lnum'] . "," . a:file['col'] . ")", ' ')
let fullcmd = Cmd . " +" . cursor_cmd . " " . fnameescape(a:file['filename'])
silent keepjumps keepalt execute fullcmd
endfunction

return map(references, 's:action(cmd, v:val)')
endfunction

call fzf#run(wrapped)
endfunction

function! ale#references#HandleLSPResponse(conn_id, response) abort
if ! (has_key(a:response, 'id') && has_key(s:references_map, a:response.id))
return
endif

let l:options = remove(s:references_map, a:response.id)

" The result can be a Dictionary item, a List of the same, or null.
let l:result = get(a:response, 'result', [])
let l:item_list = []

if type(l:result) is v:t_list
for l:response_item in l:result
call add(l:item_list,
\ ale#references#FormatLSPResponseItem(l:response_item, l:options)
\)
endfor
endif

if empty(l:item_list)
call ale#util#Execute('echom ''No references found.''')
else
if get(l:options, 'use_fzf') == 1
if !exists('*fzf#run')
throw "fzf#run function not found. You also need Vim plugin from the main fzf repository (i.e. junegunn/fzf *and* junegunn/fzf.vim)"
endif

call ale#references#ShowInFzf(l:item_list, l:options)
elseif get(l:options, 'open_in') is# 'quickfix'
call setqflist([], 'r')
call setqflist(l:item_list, 'a')
call ale#util#Execute('cc 1')
else
call ale#preview#ShowSelection(l:item_list, l:options)
endif
endif
endfunction
Expand Down Expand Up @@ -147,6 +261,8 @@ function! s:OnReady(line, column, options, linter, lsp_details) abort
let s:references_map[l:request_id] = {
\ 'use_relative_paths': has_key(a:options, 'use_relative_paths') ? a:options.use_relative_paths : 0,
\ 'open_in': get(a:options, 'open_in', 'current-buffer'),
\ 'show_contents': a:options.show_contents,
\ 'use_fzf': get(a:options, 'use_fzf', g:ale_references_use_fzf),
\}
endfunction

Expand All @@ -165,6 +281,8 @@ function! ale#references#Find(...) abort
let l:options.open_in = 'vsplit'
elseif l:option is? '-quickfix'
let l:options.open_in = 'quickfix'
elseif l:option is? '-fzf'
let l:options.use_fzf = 1
endif
endfor
endif
Expand All @@ -177,6 +295,10 @@ function! ale#references#Find(...) abort
endif
endif

if !has_key(l:options, 'show_contents')
let l:options.show_contents = ale#Var(bufnr(''), 'references_show_contents')
endif

let l:buffer = bufnr('')
let [l:line, l:column] = getpos('.')[1:2]
let l:column = min([l:column, len(getline(l:line))])
Expand Down
Loading