]> git.wincent.com - docvim.git/blob - tests/fixtures/shared/integration-ferret-private.vim
72b68b451d5e22e4a934ff9d4d80d04bbc4fe047
[docvim.git] / tests / fixtures / shared / integration-ferret-private.vim
1 " Copyright 2015-present Greg Hurrell. All rights reserved.
2 " Licensed under the terms of the BSD 2-clause license.
3
4 " Remove lines a:first through a:last from the quickfix listing.
5 function! s:delete(first, last)
6   let l:list=getqflist()
7   let l:line=a:first
8
9   while l:line >= a:first && l:line <= a:last
10     " Non-dictionary items will be ignored. This effectively deletes the line.
11     let l:list[l:line - 1]=0
12     let l:line=l:line + 1
13   endwhile
14   call setqflist(l:list, 'r')
15
16   " Show to next entry.
17   execute 'cc ' . a:first
18
19   " Move focus back to quickfix listing.
20   execute "normal \<C-W>\<C-P>"
21 endfunction
22
23 " Returns 1 if we should/can use vim-dispatch.
24 function! s:dispatch()
25   ""
26   " @option g:FerretDispatch boolean 1
27   "
28   " Controls whether to use vim-dispatch (and specifically, |:Make|) to run
29   " |:Ack| searches asynchronously, when available. To prevent vim-dispatch from
30   " being used, set to 0:
31   "
32   " ```
33   " let g:FerretDispatch=0
34   " ```
35   let l:dispatch=get(g:, 'FerretDispatch', 1)
36   return l:dispatch && exists(':Make') == 2
37 endfunction
38
39 " Use `input()` to show error output to user. Ideally, we would do this in a way
40 " that didn't require user interaction, but this is the only reliable mechanism
41 " that works for all cases. Alternatives considered:
42 "
43 " (1) Using `:echomsg`
44 "
45 "     When not using vim-dispatch, the screen is getting cleared before the
46 "     user sees it, even with a pre-emptive `:redraw!` beforehand. Note that
47 "     we can get the message to linger on the screen by making it multi-line and
48 "     forcing Vim to show a prompt (see `:h hit-enter-prompt`), but this is not
49 "     reliable because the number of lines required to force the prompt will
50 "     vary by system, depending on the value of `'cmdheight'`.
51 "
52 "     When using vim-dispatch, anything we output ends up getting swallowed
53 "     before the user sees it, because something it is doing is clearing the
54 "     screen. This is true no matter how many lines we output.
55 "
56 " (2) Writing back into the quickfix/location list
57 "
58 "     This interacts poorly with vim-dispatch. If we write back an error message
59 "     and then call `:copen 1`, vim-dispatch ends up closing the listing before
60 "     the user sees it.
61 "
62 " (3) Using `:echoerr`
63 "
64 "     This works, but presents to the user as an exception (see `:h :echoerr`).
65 "
66 function! s:error(message) abort
67   call inputsave()
68   echohl ErrorMsg
69   call input(a:message . ': press ENTER to continue')
70   echohl NONE
71   call inputrestore()
72   echo
73 endfunction
74
75 " Parses arguments, extracting a search pattern (which is stored in
76 " g:ferret_lastsearch) and escaping space-delimited arguments for use by
77 " `system()`. A string containing all the escaped arguments is returned.
78 "
79 " The basic strategy is to split on spaces, expand wildcards for non-option
80 " arguments, shellescape each word, and join.
81 "
82 " To support an edge-case (the ability to search for strings with spaces in
83 " them, however, we swap out escaped spaces first (subsituting the unlikely
84 " "<!!S!!>") and then swap them back in at the end. This allows us to perform
85 " searches like:
86 "
87 "   :Ack -i \bFoo_?Bar\b
88 "   :Ack that's\ nice\ dear
89 "
90 " and so on...
91 function! s:parse(arg) abort
92   if exists('g:ferret_lastsearch')
93     unlet g:ferret_lastsearch
94   endif
95
96   let l:escaped_spaces_replaced_with_markers=substitute(a:arg, '\\ ', '<!!S!!>', 'g')
97   let l:split_on_spaces=split(l:escaped_spaces_replaced_with_markers)
98   let l:expanded_args=[]
99
100   for l:arg in l:split_on_spaces
101     if l:arg =~# '^-'
102       " Options get passed through as-is.
103       call add(l:expanded_args, l:arg)
104     elseif exists('g:ferret_lastsearch')
105       let l:file_args=glob(l:arg, 1, 1) " Ignore 'wildignore', return a list.
106       if len(l:file_args)
107         call extend(l:expanded_args, l:file_args)
108       else
109         " Let through to `ag`/`ack`/`grep`, which will throw ENOENT.
110         call add(l:expanded_args, l:arg)
111       endif
112     else
113       " First non-option arg is considered to be search pattern.
114       let g:ferret_lastsearch=substitute(l:arg, '<!!S!!>', ' ', 'g')
115       call add(l:expanded_args, l:arg)
116     endif
117   endfor
118
119   let l:each_word_shell_escaped=map(l:expanded_args, 'shellescape(v:val)')
120   let l:joined=join(l:each_word_shell_escaped)
121   return substitute(l:joined, '<!!S!!>', ' ', 'g')
122 endfunction
123
124 function! ferret#private#post(type) abort
125   if has('autocmd')
126     augroup FerretPostQF
127       autocmd!
128     augroup END
129   endif
130
131   let l:lastsearch = get(g:, 'ferret_lastsearch', '')
132   let l:qflist = a:type == 'qf' ? getqflist() : getloclist(0)
133   let l:tip = ' [see `:help ferret-quotes`]'
134   if len(l:qflist) == 0
135     let l:base = 'No results for search pattern `' . l:lastsearch . '`'
136
137     " Search pattern has no spaces and is entirely enclosed in quotes;
138     " eg 'foo' or "bar"
139     if l:lastsearch =~ '\v^([' . "'" . '"])[^ \1]+\1$'
140       call s:error(l:base . l:tip)
141     else
142       call s:error(l:base)
143     endif
144   else
145     " Find any "invalid" entries in the list.
146     let l:invalid = filter(copy(l:qflist), 'v:val.valid == 0')
147     if len(l:invalid) == len(l:qflist)
148       " Every item in the list was invalid.
149       redraw!
150       echohl ErrorMsg
151       for l:item in l:invalid
152         echomsg l:item.text
153       endfor
154       echohl NONE
155
156       let l:base = 'Search for `' . l:lastsearch . '` failed'
157
158       " When using vim-dispatch, the messages printed above get cleared, so the
159       " only way to see them is with `:messages`.
160       let l:suffix = a:type == 'qf' && s:dispatch() ?
161             \ ' (run `:messages` to see details)' :
162             \ ''
163
164       " If search pattern looks like `'foo` or `"bar`, it means the user
165       " probably tried to search for 'foo bar' or "bar baz" etc.
166       if l:lastsearch =~ '\v^[' . "'" . '"].+[^' . "'" . '"]$'
167         call s:error(l:base . l:tip . l:suffix)
168       else
169         call s:error(l:base . l:suffix)
170       endif
171     endif
172   endif
173 endfunction
174
175 function! ferret#private#ack(command) abort
176   let l:command=s:parse(a:command)
177   call ferret#private#hlsearch()
178
179   if empty(&grepprg)
180     return
181   endif
182
183   " Prefer vim-dispatch unless otherwise instructed.
184   if s:dispatch()
185     if has('autocmd')
186       augroup FerretPostQF
187         autocmd!
188         autocmd QuickfixCmdPost cgetfile call ferret#private#post('qf')
189       augroup END
190     endif
191     let l:original_makeprg=&l:makeprg
192     let l:original_errorformat=&l:errorformat
193     try
194       let &l:makeprg=&grepprg . ' ' . l:command
195       let &l:errorformat=&grepformat
196       Make
197     catch
198       if has('autocmd')
199         augroup! FerretPostQF
200       endif
201     finally
202       let &l:makeprg=l:original_makeprg
203       let &l:errorformat=l:original_errorformat
204     endtry
205   else
206     cexpr system(&grepprg . ' ' . l:command)
207     cwindow
208     call ferret#private#post('qf')
209   endif
210 endfunction
211
212 function! ferret#private#lack(command) abort
213   let l:command=s:parse(a:command)
214   call ferret#private#hlsearch()
215
216   if empty(&grepprg)
217     return
218   endif
219
220   lexpr system(&grepprg . ' ' . l:command)
221   lwindow
222   call ferret#private#post('location')
223 endfunction
224
225 function! ferret#private#hlsearch() abort
226   if has('extra_search')
227     ""
228     " @option g:FerretHlsearch boolean
229     "
230     " Controls whether Ferret should attempt to highlight the search pattern
231     " when running |:Ack| or |:Lack|. If left unset, Ferret will respect the
232     " current 'hlsearch' setting. To force highlighting on or off irrespective
233     " of 'hlsearch', set |g:FerretHlsearch| to 1 (on) or 0 (off):
234     "
235     " ```
236     " let g:FerretHlsearch=0
237     " ```
238     let l:hlsearch=get(g:, 'FerretHlsearch', &hlsearch)
239     if l:hlsearch
240       let @/=g:ferret_lastsearch
241       call feedkeys(":let &hlsearch=1 | echo \<CR>", 'n')
242     endif
243   endif
244 endfunction
245
246 " Run the specified substitution command on all the files in the quickfix list
247 " (mnemonic: "Ack substitute").
248 "
249 " Specifically, the sequence:
250 "
251 "   :Ack foo
252 "   :Acks /foo/bar/
253 "
254 " is equivalent to:
255 "
256 "   :Ack foo
257 "   :Qargs
258 "   :argdo %s/foo/bar/ge | update
259 "
260 " (Note: there's nothing specific to Ack in this function; it's just named this
261 " way for mnemonics, as it will most often be preceded by an :Ack invocation.)
262 function! ferret#private#acks(command) abort
263   if match(a:command, '\v^/.+/.*/$') == -1 " crude sanity check
264     echoerr 'Ferret: Expected a substitution expression (/foo/bar/); got: ' . a:command
265     return
266   endif
267
268   let l:filenames=ferret#private#qargs()
269   if l:filenames ==# ''
270     echoerr 'Ferret: Quickfix filenames must be present, but there are none'
271     return
272   endif
273
274   execute 'args' l:filenames
275
276   if v:version > 703 || v:version == 703 && has('patch438')
277     silent doautocmd <nomodeline> User FerretWillWrite
278   else
279     silent doautocmd User FerretWillWrite
280   endif
281   execute 'argdo' '%s' . a:command . 'ge | update'
282   if v:version > 703 || v:version == 703 && has('patch438')
283     silent doautocmd <nomodeline> User FerretDidWrite
284   else
285     silent doautocmd User FerretDidWrite
286   endif
287 endfunction
288
289 " Populate the :args list with the filenames currently in the quickfix window.
290 function! ferret#private#qargs() abort
291   let l:buffer_numbers={}
292   for l:item in getqflist()
293     let l:buffer_numbers[l:item['bufnr']]=bufname(l:item['bufnr'])
294   endfor
295   return join(map(values(l:buffer_numbers), 'fnameescape(v:val)'))
296 endfunction
297
298 " Visual mode deletion and `dd` mapping (special case).
299 function! ferret#private#qf_delete() range
300   call s:delete(a:firstline, a:lastline)
301 endfunction
302
303 " Motion-based deletion from quickfix listing.
304 function! ferret#private#qf_delete_motion(type, ...)
305   " Save.
306   let l:selection=&selection
307   let &selection='inclusive'
308
309   let l:firstline=line("'[")
310   let l:lastline=line("']")
311   call s:delete(l:firstline, l:lastline)
312
313   " Restore.
314   let &selection=l:selection
315 endfunction