diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 17b2d22173..441f180177 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -2598,7 +2598,7 @@ 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'* + *'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| diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c index 549ed826bc..5b7017481e 100644 --- a/src/nvim/cmdexpand.c +++ b/src/nvim/cmdexpand.c @@ -2497,21 +2497,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_FILES_IN_PATH && *get_findexpr() != NUL) { + 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); } diff --git a/src/nvim/errors.h b/src/nvim/errors.h index bea56541a2..6bbec84ff9 100644 --- a/src/nvim/errors.h +++ b/src/nvim/errors.h @@ -186,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")); diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 5c61399003..f89c992cf9 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -5167,28 +5167,17 @@ static void ex_wrongmodifier(exarg_T *eap) /// 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) +static list_T *eval_findexpr(const char *ptr) { 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; - } + char *findexpr = get_findexpr(); - set_vim_var_string(VV_FNAME, ptr, (ptrdiff_t)len); + set_vim_var_string(VV_FNAME, ptr, -1); 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 @@ -5200,12 +5189,11 @@ static list_T *eval_findexpr(const char *ptr, size_t len) } 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); } - if (use_sandbox) { - sandbox--; - } textlock--; clear_evalarg(&EVALARG_EVALUATE, NULL); @@ -5215,6 +5203,52 @@ static list_T *eval_findexpr(const char *ptr, size_t len) 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; + + // File name expansion uses wildchars. But the 'findexpr' expression + // expects a regular expression argument. So convert wildchars in the + // argument to regular expression patterns. + char *regpat = file_pat_to_reg_pat(pat, NULL, NULL, false); + if (regpat == NULL) { + return FAIL; + } + + list_T *l = eval_findexpr(regpat); + + xfree(regpat); + + 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) @@ -5224,7 +5258,7 @@ static char *findexpr_find_file(char *findarg, size_t findarg_len, int count) const char cc = findarg[findarg_len]; findarg[findarg_len] = NUL; - list_T *fname_list = eval_findexpr(findarg, findarg_len); + list_T *fname_list = eval_findexpr(findarg); int fname_count = tv_list_len(fname_list); if (fname_count == 0) { diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 65401fb3a9..2e037d80d3 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -2955,6 +2955,7 @@ return { scope = { 'global', 'buffer' }, secure = true, short_desc = N_('expression used for :find'), + tags = { 'E1514' }, type = 'string', varname = 'p_fexpr', }, diff --git a/test/old/testdir/test_findfile.vim b/test/old/testdir/test_findfile.vim index baf33898fe..8e25975b53 100644 --- a/test/old/testdir/test_findfile.vim +++ b/test/old/testdir/test_findfile.vim @@ -299,7 +299,6 @@ func Test_findexpr() " 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 @@ -358,8 +357,8 @@ func Test_findexpr() set findexpr=FindExpr2() call assert_fails('find Xfindexpr1.c', 'find error') - " Try using a null string as the expression - set findexpr=v:_null_string + " 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 @@ -378,6 +377,10 @@ func Test_findexpr() 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 @@ -454,4 +457,31 @@ func Test_findexpr_scriptlocal_func() delfunc s:FindExprScript endfunc +" Test for expanding the argument to the :find command using 'findexpr' +func Test_findexpr_expand_arg() + func FindExpr1() + let fnames = ['Xfindexpr1.c', 'Xfindexpr2.c', 'Xfindexpr3.c'] + return fnames->copy()->filter('v:val =~? v:fname') + endfunc + set findexpr=FindExpr1() + + call feedkeys(":find \\\"\", "xt") + call assert_equal('"find Xfindexpr1.c', @:) + + call feedkeys(":find Xfind\\\\"\", "xt") + call assert_equal('"find Xfindexpr2.c', @:) + + call feedkeys(":find *3*\\\"\", "xt") + call assert_equal('"find Xfindexpr3.c', @:) + + call feedkeys(":find Xfind\\\"\", "xt") + call assert_equal('"find Xfindexpr1.c Xfindexpr2.c Xfindexpr3.c', @:) + + call feedkeys(":find abc\\\"\", "xt") + call assert_equal('"find abc', @:) + + set findexpr& + delfunc! FindExpr1 +endfunc + " vim: shiftwidth=2 sts=2 expandtab