Skip to content

Commit dd6e6f1

Browse files
authored
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
1 parent fa3d4f2 commit dd6e6f1

File tree

4 files changed

+156
-33
lines changed

4 files changed

+156
-33
lines changed

autoload/ale/fzf.vim

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
" Author: bretello https://github.com/bretello
2+
" Description: Functions for integrating with fzf
3+
4+
" Handle references found with ALEFindReferences using fzf
5+
function! ale#fzf#ShowReferences(item_list, options) abort
6+
let l:name = 'LSP References'
7+
let l:capname = 'References'
8+
let l:items = copy(a:item_list)
9+
let l:cwd = getcwd() " no-custom-checks
10+
let l:sep = has('win32') ? '\' : '/'
11+
12+
function! s:relative_paths(line) closure abort
13+
return substitute(a:line, '^' . l:cwd . l:sep, '', '')
14+
endfunction
15+
16+
if get(a:options, 'use_relative_paths')
17+
let l:items = map(filter(l:items, 'len(v:val)'), 's:relative_paths(v:val)')
18+
endif
19+
20+
let l:start_query = ''
21+
let l:fzf_options = {
22+
\ 'source': items,
23+
\ 'options': ['--prompt', l:name.'> ', '--query', l:start_query,
24+
\ '--multi', '--bind', 'alt-a:select-all,alt-d:deselect-all',
25+
\ '--delimiter', ':', '--preview-window', '+{2}/2']
26+
\}
27+
28+
call add(l:fzf_options['options'], '--highlight-line') " this only works for more recent fzf versions (TODO: handle version check?)
29+
30+
" wrap with #with_preview and #fzfwrap before adding the sinklist,
31+
" otherwise --expect options are not added
32+
let l:opts_with_preview = fzf#vim#with_preview(l:fzf_options)
33+
let l:bang = 0 " TODO: handle bang
34+
let l:wrapped = fzf#wrap(l:name, l:opts_with_preview, l:bang)
35+
36+
call remove(l:wrapped, 'sink*') " remove the default sinklist to add in our custom sinklist
37+
38+
function! l:wrapped.sinklist(lines) closure abort
39+
if len(a:lines) <2
40+
return
41+
endif
42+
43+
let l:cmd = a:lines[0]
44+
45+
function! s:references_to_qf(line) closure abort
46+
" mimics ag_to_qf in junegunn/fzf.vim
47+
let l:parts = matchlist(a:line, '\(.\{-}\)\s*:\s*\(\d\+\)\%(\s*:\s*\(\d\+\)\)\?\%(\s*:\(.*\)\)\?')
48+
let l:filename = &autochdir ? fnamemodify(l:parts[1], ':p') : l:parts[1]
49+
50+
return {'filename': l:filename, 'lnum': l:parts[2], 'col': l:parts[3], 'text': l:parts[4]}
51+
endfunction
52+
53+
let l:references = map(filter(a:lines[1:], 'len(v:val)'), 's:references_to_qf(v:val)')
54+
55+
if empty(l:references)
56+
return
57+
endif
58+
59+
if get(a:options, 'open_in') is# 'quickfix'
60+
call setqflist([], 'r')
61+
call setqflist(l:references, 'a')
62+
63+
call ale#util#Execute('cc 1')
64+
endif
65+
66+
function! s:action(key, file) abort
67+
" copied from fzf.vim
68+
let l:default_action = {
69+
\ 'ctrl-t': 'tab split',
70+
\ 'ctrl-x': 'split',
71+
\ 'ctrl-v': 'vsplit' }
72+
73+
let fzf_actions = get(g:, 'fzf_action', l:default_action)
74+
let l:Cmd = get(fzf_actions, a:key, 'edit')
75+
76+
let l:cursor_cmd = escape('call cursor(' . a:file['lnum'] . ',' . a:file['col'] . ')', ' ')
77+
let l:fullcmd = l:Cmd . ' +' . l:cursor_cmd . ' ' . fnameescape(a:file['filename'])
78+
silent keepjumps keepalt execute fullcmd
79+
endfunction
80+
81+
return map(l:references, 's:action(cmd, v:val)')
82+
endfunction
83+
84+
call fzf#run(l:wrapped)
85+
endfunction

autoload/ale/references.vim

Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
let g:ale_default_navigation = get(g:, 'ale_default_navigation', 'buffer')
22
let g:ale_references_show_contents = get(g:, 'ale_references_show_contents', 1)
3+
let g:ale_references_use_fzf = get(g:, 'ale_references_use_fzf', 0)
34

45
let s:references_map = {}
56

@@ -82,6 +83,16 @@ function! ale#references#FormatLSPResponseItem(response_item, options) abort
8283
endtry
8384
endif
8485

86+
if get(a:options, 'use_fzf') == 1
87+
let l:filename = ale#util#ToResource(a:response_item.uri)
88+
let l:nline = a:response_item.range.start.line + 1
89+
let l:ncol = a:response_item.range.start.character + 1
90+
91+
" grep-style output (filename:line:col:text) so that fzf can properly
92+
" show matches and previews using ':' as delimiter
93+
return l:filename . ':' . l:nline . ':' . l:ncol . ':' . l:line_text
94+
endif
95+
8596
if get(a:options, 'open_in') is# 'quickfix'
8697
return {
8798
\ 'filename': l:filename,
@@ -100,32 +111,39 @@ function! ale#references#FormatLSPResponseItem(response_item, options) abort
100111
endfunction
101112

102113
function! ale#references#HandleLSPResponse(conn_id, response) abort
103-
if has_key(a:response, 'id')
104-
\&& has_key(s:references_map, a:response.id)
105-
let l:options = remove(s:references_map, a:response.id)
106-
107-
" The result can be a Dictionary item, a List of the same, or null.
108-
let l:result = get(a:response, 'result', [])
109-
let l:item_list = []
110-
111-
if type(l:result) is v:t_list
112-
for l:response_item in l:result
113-
call add(l:item_list,
114-
\ ale#references#FormatLSPResponseItem(l:response_item, l:options)
115-
\)
116-
endfor
117-
endif
114+
if ! (has_key(a:response, 'id') && has_key(s:references_map, a:response.id))
115+
return
116+
endif
118117

119-
if empty(l:item_list)
120-
call ale#util#Execute('echom ''No references found.''')
121-
else
122-
if get(l:options, 'open_in') is# 'quickfix'
123-
call setqflist([], 'r')
124-
call setqflist(l:item_list, 'a')
125-
call ale#util#Execute('cc 1')
126-
else
127-
call ale#preview#ShowSelection(l:item_list, l:options)
118+
let l:options = remove(s:references_map, a:response.id)
119+
120+
" The result can be a Dictionary item, a List of the same, or null.
121+
let l:result = get(a:response, 'result', [])
122+
let l:item_list = []
123+
124+
if type(l:result) is v:t_list
125+
for l:response_item in l:result
126+
call add(l:item_list,
127+
\ ale#references#FormatLSPResponseItem(l:response_item, l:options)
128+
\)
129+
endfor
130+
endif
131+
132+
if empty(l:item_list)
133+
call ale#util#Execute('echom ''No references found.''')
134+
else
135+
if get(l:options, 'use_fzf') == 1
136+
if !exists('*fzf#run')
137+
throw 'fzf#run function not found. You also need Vim plugin from the main fzf repository (i.e. junegunn/fzf *and* junegunn/fzf.vim)'
128138
endif
139+
140+
call ale#fzf#ShowReferences(l:item_list, l:options)
141+
elseif get(l:options, 'open_in') is# 'quickfix'
142+
call setqflist([], 'r')
143+
call setqflist(l:item_list, 'a')
144+
call ale#util#Execute('cc 1')
145+
else
146+
call ale#preview#ShowSelection(l:item_list, l:options)
129147
endif
130148
endif
131149
endfunction
@@ -165,6 +183,7 @@ function! s:OnReady(line, column, options, linter, lsp_details) abort
165183
\ 'use_relative_paths': has_key(a:options, 'use_relative_paths') ? a:options.use_relative_paths : 0,
166184
\ 'open_in': get(a:options, 'open_in', 'current-buffer'),
167185
\ 'show_contents': a:options.show_contents,
186+
\ 'use_fzf': get(a:options, 'use_fzf', g:ale_references_use_fzf),
168187
\}
169188
endfunction
170189

@@ -185,6 +204,8 @@ function! ale#references#Find(...) abort
185204
let l:options.open_in = 'quickfix'
186205
elseif l:option is? '-contents'
187206
let l:options.show_contents = 1
207+
elseif l:option is? '-fzf'
208+
let l:options.use_fzf = 1
188209
endif
189210
endfor
190211
endif

doc/ale.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2280,6 +2280,16 @@ g:ale_references_show_contents
22802280
If set to `true` or `1`, matches found by `:ALEFindReferences` will be
22812281
shown with a preview of the matching line.
22822282

2283+
*ale-options.references_use_fzf*
2284+
*g:ale_references_use_fzf*
2285+
references_use_fzf
2286+
g:ale_references_use_fzf
2287+
Type: |Boolean| or |Number|
2288+
Default: `false`
2289+
2290+
If set to `true` or `1`, matches found by `:ALEFindReferences` will be
2291+
always shown using |fzf-vim| (https://github.com/junegunn/fzf.vim).
2292+
22832293
*ale-options.rename_tsserver_find_in_comments*
22842294
*g:ale_rename_tsserver_find_in_comments*
22852295
rename_tsserver_find_in_comments
@@ -4085,13 +4095,19 @@ documented in additional help files.
40854095
`:ALEFindReferences -vsplit` - Open the location in a vertical split.
40864096
`:ALEFindReferences -quickfix` - Put the locations into quickfix list.
40874097
`:ALEFindReferences -contents` - Show line contents for matches.
4098+
`:ALEFindReferences -fzf` - Show matches/previews using |fzf-vim|.
40884099

40894100
The default method used for navigating to a new location can be changed
40904101
by modifying |g:ale_default_navigation|.
40914102

40924103
The default behaviour on whether to show line content for matches can
40934104
be changed by modifying |g:ale_references_show_contents|.
40944105

4106+
The default behaviour on whether to use `fzf` to show matches/file previews
4107+
can be changed by modifying |g:ale_references_use_fzf|. `-fzf` can be combined
4108+
with `-tab`, `-split`, `-vsplit`, `-quickfix` and `-relative`, while line
4109+
contents/file previews are always shown.
4110+
40954111
You can add `-relative` to the command to view results with relatives paths,
40964112
instead of absolute paths. This option has no effect if `-quickfix` is used.
40974113

test/test_find_references.vader

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ Execute(Results should be shown for tsserver responses):
121121
\ 'ignorethis': 'x',
122122
\ 'open_in': 'tab',
123123
\ 'use_relative_paths': 1,
124+
\ 'use_fzf': 0,
124125
\ }
125126
\ }
126127
\)
@@ -283,7 +284,7 @@ Execute(tsserver reference requests should be sent):
283284
\ [0, 'ts@references', {'file': expand('%:p'), 'line': 2, 'offset': 5}]
284285
\ ],
285286
\ g:message_list
286-
AssertEqual {'42': {'show_contents': 1, 'open_in': 'current-buffer', 'use_relative_paths': 0}}, ale#references#GetMap()
287+
AssertEqual {'42': {'show_contents': 1, 'open_in': 'current-buffer', 'use_relative_paths': 0, 'use_fzf': 0, }}, ale#references#GetMap()
287288

288289
Execute('-relative' argument should enable 'use_relative_paths' in HandleTSServerResponse):
289290
runtime ale_linters/typescript/tsserver.vim
@@ -293,7 +294,7 @@ Execute('-relative' argument should enable 'use_relative_paths' in HandleTSServe
293294

294295
call g:InitCallback()
295296

296-
AssertEqual {'42': {'show_contents': 1, 'open_in': 'current-buffer', 'use_relative_paths': 1}}, ale#references#GetMap()
297+
AssertEqual {'42': {'show_contents': 1, 'open_in': 'current-buffer', 'use_relative_paths': 1, 'use_fzf': 0}}, ale#references#GetMap()
297298

298299
Execute(`-tab` should display results in tabs):
299300
runtime ale_linters/typescript/tsserver.vim
@@ -303,7 +304,7 @@ Execute(`-tab` should display results in tabs):
303304

304305
call g:InitCallback()
305306

306-
AssertEqual {'42': {'show_contents': 1, 'open_in': 'tab', 'use_relative_paths': 0}}, ale#references#GetMap()
307+
AssertEqual {'42': {'show_contents': 1, 'open_in': 'tab', 'use_relative_paths': 0, 'use_fzf': 0}}, ale#references#GetMap()
307308

308309
Execute(The default navigation type should be used):
309310
runtime ale_linters/typescript/tsserver.vim
@@ -314,7 +315,7 @@ Execute(The default navigation type should be used):
314315

315316
call g:InitCallback()
316317

317-
AssertEqual {'42': {'show_contents': 1, 'open_in': 'tab', 'use_relative_paths': 0}}, ale#references#GetMap()
318+
AssertEqual {'42': {'show_contents': 1, 'open_in': 'tab', 'use_relative_paths': 0, 'use_fzf': 0}}, ale#references#GetMap()
318319

319320
Execute(`-split` should display results in splits):
320321
runtime ale_linters/typescript/tsserver.vim
@@ -324,7 +325,7 @@ Execute(`-split` should display results in splits):
324325

325326
call g:InitCallback()
326327

327-
AssertEqual {'42': {'show_contents': 1, 'open_in': 'split', 'use_relative_paths': 0}}, ale#references#GetMap()
328+
AssertEqual {'42': {'show_contents': 1, 'open_in': 'split', 'use_relative_paths': 0, 'use_fzf': 0}}, ale#references#GetMap()
328329

329330
Execute(`-vsplit` should display results in vsplits):
330331
runtime ale_linters/typescript/tsserver.vim
@@ -334,7 +335,7 @@ Execute(`-vsplit` should display results in vsplits):
334335

335336
call g:InitCallback()
336337

337-
AssertEqual {'42': {'show_contents': 1, 'open_in': 'vsplit', 'use_relative_paths': 0}}, ale#references#GetMap()
338+
AssertEqual {'42': {'show_contents': 1, 'open_in': 'vsplit', 'use_relative_paths': 0, 'use_fzf': 0}}, ale#references#GetMap()
338339

339340
Execute(`-quickfix` should display results in quickfix):
340341
runtime ale_linters/typescript/tsserver.vim
@@ -344,7 +345,7 @@ Execute(`-quickfix` should display results in quickfix):
344345

345346
call g:InitCallback()
346347

347-
AssertEqual {'42': {'show_contents': 1, 'open_in': 'quickfix', 'use_relative_paths': 0}}, ale#references#GetMap()
348+
AssertEqual {'42': {'show_contents': 1, 'open_in': 'quickfix', 'use_relative_paths': 0, 'use_fzf': 0}}, ale#references#GetMap()
348349

349350
Given python(Some Python file):
350351
foo
@@ -627,7 +628,7 @@ Execute(LSP reference requests should be sent):
627628
\ ],
628629
\ g:message_list
629630

630-
AssertEqual {'42': {'show_contents': 1, 'open_in': 'current-buffer', 'use_relative_paths': 0}}, ale#references#GetMap()
631+
AssertEqual {'42': {'show_contents': 1, 'open_in': 'current-buffer', 'use_relative_paths': 0, 'use_fzf': 0}}, ale#references#GetMap()
631632

632633
Execute('-relative' argument should enable 'use_relative_paths' in HandleLSPResponse):
633634
runtime ale_linters/python/pylsp.vim
@@ -638,4 +639,4 @@ Execute('-relative' argument should enable 'use_relative_paths' in HandleLSPResp
638639

639640
call g:InitCallback()
640641

641-
AssertEqual {'42': {'show_contents': 1, 'open_in': 'current-buffer', 'use_relative_paths': 1}}, ale#references#GetMap()
642+
AssertEqual {'42': {'show_contents': 1, 'open_in': 'current-buffer', 'use_relative_paths': 1, 'use_fzf': 0}}, ale#references#GetMap()

0 commit comments

Comments
 (0)