patch 9.1.0810: cannot easily adjust the |:find| command

Problem:  cannot easily adjust the |:find| command
Solution: Add support for the 'findexpr' option (Yegappan Lakshmanan)

closes: #15901
closes: #15905

Signed-off-by: Yegappan Lakshmanan <yegappan@yahoo.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
diff --git a/src/testdir/test_findfile.vim b/src/testdir/test_findfile.vim
index a5e18b9..f8be713 100644
--- a/src/testdir/test_findfile.vim
+++ b/src/testdir/test_findfile.vim
@@ -1,5 +1,7 @@
 " Test findfile() and finddir()
 
+source check.vim
+
 let s:files = [ 'Xfinddir1/foo',
       \         'Xfinddir1/bar',
       \         'Xfinddir1/Xdir2/foo',
@@ -281,4 +283,170 @@
   let &path = save_path
 endfunc
 
+" Test for 'findexpr'
+func Test_findexpr()
+  CheckUnix
+  call assert_equal('', &findexpr)
+  call writefile(['aFile'], 'Xfindexpr1.c', 'D')
+  call writefile(['bFile'], 'Xfindexpr2.c', 'D')
+  call writefile(['cFile'], 'Xfindexpr3.c', 'D')
+
+  " basic tests
+  func FindExpr1()
+    let fnames = ['Xfindexpr1.c', 'Xfindexpr2.c', 'Xfindexpr3.c']
+    "return fnames->copy()->filter('v:val =~? v:fname')->join("\n")
+    return fnames->copy()->filter('v:val =~? v:fname')
+  endfunc
+
+  set findexpr=FindExpr1()
+  find Xfindexpr3
+  call assert_match('Xfindexpr3.c', @%)
+  bw!
+  2find Xfind
+  call assert_match('Xfindexpr2.c', @%)
+  bw!
+  call assert_fails('4find Xfind', 'E347: No more file "Xfind" found in path')
+  call assert_fails('find foobar', 'E345: Can''t find file "foobar" in path')
+
+  sfind Xfindexpr2.c
+  call assert_match('Xfindexpr2.c', @%)
+  call assert_equal(2, winnr('$'))
+  %bw!
+  call assert_fails('sfind foobar', 'E345: Can''t find file "foobar" in path')
+
+  tabfind Xfindexpr3.c
+  call assert_match('Xfindexpr3.c', @%)
+  call assert_equal(2, tabpagenr())
+  %bw!
+  call assert_fails('tabfind foobar', 'E345: Can''t find file "foobar" in path')
+
+  " Buffer-local option
+  set findexpr=['abc']
+  new
+  setlocal findexpr=['def']
+  find xxxx
+  call assert_equal('def', @%)
+  wincmd w
+  find xxxx
+  call assert_equal('abc', @%)
+  aboveleft new
+  call assert_equal("['abc']", &findexpr)
+  wincmd k
+  aboveleft new
+  call assert_equal("['abc']", &findexpr)
+  %bw!
+
+  " Empty list
+  set findexpr=[]
+  call assert_fails('find xxxx', 'E345: Can''t find file "xxxx" in path')
+
+  " Error cases
+
+  " Syntax error in the expression
+  set findexpr=FindExpr1{}
+  call assert_fails('find Xfindexpr1.c', 'E15: Invalid expression')
+
+  " Find expression throws an error
+  func FindExpr2()
+    throw 'find error'
+  endfunc
+  set findexpr=FindExpr2()
+  call assert_fails('find Xfindexpr1.c', 'find error')
+
+  " Try using a null string as the expression
+  set findexpr=test_null_string()
+  call assert_fails('find Xfindexpr1.c', 'E345: Can''t find file "Xfindexpr1.c" in path')
+
+  " Try to create a new window from the find expression
+  func FindExpr3()
+    new
+    return ["foo"]
+  endfunc
+  set findexpr=FindExpr3()
+  call assert_fails('find Xfindexpr1.c', 'E565: Not allowed to change text or change window')
+
+  " Try to modify the current buffer from the find expression
+  func FindExpr4()
+    call setline(1, ['abc'])
+    return ["foo"]
+  endfunc
+  set findexpr=FindExpr4()
+  call assert_fails('find Xfindexpr1.c', 'E565: Not allowed to change text or change window')
+
+  set findexpr&
+  delfunc! FindExpr1
+  delfunc! FindExpr2
+  delfunc! FindExpr3
+  delfunc! FindExpr4
+endfunc
+
+" Test for using a script-local function for 'findexpr'
+func Test_findexpr_scriptlocal_func()
+  func! s:FindExprScript()
+    let g:FindExprArg = v:fname
+    return ['xxx']
+  endfunc
+
+  set findexpr=s:FindExprScript()
+  call assert_equal(expand('<SID>') .. 'FindExprScript()', &findexpr)
+  call assert_equal(expand('<SID>') .. 'FindExprScript()', &g:findexpr)
+  new | only
+  let g:FindExprArg = ''
+  find abc
+  call assert_equal('abc', g:FindExprArg)
+  bw!
+
+  set findexpr=<SID>FindExprScript()
+  call assert_equal(expand('<SID>') .. 'FindExprScript()', &findexpr)
+  call assert_equal(expand('<SID>') .. 'FindExprScript()', &g:findexpr)
+  new | only
+  let g:FindExprArg = ''
+  find abc
+  call assert_equal('abc', g:FindExprArg)
+  bw!
+
+  let &findexpr = 's:FindExprScript()'
+  call assert_equal(expand('<SID>') .. 'FindExprScript()', &g:findexpr)
+  new | only
+  let g:FindExprArg = ''
+  find abc
+  call assert_equal('abc', g:FindExprArg)
+  bw!
+
+  let &findexpr = '<SID>FindExprScript()'
+  call assert_equal(expand('<SID>') .. 'FindExprScript()', &g:findexpr)
+  new | only
+  let g:FindExprArg = ''
+  find abc
+  call assert_equal('abc', g:FindExprArg)
+  bw!
+
+  set findexpr=
+  setglobal findexpr=s:FindExprScript()
+  setlocal findexpr=
+  call assert_equal(expand('<SID>') .. 'FindExprScript()', &findexpr)
+  call assert_equal(expand('<SID>') .. 'FindExprScript()', &g:findexpr)
+  call assert_equal('', &l:findexpr)
+  new | only
+  let g:FindExprArg = ''
+  find abc
+  call assert_equal('abc', g:FindExprArg)
+  bw!
+
+  new | only
+  set findexpr=
+  setglobal findexpr=
+  setlocal findexpr=s:FindExprScript()
+  call assert_equal(expand('<SID>') .. 'FindExprScript()', &findexpr)
+  call assert_equal(expand('<SID>') .. 'FindExprScript()', &l:findexpr)
+  call assert_equal('', &g:findexpr)
+  let g:FindExprArg = ''
+  find abc
+  call assert_equal('abc', g:FindExprArg)
+  bw!
+
+  set findexpr=
+  delfunc s:FindExprScript
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_modeline.vim b/src/testdir/test_modeline.vim
index 0a7240b..bb5bc6b 100644
--- a/src/testdir/test_modeline.vim
+++ b/src/testdir/test_modeline.vim
@@ -208,6 +208,7 @@
   call s:modeline_fails('equalprg', 'equalprg=Something()', 'E520:')
   call s:modeline_fails('errorfile', 'errorfile=Something()', 'E520:')
   call s:modeline_fails('exrc', 'exrc=Something()', 'E520:')
+  call s:modeline_fails('findexpr', 'findexpr=Something()', 'E520:')
   call s:modeline_fails('formatprg', 'formatprg=Something()', 'E520:')
   call s:modeline_fails('fsync', 'fsync=Something()', 'E520:')
   call s:modeline_fails('grepprg', 'grepprg=Something()', 'E520:')
diff --git a/src/testdir/test_options.vim b/src/testdir/test_options.vim
index 73e6d85..35b7d48 100644
--- a/src/testdir/test_options.vim
+++ b/src/testdir/test_options.vim
@@ -1570,7 +1570,7 @@
 
 " Test for changing options in a sandbox
 func Test_opt_sandbox()
-  for opt in ['backupdir', 'cdpath', 'exrc']
+  for opt in ['backupdir', 'cdpath', 'exrc', 'findexpr']
     call assert_fails('sandbox set ' .. opt .. '?', 'E48:')
     call assert_fails('sandbox let &' .. opt .. ' = 1', 'E48:')
   endfor