Update Ferret fixtures
authorGreg Hurrell <greg@hurrell.net>
Wed, 3 May 2017 06:35:47 +0000 (23:35 -0700)
committerGreg Hurrell <greg@hurrell.net>
Wed, 3 May 2017 06:35:47 +0000 (23:35 -0700)
tests/fixtures/integration/ferret/input/.watchmanconfig [new file with mode: 0644]
tests/fixtures/integration/ferret/input/autoload/ferret/private.vim
tests/fixtures/integration/ferret/input/autoload/ferret/private/async.vim
tests/fixtures/integration/ferret/input/autoload/ferret/private/dispatch.vim
tests/fixtures/integration/ferret/input/autoload/ferret/private/shared.vim [new file with mode: 0644]
tests/fixtures/integration/ferret/input/autoload/ferret/private/vanilla.vim
tests/fixtures/integration/ferret/input/plugin/ferret.vim
tests/fixtures/integration/ferret/input/test.rb

diff --git a/tests/fixtures/integration/ferret/input/.watchmanconfig b/tests/fixtures/integration/ferret/input/.watchmanconfig
new file mode 100644 (file)
index 0000000..e69de29
index 758d00996e0af046f4eee647a7ba805c106f04e2..66d50fb00fa0277381f5bf4f278a1a551fc6634e 100644 (file)
@@ -101,7 +101,7 @@ function! s:parse(args) abort
       if len(l:file_args)
         call extend(l:expanded_args, l:file_args)
       else
-        " Let through to `ag`/`ack`/`grep`, which will throw ENOENT.
+        " Let through to `rg`/`ag`/`ack`/`ack-grep`, which will throw ENOENT.
         call add(l:expanded_args, l:arg)
       endif
     else
@@ -178,7 +178,9 @@ function! ferret#private#ack(...) abort
   let l:command=s:parse(a:000)
   call ferret#private#hlsearch()
 
-  if empty(&grepprg)
+  let l:executable=ferret#private#executable()
+  if empty(l:executable)
+    call ferret#private#installprompt()
     return
   endif
 
@@ -191,11 +193,33 @@ function! ferret#private#ack(...) abort
   endif
 endfunction
 
+function! ferret#private#buflist() abort
+  let l:buflist=getbufinfo({'buflisted': 1})
+  let l:bufpaths=filter(map(l:buflist, 'v:val.name'), 'v:val !=# ""')
+  return l:bufpaths
+endfunction
+
+function! ferret#private#back(...) abort
+  call call('ferret#private#ack', a:000 + ferret#private#buflist())
+endfunction
+
+function! ferret#private#black(...) abort
+  call call('ferret#private#lack', a:000 + ferret#private#buflist())
+endfunction
+
+function! ferret#private#installprompt() abort
+  call ferret#private#error(
+        \   'Unable to find suitable executable; install rg, ag, ack or ack-grep'
+        \ )
+endfunction
+
 function! ferret#private#lack(...) abort
   let l:command=s:parse(a:000)
   call ferret#private#hlsearch()
 
-  if empty(&grepprg)
+  let l:executable=ferret#private#executable()
+  if empty(l:executable)
+    call ferret#private#installprompt()
     return
   endif
 
@@ -220,7 +244,7 @@ function! ferret#private#hlsearch() abort
     " let g:FerretHlsearch=0
     " ```
     let l:hlsearch=get(g:, 'FerretHlsearch', &hlsearch)
-    if l:hlsearch
+    if l:hlsearch && exists('g:ferret_lastsearch')
       let @/=g:ferret_lastsearch
       call feedkeys(":let &hlsearch=1 | echo \<CR>", 'n')
     endif
@@ -235,12 +259,17 @@ endfunction
 "   :Ack foo
 "   :Acks /foo/bar/
 "
-" is equivalent to:
+" is equivalent to the following prior to Vim 8:
 "
 "   :Ack foo
 "   :Qargs
 "   :argdo %s/foo/bar/ge | update
 "
+" and the following on Vim 8 or after:
+"
+"   :Ack foo
+"   :cfdo %s/foo/bar/ge | update
+"
 " (Note: there's nothing specific to Ack in this function; it's just named this
 " way for mnemonics, as it will most often be preceded by an :Ack invocation.)
 function! ferret#private#acks(command) abort
@@ -265,19 +294,25 @@ function! ferret#private#acks(command) abort
     let l:options .= 'g'
   endif
 
-  let l:filenames=ferret#private#qargs()
-  if l:filenames ==# ''
-    call ferret#private#error(
-          \ 'Ferret: Quickfix filenames must be present, but there are none ' .
-          \ '(must use :Ack to find files before :Acks can be used)'
-          \ )
-    return
+  let l:cfdo=has('listcmds') && exists(':cfdo') == 2
+  if !l:cfdo
+    let l:filenames=ferret#private#qargs()
+    if l:filenames ==# ''
+      call ferret#private#error(
+            \ 'Ferret: Quickfix filenames must be present, but there are none ' .
+            \ '(must use :Ack to find files before :Acks can be used)'
+            \ )
+      return
+    endif
+    execute 'args' l:filenames
   endif
 
-  execute 'args' l:filenames
-
   call ferret#private#autocmd('FerretWillWrite')
-  execute 'argdo' '%s' . l:pattern . l:options . ' | update'
+  if l:cfdo
+    execute 'cfdo' '%s' . l:pattern . l:options . ' | update'
+  else
+    execute 'argdo' '%s' . l:pattern . l:options . ' | update'
+  endif
   call ferret#private#autocmd('FerretDidWrite')
 endfunction
 
@@ -311,22 +346,26 @@ function! s:split(str) abort
 endfunction
 
 function! ferret#private#ackcomplete(arglead, cmdline, cursorpos) abort
-  return ferret#private#complete('Ack', a:arglead, a:cmdline, a:cursorpos)
+  return ferret#private#complete('Ack', a:arglead, a:cmdline, a:cursorpos, 1)
+endfunction
+
+function! ferret#private#backcomplete(arglead, cmdline, cursorpos) abort
+  return ferret#private#complete('Lack', a:arglead, a:cmdline, a:cursorpos, 0)
+endfunction
+
+function! ferret#private#blackcomplete(arglead, cmdline, cursorpos) abort
+  return ferret#private#complete('Lack', a:arglead, a:cmdline, a:cursorpos, 0)
 endfunction
 
 function! ferret#private#lackcomplete(arglead, cmdline, cursorpos) abort
-  return ferret#private#complete('Lack', a:arglead, a:cmdline, a:cursorpos)
+  return ferret#private#complete('Lack', a:arglead, a:cmdline, a:cursorpos, 1)
 endfunction
 
-if executable('ag')
-  let s:executable='ag'
-elseif executable('ack')
-  let s:executable='ack'
-elseif executable('grep')
-  let s:executable='grep'
-else
-  let s:executable=''
-endif
+" Return first word (the name of the binary) of the executable string.
+function! ferret#private#executable_name()
+  let l:executable=ferret#private#executable()
+  let l:binary=matchstr(l:executable, '\v\w+')
+endfunction
 
 let s:options = {
       \   'ack': [
@@ -376,14 +415,55 @@ let s:options = {
       \     '-u',
       \     '-v',
       \     '-w'
+      \   ],
+      \   'rg': [
+      \     '--case-sensitive',
+      \     '--files-with-matches',
+      \     '--follow',
+      \     '--glob',
+      \     '--hidden',
+      \     '--ignore-case',
+      \     '--invert-match',
+      \     '--max-count',
+      \     '--maxdepth',
+      \     '--mmap',
+      \     '--no-ignore',
+      \     '--no-ignore-parent',
+      \     '--no-ignore-vcs',
+      \     '--no-mmap',
+      \     '--regexp',
+      \     '--smart-case',
+      \     '--text',
+      \     '--threads',
+      \     '--type',
+      \     '--type-not',
+      \     '--unrestricted',
+      \     '--word-regexp',
+      \     '-F',
+      \     '-L',
+      \     '-R',
+      \     '-T',
+      \     '-a',
+      \     '-e',
+      \     '-g',
+      \     '-i',
+      \     '-j',
+      \     '-m',
+      \     '-s',
+      \     '-t',
+      \     '-u',
+      \     '-v',
+      \     '-w'
       \   ]
       \ }
+let s:options['ack-grep']=s:options['ack']
 
 " We provide our own custom command completion because the default
 " -complete=file completion will expand special characters in the pattern (like
 " "#") before we get a chance to see them, breaking the search. As a bonus, this
-" means we can provide option completion for `ack` and `ag` options as well.
-function! ferret#private#complete(cmd, arglead, cmdline, cursorpos) abort
+" means we can provide option completion for `ack`/`ack-grep`/`ag`/`rg` options
+" as well.
+function! ferret#private#complete(cmd, arglead, cmdline, cursorpos, files) abort
   let l:args=s:split(a:cmdline[:a:cursorpos])
 
   let l:command_seen=0
@@ -396,10 +476,10 @@ function! ferret#private#complete(cmd, arglead, cmdline, cursorpos) abort
 
     if ferret#private#option(l:stripped)
       if a:cursorpos <= l:position
-        let l:options=get(s:options, s:executable, [])
+        let l:options=get(s:options, ferret#private#executable_name(), [])
         return filter(l:options, 'match(v:val, l:stripped) == 0')
       endif
-    elseif l:pattern_seen
+    elseif l:pattern_seen && a:files
       if a:cursorpos <= l:position
         " Assume this is a filename, and it's the one we're trying to complete.
         " Do -complete=file style completion.
@@ -452,3 +532,86 @@ function! ferret#private#qf_delete_motion(type, ...)
   " Restore.
   let &selection=l:selection
 endfunction
+
+""
+" @option g:FerretExecutable string "rg,ag,ack,ack-grep"
+"
+" Ferret will preferentially use `rg`, `ag` and finally `ack`/`ack-grep` (in
+" that order, using the first found executable), however you can force your
+" preference for a specific tool to be used by setting an override in your
+" |.vimrc|. Valid values are a comma-separated list of "rg", "ag", "ack" or
+" "ack-grep". If no requested executable exists, Ferret will fall-back to the
+" next in the default list.
+"
+" Example:
+"
+" ```
+" " Prefer `ag` over `rg`.
+" let g:FerretExecutable='ag,rg'
+" ```
+let s:force=get(g:, 'FerretExecutable', 'rg,ag,ack,ack-grep')
+
+let s:executables={
+      \   'rg': 'rg --vimgrep --no-heading',
+      \   'ag': 'ag',
+      \   'ack': 'ack --column --with-filename',
+      \   'ack-grep': 'ack-grep --column --with-filename'
+      \ }
+
+let s:init_done=0
+
+function! ferret#private#init() abort
+  if s:init_done
+    return
+  endif
+
+  if executable('rg') && match(system('rg --help'), '--max-columns') != -1
+    let s:executables['rg'].=' --max-columns 4096'
+  endif
+
+  if executable('ag')
+    let s:ag_help=system('ag --help')
+    if match(s:ag_help, '--vimgrep') != -1
+      let s:executables['ag'].=' --vimgrep'
+    else
+      let s:executables['ag'].=' --column'
+    endif
+    if match(s:ag_help, '--width') != -1
+      let s:executables['ag'].=' --width 4096'
+    endif
+  endif
+
+  let l:executable=ferret#private#executable()
+  if !empty(l:executable)
+    let &grepprg=l:executable
+    let &grepformat=g:FerretFormat
+  endif
+
+  let s:init_done=1
+endfunction
+
+function! ferret#private#executable() abort
+  let l:valid=keys(s:executables)
+  let l:executables=split(s:force, '\v\s*,\s*')
+  let l:executables=filter(l:executables, 'index(l:valid, v:val) != -1')
+  if index(l:executables, 'rg') == -1
+    call add(l:executables, 'rg')
+  endif
+  if index(l:executables, 'ag') == -1
+    call add(l:executables, 'ag')
+  endif
+  if index(l:executables, 'ack') == -1
+    call add(l:executables, 'ack')
+  endif
+  if index(l:executables, 'ack-grep') == -1
+    call add(l:executables, 'ack-grep')
+  endif
+  for l:executable in l:executables
+    if executable(l:executable)
+      return s:executables[l:executable]
+    endif
+  endfor
+  return ''
+endfunction
+
+call ferret#private#init()
index 62405e2ec76bc201bb097bad14f95a742d2b5e7f..b2be96de9019d224674c075d5b94f521bfae883e 100644 (file)
@@ -3,13 +3,8 @@
 
 let s:jobs={}
 
-function! s:channel_id(channel)
-  " Coerce to string, pluck out ID number.
-  return matchstr(a:channel, '\d\+')
-endfunction
-
 function! s:info_from_channel(channel)
-  let l:channel_id=s:channel_id(a:channel)
+  let l:channel_id=ch_info(a:channel)['id']
   if has_key(s:jobs, l:channel_id)
     return s:jobs[l:channel_id]
   endif
@@ -18,8 +13,9 @@ endfunction
 function! ferret#private#async#search(command, ack) abort
   call ferret#private#async#cancel()
   call ferret#private#autocmd('FerretAsyncStart')
-  let l:command_and_args = extend(split(&grepprg), a:command)
+  let l:command_and_args=extend(split(ferret#private#executable()), a:command)
   let l:job=job_start(l:command_and_args, {
+        \   'in_io': 'null',
         \   'err_cb': 'ferret#private#async#err_cb',
         \   'out_cb': 'ferret#private#async#out_cb',
         \   'close_cb': 'ferret#private#async#close_cb',
@@ -27,7 +23,7 @@ function! ferret#private#async#search(command, ack) abort
         \   'out_mode': 'raw'
         \ })
   let l:channel=job_getchannel(l:job)
-  let l:channel_id=s:channel_id(l:channel)
+  let l:channel_id=ch_info(l:channel)['id']
   let s:jobs[l:channel_id]={
         \   'channel_id': l:channel_id,
         \   'job': l:job,
@@ -42,59 +38,62 @@ function! ferret#private#async#search(command, ack) abort
         \ }
 endfunction
 
-let s:max_line_length=32768
+" Quickfix listing will truncate longer lines than this.
+let s:max_line_length=4096
 
 function! ferret#private#async#err_cb(channel, msg)
   let l:info=s:info_from_channel(a:channel)
   if type(l:info) == 4
-    let l:start=0
-    while 1
-      let l:idx=match(a:msg, '\n', l:start)
-      if l:idx==-1
-        if l:info.pending_error_length < s:max_line_length
-          let l:rest=strpart(a:msg, l:start)
-          let l:length=strlen(l:rest)
-          let l:info.pending_error.=l:rest
-          let l:info.pending_error_length+=l:length
-        endif
-        break
-      else
+    let l:lines=split(a:msg, '\n', 1)
+    let l:count=len(l:lines)
+    for l:i in range(l:count)
+      let l:line=l:lines[l:i]
+      if l:i != l:count - 1 || l:line == '' && l:info.pending_error_length
         if l:info.pending_error_length < s:max_line_length
-          let l:info.pending_error.=strpart(a:msg, l:start, l:idx - l:start)
+          let l:rest=strpart(
+                \   l:line,
+                \   0,
+                \   s:max_line_length - l:info.pending_error_length
+                \ )
+          call add(l:info.errors, l:info.pending_error . l:rest)
+        else
+          call add(l:info.errors, l:info.pending_error)
         endif
-        call add(l:info.errors, l:info.pending_error)
         let l:info.pending_error=''
         let l:info.pending_error_length=0
+      elseif l:info.pending_error_length < s:max_line_length
+        let l:info.pending_error.=l:line
+        let l:info.pending_error_length+=strlen(l:line)
       endif
-      let l:start=l:idx + 1
-    endwhile
+    endfor
   endif
 endfunction
 
 function! ferret#private#async#out_cb(channel, msg)
   let l:info=s:info_from_channel(a:channel)
   if type(l:info) == 4
-    let l:start=0
-    while 1
-      let l:idx=match(a:msg, '\n', l:start)
-      if l:idx==-1
+    let l:lines=split(a:msg, '\n', 1)
+    let l:count=len(l:lines)
+    for l:i in range(l:count)
+      let l:line=l:lines[l:i]
+      if l:i != l:count - 1 || l:line == '' && l:info.pending_output_length
         if l:info.pending_output_length < s:max_line_length
-          let l:rest=strpart(a:msg, l:start)
-          let l:length=strlen(l:rest)
-          let l:info.pending_output.=l:rest
-          let l:info.pending_output_length+=l:length
+          let l:rest=strpart(
+                \   l:line,
+                \   0,
+                \   s:max_line_length - l:info.pending_output_length
+                \ )
+          call add(l:info.output, l:info.pending_output . l:rest)
+        else
+          call add(l:info.output, l:info.pending_output)
         endif
-        break
-      else
-        if l:info.pending_output_length < s:max_line_length
-          let l:info.pending_output.=strpart(a:msg, l:start, l:idx - l:start)
-        endif
-        call add(l:info.output, l:info.pending_output)
         let l:info.pending_output=''
         let l:info.pending_output_length=0
+      elseif l:info.pending_output_length < s:max_line_length
+        let l:info.pending_output.=l:line
+        let l:info.pending_output_length+=strlen(l:line)
       endif
-      let l:start=l:idx + 1
-    endwhile
+    endfor
   endif
 endfunction
 
@@ -108,7 +107,7 @@ function! ferret#private#async#close_cb(channel) abort
       " If this is a :Lack search, try to focus appropriate window.
       call win_gotoid(l:info.window)
     endif
-    call s:finalize_search(l:info.output, l:info.ack)
+    call ferret#private#shared#finalize_search(l:info.output, l:info.ack)
     for l:error in l:info.errors
       unsilent echomsg l:error
     endfor
@@ -118,7 +117,7 @@ endfunction
 function! ferret#private#async#pull() abort
   for l:channel_id in keys(s:jobs)
     let l:info=s:jobs[l:channel_id]
-    call s:finalize_search(l:info.output, l:info.ack)
+    call ferret#private#shared#finalize_search(l:info.output, l:info.ack)
   endfor
 endfunction
 
@@ -138,15 +137,3 @@ endfunction
 function! ferret#private#async#debug() abort
   return s:jobs
 endfunction
-
-function! s:finalize_search(output, ack)
-  if a:ack
-    cexpr a:output
-    execute get(g:, 'FerretQFHandler', 'botright cwindow')
-    call ferret#private#post('qf')
-  else
-    lexpr a:output
-    execute get(g:, 'FerretLLHandler', 'lwindow')
-    call ferret#private#post('location')
-  endif
-endfunction
index ffad4e78fae5ff553dc0880c2c476ced9a835c73..fc0cbe42d545dc1a4f827ebff7aac484ce54f863 100644 (file)
@@ -11,9 +11,8 @@ function! ferret#private#dispatch#search(command) abort
   let l:original_makeprg=&l:makeprg
   let l:original_errorformat=&l:errorformat
   try
-    let &l:makeprg=&grepprg . ' ' . a:command
-    let &l:errorformat=&grepformat
-    echomsg &l:makeprg
+    let &l:makeprg=ferret#private#executable() . ' ' . a:command
+    let &l:errorformat=g:FerretFormat
     Make
   catch
     call ferret#private#clearautocmd()
diff --git a/tests/fixtures/integration/ferret/input/autoload/ferret/private/shared.vim b/tests/fixtures/integration/ferret/input/autoload/ferret/private/shared.vim
new file mode 100644 (file)
index 0000000..14ad5a1
--- /dev/null
@@ -0,0 +1,31 @@
+" Copyright 2015-present Greg Hurrell. All rights reserved.
+" Licensed under the terms of the BSD 2-clause license.
+
+function! ferret#private#shared#finalize_search(output, ack)
+  let l:original_errorformat=&errorformat
+  try
+    let &errorformat=g:FerretFormat
+    if a:ack
+      call s:swallow('cexpr a:1', a:output)
+      execute get(g:, 'FerretQFHandler', 'botright cwindow')
+      call ferret#private#post('qf')
+    else
+      call s:swallow('lexpr a:1', a:output)
+      execute get(g:, 'FerretLLHandler', 'lwindow')
+      call ferret#private#post('location')
+    endif
+  finally
+    let &errorformat=l:original_errorformat
+  endtry
+endfunction
+
+" Execute `executable` expression, swallowing errors.
+" The intention is that this should catch "innocuous" errors, like a bad
+" modeline causing `cexpr` to throw an error when it tries to jump to that file.
+function! s:swallow(executable, ...)
+  try
+    execute a:executable
+  catch
+    echomsg 'Caught: ' . v:exception
+  endtry
+endfunction
index 205f0e8724a60a67537c77d02b2c75f598347696..5be33a57f03aae500ea1c18f19eccc1871193b99 100644 (file)
@@ -1,19 +1,8 @@
 " Copyright 2015-present Greg Hurrell. All rights reserved.
 " Licensed under the terms of the BSD 2-clause license.
 
-function! s:finalize_search(output, ack)
-  if a:ack
-    cexpr a:output
-    execute get(g:, 'FerretQFHandler', 'botright cwindow')
-    call ferret#private#post('qf')
-  else
-    lexpr a:output
-    execute get(g:, 'FerretLLHandler', 'lwindow')
-    call ferret#private#post('location')
-  endif
-endfunction
-
 function! ferret#private#vanilla#search(command, ack) abort
-  let l:output=system(&grepprg . ' ' . a:command)
-  call s:finalize_search(l:output, a:ack)
+  let l:executable=ferret#private#executable()
+  let l:output=system(l:executable . ' ' . a:command)
+  call ferret#private#shared#finalize_search(l:output, a:ack)
 endfunction
index ff2c7a1846583f7f94941a1fa1093eaecfc3bb13..8254bcf6cb47d10398f5245b90b47aa2a433b1d5 100644 (file)
 " ## 1. Powerful multi-file search
 "
 " Ferret provides an |:Ack| command for searching across multiple files using
-" The Silver Searcher (https://github.com/ggreer/the_silver_searcher), Ack
-" (http://beyondgrep.com/), or Grep (http://www.gnu.org/software/grep/). Support
-" for passing options through to the underlying search command exists, along
-" with the ability to use full regular expression syntax without doing special
-" escaping.
+" The Silver Searcher (https://github.com/ggreer/the_silver_searcher), or Ack
+" (http://beyondgrep.com/). Support for passing options through to the
+" underlying search command exists, along with the ability to use full regular
+" expression syntax without doing special escaping. On Vim version 8 or higher,
+" searches are performed asynchronously (without blocking the UI).
 "
 " Shortcut mappings are provided to start an |:Ack| search (<leader>a) or to
 " search for the word currently under the cursor (<leader>s).
 " provides a |:Lack| command that behaves like |:Ack| but uses the
 " |location-list| instead, and a <leader>l mapping as a shortcut to |:Lack|.
 "
+" |:Back| and |:Black| are analogous to |:Ack| and |:Lack|, but scoped to search
+" within currently open buffers only.
+"
 " Finally, Ferret offers integration with dispatch.vim
 " (https://github.com/tpope/vim-dispatch), which enables asynchronous searching
-" despite the fact that Vim itself is single-threaded.
+" on older versions of Vim (prior to version 8), despite the fact that Vim
+" itself is single-threaded.
 "
 " ## 2. Streamlined multi-file replace
 "
 " The companion to |:Ack| is |:Acks| (mnemonic: "Ack substitute", accessible via
 " shortcut <leader>r), which allows you to run a multi-file replace across all
-" the files placed in the |quickfix| window by a previous invocation of |:Ack|.
+" the files placed in the |quickfix| window by a previous invocation of |:Ack|
+" (or |:Back|).
 "
 " ## 3. Quickfix listing enhancements
 "
 " # Overrides
 "
 " Ferret overrides the 'grepformat' and 'grepprg' settings, preferentially
-" setting `ag`, `ack` or `grep` as the 'grepprg' (in that order) and configuring
-" a suitable 'grepformat'.
+" setting `rg`, `ag`, `ack` or `ack-grep` as the 'grepprg' (in that order) and
+" configuring a suitable 'grepformat'.
 "
 " Additionally, Ferret includes an |ftplugin| for the |quickfix| listing that
 " adjusts a number of settings to improve the usability of search results.
 " :Ack \blog\((['"]).*?\1\) -i --ignore-dir=src/vendor src dist build
 " ```
 "
-"
 " # FAQ
 "
 " ## Why do Ferret commands start with "Ack", "Lack" and so on?
 " |.vimrc|) around `ack`. The earliest traces of it can be seen in the initial
 " commit to my dotfiles repo in May, 2009 (https://wt.pe/h).
 "
-" So, even though Ferret has a new name now and actually prefers `ag` over `ack`
-" when available, I prefer to keep the command names intact and benefit from
-" years of accumulated muscle-memory.
-"
+" So, even though Ferret has a new name now and actually prefers `rg` then `ag`
+" over `ack`/`ack-grep` when available, I prefer to keep the command names
+" intact and benefit from years of accumulated muscle-memory.
 "
 "
 " # Related
 " order):
 "
 " - Daniel Silva
+" - Filip SzymaƄski
 " - Joe Lencioni
 " - Nelo-Thara Wallus
+" - Tom Dooner
 " - Vaibhav Sagar
 "
 "
 " # History
 "
+" ## master (not yet released)
+"
+" - Added |g:FerretLazyInit|.
+"
+" ## 1.4 (21 January 2017)
+"
+" - Drop broken support for `grep`, printing a prompt to install `rg`, `ag`, or
+"   `ack`/`ack-grep` instead.
+" - If an `ack` executable is not found, search for `ack-grep`, which is the
+"   name used on Debian-derived distros.
+"
+" ## 1.3 (8 January 2017)
+"
+" - Reset |'errorformat'| before each search (fixes issue #31).
+" - Added |:Back| and |:Black| commands, analogous to |:Ack| and |:Lack| but
+"   scoped to search within currently open buffers only.
+" - Change |:Acks| to use |:cfdo| when available rather than |:Qargs| and
+"   |:argdo|, to avoid polluting the |arglist|.
+" - Remove superfluous |QuickFixCmdPost| autocommands, resolving clash with
+"   Neomake plug-in (patch from Tom Dooner, #36).
+" - Add support for searching with ripgrep (`rg`).
+"
 " ## 1.2a (16 May 2016)
 "
 " - Add optional support for running searches asynchronously using Vim's |+job|
@@ -421,25 +448,19 @@ let g:FerretLoaded = 1
 let s:cpoptions = &cpoptions
 set cpoptions&vim
 
-if executable('ag') " The Silver Searcher: faster than ack.
-  let s:ackprg = 'ag --vimgrep'
-elseif executable('ack') " Ack: better than grep.
-  let s:ackprg = 'ack --column --with-filename'
-elseif executable('grep') " Grep: it's just grep.
-  let s:ackprg = &grepprg " default is: grep -n $* /dev/null
-endif
-
-if !empty(s:ackprg)
-  let &grepprg=s:ackprg
-  set grepformat=%f:%l:%c:%m
-endif
-
-if has('autocmd')
-  augroup Ferret
-    autocmd!
-    autocmd QuickFixCmdPost [^l]* nested cwindow
-    autocmd QuickFixCmdPost l* nested lwindow
-  augroup END
+""
+" @option g:FerretLazyInit boolean 1
+"
+" In order to minimize impact on Vim start-up time Ferret will initialize itself
+" lazily on first use by default. If you wish to force immediate initialization
+" (for example, to cause |'grepprg'| and |'grepformat'| to be set as soon as Vim
+" launches), then set |g:FerretLazyInit| to 0 in your |.vimrc|:
+"
+" ```
+" let g:FerrerLazyInit=0
+" ```
+if !get(g:, 'FerretLazyInit', 1)
+  call ferret#private#init()
 endif
 
 ""
@@ -449,15 +470,21 @@ endif
 " |:pwd|), unless otherwise overridden via {options}, and displays the results
 " in the |quickfix| listing.
 "
-" `ag` (The Silver Searcher) will be used preferentially if present on the
-" system, because it is faster, falling back to `ack` and then `grep` as needed.
+" `rg` (ripgrep) then `ag` (The Silver Searcher) will be used preferentially if
+" present on the system, because they are faster, falling back to
+" `ack`/`ack-grep` as needed.
 "
-" If dispatch.vim is installed the search process will run asynchronously via
-" the |:Make| command, otherwise it will be run synchronously via |:cexpr|.
-" Asynchronous searches are preferred because they do not block, despite the
-" fact that Vim itself is single threaded. The |g:FerretDispatch| option can be
+" On newer versions of Vim (version 8 and above), the search process runs
+" asynchronously in the background and does not block the UI.
+"
+" On older Vim versions (prior to version 8), if dispatch.vim is installed the
+" search process will run asynchronously via the |:Make| command, otherwise it
+" will be run synchronously via |:cexpr|. The |g:FerretDispatch| option can be
 " used to prevent the use of dispatch.vim.
 "
+" Asynchronous searches are preferred because they do not block, despite the
+" fact that Vim itself is single threaded.
+"
 " The {pattern} is passed through as-is to the underlying search program, and no
 " escaping is required other than preceding spaces by a single backslash. For
 " example, to search for "\bfoo[0-9]{2} bar\b" (ie. using `ag`'s Perl-style
@@ -492,6 +519,26 @@ command! -nargs=+ -complete=customlist,ferret#private#ackcomplete Ack call ferre
 " doesn't currently support the |location-list|.
 command! -nargs=+ -complete=customlist,ferret#private#lackcomplete Lack call ferret#private#lack(<f-args>)
 
+""
+" @command :Back {pattern} {options}
+"
+" Like |:Ack|, but searches only listed buffers. Note that the search is still
+" delegated to the underlying |'grepprg'| (`rg`, `ag`, `ack` or `ack-grep`),
+" which means that only buffers written to disk will be searched. If no buffers
+" are written to disk, then |:Back| behaves exactly like |:Ack| and will search
+" all files in the current directory.
+command! -nargs=+ -complete=customlist,ferret#private#backcomplete Back call ferret#private#back(<f-args>)
+
+""
+" @command :Black {pattern} {options}
+"
+" Like |:Lack|, but searches only listed buffers. As with |:Back|, the search is
+" still delegated to the underlying |'grepprg'| (`rg`, `ag`, `ack` or
+" `ack-grep`), which means that only buffers written to disk will be searched.
+" Likewise, If no buffers are written to disk, then |:Black| behaves exactly
+" like |:Lack| and will search all files in the current directory.
+command! -nargs=+ -complete=customlist,ferret#private#blackcomplete Black call ferret#private#black(<f-args>)
+
 ""
 " @command :Acks /{pattern}/{replacement}/
 "
@@ -596,8 +643,9 @@ endif
 ""
 " @command :Qargs
 "
-" This is a utility function that is used by the |:Acks| command but is also
-" generally useful enough to warrant being exposed publicly.
+" This is a utility function that is used internally when running on older
+" versions of Vim (prior to version 8) but is also generally useful enough to
+" warrant being exposed publicly.
 "
 " It takes the files currently in the |quickfix| listing and sets them as
 " |:args| so that they can be operated on en masse via the |:argdo| command.
@@ -623,6 +671,12 @@ if s:commands
   cabbrev <silent> <expr> cpf ((getcmdtype() == ':' && getcmdpos() == 4) ? 'cpf <bar> normal zz' : 'cpf')
 endif
 
+""
+" @option g:FerretFormat string "%f:%l:%c:%m"
+"
+" Sets the '|grepformat|' used by Ferret.
+let g:FerretFormat=get(g:, 'FerretFormat', '%f:%l:%c:%m')
+
 " Restore 'cpoptions' to its former value.
 let &cpoptions = s:cpoptions
 unlet s:cpoptions
index 91fd24d1f97f50dc8470e550027f24668e62cdbb..7e55ddbede229f23b5dbf613933e3eaedd6fdd35 100755 (executable)
@@ -43,7 +43,7 @@ module DSL
 
   def session(name, &block)
     escaped_name = DSL.escape(name)
-    %x{tmux new-session -d -s #{DSL.escape(escaped_name)}}
+    %x{tmux new-session -d -s #{DSL.escape(escaped_name)} -y 20}
     Session.new(escaped_name).instance_eval(&block)
     %x{tmux kill-session -t #{escaped_name}}
   end
@@ -52,10 +52,11 @@ self.extend(DSL)
 Object.instance_eval { include DSL::Constants }
 
 session('ferret-test') do |name|
-  send_keys('vim -u NONE', Enter)
-  send_keys(':set nocompatible', Enter)
-  send_keys(":set rtp+=#{DSL.escape(Dir.pwd)}", Enter)
+  send_keys('vim -u NONE -N', Enter)
+  send_keys(':set shortmess+=A', Enter)
+  send_keys(":set runtimepath+=#{DSL.escape(Dir.pwd)}", Enter)
   send_keys(':runtime! plugin/ferret.vim', Enter)
   send_keys(Backslash, 'a', 'usr/bin/env', Backslash, Space, 'ruby', Enter)
   wait_for(/module DSL/)
+  send_keys(':quitall!', Enter)
 end