Update Ferret fixtures
authorGreg Hurrell <greg@hurrell.net>
Tue, 25 Dec 2018 17:32:30 +0000 (18:32 +0100)
committerGreg Hurrell <greg@hurrell.net>
Tue, 25 Dec 2018 17:32:30 +0000 (18:32 +0100)
tests/fixtures/integration/ferret/golden/ast.golden
tests/fixtures/integration/ferret/golden/markdown.golden
tests/fixtures/integration/ferret/golden/plaintext.golden
tests/fixtures/integration/ferret/input/autoload/ferret.vim [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/nvim.vim [new file with mode: 0644]
tests/fixtures/integration/ferret/input/autoload/ferret/private/shared.vim
tests/fixtures/integration/ferret/input/plugin/ferret.vim

index 6ca26b6abfd8c8a207719deefd583cb7a2e18e5a..73551eb23a7bd53e32db12b0ef9d43e3af031b9c 100644 (file)
@@ -7,6 +7,7 @@ Project
           , "Commands"
           , "Mappings"
           , "Options"
+          , "Functions"
           , "Custom autocommands"
           , "Overrides"
           , "Troubleshooting"
@@ -105,6 +106,10 @@ Project
       , Whitespace
       , Plaintext "using"
       , Whitespace
+      , Plaintext "ripgrep"
+      , Whitespace
+      , Plaintext "(https://github.com/BurntSushi/ripgrep),"
+      , Whitespace
       , Plaintext "The"
       , Whitespace
       , Plaintext "Silver"
@@ -171,9 +176,15 @@ Project
       , Whitespace
       , Plaintext "On"
       , Whitespace
+      , Plaintext "modern"
+      , Whitespace
+      , Plaintext "versions"
+      , Whitespace
+      , Plaintext "of"
+      , Whitespace
       , Plaintext "Vim"
       , Whitespace
-      , Plaintext "version"
+      , Plaintext "(version"
       , Whitespace
       , Plaintext "8"
       , Whitespace
@@ -181,6 +192,10 @@ Project
       , Whitespace
       , Plaintext "higher,"
       , Whitespace
+      , Plaintext "or"
+      , Whitespace
+      , Plaintext "Neovim),"
+      , Whitespace
       , Plaintext "searches"
       , Whitespace
       , Plaintext "are"
@@ -346,63 +361,6 @@ Project
       , Whitespace
       , Plaintext "only."
       ]
-  , Paragraph
-      [ Plaintext "Finally,"
-      , Whitespace
-      , Plaintext "Ferret"
-      , Whitespace
-      , Plaintext "offers"
-      , Whitespace
-      , Plaintext "integration"
-      , Whitespace
-      , Plaintext "with"
-      , Whitespace
-      , Plaintext "dispatch.vim"
-      , Whitespace
-      , Plaintext "(https://github.com/tpope/vim-dispatch),"
-      , Whitespace
-      , Plaintext "which"
-      , Whitespace
-      , Plaintext "enables"
-      , Whitespace
-      , Plaintext "asynchronous"
-      , Whitespace
-      , Plaintext "searching"
-      , Whitespace
-      , Plaintext "on"
-      , Whitespace
-      , Plaintext "older"
-      , Whitespace
-      , Plaintext "versions"
-      , Whitespace
-      , Plaintext "of"
-      , Whitespace
-      , Plaintext "Vim"
-      , Whitespace
-      , Plaintext "(prior"
-      , Whitespace
-      , Plaintext "to"
-      , Whitespace
-      , Plaintext "version"
-      , Whitespace
-      , Plaintext "8),"
-      , Whitespace
-      , Plaintext "despite"
-      , Whitespace
-      , Plaintext "the"
-      , Whitespace
-      , Plaintext "fact"
-      , Whitespace
-      , Plaintext "that"
-      , Whitespace
-      , Plaintext "Vim"
-      , Whitespace
-      , Plaintext "itself"
-      , Whitespace
-      , Plaintext "is"
-      , Whitespace
-      , Plaintext "single-threaded."
-      ]
   , SubheadingAnnotation "2. Streamlined multi-file replace"
   , Paragraph
       [ Plaintext "The"
@@ -702,6 +660,16 @@ Project
       , Whitespace
       , Plaintext "covers"
       , Whitespace
+      , Plaintext "on"
+      , Whitespace
+      , Plaintext "older"
+      , Whitespace
+      , Plaintext "versions"
+      , Whitespace
+      , Plaintext "of"
+      , Whitespace
+      , Plaintext "Vim"
+      , Whitespace
       , Plaintext "by"
       , Whitespace
       , Link ":Acks"
@@ -712,7 +680,25 @@ Project
       , Whitespace
       , Plaintext "its"
       , Whitespace
-      , Plaintext "work."
+      , Plaintext "work"
+      , Whitespace
+      , Plaintext "(on"
+      , Whitespace
+      , Plaintext "newer"
+      , Whitespace
+      , Plaintext "versions"
+      , Whitespace
+      , Plaintext "the"
+      , Whitespace
+      , Plaintext "built-in"
+      , Whitespace
+      , Link ":cfdo"
+      , Whitespace
+      , Plaintext "is"
+      , Whitespace
+      , Plaintext "used"
+      , Whitespace
+      , Plaintext "instead)."
       ]
   , HeadingAnnotation "Installation"
   , Paragraph
@@ -913,6 +899,18 @@ Project
   , Fenced [ ":call pathogen#helptags()" ]
   , Project
       [ Unit
+          [ FunctionDeclaration
+              { functionBang = True
+              , functionName = "ferret#get_default_arguments"
+              , functionArguments = ArgumentList [ Argument "executable" ]
+              , functionAttributes = [ "abort" ]
+              , functionBody =
+                  [ GenericStatement
+                      "return get(ferret#private#executables(), a:executable, '')"
+                  ]
+              }
+          ]
+      , Unit
           [ FunctionDeclaration
               { functionBang = True
               , functionName = "s:delete"
@@ -933,16 +931,14 @@ Project
               }
           , FunctionDeclaration
               { functionBang = True
-              , functionName = "ferret#private#dispatch"
+              , functionName = "ferret#private#nvim"
               , functionArguments = ArgumentList []
-              , functionAttributes = [ "abort" ]
+              , functionAttributes = []
               , functionBody =
                   [ Empty
                   , LetStatement
-                      { letLexpr = "l:dispatch"
-                      , letValue = "get(g:, 'FerretDispatch', 1)"
-                      }
-                  , GenericStatement "return l:dispatch && exists(':Make') == 2"
+                      { letLexpr = "l:nvim" , letValue = "get(g:, 'FerretNvim', 1)" }
+                  , GenericStatement "return l:nvim && has('nvim')"
                   ]
               }
           , FunctionDeclaration
@@ -963,7 +959,11 @@ Project
               , functionArguments = ArgumentList [ Argument "message" ]
               , functionAttributes = [ "abort" ]
               , functionBody =
-                  [ GenericStatement "call inputsave()"
+                  [ GenericStatement "if has('lambda') && has('timers')"
+                  , GenericStatement
+                      "call timer_start(100, {-> s:print_error_with_echomsg(a:message)})"
+                  , GenericStatement "else"
+                  , GenericStatement "call inputsave()"
                   , GenericStatement "echohl ErrorMsg"
                   , GenericStatement
                       "unsilent call input(a:message . ': press ENTER to continue')"
@@ -971,6 +971,19 @@ Project
                   , GenericStatement "call inputrestore()"
                   , GenericStatement "unsilent echo"
                   , GenericStatement "redraw!"
+                  , GenericStatement "endif"
+                  ]
+              }
+          , FunctionDeclaration
+              { functionBang = True
+              , functionName = "s:print_error_with_echomsg"
+              , functionArguments = ArgumentList [ Argument "message" ]
+              , functionAttributes = []
+              , functionBody =
+                  [ GenericStatement "redraw!"
+                  , GenericStatement "echohl ErrorMsg"
+                  , GenericStatement "echomsg a:message"
+                  , GenericStatement "echohl NONE"
                   ]
               }
           , FunctionDeclaration
@@ -983,8 +996,24 @@ Project
                   , UnletStatement
                       { unletBang = False , unletBody = "g:ferret_lastsearch" }
                   , GenericStatement "endif"
+                  , LetStatement
+                      { letLexpr = "l:odd_number_of_backslashes"
+                      , letValue = "'\\\\\\@<!\\\\\\(\\\\\\\\\\)*\\\\\\@!'"
+                      }
+                  , LetStatement
+                      { letLexpr = "l:unescaped_space"
+                      , letValue = "'\\('.l:odd_number_of_backslashes.'\\)\\@<! '"
+                      }
+                  , LetStatement
+                      { letLexpr = "l:args"
+                      , letValue = "split(a:args, l:unescaped_space)"
+                      }
                   , LetStatement { letLexpr = "l:expanded_args" , letValue = "[]" }
-                  , GenericStatement "for l:arg in a:args"
+                  , GenericStatement "for l:arg in l:args"
+                  , LetStatement
+                      { letLexpr = "l:arg"
+                      , letValue = "substitute(l:arg, '\\\\ ', ' ', 'g')"
+                      }
                   , GenericStatement "if ferret#private#option(l:arg)"
                   , GenericStatement "call add(l:expanded_args, l:arg)"
                   , GenericStatement "elseif exists('g:ferret_lastsearch')"
@@ -1004,7 +1033,8 @@ Project
                   , GenericStatement "call add(l:expanded_args, l:arg)"
                   , GenericStatement "endif"
                   , GenericStatement "endfor"
-                  , GenericStatement "if ferret#private#async()"
+                  , GenericStatement
+                      "if ferret#private#nvim() || ferret#private#async()"
                   , GenericStatement "return l:expanded_args"
                   , GenericStatement "endif"
                   , LetStatement
@@ -1031,6 +1061,28 @@ Project
                   , GenericStatement "endif"
                   ]
               }
+          , FunctionDeclaration
+              { functionBang = True
+              , functionName = "s:qfsize"
+              , functionArguments = ArgumentList [ Argument "type" ]
+              , functionAttributes = [ "abort" ]
+              , functionBody =
+                  [ GenericStatement "if has('patch-8.0.1112')"
+                  , GenericStatement "if a:type ==# 'qf'"
+                  , GenericStatement "return get(getqflist({'size' : 0}), 'size', 0)"
+                  , GenericStatement "else"
+                  , GenericStatement
+                      "return get(getloclist(0, {'size' : 0}), 'size', 0)"
+                  , GenericStatement "endif"
+                  , GenericStatement "else"
+                  , LetStatement
+                      { letLexpr = "l:qflist"
+                      , letValue = "a:type ==# 'qf' ? getqflist() : getloclist(0)"
+                      }
+                  , GenericStatement "return len(l:qflist)"
+                  , GenericStatement "endif"
+                  ]
+              }
           , FunctionDeclaration
               { functionBang = True
               , functionName = "ferret#private#post"
@@ -1039,20 +1091,18 @@ Project
               , functionBody =
                   [ GenericStatement "call ferret#private#clearautocmd()"
                   , LetStatement
-                      { letLexpr = "l:lastsearch "
+                      { letLexpr = "l:lastsearch"
                       , letValue = "get(g:, 'ferret_lastsearch', '')"
                       }
                   , LetStatement
-                      { letLexpr = "l:qflist "
-                      , letValue = "a:type == 'qf' ? getqflist() : getloclist(0)"
-                      }
-                  , LetStatement
-                      { letLexpr = "l:tip "
+                      { letLexpr = "l:tip"
                       , letValue = "' [see `:help ferret-quotes`]'"
                       }
-                  , GenericStatement "if len(l:qflist) == 0"
                   , LetStatement
-                      { letLexpr = "l:base "
+                      { letLexpr = "l:len" , letValue = "s:qfsize(a:type)" }
+                  , GenericStatement "if l:len == 0"
+                  , LetStatement
+                      { letLexpr = "l:base"
                       , letValue =
                           "'No results for search pattern `' . l:lastsearch . '`'"
                       }
@@ -1064,10 +1114,14 @@ Project
                   , GenericStatement "endif"
                   , GenericStatement "else"
                   , LetStatement
-                      { letLexpr = "l:invalid "
+                      { letLexpr = "l:qflist"
+                      , letValue = "a:type ==# 'qf' ? getqflist() : getloclist(0)"
+                      }
+                  , LetStatement
+                      { letLexpr = "l:invalid"
                       , letValue = "filter(copy(l:qflist), 'v:val.valid == 0')"
                       }
-                  , GenericStatement "if len(l:invalid) == len(l:qflist)"
+                  , GenericStatement "if len(l:invalid) == l:len"
                   , GenericStatement "redraw!"
                   , GenericStatement "echohl ErrorMsg"
                   , GenericStatement "for l:item in l:invalid"
@@ -1075,34 +1129,29 @@ Project
                   , GenericStatement "endfor"
                   , GenericStatement "echohl NONE"
                   , LetStatement
-                      { letLexpr = "l:base "
+                      { letLexpr = "l:base"
                       , letValue = "'Search for `' . l:lastsearch . '` failed'"
                       }
-                  , LetStatement
-                      { letLexpr = "l:suffix "
-                      , letValue = "a:type == 'qf' && ferret#private#dispatch() ?"
-                      }
-                  , GenericStatement "\\ ' (run `:messages` to see details)' : ''"
                   , GenericStatement
                       "if l:lastsearch =~ '\\v^[' . \"'\" . '\"].+[^' . \"'\" . '\"]$'"
-                  , GenericStatement
-                      "call ferret#private#error(l:base . l:tip . l:suffix)"
+                  , GenericStatement "call ferret#private#error(l:base . l:tip)"
                   , GenericStatement "else"
-                  , GenericStatement "call ferret#private#error(l:base . l:suffix)"
+                  , GenericStatement "call ferret#private#error(l:base)"
                   , GenericStatement "endif"
                   , GenericStatement "endif"
                   , GenericStatement "endif"
+                  , GenericStatement "return l:len"
                   ]
               }
           , FunctionDeclaration
               { functionBang = True
               , functionName = "ferret#private#ack"
               , functionArguments =
-                  ArgumentList [ Argument "bang" , Argument "..." ]
+                  ArgumentList [ Argument "bang" , Argument "args" ]
               , functionAttributes = [ "abort" ]
               , functionBody =
                   [ LetStatement
-                      { letLexpr = "l:command" , letValue = "s:parse(a:000)" }
+                      { letLexpr = "l:command" , letValue = "s:parse(a:args)" }
                   , GenericStatement "call ferret#private#hlsearch()"
                   , LetStatement
                       { letLexpr = "l:executable"
@@ -1112,11 +1161,12 @@ Project
                   , GenericStatement "call ferret#private#installprompt()"
                   , GenericStatement "return"
                   , GenericStatement "endif"
-                  , GenericStatement "if ferret#private#async()"
+                  , GenericStatement "if ferret#private#nvim()"
+                  , GenericStatement
+                      "call ferret#private#nvim#search(l:command, 1, a:bang)"
+                  , GenericStatement "elseif ferret#private#async()"
                   , GenericStatement
                       "call ferret#private#async#search(l:command, 1, a:bang)"
-                  , GenericStatement "elseif ferret#private#dispatch()"
-                  , GenericStatement "call ferret#private#dispatch#search(l:command)"
                   , GenericStatement "else"
                   , GenericStatement
                       "call ferret#private#vanilla#search(l:command, 1)"
@@ -1138,29 +1188,29 @@ Project
                       , letValue =
                           "filter(map(l:buflist, 'v:val.name'), 'v:val !=# \"\"')"
                       }
-                  , GenericStatement "return l:bufpaths"
+                  , GenericStatement "return join(l:bufpaths, ' ')"
                   ]
               }
           , FunctionDeclaration
               { functionBang = True
               , functionName = "ferret#private#back"
               , functionArguments =
-                  ArgumentList [ Argument "bang" , Argument "..." ]
+                  ArgumentList [ Argument "bang" , Argument "args" ]
               , functionAttributes = [ "abort" ]
               , functionBody =
                   [ GenericStatement
-                      "call call('ferret#private#ack', a:bang, a:000 + ferret#private#buflist())"
+                      "call call('ferret#private#ack', [a:bang, a:args . ' ' . ferret#private#buflist()])"
                   ]
               }
           , FunctionDeclaration
               { functionBang = True
               , functionName = "ferret#private#black"
               , functionArguments =
-                  ArgumentList [ Argument "bang" , Argument "..." ]
+                  ArgumentList [ Argument "bang" , Argument "args" ]
               , functionAttributes = [ "abort" ]
               , functionBody =
                   [ GenericStatement
-                      "call call('ferret#private#lack', a:bang, a:000 + ferret#private#buflist())"
+                      "call call('ferret#private#lack', [a:bang, a:args . ' ' . ferret#private#buflist()])"
                   ]
               }
           , FunctionDeclaration
@@ -1177,11 +1227,11 @@ Project
               { functionBang = True
               , functionName = "ferret#private#lack"
               , functionArguments =
-                  ArgumentList [ Argument "bang" , Argument "..." ]
+                  ArgumentList [ Argument "bang" , Argument "args" ]
               , functionAttributes = [ "abort" ]
               , functionBody =
                   [ LetStatement
-                      { letLexpr = "l:command" , letValue = "s:parse(a:000)" }
+                      { letLexpr = "l:command" , letValue = "s:parse(a:args)" }
                   , GenericStatement "call ferret#private#hlsearch()"
                   , LetStatement
                       { letLexpr = "l:executable"
@@ -1191,7 +1241,10 @@ Project
                   , GenericStatement "call ferret#private#installprompt()"
                   , GenericStatement "return"
                   , GenericStatement "endif"
-                  , GenericStatement "if ferret#private#async()"
+                  , GenericStatement "if ferret#private#nvim()"
+                  , GenericStatement
+                      "call ferret#private#nvim#search(l:command, 0, a:bang)"
+                  , GenericStatement "elseif ferret#private#async()"
                   , GenericStatement
                       "call ferret#private#async#search(l:command, 0, a:bang)"
                   , GenericStatement "else"
@@ -1228,7 +1281,7 @@ Project
               , functionAttributes = [ "abort" ]
               , functionBody =
                   [ LetStatement
-                      { letLexpr = "l:matches "
+                      { letLexpr = "l:matches"
                       , letValue =
                           "matchlist(a:command, '\\v\\C^(([^|\"\\\\a-zA-Z0-9]).+\\2.*\\2)([cgeiI]*)$')"
                       }
@@ -1238,9 +1291,9 @@ Project
                   , GenericStatement "return"
                   , GenericStatement "endif"
                   , LetStatement
-                      { letLexpr = "l:pattern " , letValue = "l:matches[1]" }
+                      { letLexpr = "l:pattern" , letValue = "l:matches[1]" }
                   , LetStatement
-                      { letLexpr = "l:options " , letValue = "l:matches[3]" }
+                      { letLexpr = "l:options" , letValue = "l:matches[3]" }
                   , GenericStatement "if l:options !~# 'e'"
                   , LetStatement { letLexpr = "l:options ." , letValue = "'e'" }
                   , GenericStatement "endif"
@@ -1361,7 +1414,7 @@ Project
                       }
                   ]
               }
-          , LetStatement { letLexpr = "s:options " , letValue = "{" }
+          , LetStatement { letLexpr = "s:options" , letValue = "{" }
           , GenericStatement
               "\\ 'ack': [ '--ignore-ack-defaults', '--ignore-case', '--ignore-dir', '--ignore-directory', '--invert-match', '--known-types', '--literal', '--no-recurse', '--recurse', '--sort-files', '--type', '--word-regexp', '-1', '-Q', '-R', '-i', '-k', '-r', '-v', '-w', ], 'ag': [ '--all-types', '--all-text', '--case-sensitive', '--depth', '--follow', '--ignore', '--ignore-case', '--ignore-dir', '--invert-match', '--literal', '--max-count', '--skip-vcs-ignores', '--unrestricted', '--word-regexp', '-Q', '-U', '-a', '-i', '-m', '-s', '-t', '-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' ] }"
           , LetStatement
@@ -1476,8 +1529,15 @@ Project
               }
           , LetStatement { letLexpr = "s:executables" , letValue = "{" }
           , GenericStatement
-              "\\ 'rg': 'rg --vimgrep --no-heading', 'ag': 'ag', 'ack': 'ack --column --with-filename', 'ack-grep': 'ack-grep --column --with-filename' }"
+              "\\ 'rg': '--vimgrep --no-config --no-heading', 'ag': '', 'ack': '--column --with-filename', 'ack-grep': '--column --with-filename' }"
           , LetStatement { letLexpr = "s:init_done" , letValue = "0" }
+          , FunctionDeclaration
+              { functionBang = True
+              , functionName = "ferret#private#executables"
+              , functionArguments = ArgumentList []
+              , functionAttributes = [ "abort" ]
+              , functionBody = [ GenericStatement "return copy(s:executables)" ]
+              }
           , FunctionDeclaration
               { functionBang = True
               , functionName = "ferret#private#init"
@@ -1499,10 +1559,10 @@ Project
                       { letLexpr = "l:ag_help" , letValue = "system('ag --help')" }
                   , GenericStatement "if match(l:ag_help, '--vimgrep') != -1"
                   , LetStatement
-                      { letLexpr = "s:executables['ag']." , letValue = "' --vimgrep'" }
+                      { letLexpr = "s:executables['ag']." , letValue = "'--vimgrep'" }
                   , GenericStatement "else"
                   , LetStatement
-                      { letLexpr = "s:executables['ag']." , letValue = "' --column'" }
+                      { letLexpr = "s:executables['ag']." , letValue = "'--column'" }
                   , GenericStatement "endif"
                   , GenericStatement "if match(l:ag_help, '--width') != -1"
                   , LetStatement
@@ -1554,12 +1614,39 @@ Project
                   , GenericStatement "endif"
                   , GenericStatement "for l:executable in l:executables"
                   , GenericStatement "if executable(l:executable)"
-                  , GenericStatement "return s:executables[l:executable]"
+                  , Empty
+                  , LetStatement
+                      { letLexpr = "l:overrides"
+                      , letValue = "get(g:, 'FerretExecutableArguments', {})"
+                      }
+                  , LetStatement
+                      { letLexpr = "l:type"
+                      , letValue = "exists('v:t_dict') ? v:t_dict : 4"
+                      }
+                  , GenericStatement
+                      "if type(l:overrides) == l:type && has_key(l:overrides, l:executable)"
+                  , GenericStatement
+                      "return l:executable . ' ' . l:overrides[l:executable]"
+                  , GenericStatement "else"
+                  , GenericStatement
+                      "return l:executable . ' ' . s:executables[l:executable]"
+                  , GenericStatement "endif"
                   , GenericStatement "endif"
                   , GenericStatement "endfor"
                   , GenericStatement "return ''"
                   ]
               }
+          , FunctionDeclaration
+              { functionBang = True
+              , functionName = "ferret#private#limit"
+              , functionArguments = ArgumentList []
+              , functionAttributes = [ "abort" ]
+              , functionBody =
+                  [ Empty
+                  , GenericStatement
+                      "return max([1, +get(g:, 'FerretMaxResults', 100000)]) - 1"
+                  ]
+              }
           , GenericStatement "call ferret#private#init()"
           ]
       , Unit
@@ -1662,9 +1749,7 @@ Project
                   ]
               }
           , LetStatement
-              { letLexpr = "s:limit"
-              , letValue = "max([1, +get(g:, 'FerretMaxResults', 100000)]) - 1"
-              }
+              { letLexpr = "s:limit" , letValue = "ferret#private#limit()" }
           , FunctionDeclaration
               { functionBang = True
               , functionName = "ferret#private#async#out_cb"
@@ -1849,102 +1934,402 @@ Project
               }
           ]
       , Unit
-          [ FunctionDeclaration
+          [ LetStatement { letLexpr = "s:jobs" , letValue = "{}" }
+          , FunctionDeclaration
               { functionBang = True
-              , functionName = "ferret#private#shared#finalize_search"
-              , functionArguments =
-                  ArgumentList [ Argument "output" , Argument "ack" ]
+              , functionName = "s:info_from_job"
+              , functionArguments = ArgumentList [ Argument "job" ]
               , functionAttributes = []
               , functionBody =
-                  [ LetStatement
-                      { letLexpr = "l:original_errorformat" , letValue = "&errorformat" }
-                  , GenericStatement "try"
-                  , LetStatement
-                      { letLexpr = "&errorformat" , letValue = "g:FerretFormat" }
-                  , GenericStatement "if a:ack"
-                  , GenericStatement "call s:swallow('cexpr a:1', a:output)"
-                  , GenericStatement
-                      "execute get(g:, 'FerretQFHandler', 'botright cwindow')"
-                  , GenericStatement "call ferret#private#post('qf')"
-                  , GenericStatement "else"
-                  , GenericStatement "call s:swallow('lexpr a:1', a:output)"
-                  , GenericStatement "execute get(g:, 'FerretLLHandler', 'lwindow')"
-                  , GenericStatement "call ferret#private#post('location')"
+                  [ GenericStatement "if has_key(s:jobs, a:job)"
+                  , GenericStatement "return s:jobs[a:job]"
                   , GenericStatement "endif"
-                  , GenericStatement "finally"
-                  , LetStatement
-                      { letLexpr = "&errorformat" , letValue = "l:original_errorformat" }
-                  , GenericStatement "endtry"
                   ]
               }
           , FunctionDeclaration
               { functionBang = True
-              , functionName = "s:swallow"
-              , functionArguments =
-                  ArgumentList [ Argument "executable" , Argument "..." ]
-              , functionAttributes = []
-              , functionBody =
-                  [ GenericStatement "try"
-                  , GenericStatement "execute a:executable"
-                  , GenericStatement "catch"
-                  , GenericStatement "echomsg 'Caught: ' . v:exception"
-                  , GenericStatement "endtry"
-                  ]
-              }
-          ]
-      , Unit
-          [ FunctionDeclaration
-              { functionBang = True
-              , functionName = "ferret#private#vanilla#search"
+              , functionName = "ferret#private#nvim#search"
               , functionArguments =
-                  ArgumentList [ Argument "command" , Argument "ack" ]
+                  ArgumentList
+                    [ Argument "command" , Argument "ack" , Argument "bang" ]
               , functionAttributes = [ "abort" ]
               , functionBody =
-                  [ LetStatement
-                      { letLexpr = "l:executable"
-                      , letValue = "ferret#private#executable()"
+                  [ GenericStatement "call ferret#private#nvim#cancel()"
+                  , GenericStatement
+                      "call ferret#private#autocmd('FerretAsyncStart')"
+                  , LetStatement
+                      { letLexpr = "l:command_and_args"
+                      , letValue =
+                          "extend(split(ferret#private#executable()), a:command)"
                       }
                   , LetStatement
-                      { letLexpr = "l:output"
-                      , letValue = "system(l:executable . ' ' . a:command)"
+                      { letLexpr = "l:job"
+                      , letValue = "jobstart(l:command_and_args, {"
                       }
                   , GenericStatement
-                      "call ferret#private#shared#finalize_search(l:output, a:ack)"
+                      "\\ 'on_stderr': 'ferret#private#nvim#err_cb', 'on_stdout': 'ferret#private#nvim#out_cb', 'on_exit': 'ferret#private#nvim#close_cb' })"
+                  , LetStatement { letLexpr = "s:jobs[l:job]" , letValue = "{" }
+                  , GenericStatement
+                      "\\ 'job': l:job, 'errors': [], 'output': [], 'pending_error': '', 'pending_output': '', 'pending_error_length': 0, 'pending_output_length': 0, 'result_count': 0, 'ack': a:ack, 'bang': a:bang, 'window': win_getid() }"
                   ]
               }
-          ]
-      , Unit
-          [ LetStatement
-              { letLexpr = "s:options"
-              , letValue = "get(g:, 'FerretQFOptions', 1)"
-              }
-          , GenericStatement "if s:options"
-          , GenericStatement "setlocal nolist"
-          , GenericStatement "if exists('+relativenumber')"
-          , GenericStatement "setlocal norelativenumber"
-          , GenericStatement "endif"
-          , GenericStatement "setlocal nowrap"
-          , GenericStatement "setlocal number"
-          , LetStatement
-              { letLexpr = "s:original_scrolloff" , letValue = "&scrolloff" }
-          , GenericStatement "set scrolloff=0"
-          , GenericStatement "if has('autocmd')"
-          , GenericStatement "augroup FerretQF"
-          , GenericStatement "autocmd!"
-          , GenericStatement
-              "autocmd BufLeave <buffer> execute 'set scrolloff=' . s:original_scrolloff"
-          , GenericStatement
-              "autocmd BufEnter <buffer> set scrolloff=0 | setlocal nocursorline"
-          , GenericStatement "augroup END"
-          , GenericStatement "endif"
-          , GenericStatement "endif"
           , LetStatement
-              { letLexpr = "s:map" , letValue = "get(g:, 'FerretQFMap', 1)" }
-          , GenericStatement "if s:map"
-          , GenericStatement
-              "nnoremap <buffer> <silent> d :set operatorfunc=ferret#private#qf_delete_motion<CR>g@"
-          , GenericStatement
-              "nnoremap <buffer> <silent> dd :call ferret#private#qf_delete()<CR>"
+              { letLexpr = "s:max_line_length" , letValue = "4096" }
+          , FunctionDeclaration
+              { functionBang = True
+              , functionName = "ferret#private#nvim#err_cb"
+              , functionArguments =
+                  ArgumentList
+                    [ Argument "job" , Argument "lines" , Argument "event" ]
+              , functionAttributes = [ "dict" ]
+              , functionBody =
+                  [ LetStatement
+                      { letLexpr = "l:info" , letValue = "s:info_from_job(a:job)" }
+                  , GenericStatement "if type(l:info) == 4"
+                  , LetStatement { letLexpr = "l:count" , letValue = "len(a:lines)" }
+                  , GenericStatement "for l:i in range(l:count)"
+                  , LetStatement { letLexpr = "l:line" , letValue = "a:lines[l:i]" }
+                  , GenericStatement
+                      "if l:i != l:count - 1 || l:line == '' && l:info.pending_error_length"
+                  , GenericStatement
+                      "if l:info.pending_error_length < s:max_line_length"
+                  , LetStatement { letLexpr = "l:rest" , letValue = "strpart(" }
+                  , GenericStatement
+                      "\\ l:line, 0, s:max_line_length - l:info.pending_error_length )"
+                  , GenericStatement
+                      "call add(l:info.errors, l:info.pending_error . l:rest)"
+                  , GenericStatement "else"
+                  , GenericStatement "call add(l:info.errors, l:info.pending_error)"
+                  , GenericStatement "endif"
+                  , LetStatement
+                      { letLexpr = "l:info.pending_error" , letValue = "''" }
+                  , LetStatement
+                      { letLexpr = "l:info.pending_error_length" , letValue = "0" }
+                  , GenericStatement
+                      "elseif l:info.pending_error_length < s:max_line_length"
+                  , LetStatement
+                      { letLexpr = "l:info.pending_error." , letValue = "l:line" }
+                  , LetStatement
+                      { letLexpr = "l:info.pending_error_length+"
+                      , letValue = "strlen(l:line)"
+                      }
+                  , GenericStatement "endif"
+                  , GenericStatement "endfor"
+                  , GenericStatement "endif"
+                  ]
+              }
+          , LetStatement
+              { letLexpr = "s:limit" , letValue = "ferret#private#limit()" }
+          , FunctionDeclaration
+              { functionBang = True
+              , functionName = "ferret#private#nvim#out_cb"
+              , functionArguments =
+                  ArgumentList
+                    [ Argument "job" , Argument "lines" , Argument "event" ]
+              , functionAttributes = [ "dict" ]
+              , functionBody =
+                  [ LetStatement
+                      { letLexpr = "l:info" , letValue = "s:info_from_job(a:job)" }
+                  , GenericStatement "if type(l:info) == 4"
+                  , GenericStatement
+                      "if !l:info.bang && l:info.result_count > s:limit"
+                  , GenericStatement "call s:MaxResultsExceeded(l:info)"
+                  , GenericStatement "return"
+                  , GenericStatement "endif"
+                  , LetStatement { letLexpr = "l:count" , letValue = "len(a:lines)" }
+                  , GenericStatement "for l:i in range(l:count)"
+                  , LetStatement { letLexpr = "l:line" , letValue = "a:lines[l:i]" }
+                  , GenericStatement
+                      "if l:i != l:count - 1 || l:line == '' && l:info.pending_output_length"
+                  , GenericStatement
+                      "if l:info.pending_output_length < s:max_line_length"
+                  , LetStatement { letLexpr = "l:rest" , letValue = "strpart(" }
+                  , GenericStatement
+                      "\\ l:line, 0, s:max_line_length - l:info.pending_output_length )"
+                  , GenericStatement
+                      "call add(l:info.output, l:info.pending_output . l:rest)"
+                  , GenericStatement "else"
+                  , GenericStatement "call add(l:info.output, l:info.pending_output)"
+                  , GenericStatement "endif"
+                  , LetStatement
+                      { letLexpr = "l:info.pending_output" , letValue = "''" }
+                  , LetStatement
+                      { letLexpr = "l:info.pending_output_length" , letValue = "0" }
+                  , GenericStatement "if !l:info.bang"
+                  , LetStatement
+                      { letLexpr = "l:info.result_count+" , letValue = "1" }
+                  , GenericStatement "if l:info.result_count > s:limit"
+                  , GenericStatement "call s:MaxResultsExceeded(l:info)"
+                  , GenericStatement "break"
+                  , GenericStatement "endif"
+                  , GenericStatement "endif"
+                  , GenericStatement
+                      "elseif l:info.pending_output_length < s:max_line_length"
+                  , LetStatement
+                      { letLexpr = "l:info.pending_output." , letValue = "l:line" }
+                  , LetStatement
+                      { letLexpr = "l:info.pending_output_length+"
+                      , letValue = "strlen(l:line)"
+                      }
+                  , GenericStatement "endif"
+                  , GenericStatement "endfor"
+                  , GenericStatement "endif"
+                  ]
+              }
+          , FunctionDeclaration
+              { functionBang = True
+              , functionName = "ferret#private#nvim#close_cb"
+              , functionArguments =
+                  ArgumentList
+                    [ Argument "job" , Argument "data" , Argument "event" ]
+              , functionAttributes = [ "abort" , "dict" ]
+              , functionBody =
+                  [ LetStatement
+                      { letLexpr = "l:info" , letValue = "s:info_from_job(a:job)" }
+                  , GenericStatement "if type(l:info) == 4"
+                  , GenericStatement "call remove(s:jobs, a:job)"
+                  , GenericStatement
+                      "call ferret#private#autocmd('FerretAsyncFinish')"
+                  , GenericStatement "if !l:info.ack"
+                  , GenericStatement "call win_gotoid(l:info.window)"
+                  , GenericStatement "endif"
+                  , GenericStatement
+                      "call ferret#private#shared#finalize_search(l:info.output, l:info.ack)"
+                  , GenericStatement "for l:error in l:info.errors"
+                  , GenericStatement "unsilent echomsg l:error"
+                  , GenericStatement "endfor"
+                  , GenericStatement "endif"
+                  ]
+              }
+          , FunctionDeclaration
+              { functionBang = True
+              , functionName = "ferret#private#nvim#pull"
+              , functionArguments = ArgumentList []
+              , functionAttributes = [ "abort" ]
+              , functionBody =
+                  [ GenericStatement "for l:job in keys(s:jobs)"
+                  , LetStatement { letLexpr = "l:info" , letValue = "s:jobs[l:job]" }
+                  , GenericStatement
+                      "call ferret#private#shared#finalize_search(l:info.output, l:info.ack)"
+                  , GenericStatement "endfor"
+                  ]
+              }
+          , FunctionDeclaration
+              { functionBang = True
+              , functionName = "ferret#private#nvim#cancel"
+              , functionArguments = ArgumentList []
+              , functionAttributes = [ "abort" ]
+              , functionBody =
+                  [ LetStatement { letLexpr = "l:canceled" , letValue = "0" }
+                  , GenericStatement "for l:job in keys(s:jobs)"
+                  , GenericStatement "call jobstop(l:job)"
+                  , GenericStatement "call remove(s:jobs, l:job)"
+                  , LetStatement { letLexpr = "l:canceled" , letValue = "1" }
+                  , GenericStatement "endfor"
+                  , GenericStatement "if l:canceled"
+                  , GenericStatement
+                      "call ferret#private#autocmd('FerretAsyncFinish')"
+                  , GenericStatement "endif"
+                  ]
+              }
+          , FunctionDeclaration
+              { functionBang = True
+              , functionName = "s:MaxResultsExceeded"
+              , functionArguments = ArgumentList [ Argument "info" ]
+              , functionAttributes = []
+              , functionBody =
+                  [ GenericStatement
+                      "call ferret#private#shared#finalize_search(a:info.output, a:info.ack)"
+                  , GenericStatement "call jobstop(a:info.job)"
+                  , GenericStatement "call remove(s:jobs, a:info.job)"
+                  , GenericStatement
+                      "call ferret#private#autocmd('FerretAsyncFinish')"
+                  , GenericStatement
+                      "call ferret#private#error( 'Maximum result count exceeded. ' . 'Either increase g:FerretMaxResults or ' . 're-run the search with :Ack!, :Lack! etc.' )"
+                  ]
+              }
+          , FunctionDeclaration
+              { functionBang = True
+              , functionName = "ferret#private#nvim#debug"
+              , functionArguments = ArgumentList []
+              , functionAttributes = [ "abort" ]
+              , functionBody = [ GenericStatement "return s:jobs" ]
+              }
+          ]
+      , Unit
+          [ FunctionDeclaration
+              { functionBang = True
+              , functionName = "s:autojump"
+              , functionArguments = ArgumentList []
+              , functionAttributes = []
+              , functionBody =
+                  [ LetStatement
+                      { letLexpr = "l:autojump"
+                      , letValue = "get(g:, 'FerretAutojump', 1)"
+                      }
+                  , GenericStatement
+                      "if l:autojump != 0 && l:autojump != 1 && l:autojump != 2"
+                  , LetStatement { letLexpr = "l:autojump" , letValue = "1" }
+                  , GenericStatement "endif"
+                  , GenericStatement "return l:autojump"
+                  ]
+              }
+          , FunctionDeclaration
+              { functionBang = True
+              , functionName = "s:set_title"
+              , functionArguments =
+                  ArgumentList [ Argument "type" , Argument "title" ]
+              , functionAttributes = []
+              , functionBody =
+                  [ GenericStatement "if has('patch-7.4.2200')"
+                  , GenericStatement "if a:type ==# 'qf'"
+                  , GenericStatement "call setqflist([], 'a', {'title' : a:title})"
+                  , GenericStatement "else"
+                  , GenericStatement
+                      "call setloclist(0, [], 'a', {'title' : a:title})"
+                  , GenericStatement "endif"
+                  , GenericStatement "elseif a:type ==# 'qf'"
+                  , LetStatement
+                      { letLexpr = "w:quickfix_title" , letValue = "a:title" }
+                  , GenericStatement "endif"
+                  ]
+              }
+          , FunctionDeclaration
+              { functionBang = True
+              , functionName = "ferret#private#shared#finalize_search"
+              , functionArguments =
+                  ArgumentList [ Argument "output" , Argument "ack" ]
+              , functionAttributes = []
+              , functionBody =
+                  [ LetStatement
+                      { letLexpr = "l:lastsearch "
+                      , letValue = "get(g:, 'ferret_lastsearch', '')"
+                      }
+                  , LetStatement
+                      { letLexpr = "l:original_errorformat" , letValue = "&errorformat" }
+                  , LetStatement
+                      { letLexpr = "l:autojump" , letValue = "s:autojump()" }
+                  , GenericStatement "if a:ack"
+                  , LetStatement
+                      { letLexpr = "l:prefix"
+                      , letValue = "'c' \" Will use cexpr, cgetexpr."
+                      }
+                  , LetStatement
+                      { letLexpr = "l:handler"
+                      , letValue = "get(g:, 'FerretQFHandler', 'botright copen')"
+                      }
+                  , LetStatement { letLexpr = "l:post" , letValue = "'qf'" }
+                  , GenericStatement "else"
+                  , LetStatement
+                      { letLexpr = "l:prefix"
+                      , letValue = "'l' \" Will use lexpr, lgetexpr."
+                      }
+                  , LetStatement
+                      { letLexpr = "l:handler"
+                      , letValue = "get(g:, 'FerretLLHandler', 'lopen')"
+                      }
+                  , LetStatement { letLexpr = "l:post" , letValue = "'location'" }
+                  , GenericStatement "endif"
+                  , GenericStatement "try"
+                  , LetStatement
+                      { letLexpr = "&errorformat" , letValue = "g:FerretFormat" }
+                  , GenericStatement
+                      "if l:autojump == 2 \" Show listing and jump to first result."
+                  , GenericStatement
+                      "call s:swallow(l:prefix . 'expr a:1', a:output)"
+                  , GenericStatement "else"
+                  , GenericStatement
+                      "call s:swallow(l:prefix . 'getexpr a:1', a:output)"
+                  , GenericStatement "endif"
+                  , GenericStatement
+                      "call s:set_title(l:post, 'Search `' . l:lastsearch . '`')"
+                  , LetStatement { letLexpr = "l:before" , letValue = "winnr()" }
+                  , LetStatement
+                      { letLexpr = "l:len" , letValue = "ferret#private#post(l:post)" }
+                  , GenericStatement "if l:len"
+                  , GenericStatement "execute l:handler"
+                  , GenericStatement
+                      "if l:autojump != 1 \" Show listing, but don't jump anywhere."
+                  , LetStatement { letLexpr = "l:after" , letValue = "winnr()" }
+                  , GenericStatement "if l:before != l:after"
+                  , GenericStatement "execute l:before . 'wincmd w'"
+                  , GenericStatement "end"
+                  , GenericStatement "endif"
+                  , GenericStatement "endif"
+                  , GenericStatement "finally"
+                  , LetStatement
+                      { letLexpr = "&errorformat" , letValue = "l:original_errorformat" }
+                  , GenericStatement "endtry"
+                  ]
+              }
+          , FunctionDeclaration
+              { functionBang = True
+              , functionName = "s:swallow"
+              , functionArguments =
+                  ArgumentList [ Argument "executable" , Argument "..." ]
+              , functionAttributes = []
+              , functionBody =
+                  [ GenericStatement "try"
+                  , GenericStatement "execute a:executable"
+                  , GenericStatement "catch"
+                  , GenericStatement "echomsg 'Caught: ' . v:exception"
+                  , GenericStatement "endtry"
+                  ]
+              }
+          ]
+      , Unit
+          [ FunctionDeclaration
+              { functionBang = True
+              , functionName = "ferret#private#vanilla#search"
+              , functionArguments =
+                  ArgumentList [ Argument "command" , Argument "ack" ]
+              , functionAttributes = [ "abort" ]
+              , functionBody =
+                  [ LetStatement
+                      { letLexpr = "l:executable"
+                      , letValue = "ferret#private#executable()"
+                      }
+                  , LetStatement
+                      { letLexpr = "l:output"
+                      , letValue = "system(l:executable . ' ' . a:command)"
+                      }
+                  , GenericStatement
+                      "call ferret#private#shared#finalize_search(l:output, a:ack)"
+                  ]
+              }
+          ]
+      , Unit
+          [ LetStatement
+              { letLexpr = "s:options"
+              , letValue = "get(g:, 'FerretQFOptions', 1)"
+              }
+          , GenericStatement "if s:options"
+          , GenericStatement "setlocal nolist"
+          , GenericStatement "if exists('+relativenumber')"
+          , GenericStatement "setlocal norelativenumber"
+          , GenericStatement "endif"
+          , GenericStatement "setlocal nowrap"
+          , GenericStatement "setlocal number"
+          , LetStatement
+              { letLexpr = "s:original_scrolloff" , letValue = "&scrolloff" }
+          , GenericStatement "set scrolloff=0"
+          , GenericStatement "if has('autocmd')"
+          , GenericStatement "augroup FerretQF"
+          , GenericStatement "autocmd!"
+          , GenericStatement
+              "autocmd BufLeave <buffer> execute 'set scrolloff=' . s:original_scrolloff"
+          , GenericStatement
+              "autocmd BufEnter <buffer> set scrolloff=0 | setlocal nocursorline"
+          , GenericStatement "augroup END"
+          , GenericStatement "endif"
+          , GenericStatement "endif"
+          , LetStatement
+              { letLexpr = "s:map" , letValue = "get(g:, 'FerretQFMap', 1)" }
+          , GenericStatement "if s:map"
+          , GenericStatement
+              "nnoremap <buffer> <silent> d :set operatorfunc=ferret#private#qf_delete_motion<CR>g@"
+          , GenericStatement
+              "nnoremap <buffer> <silent> dd :call ferret#private#qf_delete()<CR>"
           , GenericStatement
               "vnoremap <buffer> <silent> d :call ferret#private#qf_delete()<CR>"
           , GenericStatement "endif"
@@ -1962,13 +2347,13 @@ Project
           , GenericStatement "call ferret#private#init()"
           , GenericStatement "endif"
           , GenericStatement
-              "command! -bang -nargs=+ -complete=customlist,ferret#private#ackcomplete Ack call ferret#private#ack(<bang>0, <f-args>)"
+              "command! -bang -nargs=1 -complete=customlist,ferret#private#ackcomplete Ack call ferret#private#ack(<bang>0, <q-args>)"
           , GenericStatement
-              "command! -bang -nargs=+ -complete=customlist,ferret#private#lackcomplete Lack call ferret#private#lack(<bang>0, <f-args>)"
+              "command! -bang -nargs=1 -complete=customlist,ferret#private#lackcomplete Lack call ferret#private#lack(<bang>0, <q-args>)"
           , GenericStatement
-              "command! -bang -nargs=+ -complete=customlist,ferret#private#backcomplete Back call ferret#private#back(<bang>0, <f-args>)"
+              "command! -bang -nargs=1 -complete=customlist,ferret#private#backcomplete Back call ferret#private#back(<bang>0, <q-args>)"
           , GenericStatement
-              "command! -bang -nargs=+ -complete=customlist,ferret#private#blackcomplete Black call ferret#private#black(<bang>0, <f-args>)"
+              "command! -bang -nargs=1 -complete=customlist,ferret#private#blackcomplete Black call ferret#private#black(<bang>0, <q-args>)"
           , GenericStatement
               "command! -nargs=1 Acks call ferret#private#acks(<q-args>)"
           , GenericStatement
@@ -2184,92 +2569,6 @@ Project
       , Whitespace
       , Plaintext "UI."
       ]
-  , Paragraph
-      [ Plaintext "On"
-      , Whitespace
-      , Plaintext "older"
-      , Whitespace
-      , Plaintext "Vim"
-      , Whitespace
-      , Plaintext "versions"
-      , Whitespace
-      , Plaintext "(prior"
-      , Whitespace
-      , Plaintext "to"
-      , Whitespace
-      , Plaintext "version"
-      , Whitespace
-      , Plaintext "8),"
-      , Whitespace
-      , Plaintext "if"
-      , Whitespace
-      , Plaintext "dispatch.vim"
-      , Whitespace
-      , Plaintext "is"
-      , Whitespace
-      , Plaintext "installed"
-      , Whitespace
-      , Plaintext "the"
-      , Whitespace
-      , Plaintext "search"
-      , Whitespace
-      , Plaintext "process"
-      , Whitespace
-      , Plaintext "will"
-      , Whitespace
-      , Plaintext "run"
-      , Whitespace
-      , Plaintext "asynchronously"
-      , Whitespace
-      , Plaintext "via"
-      , Whitespace
-      , Plaintext "the"
-      , Whitespace
-      , Link ":Make"
-      , Whitespace
-      , Plaintext "command,"
-      , Whitespace
-      , Plaintext "otherwise"
-      , Whitespace
-      , Plaintext "it"
-      , Whitespace
-      , Plaintext "will"
-      , Whitespace
-      , Plaintext "be"
-      , Whitespace
-      , Plaintext "run"
-      , Whitespace
-      , Plaintext "synchronously"
-      , Whitespace
-      , Plaintext "via"
-      , Whitespace
-      , Link ":cexpr"
-      , Plaintext "."
-      , Whitespace
-      , Plaintext "The"
-      , Whitespace
-      , Link "g:FerretDispatch"
-      , Whitespace
-      , Plaintext "option"
-      , Whitespace
-      , Plaintext "can"
-      , Whitespace
-      , Plaintext "be"
-      , Whitespace
-      , Plaintext "used"
-      , Whitespace
-      , Plaintext "to"
-      , Whitespace
-      , Plaintext "prevent"
-      , Whitespace
-      , Plaintext "the"
-      , Whitespace
-      , Plaintext "use"
-      , Whitespace
-      , Plaintext "of"
-      , Whitespace
-      , Plaintext "dispatch.vim."
-      ]
   , Paragraph
       [ Plaintext "Asynchronous"
       , Whitespace
@@ -2448,8 +2747,6 @@ Project
       , Plaintext "\"bar\""
       , Whitespace
       , Plaintext "subdirectories:"
-      , Whitespace
-      , Plaintext ">"
       ]
   , Fenced [ ":Ack -w something foo bar" ]
   , Paragraph
@@ -2630,21 +2927,6 @@ Project
       , Plaintext "via"
       , Whitespace
       , Link ":cexpr"
-      , Plaintext ","
-      , Whitespace
-      , Plaintext "because"
-      , Whitespace
-      , Plaintext "dispatch.vim"
-      , Whitespace
-      , Plaintext "doesn't"
-      , Whitespace
-      , Plaintext "currently"
-      , Whitespace
-      , Plaintext "support"
-      , Whitespace
-      , Plaintext "the"
-      , Whitespace
-      , Link "location-list"
       , Plaintext "."
       ]
   , CommandAnnotation "Lack!" (Just "{pattern} {options}")
@@ -3064,45 +3346,125 @@ Project
       , Whitespace
       , Plaintext "(mnemonic:"
       , Whitespace
-      , Plaintext "\"Ack"
+      , Plaintext "\"Ack"
+      , Whitespace
+      , Plaintext "substitute\")"
+      , Whitespace
+      , Plaintext "to"
+      , Whitespace
+      , Plaintext "perform"
+      , Whitespace
+      , Plaintext "replacements."
+      , Whitespace
+      , Plaintext "For"
+      , Whitespace
+      , Plaintext "example,"
+      , Whitespace
+      , Plaintext "to"
+      , Whitespace
+      , Plaintext "replace"
+      , Whitespace
+      , Plaintext "\"foo\""
+      , Whitespace
+      , Plaintext "with"
+      , Whitespace
+      , Plaintext "\"bar\""
+      , Whitespace
+      , Plaintext "across"
+      , Whitespace
+      , Plaintext "all"
+      , Whitespace
+      , Plaintext "files"
+      , Whitespace
+      , Plaintext "in"
+      , Whitespace
+      , Plaintext "the"
+      , Whitespace
+      , Plaintext "current"
+      , Whitespace
+      , Plaintext "directory:"
+      ]
+  , Fenced [ ":Ack foo" , ":Acks /foo/bar/" ]
+  , Paragraph
+      [ Plaintext "The"
+      , Whitespace
+      , Plaintext "pattern"
+      , Whitespace
+      , Plaintext "and"
+      , Whitespace
+      , Plaintext "replacement"
+      , Whitespace
+      , Plaintext "are"
+      , Whitespace
+      , Plaintext "passed"
+      , Whitespace
+      , Plaintext "through"
+      , Whitespace
+      , Plaintext "literally"
+      , Whitespace
+      , Plaintext "to"
+      , Whitespace
+      , Plaintext "Vim's"
+      , Whitespace
+      , Link ":substitute"
+      , Whitespace
+      , Plaintext "command,"
+      , Whitespace
+      , Plaintext "preserving"
+      , Whitespace
+      , Plaintext "all"
+      , Whitespace
+      , Plaintext "characters"
+      , Whitespace
+      , Plaintext "and"
+      , Whitespace
+      , Plaintext "escapes,"
+      , Whitespace
+      , Plaintext "including"
       , Whitespace
-      , Plaintext "substitute\")"
+      , Plaintext "references"
       , Whitespace
       , Plaintext "to"
       , Whitespace
-      , Plaintext "perform"
+      , Plaintext "matches"
       , Whitespace
-      , Plaintext "replacements."
+      , Plaintext "in"
+      , Whitespace
+      , Plaintext "the"
+      , Whitespace
+      , Plaintext "pattern."
       , Whitespace
       , Plaintext "For"
       , Whitespace
       , Plaintext "example,"
       , Whitespace
-      , Plaintext "to"
+      , Plaintext "the"
       , Whitespace
-      , Plaintext "replace"
+      , Plaintext "following"
       , Whitespace
-      , Plaintext "\"foo\""
+      , Plaintext "could"
       , Whitespace
-      , Plaintext "with"
+      , Plaintext "be"
       , Whitespace
-      , Plaintext "\"bar\""
+      , Plaintext "used"
       , Whitespace
-      , Plaintext "across"
+      , Plaintext "to"
       , Whitespace
-      , Plaintext "all"
+      , Plaintext "swap"
       , Whitespace
-      , Plaintext "files"
+      , Plaintext "the"
       , Whitespace
-      , Plaintext "in"
+      , Plaintext "order"
       , Whitespace
-      , Plaintext "the"
+      , Plaintext "of"
       , Whitespace
-      , Plaintext "current"
+      , Plaintext "\"foo123\""
       , Whitespace
-      , Plaintext "directory:"
+      , Plaintext "and"
+      , Whitespace
+      , Plaintext "\"bar\":"
       ]
-  , Fenced [ ":Ack foo" , ":Acks /foo/bar/" ]
+  , Fenced [ ":Acks /\\v(foo\\d+)(bar)/\\2\\1/" ]
   , CommandAnnotation "Qargs" Nothing
   , Paragraph
       [ Plaintext "This"
@@ -3270,7 +3632,13 @@ Project
           , Whitespace
           , Plaintext "mapping"
           , Whitespace
-          , Plaintext "for"
+          , Plaintext "with"
+          , Whitespace
+          , Plaintext "the"
+          , Whitespace
+          , Plaintext "same"
+          , Whitespace
+          , Link "{lhs}"
           , Whitespace
           , Plaintext "already"
           , Whitespace
@@ -3327,7 +3695,7 @@ Project
           , Whitespace
           , Plaintext "to"
           , Whitespace
-          , Plaintext "1"
+          , Plaintext "0"
           , Whitespace
           , Plaintext "in"
           , Whitespace
@@ -3705,7 +4073,7 @@ Project
       , "nmap <leader>u <Plug>(FerretAcks)"
       ]
   , OptionsAnnotation
-  , OptionAnnotation "g:FerretDispatch" "boolean" (Just "1")
+  , OptionAnnotation "g:FerretNvim" "boolean" (Just "1")
   , Paragraph
       [ Plaintext "Controls"
       , Whitespace
@@ -3715,34 +4083,29 @@ Project
       , Whitespace
       , Plaintext "use"
       , Whitespace
-      , Plaintext "vim-dispatch"
+      , Plaintext "Neovim's"
       , Whitespace
-      , Plaintext "(and"
+      , Link "job-control"
       , Whitespace
-      , Plaintext "specifically,"
+      , Plaintext "features,"
       , Whitespace
-      , Link ":Make"
-      , Plaintext ")"
+      , Plaintext "when"
+      , Whitespace
+      , Plaintext "available,"
       , Whitespace
       , Plaintext "to"
       , Whitespace
       , Plaintext "run"
       , Whitespace
-      , Link ":Ack"
-      , Whitespace
       , Plaintext "searches"
       , Whitespace
-      , Plaintext "asynchronously,"
-      , Whitespace
-      , Plaintext "when"
-      , Whitespace
-      , Plaintext "available."
+      , Plaintext "asynchronously."
       , Whitespace
       , Plaintext "To"
       , Whitespace
       , Plaintext "prevent"
       , Whitespace
-      , Plaintext "vim-dispatch"
+      , Plaintext "this"
       , Whitespace
       , Plaintext "from"
       , Whitespace
@@ -3754,75 +4117,87 @@ Project
       , Whitespace
       , Plaintext "to"
       , Whitespace
-      , Plaintext "0:"
-      ]
-  , Fenced [ "let g:FerretDispatch=0" ]
-  , Paragraph
-      [ Plaintext "Note"
+      , Plaintext "0,"
       , Whitespace
-      , Plaintext "that"
+      , Plaintext "in"
       , Whitespace
-      , Plaintext "on"
+      , Plaintext "which"
       , Whitespace
-      , Plaintext "sufficiently"
+      , Plaintext "case"
       , Whitespace
-      , Plaintext "recent"
+      , Plaintext "Ferret"
       , Whitespace
-      , Plaintext "versions"
+      , Plaintext "will"
       , Whitespace
-      , Plaintext "of"
+      , Plaintext "fall"
       , Whitespace
-      , Plaintext "Vim"
+      , Plaintext "back"
       , Whitespace
-      , Plaintext "with"
+      , Plaintext "to"
       , Whitespace
-      , Link "+job"
+      , Plaintext "the"
       , Whitespace
-      , Plaintext "support,"
+      , Plaintext "next"
       , Whitespace
-      , Plaintext "Ferret"
+      , Plaintext "method"
       , Whitespace
-      , Plaintext "will"
+      , Plaintext "in"
       , Whitespace
-      , Plaintext "first"
+      , Plaintext "the"
       , Whitespace
-      , Plaintext "try"
+      , Plaintext "list"
       , Whitespace
-      , Plaintext "to"
+      , Plaintext "(Vim's"
       , Whitespace
-      , Plaintext "use"
+      , Plaintext "built-in"
       , Whitespace
-      , Link "+job"
-      , Plaintext ","
+      , Plaintext "async"
       , Whitespace
-      , Plaintext "falling"
+      , Plaintext "primitives"
       , Whitespace
-      , Plaintext "back"
+      , Plaintext "--"
       , Whitespace
-      , Plaintext "to"
+      , Plaintext "see"
       , Whitespace
-      , Plaintext "vim-dispatch"
+      , Link "g:FerretJob"
       , Whitespace
-      , Plaintext "and"
+      , Plaintext "--"
       , Whitespace
-      , Plaintext "consulting"
+      , Plaintext "which"
       , Whitespace
-      , Link "g:FerretDispatch"
+      , Plaintext "are"
       , Whitespace
-      , Plaintext "only"
+      , Plaintext "typically"
       , Whitespace
-      , Plaintext "if"
+      , Plaintext "not"
       , Whitespace
-      , Link "g:FerretJob"
+      , Plaintext "available"
       , Whitespace
-      , Plaintext "is"
+      , Plaintext "in"
       , Whitespace
-      , Plaintext "set"
+      , Plaintext "Neovim,"
+      , Whitespace
+      , Plaintext "so"
+      , Whitespace
+      , Plaintext "will"
+      , Whitespace
+      , Plaintext "then"
+      , Whitespace
+      , Plaintext "fall"
+      , Whitespace
+      , Plaintext "back"
       , Whitespace
       , Plaintext "to"
       , Whitespace
-      , Plaintext "0."
+      , Plaintext "the"
+      , Whitespace
+      , Plaintext "next"
+      , Whitespace
+      , Plaintext "available"
+      , Whitespace
+      , Plaintext "method)."
       ]
+  , Fenced [ "let g:FerretNvim=0" ]
   , OptionAnnotation "g:FerretJob" "boolean" (Just "1")
   , Paragraph
       [ Plaintext "Controls"
@@ -3885,14 +4260,13 @@ Project
       , Whitespace
       , Plaintext "to"
       , Whitespace
-      , Plaintext "vim-dispatch"
+      , Plaintext "the"
       , Whitespace
-      , Plaintext "(see"
+      , Plaintext "next"
       , Whitespace
-      , Plaintext "also:"
+      , Plaintext "available"
       , Whitespace
-      , Link "g:FerretDispatch"
-      , Plaintext "):"
+      , Plaintext "method."
       ]
   , Fenced [ "let g:FerretJob=0" ]
   , OptionAnnotation "g:FerretHlsearch" "boolean" Nothing
@@ -4121,6 +4495,97 @@ Project
   , Paragraph [ Plaintext "Example:" ]
   , Fenced
       [ "\" Prefer `ag` over `rg`." , "let g:FerretExecutable='ag,rg'" ]
+  , OptionAnnotation "g:FerretExecutableArguments" "dict" (Just "{}")
+  , Paragraph
+      [ Plaintext "Allows"
+      , Whitespace
+      , Plaintext "you"
+      , Whitespace
+      , Plaintext "to"
+      , Whitespace
+      , Plaintext "override"
+      , Whitespace
+      , Plaintext "the"
+      , Whitespace
+      , Plaintext "default"
+      , Whitespace
+      , Plaintext "arguments"
+      , Whitespace
+      , Plaintext "that"
+      , Whitespace
+      , Plaintext "get"
+      , Whitespace
+      , Plaintext "passed"
+      , Whitespace
+      , Plaintext "to"
+      , Whitespace
+      , Plaintext "the"
+      , Whitespace
+      , Plaintext "underlying"
+      , Whitespace
+      , Plaintext "search"
+      , Whitespace
+      , Plaintext "executables."
+      , Whitespace
+      , Plaintext "For"
+      , Whitespace
+      , Plaintext "example,"
+      , Whitespace
+      , Plaintext "to"
+      , Whitespace
+      , Plaintext "add"
+      , Whitespace
+      , Code "-s"
+      , Whitespace
+      , Plaintext "to"
+      , Whitespace
+      , Plaintext "the"
+      , Whitespace
+      , Plaintext "default"
+      , Whitespace
+      , Plaintext "arguments"
+      , Whitespace
+      , Plaintext "passed"
+      , Whitespace
+      , Plaintext "to"
+      , Whitespace
+      , Code "ack"
+      , Whitespace
+      , Plaintext "("
+      , Code "--column --with-filename"
+      , Plaintext "):"
+      ]
+  , Fenced
+      [ "let g:FerretExecutableArguments = {"
+      , "  \\   'ack': '--column --with-filename -s'"
+      , "  \\ }"
+      ]
+  , Paragraph
+      [ Plaintext "To"
+      , Whitespace
+      , Plaintext "find"
+      , Whitespace
+      , Plaintext "out"
+      , Whitespace
+      , Plaintext "the"
+      , Whitespace
+      , Plaintext "default"
+      , Whitespace
+      , Plaintext "arguments"
+      , Whitespace
+      , Plaintext "for"
+      , Whitespace
+      , Plaintext "a"
+      , Whitespace
+      , Plaintext "given"
+      , Whitespace
+      , Plaintext "executable,"
+      , Whitespace
+      , Plaintext "see"
+      , Whitespace
+      , Link "ferret#get_default_arguments()"
+      , Plaintext "."
+      ]
   , OptionAnnotation "g:FerretMaxResults" "number" (Just "100000")
   , Paragraph
       [ Plaintext "Controls"
@@ -4357,6 +4822,128 @@ Project
       , Whitespace
       , Plaintext "limit."
       ]
+  , OptionAnnotation "g:FerretAutojump" "number" (Just "1")
+  , Paragraph
+      [ Plaintext "Controls"
+      , Whitespace
+      , Plaintext "whether"
+      , Whitespace
+      , Plaintext "Ferret"
+      , Whitespace
+      , Plaintext "will"
+      , Whitespace
+      , Plaintext "automatically"
+      , Whitespace
+      , Plaintext "jump"
+      , Whitespace
+      , Plaintext "to"
+      , Whitespace
+      , Plaintext "the"
+      , Whitespace
+      , Plaintext "first"
+      , Whitespace
+      , Plaintext "found"
+      , Whitespace
+      , Plaintext "match."
+      ]
+  , List
+      [ ListItem
+          [ Plaintext "Set"
+          , Whitespace
+          , Plaintext "to"
+          , Whitespace
+          , Plaintext "0,"
+          , Whitespace
+          , Plaintext "Ferret"
+          , Whitespace
+          , Plaintext "will"
+          , Whitespace
+          , Plaintext "show"
+          , Whitespace
+          , Plaintext "the"
+          , Whitespace
+          , Plaintext "search"
+          , Whitespace
+          , Plaintext "results"
+          , Whitespace
+          , Plaintext "but"
+          , Whitespace
+          , Plaintext "perform"
+          , Whitespace
+          , Plaintext "no"
+          , Whitespace
+          , Plaintext "jump."
+          ]
+      , ListItem
+          [ Plaintext "Set"
+          , Whitespace
+          , Plaintext "to"
+          , Whitespace
+          , Plaintext "1"
+          , Whitespace
+          , Plaintext "(the"
+          , Whitespace
+          , Plaintext "default),"
+          , Whitespace
+          , Plaintext "Ferret"
+          , Whitespace
+          , Plaintext "will"
+          , Whitespace
+          , Plaintext "show"
+          , Whitespace
+          , Plaintext "the"
+          , Whitespace
+          , Plaintext "search"
+          , Whitespace
+          , Plaintext "results"
+          , Whitespace
+          , Plaintext "and"
+          , Whitespace
+          , Plaintext "focus"
+          , Whitespace
+          , Plaintext "the"
+          , Whitespace
+          , Plaintext "result"
+          , Whitespace
+          , Plaintext "listing."
+          ]
+      , ListItem
+          [ Plaintext "Set"
+          , Whitespace
+          , Plaintext "to"
+          , Whitespace
+          , Plaintext "2,"
+          , Whitespace
+          , Plaintext "Ferret"
+          , Whitespace
+          , Plaintext "will"
+          , Whitespace
+          , Plaintext "show"
+          , Whitespace
+          , Plaintext "the"
+          , Whitespace
+          , Plaintext "search"
+          , Whitespace
+          , Plaintext "results"
+          , Whitespace
+          , Plaintext "and"
+          , Whitespace
+          , Plaintext "jump"
+          , Whitespace
+          , Plaintext "to"
+          , Whitespace
+          , Plaintext "the"
+          , Whitespace
+          , Plaintext "first"
+          , Whitespace
+          , Plaintext "found"
+          , Whitespace
+          , Plaintext "match."
+          ]
+      ]
+  , Paragraph
+      [ Plaintext "Example" , Whitespace , Plaintext "override:" ]
+  , Fenced [ "let g:FerretAutojump=2" ]
   , OptionAnnotation "g:FerretQFOptions" "boolean" (Just "1")
   , Paragraph
       [ Plaintext "Controls"
@@ -4891,6 +5478,90 @@ Project
       , Whitespace
       , Plaintext "Ferret."
       ]
+  , FunctionsAnnotation
+  , FunctionAnnotation "ferret#get_default_arguments"
+  , Paragraph
+      [ Plaintext "Call"
+      , Whitespace
+      , Plaintext "this"
+      , Whitespace
+      , Plaintext "with"
+      , Whitespace
+      , Plaintext "an"
+      , Whitespace
+      , Plaintext "executable"
+      , Whitespace
+      , Plaintext "name"
+      , Whitespace
+      , Plaintext "to"
+      , Whitespace
+      , Plaintext "find"
+      , Whitespace
+      , Plaintext "out"
+      , Whitespace
+      , Plaintext "the"
+      , Whitespace
+      , Plaintext "default"
+      , Whitespace
+      , Plaintext "arguments"
+      , Whitespace
+      , Plaintext "that"
+      , Whitespace
+      , Plaintext "will"
+      , Whitespace
+      , Plaintext "be"
+      , Whitespace
+      , Plaintext "passed"
+      , Whitespace
+      , Plaintext "when"
+      , Whitespace
+      , Plaintext "invoking"
+      , Whitespace
+      , Plaintext "that"
+      , Whitespace
+      , Plaintext "executable."
+      , Whitespace
+      , Plaintext "For"
+      , Whitespace
+      , Plaintext "example:"
+      ]
+  , Fenced [ "echo ferret#get_default_arguments('rg')" ]
+  , Paragraph
+      [ Plaintext "This"
+      , Whitespace
+      , Plaintext "may"
+      , Whitespace
+      , Plaintext "be"
+      , Whitespace
+      , Plaintext "useful"
+      , Whitespace
+      , Plaintext "if"
+      , Whitespace
+      , Plaintext "you"
+      , Whitespace
+      , Plaintext "wish"
+      , Whitespace
+      , Plaintext "to"
+      , Whitespace
+      , Plaintext "extend"
+      , Whitespace
+      , Plaintext "or"
+      , Whitespace
+      , Plaintext "otherwise"
+      , Whitespace
+      , Plaintext "modify"
+      , Whitespace
+      , Plaintext "the"
+      , Whitespace
+      , Plaintext "arguments"
+      , Whitespace
+      , Plaintext "by"
+      , Whitespace
+      , Plaintext "setting"
+      , Whitespace
+      , Link "g:FerretExecutableArguments"
+      , Plaintext "."
+      ]
   , FooterAnnotation
   , HeadingAnnotation "Custom autocommands"
   , LinkTargets [ "FerretWillWrite" , "FerretDidWrite" ]
@@ -5086,7 +5757,7 @@ Project
       ]
   , IndentAnnotation
   , LinkTargets [ "ferret-nolist" ]
-  , Paragraph [ Plaintext "'nolist'" ]
+  , SubheadingAnnotation "'nolist'"
   , Paragraph
       [ Plaintext "Turned"
       , Whitespace
@@ -5151,7 +5822,7 @@ Project
       , Plaintext "results."
       ]
   , LinkTargets [ "ferret-norelativenumber" ]
-  , Paragraph [ Plaintext "'norelativenumber'" ]
+  , SubheadingAnnotation "'norelativenumber'"
   , Paragraph
       [ Plaintext "Turned"
       , Whitespace
@@ -5260,7 +5931,7 @@ Project
       , Plaintext "respectively)."
       ]
   , LinkTargets [ "ferret-nowrap" ]
-  , Paragraph [ Plaintext "'nowrap'" ]
+  , SubheadingAnnotation "'nowrap'"
   , Paragraph
       [ Plaintext "Turned"
       , Whitespace
@@ -5339,7 +6010,7 @@ Project
       , Plaintext "wrapping."
       ]
   , LinkTargets [ "ferret-number" ]
-  , Paragraph [ Plaintext "'number'" ]
+  , SubheadingAnnotation "'number'"
   , Paragraph
       [ Plaintext "Turned"
       , Whitespace
@@ -5366,7 +6037,7 @@ Project
       , Plaintext "results."
       ]
   , LinkTargets [ "ferret-scrolloff" ]
-  , Paragraph [ Plaintext "'scrolloff'" ]
+  , SubheadingAnnotation "'scrolloff'"
   , Paragraph
       [ Plaintext "Set"
       , Whitespace
@@ -5441,7 +6112,7 @@ Project
       , Plaintext "edge."
       ]
   , LinkTargets [ "ferret-nocursorline" ]
-  , Paragraph [ Plaintext "'nocursorline'" ]
+  , SubheadingAnnotation "'nocursorline'"
   , Paragraph
       [ Plaintext "Turned"
       , Whitespace
@@ -5828,7 +6499,7 @@ Project
       , Whitespace
       , Plaintext "2009"
       , Whitespace
-      , Plaintext "(https://wt.pe/h)."
+      , Plaintext "(https://rfr.to/h)."
       ]
   , Paragraph
       [ Plaintext "So,"
@@ -6532,33 +7203,6 @@ Project
       , Whitespace
       , Plaintext "<greg@hurrell.net>."
       ]
-  , Paragraph
-      [ Plaintext "The"
-      , Whitespace
-      , Plaintext "idea"
-      , Whitespace
-      , Plaintext "for"
-      , Whitespace
-      , Plaintext "vim-dispatch"
-      , Whitespace
-      , Plaintext "integration"
-      , Whitespace
-      , Plaintext "was"
-      , Whitespace
-      , Plaintext "taken"
-      , Whitespace
-      , Plaintext "from"
-      , Whitespace
-      , Plaintext "Miles"
-      , Whitespace
-      , Plaintext "Sterrett's"
-      , Whitespace
-      , Plaintext "ack.vim"
-      , Whitespace
-      , Plaintext "plug-in"
-      , Whitespace
-      , Plaintext "(https://github.com/mileszs/ack.vim)."
-      ]
   , Paragraph
       [ Plaintext "Other"
       , Whitespace
@@ -6585,6 +7229,7 @@ Project
       , ListItem
           [ Plaintext "Filip" , Whitespace , Plaintext "Szyma\324ski" ]
       , ListItem [ Plaintext "Joe" , Whitespace , Plaintext "Lencioni" ]
+      , ListItem [ Plaintext "Jon" , Whitespace , Plaintext "Parise" ]
       , ListItem
           [ Plaintext "Nelo-Thara" , Whitespace , Plaintext "Wallus" ]
       , ListItem [ Plaintext "Tom" , Whitespace , Plaintext "Dooner" ]
@@ -6592,6 +7237,179 @@ Project
       ]
   , HeadingAnnotation "History"
   , SubheadingAnnotation "master (not yet released)"
+  , List
+      [ ListItem
+          [ Plaintext "Try"
+          , Whitespace
+          , Plaintext "to"
+          , Whitespace
+          , Plaintext "avoid"
+          , Whitespace
+          , Plaintext "\"press"
+          , Whitespace
+          , Plaintext "ENTER"
+          , Whitespace
+          , Plaintext "to"
+          , Whitespace
+          , Plaintext "continue\""
+          , Whitespace
+          , Plaintext "prompts."
+          ]
+      , ListItem
+          [ Plaintext "Put"
+          , Whitespace
+          , Plaintext "search"
+          , Whitespace
+          , Plaintext "term"
+          , Whitespace
+          , Plaintext "in"
+          , Whitespace
+          , Link "w:quickfix_title"
+          , Whitespace
+          , Plaintext "for"
+          , Whitespace
+          , Plaintext "use"
+          , Whitespace
+          , Plaintext "in"
+          , Whitespace
+          , Plaintext "statuslines"
+          , Whitespace
+          , Plaintext "(https://github.com/wincent/ferret/pull/57)."
+          ]
+      , ListItem
+          [ Plaintext "Add"
+          , Whitespace
+          , Link "g:FerretExecutableArguments"
+          , Whitespace
+          , Plaintext "and"
+          , Whitespace
+          , Link "ferret#get_default_arguments()"
+          , Whitespace
+          , Plaintext "(https://github.com/wincent/ferret/pull/46)."
+          ]
+      ]
+  , SubheadingAnnotation "3.0.3 (23 March 2018)"
+  , List
+      [ ListItem
+          [ Plaintext "Fix"
+          , Whitespace
+          , Plaintext "for"
+          , Whitespace
+          , Link ":Lack"
+          , Whitespace
+          , Plaintext "results"
+          , Whitespace
+          , Plaintext "opening"
+          , Whitespace
+          , Plaintext "in"
+          , Whitespace
+          , Plaintext "quickfix"
+          , Whitespace
+          , Plaintext "listing"
+          , Whitespace
+          , Plaintext "in"
+          , Whitespace
+          , Plaintext "Neovim"
+          , Whitespace
+          , Plaintext "(https://github.com/wincent/ferret/issues/47)."
+          ]
+      ]
+  , SubheadingAnnotation "3.0.2 (25 October 2017)"
+  , List
+      [ ListItem
+          [ Plaintext "Fix"
+          , Whitespace
+          , Plaintext "broken"
+          , Whitespace
+          , Link ":Back"
+          , Whitespace
+          , Plaintext "and"
+          , Whitespace
+          , Link ":Black"
+          , Whitespace
+          , Plaintext "commands"
+          , Whitespace
+          , Plaintext "(https://github.com/wincent/ferret/issues/48)."
+          ]
+      ]
+  , SubheadingAnnotation "3.0.1 (24 August 2017)"
+  , List
+      [ ListItem
+          [ Plaintext "Fix"
+          , Whitespace
+          , Plaintext "failure"
+          , Whitespace
+          , Plaintext "to"
+          , Whitespace
+          , Plaintext "handle"
+          , Whitespace
+          , Plaintext "search"
+          , Whitespace
+          , Plaintext "patterns"
+          , Whitespace
+          , Plaintext "containing"
+          , Whitespace
+          , Plaintext "multiple"
+          , Whitespace
+          , Plaintext "escaped"
+          , Whitespace
+          , Plaintext "spaces"
+          , Whitespace
+          , Plaintext "(https://github.com/wincent/ferret/issues/49)."
+          ]
+      ]
+  , SubheadingAnnotation "3.0 (13 June 2017)"
+  , List
+      [ ListItem
+          [ Plaintext "Improve"
+          , Whitespace
+          , Plaintext "handling"
+          , Whitespace
+          , Plaintext "of"
+          , Whitespace
+          , Plaintext "backslash"
+          , Whitespace
+          , Plaintext "escapes"
+          , Whitespace
+          , Plaintext "(https://github.com/wincent/ferret/issues/41)."
+          ]
+      , ListItem
+          [ Plaintext "Add"
+          , Whitespace
+          , Link "g:FerretAutojump"
+          , Plaintext "."
+          ]
+      , ListItem
+          [ Plaintext "Drop"
+          , Whitespace
+          , Plaintext "support"
+          , Whitespace
+          , Plaintext "for"
+          , Whitespace
+          , Plaintext "vim-dispatch."
+          ]
+      ]
+  , SubheadingAnnotation "2.0 (6 June 2017)"
+  , List
+      [ ListItem
+          [ Plaintext "Add"
+          , Whitespace
+          , Plaintext "support"
+          , Whitespace
+          , Plaintext "for"
+          , Whitespace
+          , Plaintext "Neovim,"
+          , Whitespace
+          , Plaintext "along"
+          , Whitespace
+          , Plaintext "with"
+          , Whitespace
+          , Link "g:FerretNvim"
+          , Whitespace
+          , Plaintext "setting."
+          ]
+      ]
+  , SubheadingAnnotation "1.5 \"Cinco de Cuatro\" (4 May 2017)"
   , List
       [ ListItem
           [ Plaintext "Improvements"
index 1c217864cc779d33e757e1681685e2f8d93e9d3f..ccb1b4417735960ad92b64eec5d6de863412f02f 100644 (file)
@@ -12,7 +12,7 @@ Ferret improves Vim's multi-file search in four ways:
 
 ### 1. Powerful multi-file search<a name="ferret-1-powerful-multi-file-search" href="#user-content-ferret-1-powerful-multi-file-search"></a>
 
-Ferret provides an <strong>[`:Ack`](#user-content-ack)</strong> command for searching across multiple files using 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).
+Ferret provides an <strong>[`:Ack`](#user-content-ack)</strong> command for searching across multiple files using ripgrep (https://github.com/BurntSushi/ripgrep), 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 modern versions of Vim (version 8 or higher, or Neovim), searches are performed asynchronously (without blocking the UI).
 
 Shortcut mappings are provided to start an <strong>[`:Ack`](#user-content-ack)</strong> search (&lt;leader&gt;a) or to search for the word currently under the cursor (&lt;leader&gt;s).
 
@@ -20,8 +20,6 @@ Results are normally displayed in the <strong>`quickfix`</strong> window, but Fe
 
 <strong>[`:Back`](#user-content-back)</strong> and <strong>[`:Black`](#user-content-black)</strong> are analogous to <strong>[`:Ack`](#user-content-ack)</strong> and <strong>[`:Lack`](#user-content-lack)</strong>, 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 on older versions of Vim (prior to version 8), despite the fact that Vim itself is single-threaded.
-
 
 ### 2. Streamlined multi-file replace<a name="ferret-2-streamlined-multi-file-replace" href="#user-content-ferret-2-streamlined-multi-file-replace"></a>
 
@@ -37,7 +35,7 @@ Additionally, Vim's <strong>`:cn`</strong>, <strong>`:cp`</strong>, <strong>`:cn
 
 ### 4. Easy operations on files in the quickfix listing<a name="ferret-4-easy-operations-on-files-in-the-quickfix-listing" href="#user-content-ferret-4-easy-operations-on-files-in-the-quickfix-listing"></a>
 
-Finally, Ferret provides a <strong>[`:Qargs`](#user-content-qargs)</strong> command that puts the files currently in the <strong>`quickfix`</strong> listing into the <strong>`:args`</strong> list, where they can be operated on in bulk via the <strong>`:argdo`</strong> command. This is what's used under the covers by <strong>[`:Acks`](#user-content-acks)</strong> to do its work.
+Finally, Ferret provides a <strong>[`:Qargs`](#user-content-qargs)</strong> command that puts the files currently in the <strong>`quickfix`</strong> listing into the <strong>`:args`</strong> list, where they can be operated on in bulk via the <strong>`:argdo`</strong> command. This is what's used under the covers on older versions of Vim by <strong>[`:Acks`](#user-content-acks)</strong> to do its work (on newer versions the built-in <strong>`:cfdo`</strong> is used instead).
 
 
 ## Installation<a name="ferret-installation" href="#user-content-ferret-installation"></a>
@@ -76,8 +74,6 @@ Searches for {pattern} in all the files under the current directory (see <strong
 
 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 <strong>`:Make`</strong> command, otherwise it will be run synchronously via <strong>`:cexpr`</strong>. The <strong>[`g:FerretDispatch`](#user-content-gferretdispatch)</strong> 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 &quot;\bfoo[0-9]{2} bar\b&quot; (ie. using `ag`'s Perl-style regular expression syntax), you could do:
@@ -86,7 +82,7 @@ The {pattern} is passed through as-is to the underlying search program, and no e
 :Ack \bfoo[0-9]{2}\ bar\b
 ```
 
-Likewise, {options} are passed through. In this example, we pass the `-w` option (to search on word boundaries), and scope the search to the &quot;foo&quot; and &quot;bar&quot; subdirectories: &gt;
+Likewise, {options} are passed through. In this example, we pass the `-w` option (to search on word boundaries), and scope the search to the &quot;foo&quot; and &quot;bar&quot; subdirectories:
 
 ```
 :Ack -w something foo bar
@@ -106,7 +102,7 @@ Like <strong>[`:Ack`](#user-content-ack)</strong>, but returns all results irres
 
 Just like <strong>[`:Ack`](#user-content-ack)</strong>, but instead of using the <strong>`quickfix`</strong> listing, which is global across an entire Vim instance, it uses the <strong>`location-list`</strong>, which is a per-window construct.
 
-Note that <strong>[`:Lack`](#user-content-lack)</strong> always runs synchronously via <strong>`:cexpr`</strong>, because dispatch.vim doesn't currently support the <strong>`location-list`</strong>.
+Note that <strong>[`:Lack`](#user-content-lack)</strong> always runs synchronously via <strong>`:cexpr`</strong>.
 
 <p align="right"><a name="lack" href="#user-content-lack"><code>:Lack!</code></a></p>
 
@@ -151,6 +147,12 @@ A typical sequence consists of an <strong>[`:Ack`](#user-content-ack)</strong> i
 :Acks /foo/bar/
 ```
 
+The pattern and replacement are passed through literally to Vim's <strong>`:substitute`</strong> command, preserving all characters and escapes, including references to matches in the pattern. For example, the following could be used to swap the order of &quot;foo123&quot; and &quot;bar&quot;:
+
+```
+:Acks /\v(foo\d+)(bar)/\2\1/
+```
+
 <p align="right"><a name="qargs" href="#user-content-qargs"><code>:Qargs</code></a></p>
 
 ### `:Qargs`<a name="ferret-qargs" href="#user-content-ferret-qargs"></a>
@@ -167,9 +169,9 @@ It takes the files currently in the <strong>`quickfix`</strong> listing and sets
 
 Note that Ferret will not try to set up the &lt;leader&gt; mappings if any of the following are true:
 
-- A mapping for already exists.
+- A mapping with the same <strong>`{lhs}`</strong> already exists.
 - An alternative mapping for the same functionality has already been set up from a <strong>`.vimrc`</strong>.
-- The mapping has been suppressed by setting <strong>[`g:FerretMap`](#user-content-gferretmap)</strong> to 1 in your <strong>`.vimrc`</strong>.
+- The mapping has been suppressed by setting <strong>[`g:FerretMap`](#user-content-gferretmap)</strong> to 0 in your <strong>`.vimrc`</strong>.
 
 
 ### Mappings specific to the quickfix window<a name="ferret-mappings-specific-to-the-quickfix-window" href="#user-content-ferret-mappings-specific-to-the-quickfix-window"></a>
@@ -223,23 +225,21 @@ nmap <leader>u <Plug>(FerretAcks)
 
 ## Options<a name="ferret-options" href="#user-content-ferret-options"></a>
 
-<p align="right"><a name="gferretdispatch" href="#user-content-gferretdispatch"><code>g:FerretDispatch</code></a></p>
+<p align="right"><a name="gferretnvim" href="#user-content-gferretnvim"><code>g:FerretNvim</code></a></p>
 
-### `g:FerretDispatch` (boolean, default: 1)<a name="ferret-gferretdispatch-boolean-default-1" href="#user-content-ferret-gferretdispatch-boolean-default-1"></a>
+### `g:FerretNvim` (boolean, default: 1)<a name="ferret-gferretnvim-boolean-default-1" href="#user-content-ferret-gferretnvim-boolean-default-1"></a>
 
-Controls whether to use vim-dispatch (and specifically, <strong>`:Make`</strong>) to run <strong>[`:Ack`](#user-content-ack)</strong> searches asynchronously, when available. To prevent vim-dispatch from being used, set to 0:
+Controls whether to use Neovim's <strong>`job-control`</strong> features, when available, to run searches asynchronously. To prevent this from being used, set to 0, in which case Ferret will fall back to the next method in the list (Vim's built-in async primitives -- see <strong>[`g:FerretJob`](#user-content-gferretjob)</strong> -- which are typically not available in Neovim, so will then fall back to the next available method).
 
 ```
-let g:FerretDispatch=0
+let g:FerretNvim=0
 ```
 
-Note that on sufficiently recent versions of Vim with <strong>`+job`</strong> support, Ferret will first try to use <strong>`+job`</strong>, falling back to vim-dispatch and consulting <strong>[`g:FerretDispatch`](#user-content-gferretdispatch)</strong> only if <strong>[`g:FerretJob`](#user-content-gferretjob)</strong> is set to 0.
-
 <p align="right"><a name="gferretjob" href="#user-content-gferretjob"><code>g:FerretJob</code></a></p>
 
 ### `g:FerretJob` (boolean, default: 1)<a name="ferret-gferretjob-boolean-default-1" href="#user-content-ferret-gferretjob-boolean-default-1"></a>
 
-Controls whether to use Vim's <strong>`+job`</strong> feature, when available, to run searches asynchronously. To prevent <strong>`+job`</strong> from being used, set to 0, in which case Ferret will fall back to vim-dispatch (see also: <strong>[`g:FerretDispatch`](#user-content-gferretdispatch)</strong>):
+Controls whether to use Vim's <strong>`+job`</strong> feature, when available, to run searches asynchronously. To prevent <strong>`+job`</strong> from being used, set to 0, in which case Ferret will fall back to the next available method.
 
 ```
 let g:FerretJob=0
@@ -268,6 +268,20 @@ Example:
 let g:FerretExecutable='ag,rg'
 ```
 
+<p align="right"><a name="gferretexecutablearguments" href="#user-content-gferretexecutablearguments"><code>g:FerretExecutableArguments</code></a></p>
+
+### `g:FerretExecutableArguments` (dict, default: {})<a name="ferret-gferretexecutablearguments-dict-default-" href="#user-content-ferret-gferretexecutablearguments-dict-default-"></a>
+
+Allows you to override the default arguments that get passed to the underlying search executables. For example, to add `-s` to the default arguments passed to `ack` (`--column --with-filename`):
+
+```
+let g:FerretExecutableArguments = {
+  \   'ack': '--column --with-filename -s'
+  \ }
+```
+
+To find out the default arguments for a given executable, see <strong>`ferret#get_default_arguments()`</strong>.
+
 <p align="right"><a name="gferretmaxresults" href="#user-content-gferretmaxresults"><code>g:FerretMaxResults</code></a></p>
 
 ### `g:FerretMaxResults` (number, default: 100000)<a name="ferret-gferretmaxresults-number-default-100000" href="#user-content-ferret-gferretmaxresults-number-default-100000"></a>
@@ -278,6 +292,22 @@ The intent of this option is to prevent runaway search processes that produce hu
 
 In the event that Ferret aborts a search that has hit the <strong>[`g:FerretMaxResults`](#user-content-gferretmaxresults)</strong> limit, a message will be printed prompting users to run the search again with <strong>[`:Ack!`](#user-content-ack)</strong> or <strong>[`:Lack!`](#user-content-lack)</strong> if they want to bypass the limit.
 
+<p align="right"><a name="gferretautojump" href="#user-content-gferretautojump"><code>g:FerretAutojump</code></a></p>
+
+### `g:FerretAutojump` (number, default: 1)<a name="ferret-gferretautojump-number-default-1" href="#user-content-ferret-gferretautojump-number-default-1"></a>
+
+Controls whether Ferret will automatically jump to the first found match.
+
+- Set to 0, Ferret will show the search results but perform no jump.
+- Set to 1 (the default), Ferret will show the search results and focus the result listing.
+- Set to 2, Ferret will show the search results and jump to the first found match.
+
+Example override:
+
+```
+let g:FerretAutojump=2
+```
+
 <p align="right"><a name="gferretqfoptions" href="#user-content-gferretqfoptions"><code>g:FerretQFOptions</code></a></p>
 
 ### `g:FerretQFOptions` (boolean, default: 1)<a name="ferret-gferretqfoptions-boolean-default-1" href="#user-content-ferret-gferretqfoptions-boolean-default-1"></a>
@@ -355,6 +385,21 @@ let g:FerretQFCommands=0
 Sets the '<strong>`grepformat`</strong>' used by Ferret.
 
 
+## Functions<a name="ferret-functions" href="#user-content-ferret-functions"></a>
+
+<p align="right"><a name="ferretgetdefaultarguments" href="#user-content-ferretgetdefaultarguments"><code>ferret#get_default_arguments()</code></a></p>
+
+### `ferret#get_default_arguments()`<a name="ferret-ferretgetdefaultarguments" href="#user-content-ferret-ferretgetdefaultarguments"></a>
+
+Call this with an executable name to find out the default arguments that will be passed when invoking that executable. For example:
+
+```
+echo ferret#get_default_arguments('rg')
+```
+
+This may be useful if you wish to extend or otherwise modify the arguments by setting <strong>[`g:FerretExecutableArguments`](#user-content-gferretexecutablearguments)</strong>.
+
+
 ## Custom autocommands<a name="ferret-custom-autocommands" href="#user-content-ferret-custom-autocommands"></a>
 
 <p align="right"><a name="ferretdidwrite" href="#user-content-ferretdidwrite"><code>FerretDidWrite</code></a> <a name="ferretwillwrite" href="#user-content-ferretwillwrite"><code>FerretWillWrite</code></a></p>
@@ -380,32 +425,38 @@ Ferret overrides the 'grepformat' and 'grepprg' settings, preferentially setting
 Additionally, Ferret includes an <strong>`ftplugin`</strong> for the <strong>`quickfix`</strong> listing that adjusts a number of settings to improve the usability of search results.
 
 <p align="right"><a name="ferret-nolist" href="#user-content-ferret-nolist"><code>ferret-nolist</code></a></p>
-'nolist'
+
+### 'nolist'<a name="ferret-nolist" href="#user-content-ferret-nolist"></a>
 
 Turned off to reduce visual clutter in the search results, and because 'list' is most useful in files that are being actively edited, which is not the case for <strong>`quickfix`</strong> results.
 
 <p align="right"><a name="ferret-norelativenumber" href="#user-content-ferret-norelativenumber"><code>ferret-norelativenumber</code></a></p>
-'norelativenumber'
+
+### 'norelativenumber'<a name="ferret-norelativenumber" href="#user-content-ferret-norelativenumber"></a>
 
 Turned off, because it is more useful to have a sense of absolute progress through the results list than to have the ability to jump to nearby results (especially seeing as the most common operations are moving to the next or previous file, which are both handled nicely by <strong>`:cnf`</strong> and <strong>`:cpf`</strong> respectively).
 
 <p align="right"><a name="ferret-nowrap" href="#user-content-ferret-nowrap"><code>ferret-nowrap</code></a></p>
-'nowrap'
+
+### 'nowrap'<a name="ferret-nowrap" href="#user-content-ferret-nowrap"></a>
 
 Turned off to avoid ugly wrapping that makes the results list hard to read, and because in search results, the most relevant information is the filename, which is on the left and is usually visible even without wrapping.
 
 <p align="right"><a name="ferret-number" href="#user-content-ferret-number"><code>ferret-number</code></a></p>
-'number'
+
+### 'number'<a name="ferret-number" href="#user-content-ferret-number"></a>
 
 Turned on to give a sense of absolute progress through the results.
 
 <p align="right"><a name="ferret-scrolloff" href="#user-content-ferret-scrolloff"><code>ferret-scrolloff</code></a></p>
-'scrolloff'
+
+### 'scrolloff'<a name="ferret-scrolloff" href="#user-content-ferret-scrolloff"></a>
 
 Set to 0 because the <strong>`quickfix`</strong> listing is usually small by default, so trying to keep the current line away from the edge of the viewpoint is futile; by definition it is usually near the edge.
 
 <p align="right"><a name="ferret-nocursorline" href="#user-content-ferret-nocursorline"><code>ferret-nocursorline</code></a></p>
-'nocursorline'
+
+### 'nocursorline'<a name="ferret-nocursorline" href="#user-content-ferret-nocursorline"></a>
 
 Turned off to reduce visual clutter.
 
@@ -458,7 +509,7 @@ This approach to escaping is taken in order to make it straightfoward to use pow
 
 ### Why do Ferret commands start with "Ack", "Lack" and so on?<a name="ferret-why-do-ferret-commands-start-with-ack-lack-and-so-on" href="#user-content-ferret-why-do-ferret-commands-start-with-ack-lack-and-so-on"></a>
 
-Ferret was originally the thinnest of wrappers (7 lines of code in my <strong>`.vimrc`</strong>) 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).
+Ferret was originally the thinnest of wrappers (7 lines of code in my <strong>`.vimrc`</strong>) around `ack`. The earliest traces of it can be seen in the initial commit to my dotfiles repo in May, 2009 (https://rfr.to/h).
 
 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.
 
@@ -544,13 +595,12 @@ git archive -o ferret-$VERSION.zip HEAD -- .
 
 Ferret is written and maintained by Greg Hurrell &lt;greg@hurrell.net&gt;.
 
-The idea for vim-dispatch integration was taken from Miles Sterrett's ack.vim plug-in (https://github.com/mileszs/ack.vim).
-
 Other contributors that have submitted patches include (in alphabetical order):
 
 - Daniel Silva
 - Filip Szymański
 - Joe Lencioni
+- Jon Parise
 - Nelo-Thara Wallus
 - Tom Dooner
 - Vaibhav Sagar
@@ -561,6 +611,40 @@ Other contributors that have submitted patches include (in alphabetical order):
 
 ### master (not yet released)<a name="ferret-master-not-yet-released" href="#user-content-ferret-master-not-yet-released"></a>
 
+- Try to avoid &quot;press ENTER to continue&quot; prompts.
+- Put search term in <strong>`w:quickfix_title`</strong> for use in statuslines (https://github.com/wincent/ferret/pull/57).
+- Add <strong>[`g:FerretExecutableArguments`](#user-content-gferretexecutablearguments)</strong> and <strong>`ferret#get_default_arguments()`</strong> (https://github.com/wincent/ferret/pull/46).
+
+
+### 3.0.3 (23 March 2018)<a name="ferret-303-23-march-2018" href="#user-content-ferret-303-23-march-2018"></a>
+
+- Fix for <strong>[`:Lack`](#user-content-lack)</strong> results opening in quickfix listing in Neovim (https://github.com/wincent/ferret/issues/47).
+
+
+### 3.0.2 (25 October 2017)<a name="ferret-302-25-october-2017" href="#user-content-ferret-302-25-october-2017"></a>
+
+- Fix broken <strong>[`:Back`](#user-content-back)</strong> and <strong>[`:Black`](#user-content-black)</strong> commands (https://github.com/wincent/ferret/issues/48).
+
+
+### 3.0.1 (24 August 2017)<a name="ferret-301-24-august-2017" href="#user-content-ferret-301-24-august-2017"></a>
+
+- Fix failure to handle search patterns containing multiple escaped spaces (https://github.com/wincent/ferret/issues/49).
+
+
+### 3.0 (13 June 2017)<a name="ferret-30-13-june-2017" href="#user-content-ferret-30-13-june-2017"></a>
+
+- Improve handling of backslash escapes (https://github.com/wincent/ferret/issues/41).
+- Add <strong>[`g:FerretAutojump`](#user-content-gferretautojump)</strong>.
+- Drop support for vim-dispatch.
+
+
+### 2.0 (6 June 2017)<a name="ferret-20-6-june-2017" href="#user-content-ferret-20-6-june-2017"></a>
+
+- Add support for Neovim, along with <strong>[`g:FerretNvim`](#user-content-gferretnvim)</strong> setting.
+
+
+### 1.5 "Cinco de Cuatro" (4 May 2017)<a name="ferret-15-cinco-de-cuatro-4-may-2017" href="#user-content-ferret-15-cinco-de-cuatro-4-may-2017"></a>
+
 - Improvements to the handling of very large result sets (due to wide lines or many results).
 - Added <strong>[`g:FerretLazyInit`](#user-content-gferretlazyinit)</strong>.
 - Added missing documentation for <strong>[`g:FerretJob`](#user-content-gferretjob)</strong>.
index 72b656bf1becf96875939e6a8f7629a857a05e22..21a824516384e5aac9610b65eb86dfc94ea3639e 100644 (file)
@@ -7,16 +7,17 @@ CONTENTS                                                       *ferret-contents*
 3. Commands               |ferret-commands|
 4. Mappings               |ferret-mappings|
 5. Options                |ferret-options|
-6. Custom autocommands    |ferret-custom-autocommands|
-7. Overrides              |ferret-overrides|
-8. Troubleshooting        |ferret-troubleshooting|
-9. FAQ                    |ferret-faq|
-10. Related               |ferret-related|
-11. Website               |ferret-website|
-12. License               |ferret-license|
-13. Development           |ferret-development|
-14. Authors               |ferret-authors|
-15. History               |ferret-history|
+6. Functions              |ferret-functions|
+7. Custom autocommands    |ferret-custom-autocommands|
+8. Overrides              |ferret-overrides|
+9. Troubleshooting        |ferret-troubleshooting|
+10. FAQ                   |ferret-faq|
+11. Related               |ferret-related|
+12. Website               |ferret-website|
+13. License               |ferret-license|
+14. Development           |ferret-development|
+15. Authors               |ferret-authors|
+16. History               |ferret-history|
 
 INTRO                                                             *ferret-intro*
 
@@ -31,11 +32,13 @@ Ferret improves Vim's multi-file search in four ways:
 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), or Ack
+ripgrep (https://github.com/BurntSushi/ripgrep), 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).
+expression syntax without doing special escaping. On modern versions of Vim
+(version 8 or higher, or Neovim), 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).
@@ -47,11 +50,6 @@ 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 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
@@ -73,8 +71,9 @@ easier to immediately identify matches by centering them within the viewport.
 
 Finally, Ferret provides a |:Qargs| command that puts the files currently in
 the |quickfix| listing into the |:args| list, where they can be operated on in
-bulk via the |:argdo| command. This is what's used under the covers by |:Acks|
-to do its work.
+bulk via the |:argdo| command. This is what's used under the covers on older
+versions of Vim by |:Acks| to do its work (on newer versions the built-in
+|:cfdo| is used instead).
 
 INSTALLATION                                               *ferret-installation*
 
@@ -113,11 +112,6 @@ as needed.
 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.
 
@@ -130,7 +124,7 @@ regular expression syntax), you could do:
 <
 Likewise, {options} are passed through. In this example, we pass the `-w`
 option (to search on word boundaries), and scope the search to the "foo" and
-"bar" subdirectories: >
+"bar" subdirectories:
 >
     :Ack -w something foo bar
 <
@@ -152,8 +146,7 @@ Just like |:Ack|, but instead of using the |quickfix| listing, which is global
 across an entire Vim instance, it uses the |location-list|, which is a
 per-window construct.
 
-Note that |:Lack| always runs synchronously via |:cexpr|, because dispatch.vim
-doesn't currently support the |location-list|.
+Note that |:Lack| always runs synchronously via |:cexpr|.
 
                                                                         *:Lack!*
 :Lack! {pattern} {options} ~
@@ -205,6 +198,13 @@ directory:
 >
     :Ack foo
     :Acks /foo/bar/
+<
+The pattern and replacement are passed through literally to Vim's
+|:substitute| command, preserving all characters and escapes, including
+references to matches in the pattern. For example, the following could be
+used to swap the order of "foo123" and "bar":
+>
+    :Acks /\v(foo\d+)(bar)/\2\1/
 <
                                                                         *:Qargs*
 :Qargs  ~
@@ -223,10 +223,10 @@ Circumstances where mappings do not get set up ~
 Note that Ferret will not try to set up the <leader> mappings if any of the
 following are true:
 
-- A mapping for already exists.
+- A mapping with the same |{lhs}| already exists.
 - An alternative mapping for the same functionality has already been set up
   from a |.vimrc|.
-- The mapping has been suppressed by setting |g:FerretMap| to 1 in your |.vimrc|.
+- The mapping has been suppressed by setting |g:FerretMap| to 0 in your |.vimrc|.
 
 Mappings specific to the quickfix window ~
 
@@ -278,26 +278,24 @@ in your |.vimrc| instead using |:nmap|:
 OPTIONS                                                         *ferret-options*
 
 
-                                                              *g:FerretDispatch*
-|g:FerretDispatch|                                          boolean (default: 1)
+                                                                  *g:FerretNvim*
+|g:FerretNvim|                                              boolean (default: 1)
 
-Controls whether to use vim-dispatch (and specifically, |:Make|) to run |:Ack|
-searches asynchronously, when available. To prevent vim-dispatch from being
-used, set to 0:
+Controls whether to use Neovim's |job-control| features, when available, to
+run searches asynchronously. To prevent this from being used, set to 0, in
+which case Ferret will fall back to the next method in the list (Vim's
+built-in async primitives -- see |g:FerretJob| -- which are typically not
+available in Neovim, so will then fall back to the next available method).
 >
-    let g:FerretDispatch=0
+    let g:FerretNvim=0
 <
-Note that on sufficiently recent versions of Vim with |+job| support, Ferret
-will first try to use |+job|, falling back to vim-dispatch and consulting
-|g:FerretDispatch| only if |g:FerretJob| is set to 0.
-
 
                                                                    *g:FerretJob*
 |g:FerretJob|                                               boolean (default: 1)
 
 Controls whether to use Vim's |+job| feature, when available, to run searches
 asynchronously. To prevent |+job| from being used, set to 0, in which case
-Ferret will fall back to vim-dispatch (see also: |g:FerretDispatch|):
+Ferret will fall back to the next available method.
 >
     let g:FerretJob=0
 <
@@ -329,6 +327,21 @@ Example:
     let g:FerretExecutable='ag,rg'
 <
 
+                                                   *g:FerretExecutableArguments*
+|g:FerretExecutableArguments|                                 dict (default: {})
+
+Allows you to override the default arguments that get passed to the
+underlying search executables. For example, to add `-s` to the default
+arguments passed to `ack` (`--column --with-filename`):
+>
+    let g:FerretExecutableArguments = {
+      \   'ack': '--column --with-filename -s'
+      \ }
+<
+To find out the default arguments for a given executable, see
+|ferret#get_default_arguments()|.
+
+
                                                             *g:FerretMaxResults*
 |g:FerretMaxResults|                                    number (default: 100000)
 
@@ -347,6 +360,22 @@ limit, a message will be printed prompting users to run the search again
 with |:Ack!| or |:Lack!| if they want to bypass the limit.
 
 
+                                                              *g:FerretAutojump*
+|g:FerretAutojump|                                           number (default: 1)
+
+Controls whether Ferret will automatically jump to the first found match.
+
+- Set to 0, Ferret will show the search results but perform no jump.
+- Set to 1 (the default), Ferret will show the search results and focus the
+  result listing.
+- Set to 2, Ferret will show the search results and jump to the first found
+  match.
+
+Example override:
+>
+    let g:FerretAutojump=2
+<
+
                                                              *g:FerretQFOptions*
 |g:FerretQFOptions|                                         boolean (default: 1)
 
@@ -424,6 +453,19 @@ set to 0:
 
 Sets the '|grepformat|' used by Ferret.
 
+FUNCTIONS                                                     *ferret-functions*
+
+                                                *ferret#get_default_arguments()*
+ferret#get_default_arguments() ~
+
+Call this with an executable name to find out the default arguments that
+will be passed when invoking that executable. For example:
+>
+    echo ferret#get_default_arguments('rg')
+<
+This may be useful if you wish to extend or otherwise modify the arguments
+by setting |g:FerretExecutableArguments|.
+
 CUSTOM AUTOCOMMANDS                                 *ferret-custom-autocommands*
 
 
@@ -454,7 +496,7 @@ adjusts a number of settings to improve the usability of search results.
 
 
                                                                  *ferret-nolist*
-'nolist'
+'nolist' ~
 
 Turned off to reduce visual clutter in the search results, and because
 'list' is most useful in files that are being actively edited, which is not
@@ -462,7 +504,7 @@ the case for |quickfix| results.
 
 
                                                        *ferret-norelativenumber*
-'norelativenumber'
+'norelativenumber' ~
 
 Turned off, because it is more useful to have a sense of absolute progress
 through the results list than to have the ability to jump to nearby results
@@ -471,7 +513,7 @@ previous file, which are both handled nicely by |:cnf| and |:cpf| respectively).
 
 
                                                                  *ferret-nowrap*
-'nowrap'
+'nowrap' ~
 
 Turned off to avoid ugly wrapping that makes the results list hard to read,
 and because in search results, the most relevant information is the
@@ -479,13 +521,13 @@ filename, which is on the left and is usually visible even without wrapping.
 
 
                                                                  *ferret-number*
-'number'
+'number' ~
 
 Turned on to give a sense of absolute progress through the results.
 
 
                                                               *ferret-scrolloff*
-'scrolloff'
+'scrolloff' ~
 
 Set to 0 because the |quickfix| listing is usually small by default, so trying
 to keep the current line away from the edge of the viewpoint is futile; by
@@ -493,7 +535,7 @@ definition it is usually near the edge.
 
 
                                                            *ferret-nocursorline*
-'nocursorline'
+'nocursorline' ~
 
 Turned off to reduce visual clutter.
 
@@ -542,7 +584,7 @@ Why do Ferret commands start with "Ack", "Lack" and so on? ~
 
 Ferret was originally the thinnest of wrappers (7 lines of code in my
 |.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).
+commit to my dotfiles repo in May, 2009 (https://rfr.to/h).
 
 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
@@ -631,15 +673,13 @@ AUTHORS                                                         *ferret-authors*
 
 Ferret is written and maintained by Greg Hurrell <greg@hurrell.net>.
 
-The idea for vim-dispatch integration was taken from Miles Sterrett's
-ack.vim plug-in (https://github.com/mileszs/ack.vim).
-
 Other contributors that have submitted patches include (in alphabetical
 order):
 
 - Daniel Silva
 - Filip Szymański
 - Joe Lencioni
+- Jon Parise
 - Nelo-Thara Wallus
 - Tom Dooner
 - Vaibhav Sagar
@@ -648,6 +688,40 @@ HISTORY                                                         *ferret-history*
 
 master (not yet released) ~
 
+- Try to avoid "press ENTER to continue" prompts.
+- Put search term in |w:quickfix_title| for use in statuslines
+  (https://github.com/wincent/ferret/pull/57).
+- Add |g:FerretExecutableArguments| and |ferret#get_default_arguments()|
+  (https://github.com/wincent/ferret/pull/46).
+
+3.0.3 (23 March 2018) ~
+
+- Fix for |:Lack| results opening in quickfix listing in Neovim
+  (https://github.com/wincent/ferret/issues/47).
+
+3.0.2 (25 October 2017) ~
+
+- Fix broken |:Back| and |:Black| commands
+  (https://github.com/wincent/ferret/issues/48).
+
+3.0.1 (24 August 2017) ~
+
+- Fix failure to handle search patterns containing multiple escaped spaces
+  (https://github.com/wincent/ferret/issues/49).
+
+3.0 (13 June 2017) ~
+
+- Improve handling of backslash escapes
+  (https://github.com/wincent/ferret/issues/41).
+- Add |g:FerretAutojump|.
+- Drop support for vim-dispatch.
+
+2.0 (6 June 2017) ~
+
+- Add support for Neovim, along with |g:FerretNvim| setting.
+
+1.5 "Cinco de Cuatro" (4 May 2017) ~
+
 - Improvements to the handling of very large result sets (due to wide lines
   or many results).
 - Added |g:FerretLazyInit|.
diff --git a/tests/fixtures/integration/ferret/input/autoload/ferret.vim b/tests/fixtures/integration/ferret/input/autoload/ferret.vim
new file mode 100644 (file)
index 0000000..91b368d
--- /dev/null
@@ -0,0 +1,19 @@
+" Copyright 2018-present Greg Hurrell. All rights reserved.
+" Licensed under the terms of the BSD 2-clause license.
+
+""
+" @function ferret#get_default_arguments
+"
+" Call this with an executable name to find out the default arguments that will
+" be passed when invoking that executable. For example:
+"
+" ```
+" echo ferret#get_default_arguments('rg')
+" ```
+"
+" This may be useful if you wish to extend or otherwise modify the arguments
+" by setting |g:FerretExecutableArguments|.
+"
+function! ferret#get_default_arguments(executable) abort
+  return get(ferret#private#executables(), a:executable, '')
+endfunction
index 28a7988d9f1b82d30d96b70eb61ea60e8aae3019..4aa266120a902dbb8ae81b02e9c01e838843ab1d 100644 (file)
@@ -20,25 +20,24 @@ function! s:delete(first, last)
   execute "normal \<C-W>\<C-P>"
 endfunction
 
-" Returns 1 if we should/can use vim-dispatch.
-function! ferret#private#dispatch() abort
+" Returns 1 if we should use Neovim's |job-control| features.
+function! ferret#private#nvim()
   ""
-  " @option g:FerretDispatch boolean 1
+  " @option g:FerretNvim boolean 1
   "
-  " Controls whether to use vim-dispatch (and specifically, |:Make|) to run
-  " |:Ack| searches asynchronously, when available. To prevent vim-dispatch from
-  " being used, set to 0:
+  " Controls whether to use Neovim's |job-control| features, when
+  " available, to run searches asynchronously. To prevent this from
+  " being used, set to 0, in which case Ferret will fall back to the next
+  " method in the list (Vim's built-in async primitives -- see
+  " |g:FerretJob| -- which are typically not available in Neovim, so
+  " will then fall back to the next available method).
   "
   " ```
-  " let g:FerretDispatch=0
+  " let g:FerretNvim=0
   " ```
-  "
-  " Note that on sufficiently recent versions of Vim with |+job| support, Ferret
-  " will first try to use |+job|, falling back to vim-dispatch and consulting
-  " |g:FerretDispatch| only if |g:FerretJob| is set to 0.
-  "
-  let l:dispatch=get(g:, 'FerretDispatch', 1)
-  return l:dispatch && exists(':Make') == 2
+  let l:nvim=get(g:, 'FerretNvim', 1)
+
+  return l:nvim && has('nvim')
 endfunction
 
 " Returns 1 if we can use Vim's built-in async primitives.
@@ -48,8 +47,7 @@ function! ferret#private#async()
   "
   " Controls whether to use Vim's |+job| feature, when available, to run
   " searches asynchronously. To prevent |+job| from being used, set to 0, in
-  " which case Ferret will fall back to vim-dispatch (see also:
-  " |g:FerretDispatch|):
+  " which case Ferret will fall back to the next available method.
   "
   " ```
   " let g:FerretJob=0
@@ -61,41 +59,44 @@ function! ferret#private#async()
   return l:async && has('patch-7-4-1829')
 endfunction
 
-" Use `input()` to show error output to user. Ideally, we would do this in a way
-" that didn't require user interaction, but this is the only reliable mechanism
-" that works for all cases. Alternatives considered:
-"
-" (1) Using `:echomsg`
-"
-"     When not using vim-dispatch, the screen is getting cleared before the
-"     user sees it, even with a pre-emptive `:redraw!` beforehand. Note that
-"     we can get the message to linger on the screen by making it multi-line and
-"     forcing Vim to show a prompt (see `:h hit-enter-prompt`), but this is not
-"     reliable because the number of lines required to force the prompt will
-"     vary by system, depending on the value of `'cmdheight'`.
-"
-"     When using vim-dispatch, anything we output ends up getting swallowed
-"     before the user sees it, because something it is doing is clearing the
-"     screen. This is true no matter how many lines we output.
-"
-" (2) Writing back into the quickfix/location list
-"
-"     This interacts poorly with vim-dispatch. If we write back an error message
-"     and then call `:copen 1`, vim-dispatch ends up closing the listing before
-"     the user sees it.
-"
-" (3) Using `:echoerr`
-"
-"     This works, but presents to the user as an exception (see `:h :echoerr`).
-"
 function! ferret#private#error(message) abort
-  call inputsave()
+  if has('lambda') && has('timers')
+    call timer_start(100, {-> s:print_error_with_echomsg(a:message)})
+  else
+    " Use `input()` to show error output to user. Ideally, we would do this
+    " in a way that didn't require user interaction, but this is the only
+    " reliable mechanism that works for all cases. Alternatives considered:
+    "
+    " (1) Using straight `:echomsg`
+    "
+    "     The screen gets cleared before the user sees it, even with a
+    "     pre-emptive `:redraw!` beforehand. Note that we can get the
+    "     message to linger on the screen by making it multi-line and
+    "     forcing Vim to show a prompt (see `:h hit-enter-prompt`), but
+    "     this is not reliable because the number of lines required to
+    "     force the prompt will vary by system, depending on the value
+    "     of `'cmdheight'`.
+    "
+    " (2) Using `:echoerr`
+    "
+    "     This works, but presents to the user as an exception (see `:h
+    "     :echoerr`).
+    "
+    call inputsave()
+    echohl ErrorMsg
+    unsilent call input(a:message . ': press ENTER to continue')
+    echohl NONE
+    call inputrestore()
+    unsilent echo
+    redraw!
+  endif
+endfunction
+
+function! s:print_error_with_echomsg(message)
+  redraw!
   echohl ErrorMsg
-  unsilent call input(a:message . ': press ENTER to continue')
+  echomsg a:message
   echohl NONE
-  call inputrestore()
-  unsilent echo
-  redraw!
 endfunction
 
 " Parses arguments, extracting a search pattern (which is stored in
@@ -106,9 +107,39 @@ function! s:parse(args) abort
     unlet g:ferret_lastsearch
   endif
 
+  " Split on unescaped spaces:
+  "
+  "   foo bar     -> [foo, bar]
+  "   foo\ bar    -> [foo\ bar] (no split)
+  "   foo\\ bar   -> [foo\\, bar]
+  "   foo\\\ bar  -> [foo\\\ bar] (no split)
+  "   foo\\\\ bar -> [foo\\\\, bar]
+  "
+  " We build a regex for this as follows:
+  "
+  "   - match an odd number of "X": X(XX)*
+  "   - add negative lookbehind (don't match after an "X"): X\@<!X(XX)*
+  "   - with whitespace (for readability): X \@<! X(XX)*
+  "   - add negative lookahead (don't match before an "X"): X\@<!X(XX)*X\@!
+  "   - with whitespace: X \@<! X(XX)* X\@!
+  "   - denote this "..."
+  "   - match a "Y" not preceded by the above: (...)\@<!Y
+  "   - with whitespace: (...) \@<! Y
+  "   - replace "..." with actual pattern: (X\@<!X(XX)*X\@!)\@<!Y
+  "   - escape ( and ): \(X\@<!X\(XX\)*X\@!\)\@<!Y
+  "   - replace "X" with "\\": \(\\\@<!\\\(\\\\\)*\\\@!\)\@<!Y
+  "   - replace "Y" with " ": '\(\\\@<!\\\(\\\\\)*\\\@!\)\@<! '
+  "
+  let l:odd_number_of_backslashes='\\\@<!\\\(\\\\\)*\\\@!'
+  let l:unescaped_space='\('.l:odd_number_of_backslashes.'\)\@<! '
+  let l:args=split(a:args, l:unescaped_space)
   let l:expanded_args=[]
 
-  for l:arg in a:args
+  for l:arg in l:args
+    " Because we split on unescaped spaces, we know any escaped spaces remaining
+    " inside arguments really are supposed to be just spaces.
+    let l:arg=substitute(l:arg, '\\ ', ' ', 'g')
+
     if ferret#private#option(l:arg)
       " Options get passed through as-is.
       call add(l:expanded_args, l:arg)
@@ -127,7 +158,7 @@ function! s:parse(args) abort
     endif
   endfor
 
-  if ferret#private#async()
+  if ferret#private#nvim() || ferret#private#async()
     return l:expanded_args
   endif
 
@@ -144,13 +175,26 @@ function! ferret#private#clearautocmd() abort
   endif
 endfunction
 
+function! s:qfsize(type) abort
+  if has('patch-8.0.1112')
+    if a:type ==# 'qf'
+      return get(getqflist({'size' : 0}), 'size', 0)
+    else
+      return get(getloclist(0, {'size' : 0}), 'size', 0)
+    endif
+  else
+    let l:qflist=a:type ==# 'qf' ? getqflist() : getloclist(0)
+    return len(l:qflist)
+  endif
+endfunction
+
 function! ferret#private#post(type) abort
   call ferret#private#clearautocmd()
-  let l:lastsearch = get(g:, 'ferret_lastsearch', '')
-  let l:qflist = a:type == 'qf' ? getqflist() : getloclist(0)
-  let l:tip = ' [see `:help ferret-quotes`]'
-  if len(l:qflist) == 0
-    let l:base = 'No results for search pattern `' . l:lastsearch . '`'
+  let l:lastsearch=get(g:, 'ferret_lastsearch', '')
+  let l:tip=' [see `:help ferret-quotes`]'
+  let l:len=s:qfsize(a:type)
+  if l:len == 0
+    let l:base='No results for search pattern `' . l:lastsearch . '`'
 
     " Search pattern has no spaces and is entirely enclosed in quotes;
     " eg 'foo' or "bar"
@@ -161,8 +205,9 @@ function! ferret#private#post(type) abort
     endif
   else
     " Find any "invalid" entries in the list.
-    let l:invalid = filter(copy(l:qflist), 'v:val.valid == 0')
-    if len(l:invalid) == len(l:qflist)
+    let l:qflist=a:type ==# 'qf' ? getqflist() : getloclist(0)
+    let l:invalid=filter(copy(l:qflist), 'v:val.valid == 0')
+    if len(l:invalid) == l:len
       " Every item in the list was invalid.
       redraw!
       echohl ErrorMsg
@@ -171,27 +216,22 @@ function! ferret#private#post(type) abort
       endfor
       echohl NONE
 
-      let l:base = 'Search for `' . l:lastsearch . '` failed'
-
-      " When using vim-dispatch, the messages printed above get cleared, so the
-      " only way to see them is with `:messages`.
-      let l:suffix = a:type == 'qf' && ferret#private#dispatch() ?
-            \ ' (run `:messages` to see details)' :
-            \ ''
+      let l:base='Search for `' . l:lastsearch . '` failed'
 
       " If search pattern looks like `'foo` or `"bar`, it means the user
       " probably tried to search for 'foo bar' or "bar baz" etc.
       if l:lastsearch =~ '\v^[' . "'" . '"].+[^' . "'" . '"]$'
-        call ferret#private#error(l:base . l:tip . l:suffix)
+        call ferret#private#error(l:base . l:tip)
       else
-        call ferret#private#error(l:base . l:suffix)
+        call ferret#private#error(l:base)
       endif
     endif
   endif
+  return l:len
 endfunction
 
-function! ferret#private#ack(bang, ...) abort
-  let l:command=s:parse(a:000)
+function! ferret#private#ack(bang, args) abort
+  let l:command=s:parse(a:args)
   call ferret#private#hlsearch()
 
   let l:executable=ferret#private#executable()
@@ -200,10 +240,10 @@ function! ferret#private#ack(bang, ...) abort
     return
   endif
 
-  if ferret#private#async()
+  if ferret#private#nvim()
+    call ferret#private#nvim#search(l:command, 1, a:bang)
+  elseif ferret#private#async()
     call ferret#private#async#search(l:command, 1, a:bang)
-  elseif ferret#private#dispatch()
-    call ferret#private#dispatch#search(l:command)
   else
     call ferret#private#vanilla#search(l:command, 1)
   endif
@@ -212,15 +252,15 @@ 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
+  return join(l:bufpaths, ' ')
 endfunction
 
-function! ferret#private#back(bang, ...) abort
-  call call('ferret#private#ack', a:bang, a:000 + ferret#private#buflist())
+function! ferret#private#back(bang, args) abort
+  call call('ferret#private#ack', [a:bang, a:args . ' ' . ferret#private#buflist()])
 endfunction
 
-function! ferret#private#black(bang, ...) abort
-  call call('ferret#private#lack', a:bang, a:000 + ferret#private#buflist())
+function! ferret#private#black(bang, args) abort
+  call call('ferret#private#lack', [a:bang, a:args . ' ' . ferret#private#buflist()])
 endfunction
 
 function! ferret#private#installprompt() abort
@@ -229,8 +269,8 @@ function! ferret#private#installprompt() abort
         \ )
 endfunction
 
-function! ferret#private#lack(bang, ...) abort
-  let l:command=s:parse(a:000)
+function! ferret#private#lack(bang, args) abort
+  let l:command=s:parse(a:args)
   call ferret#private#hlsearch()
 
   let l:executable=ferret#private#executable()
@@ -239,7 +279,9 @@ function! ferret#private#lack(bang, ...) abort
     return
   endif
 
-  if ferret#private#async()
+  if ferret#private#nvim()
+    call ferret#private#nvim#search(l:command, 0, a:bang)
+  elseif ferret#private#async()
     call ferret#private#async#search(l:command, 0, a:bang)
   else
     call ferret#private#vanilla#search(l:command, 0)
@@ -290,7 +332,7 @@ endfunction
 " way for mnemonics, as it will most often be preceded by an :Ack invocation.)
 function! ferret#private#acks(command) abort
   " Accept any pattern allowed by E146 (crude sanity check).
-  let l:matches = matchlist(a:command, '\v\C^(([^|"\\a-zA-Z0-9]).+\2.*\2)([cgeiI]*)$')
+  let l:matches=matchlist(a:command, '\v\C^(([^|"\\a-zA-Z0-9]).+\2.*\2)([cgeiI]*)$')
   if !len(l:matches)
     call ferret#private#error(
           \ 'Ferret: Expected a substitution expression (/foo/bar/); got: ' .
@@ -301,8 +343,8 @@ function! ferret#private#acks(command) abort
 
   " Pass through options `c`, `i`/`I` to `:substitute`.
   " Add options `e` and `g` if not already present.
-  let l:pattern = l:matches[1]
-  let l:options = l:matches[3]
+  let l:pattern=l:matches[1]
+  let l:options=l:matches[3]
   if l:options !~# 'e'
     let l:options .= 'e'
   endif
@@ -383,7 +425,7 @@ function! ferret#private#executable_name()
   let l:binary=matchstr(l:executable, '\v\w+')
 endfunction
 
-let s:options = {
+let s:options={
       \   'ack': [
       \     '--ignore-ack-defaults',
       \     '--ignore-case',
@@ -567,15 +609,21 @@ endfunction
 " ```
 let s:force=get(g:, 'FerretExecutable', 'rg,ag,ack,ack-grep')
 
+" Base set of default arguments for each executable; these get extended by
+" ferret#private#init() upon startup.
 let s:executables={
-      \   'rg': 'rg --vimgrep --no-heading',
-      \   'ag': 'ag',
-      \   'ack': 'ack --column --with-filename',
-      \   'ack-grep': 'ack-grep --column --with-filename'
+      \   'rg': '--vimgrep --no-config --no-heading',
+      \   'ag': '',
+      \   'ack': '--column --with-filename',
+      \   'ack-grep': '--column --with-filename'
       \ }
 
 let s:init_done=0
 
+function! ferret#private#executables() abort
+  return copy(s:executables)
+endfunction
+
 function! ferret#private#init() abort
   if s:init_done
     return
@@ -588,9 +636,9 @@ function! ferret#private#init() abort
   if executable('ag')
     let l:ag_help=system('ag --help')
     if match(l:ag_help, '--vimgrep') != -1
-      let s:executables['ag'].=' --vimgrep'
+      let s:executables['ag'].='--vimgrep'
     else
-      let s:executables['ag'].=' --column'
+      let s:executables['ag'].='--column'
     endif
     if match(l:ag_help, '--width') != -1
       let s:executables['ag'].=' --width 4096'
@@ -624,10 +672,52 @@ function! ferret#private#executable() abort
   endif
   for l:executable in l:executables
     if executable(l:executable)
-      return s:executables[l:executable]
+      ""
+      " @option g:FerretExecutableArguments dict {}
+      "
+      " Allows you to override the default arguments that get passed to the
+      " underlying search executables. For example, to add `-s` to the default
+      " arguments passed to `ack` (`--column --with-filename`):
+      "
+      " ```
+      " let g:FerretExecutableArguments = {
+      "   \   'ack': '--column --with-filename -s'
+      "   \ }
+      " ```
+      "
+      " To find out the default arguments for a given executable, see
+      " |ferret#get_default_arguments()|.
+      "
+      let l:overrides=get(g:, 'FerretExecutableArguments', {})
+      let l:type=exists('v:t_dict') ? v:t_dict : 4
+      if type(l:overrides) == l:type && has_key(l:overrides, l:executable)
+        return l:executable . ' ' . l:overrides[l:executable]
+      else
+        return l:executable . ' ' . s:executables[l:executable]
+      endif
     endif
   endfor
   return ''
 endfunction
 
+function! ferret#private#limit() abort
+  ""
+  " @option g:FerretMaxResults number 100000
+  "
+  " Controls the maximum number of results Ferret will attempt to gather before
+  " displaying the results. Note that this only applies when searching
+  " asynchronously; that is, on recent versions of Vim with |+job| support and
+  " when |g:FerretJob| is not set to 0.
+  "
+  " The intent of this option is to prevent runaway search processes that produce
+  " huge volumes of output (for example, searching for a common string like "test"
+  " inside a |$HOME| directory containing millions of files) from locking up Vim.
+  "
+  " In the event that Ferret aborts a search that has hit the |g:FerretMaxResults|
+  " limit, a message will be printed prompting users to run the search again
+  " with |:Ack!| or |:Lack!| if they want to bypass the limit.
+  "
+  return max([1, +get(g:, 'FerretMaxResults', 100000)]) - 1
+endfunction
+
 call ferret#private#init()
index 97a513ba72ac4e62c8c07df0c3ccd5c490ff7385..0d48a9b442e3752e2739426cc201b68fa5aec94d 100644 (file)
@@ -71,23 +71,7 @@ function! ferret#private#async#err_cb(channel, msg)
   endif
 endfunction
 
-""
-" @option g:FerretMaxResults number 100000
-"
-" Controls the maximum number of results Ferret will attempt to gather before
-" displaying the results. Note that this only applies when searching
-" asynchronously; that is, on recent versions of Vim with |+job| support and
-" when |g:FerretJob| is not set to 0.
-"
-" The intent of this option is to prevent runaway search processes that produce
-" huge volumes of output (for example, searching for a common string like "test"
-" inside a |$HOME| directory containing millions of files) from locking up Vim.
-"
-" In the event that Ferret aborts a search that has hit the |g:FerretMaxResults|
-" limit, a message will be printed prompting users to run the search again
-" with |:Ack!| or |:Lack!| if they want to bypass the limit.
-"
-let s:limit=max([1, +get(g:, 'FerretMaxResults', 100000)]) - 1
+let s:limit=ferret#private#limit()
 
 function! ferret#private#async#out_cb(channel, msg)
   let l:info=s:info_from_channel(a:channel)
diff --git a/tests/fixtures/integration/ferret/input/autoload/ferret/private/nvim.vim b/tests/fixtures/integration/ferret/input/autoload/ferret/private/nvim.vim
new file mode 100644 (file)
index 0000000..6350dec
--- /dev/null
@@ -0,0 +1,157 @@
+" Copyright 2015-present Greg Hurrell. All rights reserved.
+" Licensed under the terms of the BSD 2-clause license.
+
+let s:jobs={}
+
+function! s:info_from_job(job)
+  if has_key(s:jobs, a:job)
+    return s:jobs[a:job]
+  endif
+endfunction
+
+function! ferret#private#nvim#search(command, ack, bang) abort
+  call ferret#private#nvim#cancel()
+  call ferret#private#autocmd('FerretAsyncStart')
+  let l:command_and_args=extend(split(ferret#private#executable()), a:command)
+  let l:job=jobstart(l:command_and_args, {
+        \   'on_stderr': 'ferret#private#nvim#err_cb',
+        \   'on_stdout': 'ferret#private#nvim#out_cb',
+        \   'on_exit': 'ferret#private#nvim#close_cb'
+        \ })
+  let s:jobs[l:job]={
+        \   'job': l:job,
+        \   'errors': [],
+        \   'output': [],
+        \   'pending_error': '',
+        \   'pending_output': '',
+        \   'pending_error_length': 0,
+        \   'pending_output_length': 0,
+        \   'result_count': 0,
+        \   'ack': a:ack,
+        \   'bang': a:bang,
+        \   'window': win_getid()
+        \ }
+endfunction
+
+" Quickfix listing will truncate longer lines than this.
+let s:max_line_length=4096
+
+function! ferret#private#nvim#err_cb(job, lines, event) dict
+  let l:info=s:info_from_job(a:job)
+  if type(l:info) == 4
+    let l:count=len(a:lines)
+    for l:i in range(l:count)
+      let l:line=a: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: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
+        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
+    endfor
+  endif
+endfunction
+
+let s:limit=ferret#private#limit()
+
+function! ferret#private#nvim#out_cb(job, lines, event) dict
+  let l:info=s:info_from_job(a:job)
+  if type(l:info) == 4
+    if !l:info.bang && l:info.result_count > s:limit
+      call s:MaxResultsExceeded(l:info)
+      return
+    endif
+    let l:count=len(a:lines)
+    for l:i in range(l:count)
+      let l:line=a: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(
+                \   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
+        let l:info.pending_output=''
+        let l:info.pending_output_length=0
+        if !l:info.bang
+          let l:info.result_count+=1
+          if l:info.result_count > s:limit
+            call s:MaxResultsExceeded(l:info)
+            break
+          endif
+        endif
+      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
+    endfor
+  endif
+endfunction
+
+function! ferret#private#nvim#close_cb(job, data, event) abort dict
+  " Job may have been canceled with cancel_async. Do nothing in that case.
+  let l:info=s:info_from_job(a:job)
+  if type(l:info) == 4
+    call remove(s:jobs, a:job)
+    call ferret#private#autocmd('FerretAsyncFinish')
+    if !l:info.ack
+      " If this is a :Lack search, try to focus appropriate window.
+      call win_gotoid(l:info.window)
+    endif
+    call ferret#private#shared#finalize_search(l:info.output, l:info.ack)
+    for l:error in l:info.errors
+      unsilent echomsg l:error
+    endfor
+  endif
+endfunction
+
+function! ferret#private#nvim#pull() abort
+  for l:job in keys(s:jobs)
+    let l:info=s:jobs[l:job]
+    call ferret#private#shared#finalize_search(l:info.output, l:info.ack)
+  endfor
+endfunction
+
+function! ferret#private#nvim#cancel() abort
+  let l:canceled=0
+  for l:job in keys(s:jobs)
+    call jobstop(l:job)
+    call remove(s:jobs, l:job)
+    let l:canceled=1
+  endfor
+  if l:canceled
+    call ferret#private#autocmd('FerretAsyncFinish')
+  endif
+endfunction
+
+" Stop a single job as a result of hitting g:FerretMaxResults.
+function! s:MaxResultsExceeded(info)
+  call ferret#private#shared#finalize_search(a:info.output, a:info.ack)
+  call jobstop(a:info.job)
+  call remove(s:jobs, a:info.job)
+  call ferret#private#autocmd('FerretAsyncFinish')
+  call ferret#private#error(
+        \   'Maximum result count exceeded. ' .
+        \   'Either increase g:FerretMaxResults or ' .
+        \   're-run the search with :Ack!, :Lack! etc.'
+        \ )
+endfunction
+
+function! ferret#private#nvim#debug() abort
+  return s:jobs
+endfunction
index 14ad5a1926ed5fbc0bd1d9aa1be09cb84c115a57..90fbbb2fa8e81c6ea70a5490d8aa9b95217e4fde 100644 (file)
@@ -1,18 +1,74 @@
 " Copyright 2015-present Greg Hurrell. All rights reserved.
 " Licensed under the terms of the BSD 2-clause license.
 
+""
+"
+" @option g:FerretAutojump number 1
+"
+" Controls whether Ferret will automatically jump to the first found match.
+"
+" - Set to 0, Ferret will show the search results but perform no jump.
+" - Set to 1 (the default), Ferret will show the search results and
+"   focus the result listing.
+" - Set to 2, Ferret will show the search results and jump to the first found
+"   match.
+"
+" Example override:
+"
+" ```
+" let g:FerretAutojump=2
+" ```
+function! s:autojump()
+  let l:autojump=get(g:, 'FerretAutojump', 1)
+  if l:autojump != 0 && l:autojump != 1 && l:autojump != 2
+    let l:autojump=1
+  endif
+  return l:autojump
+endfunction
+
+function! s:set_title(type, title)
+  if has('patch-7.4.2200')
+    if a:type ==# 'qf'
+      call setqflist([], 'a', {'title' : a:title})
+    else
+      call setloclist(0, [], 'a', {'title' : a:title})
+    endif
+  elseif a:type ==# 'qf'
+    let w:quickfix_title=a:title
+  endif
+endfunction
+
 function! ferret#private#shared#finalize_search(output, ack)
+  let l:lastsearch = get(g:, 'ferret_lastsearch', '')
   let l:original_errorformat=&errorformat
+  let l:autojump=s:autojump()
+  if a:ack
+    let l:prefix='c' " Will use cexpr, cgetexpr.
+    let l:handler=get(g:, 'FerretQFHandler', 'botright copen')
+    let l:post='qf'
+  else
+    let l:prefix='l' " Will use lexpr, lgetexpr.
+    let l:handler=get(g:, 'FerretLLHandler', 'lopen')
+    let l:post='location'
+  endif
   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')
+    if l:autojump == 2 " Show listing and jump to first result.
+      call s:swallow(l:prefix . 'expr a:1', a:output)
     else
-      call s:swallow('lexpr a:1', a:output)
-      execute get(g:, 'FerretLLHandler', 'lwindow')
-      call ferret#private#post('location')
+      call s:swallow(l:prefix . 'getexpr a:1', a:output)
+    endif
+    call s:set_title(l:post, 'Search `' . l:lastsearch . '`')
+    let l:before=winnr()
+    let l:len=ferret#private#post(l:post)
+    if l:len
+      execute l:handler
+      if l:autojump != 1 " Show listing, but don't jump anywhere.
+        let l:after=winnr()
+        if l:before != l:after
+          execute l:before . 'wincmd w'
+        end
+      endif
     endif
   finally
     let &errorformat=l:original_errorformat
index 32261908ad873092c68739fb8e706e82ce3ef279..4c62ae2bceacd363144cf2d05be5b567d55ca71b 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), or Ack
+" ripgrep (https://github.com/BurntSushi/ripgrep), 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).
+" expression syntax without doing special escaping. On modern versions
+" of Vim (version 8 or higher, or Neovim), 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).
 " |: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
-" 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
@@ -59,8 +56,9 @@
 "
 " Finally, Ferret provides a |:Qargs| command that puts the files currently in
 " the |quickfix| listing into the |:args| list, where they can be operated on in
-" bulk via the |:argdo| command. This is what's used under the covers by |:Acks|
-" to do its work.
+" bulk via the |:argdo| command. This is what's used under the covers on older
+" versions of Vim by |:Acks| to do its work (on newer versions the built-in
+" |:cfdo| is used instead).
 "
 "
 " # Installation
 " Note that Ferret will not try to set up the <leader> mappings if any of the
 " following are true:
 "
-" - A mapping for already exists.
+" - A mapping with the same |{lhs}| already exists.
 " - An alternative mapping for the same functionality has already been set up
 "   from a |.vimrc|.
-" - The mapping has been suppressed by setting |g:FerretMap| to 1 in your
+" - The mapping has been suppressed by setting |g:FerretMap| to 0 in your
 "   |.vimrc|.
 "
 " ## Mappings specific to the quickfix window
 "
 " @indent
 "                                                                 *ferret-nolist*
-"   'nolist'
+"   ## 'nolist'
 "
 "   Turned off to reduce visual clutter in the search results, and because
 "   'list' is most useful in files that are being actively edited, which is not
 "   the case for |quickfix| results.
 "
 "                                                       *ferret-norelativenumber*
-"   'norelativenumber'
+"   ## 'norelativenumber'
 "
 "   Turned off, because it is more useful to have a sense of absolute progress
 "   through the results list than to have the ability to jump to nearby results
 "   respectively).
 "
 "                                                                 *ferret-nowrap*
-"   'nowrap'
+"   ## 'nowrap'
 "
 "   Turned off to avoid ugly wrapping that makes the results list hard to read,
 "   and because in search results, the most relevant information is the
 "   filename, which is on the left and is usually visible even without wrapping.
 "
 "                                                                 *ferret-number*
-"   'number'
+"   ## 'number'
 "
 "   Turned on to give a sense of absolute progress through the results.
 "
 "                                                              *ferret-scrolloff*
-"   'scrolloff'
+"   ## 'scrolloff'
 "
 "   Set to 0 because the |quickfix| listing is usually small by default, so
 "   trying to keep the current line away from the edge of the viewpoint is
 "   futile; by definition it is usually near the edge.
 "
 "                                                           *ferret-nocursorline*
-"   'nocursorline'
+"   ## 'nocursorline'
 "
 "   Turned off to reduce visual clutter.
 "
 "
 " Ferret was originally the thinnest of wrappers (7 lines of code in my
 " |.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).
+" commit to my dotfiles repo in May, 2009 (https://rfr.to/h).
 "
 " 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
 "
 " Ferret is written and maintained by Greg Hurrell <greg@hurrell.net>.
 "
-" The idea for vim-dispatch integration was taken from Miles Sterrett's ack.vim
-" plug-in (https://github.com/mileszs/ack.vim).
-"
 " Other contributors that have submitted patches include (in alphabetical
 " order):
 "
 " - Daniel Silva
 " - Filip Szymański
 " - Joe Lencioni
+" - Jon Parise
 " - Nelo-Thara Wallus
 " - Tom Dooner
 " - Vaibhav Sagar
 "
 " ## master (not yet released)
 "
+" - Try to avoid "press ENTER to continue" prompts.
+" - Put search term in |w:quickfix_title| for use in statuslines
+"   (https://github.com/wincent/ferret/pull/57).
+" - Add |g:FerretExecutableArguments| and |ferret#get_default_arguments()|
+"   (https://github.com/wincent/ferret/pull/46).
+"
+" ## 3.0.3 (23 March 2018)
+"
+" - Fix for |:Lack| results opening in quickfix listing in Neovim
+"   (https://github.com/wincent/ferret/issues/47).
+"
+" ## 3.0.2 (25 October 2017)
+"
+" - Fix broken |:Back| and |:Black| commands
+"   (https://github.com/wincent/ferret/issues/48).
+"
+" ## 3.0.1 (24 August 2017)
+"
+" - Fix failure to handle search patterns containing multiple escaped spaces
+"   (https://github.com/wincent/ferret/issues/49).
+"
+" ## 3.0 (13 June 2017)
+"
+" - Improve handling of backslash escapes
+"   (https://github.com/wincent/ferret/issues/41).
+" - Add |g:FerretAutojump|.
+" - Drop support for vim-dispatch.
+"
+" ## 2.0 (6 June 2017)
+"
+" - Add support for Neovim, along with |g:FerretNvim| setting.
+"
+" ## 1.5 "Cinco de Cuatro" (4 May 2017)
+"
 " - Improvements to the handling of very large result sets (due to wide lines or
 "   many results).
 " - Added |g:FerretLazyInit|.
@@ -484,11 +514,6 @@ endif
 " 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.
 "
@@ -503,7 +528,7 @@ endif
 "
 " Likewise, {options} are passed through. In this example, we pass the `-w`
 " option (to search on word boundaries), and scope the search to the "foo" and
-" "bar" subdirectories: >
+" "bar" subdirectories:
 "
 " ```
 " :Ack -w something foo bar
@@ -519,7 +544,7 @@ endif
 " Like |:Ack|, but returns all results irrespective of the value of
 " |g:FerretMaxResults|.
 "
-command! -bang -nargs=+ -complete=customlist,ferret#private#ackcomplete Ack call ferret#private#ack(<bang>0, <f-args>)
+command! -bang -nargs=1 -complete=customlist,ferret#private#ackcomplete Ack call ferret#private#ack(<bang>0, <q-args>)
 
 ""
 " @command :Lack {pattern} {options}
@@ -528,15 +553,14 @@ command! -bang -nargs=+ -complete=customlist,ferret#private#ackcomplete Ack call
 " across an entire Vim instance, it uses the |location-list|, which is a
 " per-window construct.
 "
-" Note that |:Lack| always runs synchronously via |:cexpr|, because dispatch.vim
-" doesn't currently support the |location-list|.
+" Note that |:Lack| always runs synchronously via |:cexpr|.
 "
 " @command :Lack! {pattern} {options}
 "
 " Like |:Lack|, but returns all results irrespective of the value of
 " |g:FerretMaxResults|.
 "
-command! -bang -nargs=+ -complete=customlist,ferret#private#lackcomplete Lack call ferret#private#lack(<bang>0, <f-args>)
+command! -bang -nargs=1 -complete=customlist,ferret#private#lackcomplete Lack call ferret#private#lack(<bang>0, <q-args>)
 
 ""
 " @command :Back {pattern} {options}
@@ -552,7 +576,7 @@ command! -bang -nargs=+ -complete=customlist,ferret#private#lackcomplete Lack ca
 " Like |:Back|, but returns all results irrespective of the value of
 " |g:FerretMaxResults|.
 "
-command! -bang -nargs=+ -complete=customlist,ferret#private#backcomplete Back call ferret#private#back(<bang>0, <f-args>)
+command! -bang -nargs=1 -complete=customlist,ferret#private#backcomplete Back call ferret#private#back(<bang>0, <q-args>)
 
 ""
 " @command :Black {pattern} {options}
@@ -568,7 +592,7 @@ command! -bang -nargs=+ -complete=customlist,ferret#private#backcomplete Back ca
 " Like |:Black|, but returns all results irrespective of the value of
 " |g:FerretMaxResults|.
 "
-command! -bang -nargs=+ -complete=customlist,ferret#private#blackcomplete Black call ferret#private#black(<bang>0, <f-args>)
+command! -bang -nargs=1 -complete=customlist,ferret#private#blackcomplete Black call ferret#private#black(<bang>0, <q-args>)
 
 ""
 " @command :Acks /{pattern}/{replacement}/
@@ -586,6 +610,15 @@ command! -bang -nargs=+ -complete=customlist,ferret#private#blackcomplete Black
 " :Ack foo
 " :Acks /foo/bar/
 " ```
+"
+" The pattern and replacement are passed through literally to Vim's
+" |:substitute| command, preserving all characters and escapes,
+" including references to matches in the pattern. For example, the
+" following could be used to swap the order of "foo123" and "bar":
+"
+" ```
+" :Acks /\v(foo\d+)(bar)/\2\1/
+" ```
 command! -nargs=1 Acks call ferret#private#acks(<q-args>)
 command! FerretCancelAsync call ferret#private#async#cancel()
 command! FerretPullAsync call ferret#private#async#pull()