changeset 36447:fb5b2d1f2439 draft v9.1.0831

patch 9.1.0831: 'findexpr' can't be used as lambad or Funcref Commit: https://github.com/vim/vim/commit/a13f3a4f5de9c150f70298850e34747838904995 Author: Yegappan Lakshmanan <yegappan@yahoo.com> Date: Sat Nov 2 18:40:10 2024 +0100 patch 9.1.0831: 'findexpr' can't be used as lambad or Funcref Problem: 'findexpr' can't be used for lambads (Justin Keyes) Solution: Replace the findexpr option with the findfunc option (Yegappan Lakshmanan) related: #15905 closes: #15976 Signed-off-by: Yegappan Lakshmanan <yegappan@yahoo.com> Signed-off-by: Christian Brabandt <cb@256bit.org>
author Christian Brabandt <cb@256bit.org>
date Sat, 02 Nov 2024 18:45:23 +0100
parents efc16615e72a
children 3363588b46a2
files runtime/doc/eval.txt runtime/doc/options.txt runtime/doc/quickref.txt runtime/doc/tags runtime/doc/version9.txt src/buffer.c src/cmdexpand.c src/errors.h src/evalbuffer.c src/evalvars.c src/ex_docmd.c src/gc.c src/option.c src/option.h src/optiondefs.h src/optionstr.c src/proto/ex_docmd.pro src/proto/option.pro src/structs.h src/testdir/test_findfile.vim src/testdir/test_modeline.vim src/testdir/test_options.vim src/testdir/test_vim9_import.vim src/version.c src/vim.h
diffstat 25 files changed, 639 insertions(+), 266 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -1,4 +1,4 @@
-*eval.txt*	For Vim version 9.1.  Last change: 2024 Oct 28
+*eval.txt*	For Vim version 9.1.  Last change: 2024 Nov 02
 
 
 		  VIM REFERENCE MANUAL	  by Bram Moolenaar
@@ -2027,10 +2027,6 @@ v:cmdbang	Set like v:cmdarg for a file r
 		can only be used in autocommands.  For user commands |<bang>|
 		can be used.
 
-					*v:cmdcomplete* *cmdcomplete-variable*
-v:cmdcomplete	When evaluating 'findexpr': if 'findexpr' is used for cmdline
-		completion the value is |v:true|, otherwise it is |v:false|.
-
 						*v:collate* *collate-variable*
 v:collate	The current locale setting for collation order of the runtime
 		environment.  This allows Vim scripts to be aware of the
@@ -2228,8 +2224,7 @@ v:fcs_choice	What should happen after a 
 
 					*v:fname* *fname-variable*
 v:fname		When evaluating 'includeexpr': the file name that was
-		detected.  When evaluating 'findexpr': the argument passed to
-		the |:find| command.  Empty otherwise.
+		detected.  Empty otherwise.
 
 					*v:fname_in* *fname_in-variable*
 v:fname_in	The name of the input file.  Valid while evaluating:
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -1,4 +1,4 @@
-*options.txt*	For Vim version 9.1.  Last change: 2024 Oct 28
+*options.txt*	For Vim version 9.1.  Last change: 2024 Nov 02
 
 
 		  VIM REFERENCE MANUAL	  by Bram Moolenaar
@@ -439,10 +439,11 @@ Note: In the future more global options 
 ":setlocal" on a global option might work differently then.
 
 						*option-value-function*
-Some options ('completefunc', 'imactivatefunc', 'imstatusfunc', 'omnifunc',
-'operatorfunc', 'quickfixtextfunc', 'tagfunc' and 'thesaurusfunc') are set to
-a function name or a function reference or a lambda function.  When using a
-lambda it will be converted to the name, e.g. "<lambda>123".  Examples:
+Some options ('completefunc', 'findfunc', 'imactivatefunc', 'imstatusfunc',
+'omnifunc', 'operatorfunc', 'quickfixtextfunc', 'tagfunc' and 'thesaurusfunc')
+are set to a function name or a function reference or a lambda function.  When
+using a lambda it will be converted to the name, e.g. "<lambda>123".
+Examples:
 >
 	set opfunc=MyOpFunc
 	set opfunc=function('MyOpFunc')
@@ -3552,36 +3553,36 @@ A jump table for the options with a shor
 	  eob		EndOfBuffer		|hl-EndOfBuffer|
 	  lastline	NonText			|hl-NonText|
 
-						*'findexpr'* *'fexpr'* *E1514*
-'findexpr' 'fexpr'	string	(default "")
+						*'findfunc'* *'ffu'* *E1514*
+'findfunc' 'ffu'	string	(default empty)
 			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|
+	Function that is called 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'.
-
-	The expression may be evaluated for command-line completion as well,
-	in which case the |v:cmdcomplete| variable will be set to |v:true|,
-	otherwise it will be set to |v:false|.
-
-	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
+	The value can be the name of a function, a |lambda| or a |Funcref|.
+	See |option-value-function| for more information.
+
+	The function is called with two arguments.  The first argument is a
+	|String| and is the |:find| command argument.  The second argument is
+	a |Boolean| and is set to |v:true| when the function is called to get
+	a List of command-line completion matches for the |:find| command.
+	The function should return a List of strings.
+
+	The function is called only once per |:find| command invocation.
+	The function can process all the directories specified in 'path'.
+
+	If a match is found, the function should return a |List| containing
+	one or more file names.  If a match is not found, the function
 	should return an empty List.
 
-	If any errors are encountered during the expression evaluation, an
+	If any errors are encountered during the function invocation, 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|.
+	executing the 'findfunc' |textlock|.
 
 	This option cannot be set from a |modeline| or in the |sandbox|, for
 	security reasons.
@@ -3589,18 +3590,18 @@ A jump table for the options with a shor
 	Examples:
 >
 	    " Use glob()
-	    func FindExprGlob()
-		let pat = v:cmdcomplete ? $'{v:fname}*' : v:fname
+	    func FindFuncGlob(cmdarg, cmdcomplete)
+		let pat = a:cmdcomplete ? $'{a:cmdarg}*' : a:cmdarg
 		return glob(pat, v:false, v:true)
 	    endfunc
-	    set findexpr=FindExprGlob()
+	    set findfunc=FindFuncGlob
 
 	    " Use the 'git ls-files' output
-	    func FindGitFiles()
+	    func FindGitFiles(cmdarg, cmdcomplete)
 		let fnames = systemlist('git ls-files')
-		return fnames->filter('v:val =~? v:fname')
+		return fnames->filter('v:val =~? a:cmdarg')
 	    endfunc
-	    set findexpr=FindGitFiles()
+	    set findfunc=FindGitFiles
 <
 		*'fixendofline'* *'fixeol'* *'nofixendofline'* *'nofixeol'*
 'fixendofline' 'fixeol'	boolean	(default on)
--- a/runtime/doc/quickref.txt
+++ b/runtime/doc/quickref.txt
@@ -1,4 +1,4 @@
-*quickref.txt*  For Vim version 9.1.  Last change: 2024 Oct 22
+*quickref.txt*  For Vim version 9.1.  Last change: 2024 Nov 02
 
 
 		  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -707,7 +707,7 @@ Short explanation of each option:		*opti
 '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|
+'findfunc'	  'ffu'     function to be called for the |:find| command
 '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
--- a/runtime/doc/tags
+++ b/runtime/doc/tags
@@ -267,9 +267,9 @@
 'fenc'	options.txt	/*'fenc'*
 'fencs'	options.txt	/*'fencs'*
 'fex'	options.txt	/*'fex'*
-'fexpr'	options.txt	/*'fexpr'*
 'ff'	options.txt	/*'ff'*
 'ffs'	options.txt	/*'ffs'*
+'ffu'	options.txt	/*'ffu'*
 'fic'	options.txt	/*'fic'*
 'fileencoding'	options.txt	/*'fileencoding'*
 'fileencodings'	options.txt	/*'fileencodings'*
@@ -278,7 +278,7 @@
 'fileignorecase'	options.txt	/*'fileignorecase'*
 'filetype'	options.txt	/*'filetype'*
 'fillchars'	options.txt	/*'fillchars'*
-'findexpr'	options.txt	/*'findexpr'*
+'findfunc'	options.txt	/*'findfunc'*
 'fixendofline'	options.txt	/*'fixendofline'*
 'fixeol'	options.txt	/*'fixeol'*
 'fk'	options.txt	/*'fk'*
@@ -6510,7 +6510,6 @@ close_cb	channel.txt	/*close_cb*
 closure	eval.txt	/*closure*
 cmdarg-variable	eval.txt	/*cmdarg-variable*
 cmdbang-variable	eval.txt	/*cmdbang-variable*
-cmdcomplete-variable	eval.txt	/*cmdcomplete-variable*
 cmdline-arguments	vi_diff.txt	/*cmdline-arguments*
 cmdline-changed	version5.txt	/*cmdline-changed*
 cmdline-completion	cmdline.txt	/*cmdline-completion*
@@ -10942,7 +10941,6 @@ v:charconvert_from	eval.txt	/*v:charconv
 v:charconvert_to	eval.txt	/*v:charconvert_to*
 v:cmdarg	eval.txt	/*v:cmdarg*
 v:cmdbang	eval.txt	/*v:cmdbang*
-v:cmdcomplete	eval.txt	/*v:cmdcomplete*
 v:collate	eval.txt	/*v:collate*
 v:colornames	eval.txt	/*v:colornames*
 v:completed_item	eval.txt	/*v:completed_item*
--- a/runtime/doc/version9.txt
+++ b/runtime/doc/version9.txt
@@ -41652,7 +41652,7 @@ Options: ~
 
 'completeitemalign'	Order of |complete-items| in Insert mode completion
 			popup
-'findexpr'		Vim expression to obtain the results for a |:find|
+'findfunc'		Vim function 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
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -2412,7 +2412,6 @@ 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
@@ -2485,6 +2484,8 @@ free_buf_options(
 #ifdef FEAT_EVAL
     clear_string_option(&buf->b_p_tfu);
     free_callback(&buf->b_tfu_cb);
+    clear_string_option(&buf->b_p_ffu);
+    free_callback(&buf->b_ffu_cb);
 #endif
     clear_string_option(&buf->b_p_dict);
     clear_string_option(&buf->b_p_tsr);
--- a/src/cmdexpand.c
+++ b/src/cmdexpand.c
@@ -50,7 +50,7 @@ cmdline_fuzzy_completion_supported(expan
 	    && xp->xp_context != EXPAND_FILES
 	    && xp->xp_context != EXPAND_FILES_IN_PATH
 	    && xp->xp_context != EXPAND_FILETYPE
-	    && xp->xp_context != EXPAND_FINDEXPR
+	    && xp->xp_context != EXPAND_FINDFUNC
 	    && xp->xp_context != EXPAND_HELP
 	    && xp->xp_context != EXPAND_KEYMAP
 	    && xp->xp_context != EXPAND_OLD_SETTING
@@ -1419,7 +1419,7 @@ addstar(
 
 	// For help tags the translation is done in find_help_tags().
 	// For a tag pattern starting with "/" no translation is needed.
-	if (context == EXPAND_FINDEXPR
+	if (context == EXPAND_FINDFUNC
 		|| context == EXPAND_HELP
 		|| context == EXPAND_COLORS
 		|| context == EXPAND_COMPILER
@@ -2140,7 +2140,7 @@ set_context_by_cmdname(
 	case CMD_sfind:
 	case CMD_tabfind:
 	    if (xp->xp_context == EXPAND_FILES)
-		xp->xp_context = *get_findexpr() != NUL ? EXPAND_FINDEXPR
+		xp->xp_context = *get_findfunc() != NUL ? EXPAND_FINDFUNC
 							: EXPAND_FILES_IN_PATH;
 	    break;
 	case CMD_cd:
@@ -2853,10 +2853,10 @@ expand_files_and_dirs(
 	    }
     }
 
-    if (xp->xp_context == EXPAND_FINDEXPR)
+    if (xp->xp_context == EXPAND_FINDFUNC)
     {
 #ifdef FEAT_EVAL
-	ret = expand_findexpr(pat, matches, numMatches);
+	ret = expand_findfunc(pat, matches, numMatches);
 #endif
     }
     else
@@ -3122,7 +3122,7 @@ ExpandFromContext(
     if (xp->xp_context == EXPAND_FILES
 	    || xp->xp_context == EXPAND_DIRECTORIES
 	    || xp->xp_context == EXPAND_FILES_IN_PATH
-	    || xp->xp_context == EXPAND_FINDEXPR
+	    || xp->xp_context == EXPAND_FINDFUNC
 	    || xp->xp_context == EXPAND_DIRS_IN_CDPATH)
 	return expand_files_and_dirs(xp, pat, matches, numMatches, flags,
 								options);
--- a/src/errors.h
+++ b/src/errors.h
@@ -3652,5 +3652,5 @@ EXTERN char e_wrong_character_width_for_
 	INIT(= N_("E1512: Wrong character width for field \"%s\""));
 EXTERN char e_winfixbuf_cannot_go_to_buffer[]
 	INIT(= N_("E1513: Cannot switch buffer. 'winfixbuf' is enabled"));
-EXTERN char e_invalid_return_type_from_findexpr[]
-	INIT(= N_("E1514: 'findexpr' did not return a List type"));
+EXTERN char e_invalid_return_type_from_findfunc[]
+	INIT(= N_("E1514: 'findfunc' did not return a List type"));
--- a/src/evalbuffer.c
+++ b/src/evalbuffer.c
@@ -45,6 +45,8 @@ set_ref_in_buffers(int copyID)
 #endif
 	if (!abort)
 	    abort = abort || set_ref_in_callback(&bp->b_tfu_cb, copyID);
+	if (!abort)
+	    abort = abort || set_ref_in_callback(&bp->b_ffu_cb, copyID);
 	if (abort)
 	    break;
     }
--- a/src/evalvars.c
+++ b/src/evalvars.c
@@ -160,8 +160,7 @@ static struct vimvar
     {VV_NAME("python3_version",	 VAR_NUMBER), NULL, VV_RO},
     {VV_NAME("t_typealias",	 VAR_NUMBER), NULL, VV_RO},
     {VV_NAME("t_enum",		 VAR_NUMBER), NULL, VV_RO},
-    {VV_NAME("t_enumvalue",	 VAR_NUMBER), NULL, VV_RO},
-    {VV_NAME("cmdcomplete",	 VAR_BOOL), NULL, VV_RO},
+    {VV_NAME("t_enumvalue",	 VAR_NUMBER), NULL, VV_RO}
 };
 
 // shorthand
@@ -235,7 +234,6 @@ evalvars_init(void)
     set_vim_var_nr(VV_SEARCHFORWARD, 1L);
     set_vim_var_nr(VV_HLSEARCH, 1L);
     set_vim_var_nr(VV_EXITING, VVAL_NULL);
-    set_vim_var_nr(VV_CMDCOMPLETE, VVAL_FALSE);
     set_vim_var_dict(VV_COMPLETED_ITEM, dict_alloc_lock(VAR_FIXED));
     set_vim_var_list(VV_ERRORS, list_alloc());
     set_vim_var_dict(VV_EVENT, dict_alloc_lock(VAR_FIXED));
--- a/src/ex_docmd.c
+++ b/src/ex_docmd.c
@@ -6924,58 +6924,71 @@ ex_wrongmodifier(exarg_T *eap)
 }
 
 #if defined(FEAT_EVAL) || defined(PROTO)
-/*
- * Evaluate the 'findexpr' expression and return the result.  When evaluating
- * the expression, v:fname is set to the ":find" command argument.
- */
+
+// callback function for 'findfunc'
+static callback_T ffu_cb;
+
+    static callback_T *
+get_findfunc_callback(void)
+{
+    return *curbuf->b_p_ffu != NUL ? &curbuf->b_ffu_cb : &ffu_cb;
+}
+
     static list_T *
-eval_findexpr(char_u *pat, int cmdcomplete)
-{
+call_findfunc(char_u *pat, int cmdcomplete)
+{
+    typval_T	args[3];
+    callback_T	*cb;
+    typval_T	rettv;
+    int		retval;
     sctx_T	saved_sctx = current_sctx;
-    char_u	*findexpr;
-    char_u	*arg;
-    typval_T	tv;
-    list_T	*retlist = NULL;
-
-    findexpr = get_findexpr();
-
-    set_vim_var_string(VV_FNAME, pat, -1);
-    set_vim_var_nr(VV_CMDCOMPLETE, cmdcomplete ? VVAL_TRUE : VVAL_FALSE);
-    current_sctx = curbuf->b_p_script_ctx[BV_FEXPR];
-
-    arg = skipwhite(findexpr);
-
+    sctx_T	*ctx;
+
+    // Call 'findfunc' to obtain the list of file names.
+    args[0].v_type = VAR_STRING;
+    args[0].vval.v_string = pat;
+    args[1].v_type = VAR_BOOL;
+    args[1].vval.v_number = cmdcomplete;
+    args[2].v_type = VAR_UNKNOWN;
+
+    // Lock the text to prevent weird things from happening.  Also disallow
+    // switching to another window, it should not be needed and may end up in
+    // Insert mode in another buffer.
     ++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());
+    ctx = get_option_sctx("findfunc");
+    if (ctx != NULL)
+	current_sctx = *ctx;
+
+    cb = get_findfunc_callback();
+    retval = call_callback(cb, -1, &rettv, 2, args);
+
+    current_sctx = saved_sctx;
+
+    --textlock;
+
+    list_T *retlist = NULL;
+
+    if (retval == OK)
+    {
+	if (rettv.v_type == VAR_LIST)
+	    retlist = list_copy(rettv.vval.v_list, FALSE, FALSE, get_copyID());
 	else
-	    emsg(_(e_invalid_return_type_from_findexpr));
-	clear_tv(&tv);
-    }
-    --textlock;
-    clear_evalarg(&EVALARG_EVALUATE, NULL);
-
-    set_vim_var_string(VV_FNAME, NULL, 0);
-    set_vim_var_nr(VV_CMDCOMPLETE, VVAL_FALSE);
-    current_sctx = saved_sctx;
+	    emsg(_(e_invalid_return_type_from_findfunc));
+
+	clear_tv(&rettv);
+    }
 
     return retlist;
 }
 
 /*
- * Find file names matching "pat" using 'findexpr' and return it in "files".
+ * Find file names matching "pat" using 'findfunc' and return it in "files".
  * Used for expanding the :find, :sfind and :tabfind command argument.
  * Returns OK on success and FAIL otherwise.
  */
     int
-expand_findexpr(char_u *pat, char_u ***files, int *numMatches)
+expand_findfunc(char_u *pat, char_u ***files, int *numMatches)
 {
     list_T	*l;
     int		len;
@@ -6983,7 +6996,7 @@ expand_findexpr(char_u *pat, char_u ***f
     *numMatches = 0;
     *files = NULL;
 
-    l = eval_findexpr(pat, TRUE);
+    l = call_findfunc(pat, VVAL_TRUE);
 
     if (l == NULL)
 	return FAIL;
@@ -7015,11 +7028,11 @@ expand_findexpr(char_u *pat, char_u ***f
 }
 
 /*
- * Use 'findexpr' to find file 'findarg'.  The 'count' argument is used to find
+ * Use 'findfunc' 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)
+findfunc_find_file(char_u *findarg, int findarg_len, int count)
 {
     list_T	*fname_list;
     char_u	*ret_fname = NULL;
@@ -7029,7 +7042,7 @@ findexpr_find_file(char_u *findarg, int 
     cc = findarg[findarg_len];
     findarg[findarg_len] = NUL;
 
-    fname_list = eval_findexpr(findarg, FALSE);
+    fname_list = call_findfunc(findarg, VVAL_FALSE);
     fname_count = list_len(fname_list);
 
     if (fname_count == 0)
@@ -7053,6 +7066,62 @@ findexpr_find_file(char_u *findarg, int 
 
     return ret_fname;
 }
+
+/*
+ * Process the 'findfunc' option value.
+ * Returns NULL on success and an error message on failure.
+ */
+    char *
+did_set_findfunc(optset_T *args UNUSED)
+{
+    int	retval;
+
+    if (*curbuf->b_p_ffu != NUL)
+    {
+	// buffer-local option set
+	retval = option_set_callback_func(curbuf->b_p_ffu, &curbuf->b_ffu_cb);
+    }
+    else
+    {
+	// global option set
+	retval = option_set_callback_func(p_ffu, &ffu_cb);
+    }
+
+    if (retval == FAIL)
+	return e_invalid_argument;
+
+    // If the option value starts with <SID> or s:, then replace that with
+    // the script identifier.
+    char_u	**varp = (char_u **)args->os_varp;
+    char_u	*name = get_scriptlocal_funcname(*varp);
+    if (name != NULL)
+    {
+	free_string_option(*varp);
+	*varp = name;
+    }
+
+    return NULL;
+}
+
+# if defined(EXITFREE) || defined(PROTO)
+    void
+free_findfunc_option(void)
+{
+    free_callback(&ffu_cb);
+}
+# endif
+
+/*
+ * Mark the global 'findfunc' callback with "copyID" so that it is not
+ * garbage collected.
+ */
+    int
+set_ref_in_findfunc(int copyID UNUSED)
+{
+    int abort = FALSE;
+    abort = set_ref_in_callback(&ffu_cb, copyID);
+    return abort;
+}
 #endif
 
 /*
@@ -7105,10 +7174,10 @@ ex_splitview(exarg_T *eap)
 	char_u	*file_to_find = NULL;
 	char	*search_ctx = NULL;
 
-	if (*get_findexpr() != NUL)
+	if (*get_findfunc() != NUL)
 	{
 #ifdef FEAT_EVAL
-	    fname = findexpr_find_file(eap->arg, (int)STRLEN(eap->arg),
+	    fname = findfunc_find_file(eap->arg, (int)STRLEN(eap->arg),
 				       eap->addr_count > 0 ? eap->line2 : 1);
 #endif
 	}
@@ -7389,10 +7458,10 @@ ex_find(exarg_T *eap)
     char_u	*file_to_find = NULL;
     char	*search_ctx = NULL;
 
-    if (*get_findexpr() != NUL)
+    if (*get_findfunc() != NUL)
     {
 #ifdef FEAT_EVAL
-	fname = findexpr_find_file(eap->arg, (int)STRLEN(eap->arg),
+	fname = findfunc_find_file(eap->arg, (int)STRLEN(eap->arg),
 					eap->addr_count > 0 ? eap->line2 : 1);
 #endif
     }
--- a/src/gc.c
+++ b/src/gc.c
@@ -183,6 +183,9 @@ garbage_collect(int testing)
     // 'imactivatefunc' and 'imstatusfunc' callbacks
     abort = abort || set_ref_in_im_funcs(copyID);
 
+    // 'findfunc' callback
+    abort = abort || set_ref_in_findfunc(copyID);
+
 #ifdef FEAT_LUA
     abort = abort || set_ref_in_lua(copyID);
 #endif
--- a/src/option.c
+++ b/src/option.c
@@ -1014,6 +1014,9 @@ free_all_options(void)
     }
     free_operatorfunc_option();
     free_tagfunc_option();
+# if defined(FEAT_EVAL)
+    free_findfunc_option();
+# endif
 }
 #endif
 
@@ -6372,8 +6375,8 @@ unset_global_local_option(char_u *name, 
 	    clear_string_option(&buf->b_p_fp);
 	    break;
 # ifdef FEAT_EVAL
-	case PV_FEXPR:
-	    clear_string_option(&buf->b_p_fexpr);
+	case PV_FFU:
+	    clear_string_option(&buf->b_p_ffu);
 	    break;
 # endif
 # ifdef FEAT_QUICKFIX
@@ -6455,7 +6458,7 @@ get_varp_scope(struct vimoption *p, int 
 	{
 	    case PV_FP:   return (char_u *)&(curbuf->b_p_fp);
 #ifdef FEAT_EVAL
-	    case PV_FEXPR:   return (char_u *)&(curbuf->b_p_fexpr);
+	    case PV_FFU: return (char_u *)&(curbuf->b_p_ffu);
 #endif
 #ifdef FEAT_QUICKFIX
 	    case PV_EFM:  return (char_u *)&(curbuf->b_p_efm);
@@ -6568,8 +6571,8 @@ get_varp(struct vimoption *p)
 	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;
+	case PV_FFU:	return *curbuf->b_p_ffu != NUL
+				    ? (char_u *)&(curbuf->b_p_ffu) : p->var;
 #endif
 #ifdef FEAT_QUICKFIX
 	case PV_EFM:	return *curbuf->b_p_efm != NUL
@@ -6818,15 +6821,15 @@ get_equalprg(void)
 }
 
 /*
- * Get the value of 'findexpr', either the buffer-local one or the global one.
+ * Get the value of 'findfunc', either the buffer-local one or the global one.
  */
     char_u *
-get_findexpr(void)
+get_findfunc(void)
 {
 #ifdef FEAT_EVAL
-    if (*curbuf->b_p_fexpr == NUL)
-	return p_fexpr;
-    return curbuf->b_p_fexpr;
+    if (*curbuf->b_p_ffu == NUL)
+	return p_ffu;
+    return curbuf->b_p_ffu;
 #else
     return (char_u *)"";
 #endif
@@ -7361,8 +7364,7 @@ buf_copy_options(buf_T *buf, int flags)
 #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);
+	    buf->b_p_ffu = empty_option;
 #endif
 	    buf->b_p_kp = empty_option;
 	    buf->b_p_path = empty_option;
@@ -8749,6 +8751,7 @@ option_set_callback_func(char_u *optval 
 #ifdef FEAT_EVAL
     typval_T	*tv;
     callback_T	cb;
+    int		funcname = FALSE;
 
     if (optval == NULL || *optval == NUL)
     {
@@ -8762,8 +8765,11 @@ option_set_callback_func(char_u *optval 
 	// Lambda expression or a funcref
 	tv = eval_expr(optval, NULL);
     else
+    {
 	// treat everything else as a function name string
 	tv = alloc_string_tv(vim_strsave(optval));
+	funcname = TRUE;
+    }
     if (tv == NULL)
 	return FAIL;
 
@@ -8780,6 +8786,16 @@ option_set_callback_func(char_u *optval 
 	vim_free(cb.cb_name);
     free_tv(tv);
 
+    if (in_vim9script() && funcname && (vim_strchr(optval, '.') != NULL))
+    {
+	// When a Vim9 imported function name is used, it is expanded by the
+	// call to get_callback() above to <SNR>_funcname.   Revert the name to
+	// back to "import.funcname".
+	if (optcb->cb_free_name)
+	    vim_free(optcb->cb_name);
+	optcb->cb_name = vim_strsave(optval);
+	optcb->cb_free_name = TRUE;
+    }
     // when using Vim9 style "import.funcname" it needs to be expanded to
     // "import#funcname".
     expand_autload_callback(optcb);
--- a/src/option.h
+++ b/src/option.h
@@ -597,7 +597,7 @@ 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'
+EXTERN char_u	*p_ffu;		// 'findfunc'
 #endif
 EXTERN int	p_fixeol;	// 'fixendofline'
 #ifdef FEAT_FOLDING
@@ -1171,11 +1171,11 @@ enum
     , BV_EP
     , BV_ET
     , BV_FENC
-    , BV_FEXPR
     , BV_FP
 #ifdef FEAT_EVAL
     , BV_BEXPR
     , BV_FEX
+    , BV_FFU
 #endif
     , BV_FF
     , BV_FLP
--- a/src/optiondefs.h
+++ b/src/optiondefs.h
@@ -54,6 +54,7 @@
 #define PV_CPT		OPT_BUF(BV_CPT)
 #define PV_DICT		OPT_BOTH(OPT_BUF(BV_DICT))
 #define PV_TSR		OPT_BOTH(OPT_BUF(BV_TSR))
+#define PV_FFU		OPT_BOTH(OPT_BUF(BV_FFU))
 #define PV_CSL		OPT_BUF(BV_CSL)
 #ifdef FEAT_COMPL_FUNC
 # define PV_CFU		OPT_BUF(BV_CFU)
@@ -74,7 +75,6 @@
 #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)
@@ -959,9 +959,10 @@ 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,
+    {"findfunc", "ffu",     P_STRING|P_ALLOCED|P_VI_DEF|P_SECURE|P_FUNC,
+#ifdef FEAT_EVAL
+			    (char_u *)&p_ffu, PV_FFU,
+			    did_set_findfunc, NULL,
 			    {(char_u *)"", (char_u *)0L}
 #else
 			    (char_u *)NULL, PV_NONE, NULL, NULL,
--- a/src/optionstr.c
+++ b/src/optionstr.c
@@ -313,6 +313,7 @@ check_buf_options(buf_T *buf)
     check_string_option(&buf->b_p_tsrfu);
 #endif
 #ifdef FEAT_EVAL
+    check_string_option(&buf->b_p_ffu);
     check_string_option(&buf->b_p_tfu);
 #endif
 #ifdef FEAT_KEYMAP
@@ -324,9 +325,6 @@ 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);
@@ -3135,9 +3133,8 @@ expand_set_nrformats(optexpand_T *args, 
 #if defined(FEAT_EVAL) || defined(PROTO)
 /*
  * One of the '*expr' options is changed: 'balloonexpr', 'diffexpr',
- * 'findexpr', 'foldexpr', 'foldtext', 'formatexpr', 'includeexpr',
- * 'indentexpr', 'patchexpr', 'printexpr' and 'charconvert'.
- *
+ * 'foldexpr', 'foldtext', 'formatexpr', 'includeexpr', 'indentexpr',
+ * 'patchexpr', 'printexpr' and 'charconvert'.
  */
     char *
 did_set_optexpr(optset_T *args)
--- a/src/proto/ex_docmd.pro
+++ b/src/proto/ex_docmd.pro
@@ -46,7 +46,10 @@ void tabpage_close_other(tabpage_T *tp, 
 void ex_stop(exarg_T *eap);
 void handle_drop(int filec, char_u **filev, int split, void (*callback)(void *), void *cookie);
 void handle_any_postponed_drop(void);
-int expand_findexpr(char_u *pat, char_u ***files, int *numMatches);
+int expand_findfunc(char_u *pat, char_u ***files, int *numMatches);
+char *did_set_findfunc(optset_T *args);
+void free_findfunc_option(void);
+int set_ref_in_findfunc(int copyID);
 void ex_splitview(exarg_T *eap);
 void tabpage_new(void);
 void do_exedit(exarg_T *eap, win_T *old_curwin);
--- a/src/proto/option.pro
+++ b/src/proto/option.pro
@@ -120,7 +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);
+char_u *get_findfunc(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);
--- a/src/structs.h
+++ b/src/structs.h
@@ -3248,6 +3248,8 @@ struct file_buffer
 #ifdef FEAT_EVAL
     char_u	*b_p_tfu;	// 'tagfunc' option value
     callback_T	b_tfu_cb;	// 'tagfunc' callback
+    char_u	*b_p_ffu;	// 'findfunc' option value
+    callback_T	b_ffu_cb;	// 'findfunc' callback
 #endif
     int		b_p_eof;	// 'endoffile'
     int		b_p_eol;	// 'endofline'
@@ -3334,9 +3336,6 @@ 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
--- a/src/testdir/test_findfile.vim
+++ b/src/testdir/test_findfile.vim
@@ -1,6 +1,7 @@
 " Test findfile() and finddir()
 
 source check.vim
+import './vim9.vim' as v9
 
 let s:files = [ 'Xfinddir1/foo',
       \         'Xfinddir1/bar',
@@ -283,223 +284,491 @@ func Test_find_non_existing_path()
   let &path = save_path
 endfunc
 
-" Test for 'findexpr'
-func Test_findexpr()
+" Test for 'findfunc'
+func Test_findfunc()
   CheckUnix
-  call assert_equal('', &findexpr)
-  call writefile(['aFile'], 'Xfindexpr1.c', 'D')
-  call writefile(['bFile'], 'Xfindexpr2.c', 'D')
-  call writefile(['cFile'], 'Xfindexpr3.c', 'D')
+  call assert_equal('', &findfunc)
+  call writefile(['aFile'], 'Xfindfunc1.c', 'D')
+  call writefile(['bFile'], 'Xfindfunc2.c', 'D')
+  call writefile(['cFile'], 'Xfindfunc3.c', 'D')
 
   " basic tests
-  func FindExpr1()
-    let fnames = ['Xfindexpr1.c', 'Xfindexpr2.c', 'Xfindexpr3.c']
-    return fnames->copy()->filter('v:val =~? v:fname')
+  func FindFuncBasic(pat, cmdcomplete)
+    let fnames = ['Xfindfunc1.c', 'Xfindfunc2.c', 'Xfindfunc3.c']
+    return fnames->copy()->filter('v:val =~? a:pat')
   endfunc
 
-  set findexpr=FindExpr1()
-  find Xfindexpr3
-  call assert_match('Xfindexpr3.c', @%)
+  set findfunc=FindFuncBasic
+  find Xfindfunc3
+  call assert_match('Xfindfunc3.c', @%)
   bw!
   2find Xfind
-  call assert_match('Xfindexpr2.c', @%)
+  call assert_match('Xfindfunc2.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', @%)
+  sfind Xfindfunc2.c
+  call assert_match('Xfindfunc2.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', @%)
+  tabfind Xfindfunc3.c
+  call assert_match('Xfindfunc3.c', @%)
   call assert_equal(2, tabpagenr())
   %bw!
   call assert_fails('tabfind foobar', 'E345: Can''t find file "foobar" in path')
 
+  " Test garbage collection
+  call test_garbagecollect_now()
+  find Xfindfunc2
+  call assert_match('Xfindfunc2.c', @%)
+  bw!
+  delfunc FindFuncBasic
+  call test_garbagecollect_now()
+  call assert_fails('find Xfindfunc2', 'E117: Unknown function: FindFuncBasic')
+
   " Buffer-local option
-  set findexpr=['abc']
+  func GlobalFindFunc(pat, cmdcomplete)
+    return ['global']
+  endfunc
+  func LocalFindFunc(pat, cmdcomplete)
+    return ['local']
+  endfunc
+  set findfunc=GlobalFindFunc
   new
-  setlocal findexpr=['def']
+  setlocal findfunc=LocalFindFunc
   find xxxx
-  call assert_equal('def', @%)
+  call assert_equal('local', @%)
   wincmd w
   find xxxx
-  call assert_equal('abc', @%)
+  call assert_equal('global', @%)
   aboveleft new
-  call assert_equal("['abc']", &findexpr)
+  call assert_equal("GlobalFindFunc", &findfunc)
   wincmd k
   aboveleft new
-  call assert_equal("['abc']", &findexpr)
+  call assert_equal("GlobalFindFunc", &findfunc)
   %bw!
+  delfunc GlobalFindFunc
+  delfunc LocalFindFunc
 
-  " Empty list
-  set findexpr=[]
-  call assert_fails('find xxxx', 'E345: Can''t find file "xxxx" in path')
+  " Assign an expression
+  set findfunc=[]
+  call assert_fails('find xxxx', 'E117: Unknown function: []')
 
   " Error cases
 
-  " Syntax error in the expression
-  set findexpr=FindExpr1{}
-  call assert_fails('find Xfindexpr1.c', 'E15: Invalid expression')
+  " Function that doesn't any argument
+  func FindFuncNoArg()
+  endfunc
+  set findfunc=FindFuncNoArg
+  call assert_fails('find Xfindfunc1.c', 'E118: Too many arguments for function: FindFuncNoArg')
+  delfunc FindFuncNoArg
 
-  " Find expression throws an error
-  func FindExpr2()
+  " Syntax error in the function
+  func FindFuncSyntaxError(pat, cmdcomplete)
+    return l
+  endfunc
+  set findfunc=FindFuncSyntaxError
+  call assert_fails('find Xfindfunc1.c', 'E121: Undefined variable: l')
+  delfunc FindFuncSyntaxError
+
+  " Find function throws an error
+  func FindFuncWithThrow(pat, cmdcomplete)
     throw 'find error'
   endfunc
-  set findexpr=FindExpr2()
-  call assert_fails('find Xfindexpr1.c', 'find error')
+  set findfunc=FindFuncWithThrow
+  call assert_fails('find Xfindfunc1.c', 'find error')
+  delfunc FindFuncWithThrow
 
-  " Try using a null List as the expression
-  set findexpr=test_null_list()
-  call assert_fails('find Xfindexpr1.c', 'E345: Can''t find file "Xfindexpr1.c" in path')
+  " Try using a null function
+  call assert_fails('let &findfunc = test_null_function()', 'E129: Function name required')
 
-  " Try to create a new window from the find expression
-  func FindExpr3()
+  " Try to create a new window from the find function
+  func FindFuncNewWindow(pat, cmdexpand)
     new
     return ["foo"]
   endfunc
-  set findexpr=FindExpr3()
-  call assert_fails('find Xfindexpr1.c', 'E565: Not allowed to change text or change window')
+  set findfunc=FindFuncNewWindow
+  call assert_fails('find Xfindfunc1.c', 'E565: Not allowed to change text or change window')
+  delfunc FindFuncNewWindow
 
-  " Try to modify the current buffer from the find expression
-  func FindExpr4()
+  " Try to modify the current buffer from the find function
+  func FindFuncModifyBuf(pat, cmdexpand)
     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 findfunc=FindFuncModifyBuf
+  call assert_fails('find Xfindfunc1.c', 'E565: Not allowed to change text or change window')
+  delfunc FindFuncModifyBuf
 
-  " Expression returning a string
-  set findexpr='abc'
-  call assert_fails('find Xfindexpr1.c', "E1514: 'findexpr' did not return a List type")
+  " Return the wrong type from the function
+  func FindFuncWrongRet(pat, cmdexpand)
+    return 'foo'
+  endfunc
+  set findfunc=FindFuncWrongRet
+  call assert_fails('find Xfindfunc1.c', "E1514: 'findfunc' did not return a List type")
+  delfunc FindFuncWrongRet
 
-  set findexpr&
-  delfunc! FindExpr1
-  delfunc! FindExpr2
-  delfunc! FindExpr3
-  delfunc! FindExpr4
+  set findfunc&
 endfunc
 
-" Test for using a script-local function for 'findexpr'
-func Test_findexpr_scriptlocal_func()
-  func! s:FindExprScript()
-    let g:FindExprArg = v:fname
+" Test for using a script-local function for 'findfunc'
+func Test_findfunc_scriptlocal_func()
+  func! s:FindFuncScript(pat, cmdexpand)
+    let g:FindFuncArg = a:pat
     return ['xxx']
   endfunc
 
-  set findexpr=s:FindExprScript()
-  call assert_equal(expand('<SID>') .. 'FindExprScript()', &findexpr)
-  call assert_equal(expand('<SID>') .. 'FindExprScript()', &g:findexpr)
+  set findfunc=s:FindFuncScript
+  call assert_equal(expand('<SID>') .. 'FindFuncScript', &findfunc)
+  call assert_equal(expand('<SID>') .. 'FindFuncScript', &g:findfunc)
   new | only
-  let g:FindExprArg = ''
+  let g:FindFuncArg = ''
   find abc
-  call assert_equal('abc', g:FindExprArg)
+  call assert_equal('abc', g:FindFuncArg)
   bw!
 
-  set findexpr=<SID>FindExprScript()
-  call assert_equal(expand('<SID>') .. 'FindExprScript()', &findexpr)
-  call assert_equal(expand('<SID>') .. 'FindExprScript()', &g:findexpr)
+  set findfunc=<SID>FindFuncScript
+  call assert_equal(expand('<SID>') .. 'FindFuncScript', &findfunc)
+  call assert_equal(expand('<SID>') .. 'FindFuncScript', &g:findfunc)
   new | only
-  let g:FindExprArg = ''
+  let g:FindFuncArg = ''
   find abc
-  call assert_equal('abc', g:FindExprArg)
+  call assert_equal('abc', g:FindFuncArg)
   bw!
 
-  let &findexpr = 's:FindExprScript()'
-  call assert_equal(expand('<SID>') .. 'FindExprScript()', &g:findexpr)
+  let &findfunc = 's:FindFuncScript'
+  call assert_equal(expand('<SID>') .. 'FindFuncScript', &g:findfunc)
   new | only
-  let g:FindExprArg = ''
+  let g:FindFuncArg = ''
   find abc
-  call assert_equal('abc', g:FindExprArg)
+  call assert_equal('abc', g:FindFuncArg)
   bw!
 
-  let &findexpr = '<SID>FindExprScript()'
-  call assert_equal(expand('<SID>') .. 'FindExprScript()', &g:findexpr)
+  let &findfunc = '<SID>FindFuncScript'
+  call assert_equal(expand('<SID>') .. 'FindFuncScript', &g:findfunc)
   new | only
-  let g:FindExprArg = ''
+  let g:FindFuncArg = ''
   find abc
-  call assert_equal('abc', g:FindExprArg)
+  call assert_equal('abc', g:FindFuncArg)
   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)
+  set findfunc=
+  setglobal findfunc=s:FindFuncScript
+  setlocal findfunc=
+  call assert_equal(expand('<SID>') .. 'FindFuncScript', &findfunc)
+  call assert_equal(expand('<SID>') .. 'FindFuncScript', &g:findfunc)
+  call assert_equal('', &l:findfunc)
   new | only
-  let g:FindExprArg = ''
+  let g:FindFuncArg = ''
   find abc
-  call assert_equal('abc', g:FindExprArg)
+  call assert_equal('abc', g:FindFuncArg)
   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 = ''
+  set findfunc=
+  setglobal findfunc=
+  setlocal findfunc=s:FindFuncScript
+  call assert_equal(expand('<SID>') .. 'FindFuncScript', &findfunc)
+  call assert_equal(expand('<SID>') .. 'FindFuncScript', &l:findfunc)
+  call assert_equal('', &g:findfunc)
+  let g:FindFuncArg = ''
   find abc
-  call assert_equal('abc', g:FindExprArg)
+  call assert_equal('abc', g:FindFuncArg)
   bw!
 
-  set findexpr=
-  delfunc s:FindExprScript
+  set findfunc=
+  delfunc s:FindFuncScript
 endfunc
 
-" Test for expanding the argument to the :find command using 'findexpr'
-func Test_findexpr_expand_arg()
-  let s:fnames = ['Xfindexpr1.c', 'Xfindexpr2.c', 'Xfindexpr3.c']
+" Test for expanding the argument to the :find command using 'findfunc'
+func Test_findfunc_expand_arg()
+  let s:fnames = ['Xfindfunc1.c', 'Xfindfunc2.c', 'Xfindfunc3.c']
 
-  " 'findexpr' that accepts a regular expression
-  func FindExprRegexp()
-    return s:fnames->copy()->filter('v:val =~? v:fname')
+  " 'findfunc' that accepts a regular expression
+  func FindFuncRegexp(pat, cmdcomplete)
+    return s:fnames->copy()->filter('v:val =~? a:pat')
   endfunc
 
-  " 'findexpr' that accepts a glob
-  func FindExprGlob()
-    let pat = glob2regpat(v:cmdcomplete ? $'*{v:fname}*' : v:fname)
+  " 'findfunc' that accepts a glob
+  func FindFuncGlob(pat_arg, cmdcomplete)
+    let pat = glob2regpat(a:cmdcomplete ? $'*{a:pat_arg}*' : a:pat_arg)
     return s:fnames->copy()->filter('v:val =~? pat')
   endfunc
 
   for regexp in [v:true, v:false]
-    let &findexpr = regexp ? 'FindExprRegexp()' : 'FindExprGlob()'
+    let &findfunc = regexp ? 'FindFuncRegexp' : 'FindFuncGlob'
 
     call feedkeys(":find \<Tab>\<C-B>\"\<CR>", "xt")
-    call assert_equal('"find Xfindexpr1.c', @:)
+    call assert_equal('"find Xfindfunc1.c', @:)
 
     call feedkeys(":find Xfind\<Tab>\<Tab>\<C-B>\"\<CR>", "xt")
-    call assert_equal('"find Xfindexpr2.c', @:)
+    call assert_equal('"find Xfindfunc2.c', @:)
 
     call assert_equal(s:fnames, getcompletion('find ', 'cmdline'))
     call assert_equal(s:fnames, getcompletion('find Xfind', 'cmdline'))
 
     let pat = regexp ? 'X.*1\.c' : 'X*1.c'
     call feedkeys($":find {pat}\<Tab>\<C-B>\"\<CR>", "xt")
-    call assert_equal('"find Xfindexpr1.c', @:)
-    call assert_equal(['Xfindexpr1.c'], getcompletion($'find {pat}', 'cmdline'))
+    call assert_equal('"find Xfindfunc1.c', @:)
+    call assert_equal(['Xfindfunc1.c'], getcompletion($'find {pat}', 'cmdline'))
 
     call feedkeys(":find 3\<Tab>\<C-B>\"\<CR>", "xt")
-    call assert_equal('"find Xfindexpr3.c', @:)
-    call assert_equal(['Xfindexpr3.c'], getcompletion($'find 3', 'cmdline'))
+    call assert_equal('"find Xfindfunc3.c', @:)
+    call assert_equal(['Xfindfunc3.c'], getcompletion($'find 3', 'cmdline'))
 
     call feedkeys(":find Xfind\<C-A>\<C-B>\"\<CR>", "xt")
-    call assert_equal('"find Xfindexpr1.c Xfindexpr2.c Xfindexpr3.c', @:)
+    call assert_equal('"find Xfindfunc1.c Xfindfunc2.c Xfindfunc3.c', @:)
 
     call feedkeys(":find abc\<Tab>\<C-B>\"\<CR>", "xt")
     call assert_equal('"find abc', @:)
     call assert_equal([], getcompletion('find abc', 'cmdline'))
   endfor
 
-  set findexpr&
-  delfunc! FindExprRegexp
-  delfunc! FindExprGlob
+  set findfunc&
+  delfunc! FindFuncRegexp
+  delfunc! FindFuncGlob
   unlet s:fnames
 endfunc
 
+" Test for different ways of setting the 'findfunc' option
+func Test_findfunc_callback()
+  new
+  func FindFunc1(pat, cmdexpand)
+    let g:FindFunc1Args = [a:pat, a:cmdexpand]
+    return ['findfunc1']
+  endfunc
+
+  let lines =<< trim END
+    #" Test for using a function name
+    LET &findfunc = 'g:FindFunc1'
+    LET g:FindFunc1Args = []
+    find abc1
+    call assert_equal(['abc1', v:false], g:FindFunc1Args)
+
+    #" Test for using a function()
+    set findfunc=function('g:FindFunc1')
+    LET g:FindFunc1Args = []
+    find abc2
+    call assert_equal(['abc2', v:false], g:FindFunc1Args)
+
+    #" Using a funcref variable to set 'findfunc'
+    VAR Fn = function('g:FindFunc1')
+    LET &findfunc = Fn
+    LET g:FindFunc1Args = []
+    find abc3
+    call assert_equal(['abc3', v:false], g:FindFunc1Args)
+
+    #" Using a string(funcref_variable) to set 'findfunc'
+    LET Fn = function('g:FindFunc1')
+    LET &findfunc = string(Fn)
+    LET g:FindFunc1Args = []
+    find abc4
+    call assert_equal(['abc4', v:false], g:FindFunc1Args)
+
+    #" Test for using a funcref()
+    set findfunc=funcref('g:FindFunc1')
+    LET g:FindFunc1Args = []
+    find abc5
+    call assert_equal(['abc5', v:false], g:FindFunc1Args)
+
+    #" Using a funcref variable to set 'findfunc'
+    LET Fn = funcref('g:FindFunc1')
+    LET &findfunc = Fn
+    LET g:FindFunc1Args = []
+    find abc6
+    call assert_equal(['abc6', v:false], g:FindFunc1Args)
+
+    #" Using a string(funcref_variable) to set 'findfunc'
+    LET Fn = funcref('g:FindFunc1')
+    LET &findfunc = string(Fn)
+    LET g:FindFunc1Args = []
+    find abc7
+    call assert_equal(['abc7', v:false], g:FindFunc1Args)
+
+    #" Test for using a lambda function using set
+    VAR optval = "LSTART pat, cmdexpand LMIDDLE FindFunc1(pat, cmdexpand) LEND"
+    LET optval = substitute(optval, ' ', '\\ ', 'g')
+    exe "set findfunc=" .. optval
+    LET g:FindFunc1Args = []
+    find abc8
+    call assert_equal(['abc8', v:false], g:FindFunc1Args)
+
+    #" Test for using a lambda function using LET
+    LET &findfunc = LSTART pat, _ LMIDDLE FindFunc1(pat, v:false) LEND
+    LET g:FindFunc1Args = []
+    find abc9
+    call assert_equal(['abc9', v:false], g:FindFunc1Args)
+
+    #" Set 'findfunc' to a string(lambda expression)
+    LET &findfunc = 'LSTART pat, _ LMIDDLE FindFunc1(pat, v:false) LEND'
+    LET g:FindFunc1Args = []
+    find abc10
+    call assert_equal(['abc10', v:false], g:FindFunc1Args)
+
+    #" Set 'findfunc' to a variable with a lambda expression
+    VAR Lambda = LSTART pat, _ LMIDDLE FindFunc1(pat, v:false) LEND
+    LET &findfunc = Lambda
+    LET g:FindFunc1Args = []
+    find abc11
+    call assert_equal(['abc11', v:false], g:FindFunc1Args)
+
+    #" Set 'findfunc' to a string(variable with a lambda expression)
+    LET Lambda = LSTART pat, _ LMIDDLE FindFunc1(pat, v:false) LEND
+    LET &findfunc = string(Lambda)
+    LET g:FindFunc1Args = []
+    find abc12
+    call assert_equal(['abc12', v:false], g:FindFunc1Args)
+
+    #" Try to use 'findfunc' after the function is deleted
+    func g:TmpFindFunc(pat, cmdexpand)
+      let g:TmpFindFunc1Args = [a:pat, a:cmdexpand]
+    endfunc
+    LET &findfunc = function('g:TmpFindFunc')
+    delfunc g:TmpFindFunc
+    call test_garbagecollect_now()
+    LET g:TmpFindFunc1Args = []
+    call assert_fails('find abc13', 'E117:')
+    call assert_equal([], g:TmpFindFunc1Args)
+
+    #" Try to use a function with three arguments for 'findfunc'
+    func g:TmpFindFunc2(x, y, z)
+      let g:TmpFindFunc2Args = [a:x, a:y, a:z]
+    endfunc
+    set findfunc=TmpFindFunc2
+    LET g:TmpFindFunc2Args = []
+    call assert_fails('find abc14', 'E119:')
+    call assert_equal([], g:TmpFindFunc2Args)
+    delfunc TmpFindFunc2
+
+    #" Try to use a function with zero arguments for 'findfunc'
+    func g:TmpFindFunc3()
+      let g:TmpFindFunc3Called = v:true
+    endfunc
+    set findfunc=TmpFindFunc3
+    LET g:TmpFindFunc3Called = v:false
+    call assert_fails('find abc15', 'E118:')
+    call assert_equal(v:false, g:TmpFindFunc3Called)
+    delfunc TmpFindFunc3
+
+    #" Try to use a lambda function with three arguments for 'findfunc'
+    LET &findfunc = LSTART a, b, c LMIDDLE FindFunc1(a, v:false) LEND
+    LET g:FindFunc1Args = []
+    call assert_fails('find abc16', 'E119:')
+    call assert_equal([], g:FindFunc1Args)
+
+    #" Test for clearing the 'findfunc' option
+    set findfunc=''
+    set findfunc&
+    call assert_fails("set findfunc=function('abc')", "E700:")
+    call assert_fails("set findfunc=funcref('abc')", "E700:")
+
+    #" set 'findfunc' to a non-existing function
+    LET &findfunc = function('g:FindFunc1')
+    call assert_fails("set findfunc=function('NonExistingFunc')", 'E700:')
+    call assert_fails("LET &findfunc = function('NonExistingFunc')", 'E700:')
+    LET g:FindFunc1Args = []
+    find abc17
+    call assert_equal(['abc17', v:false], g:FindFunc1Args)
+  END
+  call v9.CheckTransLegacySuccess(lines)
+
+  " Test for using a script-local function name
+  func s:FindFunc2(pat, cmdexpand)
+    let g:FindFunc2Args = [a:pat, a:cmdexpand]
+    return ['findfunc2']
+  endfunc
+  set findfunc=s:FindFunc2
+  let g:FindFunc2Args = []
+  find abc18
+  call assert_equal(['abc18', v:false], g:FindFunc2Args)
+
+  let &findfunc = 's:FindFunc2'
+  let g:FindFunc2Args = []
+  find abc19
+  call assert_equal(['abc19', v:false], g:FindFunc2Args)
+  delfunc s:FindFunc2
+
+  " Using Vim9 lambda expression in legacy context should fail
+  set findfunc=(pat,\ cmdexpand)\ =>\ FindFunc1(pat,\ v:false)
+  let g:FindFunc1Args = []
+  call assert_fails('find abc20', 'E117:')
+  call assert_equal([], g:FindFunc1Args)
+
+  " set 'findfunc' to a partial with dict.
+  func SetFindFunc()
+    let operator = {'execute': function('FindFuncExecute')}
+    let &findfunc = operator.execute
+  endfunc
+  func FindFuncExecute(pat, cmdexpand) dict
+    return ['findfuncexecute']
+  endfunc
+  call SetFindFunc()
+  call test_garbagecollect_now()
+  set findfunc=
+  delfunc SetFindFunc
+  delfunc FindFuncExecute
+
+  func FindFunc2(pat, cmdexpand)
+    let g:FindFunc2Args = [a:pat, a:cmdexpand]
+    return ['findfunc2']
+  endfunc
+
+  " Vim9 tests
+  let lines =<< trim END
+    vim9script
+
+    def g:Vim9findFunc(pat: string, cmdexpand: bool): list<string>
+      g:FindFunc1Args = [pat, cmdexpand]
+      return ['vim9findfunc']
+    enddef
+
+    # Test for using a def function with findfunc
+    set findfunc=function('g:Vim9findFunc')
+    g:FindFunc1Args = []
+    find abc21
+    assert_equal(['abc21', false], g:FindFunc1Args)
+
+    # Test for using a global function name
+    &findfunc = g:FindFunc2
+    g:FindFunc2Args = []
+    find abc22
+    assert_equal(['abc22', false], g:FindFunc2Args)
+    bw!
+
+    # Test for using a script-local function name
+    def LocalFindFunc(pat: string, cmdexpand: bool): list<string>
+      g:LocalFindFuncArgs = [pat, cmdexpand]
+      return ['localfindfunc']
+    enddef
+    &findfunc = LocalFindFunc
+    g:LocalFindFuncArgs = []
+    find abc23
+    assert_equal(['abc23', false], g:LocalFindFuncArgs)
+    bw!
+  END
+  call v9.CheckScriptSuccess(lines)
+
+  " setting 'findfunc' to a script local function outside of a script context
+  " should fail
+  let cleanup =<< trim END
+    call writefile([execute('messages')], 'Xtest.out')
+    qall
+  END
+  call writefile(cleanup, 'Xverify.vim', 'D')
+  call RunVim([], [], "-c \"set findfunc=s:abc\" -S Xverify.vim")
+  call assert_match('E81: Using <SID> not in a', readfile('Xtest.out')[0])
+  call delete('Xtest.out')
+
+  " cleanup
+  set findfunc&
+  delfunc FindFunc1
+  delfunc FindFunc2
+  unlet g:FindFunc1Args g:FindFunc2Args
+  %bw!
+endfunc
+
+
 " vim: shiftwidth=2 sts=2 expandtab
--- a/src/testdir/test_modeline.vim
+++ b/src/testdir/test_modeline.vim
@@ -208,7 +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('findfunc', 'findfunc=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:')
--- 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', 'findexpr']
+  for opt in ['backupdir', 'cdpath', 'exrc', 'findfunc']
     call assert_fails('sandbox set ' .. opt .. '?', 'E48:')
     call assert_fails('sandbox let &' .. opt .. ' = 1', 'E48:')
   endfor
--- a/src/testdir/test_vim9_import.vim
+++ b/src/testdir/test_vim9_import.vim
@@ -1560,27 +1560,28 @@ def Run_Test_import_in_printexpr()
   set printexpr=
 enddef
 
-" Test for using an imported function as 'findexpr'
-func Test_import_in_findexpr()
-  call Run_Test_import_in_findexpr()
+" Test for using an imported function as 'findfunc'
+func Test_import_in_findfunc()
+  call Run_Test_import_in_findfunc()
 endfunc
 
-def Run_Test_import_in_findexpr()
+def Run_Test_import_in_findfunc()
   var lines =<< trim END
-      vim9script
-
-      export def FindExpr(): list<string>
-        var fnames = ['Xfile1.c', 'Xfile2.c', 'Xfile3.c']
-        return fnames->copy()->filter('v:val =~? v:fname')
-      enddef
+    vim9script
+
+    export def FindFunc(pat: string, cmdexpand: bool): list<string>
+      var fnames = ['Xfile1.c', 'Xfile2.c', 'Xfile3.c']
+      return fnames->filter((_, v) => v =~? pat)
+    enddef
   END
-  writefile(lines, 'Xfindexpr', 'D')
-
+  writefile(lines, 'Xfindfunc', 'D')
+
+  # Test using the "set" command
   lines =<< trim END
-      vim9script
-      import './Xfindexpr' as find
-
-      set findexpr=find.FindExpr()
+    vim9script
+    import './Xfindfunc' as find1
+
+    set findfunc=find1.FindFunc
   END
   v9.CheckScriptSuccess(lines)
 
@@ -1592,8 +1593,27 @@ def Run_Test_import_in_findexpr()
   botright vert new
   find Xfile1
   assert_equal('Xfile1.c', @%)
-
-  set findexpr=
+  bw!
+
+  # Test using the option variable
+  lines =<< trim END
+    vim9script
+    import './Xfindfunc' as find2
+
+    &findfunc = find2.FindFunc
+  END
+  v9.CheckScriptSuccess(lines)
+
+  enew!
+  find Xfile2
+  assert_equal('Xfile2.c', @%)
+  bwipe!
+
+  botright vert new
+  find Xfile1
+  assert_equal('Xfile1.c', @%)
+
+  set findfunc=
   bwipe!
 enddef
 
--- a/src/version.c
+++ b/src/version.c
@@ -705,6 +705,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    831,
+/**/
     830,
 /**/
     829,
--- a/src/vim.h
+++ b/src/vim.h
@@ -845,7 +845,7 @@ extern int (*dyn_libintl_wputenv)(const 
 #define EXPAND_KEYMAP		58
 #define EXPAND_DIRS_IN_CDPATH	59
 #define EXPAND_SHELLCMDLINE	60
-#define EXPAND_FINDEXPR		61
+#define EXPAND_FINDFUNC		61
 
 
 // Values for exmode_active (0 is no exmode)
@@ -2189,8 +2189,7 @@ typedef int sock_T;
 #define VV_TYPE_TYPEALIAS 107
 #define VV_TYPE_ENUM	  108
 #define VV_TYPE_ENUMVALUE 109
-#define VV_CMDCOMPLETE	110
-#define VV_LEN		111	// number of v: vars
+#define VV_LEN		110	// number of v: vars
 
 // used for v_number in VAR_BOOL and VAR_SPECIAL
 #define VVAL_FALSE	0L	// VAR_BOOL