aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorbretello2025-12-21 05:06:34 +0100
committerGitHub2025-12-21 13:06:34 +0900
commitdd6e6f1b15b58c857cf4990a5d98ea6cd49d4c51 (patch)
treefd04fcff824e2d1e3a90fb623a34af847522552f
parentfa3d4f2f13966168fe0236c5e8884f8e60369f6b (diff)
downloadale-dd6e6f1b15b58c857cf4990a5d98ea6cd49d4c51.tar.gz

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

-rw-r--r--autoload/ale/fzf.vim85
-rw-r--r--autoload/ale/references.vim69
-rw-r--r--doc/ale.txt16
-rw-r--r--test/test_find_references.vader19
4 files changed, 156 insertions, 33 deletions
diff --git a/autoload/ale/fzf.vim b/autoload/ale/fzf.vim
new file mode 100644
index 000000000..368d6eab8
--- /dev/null
+++ b/autoload/ale/fzf.vim
@@ -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
diff --git a/autoload/ale/references.vim b/autoload/ale/references.vim
index 7ffa53818..6cf3ff5ec 100644
--- a/autoload/ale/references.vim
+++ b/autoload/ale/references.vim
@@ -1,5 +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 = {}
@@ -82,6 +83,16 @@ function! ale#references#FormatLSPResponseItem(response_item, options) abort
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': l:filename,
@@ -100,32 +111,39 @@ function! ale#references#FormatLSPResponseItem(response_item, options) abort
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
- endif
+ if ! (has_key(a:response, 'id') && has_key(s:references_map, a:response.id))
+ return
+ endif
- 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 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#fzf#ShowReferences(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
@@ -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,
\ '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
@@ -185,6 +204,8 @@ function! ale#references#Find(...) abort
let l:options.open_in = 'quickfix'
elseif l:option is? '-contents'
let l:options.show_contents = 1
+ elseif l:option is? '-fzf'
+ let l:options.use_fzf = 1
endif
endfor
endif
diff --git a/doc/ale.txt b/doc/ale.txt
index da9bd7fe3..a2cd542b7 100644
--- a/doc/ale.txt
+++ b/doc/ale.txt
@@ -2280,6 +2280,16 @@ g:ale_references_show_contents
If set to `true` or `1`, matches found by `:ALEFindReferences` will be
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*
*g:ale_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 -quickfix` - Put the locations into quickfix list.
`: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
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
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,
instead of absolute paths. This option has no effect if `-quickfix` is used.
diff --git a/test/test_find_references.vader b/test/test_find_references.vader
index f1e677941..db191eab0 100644
--- a/test/test_find_references.vader
+++ b/test/test_find_references.vader
@@ -121,6 +121,7 @@ Execute(Results should be shown for tsserver responses):
\ 'ignorethis': 'x',
\ 'open_in': 'tab',
\ '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}]
\ ],
\ 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):
runtime ale_linters/typescript/tsserver.vim
@@ -293,7 +294,7 @@ Execute('-relative' argument should enable 'use_relative_paths' in HandleTSServe
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):
runtime ale_linters/typescript/tsserver.vim
@@ -303,7 +304,7 @@ Execute(`-tab` should display results in tabs):
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):
runtime ale_linters/typescript/tsserver.vim
@@ -314,7 +315,7 @@ Execute(The default navigation type should be used):
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):
runtime ale_linters/typescript/tsserver.vim
@@ -324,7 +325,7 @@ Execute(`-split` should display results in splits):
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):
runtime ale_linters/typescript/tsserver.vim
@@ -334,7 +335,7 @@ Execute(`-vsplit` should display results in vsplits):
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):
runtime ale_linters/typescript/tsserver.vim
@@ -344,7 +345,7 @@ Execute(`-quickfix` should display results in quickfix):
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):
foo
@@ -627,7 +628,7 @@ Execute(LSP reference requests should be sent):
\ ],
\ 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):
runtime ale_linters/python/pylsp.vim
@@ -638,4 +639,4 @@ Execute('-relative' argument should enable 'use_relative_paths' in HandleLSPResp
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()