ALEFindReferences: add -fzf flag to show output in fzf (#5018)

* references: add ALEFindReferences -fzf option

Allows using -fzf to show previews using fzf.vim. Includes:

- add support for opening in bufers, splits, tabs and for adding matches quickfix
- add support for -relative
- add fzf preview `--highlight-line` option
- add fzf.vim autoload module

* tests: fix references tests for fzf support update
This commit is contained in:
bretello
2025-12-21 05:06:34 +01:00
committed by GitHub
parent fa3d4f2f13
commit dd6e6f1b15
4 changed files with 154 additions and 31 deletions

85
autoload/ale/fzf.vim Normal file
View File

@@ -0,0 +1,85 @@
" Author: bretello https://github.com/bretello
" Description: Functions for integrating with fzf
" Handle references found with ALEFindReferences using fzf
function! ale#fzf#ShowReferences(item_list, options) abort
let l:name = 'LSP References'
let l:capname = 'References'
let l:items = copy(a:item_list)
let l:cwd = getcwd() " no-custom-checks
let l:sep = has('win32') ? '\' : '/'
function! s:relative_paths(line) closure abort
return substitute(a:line, '^' . l:cwd . l:sep, '', '')
endfunction
if get(a:options, 'use_relative_paths')
let l:items = map(filter(l:items, 'len(v:val)'), 's:relative_paths(v:val)')
endif
let l:start_query = ''
let l:fzf_options = {
\ 'source': items,
\ 'options': ['--prompt', l:name.'> ', '--query', l:start_query,
\ '--multi', '--bind', 'alt-a:select-all,alt-d:deselect-all',
\ '--delimiter', ':', '--preview-window', '+{2}/2']
\}
call add(l: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 l:opts_with_preview = fzf#vim#with_preview(l:fzf_options)
let l:bang = 0 " TODO: handle bang
let l:wrapped = fzf#wrap(l:name, l:opts_with_preview, l:bang)
call remove(l:wrapped, 'sink*') " remove the default sinklist to add in our custom sinklist
function! l:wrapped.sinklist(lines) closure abort
if len(a:lines) <2
return
endif
let l:cmd = a:lines[0]
function! s:references_to_qf(line) closure abort
" mimics ag_to_qf in junegunn/fzf.vim
let l:parts = matchlist(a:line, '\(.\{-}\)\s*:\s*\(\d\+\)\%(\s*:\s*\(\d\+\)\)\?\%(\s*:\(.*\)\)\?')
let l:filename = &autochdir ? fnamemodify(l:parts[1], ':p') : l:parts[1]
return {'filename': l:filename, 'lnum': l:parts[2], 'col': l:parts[3], 'text': l:parts[4]}
endfunction
let l:references = map(filter(a:lines[1:], 'len(v:val)'), 's:references_to_qf(v:val)')
if empty(l:references)
return
endif
if get(a:options, 'open_in') is# 'quickfix'
call setqflist([], 'r')
call setqflist(l:references, 'a')
call ale#util#Execute('cc 1')
endif
function! s:action(key, file) abort
" copied from fzf.vim
let l:default_action = {
\ 'ctrl-t': 'tab split',
\ 'ctrl-x': 'split',
\ 'ctrl-v': 'vsplit' }
let fzf_actions = get(g:, 'fzf_action', l:default_action)
let l:Cmd = get(fzf_actions, a:key, 'edit')
let l:cursor_cmd = escape('call cursor(' . a:file['lnum'] . ',' . a:file['col'] . ')', ' ')
let l:fullcmd = l:Cmd . ' +' . l:cursor_cmd . ' ' . fnameescape(a:file['filename'])
silent keepjumps keepalt execute fullcmd
endfunction
return map(l:references, 's:action(cmd, v:val)')
endfunction
call fzf#run(l:wrapped)
endfunction

View File

@@ -1,5 +1,6 @@
let g:ale_default_navigation = get(g:, 'ale_default_navigation', 'buffer') 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_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 = {} let s:references_map = {}
@@ -82,6 +83,16 @@ function! ale#references#FormatLSPResponseItem(response_item, options) abort
endtry endtry
endif 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' if get(a:options, 'open_in') is# 'quickfix'
return { return {
\ 'filename': l:filename, \ 'filename': l:filename,
@@ -100,8 +111,10 @@ function! ale#references#FormatLSPResponseItem(response_item, options) abort
endfunction endfunction
function! ale#references#HandleLSPResponse(conn_id, response) abort function! ale#references#HandleLSPResponse(conn_id, response) abort
if has_key(a:response, 'id') if ! (has_key(a:response, 'id') && has_key(s:references_map, a:response.id))
\&& has_key(s:references_map, a:response.id) return
endif
let l:options = remove(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. " The result can be a Dictionary item, a List of the same, or null.
@@ -119,7 +132,13 @@ function! ale#references#HandleLSPResponse(conn_id, response) abort
if empty(l:item_list) if empty(l:item_list)
call ale#util#Execute('echom ''No references found.''') call ale#util#Execute('echom ''No references found.''')
else else
if get(l:options, 'open_in') is# 'quickfix' 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#fzf#ShowReferences(l:item_list, l:options)
elseif get(l:options, 'open_in') is# 'quickfix'
call setqflist([], 'r') call setqflist([], 'r')
call setqflist(l:item_list, 'a') call setqflist(l:item_list, 'a')
call ale#util#Execute('cc 1') call ale#util#Execute('cc 1')
@@ -127,7 +146,6 @@ function! ale#references#HandleLSPResponse(conn_id, response) abort
call ale#preview#ShowSelection(l:item_list, l:options) call ale#preview#ShowSelection(l:item_list, l:options)
endif endif
endif endif
endif
endfunction endfunction
function! s:OnReady(line, column, options, linter, lsp_details) abort function! s:OnReady(line, column, options, linter, lsp_details) abort
@@ -165,6 +183,7 @@ function! s:OnReady(line, column, options, linter, lsp_details) abort
\ 'use_relative_paths': has_key(a:options, 'use_relative_paths') ? a:options.use_relative_paths : 0, \ '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'), \ 'open_in': get(a:options, 'open_in', 'current-buffer'),
\ 'show_contents': a:options.show_contents, \ 'show_contents': a:options.show_contents,
\ 'use_fzf': get(a:options, 'use_fzf', g:ale_references_use_fzf),
\} \}
endfunction endfunction
@@ -185,6 +204,8 @@ function! ale#references#Find(...) abort
let l:options.open_in = 'quickfix' let l:options.open_in = 'quickfix'
elseif l:option is? '-contents' elseif l:option is? '-contents'
let l:options.show_contents = 1 let l:options.show_contents = 1
elseif l:option is? '-fzf'
let l:options.use_fzf = 1
endif endif
endfor endfor
endif endif

View File

@@ -2280,6 +2280,16 @@ g:ale_references_show_contents
If set to `true` or `1`, matches found by `:ALEFindReferences` will be If set to `true` or `1`, matches found by `:ALEFindReferences` will be
shown with a preview of the matching line. shown with a preview of the matching line.
*ale-options.references_use_fzf*
*g:ale_references_use_fzf*
references_use_fzf
g:ale_references_use_fzf
Type: |Boolean| or |Number|
Default: `false`
If set to `true` or `1`, matches found by `:ALEFindReferences` will be
always shown using |fzf-vim| (https://github.com/junegunn/fzf.vim).
*ale-options.rename_tsserver_find_in_comments* *ale-options.rename_tsserver_find_in_comments*
*g:ale_rename_tsserver_find_in_comments* *g:ale_rename_tsserver_find_in_comments*
rename_tsserver_find_in_comments rename_tsserver_find_in_comments
@@ -4085,6 +4095,7 @@ documented in additional help files.
`:ALEFindReferences -vsplit` - Open the location in a vertical split. `:ALEFindReferences -vsplit` - Open the location in a vertical split.
`:ALEFindReferences -quickfix` - Put the locations into quickfix list. `:ALEFindReferences -quickfix` - Put the locations into quickfix list.
`:ALEFindReferences -contents` - Show line contents for matches. `:ALEFindReferences -contents` - Show line contents for matches.
`:ALEFindReferences -fzf` - Show matches/previews using |fzf-vim|.
The default method used for navigating to a new location can be changed The default method used for navigating to a new location can be changed
by modifying |g:ale_default_navigation|. by modifying |g:ale_default_navigation|.
@@ -4092,6 +4103,11 @@ documented in additional help files.
The default behaviour on whether to show line content for matches can The default behaviour on whether to show line content for matches can
be changed by modifying |g:ale_references_show_contents|. be changed by modifying |g:ale_references_show_contents|.
The default behaviour on whether to use `fzf` to show matches/file previews
can be changed by modifying |g:ale_references_use_fzf|. `-fzf` can be combined
with `-tab`, `-split`, `-vsplit`, `-quickfix` and `-relative`, while line
contents/file previews are always shown.
You can add `-relative` to the command to view results with relatives paths, You can add `-relative` to the command to view results with relatives paths,
instead of absolute paths. This option has no effect if `-quickfix` is used. instead of absolute paths. This option has no effect if `-quickfix` is used.

View File

@@ -121,6 +121,7 @@ Execute(Results should be shown for tsserver responses):
\ 'ignorethis': 'x', \ 'ignorethis': 'x',
\ 'open_in': 'tab', \ 'open_in': 'tab',
\ 'use_relative_paths': 1, \ 'use_relative_paths': 1,
\ 'use_fzf': 0,
\ } \ }
\ } \ }
\) \)
@@ -283,7 +284,7 @@ Execute(tsserver reference requests should be sent):
\ [0, 'ts@references', {'file': expand('%:p'), 'line': 2, 'offset': 5}] \ [0, 'ts@references', {'file': expand('%:p'), 'line': 2, 'offset': 5}]
\ ], \ ],
\ g:message_list \ g:message_list
AssertEqual {'42': {'show_contents': 1, 'open_in': 'current-buffer', 'use_relative_paths': 0}}, ale#references#GetMap() AssertEqual {'42': {'show_contents': 1, 'open_in': 'current-buffer', 'use_relative_paths': 0, 'use_fzf': 0, }}, ale#references#GetMap()
Execute('-relative' argument should enable 'use_relative_paths' in HandleTSServerResponse): Execute('-relative' argument should enable 'use_relative_paths' in HandleTSServerResponse):
runtime ale_linters/typescript/tsserver.vim runtime ale_linters/typescript/tsserver.vim
@@ -293,7 +294,7 @@ Execute('-relative' argument should enable 'use_relative_paths' in HandleTSServe
call g:InitCallback() call g:InitCallback()
AssertEqual {'42': {'show_contents': 1, 'open_in': 'current-buffer', 'use_relative_paths': 1}}, ale#references#GetMap() AssertEqual {'42': {'show_contents': 1, 'open_in': 'current-buffer', 'use_relative_paths': 1, 'use_fzf': 0}}, ale#references#GetMap()
Execute(`-tab` should display results in tabs): Execute(`-tab` should display results in tabs):
runtime ale_linters/typescript/tsserver.vim runtime ale_linters/typescript/tsserver.vim
@@ -303,7 +304,7 @@ Execute(`-tab` should display results in tabs):
call g:InitCallback() call g:InitCallback()
AssertEqual {'42': {'show_contents': 1, 'open_in': 'tab', 'use_relative_paths': 0}}, ale#references#GetMap() AssertEqual {'42': {'show_contents': 1, 'open_in': 'tab', 'use_relative_paths': 0, 'use_fzf': 0}}, ale#references#GetMap()
Execute(The default navigation type should be used): Execute(The default navigation type should be used):
runtime ale_linters/typescript/tsserver.vim runtime ale_linters/typescript/tsserver.vim
@@ -314,7 +315,7 @@ Execute(The default navigation type should be used):
call g:InitCallback() call g:InitCallback()
AssertEqual {'42': {'show_contents': 1, 'open_in': 'tab', 'use_relative_paths': 0}}, ale#references#GetMap() AssertEqual {'42': {'show_contents': 1, 'open_in': 'tab', 'use_relative_paths': 0, 'use_fzf': 0}}, ale#references#GetMap()
Execute(`-split` should display results in splits): Execute(`-split` should display results in splits):
runtime ale_linters/typescript/tsserver.vim runtime ale_linters/typescript/tsserver.vim
@@ -324,7 +325,7 @@ Execute(`-split` should display results in splits):
call g:InitCallback() call g:InitCallback()
AssertEqual {'42': {'show_contents': 1, 'open_in': 'split', 'use_relative_paths': 0}}, ale#references#GetMap() AssertEqual {'42': {'show_contents': 1, 'open_in': 'split', 'use_relative_paths': 0, 'use_fzf': 0}}, ale#references#GetMap()
Execute(`-vsplit` should display results in vsplits): Execute(`-vsplit` should display results in vsplits):
runtime ale_linters/typescript/tsserver.vim runtime ale_linters/typescript/tsserver.vim
@@ -334,7 +335,7 @@ Execute(`-vsplit` should display results in vsplits):
call g:InitCallback() call g:InitCallback()
AssertEqual {'42': {'show_contents': 1, 'open_in': 'vsplit', 'use_relative_paths': 0}}, ale#references#GetMap() AssertEqual {'42': {'show_contents': 1, 'open_in': 'vsplit', 'use_relative_paths': 0, 'use_fzf': 0}}, ale#references#GetMap()
Execute(`-quickfix` should display results in quickfix): Execute(`-quickfix` should display results in quickfix):
runtime ale_linters/typescript/tsserver.vim runtime ale_linters/typescript/tsserver.vim
@@ -344,7 +345,7 @@ Execute(`-quickfix` should display results in quickfix):
call g:InitCallback() call g:InitCallback()
AssertEqual {'42': {'show_contents': 1, 'open_in': 'quickfix', 'use_relative_paths': 0}}, ale#references#GetMap() AssertEqual {'42': {'show_contents': 1, 'open_in': 'quickfix', 'use_relative_paths': 0, 'use_fzf': 0}}, ale#references#GetMap()
Given python(Some Python file): Given python(Some Python file):
foo foo
@@ -627,7 +628,7 @@ Execute(LSP reference requests should be sent):
\ ], \ ],
\ g:message_list \ g:message_list
AssertEqual {'42': {'show_contents': 1, 'open_in': 'current-buffer', 'use_relative_paths': 0}}, ale#references#GetMap() AssertEqual {'42': {'show_contents': 1, 'open_in': 'current-buffer', 'use_relative_paths': 0, 'use_fzf': 0}}, ale#references#GetMap()
Execute('-relative' argument should enable 'use_relative_paths' in HandleLSPResponse): Execute('-relative' argument should enable 'use_relative_paths' in HandleLSPResponse):
runtime ale_linters/python/pylsp.vim runtime ale_linters/python/pylsp.vim
@@ -638,4 +639,4 @@ Execute('-relative' argument should enable 'use_relative_paths' in HandleLSPResp
call g:InitCallback() call g:InitCallback()
AssertEqual {'42': {'show_contents': 1, 'open_in': 'current-buffer', 'use_relative_paths': 1}}, ale#references#GetMap() AssertEqual {'42': {'show_contents': 1, 'open_in': 'current-buffer', 'use_relative_paths': 1, 'use_fzf': 0}}, ale#references#GetMap()