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

Commit: 
https://github.com/vim/vim/commit/aeb1c97db5b9de4f4903e7f288f2aa5ad6c49440
Author: Yegappan Lakshmanan <yegap...@yahoo.com>
Date:   Tue Oct 22 23:42:20 2024 +0200

    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 <yegap...@yahoo.com>
    Signed-off-by: Christian Brabandt <c...@256bit.org>

diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index e2c6967b5..80ccce8f6 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -1,4 +1,4 @@
-*eval.txt*     For Vim version 9.1.  Last change: 2024 Jul 28
+*eval.txt*     For Vim version 9.1.  Last change: 2024 Oct 22
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -2223,7 +2223,8 @@ v:fcs_choice      What should happen after a 
|FileChangedShell| event was
 
                                        *v:fname* *fname-variable*
 v:fname                When evaluating 'includeexpr': the file name that was
-               detected.  Empty otherwise.
+               detected.  When evaluating 'findexpr': the argument passed to
+               the |:find| command.  Empty otherwise.
 
                                        *v:fname_in* *fname_in-variable*
 v:fname_in     The name of the input file.  Valid while evaluating:
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index bc0e18379..5593523a0 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -3552,6 +3552,51 @@ A jump table for the options with a short description 
can be found at |Q_op|.
          eob           EndOfBuffer             |hl-EndOfBuffer|
          lastline      NonText                 |hl-NonText|
 
+                                               *'findexpr'* *'fexpr'*
+'findexpr' 'fexpr'     string  (default "")
+                       global or local to buffer |global-local|
+                       {not available when compiled without the |+eval|
+                       feature}
+       Expression that is evaluated to obtain the filename(s) for the |:find|
+       command.  When this option is empty, the internal |file-searching|
+       mechanism is used.
+
+       While evaluating the expression, the |v:fname| variable is set to the
+       argument of the |:find| command.
+
+       The expression is evaluated only once per |:find| command invocation.
+       The expression can process all the directories specified in 'path'.
+
+       If a match is found, the expression should return a |List| containing
+       one or more file names.  If a match is not found, the expression
+       should return an empty List.
+
+       If any errors are encountered during the expression evaluation, an
+       empty List is used as the return value.
+
+       Using a function call without arguments is faster |expr-option-function|
+
+       It is not allowed to change text or jump to another window while
+       evaluating 'findexpr' |textlock|.
+
+       This option cannot be set from a |modeline| or in the |sandbox|, for
+       security reasons.
+
+       Examples:
+>
+           " Use glob()
+           func FindExprGlob()
+               return glob(v:fname, v:false, v:true)
+           endfunc
+           set findexpr=FindExprGlob()
+
+           " Use the 'git ls-files' output
+           func FindGitFiles()
+               let fnames = systemlist('git ls-files')
+               return fnames->filter('v:val =~? v:fname')
+           endfunc
+           set findexpr=FindGitFiles()
+<
                *'fixendofline'* *'fixeol'* *'nofixendofline'* *'nofixeol'*
 'fixendofline' 'fixeol'        boolean (default on)
                        local to buffer
diff --git a/runtime/doc/quickref.txt b/runtime/doc/quickref.txt
index 42b5228a8..0f7453a9e 100644
--- a/runtime/doc/quickref.txt
+++ b/runtime/doc/quickref.txt
@@ -1,4 +1,4 @@
-*quickref.txt*  For Vim version 9.1.  Last change: 2024 Mar 03
+*quickref.txt*  For Vim version 9.1.  Last change: 2024 Oct 22
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -707,6 +707,7 @@ Short explanation of each option:           *option-list*
 'fileignorecase'  'fic'     ignore case when using file names
 'filetype'       'ft'      type of file, used for autocommands
 'fillchars'      'fcs'     characters to use for displaying special items
+'findexpr'       'fexpr'   expression to evaluate for |:find|
 'fixendofline'   'fixeol'  make sure last line in file has <EOL>
 'fkmap'                  'fk'      obsolete option for Farsi
 'foldclose'      'fcl'     close a fold when the cursor leaves it
diff --git a/runtime/doc/tags b/runtime/doc/tags
index f8359ac11..7a6e84c85 100644
--- a/runtime/doc/tags
+++ b/runtime/doc/tags
@@ -267,6 +267,7 @@ $quote      eval.txt        /*$quote*
 'fenc' options.txt     /*'fenc'*
 'fencs'        options.txt     /*'fencs'*
 'fex'  options.txt     /*'fex'*
+'fexpr'        options.txt     /*'fexpr'*
 'ff'   options.txt     /*'ff'*
 'ffs'  options.txt     /*'ffs'*
 'fic'  options.txt     /*'fic'*
@@ -277,6 +278,7 @@ $quote      eval.txt        /*$quote*
 'fileignorecase'       options.txt     /*'fileignorecase'*
 'filetype'     options.txt     /*'filetype'*
 'fillchars'    options.txt     /*'fillchars'*
+'findexpr'     options.txt     /*'findexpr'*
 'fixendofline' options.txt     /*'fixendofline'*
 'fixeol'       options.txt     /*'fixeol'*
 'fk'   options.txt     /*'fk'*
diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt
index 400ccd74a..af180a223 100644
--- a/runtime/doc/version9.txt
+++ b/runtime/doc/version9.txt
@@ -1,4 +1,4 @@
-*version9.txt*  For Vim version 9.1.  Last change: 2024 Oct 14
+*version9.txt*  For Vim version 9.1.  Last change: 2024 Oct 22
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -41649,6 +41649,8 @@ Options: ~
 
 'completeitemalign'    Order of |complete-items| in Insert mode completion
                        popup
+'findexpr'             Vim expression to obtain the results for a |:find|
+                       command
 'winfixbuf'            Keep buffer focused in a window
 'tabclose'             Which tab page to focus after closing a tab page
 't_xo'                 Terminal uses XON/XOFF handshaking (e.g. vt420)
diff --git a/src/buffer.c b/src/buffer.c
index 5359527c2..f1e133095 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -2412,6 +2412,7 @@ free_buf_options(
     clear_string_option(&buf->b_p_fp);
 #if defined(FEAT_EVAL)
     clear_string_option(&buf->b_p_fex);
+    clear_string_option(&buf->b_p_fexpr);
 #endif
 #ifdef FEAT_CRYPT
 # ifdef FEAT_SODIUM
diff --git a/src/ex_docmd.c b/src/ex_docmd.c
index 32cd7c560..9447a2d2e 100644
--- a/src/ex_docmd.c
+++ b/src/ex_docmd.c
@@ -6923,6 +6923,103 @@ ex_wrongmodifier(exarg_T *eap)
     eap->errmsg = ex_errmsg(e_invalid_command_str, eap->cmd);
 }
 
+#ifdef FEAT_EVAL
+/*
+ * Evaluate the 'findexpr' expression and return the result.  When evaluating
+ * the expression, v:fname is set to the ":find" command argument.
+ */
+    static list_T *
+eval_findexpr(char_u *ptr, int len)
+{
+    sctx_T     saved_sctx = current_sctx;
+    int                use_sandbox = FALSE;
+    char_u     *findexpr;
+    char_u     *arg;
+    typval_T   tv;
+    list_T     *retlist = NULL;
+
+    if (*curbuf->b_p_fexpr == NUL)
+    {
+       use_sandbox = was_set_insecurely((char_u *)"findexpr", OPT_GLOBAL);
+       findexpr = p_fexpr;
+    }
+    else
+    {
+       use_sandbox = was_set_insecurely((char_u *)"findexpr", OPT_LOCAL);
+       findexpr = curbuf->b_p_fexpr;
+    }
+
+    set_vim_var_string(VV_FNAME, ptr, len);
+    current_sctx = curbuf->b_p_script_ctx[BV_FEXPR];
+
+    arg = skipwhite(findexpr);
+
+    if (use_sandbox)
+       ++sandbox;
+    ++textlock;
+
+    // Evaluate the expression.  If the expression is "FuncName()" call the
+    // function directly.
+    if (eval0_simple_funccal(arg, &tv, NULL, &EVALARG_EVALUATE) == FAIL)
+       retlist = NULL;
+    else
+    {
+       if (tv.v_type == VAR_LIST)
+           retlist = list_copy(tv.vval.v_list, TRUE, TRUE, get_copyID());
+       clear_tv(&tv);
+    }
+    if (use_sandbox)
+       --sandbox;
+    --textlock;
+    clear_evalarg(&EVALARG_EVALUATE, NULL);
+
+    set_vim_var_string(VV_FNAME, NULL, 0);
+    current_sctx = saved_sctx;
+
+    return retlist;
+}
+
+/*
+ * Use 'findexpr' to find file 'findarg'.  The 'count' argument is used to find
+ * the n'th matching file.
+ */
+    static char_u *
+findexpr_find_file(char_u *findarg, int findarg_len, int count)
+{
+    list_T     *fname_list;
+    char_u     *ret_fname = NULL;
+    char_u     cc;
+    int                fname_count;
+
+    cc = findarg[findarg_len];
+    findarg[findarg_len] = NUL;
+
+    fname_list = eval_findexpr(findarg, findarg_len);
+    fname_count = list_len(fname_list);
+
+    if (fname_count == 0)
+       semsg(_(e_cant_find_file_str_in_path), findarg);
+    else
+    {
+       if (count > fname_count)
+           semsg(_(e_no_more_file_str_found_in_path), findarg);
+       else
+       {
+           listitem_T *li = list_find(fname_list, count - 1);
+           if (li != NULL && li->li_tv.v_type == VAR_STRING)
+               ret_fname = vim_strsave(li->li_tv.vval.v_string);
+       }
+    }
+
+    if (fname_list != NULL)
+       list_free(fname_list);
+
+    findarg[findarg_len] = cc;
+
+    return ret_fname;
+}
+#endif
+
 /*
  * :sview [+command] file      split window with new file, read-only
  * :split [[+command] file]    split window with current or new file
@@ -6972,11 +7069,22 @@ ex_splitview(exarg_T *eap)
     {
        char_u  *file_to_find = NULL;
        char    *search_ctx = NULL;
-       fname = find_file_in_path(eap->arg, (int)STRLEN(eap->arg),
-                                         FNAME_MESS, TRUE, curbuf->b_ffname,
-                                         &file_to_find, &search_ctx);
-       vim_free(file_to_find);
-       vim_findfile_cleanup(search_ctx);
+
+       if (*get_findexpr() != NUL)
+       {
+#ifdef FEAT_EVAL
+           fname = findexpr_find_file(eap->arg, (int)STRLEN(eap->arg),
+                                      eap->addr_count > 0 ? eap->line2 : 1);
+#endif
+       }
+       else
+       {
+           fname = find_file_in_path(eap->arg, (int)STRLEN(eap->arg),
+                                             FNAME_MESS, TRUE, 
curbuf->b_ffname,
+                                             &file_to_find, &search_ctx);
+           vim_free(file_to_find);
+           vim_findfile_cleanup(search_ctx);
+       }
        if (fname == NULL)
            goto theend;
        eap->arg = fname;
@@ -7241,27 +7349,37 @@ ex_find(exarg_T *eap)
     if (!check_can_set_curbuf_forceit(eap->forceit))
        return;
 
-    char_u     *fname;
+    char_u     *fname = NULL;
     int                count;
     char_u     *file_to_find = NULL;
     char       *search_ctx = NULL;
 
-    fname = find_file_in_path(eap->arg, (int)STRLEN(eap->arg), FNAME_MESS,
-                          TRUE, curbuf->b_ffname, &file_to_find, &search_ctx);
-    if (eap->addr_count > 0)
+    if (*get_findexpr() != NUL)
+    {
+#ifdef FEAT_EVAL
+       fname = findexpr_find_file(eap->arg, (int)STRLEN(eap->arg),
+                                       eap->addr_count > 0 ? eap->line2 : 1);
+#endif
+    }
+    else
     {
-       // Repeat finding the file "count" times.  This matters when it appears
-       // several times in the path.
-       count = eap->line2;
-       while (fname != NULL && --count > 0)
+       fname = find_file_in_path(eap->arg, (int)STRLEN(eap->arg), FNAME_MESS,
+                              TRUE, curbuf->b_ffname, &file_to_find, 
&search_ctx);
+       if (eap->addr_count > 0)
        {
-           vim_free(fname);
-           fname = find_file_in_path(NULL, 0, FNAME_MESS,
-                         FALSE, curbuf->b_ffname, &file_to_find, &search_ctx);
+           // Repeat finding the file "count" times.  This matters when it 
appears
+           // several times in the path.
+           count = eap->line2;
+           while (fname != NULL && --count > 0)
+           {
+               vim_free(fname);
+               fname = find_file_in_path(NULL, 0, FNAME_MESS,
+                             FALSE, curbuf->b_ffname, &file_to_find, 
&search_ctx);
+           }
        }
+       VIM_CLEAR(file_to_find);
+       vim_findfile_cleanup(search_ctx);
     }
-    VIM_CLEAR(file_to_find);
-    vim_findfile_cleanup(search_ctx);
 
     if (fname == NULL)
        return;
diff --git a/src/option.c b/src/option.c
index 84c469ea6..e82a79b6b 100644
--- a/src/option.c
+++ b/src/option.c
@@ -6313,6 +6313,11 @@ unset_global_local_option(char_u *name, void *from)
        case PV_FP:
            clear_string_option(&buf->b_p_fp);
            break;
+# ifdef FEAT_EVAL
+       case PV_FEXPR:
+           clear_string_option(&buf->b_p_fexpr);
+           break;
+# endif
 # ifdef FEAT_QUICKFIX
        case PV_EFM:
            clear_string_option(&buf->b_p_efm);
@@ -6391,6 +6396,9 @@ get_varp_scope(struct vimoption *p, int scope)
        switch ((int)p->indir)
        {
            case PV_FP:   return (char_u *)&(curbuf->b_p_fp);
+#ifdef FEAT_EVAL
+           case PV_FEXPR:   return (char_u *)&(curbuf->b_p_fexpr);
+#endif
 #ifdef FEAT_QUICKFIX
            case PV_EFM:  return (char_u *)&(curbuf->b_p_efm);
            case PV_GP:   return (char_u *)&(curbuf->b_p_gp);
@@ -6501,6 +6509,10 @@ get_varp(struct vimoption *p)
 #endif
        case PV_FP:     return *curbuf->b_p_fp != NUL
                                    ? (char_u *)&(curbuf->b_p_fp) : p->var;
+#ifdef FEAT_EVAL
+       case PV_FEXPR:  return *curbuf->b_p_fexpr != NUL
+                                   ? (char_u *)&curbuf->b_p_fexpr : p->var;
+#endif
 #ifdef FEAT_QUICKFIX
        case PV_EFM:    return *curbuf->b_p_efm != NUL
                                    ? (char_u *)&(curbuf->b_p_efm) : p->var;
@@ -6747,6 +6759,21 @@ get_equalprg(void)
     return curbuf->b_p_ep;
 }
 
+/*
+ * Get the value of 'findexpr', either the buffer-local one or the global one.
+ */
+    char_u *
+get_findexpr(void)
+{
+#ifdef FEAT_EVAL
+    if (*curbuf->b_p_fexpr == NUL)
+       return p_fexpr;
+    return curbuf->b_p_fexpr;
+#else
+    return (char_u *)"";
+#endif
+}
+
 /*
  * Copy options from one window to another.
  * Used when splitting a window.
@@ -7275,6 +7302,10 @@ buf_copy_options(buf_T *buf, int flags)
            buf->b_p_efm = empty_option;
 #endif
            buf->b_p_ep = empty_option;
+#if defined(FEAT_EVAL)
+           buf->b_p_fexpr = vim_strsave(p_fexpr);
+           COPY_OPT_SCTX(buf, BV_FEXPR);
+#endif
            buf->b_p_kp = empty_option;
            buf->b_p_path = empty_option;
            buf->b_p_tags = empty_option;
diff --git a/src/option.h b/src/option.h
index ef39031aa..641514d21 100644
--- a/src/option.h
+++ b/src/option.h
@@ -596,6 +596,9 @@ EXTERN char_u       *p_ffs;         // 'fileformats'
 EXTERN int     p_fic;          // 'fileignorecase'
 EXTERN char_u  *p_ft;          // 'filetype'
 EXTERN char_u  *p_fcs;         // 'fillchar'
+#ifdef FEAT_EVAL
+EXTERN char_u  *p_fexpr;       // 'findexpr'
+#endif
 EXTERN int     p_fixeol;       // 'fixendofline'
 #ifdef FEAT_FOLDING
 EXTERN char_u  *p_fcl;         // 'foldclose'
@@ -1168,6 +1171,7 @@ enum
     , BV_EP
     , BV_ET
     , BV_FENC
+    , BV_FEXPR
     , BV_FP
 #ifdef FEAT_EVAL
     , BV_BEXPR
diff --git a/src/optiondefs.h b/src/optiondefs.h
index ccc527e08..96c30e283 100644
--- a/src/optiondefs.h
+++ b/src/optiondefs.h
@@ -74,6 +74,7 @@
 #define PV_FP          OPT_BOTH(OPT_BUF(BV_FP))
 #ifdef FEAT_EVAL
 # define PV_FEX                OPT_BUF(BV_FEX)
+# define PV_FEXPR      OPT_BOTH(OPT_BUF(BV_FEXPR))
 #endif
 #define PV_FF          OPT_BUF(BV_FF)
 #define PV_FLP         OPT_BUF(BV_FLP)
@@ -958,6 +959,15 @@ static struct vimoption options[] =
                            {(char_u *)"vert:|,fold:-,eob:~,lastline:@",
                                                                  (char_u *)0L}
                            SCTX_INIT},
+    {"findexpr",   "fexpr", P_STRING|P_ALLOCED|P_VI_DEF|P_VIM|P_SECURE,
+#if defined(FEAT_EVAL)
+                           (char_u *)&p_fexpr, PV_FEXPR, did_set_optexpr, NULL,
+                           {(char_u *)"", (char_u *)0L}
+#else
+                           (char_u *)NULL, PV_NONE, NULL, NULL,
+                           {(char_u *)0L, (char_u *)0L}
+#endif
+                           SCTX_INIT},
     {"fixendofline",  "fixeol", P_BOOL|P_VI_DEF|P_RSTAT,
                            (char_u *)&p_fixeol, PV_FIXEOL,
                            did_set_eof_eol_fixeol_bomb, NULL,
diff --git a/src/optionstr.c b/src/optionstr.c
index c45b3bce7..d82b8e116 100644
--- a/src/optionstr.c
+++ b/src/optionstr.c
@@ -324,6 +324,9 @@ check_buf_options(buf_T *buf)
     check_string_option(&buf->b_p_efm);
 #endif
     check_string_option(&buf->b_p_ep);
+#ifdef FEAT_EVAL
+    check_string_option(&buf->b_p_fexpr);
+#endif
     check_string_option(&buf->b_p_path);
     check_string_option(&buf->b_p_tags);
     check_string_option(&buf->b_p_tc);
@@ -3132,8 +3135,8 @@ expand_set_nrformats(optexpand_T *args, int *numMatches, 
char_u ***matches)
 #if defined(FEAT_EVAL) || defined(PROTO)
 /*
  * One of the '*expr' options is changed: 'balloonexpr', 'diffexpr',
- * 'foldexpr', 'foldtext', 'formatexpr', 'includeexpr', 'indentexpr',
- * 'patchexpr', 'printexpr' and 'charconvert'.
+ * 'findexpr', 'foldexpr', 'foldtext', 'formatexpr', 'includeexpr',
+ * 'indentexpr', 'patchexpr', 'printexpr' and 'charconvert'.
  *
  */
     char *
diff --git a/src/proto/option.pro b/src/proto/option.pro
index db92bf253..fce919918 100644
--- a/src/proto/option.pro
+++ b/src/proto/option.pro
@@ -120,6 +120,7 @@ char_u *get_option_var(int opt_idx);
 char_u *get_option_fullname(int opt_idx);
 opt_did_set_cb_T get_option_did_set_cb(int opt_idx);
 char_u *get_equalprg(void);
+char_u *get_findexpr(void);
 void win_copy_options(win_T *wp_from, win_T *wp_to);
 void after_copy_winopt(win_T *wp);
 void copy_winopt(winopt_T *from, winopt_T *to);
diff --git a/src/structs.h b/src/structs.h
index e4a79f585..39e60a42e 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -3327,6 +3327,9 @@ struct file_buffer
     char_u     *b_p_efm;       // 'errorformat' local value
 #endif
     char_u     *b_p_ep;        // 'equalprg' local value
+#ifdef FEAT_EVAL
+    char_u     *b_p_fexpr;     // 'findexpr' local value
+#endif
     char_u     *b_p_path;      // 'path' local value
     int                b_p_ar;         // 'autoread' local value
     char_u     *b_p_tags;      // 'tags' local value
diff --git a/src/testdir/test_findfile.vim b/src/testdir/test_findfile.vim
index a5e18b957..f8be7136e 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 @@ func Test_find_non_existing_path()
   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("
")
+    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 0a7240b89..bb5bc6b51 100644
--- a/src/testdir/test_modeline.vim
+++ b/src/testdir/test_modeline.vim
@@ -208,6 +208,7 @@ func Test_modeline_fails_always()
   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 73e6d850c..35b7d48de 100644
--- a/src/testdir/test_options.vim
+++ b/src/testdir/test_options.vim
@@ -1570,7 +1570,7 @@ endfunc
 
 " 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
diff --git a/src/version.c b/src/version.c
index f97eb8c2c..c60a7e9a1 100644
--- a/src/version.c
+++ b/src/version.c
@@ -704,6 +704,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    810,
 /**/
     809,
 /**/

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to vim_dev+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/E1t3Mv5-000X9R-R8%40256bit.org.

Raspunde prin e-mail lui