vim-patch:9.1.0831: 'findexpr' can't be used as lambad or Funcref (#31058)

Problem:  'findexpr' can't be used for lambads
          (Justin Keyes)
Solution: Replace the findexpr option with the findfunc option
          (Yegappan Lakshmanan)

related: vim/vim#15905
closes: vim/vim#15976

a13f3a4f5d

Co-authored-by: Yegappan Lakshmanan <yegappan@yahoo.com>
This commit is contained in:
zeertzjq
2024-11-03 10:06:41 +08:00
committed by GitHub
parent ed3fb1bb9a
commit 3075c69ff0
25 changed files with 604 additions and 292 deletions

View File

@@ -370,10 +370,11 @@ Note: In the future more global options can be made |global-local|. Using
":setlocal" on a global option might work differently then. ":setlocal" on a global option might work differently then.
*option-value-function* *option-value-function*
Some options ('completefunc', 'omnifunc', 'operatorfunc', 'quickfixtextfunc', Some options ('completefunc', 'findfunc', 'omnifunc', 'operatorfunc',
'tagfunc' and 'thesaurusfunc') are set to a function name or a function 'quickfixtextfunc', 'tagfunc' and 'thesaurusfunc') are set to a function name
reference or a lambda function. When using a lambda it will be converted to or a function reference or a lambda function. When using a lambda it will be
the name, e.g. "<lambda>123". Examples: converted to the name, e.g. "<lambda>123".
Examples:
> >
set opfunc=MyOpFunc set opfunc=MyOpFunc
set opfunc=function('MyOpFunc') set opfunc=function('MyOpFunc')
@@ -2598,34 +2599,34 @@ A jump table for the options with a short description can be found at |Q_op|.
eob EndOfBuffer |hl-EndOfBuffer| eob EndOfBuffer |hl-EndOfBuffer|
lastline NonText |hl-NonText| lastline NonText |hl-NonText|
*'findexpr'* *'fexpr'* *E1514* *'findfunc'* *'ffu'* *E1514*
'findexpr' 'fexpr' string (default "") 'findfunc' 'ffu' string (default "")
global or local to buffer |global-local| global or local to buffer |global-local|
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| command. When this option is empty, the internal |file-searching|
mechanism is used. mechanism is used.
While evaluating the expression, the |v:fname| variable is set to the The value can be the name of a function, a |lambda| or a |Funcref|.
argument of the |:find| command. See |option-value-function| for more information.
The expression is evaluated only once per |:find| command invocation. The function is called with two arguments. The first argument is a
The expression can process all the directories specified in 'path'. |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 expression may be evaluated for command-line completion as well, The function is called only once per |:find| command invocation.
in which case the |v:cmdcomplete| variable will be set to |v:true|, The function can process all the directories specified in 'path'.
otherwise it will be set to |v:false|.
If a match is found, the expression should return a |List| containing If a match is found, the function should return a |List| containing
one or more file names. If a match is not found, the expression one or more file names. If a match is not found, the function
should return an empty List. 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. 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 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 This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons. security reasons.
@@ -2633,18 +2634,18 @@ A jump table for the options with a short description can be found at |Q_op|.
Examples: Examples:
>vim >vim
" Use glob() " Use glob()
func FindExprGlob() func FindFuncGlob(cmdarg, cmdcomplete)
let pat = v:cmdcomplete ? $'{v:fname}*' : v:fname let pat = a:cmdcomplete ? $'{a:cmdarg}*' : a:cmdarg
return glob(pat, v:false, v:true) return glob(pat, v:false, v:true)
endfunc endfunc
set findexpr=FindExprGlob() set findfunc=FindFuncGlob
" Use the 'git ls-files' output " Use the 'git ls-files' output
func FindGitFiles() func FindGitFiles(cmdarg, cmdcomplete)
let fnames = systemlist('git ls-files') let fnames = systemlist('git ls-files')
return fnames->filter('v:val =~? v:fname') return fnames->filter('v:val =~? a:cmdarg')
endfunc endfunc
set findexpr=FindGitFiles() set findfunc=FindGitFiles
< <
*'fixendofline'* *'fixeol'* *'nofixendofline'* *'nofixeol'* *'fixendofline'* *'fixeol'* *'nofixendofline'* *'nofixeol'*

View File

@@ -705,7 +705,7 @@ Short explanation of each option: *option-list*
'fileignorecase' 'fic' ignore case when using file names 'fileignorecase' 'fic' ignore case when using file names
'filetype' 'ft' type of file, used for autocommands 'filetype' 'ft' type of file, used for autocommands
'fillchars' 'fcs' characters to use for displaying special items '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> 'fixendofline' 'fixeol' make sure last line in file has <EOL>
'foldclose' 'fcl' close a fold when the cursor leaves it 'foldclose' 'fcl' close a fold when the cursor leaves it
'foldcolumn' 'fdc' width of the column used to indicate folds 'foldcolumn' 'fdc' width of the column used to indicate folds

View File

@@ -48,11 +48,6 @@ v:cmdbang
can only be used in autocommands. For user commands |<bang>| can only be used in autocommands. For user commands |<bang>|
can be used. 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* *collate-variable*
v:collate v:collate
The current locale setting for collation order of the runtime The current locale setting for collation order of the runtime
@@ -259,8 +254,7 @@ v:fcs_reason
*v:fname* *fname-variable* *v:fname* *fname-variable*
v:fname v:fname
When evaluating 'includeexpr': the file name that was When evaluating 'includeexpr': the file name that was
detected. When evaluating 'findexpr': the argument passed to detected. Empty otherwise.
the |:find| command. Empty otherwise.
*v:fname_diff* *fname_diff-variable* *v:fname_diff* *fname_diff-variable*
v:fname_diff v:fname_diff

View File

@@ -2294,31 +2294,31 @@ vim.wo.fcs = vim.wo.fillchars
vim.go.fillchars = vim.o.fillchars vim.go.fillchars = vim.o.fillchars
vim.go.fcs = vim.go.fillchars vim.go.fcs = vim.go.fillchars
--- 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` --- command. When this option is empty, the internal `file-searching`
--- mechanism is used. --- mechanism is used.
--- ---
--- While evaluating the expression, the `v:fname` variable is set to the --- The value can be the name of a function, a `lambda` or a `Funcref`.
--- argument of the `:find` command. --- See `option-value-function` for more information.
--- ---
--- The expression is evaluated only once per `:find` command invocation. --- The function is called with two arguments. The first argument is a
--- The expression can process all the directories specified in 'path'. --- `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 expression may be evaluated for command-line completion as well, --- The function is called only once per `:find` command invocation.
--- in which case the `v:cmdcomplete` variable will be set to `v:true`, --- The function can process all the directories specified in 'path'.
--- otherwise it will be set to `v:false`.
--- ---
--- If a match is found, the expression should return a `List` containing --- If a match is found, the function should return a `List` containing
--- one or more file names. If a match is not found, the expression --- one or more file names. If a match is not found, the function
--- should return an empty List. --- 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. --- 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 --- 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 --- This option cannot be set from a `modeline` or in the `sandbox`, for
--- security reasons. --- security reasons.
@@ -2327,28 +2327,28 @@ vim.go.fcs = vim.go.fillchars
--- ---
--- ```vim --- ```vim
--- " Use glob() --- " Use glob()
--- func FindExprGlob() --- func FindFuncGlob(cmdarg, cmdcomplete)
--- let pat = v:cmdcomplete ? $'{v:fname}*' : v:fname --- let pat = a:cmdcomplete ? $'{a:cmdarg}*' : a:cmdarg
--- return glob(pat, v:false, v:true) --- return glob(pat, v:false, v:true)
--- endfunc --- endfunc
--- set findexpr=FindExprGlob() --- set findfunc=FindFuncGlob
--- ---
--- " Use the 'git ls-files' output --- " Use the 'git ls-files' output
--- func FindGitFiles() --- func FindGitFiles(cmdarg, cmdcomplete)
--- let fnames = systemlist('git ls-files') --- let fnames = systemlist('git ls-files')
--- return fnames->filter('v:val =~? v:fname') --- return fnames->filter('v:val =~? a:cmdarg')
--- endfunc --- endfunc
--- set findexpr=FindGitFiles() --- set findfunc=FindGitFiles
--- ``` --- ```
--- ---
--- ---
--- @type string --- @type string
vim.o.findexpr = "" vim.o.findfunc = ""
vim.o.fexpr = vim.o.findexpr vim.o.ffu = vim.o.findfunc
vim.bo.findexpr = vim.o.findexpr vim.bo.findfunc = vim.o.findfunc
vim.bo.fexpr = vim.bo.findexpr vim.bo.ffu = vim.bo.findfunc
vim.go.findexpr = vim.o.findexpr vim.go.findfunc = vim.o.findfunc
vim.go.fexpr = vim.go.findexpr vim.go.ffu = vim.go.findfunc
--- When writing a file and this option is on, <EOL> at the end of file --- When writing a file and this option is on, <EOL> at the end of file
--- will be restored if missing. Turn this option off if you want to --- will be restored if missing. Turn this option off if you want to

View File

@@ -44,11 +44,6 @@ vim.v.cmdarg = ...
--- @type integer --- @type integer
vim.v.cmdbang = ... vim.v.cmdbang = ...
--- When evaluating 'findexpr': if 'findexpr' is used for cmdline
--- completion the value is `v:true`, otherwise it is `v:false`.
--- @type boolean
vim.v.cmdcomplete = ...
--- The current locale setting for collation order of the runtime --- The current locale setting for collation order of the runtime
--- environment. This allows Vim scripts to be aware of the --- environment. This allows Vim scripts to be aware of the
--- current locale encoding. Technical: it's the value of --- current locale encoding. Technical: it's the value of
@@ -272,8 +267,7 @@ vim.v.fcs_choice = ...
vim.v.fcs_reason = ... vim.v.fcs_reason = ...
--- When evaluating 'includeexpr': the file name that was --- When evaluating 'includeexpr': the file name that was
--- detected. When evaluating 'findexpr': the argument passed to --- detected. Empty otherwise.
--- the `:find` command. Empty otherwise.
--- @type string --- @type string
vim.v.fname = ... vim.v.fname = ...

View File

@@ -2049,7 +2049,6 @@ void free_buf_options(buf_T *buf, bool free_p_ff)
clear_string_option(&buf->b_p_indk); clear_string_option(&buf->b_p_indk);
clear_string_option(&buf->b_p_fp); clear_string_option(&buf->b_p_fp);
clear_string_option(&buf->b_p_fex); clear_string_option(&buf->b_p_fex);
clear_string_option(&buf->b_p_fexpr);
clear_string_option(&buf->b_p_kp); clear_string_option(&buf->b_p_kp);
clear_string_option(&buf->b_p_mps); clear_string_option(&buf->b_p_mps);
clear_string_option(&buf->b_p_fo); clear_string_option(&buf->b_p_fo);
@@ -2098,6 +2097,8 @@ void free_buf_options(buf_T *buf, bool free_p_ff)
clear_string_option(&buf->b_p_tc); clear_string_option(&buf->b_p_tc);
clear_string_option(&buf->b_p_tfu); clear_string_option(&buf->b_p_tfu);
callback_free(&buf->b_tfu_cb); callback_free(&buf->b_tfu_cb);
clear_string_option(&buf->b_p_ffu);
callback_free(&buf->b_ffu_cb);
clear_string_option(&buf->b_p_dict); clear_string_option(&buf->b_p_dict);
clear_string_option(&buf->b_p_tsr); clear_string_option(&buf->b_p_tsr);
clear_string_option(&buf->b_p_qe); clear_string_option(&buf->b_p_qe);

View File

@@ -543,8 +543,10 @@ struct file_buffer {
Callback b_cfu_cb; ///< 'completefunc' callback Callback b_cfu_cb; ///< 'completefunc' callback
char *b_p_ofu; ///< 'omnifunc' char *b_p_ofu; ///< 'omnifunc'
Callback b_ofu_cb; ///< 'omnifunc' callback Callback b_ofu_cb; ///< 'omnifunc' callback
char *b_p_tfu; ///< 'tagfunc' char *b_p_tfu; ///< 'tagfunc' option value
Callback b_tfu_cb; ///< 'tagfunc' callback Callback b_tfu_cb; ///< 'tagfunc' callback
char *b_p_ffu; ///< 'findfunc' option value
Callback b_ffu_cb; ///< 'findfunc' callback
int b_p_eof; ///< 'endoffile' int b_p_eof; ///< 'endoffile'
int b_p_eol; ///< 'endofline' int b_p_eol; ///< 'endofline'
int b_p_fixeol; ///< 'fixendofline' int b_p_fixeol; ///< 'fixendofline'
@@ -608,7 +610,6 @@ struct file_buffer {
char *b_p_mp; ///< 'makeprg' local value char *b_p_mp; ///< 'makeprg' local value
char *b_p_efm; ///< 'errorformat' local value char *b_p_efm; ///< 'errorformat' local value
char *b_p_ep; ///< 'equalprg' local value char *b_p_ep; ///< 'equalprg' local value
char *b_p_fexpr; ///< 'findexpr' local value
char *b_p_path; ///< 'path' local value char *b_p_path; ///< 'path' local value
int b_p_ar; ///< 'autoread' local value int b_p_ar; ///< 'autoread' local value
char *b_p_tags; ///< 'tags' local value char *b_p_tags; ///< 'tags' local value

View File

@@ -109,7 +109,7 @@ static bool cmdline_fuzzy_completion_supported(const expand_T *const xp)
&& xp->xp_context != EXPAND_FILES && xp->xp_context != EXPAND_FILES
&& xp->xp_context != EXPAND_FILES_IN_PATH && xp->xp_context != EXPAND_FILES_IN_PATH
&& xp->xp_context != EXPAND_FILETYPE && xp->xp_context != EXPAND_FILETYPE
&& xp->xp_context != EXPAND_FINDEXPR && xp->xp_context != EXPAND_FINDFUNC
&& xp->xp_context != EXPAND_HELP && xp->xp_context != EXPAND_HELP
&& xp->xp_context != EXPAND_KEYMAP && xp->xp_context != EXPAND_KEYMAP
&& xp->xp_context != EXPAND_LUA && xp->xp_context != EXPAND_LUA
@@ -1229,7 +1229,7 @@ char *addstar(char *fname, size_t len, int context)
// For help tags the translation is done in find_help_tags(). // For help tags the translation is done in find_help_tags().
// For a tag pattern starting with "/" no translation is needed. // For a tag pattern starting with "/" no translation is needed.
if (context == EXPAND_FINDEXPR if (context == EXPAND_FINDFUNC
|| context == EXPAND_HELP || context == EXPAND_HELP
|| context == EXPAND_COLORS || context == EXPAND_COLORS
|| context == EXPAND_COMPILER || context == EXPAND_COMPILER
@@ -1829,7 +1829,7 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expa
case CMD_sfind: case CMD_sfind:
case CMD_tabfind: case CMD_tabfind:
if (xp->xp_context == EXPAND_FILES) { if (xp->xp_context == EXPAND_FILES) {
xp->xp_context = *get_findexpr() != NUL ? EXPAND_FINDEXPR : EXPAND_FILES_IN_PATH; xp->xp_context = *get_findfunc() != NUL ? EXPAND_FINDFUNC : EXPAND_FILES_IN_PATH;
} }
break; break;
case CMD_cd: case CMD_cd:
@@ -2500,8 +2500,8 @@ static int expand_files_and_dirs(expand_T *xp, char *pat, char ***matches, int *
} }
int ret = FAIL; int ret = FAIL;
if (xp->xp_context == EXPAND_FINDEXPR) { if (xp->xp_context == EXPAND_FINDFUNC) {
ret = expand_findexpr(pat, matches, numMatches); ret = expand_findfunc(pat, matches, numMatches);
} else { } else {
if (xp->xp_context == EXPAND_FILES) { if (xp->xp_context == EXPAND_FILES) {
flags |= EW_FILE; flags |= EW_FILE;
@@ -2722,7 +2722,7 @@ static int ExpandFromContext(expand_T *xp, char *pat, char ***matches, int *numM
if (xp->xp_context == EXPAND_FILES if (xp->xp_context == EXPAND_FILES
|| xp->xp_context == EXPAND_DIRECTORIES || xp->xp_context == EXPAND_DIRECTORIES
|| xp->xp_context == EXPAND_FILES_IN_PATH || xp->xp_context == EXPAND_FILES_IN_PATH
|| xp->xp_context == EXPAND_FINDEXPR || xp->xp_context == EXPAND_FINDFUNC
|| xp->xp_context == EXPAND_DIRS_IN_CDPATH) { || xp->xp_context == EXPAND_DIRS_IN_CDPATH) {
return expand_files_and_dirs(xp, pat, matches, numMatches, flags, options); return expand_files_and_dirs(xp, pat, matches, numMatches, flags, options);
} }

View File

@@ -107,7 +107,7 @@ enum {
EXPAND_KEYMAP, EXPAND_KEYMAP,
EXPAND_DIRS_IN_CDPATH, EXPAND_DIRS_IN_CDPATH,
EXPAND_SHELLCMDLINE, EXPAND_SHELLCMDLINE,
EXPAND_FINDEXPR, EXPAND_FINDFUNC,
EXPAND_CHECKHEALTH, EXPAND_CHECKHEALTH,
EXPAND_LUA, EXPAND_LUA,
}; };

View File

@@ -186,7 +186,7 @@ INIT(= N_("E5767: Cannot use :undo! to redo or move to a different undo branch")
EXTERN const char e_winfixbuf_cannot_go_to_buffer[] EXTERN const char e_winfixbuf_cannot_go_to_buffer[]
INIT(= N_("E1513: Cannot switch buffer. 'winfixbuf' is enabled")); INIT(= N_("E1513: Cannot switch buffer. 'winfixbuf' is enabled"));
EXTERN const char e_invalid_return_type_from_findexpr[] INIT( = N_("E1514: 'findexpr' did not return a List type")); EXTERN const char e_invalid_return_type_from_findfunc[] INIT( = N_("E1514: 'findfunc' did not return a List type"));
EXTERN const char e_trustfile[] INIT(= N_("E5570: Cannot update trust file: %s")); EXTERN const char e_trustfile[] INIT(= N_("E5570: Cannot update trust file: %s"));

View File

@@ -270,7 +270,6 @@ static struct vimvar {
VV(VV_COLLATE, "collate", VAR_STRING, VV_RO), VV(VV_COLLATE, "collate", VAR_STRING, VV_RO),
VV(VV_EXITING, "exiting", VAR_NUMBER, VV_RO), VV(VV_EXITING, "exiting", VAR_NUMBER, VV_RO),
VV(VV_MAXCOL, "maxcol", VAR_NUMBER, VV_RO), VV(VV_MAXCOL, "maxcol", VAR_NUMBER, VV_RO),
VV(VV_CMDCOMPLETE, "cmdcomplete", VAR_BOOL, VV_RO),
// Neovim // Neovim
VV(VV_STDERR, "stderr", VAR_NUMBER, VV_RO), VV(VV_STDERR, "stderr", VAR_NUMBER, VV_RO),
VV(VV_MSGPACK_TYPES, "msgpack_types", VAR_DICT, VV_RO), VV(VV_MSGPACK_TYPES, "msgpack_types", VAR_DICT, VV_RO),
@@ -462,7 +461,6 @@ void eval_init(void)
set_vim_var_nr(VV_HLSEARCH, 1); set_vim_var_nr(VV_HLSEARCH, 1);
set_vim_var_nr(VV_COUNT1, 1); set_vim_var_nr(VV_COUNT1, 1);
set_vim_var_special(VV_EXITING, kSpecialVarNull); set_vim_var_special(VV_EXITING, kSpecialVarNull);
set_vim_var_bool(VV_CMDCOMPLETE, kBoolVarFalse);
set_vim_var_nr(VV_TYPE_NUMBER, VAR_TYPE_NUMBER); set_vim_var_nr(VV_TYPE_NUMBER, VAR_TYPE_NUMBER);
set_vim_var_nr(VV_TYPE_STRING, VAR_TYPE_STRING); set_vim_var_nr(VV_TYPE_STRING, VAR_TYPE_STRING);
@@ -4793,6 +4791,7 @@ bool garbage_collect(bool testing)
ABORTING(set_ref_in_callback)(&buf->b_ofu_cb, copyID, NULL, NULL); ABORTING(set_ref_in_callback)(&buf->b_ofu_cb, copyID, NULL, NULL);
ABORTING(set_ref_in_callback)(&buf->b_tsrfu_cb, copyID, NULL, NULL); ABORTING(set_ref_in_callback)(&buf->b_tsrfu_cb, copyID, NULL, NULL);
ABORTING(set_ref_in_callback)(&buf->b_tfu_cb, copyID, NULL, NULL); ABORTING(set_ref_in_callback)(&buf->b_tfu_cb, copyID, NULL, NULL);
ABORTING(set_ref_in_callback)(&buf->b_ffu_cb, copyID, NULL, NULL);
} }
// 'completefunc', 'omnifunc' and 'thesaurusfunc' callbacks // 'completefunc', 'omnifunc' and 'thesaurusfunc' callbacks
@@ -4804,6 +4803,9 @@ bool garbage_collect(bool testing)
// 'tagfunc' callback // 'tagfunc' callback
ABORTING(set_ref_in_tagfunc)(copyID); ABORTING(set_ref_in_tagfunc)(copyID);
// 'findfunc' callback
ABORTING(set_ref_in_findfunc)(copyID);
FOR_ALL_TAB_WINDOWS(tp, wp) { FOR_ALL_TAB_WINDOWS(tp, wp) {
// window-local variables // window-local variables
ABORTING(set_ref_in_item)(&wp->w_winvar.di_tv, copyID, NULL, NULL); ABORTING(set_ref_in_item)(&wp->w_winvar.di_tv, copyID, NULL, NULL);

View File

@@ -167,7 +167,6 @@ typedef enum {
VV_COLLATE, VV_COLLATE,
VV_EXITING, VV_EXITING,
VV_MAXCOL, VV_MAXCOL,
VV_CMDCOMPLETE,
// Nvim // Nvim
VV_STDERR, VV_STDERR,
VV_MSGPACK_TYPES, VV_MSGPACK_TYPES,

View File

@@ -5165,55 +5165,68 @@ static void ex_wrongmodifier(exarg_T *eap)
eap->errmsg = _(e_invcmd); eap->errmsg = _(e_invcmd);
} }
/// Evaluate the 'findexpr' expression and return the result. When evaluating /// callback function for 'findfunc'
/// the expression, v:fname is set to the ":find" command argument. static Callback ffu_cb;
static list_T *eval_findexpr(const char *pat, bool cmdcomplete)
static Callback *get_findfunc_callback(void)
{
return *curbuf->b_p_ffu != NUL ? &curbuf->b_ffu_cb : &ffu_cb;
}
/// Call 'findfunc' to obtain the list of file names.
static list_T *call_findfunc(char *pat, BoolVarValue cmdcomplete)
{ {
const sctx_T saved_sctx = current_sctx; const sctx_T saved_sctx = current_sctx;
char *findexpr = get_findexpr(); typval_T args[3];
args[0].v_type = VAR_STRING;
set_vim_var_string(VV_FNAME, pat, -1); args[0].vval.v_string = pat;
set_vim_var_bool(VV_CMDCOMPLETE, cmdcomplete ? kBoolVarTrue : kBoolVarFalse); args[1].v_type = VAR_BOOL;
current_sctx = curbuf->b_p_script_ctx[BV_FEXPR].script_ctx; args[1].vval.v_bool = cmdcomplete;
args[2].v_type = VAR_UNKNOWN;
char *arg = skipwhite(findexpr);
// 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++; textlock++;
// Evaluate the expression. If the expression is "FuncName()" call the sctx_T *ctx = get_option_sctx(kOptFindfunc);
// function directly. if (ctx != NULL) {
typval_T tv; current_sctx = *ctx;
list_T *retlist = NULL;
if (eval0_simple_funccal(arg, &tv, NULL, &EVALARG_EVALUATE) == FAIL) {
retlist = NULL;
} else {
if (tv.v_type == VAR_LIST) {
retlist = tv_list_copy(NULL, tv.vval.v_list, true, get_copyID());
} else {
emsg(_(e_invalid_return_type_from_findexpr));
} }
tv_clear(&tv);
}
textlock--;
clear_evalarg(&EVALARG_EVALUATE, NULL);
set_vim_var_string(VV_FNAME, NULL, 0); Callback *cb = get_findfunc_callback();
set_vim_var_bool(VV_CMDCOMPLETE, kBoolVarFalse); typval_T rettv;
int retval = callback_call(cb, 2, args, &rettv);
current_sctx = saved_sctx; current_sctx = saved_sctx;
textlock--;
list_T *retlist = NULL;
if (retval == OK) {
if (rettv.v_type == VAR_LIST) {
retlist = tv_list_copy(NULL, rettv.vval.v_list, false, get_copyID());
} else {
emsg(_(e_invalid_return_type_from_findfunc));
}
tv_clear(&rettv);
}
return retlist; 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. /// Used for expanding the :find, :sfind and :tabfind command argument.
/// Returns OK on success and FAIL otherwise. /// Returns OK on success and FAIL otherwise.
int expand_findexpr(const char *pat, char ***files, int *numMatches) int expand_findfunc(char *pat, char ***files, int *numMatches)
{ {
*numMatches = 0; *numMatches = 0;
*files = NULL; *files = NULL;
list_T *l = eval_findexpr(pat, true); list_T *l = call_findfunc(pat, kBoolVarTrue);
if (l == NULL) { if (l == NULL) {
return FAIL; return FAIL;
} }
@@ -5240,16 +5253,16 @@ int expand_findexpr(const char *pat, char ***files, int *numMatches)
return OK; return OK;
} }
/// 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. /// the n'th matching file.
static char *findexpr_find_file(char *findarg, size_t findarg_len, int count) static char *findfunc_find_file(char *findarg, size_t findarg_len, int count)
{ {
char *ret_fname = NULL; char *ret_fname = NULL;
const char cc = findarg[findarg_len]; const char cc = findarg[findarg_len];
findarg[findarg_len] = NUL; findarg[findarg_len] = NUL;
list_T *fname_list = eval_findexpr(findarg, false); list_T *fname_list = call_findfunc(findarg, kBoolVarFalse);
int fname_count = tv_list_len(fname_list); int fname_count = tv_list_len(fname_list);
if (fname_count == 0) { if (fname_count == 0) {
@@ -5274,6 +5287,51 @@ static char *findexpr_find_file(char *findarg, size_t findarg_len, int count)
return ret_fname; return ret_fname;
} }
/// Process the 'findfunc' option value.
/// Returns NULL on success and an error message on failure.
const char *did_set_findfunc(optset_T *args)
{
buf_T *buf = (buf_T *)args->os_buf;
int retval;
if (*buf->b_p_ffu != NUL) {
// buffer-local option set
retval = option_set_callback_func(buf->b_p_ffu, &buf->b_ffu_cb);
} else {
// global option set
retval = option_set_callback_func(p_ffu, &ffu_cb);
}
if (retval == FAIL) {
return e_invarg;
}
// If the option value starts with <SID> or s:, then replace that with
// the script identifier.
char **varp = (char **)args->os_varp;
char *name = get_scriptlocal_funcname(*varp);
if (name != NULL) {
free_string_option(*varp);
*varp = name;
}
return NULL;
}
void free_findfunc_option(void)
{
callback_free(&ffu_cb);
}
/// Mark the global 'findfunc' callback with "copyID" so that it is not
/// garbage collected.
bool set_ref_in_findfunc(int copyID)
{
bool abort = false;
abort = set_ref_in_callback(&ffu_cb, copyID, NULL, NULL);
return abort;
}
/// :sview [+command] file split window with new file, read-only /// :sview [+command] file split window with new file, read-only
/// :split [[+command] file] split window with current or new file /// :split [[+command] file] split window with current or new file
/// :vsplit [[+command] file] split window vertically with current or new file /// :vsplit [[+command] file] split window vertically with current or new file
@@ -5305,8 +5363,8 @@ void ex_splitview(exarg_T *eap)
} }
if (eap->cmdidx == CMD_sfind || eap->cmdidx == CMD_tabfind) { if (eap->cmdidx == CMD_sfind || eap->cmdidx == CMD_tabfind) {
if (*get_findexpr() != NUL) { if (*get_findfunc() != NUL) {
fname = findexpr_find_file(eap->arg, strlen(eap->arg), fname = findfunc_find_file(eap->arg, strlen(eap->arg),
eap->addr_count > 0 ? eap->line2 : 1); eap->addr_count > 0 ? eap->line2 : 1);
} else { } else {
char *file_to_find = NULL; char *file_to_find = NULL;
@@ -5512,8 +5570,8 @@ static void ex_find(exarg_T *eap)
} }
char *fname = NULL; char *fname = NULL;
if (*get_findexpr() != NUL) { if (*get_findfunc() != NUL) {
fname = findexpr_find_file(eap->arg, strlen(eap->arg), fname = findfunc_find_file(eap->arg, strlen(eap->arg),
eap->addr_count > 0 ? eap->line2 : 1); eap->addr_count > 0 ? eap->line2 : 1);
} else { } else {
char *file_to_find = NULL; char *file_to_find = NULL;

View File

@@ -260,6 +260,7 @@ local function dump_option(i, o)
end end
w([[ w([[
#include "nvim/ex_docmd.h"
#include "nvim/ex_getln.h" #include "nvim/ex_getln.h"
#include "nvim/insexpand.h" #include "nvim/insexpand.h"
#include "nvim/mapping.h" #include "nvim/mapping.h"

View File

@@ -578,6 +578,7 @@ void free_all_options(void)
} }
free_operatorfunc_option(); free_operatorfunc_option();
free_tagfunc_option(); free_tagfunc_option();
free_findfunc_option();
XFREE_CLEAR(fenc_default); XFREE_CLEAR(fenc_default);
XFREE_CLEAR(p_term); XFREE_CLEAR(p_term);
XFREE_CLEAR(p_ttytype); XFREE_CLEAR(p_ttytype);
@@ -4472,8 +4473,8 @@ void *get_varp_scope_from(vimoption_T *p, int scope, buf_T *buf, win_T *win)
switch ((int)p->indir) { switch ((int)p->indir) {
case PV_FP: case PV_FP:
return &(buf->b_p_fp); return &(buf->b_p_fp);
case PV_FEXPR: case PV_FFU:
return &(buf->b_p_fexpr); return &(buf->b_p_ffu);
case PV_EFM: case PV_EFM:
return &(buf->b_p_efm); return &(buf->b_p_efm);
case PV_GP: case PV_GP:
@@ -4595,8 +4596,8 @@ void *get_varp_from(vimoption_T *p, buf_T *buf, win_T *win)
return *buf->b_p_tsrfu != NUL ? &(buf->b_p_tsrfu) : p->var; return *buf->b_p_tsrfu != NUL ? &(buf->b_p_tsrfu) : p->var;
case PV_FP: case PV_FP:
return *buf->b_p_fp != NUL ? &(buf->b_p_fp) : p->var; return *buf->b_p_fp != NUL ? &(buf->b_p_fp) : p->var;
case PV_FEXPR: case PV_FFU:
return *buf->b_p_fexpr != NUL ? &(buf->b_p_fexpr) : p->var; return *buf->b_p_ffu != NUL ? &(buf->b_p_ffu) : p->var;
case PV_EFM: case PV_EFM:
return *buf->b_p_efm != NUL ? &(buf->b_p_efm) : p->var; return *buf->b_p_efm != NUL ? &(buf->b_p_efm) : p->var;
case PV_GP: case PV_GP:
@@ -4868,13 +4869,13 @@ char *get_equalprg(void)
return curbuf->b_p_ep; return curbuf->b_p_ep;
} }
/// 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 *get_findexpr(void) char *get_findfunc(void)
{ {
if (*curbuf->b_p_fexpr == NUL) { if (*curbuf->b_p_ffu == NUL) {
return p_fexpr; return p_ffu;
} }
return curbuf->b_p_fexpr; return curbuf->b_p_ffu;
} }
/// Copy options from one window to another. /// Copy options from one window to another.
@@ -5275,8 +5276,7 @@ void buf_copy_options(buf_T *buf, int flags)
buf->b_p_mp = empty_string_option; buf->b_p_mp = empty_string_option;
buf->b_p_efm = empty_string_option; buf->b_p_efm = empty_string_option;
buf->b_p_ep = empty_string_option; buf->b_p_ep = empty_string_option;
buf->b_p_fexpr = xstrdup(p_fexpr); buf->b_p_ffu = empty_string_option;
COPY_OPT_SCTX(buf, BV_FEXPR);
buf->b_p_kp = empty_string_option; buf->b_p_kp = empty_string_option;
buf->b_p_path = empty_string_option; buf->b_p_path = empty_string_option;
buf->b_p_tags = empty_string_option; buf->b_p_tags = empty_string_option;

View File

@@ -451,7 +451,7 @@ EXTERN char *p_ffs; ///< 'fileformats'
EXTERN int p_fic; ///< 'fileignorecase' EXTERN int p_fic; ///< 'fileignorecase'
EXTERN char *p_ft; ///< 'filetype' EXTERN char *p_ft; ///< 'filetype'
EXTERN char *p_fcs; ///< 'fillchar' EXTERN char *p_fcs; ///< 'fillchar'
EXTERN char *p_fexpr; ///< 'findexpr' EXTERN char *p_ffu; ///< 'findfunc'
EXTERN int p_fixeol; ///< 'fixendofline' EXTERN int p_fixeol; ///< 'fixendofline'
EXTERN char *p_fcl; ///< 'foldclose' EXTERN char *p_fcl; ///< 'foldclose'
EXTERN OptInt p_fdls; ///< 'foldlevelstart' EXTERN OptInt p_fdls; ///< 'foldlevelstart'

View File

@@ -2906,35 +2906,35 @@ return {
varname = 'p_fcs', varname = 'p_fcs',
}, },
{ {
abbreviation = 'fexpr', abbreviation = 'ffu',
cb = 'did_set_optexpr', cb = 'did_set_findfunc',
defaults = { if_true = '' }, defaults = { if_true = '' },
desc = [=[ desc = [=[
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| command. When this option is empty, the internal |file-searching|
mechanism is used. mechanism is used.
While evaluating the expression, the |v:fname| variable is set to the The value can be the name of a function, a |lambda| or a |Funcref|.
argument of the |:find| command. See |option-value-function| for more information.
The expression is evaluated only once per |:find| command invocation. The function is called with two arguments. The first argument is a
The expression can process all the directories specified in 'path'. |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 expression may be evaluated for command-line completion as well, The function is called only once per |:find| command invocation.
in which case the |v:cmdcomplete| variable will be set to |v:true|, The function can process all the directories specified in 'path'.
otherwise it will be set to |v:false|.
If a match is found, the expression should return a |List| containing If a match is found, the function should return a |List| containing
one or more file names. If a match is not found, the expression one or more file names. If a match is not found, the function
should return an empty List. 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. 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 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 This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons. security reasons.
@@ -2942,27 +2942,28 @@ return {
Examples: Examples:
>vim >vim
" Use glob() " Use glob()
func FindExprGlob() func FindFuncGlob(cmdarg, cmdcomplete)
let pat = v:cmdcomplete ? $'{v:fname}*' : v:fname let pat = a:cmdcomplete ? $'{a:cmdarg}*' : a:cmdarg
return glob(pat, v:false, v:true) return glob(pat, v:false, v:true)
endfunc endfunc
set findexpr=FindExprGlob() set findfunc=FindFuncGlob
" Use the 'git ls-files' output " Use the 'git ls-files' output
func FindGitFiles() func FindGitFiles(cmdarg, cmdcomplete)
let fnames = systemlist('git ls-files') let fnames = systemlist('git ls-files')
return fnames->filter('v:val =~? v:fname') return fnames->filter('v:val =~? a:cmdarg')
endfunc endfunc
set findexpr=FindGitFiles() set findfunc=FindGitFiles
< <
]=], ]=],
full_name = 'findexpr', full_name = 'findfunc',
func = true,
scope = { 'global', 'buffer' }, scope = { 'global', 'buffer' },
secure = true, secure = true,
short_desc = N_('expression used for :find'), short_desc = N_('function called for :find'),
tags = { 'E1514' }, tags = { 'E1514' },
type = 'string', type = 'string',
varname = 'p_fexpr', varname = 'p_ffu',
}, },
{ {
abbreviation = 'fixeol', abbreviation = 'fixeol',

View File

@@ -233,9 +233,9 @@ void check_buf_options(buf_T *buf)
check_string_option(&buf->b_p_mp); check_string_option(&buf->b_p_mp);
check_string_option(&buf->b_p_efm); check_string_option(&buf->b_p_efm);
check_string_option(&buf->b_p_ep); check_string_option(&buf->b_p_ep);
check_string_option(&buf->b_p_fexpr);
check_string_option(&buf->b_p_path); check_string_option(&buf->b_p_path);
check_string_option(&buf->b_p_tags); check_string_option(&buf->b_p_tags);
check_string_option(&buf->b_p_ffu);
check_string_option(&buf->b_p_tfu); check_string_option(&buf->b_p_tfu);
check_string_option(&buf->b_p_tc); check_string_option(&buf->b_p_tc);
check_string_option(&buf->b_p_dict); check_string_option(&buf->b_p_dict);
@@ -1886,9 +1886,8 @@ int expand_set_nrformats(optexpand_T *args, int *numMatches, char ***matches)
matches); matches);
} }
/// One of the '*expr' options is changed:, 'diffexpr', 'findexpr', /// One of the '*expr' options is changed:, 'diffexpr', 'foldexpr', 'foldtext',
/// 'foldexpr', 'foldtext', 'formatexpr', 'includeexpr', 'indentexpr', /// 'formatexpr', 'includeexpr', 'indentexpr', 'patchexpr' and 'charconvert'.
/// 'patchexpr' and 'charconvert'.
const char *did_set_optexpr(optset_T *args) const char *did_set_optexpr(optset_T *args)
{ {
char **varp = (char **)args->os_varp; char **varp = (char **)args->os_varp;

View File

@@ -50,13 +50,6 @@ M.vars = {
can be used. can be used.
]=], ]=],
}, },
cmdcomplete = {
type = 'boolean',
desc = [=[
When evaluating 'findexpr': if 'findexpr' is used for cmdline
completion the value is |v:true|, otherwise it is |v:false|.
]=],
},
collate = { collate = {
type = 'string', type = 'string',
desc = [=[ desc = [=[
@@ -291,8 +284,7 @@ M.vars = {
type = 'string', type = 'string',
desc = [=[ desc = [=[
When evaluating 'includeexpr': the file name that was When evaluating 'includeexpr': the file name that was
detected. When evaluating 'findexpr': the argument passed to detected. Empty otherwise.
the |:find| command. Empty otherwise.
]=], ]=],
}, },
fname_diff = { fname_diff = {

View File

@@ -1,6 +1,7 @@
" Test findfile() and finddir() " Test findfile() and finddir()
source check.vim source check.vim
source vim9.vim
let s:files = [ 'Xfinddir1/foo', let s:files = [ 'Xfinddir1/foo',
\ 'Xfinddir1/bar', \ 'Xfinddir1/bar',
@@ -288,223 +289,491 @@ func Test_find_non_existing_path()
let &path = save_path let &path = save_path
endfunc endfunc
" Test for 'findexpr' " Test for 'findfunc'
func Test_findexpr() func Test_findfunc()
CheckUnix CheckUnix
call assert_equal('', &findexpr) call assert_equal('', &findfunc)
call writefile(['aFile'], 'Xfindexpr1.c', 'D') call writefile(['aFile'], 'Xfindfunc1.c', 'D')
call writefile(['bFile'], 'Xfindexpr2.c', 'D') call writefile(['bFile'], 'Xfindfunc2.c', 'D')
call writefile(['cFile'], 'Xfindexpr3.c', 'D') call writefile(['cFile'], 'Xfindfunc3.c', 'D')
" basic tests " basic tests
func FindExpr1() func FindFuncBasic(pat, cmdcomplete)
let fnames = ['Xfindexpr1.c', 'Xfindexpr2.c', 'Xfindexpr3.c'] let fnames = ['Xfindfunc1.c', 'Xfindfunc2.c', 'Xfindfunc3.c']
return fnames->copy()->filter('v:val =~? v:fname') return fnames->copy()->filter('v:val =~? a:pat')
endfunc endfunc
set findexpr=FindExpr1() set findfunc=FindFuncBasic
find Xfindexpr3 find Xfindfunc3
call assert_match('Xfindexpr3.c', @%) call assert_match('Xfindfunc3.c', @%)
bw! bw!
2find Xfind 2find Xfind
call assert_match('Xfindexpr2.c', @%) call assert_match('Xfindfunc2.c', @%)
bw! bw!
call assert_fails('4find Xfind', 'E347: No more file "Xfind" found in path') 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') call assert_fails('find foobar', 'E345: Can''t find file "foobar" in path')
sfind Xfindexpr2.c sfind Xfindfunc2.c
call assert_match('Xfindexpr2.c', @%) call assert_match('Xfindfunc2.c', @%)
call assert_equal(2, winnr('$')) call assert_equal(2, winnr('$'))
%bw! %bw!
call assert_fails('sfind foobar', 'E345: Can''t find file "foobar" in path') call assert_fails('sfind foobar', 'E345: Can''t find file "foobar" in path')
tabfind Xfindexpr3.c tabfind Xfindfunc3.c
call assert_match('Xfindexpr3.c', @%) call assert_match('Xfindfunc3.c', @%)
call assert_equal(2, tabpagenr()) call assert_equal(2, tabpagenr())
%bw! %bw!
call assert_fails('tabfind foobar', 'E345: Can''t find file "foobar" in path') 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 " Buffer-local option
set findexpr=['abc'] func GlobalFindFunc(pat, cmdcomplete)
return ['global']
endfunc
func LocalFindFunc(pat, cmdcomplete)
return ['local']
endfunc
set findfunc=GlobalFindFunc
new new
setlocal findexpr=['def'] setlocal findfunc=LocalFindFunc
find xxxx find xxxx
call assert_equal('def', @%) call assert_equal('local', @%)
wincmd w wincmd w
find xxxx find xxxx
call assert_equal('abc', @%) call assert_equal('global', @%)
aboveleft new aboveleft new
call assert_equal("['abc']", &findexpr) call assert_equal("GlobalFindFunc", &findfunc)
wincmd k wincmd k
aboveleft new aboveleft new
call assert_equal("['abc']", &findexpr) call assert_equal("GlobalFindFunc", &findfunc)
%bw! %bw!
delfunc GlobalFindFunc
delfunc LocalFindFunc
" Empty list " Assign an expression
set findexpr=[] set findfunc=[]
call assert_fails('find xxxx', 'E345: Can''t find file "xxxx" in path') call assert_fails('find xxxx', 'E117: Unknown function: []')
" Error cases " Error cases
" Syntax error in the expression " Function that doesn't any argument
set findexpr=FindExpr1{} func FindFuncNoArg()
call assert_fails('find Xfindexpr1.c', 'E15: Invalid expression') endfunc
set findfunc=FindFuncNoArg
call assert_fails('find Xfindfunc1.c', 'E118: Too many arguments for function: FindFuncNoArg')
delfunc FindFuncNoArg
" Find expression throws an error " Syntax error in the function
func FindExpr2() 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' throw 'find error'
endfunc endfunc
set findexpr=FindExpr2() set findfunc=FindFuncWithThrow
call assert_fails('find Xfindexpr1.c', 'find error') call assert_fails('find Xfindfunc1.c', 'find error')
delfunc FindFuncWithThrow
" Try using a null List as the expression " Try using a null function
set findexpr=v:_null_list "call assert_fails('let &findfunc = test_null_function()', 'E129: Function name required')
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 " Try to create a new window from the find function
func FindExpr3() func FindFuncNewWindow(pat, cmdexpand)
new new
return ["foo"] return ["foo"]
endfunc endfunc
set findexpr=FindExpr3() set findfunc=FindFuncNewWindow
call assert_fails('find Xfindexpr1.c', 'E565: Not allowed to change text or change window') 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 " Try to modify the current buffer from the find function
func FindExpr4() func FindFuncModifyBuf(pat, cmdexpand)
call setline(1, ['abc']) call setline(1, ['abc'])
return ["foo"] return ["foo"]
endfunc endfunc
set findexpr=FindExpr4() set findfunc=FindFuncModifyBuf
call assert_fails('find Xfindexpr1.c', 'E565: Not allowed to change text or change window') call assert_fails('find Xfindfunc1.c', 'E565: Not allowed to change text or change window')
delfunc FindFuncModifyBuf
" Expression returning a string " Return the wrong type from the function
set findexpr='abc' func FindFuncWrongRet(pat, cmdexpand)
call assert_fails('find Xfindexpr1.c', "E1514: 'findexpr' did not return a List type") return 'foo'
endfunc
set findfunc=FindFuncWrongRet
call assert_fails('find Xfindfunc1.c', "E1514: 'findfunc' did not return a List type")
delfunc FindFuncWrongRet
set findexpr& set findfunc&
delfunc! FindExpr1
delfunc! FindExpr2
delfunc! FindExpr3
delfunc! FindExpr4
endfunc endfunc
" Test for using a script-local function for 'findexpr' " Test for using a script-local function for 'findfunc'
func Test_findexpr_scriptlocal_func() func Test_findfunc_scriptlocal_func()
func! s:FindExprScript() func! s:FindFuncScript(pat, cmdexpand)
let g:FindExprArg = v:fname let g:FindFuncArg = a:pat
return ['xxx'] return ['xxx']
endfunc endfunc
set findexpr=s:FindExprScript() set findfunc=s:FindFuncScript
call assert_equal(expand('<SID>') .. 'FindExprScript()', &findexpr) call assert_equal(expand('<SID>') .. 'FindFuncScript', &findfunc)
call assert_equal(expand('<SID>') .. 'FindExprScript()', &g:findexpr) call assert_equal(expand('<SID>') .. 'FindFuncScript', &g:findfunc)
new | only new | only
let g:FindExprArg = '' let g:FindFuncArg = ''
find abc find abc
call assert_equal('abc', g:FindExprArg) call assert_equal('abc', g:FindFuncArg)
bw! bw!
set findexpr=<SID>FindExprScript() set findfunc=<SID>FindFuncScript
call assert_equal(expand('<SID>') .. 'FindExprScript()', &findexpr) call assert_equal(expand('<SID>') .. 'FindFuncScript', &findfunc)
call assert_equal(expand('<SID>') .. 'FindExprScript()', &g:findexpr) call assert_equal(expand('<SID>') .. 'FindFuncScript', &g:findfunc)
new | only new | only
let g:FindExprArg = '' let g:FindFuncArg = ''
find abc find abc
call assert_equal('abc', g:FindExprArg) call assert_equal('abc', g:FindFuncArg)
bw! bw!
let &findexpr = 's:FindExprScript()' let &findfunc = 's:FindFuncScript'
call assert_equal(expand('<SID>') .. 'FindExprScript()', &g:findexpr) call assert_equal(expand('<SID>') .. 'FindFuncScript', &g:findfunc)
new | only new | only
let g:FindExprArg = '' let g:FindFuncArg = ''
find abc find abc
call assert_equal('abc', g:FindExprArg) call assert_equal('abc', g:FindFuncArg)
bw! bw!
let &findexpr = '<SID>FindExprScript()' let &findfunc = '<SID>FindFuncScript'
call assert_equal(expand('<SID>') .. 'FindExprScript()', &g:findexpr) call assert_equal(expand('<SID>') .. 'FindFuncScript', &g:findfunc)
new | only new | only
let g:FindExprArg = '' let g:FindFuncArg = ''
find abc find abc
call assert_equal('abc', g:FindExprArg) call assert_equal('abc', g:FindFuncArg)
bw! bw!
set findexpr= set findfunc=
setglobal findexpr=s:FindExprScript() setglobal findfunc=s:FindFuncScript
setlocal findexpr= setlocal findfunc=
call assert_equal(expand('<SID>') .. 'FindExprScript()', &findexpr) call assert_equal(expand('<SID>') .. 'FindFuncScript', &findfunc)
call assert_equal(expand('<SID>') .. 'FindExprScript()', &g:findexpr) call assert_equal(expand('<SID>') .. 'FindFuncScript', &g:findfunc)
call assert_equal('', &l:findexpr) call assert_equal('', &l:findfunc)
new | only new | only
let g:FindExprArg = '' let g:FindFuncArg = ''
find abc find abc
call assert_equal('abc', g:FindExprArg) call assert_equal('abc', g:FindFuncArg)
bw! bw!
new | only new | only
set findexpr= set findfunc=
setglobal findexpr= setglobal findfunc=
setlocal findexpr=s:FindExprScript() setlocal findfunc=s:FindFuncScript
call assert_equal(expand('<SID>') .. 'FindExprScript()', &findexpr) call assert_equal(expand('<SID>') .. 'FindFuncScript', &findfunc)
call assert_equal(expand('<SID>') .. 'FindExprScript()', &l:findexpr) call assert_equal(expand('<SID>') .. 'FindFuncScript', &l:findfunc)
call assert_equal('', &g:findexpr) call assert_equal('', &g:findfunc)
let g:FindExprArg = '' let g:FindFuncArg = ''
find abc find abc
call assert_equal('abc', g:FindExprArg) call assert_equal('abc', g:FindFuncArg)
bw! bw!
set findexpr= set findfunc=
delfunc s:FindExprScript delfunc s:FindFuncScript
endfunc endfunc
" Test for expanding the argument to the :find command using 'findexpr' " Test for expanding the argument to the :find command using 'findfunc'
func Test_findexpr_expand_arg() func Test_findfunc_expand_arg()
let s:fnames = ['Xfindexpr1.c', 'Xfindexpr2.c', 'Xfindexpr3.c'] let s:fnames = ['Xfindfunc1.c', 'Xfindfunc2.c', 'Xfindfunc3.c']
" 'findexpr' that accepts a regular expression " 'findfunc' that accepts a regular expression
func FindExprRegexp() func FindFuncRegexp(pat, cmdcomplete)
return s:fnames->copy()->filter('v:val =~? v:fname') return s:fnames->copy()->filter('v:val =~? a:pat')
endfunc endfunc
" 'findexpr' that accepts a glob " 'findfunc' that accepts a glob
func FindExprGlob() func FindFuncGlob(pat_arg, cmdcomplete)
let pat = glob2regpat(v:cmdcomplete ? $'*{v:fname}*' : v:fname) let pat = glob2regpat(a:cmdcomplete ? $'*{a:pat_arg}*' : a:pat_arg)
return s:fnames->copy()->filter('v:val =~? pat') return s:fnames->copy()->filter('v:val =~? pat')
endfunc endfunc
for regexp in [v:true, v:false] 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 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 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 ', 'cmdline'))
call assert_equal(s:fnames, getcompletion('find Xfind', 'cmdline')) call assert_equal(s:fnames, getcompletion('find Xfind', 'cmdline'))
let pat = regexp ? 'X.*1\.c' : 'X*1.c' let pat = regexp ? 'X.*1\.c' : 'X*1.c'
call feedkeys($":find {pat}\<Tab>\<C-B>\"\<CR>", "xt") call feedkeys($":find {pat}\<Tab>\<C-B>\"\<CR>", "xt")
call assert_equal('"find Xfindexpr1.c', @:) call assert_equal('"find Xfindfunc1.c', @:)
call assert_equal(['Xfindexpr1.c'], getcompletion($'find {pat}', 'cmdline')) call assert_equal(['Xfindfunc1.c'], getcompletion($'find {pat}', 'cmdline'))
call feedkeys(":find 3\<Tab>\<C-B>\"\<CR>", "xt") call feedkeys(":find 3\<Tab>\<C-B>\"\<CR>", "xt")
call assert_equal('"find Xfindexpr3.c', @:) call assert_equal('"find Xfindfunc3.c', @:)
call assert_equal(['Xfindexpr3.c'], getcompletion($'find 3', 'cmdline')) call assert_equal(['Xfindfunc3.c'], getcompletion($'find 3', 'cmdline'))
call feedkeys(":find Xfind\<C-A>\<C-B>\"\<CR>", "xt") 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 feedkeys(":find abc\<Tab>\<C-B>\"\<CR>", "xt")
call assert_equal('"find abc', @:) call assert_equal('"find abc', @:)
call assert_equal([], getcompletion('find abc', 'cmdline')) call assert_equal([], getcompletion('find abc', 'cmdline'))
endfor endfor
set findexpr& set findfunc&
delfunc! FindExprRegexp delfunc! FindFuncRegexp
delfunc! FindExprGlob delfunc! FindFuncGlob
unlet s:fnames unlet s:fnames
endfunc 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 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 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 " vim: shiftwidth=2 sts=2 expandtab

View File

@@ -1713,10 +1713,10 @@ func Test_completefunc_callback()
call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
" Using Vim9 lambda expression in legacy context should fail " Using Vim9 lambda expression in legacy context should fail
" set completefunc=(a,\ b)\ =>\ CompleteFunc1(21,\ a,\ b) set completefunc=(a,\ b)\ =>\ CompleteFunc1(21,\ a,\ b)
new | only new | only
let g:CompleteFunc1Args = [] let g:CompleteFunc1Args = []
" call assert_fails('call feedkeys("A\<C-X>\<C-U>\<Esc>", "x")', 'E117:') call assert_fails('call feedkeys("A\<C-X>\<C-U>\<Esc>", "x")', 'E117:')
call assert_equal([], g:CompleteFunc1Args) call assert_equal([], g:CompleteFunc1Args)
" set 'completefunc' to a partial with dict. This used to cause a crash. " set 'completefunc' to a partial with dict. This used to cause a crash.
@@ -1970,10 +1970,10 @@ func Test_omnifunc_callback()
call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
" Using Vim9 lambda expression in legacy context should fail " Using Vim9 lambda expression in legacy context should fail
" set omnifunc=(a,\ b)\ =>\ OmniFunc1(21,\ a,\ b) set omnifunc=(a,\ b)\ =>\ OmniFunc1(21,\ a,\ b)
new | only new | only
let g:OmniFunc1Args = [] let g:OmniFunc1Args = []
" call assert_fails('call feedkeys("A\<C-X>\<C-O>\<Esc>", "x")', 'E117:') call assert_fails('call feedkeys("A\<C-X>\<C-O>\<Esc>", "x")', 'E117:')
call assert_equal([], g:OmniFunc1Args) call assert_equal([], g:OmniFunc1Args)
" set 'omnifunc' to a partial with dict. This used to cause a crash. " set 'omnifunc' to a partial with dict. This used to cause a crash.
@@ -2250,10 +2250,10 @@ func Test_thesaurusfunc_callback()
call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
" Using Vim9 lambda expression in legacy context should fail " Using Vim9 lambda expression in legacy context should fail
" set thesaurusfunc=(a,\ b)\ =>\ TsrFunc1(21,\ a,\ b) set thesaurusfunc=(a,\ b)\ =>\ TsrFunc1(21,\ a,\ b)
new | only new | only
let g:TsrFunc1Args = [] let g:TsrFunc1Args = []
" call assert_fails('call feedkeys("A\<C-X>\<C-T>\<Esc>", "x")', 'E117:') call assert_fails('call feedkeys("A\<C-X>\<C-T>\<Esc>", "x")', 'E117:')
call assert_equal([], g:TsrFunc1Args) call assert_equal([], g:TsrFunc1Args)
bw! bw!

View File

@@ -217,7 +217,7 @@ func Test_modeline_fails_always()
call s:modeline_fails('equalprg', 'equalprg=Something()', 'E520:') call s:modeline_fails('equalprg', 'equalprg=Something()', 'E520:')
call s:modeline_fails('errorfile', 'errorfile=Something()', 'E520:') call s:modeline_fails('errorfile', 'errorfile=Something()', 'E520:')
call s:modeline_fails('exrc', 'exrc=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('formatprg', 'formatprg=Something()', 'E520:')
call s:modeline_fails('fsync', 'fsync=Something()', 'E520:') call s:modeline_fails('fsync', 'fsync=Something()', 'E520:')
call s:modeline_fails('grepprg', 'grepprg=Something()', 'E520:') call s:modeline_fails('grepprg', 'grepprg=Something()', 'E520:')

View File

@@ -692,9 +692,9 @@ func Test_opfunc_callback()
delfunc s:OpFunc3 delfunc s:OpFunc3
" Using Vim9 lambda expression in legacy context should fail " Using Vim9 lambda expression in legacy context should fail
" set opfunc=(a)\ =>\ OpFunc1(24,\ a) set opfunc=(a)\ =>\ OpFunc1(24,\ a)
let g:OpFunc1Args = [] let g:OpFunc1Args = []
" call assert_fails('normal! g@l', 'E117:') call assert_fails('normal! g@l', 'E117:')
call assert_equal([], g:OpFunc1Args) call assert_equal([], g:OpFunc1Args)
" set 'operatorfunc' to a partial with dict. This used to cause a crash. " set 'operatorfunc' to a partial with dict. This used to cause a crash.

View File

@@ -1559,7 +1559,7 @@ endfunc
" Test for changing options in a sandbox " Test for changing options in a sandbox
func Test_opt_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 set ' .. opt .. '?', 'E48:')
call assert_fails('sandbox let &' .. opt .. ' = 1', 'E48:') call assert_fails('sandbox let &' .. opt .. ' = 1', 'E48:')
endfor endfor

View File

@@ -291,10 +291,10 @@ func Test_tagfunc_callback()
call assert_fails("echo taglist('a')", "E987:") call assert_fails("echo taglist('a')", "E987:")
" Using Vim9 lambda expression in legacy context should fail " Using Vim9 lambda expression in legacy context should fail
" set tagfunc=(a,\ b,\ c)\ =>\ g:TagFunc1(21,\ a,\ b,\ c) set tagfunc=(a,\ b,\ c)\ =>\ g:TagFunc1(21,\ a,\ b,\ c)
new new
let g:TagFunc1Args = [] let g:TagFunc1Args = []
" call assert_fails("tag a17", "E117:") call assert_fails("tag a17", "E117:")
call assert_equal([], g:TagFunc1Args) call assert_equal([], g:TagFunc1Args)
bw! bw!