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

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

closes: vim/vim#15901
closes: vim/vim#15905

aeb1c97db5

Co-authored-by: Yegappan Lakshmanan <yegappan@yahoo.com>
This commit is contained in:
zeertzjq
2024-10-28 15:14:15 +08:00
parent 42fa3d080e
commit 378d9135e7
19 changed files with 472 additions and 33 deletions

View File

@@ -2598,6 +2598,50 @@ 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'*
'findexpr' 'fexpr' string (default "")
global or local to buffer |global-local|
Expression that is evaluated to obtain the filename(s) for the |:find|
command. When this option is empty, the internal |file-searching|
mechanism is used.
While evaluating the expression, the |v:fname| variable is set to the
argument of the |:find| command.
The expression is evaluated only once per |:find| command invocation.
The expression can process all the directories specified in 'path'.
If a match is found, the expression should return a |List| containing
one or more file names. If a match is not found, the expression
should return an empty List.
If any errors are encountered during the expression evaluation, an
empty List is used as the return value.
Using a function call without arguments is faster |expr-option-function|
It is not allowed to change text or jump to another window while
evaluating 'findexpr' |textlock|.
This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.
Examples:
>vim
" Use glob()
func FindExprGlob()
return glob(v:fname, v:false, v:true)
endfunc
set findexpr=FindExprGlob()
" Use the 'git ls-files' output
func FindGitFiles()
let fnames = systemlist('git ls-files')
return fnames->filter('v:val =~? v:fname')
endfunc
set findexpr=FindGitFiles()
<
*'fixendofline'* *'fixeol'* *'nofixendofline'* *'nofixeol'* *'fixendofline'* *'fixeol'* *'nofixendofline'* *'nofixeol'*
'fixendofline' 'fixeol' boolean (default on) 'fixendofline' 'fixeol' boolean (default on)
local to buffer local to buffer

View File

@@ -705,6 +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|
'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

@@ -254,7 +254,8 @@ 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. Empty otherwise. detected. When evaluating 'findexpr': the argument passed to
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,6 +2294,57 @@ 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`
--- command. When this option is empty, the internal `file-searching`
--- mechanism is used.
---
--- While evaluating the expression, the `v:fname` variable is set to the
--- argument of the `:find` command.
---
--- The expression is evaluated only once per `:find` command invocation.
--- The expression can process all the directories specified in 'path'.
---
--- If a match is found, the expression should return a `List` containing
--- one or more file names. If a match is not found, the expression
--- should return an empty List.
---
--- If any errors are encountered during the expression evaluation, an
--- empty List is used as the return value.
---
--- Using a function call without arguments is faster `expr-option-function`
---
--- It is not allowed to change text or jump to another window while
--- evaluating 'findexpr' `textlock`.
---
--- This option cannot be set from a `modeline` or in the `sandbox`, for
--- security reasons.
---
--- Examples:
---
--- ```vim
--- " Use glob()
--- func FindExprGlob()
--- return glob(v:fname, v:false, v:true)
--- endfunc
--- set findexpr=FindExprGlob()
---
--- " Use the 'git ls-files' output
--- func FindGitFiles()
--- let fnames = systemlist('git ls-files')
--- return fnames->filter('v:val =~? v:fname')
--- endfunc
--- set findexpr=FindGitFiles()
--- ```
---
---
--- @type string
vim.o.findexpr = ""
vim.o.fexpr = vim.o.findexpr
vim.bo.findexpr = vim.o.findexpr
vim.bo.fexpr = vim.bo.findexpr
vim.go.findexpr = vim.o.findexpr
vim.go.fexpr = vim.go.findexpr
--- 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
--- preserve the situation from the original file. --- preserve the situation from the original file.

View File

@@ -267,7 +267,8 @@ 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. Empty otherwise. --- detected. When evaluating 'findexpr': the argument passed to
--- the `:find` command. Empty otherwise.
--- @type string --- @type string
vim.v.fname = ... vim.v.fname = ...

View File

@@ -2049,6 +2049,7 @@ 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);

View File

@@ -608,6 +608,7 @@ 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

@@ -156,6 +156,11 @@ EXTERN const char e_luv_api_disabled[] INIT(= N_("E5560: %s must not be called i
EXTERN const char e_floatonly[] INIT(= N_("E5601: Cannot close window, only floating window would remain")); EXTERN const char e_floatonly[] INIT(= N_("E5601: Cannot close window, only floating window would remain"));
EXTERN const char e_floatexchange[] INIT(= N_("E5602: Cannot exchange or rotate float")); EXTERN const char e_floatexchange[] INIT(= N_("E5602: Cannot exchange or rotate float"));
EXTERN const char e_cant_find_directory_str_in_cdpath[] INIT(= N_("E344: Can't find directory \"%s\" in cdpath"));
EXTERN const char e_cant_find_file_str_in_path[] INIT(= N_("E345: Can't find file \"%s\" in path"));
EXTERN const char e_no_more_directory_str_found_in_cdpath[] INIT(= N_("E346: No more directory \"%s\" found in cdpath"));
EXTERN const char e_no_more_file_str_found_in_path[] INIT(= N_("E347: No more file \"%s\" found in path"));
EXTERN const char e_cannot_define_autocommands_for_all_events[] INIT(= N_("E1155: Cannot define autocommands for ALL events")); EXTERN const char e_cannot_define_autocommands_for_all_events[] INIT(= N_("E1155: Cannot define autocommands for ALL events"));
EXTERN const char e_resulting_text_too_long[] INIT(= N_("E1240: Resulting text too long")); EXTERN const char e_resulting_text_too_long[] INIT(= N_("E1240: Resulting text too long"));

View File

@@ -2631,7 +2631,7 @@ static int may_call_simple_func(const char *arg, typval_T *rettv)
/// Handle zero level expression with optimization for a simple function call. /// Handle zero level expression with optimization for a simple function call.
/// Same arguments and return value as eval0(). /// Same arguments and return value as eval0().
static int eval0_simple_funccal(char *arg, typval_T *rettv, exarg_T *eap, evalarg_T *const evalarg) int eval0_simple_funccal(char *arg, typval_T *rettv, exarg_T *eap, evalarg_T *const evalarg)
{ {
int r = may_call_simple_func(arg, rettv); int r = may_call_simple_func(arg, rettv);

View File

@@ -5165,6 +5165,90 @@ 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
/// the expression, v:fname is set to the ":find" command argument.
static list_T *eval_findexpr(const char *ptr, size_t len)
{
const sctx_T saved_sctx = current_sctx;
bool use_sandbox = false;
char *findexpr;
if (*curbuf->b_p_fexpr == NUL) {
use_sandbox = was_set_insecurely(curwin, kOptFindexpr, OPT_GLOBAL);
findexpr = p_fexpr;
} else {
use_sandbox = was_set_insecurely(curwin, kOptFindexpr, OPT_LOCAL);
findexpr = curbuf->b_p_fexpr;
}
set_vim_var_string(VV_FNAME, ptr, (ptrdiff_t)len);
current_sctx = curbuf->b_p_script_ctx[BV_FEXPR].script_ctx;
char *arg = skipwhite(findexpr);
if (use_sandbox) {
sandbox++;
}
textlock++;
// Evaluate the expression. If the expression is "FuncName()" call the
// function directly.
typval_T tv;
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());
}
tv_clear(&tv);
}
if (use_sandbox) {
sandbox--;
}
textlock--;
clear_evalarg(&EVALARG_EVALUATE, NULL);
set_vim_var_string(VV_FNAME, NULL, 0);
current_sctx = saved_sctx;
return retlist;
}
/// Use 'findexpr' to find file 'findarg'. The 'count' argument is used to find
/// the n'th matching file.
static char *findexpr_find_file(char *findarg, size_t findarg_len, int count)
{
char *ret_fname = NULL;
const char cc = findarg[findarg_len];
findarg[findarg_len] = NUL;
list_T *fname_list = eval_findexpr(findarg, findarg_len);
int fname_count = tv_list_len(fname_list);
if (fname_count == 0) {
semsg(_(e_cant_find_file_str_in_path), findarg);
} else {
if (count > fname_count) {
semsg(_(e_no_more_file_str_found_in_path), findarg);
} else {
listitem_T *li = tv_list_find(fname_list, count - 1);
if (li != NULL && TV_LIST_ITEM_TV(li)->v_type == VAR_STRING) {
ret_fname = xstrdup(TV_LIST_ITEM_TV(li)->vval.v_string);
}
}
}
if (fname_list != NULL) {
tv_list_free(fname_list);
}
findarg[findarg_len] = cc;
return ret_fname;
}
/// :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
@@ -5196,13 +5280,17 @@ 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) {
fname = findexpr_find_file(eap->arg, strlen(eap->arg),
eap->addr_count > 0 ? eap->line2 : 1);
} else {
char *file_to_find = NULL; char *file_to_find = NULL;
char *search_ctx = NULL; char *search_ctx = NULL;
fname = find_file_in_path(eap->arg, strlen(eap->arg), fname = find_file_in_path(eap->arg, strlen(eap->arg), FNAME_MESS, true,
FNAME_MESS, true, curbuf->b_ffname, curbuf->b_ffname, &file_to_find, &search_ctx);
&file_to_find, &search_ctx);
xfree(file_to_find); xfree(file_to_find);
vim_findfile_cleanup(search_ctx); vim_findfile_cleanup(search_ctx);
}
if (fname == NULL) { if (fname == NULL) {
goto theend; goto theend;
} }
@@ -5398,23 +5486,28 @@ static void ex_find(exarg_T *eap)
return; return;
} }
char *fname = NULL;
if (*get_findexpr() != NUL) {
fname = findexpr_find_file(eap->arg, strlen(eap->arg),
eap->addr_count > 0 ? eap->line2 : 1);
} else {
char *file_to_find = NULL; char *file_to_find = NULL;
char *search_ctx = NULL; char *search_ctx = NULL;
char *fname = find_file_in_path(eap->arg, strlen(eap->arg), fname = find_file_in_path(eap->arg, strlen(eap->arg), FNAME_MESS, true,
FNAME_MESS, true, curbuf->b_ffname, curbuf->b_ffname, &file_to_find, &search_ctx);
&file_to_find, &search_ctx);
if (eap->addr_count > 0) { if (eap->addr_count > 0) {
// Repeat finding the file "count" times. This matters when it appears // Repeat finding the file "count" times. This matters when it appears
// several times in the path. // several times in the path.
linenr_T count = eap->line2; linenr_T count = eap->line2;
while (fname != NULL && --count > 0) { while (fname != NULL && --count > 0) {
xfree(fname); xfree(fname);
fname = find_file_in_path(NULL, 0, FNAME_MESS, false, curbuf->b_ffname, fname = find_file_in_path(NULL, 0, FNAME_MESS, false,
&file_to_find, &search_ctx); curbuf->b_ffname, &file_to_find, &search_ctx);
} }
} }
xfree(file_to_find); xfree(file_to_find);
vim_findfile_cleanup(search_ctx); vim_findfile_cleanup(search_ctx);
}
if (fname == NULL) { if (fname == NULL) {
return; return;

View File

@@ -1489,15 +1489,15 @@ char *find_file_in_path_option(char *ptr, size_t len, int options, int first, ch
if (file_name == NULL && (options & FNAME_MESS)) { if (file_name == NULL && (options & FNAME_MESS)) {
if (first == true) { if (first == true) {
if (find_what == FINDFILE_DIR) { if (find_what == FINDFILE_DIR) {
semsg(_("E344: Can't find directory \"%s\" in cdpath"), *file_to_find); semsg(_(e_cant_find_directory_str_in_cdpath), *file_to_find);
} else { } else {
semsg(_("E345: Can't find file \"%s\" in path"), *file_to_find); semsg(_(e_cant_find_file_str_in_path), *file_to_find);
} }
} else { } else {
if (find_what == FINDFILE_DIR) { if (find_what == FINDFILE_DIR) {
semsg(_("E346: No more directory \"%s\" found in cdpath"), *file_to_find); semsg(_(e_no_more_directory_str_found_in_cdpath), *file_to_find);
} else { } else {
semsg(_("E347: No more file \"%s\" found in path"), *file_to_find); semsg(_(e_no_more_file_str_found_in_path), *file_to_find);
} }
} }
} }

View File

@@ -4530,6 +4530,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:
return &(buf->b_p_fexpr);
case PV_EFM: case PV_EFM:
return &(buf->b_p_efm); return &(buf->b_p_efm);
case PV_GP: case PV_GP:
@@ -4651,6 +4653,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:
return *buf->b_p_fexpr != NUL ? &(buf->b_p_fexpr) : 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:
@@ -4922,6 +4926,15 @@ 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.
char *get_findexpr(void)
{
if (*curbuf->b_p_fexpr == NUL) {
return p_fexpr;
}
return curbuf->b_p_fexpr;
}
/// Copy options from one window to another. /// Copy options from one window to another.
/// Used when splitting a window. /// Used when splitting a window.
void win_copy_options(win_T *wp_from, win_T *wp_to) void win_copy_options(win_T *wp_from, win_T *wp_to)
@@ -5320,6 +5333,8 @@ 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);
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,6 +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 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

@@ -2905,6 +2905,59 @@ return {
type = 'string', type = 'string',
varname = 'p_fcs', varname = 'p_fcs',
}, },
{
abbreviation = 'fexpr',
cb = 'did_set_optexpr',
defaults = { if_true = '' },
desc = [=[
Expression that is evaluated to obtain the filename(s) for the |:find|
command. When this option is empty, the internal |file-searching|
mechanism is used.
While evaluating the expression, the |v:fname| variable is set to the
argument of the |:find| command.
The expression is evaluated only once per |:find| command invocation.
The expression can process all the directories specified in 'path'.
If a match is found, the expression should return a |List| containing
one or more file names. If a match is not found, the expression
should return an empty List.
If any errors are encountered during the expression evaluation, an
empty List is used as the return value.
Using a function call without arguments is faster |expr-option-function|
It is not allowed to change text or jump to another window while
evaluating 'findexpr' |textlock|.
This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.
Examples:
>vim
" Use glob()
func FindExprGlob()
return glob(v:fname, v:false, v:true)
endfunc
set findexpr=FindExprGlob()
" Use the 'git ls-files' output
func FindGitFiles()
let fnames = systemlist('git ls-files')
return fnames->filter('v:val =~? v:fname')
endfunc
set findexpr=FindGitFiles()
<
]=],
full_name = 'findexpr',
scope = { 'global', 'buffer' },
secure = true,
short_desc = N_('expression used for :find'),
type = 'string',
varname = 'p_fexpr',
},
{ {
abbreviation = 'fixeol', abbreviation = 'fixeol',
cb = 'did_set_eof_eol_fixeol_bomb', cb = 'did_set_eof_eol_fixeol_bomb',

View File

@@ -233,6 +233,7 @@ 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_tfu); check_string_option(&buf->b_p_tfu);
@@ -1885,8 +1886,9 @@ int expand_set_nrformats(optexpand_T *args, int *numMatches, char ***matches)
matches); matches);
} }
/// One of the '*expr' options is changed:, 'diffexpr', 'foldexpr', 'foldtext', /// One of the '*expr' options is changed:, 'diffexpr', 'findexpr',
/// 'formatexpr', 'includeexpr', 'indentexpr', 'patchexpr' and 'charconvert'. /// 'foldexpr', 'foldtext', 'formatexpr', 'includeexpr', 'indentexpr',
/// '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

@@ -284,7 +284,8 @@ 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. Empty otherwise. detected. When evaluating 'findexpr': the argument passed to
the |:find| command. Empty otherwise.
]=], ]=],
}, },
fname_diff = { fname_diff = {

View File

@@ -1,5 +1,7 @@
" Test findfile() and finddir() " Test findfile() and finddir()
source check.vim
let s:files = [ 'Xfinddir1/foo', let s:files = [ 'Xfinddir1/foo',
\ 'Xfinddir1/bar', \ 'Xfinddir1/bar',
\ 'Xfinddir1/Xdir2/foo', \ 'Xfinddir1/Xdir2/foo',
@@ -286,4 +288,170 @@ func Test_find_non_existing_path()
let &path = save_path let &path = save_path
endfunc endfunc
" Test for 'findexpr'
func Test_findexpr()
CheckUnix
call assert_equal('', &findexpr)
call writefile(['aFile'], 'Xfindexpr1.c', 'D')
call writefile(['bFile'], 'Xfindexpr2.c', 'D')
call writefile(['cFile'], 'Xfindexpr3.c', 'D')
" basic tests
func FindExpr1()
let fnames = ['Xfindexpr1.c', 'Xfindexpr2.c', 'Xfindexpr3.c']
"return fnames->copy()->filter('v:val =~? v:fname')->join("\n")
return fnames->copy()->filter('v:val =~? v:fname')
endfunc
set findexpr=FindExpr1()
find Xfindexpr3
call assert_match('Xfindexpr3.c', @%)
bw!
2find Xfind
call assert_match('Xfindexpr2.c', @%)
bw!
call assert_fails('4find Xfind', 'E347: No more file "Xfind" found in path')
call assert_fails('find foobar', 'E345: Can''t find file "foobar" in path')
sfind Xfindexpr2.c
call assert_match('Xfindexpr2.c', @%)
call assert_equal(2, winnr('$'))
%bw!
call assert_fails('sfind foobar', 'E345: Can''t find file "foobar" in path')
tabfind Xfindexpr3.c
call assert_match('Xfindexpr3.c', @%)
call assert_equal(2, tabpagenr())
%bw!
call assert_fails('tabfind foobar', 'E345: Can''t find file "foobar" in path')
" Buffer-local option
set findexpr=['abc']
new
setlocal findexpr=['def']
find xxxx
call assert_equal('def', @%)
wincmd w
find xxxx
call assert_equal('abc', @%)
aboveleft new
call assert_equal("['abc']", &findexpr)
wincmd k
aboveleft new
call assert_equal("['abc']", &findexpr)
%bw!
" Empty list
set findexpr=[]
call assert_fails('find xxxx', 'E345: Can''t find file "xxxx" in path')
" Error cases
" Syntax error in the expression
set findexpr=FindExpr1{}
call assert_fails('find Xfindexpr1.c', 'E15: Invalid expression')
" Find expression throws an error
func FindExpr2()
throw 'find error'
endfunc
set findexpr=FindExpr2()
call assert_fails('find Xfindexpr1.c', 'find error')
" Try using a null string as the expression
set findexpr=v:_null_string
call assert_fails('find Xfindexpr1.c', 'E345: Can''t find file "Xfindexpr1.c" in path')
" Try to create a new window from the find expression
func FindExpr3()
new
return ["foo"]
endfunc
set findexpr=FindExpr3()
call assert_fails('find Xfindexpr1.c', 'E565: Not allowed to change text or change window')
" Try to modify the current buffer from the find expression
func FindExpr4()
call setline(1, ['abc'])
return ["foo"]
endfunc
set findexpr=FindExpr4()
call assert_fails('find Xfindexpr1.c', 'E565: Not allowed to change text or change window')
set findexpr&
delfunc! FindExpr1
delfunc! FindExpr2
delfunc! FindExpr3
delfunc! FindExpr4
endfunc
" Test for using a script-local function for 'findexpr'
func Test_findexpr_scriptlocal_func()
func! s:FindExprScript()
let g:FindExprArg = v:fname
return ['xxx']
endfunc
set findexpr=s:FindExprScript()
call assert_equal(expand('<SID>') .. 'FindExprScript()', &findexpr)
call assert_equal(expand('<SID>') .. 'FindExprScript()', &g:findexpr)
new | only
let g:FindExprArg = ''
find abc
call assert_equal('abc', g:FindExprArg)
bw!
set findexpr=<SID>FindExprScript()
call assert_equal(expand('<SID>') .. 'FindExprScript()', &findexpr)
call assert_equal(expand('<SID>') .. 'FindExprScript()', &g:findexpr)
new | only
let g:FindExprArg = ''
find abc
call assert_equal('abc', g:FindExprArg)
bw!
let &findexpr = 's:FindExprScript()'
call assert_equal(expand('<SID>') .. 'FindExprScript()', &g:findexpr)
new | only
let g:FindExprArg = ''
find abc
call assert_equal('abc', g:FindExprArg)
bw!
let &findexpr = '<SID>FindExprScript()'
call assert_equal(expand('<SID>') .. 'FindExprScript()', &g:findexpr)
new | only
let g:FindExprArg = ''
find abc
call assert_equal('abc', g:FindExprArg)
bw!
set findexpr=
setglobal findexpr=s:FindExprScript()
setlocal findexpr=
call assert_equal(expand('<SID>') .. 'FindExprScript()', &findexpr)
call assert_equal(expand('<SID>') .. 'FindExprScript()', &g:findexpr)
call assert_equal('', &l:findexpr)
new | only
let g:FindExprArg = ''
find abc
call assert_equal('abc', g:FindExprArg)
bw!
new | only
set findexpr=
setglobal findexpr=
setlocal findexpr=s:FindExprScript()
call assert_equal(expand('<SID>') .. 'FindExprScript()', &findexpr)
call assert_equal(expand('<SID>') .. 'FindExprScript()', &l:findexpr)
call assert_equal('', &g:findexpr)
let g:FindExprArg = ''
find abc
call assert_equal('abc', g:FindExprArg)
bw!
set findexpr=
delfunc s:FindExprScript
endfunc
" vim: shiftwidth=2 sts=2 expandtab " vim: shiftwidth=2 sts=2 expandtab

View File

@@ -217,6 +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('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

@@ -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'] for opt in ['backupdir', 'cdpath', 'exrc', 'findexpr']
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