-*eval.txt* For Vim version 9.1. Last change: 2024 Jul 28
+*eval.txt* For Vim version 9.1. Last change: 2024 Oct 22
VIM REFERENCE MANUAL by Bram Moolenaar
*v:fname* *fname-variable*
v:fname When evaluating 'includeexpr': the file name that was
- detected. Empty otherwise.
+ detected. When evaluating 'findexpr': the argument passed to
+ the |:find| command. Empty otherwise.
*v:fname_in* *fname_in-variable*
v:fname_in The name of the input file. Valid while evaluating:
eob EndOfBuffer |hl-EndOfBuffer|
lastline NonText |hl-NonText|
+ *'findexpr'* *'fexpr'*
+'findexpr' 'fexpr' string (default "")
+ global or local to buffer |global-local|
+ {not available when compiled without the |+eval|
+ feature}
+ Expression that is evaluated to obtain the filename(s) for the |:find|
+ command. When this option is empty, the internal |file-searching|
+ mechanism is used.
+
+ While evaluating the expression, the |v:fname| variable is set to the
+ argument of the |:find| command.
+
+ The expression is evaluated only once per |:find| command invocation.
+ The expression can process all the directories specified in 'path'.
+
+ If a match is found, the expression should return a |List| containing
+ one or more file names. If a match is not found, the expression
+ should return an empty List.
+
+ If any errors are encountered during the expression evaluation, an
+ empty List is used as the return value.
+
+ Using a function call without arguments is faster |expr-option-function|
+
+ It is not allowed to change text or jump to another window while
+ evaluating 'findexpr' |textlock|.
+
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+
+ Examples:
+>
+ " Use glob()
+ func FindExprGlob()
+ return glob(v:fname, v:false, v:true)
+ endfunc
+ set findexpr=FindExprGlob()
+
+ " Use the 'git ls-files' output
+ func FindGitFiles()
+ let fnames = systemlist('git ls-files')
+ return fnames->filter('v:val =~? v:fname')
+ endfunc
+ set findexpr=FindGitFiles()
+<
*'fixendofline'* *'fixeol'* *'nofixendofline'* *'nofixeol'*
'fixendofline' 'fixeol' boolean (default on)
local to buffer
-*quickref.txt* For Vim version 9.1. Last change: 2024 Mar 03
+*quickref.txt* For Vim version 9.1. Last change: 2024 Oct 22
VIM REFERENCE MANUAL by Bram Moolenaar
'fileignorecase' 'fic' ignore case when using file names
'filetype' 'ft' type of file, used for autocommands
'fillchars' 'fcs' characters to use for displaying special items
+'findexpr' 'fexpr' expression to evaluate for |:find|
'fixendofline' 'fixeol' make sure last line in file has <EOL>
'fkmap' 'fk' obsolete option for Farsi
'foldclose' 'fcl' close a fold when the cursor leaves it
'fenc' options.txt /*'fenc'*
'fencs' options.txt /*'fencs'*
'fex' options.txt /*'fex'*
+'fexpr' options.txt /*'fexpr'*
'ff' options.txt /*'ff'*
'ffs' options.txt /*'ffs'*
'fic' options.txt /*'fic'*
'fileignorecase' options.txt /*'fileignorecase'*
'filetype' options.txt /*'filetype'*
'fillchars' options.txt /*'fillchars'*
+'findexpr' options.txt /*'findexpr'*
'fixendofline' options.txt /*'fixendofline'*
'fixeol' options.txt /*'fixeol'*
'fk' options.txt /*'fk'*
-*version9.txt* For Vim version 9.1. Last change: 2024 Oct 14
+*version9.txt* For Vim version 9.1. Last change: 2024 Oct 22
VIM REFERENCE MANUAL by Bram Moolenaar
'completeitemalign' Order of |complete-items| in Insert mode completion
popup
+'findexpr' Vim expression to obtain the results for a |:find|
+ command
'winfixbuf' Keep buffer focused in a window
'tabclose' Which tab page to focus after closing a tab page
't_xo' Terminal uses XON/XOFF handshaking (e.g. vt420)
clear_string_option(&buf->b_p_fp);
#if defined(FEAT_EVAL)
clear_string_option(&buf->b_p_fex);
+ clear_string_option(&buf->b_p_fexpr);
#endif
#ifdef FEAT_CRYPT
# ifdef FEAT_SODIUM
eap->errmsg = ex_errmsg(e_invalid_command_str, eap->cmd);
}
+#ifdef FEAT_EVAL
+/*
+ * Evaluate the 'findexpr' expression and return the result. When evaluating
+ * the expression, v:fname is set to the ":find" command argument.
+ */
+ static list_T *
+eval_findexpr(char_u *ptr, int len)
+{
+ sctx_T saved_sctx = current_sctx;
+ int use_sandbox = FALSE;
+ char_u *findexpr;
+ char_u *arg;
+ typval_T tv;
+ list_T *retlist = NULL;
+
+ if (*curbuf->b_p_fexpr == NUL)
+ {
+ use_sandbox = was_set_insecurely((char_u *)"findexpr", OPT_GLOBAL);
+ findexpr = p_fexpr;
+ }
+ else
+ {
+ use_sandbox = was_set_insecurely((char_u *)"findexpr", OPT_LOCAL);
+ findexpr = curbuf->b_p_fexpr;
+ }
+
+ set_vim_var_string(VV_FNAME, ptr, len);
+ current_sctx = curbuf->b_p_script_ctx[BV_FEXPR];
+
+ arg = skipwhite(findexpr);
+
+ if (use_sandbox)
+ ++sandbox;
+ ++textlock;
+
+ // Evaluate the expression. If the expression is "FuncName()" call the
+ // function directly.
+ if (eval0_simple_funccal(arg, &tv, NULL, &EVALARG_EVALUATE) == FAIL)
+ retlist = NULL;
+ else
+ {
+ if (tv.v_type == VAR_LIST)
+ retlist = list_copy(tv.vval.v_list, TRUE, TRUE, get_copyID());
+ clear_tv(&tv);
+ }
+ if (use_sandbox)
+ --sandbox;
+ --textlock;
+ clear_evalarg(&EVALARG_EVALUATE, NULL);
+
+ set_vim_var_string(VV_FNAME, NULL, 0);
+ current_sctx = saved_sctx;
+
+ return retlist;
+}
+
+/*
+ * Use 'findexpr' to find file 'findarg'. The 'count' argument is used to find
+ * the n'th matching file.
+ */
+ static char_u *
+findexpr_find_file(char_u *findarg, int findarg_len, int count)
+{
+ list_T *fname_list;
+ char_u *ret_fname = NULL;
+ char_u cc;
+ int fname_count;
+
+ cc = findarg[findarg_len];
+ findarg[findarg_len] = NUL;
+
+ fname_list = eval_findexpr(findarg, findarg_len);
+ fname_count = list_len(fname_list);
+
+ if (fname_count == 0)
+ semsg(_(e_cant_find_file_str_in_path), findarg);
+ else
+ {
+ if (count > fname_count)
+ semsg(_(e_no_more_file_str_found_in_path), findarg);
+ else
+ {
+ listitem_T *li = list_find(fname_list, count - 1);
+ if (li != NULL && li->li_tv.v_type == VAR_STRING)
+ ret_fname = vim_strsave(li->li_tv.vval.v_string);
+ }
+ }
+
+ if (fname_list != NULL)
+ list_free(fname_list);
+
+ findarg[findarg_len] = cc;
+
+ return ret_fname;
+}
+#endif
+
/*
* :sview [+command] file split window with new file, read-only
* :split [[+command] file] split window with current or new file
{
char_u *file_to_find = NULL;
char *search_ctx = NULL;
- fname = find_file_in_path(eap->arg, (int)STRLEN(eap->arg),
- FNAME_MESS, TRUE, curbuf->b_ffname,
- &file_to_find, &search_ctx);
- vim_free(file_to_find);
- vim_findfile_cleanup(search_ctx);
+
+ if (*get_findexpr() != NUL)
+ {
+#ifdef FEAT_EVAL
+ fname = findexpr_find_file(eap->arg, (int)STRLEN(eap->arg),
+ eap->addr_count > 0 ? eap->line2 : 1);
+#endif
+ }
+ else
+ {
+ fname = find_file_in_path(eap->arg, (int)STRLEN(eap->arg),
+ FNAME_MESS, TRUE, curbuf->b_ffname,
+ &file_to_find, &search_ctx);
+ vim_free(file_to_find);
+ vim_findfile_cleanup(search_ctx);
+ }
if (fname == NULL)
goto theend;
eap->arg = fname;
if (!check_can_set_curbuf_forceit(eap->forceit))
return;
- char_u *fname;
+ char_u *fname = NULL;
int count;
char_u *file_to_find = NULL;
char *search_ctx = NULL;
- fname = find_file_in_path(eap->arg, (int)STRLEN(eap->arg), FNAME_MESS,
- TRUE, curbuf->b_ffname, &file_to_find, &search_ctx);
- if (eap->addr_count > 0)
+ if (*get_findexpr() != NUL)
+ {
+#ifdef FEAT_EVAL
+ fname = findexpr_find_file(eap->arg, (int)STRLEN(eap->arg),
+ eap->addr_count > 0 ? eap->line2 : 1);
+#endif
+ }
+ else
{
- // Repeat finding the file "count" times. This matters when it appears
- // several times in the path.
- count = eap->line2;
- while (fname != NULL && --count > 0)
+ fname = find_file_in_path(eap->arg, (int)STRLEN(eap->arg), FNAME_MESS,
+ TRUE, curbuf->b_ffname, &file_to_find, &search_ctx);
+ if (eap->addr_count > 0)
{
- vim_free(fname);
- fname = find_file_in_path(NULL, 0, FNAME_MESS,
- FALSE, curbuf->b_ffname, &file_to_find, &search_ctx);
+ // Repeat finding the file "count" times. This matters when it appears
+ // several times in the path.
+ count = eap->line2;
+ while (fname != NULL && --count > 0)
+ {
+ vim_free(fname);
+ fname = find_file_in_path(NULL, 0, FNAME_MESS,
+ FALSE, curbuf->b_ffname, &file_to_find, &search_ctx);
+ }
}
+ VIM_CLEAR(file_to_find);
+ vim_findfile_cleanup(search_ctx);
}
- VIM_CLEAR(file_to_find);
- vim_findfile_cleanup(search_ctx);
if (fname == NULL)
return;
case PV_FP:
clear_string_option(&buf->b_p_fp);
break;
+# ifdef FEAT_EVAL
+ case PV_FEXPR:
+ clear_string_option(&buf->b_p_fexpr);
+ break;
+# endif
# ifdef FEAT_QUICKFIX
case PV_EFM:
clear_string_option(&buf->b_p_efm);
switch ((int)p->indir)
{
case PV_FP: return (char_u *)&(curbuf->b_p_fp);
+#ifdef FEAT_EVAL
+ case PV_FEXPR: return (char_u *)&(curbuf->b_p_fexpr);
+#endif
#ifdef FEAT_QUICKFIX
case PV_EFM: return (char_u *)&(curbuf->b_p_efm);
case PV_GP: return (char_u *)&(curbuf->b_p_gp);
#endif
case PV_FP: return *curbuf->b_p_fp != NUL
? (char_u *)&(curbuf->b_p_fp) : p->var;
+#ifdef FEAT_EVAL
+ case PV_FEXPR: return *curbuf->b_p_fexpr != NUL
+ ? (char_u *)&curbuf->b_p_fexpr : p->var;
+#endif
#ifdef FEAT_QUICKFIX
case PV_EFM: return *curbuf->b_p_efm != NUL
? (char_u *)&(curbuf->b_p_efm) : p->var;
return curbuf->b_p_ep;
}
+/*
+ * Get the value of 'findexpr', either the buffer-local one or the global one.
+ */
+ char_u *
+get_findexpr(void)
+{
+#ifdef FEAT_EVAL
+ if (*curbuf->b_p_fexpr == NUL)
+ return p_fexpr;
+ return curbuf->b_p_fexpr;
+#else
+ return (char_u *)"";
+#endif
+}
+
/*
* Copy options from one window to another.
* Used when splitting a window.
buf->b_p_efm = empty_option;
#endif
buf->b_p_ep = empty_option;
+#if defined(FEAT_EVAL)
+ buf->b_p_fexpr = vim_strsave(p_fexpr);
+ COPY_OPT_SCTX(buf, BV_FEXPR);
+#endif
buf->b_p_kp = empty_option;
buf->b_p_path = empty_option;
buf->b_p_tags = empty_option;
EXTERN int p_fic; // 'fileignorecase'
EXTERN char_u *p_ft; // 'filetype'
EXTERN char_u *p_fcs; // 'fillchar'
+#ifdef FEAT_EVAL
+EXTERN char_u *p_fexpr; // 'findexpr'
+#endif
EXTERN int p_fixeol; // 'fixendofline'
#ifdef FEAT_FOLDING
EXTERN char_u *p_fcl; // 'foldclose'
, BV_EP
, BV_ET
, BV_FENC
+ , BV_FEXPR
, BV_FP
#ifdef FEAT_EVAL
, BV_BEXPR
#define PV_FP OPT_BOTH(OPT_BUF(BV_FP))
#ifdef FEAT_EVAL
# define PV_FEX OPT_BUF(BV_FEX)
+# define PV_FEXPR OPT_BOTH(OPT_BUF(BV_FEXPR))
#endif
#define PV_FF OPT_BUF(BV_FF)
#define PV_FLP OPT_BUF(BV_FLP)
{(char_u *)"vert:|,fold:-,eob:~,lastline:@",
(char_u *)0L}
SCTX_INIT},
+ {"findexpr", "fexpr", P_STRING|P_ALLOCED|P_VI_DEF|P_VIM|P_SECURE,
+#if defined(FEAT_EVAL)
+ (char_u *)&p_fexpr, PV_FEXPR, did_set_optexpr, NULL,
+ {(char_u *)"", (char_u *)0L}
+#else
+ (char_u *)NULL, PV_NONE, NULL, NULL,
+ {(char_u *)0L, (char_u *)0L}
+#endif
+ SCTX_INIT},
{"fixendofline", "fixeol", P_BOOL|P_VI_DEF|P_RSTAT,
(char_u *)&p_fixeol, PV_FIXEOL,
did_set_eof_eol_fixeol_bomb, NULL,
check_string_option(&buf->b_p_efm);
#endif
check_string_option(&buf->b_p_ep);
+#ifdef FEAT_EVAL
+ check_string_option(&buf->b_p_fexpr);
+#endif
check_string_option(&buf->b_p_path);
check_string_option(&buf->b_p_tags);
check_string_option(&buf->b_p_tc);
#if defined(FEAT_EVAL) || defined(PROTO)
/*
* One of the '*expr' options is changed: 'balloonexpr', 'diffexpr',
- * 'foldexpr', 'foldtext', 'formatexpr', 'includeexpr', 'indentexpr',
- * 'patchexpr', 'printexpr' and 'charconvert'.
+ * 'findexpr', 'foldexpr', 'foldtext', 'formatexpr', 'includeexpr',
+ * 'indentexpr', 'patchexpr', 'printexpr' and 'charconvert'.
*
*/
char *
char_u *get_option_fullname(int opt_idx);
opt_did_set_cb_T get_option_did_set_cb(int opt_idx);
char_u *get_equalprg(void);
+char_u *get_findexpr(void);
void win_copy_options(win_T *wp_from, win_T *wp_to);
void after_copy_winopt(win_T *wp);
void copy_winopt(winopt_T *from, winopt_T *to);
char_u *b_p_efm; // 'errorformat' local value
#endif
char_u *b_p_ep; // 'equalprg' local value
+#ifdef FEAT_EVAL
+ char_u *b_p_fexpr; // 'findexpr' local value
+#endif
char_u *b_p_path; // 'path' local value
int b_p_ar; // 'autoread' local value
char_u *b_p_tags; // 'tags' local value
" Test findfile() and finddir()
+source check.vim
+
let s:files = [ 'Xfinddir1/foo',
\ 'Xfinddir1/bar',
\ 'Xfinddir1/Xdir2/foo',
let &path = save_path
endfunc
+" Test for 'findexpr'
+func Test_findexpr()
+ CheckUnix
+ call assert_equal('', &findexpr)
+ call writefile(['aFile'], 'Xfindexpr1.c', 'D')
+ call writefile(['bFile'], 'Xfindexpr2.c', 'D')
+ call writefile(['cFile'], 'Xfindexpr3.c', 'D')
+
+ " basic tests
+ func FindExpr1()
+ let fnames = ['Xfindexpr1.c', 'Xfindexpr2.c', 'Xfindexpr3.c']
+ "return fnames->copy()->filter('v:val =~? v:fname')->join("\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=test_null_string()
+ call assert_fails('find Xfindexpr1.c', 'E345: Can''t find file "Xfindexpr1.c" in path')
+
+ " Try to create a new window from the find expression
+ func FindExpr3()
+ new
+ return ["foo"]
+ endfunc
+ set findexpr=FindExpr3()
+ call assert_fails('find Xfindexpr1.c', 'E565: Not allowed to change text or change window')
+
+ " Try to modify the current buffer from the find expression
+ func FindExpr4()
+ call setline(1, ['abc'])
+ return ["foo"]
+ endfunc
+ set findexpr=FindExpr4()
+ call assert_fails('find Xfindexpr1.c', 'E565: Not allowed to change text or change window')
+
+ set findexpr&
+ delfunc! FindExpr1
+ delfunc! FindExpr2
+ delfunc! FindExpr3
+ delfunc! FindExpr4
+endfunc
+
+" Test for using a script-local function for 'findexpr'
+func Test_findexpr_scriptlocal_func()
+ func! s:FindExprScript()
+ let g:FindExprArg = v:fname
+ return ['xxx']
+ endfunc
+
+ set findexpr=s:FindExprScript()
+ call assert_equal(expand('<SID>') .. 'FindExprScript()', &findexpr)
+ call assert_equal(expand('<SID>') .. 'FindExprScript()', &g:findexpr)
+ new | only
+ let g:FindExprArg = ''
+ find abc
+ call assert_equal('abc', g:FindExprArg)
+ bw!
+
+ set findexpr=<SID>FindExprScript()
+ call assert_equal(expand('<SID>') .. 'FindExprScript()', &findexpr)
+ call assert_equal(expand('<SID>') .. 'FindExprScript()', &g:findexpr)
+ new | only
+ let g:FindExprArg = ''
+ find abc
+ call assert_equal('abc', g:FindExprArg)
+ bw!
+
+ let &findexpr = 's:FindExprScript()'
+ call assert_equal(expand('<SID>') .. 'FindExprScript()', &g:findexpr)
+ new | only
+ let g:FindExprArg = ''
+ find abc
+ call assert_equal('abc', g:FindExprArg)
+ bw!
+
+ let &findexpr = '<SID>FindExprScript()'
+ call assert_equal(expand('<SID>') .. 'FindExprScript()', &g:findexpr)
+ new | only
+ let g:FindExprArg = ''
+ find abc
+ call assert_equal('abc', g:FindExprArg)
+ bw!
+
+ set findexpr=
+ setglobal findexpr=s:FindExprScript()
+ setlocal findexpr=
+ call assert_equal(expand('<SID>') .. 'FindExprScript()', &findexpr)
+ call assert_equal(expand('<SID>') .. 'FindExprScript()', &g:findexpr)
+ call assert_equal('', &l:findexpr)
+ new | only
+ let g:FindExprArg = ''
+ find abc
+ call assert_equal('abc', g:FindExprArg)
+ bw!
+
+ new | only
+ set findexpr=
+ setglobal findexpr=
+ setlocal findexpr=s:FindExprScript()
+ call assert_equal(expand('<SID>') .. 'FindExprScript()', &findexpr)
+ call assert_equal(expand('<SID>') .. 'FindExprScript()', &l:findexpr)
+ call assert_equal('', &g:findexpr)
+ let g:FindExprArg = ''
+ find abc
+ call assert_equal('abc', g:FindExprArg)
+ bw!
+
+ set findexpr=
+ delfunc s:FindExprScript
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
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:')
" 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
static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 810,
/**/
809,
/**/