Merge pull request #30979 from zeertzjq/vim-9.1.0810

vim-patch:9.1.{0810,0811,0821}: 'findexpr'
This commit is contained in:
zeertzjq 2024-10-29 09:01:46 +08:00 committed by GitHub
commit 0e32c48060
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 612 additions and 49 deletions

View File

@ -2598,6 +2598,55 @@ A jump table for the options with a short description can be found at |Q_op|.
eob EndOfBuffer |hl-EndOfBuffer|
lastline NonText |hl-NonText|
*'findexpr'* *'fexpr'* *E1514*
'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'.
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
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()
let pat = v:cmdcomplete ? $'{v:fname}*' : v:fname
return glob(pat, v:false, v:true)
endfunc
set findexpr=FindExprGlob()
" Use the 'git ls-files' output
func FindGitFiles()
let fnames = systemlist('git ls-files')
return fnames->filter('v:val =~? v:fname')
endfunc
set findexpr=FindGitFiles()
<
*'fixendofline'* *'fixeol'* *'nofixendofline'* *'nofixeol'*
'fixendofline' 'fixeol' boolean (default on)
local to buffer

View File

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

View File

@ -48,6 +48,11 @@ v:cmdbang
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
@ -254,7 +259,8 @@ v:fcs_reason
*v:fname* *fname-variable*
v:fname
When evaluating 'includeexpr': the file name that was
detected. Empty otherwise.
detected. When evaluating 'findexpr': the argument passed to
the |:find| command. Empty otherwise.
*v:fname_diff* *fname_diff-variable*
v:fname_diff

View File

@ -2294,6 +2294,62 @@ vim.wo.fcs = vim.wo.fillchars
vim.go.fillchars = vim.o.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'.
---
--- 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
--- 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()
--- let pat = v:cmdcomplete ? $'{v:fname}*' : v:fname
--- return glob(pat, 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
--- will be restored if missing. Turn this option off if you want to
--- preserve the situation from the original file.

View File

@ -44,6 +44,11 @@ vim.v.cmdarg = ...
--- @type integer
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
--- environment. This allows Vim scripts to be aware of the
--- current locale encoding. Technical: it's the value of
@ -267,7 +272,8 @@ vim.v.fcs_choice = ...
vim.v.fcs_reason = ...
--- 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
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_fp);
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_mps);
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_efm; ///< 'errorformat' local value
char *b_p_ep; ///< 'equalprg' local value
char *b_p_fexpr; ///< 'findexpr' local value
char *b_p_path; ///< 'path' local value
int b_p_ar; ///< 'autoread' local value
char *b_p_tags; ///< 'tags' local value

View File

@ -109,6 +109,7 @@ static bool cmdline_fuzzy_completion_supported(const expand_T *const xp)
&& 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_HELP
&& xp->xp_context != EXPAND_KEYMAP
&& xp->xp_context != EXPAND_LUA
@ -1228,7 +1229,8 @@ char *addstar(char *fname, size_t len, int context)
// For help tags the translation is done in find_help_tags().
// For a tag pattern starting with "/" no translation is needed.
if (context == EXPAND_HELP
if (context == EXPAND_FINDEXPR
|| context == EXPAND_HELP
|| context == EXPAND_COLORS
|| context == EXPAND_COMPILER
|| context == EXPAND_OWNSYNTAX
@ -1827,7 +1829,7 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expa
case CMD_sfind:
case CMD_tabfind:
if (xp->xp_context == EXPAND_FILES) {
xp->xp_context = EXPAND_FILES_IN_PATH;
xp->xp_context = *get_findexpr() != NUL ? EXPAND_FINDEXPR : EXPAND_FILES_IN_PATH;
}
break;
case CMD_cd:
@ -2497,21 +2499,25 @@ static int expand_files_and_dirs(expand_T *xp, char *pat, char ***matches, int *
}
}
if (xp->xp_context == EXPAND_FILES) {
flags |= EW_FILE;
} else if (xp->xp_context == EXPAND_FILES_IN_PATH) {
flags |= (EW_FILE | EW_PATH);
} else if (xp->xp_context == EXPAND_DIRS_IN_CDPATH) {
flags = (flags | EW_DIR | EW_CDPATH) & ~EW_FILE;
int ret = FAIL;
if (xp->xp_context == EXPAND_FINDEXPR) {
ret = expand_findexpr(pat, matches, numMatches);
} else {
flags = (flags | EW_DIR) & ~EW_FILE;
if (xp->xp_context == EXPAND_FILES) {
flags |= EW_FILE;
} else if (xp->xp_context == EXPAND_FILES_IN_PATH) {
flags |= (EW_FILE | EW_PATH);
} else if (xp->xp_context == EXPAND_DIRS_IN_CDPATH) {
flags = (flags | EW_DIR | EW_CDPATH) & ~EW_FILE;
} else {
flags = (flags | EW_DIR) & ~EW_FILE;
}
if (options & WILD_ICASE) {
flags |= EW_ICASE;
}
// Expand wildcards, supporting %:h and the like.
ret = expand_wildcards_eval(&pat, numMatches, matches, flags);
}
if (options & WILD_ICASE) {
flags |= EW_ICASE;
}
// Expand wildcards, supporting %:h and the like.
int ret = expand_wildcards_eval(&pat, numMatches, matches, flags);
if (free_pat) {
xfree(pat);
}
@ -2716,6 +2722,7 @@ static int ExpandFromContext(expand_T *xp, char *pat, char ***matches, int *numM
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_DIRS_IN_CDPATH) {
return expand_files_and_dirs(xp, pat, matches, numMatches, flags, options);
}

View File

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

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_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_resulting_text_too_long[] INIT(= N_("E1240: Resulting text too long"));
@ -181,6 +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[]
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_trustfile[] INIT(= N_("E5570: Cannot update trust file: %s"));

View File

@ -270,6 +270,7 @@ static struct vimvar {
VV(VV_COLLATE, "collate", VAR_STRING, VV_RO),
VV(VV_EXITING, "exiting", VAR_NUMBER, VV_RO),
VV(VV_MAXCOL, "maxcol", VAR_NUMBER, VV_RO),
VV(VV_CMDCOMPLETE, "cmdcomplete", VAR_BOOL, VV_RO),
// Neovim
VV(VV_STDERR, "stderr", VAR_NUMBER, VV_RO),
VV(VV_MSGPACK_TYPES, "msgpack_types", VAR_DICT, VV_RO),
@ -460,6 +461,9 @@ void eval_init(void)
set_vim_var_nr(VV_SEARCHFORWARD, 1);
set_vim_var_nr(VV_HLSEARCH, 1);
set_vim_var_nr(VV_COUNT1, 1);
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_STRING, VAR_TYPE_STRING);
set_vim_var_nr(VV_TYPE_FUNC, VAR_TYPE_FUNC);
@ -475,7 +479,6 @@ void eval_init(void)
set_vim_var_nr(VV_NUMBERMAX, VARNUMBER_MAX);
set_vim_var_nr(VV_NUMBERMIN, VARNUMBER_MIN);
set_vim_var_nr(VV_NUMBERSIZE, sizeof(varnumber_T) * 8);
set_vim_var_special(VV_EXITING, kSpecialVarNull);
set_vim_var_nr(VV_MAXCOL, MAXCOL);
set_vim_var_nr(VV_ECHOSPACE, sc_col - 1);
@ -2631,7 +2634,7 @@ static int may_call_simple_func(const char *arg, typval_T *rettv)
/// Handle zero level expression with optimization for a simple function call.
/// 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);

View File

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

View File

@ -5165,6 +5165,115 @@ static void ex_wrongmodifier(exarg_T *eap)
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 *pat, bool cmdcomplete)
{
const sctx_T saved_sctx = current_sctx;
char *findexpr = get_findexpr();
set_vim_var_string(VV_FNAME, pat, -1);
set_vim_var_bool(VV_CMDCOMPLETE, cmdcomplete ? kBoolVarTrue : kBoolVarFalse);
current_sctx = curbuf->b_p_script_ctx[BV_FEXPR].script_ctx;
char *arg = skipwhite(findexpr);
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());
} 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);
set_vim_var_bool(VV_CMDCOMPLETE, kBoolVarFalse);
current_sctx = saved_sctx;
return retlist;
}
/// Find file names matching "pat" using 'findexpr' 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(const char *pat, char ***files, int *numMatches)
{
*numMatches = 0;
*files = NULL;
list_T *l = eval_findexpr(pat, true);
if (l == NULL) {
return FAIL;
}
int len = tv_list_len(l);
if (len == 0) { // empty List
return FAIL;
}
*files = xmalloc(sizeof(char *) * (size_t)len);
// Copy all the List items
int idx = 0;
TV_LIST_ITER_CONST(l, li, {
if (TV_LIST_ITEM_TV(li)->v_type == VAR_STRING) {
(*files)[idx] = xstrdup(TV_LIST_ITEM_TV(li)->vval.v_string);
idx++;
}
});
*numMatches = idx;
tv_list_free(l);
return OK;
}
/// 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, false);
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
/// :split [[+command] file] split window with current or new file
/// :vsplit [[+command] file] split window vertically with current or new file
@ -5196,13 +5305,17 @@ void ex_splitview(exarg_T *eap)
}
if (eap->cmdidx == CMD_sfind || eap->cmdidx == CMD_tabfind) {
char *file_to_find = NULL;
char *search_ctx = NULL;
fname = find_file_in_path(eap->arg, strlen(eap->arg),
FNAME_MESS, true, curbuf->b_ffname,
&file_to_find, &search_ctx);
xfree(file_to_find);
vim_findfile_cleanup(search_ctx);
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 *search_ctx = NULL;
fname = find_file_in_path(eap->arg, strlen(eap->arg), FNAME_MESS, true,
curbuf->b_ffname, &file_to_find, &search_ctx);
xfree(file_to_find);
vim_findfile_cleanup(search_ctx);
}
if (fname == NULL) {
goto theend;
}
@ -5398,23 +5511,28 @@ static void ex_find(exarg_T *eap)
return;
}
char *file_to_find = NULL;
char *search_ctx = NULL;
char *fname = find_file_in_path(eap->arg, strlen(eap->arg),
FNAME_MESS, true, curbuf->b_ffname,
&file_to_find, &search_ctx);
if (eap->addr_count > 0) {
// Repeat finding the file "count" times. This matters when it appears
// several times in the path.
linenr_T count = eap->line2;
while (fname != NULL && --count > 0) {
xfree(fname);
fname = find_file_in_path(NULL, 0, FNAME_MESS, false, curbuf->b_ffname,
&file_to_find, &search_ctx);
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 *search_ctx = NULL;
fname = find_file_in_path(eap->arg, strlen(eap->arg), FNAME_MESS, true,
curbuf->b_ffname, &file_to_find, &search_ctx);
if (eap->addr_count > 0) {
// Repeat finding the file "count" times. This matters when it appears
// several times in the path.
linenr_T count = eap->line2;
while (fname != NULL && --count > 0) {
xfree(fname);
fname = find_file_in_path(NULL, 0, FNAME_MESS, false,
curbuf->b_ffname, &file_to_find, &search_ctx);
}
}
xfree(file_to_find);
vim_findfile_cleanup(search_ctx);
}
xfree(file_to_find);
vim_findfile_cleanup(search_ctx);
if (fname == NULL) {
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 (first == true) {
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 {
semsg(_("E345: Can't find file \"%s\" in path"), *file_to_find);
semsg(_(e_cant_find_file_str_in_path), *file_to_find);
}
} else {
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 {
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) {
case PV_FP:
return &(buf->b_p_fp);
case PV_FEXPR:
return &(buf->b_p_fexpr);
case PV_EFM:
return &(buf->b_p_efm);
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;
case PV_FP:
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:
return *buf->b_p_efm != NUL ? &(buf->b_p_efm) : p->var;
case PV_GP:
@ -4922,6 +4926,15 @@ char *get_equalprg(void)
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.
/// Used when splitting a window.
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_efm = 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_path = 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 char *p_ft; ///< 'filetype'
EXTERN char *p_fcs; ///< 'fillchar'
EXTERN char *p_fexpr; ///< 'findexpr'
EXTERN int p_fixeol; ///< 'fixendofline'
EXTERN char *p_fcl; ///< 'foldclose'
EXTERN OptInt p_fdls; ///< 'foldlevelstart'

View File

@ -2905,6 +2905,65 @@ return {
type = 'string',
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'.
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
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()
let pat = v:cmdcomplete ? $'{v:fname}*' : v:fname
return glob(pat, 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'),
tags = { 'E1514' },
type = 'string',
varname = 'p_fexpr',
},
{
abbreviation = 'fixeol',
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_efm);
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_tags);
check_string_option(&buf->b_p_tfu);
@ -1885,8 +1886,9 @@ int expand_set_nrformats(optexpand_T *args, int *numMatches, char ***matches)
matches);
}
/// One of the '*expr' options is changed:, 'diffexpr', 'foldexpr', 'foldtext',
/// 'formatexpr', 'includeexpr', 'indentexpr', 'patchexpr' and 'charconvert'.
/// One of the '*expr' options is changed:, 'diffexpr', 'findexpr',
/// 'foldexpr', 'foldtext', 'formatexpr', 'includeexpr', 'indentexpr',
/// 'patchexpr' and 'charconvert'.
const char *did_set_optexpr(optset_T *args)
{
char **varp = (char **)args->os_varp;

View File

@ -50,6 +50,13 @@ M.vars = {
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 = {
type = 'string',
desc = [=[
@ -284,7 +291,8 @@ M.vars = {
type = 'string',
desc = [=[
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 = {

View File

@ -1,5 +1,7 @@
" Test findfile() and finddir()
source check.vim
let s:files = [ 'Xfinddir1/foo',
\ 'Xfinddir1/bar',
\ 'Xfinddir1/Xdir2/foo',
@ -286,4 +288,223 @@ func Test_find_non_existing_path()
let &path = save_path
endfunc
" Test for 'findexpr'
func Test_findexpr()
CheckUnix
call assert_equal('', &findexpr)
call writefile(['aFile'], 'Xfindexpr1.c', 'D')
call writefile(['bFile'], 'Xfindexpr2.c', 'D')
call writefile(['cFile'], 'Xfindexpr3.c', 'D')
" basic tests
func FindExpr1()
let fnames = ['Xfindexpr1.c', 'Xfindexpr2.c', 'Xfindexpr3.c']
return fnames->copy()->filter('v:val =~? v:fname')
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 List as the expression
set findexpr=v:_null_list
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')
" Expression returning a string
set findexpr='abc'
call assert_fails('find Xfindexpr1.c', "E1514: 'findexpr' did not return a List type")
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
" 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']
" 'findexpr' that accepts a regular expression
func FindExprRegexp()
return s:fnames->copy()->filter('v:val =~? v:fname')
endfunc
" 'findexpr' that accepts a glob
func FindExprGlob()
let pat = glob2regpat(v:cmdcomplete ? $'*{v:fname}*' : v:fname)
return s:fnames->copy()->filter('v:val =~? pat')
endfunc
for regexp in [v:true, v:false]
let &findexpr = regexp ? 'FindExprRegexp()' : 'FindExprGlob()'
call feedkeys(":find \<Tab>\<C-B>\"\<CR>", "xt")
call assert_equal('"find Xfindexpr1.c', @:)
call feedkeys(":find Xfind\<Tab>\<Tab>\<C-B>\"\<CR>", "xt")
call assert_equal('"find Xfindexpr2.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 feedkeys(":find 3\<Tab>\<C-B>\"\<CR>", "xt")
call assert_equal('"find Xfindexpr3.c', @:)
call assert_equal(['Xfindexpr3.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 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
unlet s:fnames
endfunc
" 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('errorfile', 'errorfile=Something()', 'E520:')
call s:modeline_fails('exrc', 'exrc=Something()', 'E520:')
call s:modeline_fails('findexpr', 'findexpr=Something()', 'E520:')
call s:modeline_fails('formatprg', 'formatprg=Something()', 'E520:')
call s:modeline_fails('fsync', 'fsync=Something()', 'E520:')
call s:modeline_fails('grepprg', 'grepprg=Something()', 'E520:')

View File

@ -1559,7 +1559,7 @@ endfunc
" Test for changing options in a sandbox
func Test_opt_sandbox()
for opt in ['backupdir', 'cdpath', 'exrc']
for opt in ['backupdir', 'cdpath', 'exrc', 'findexpr']
call assert_fails('sandbox set ' .. opt .. '?', 'E48:')
call assert_fails('sandbox let &' .. opt .. ' = 1', 'E48:')
endfor