diff options
-rw-r--r-- | runtime/doc/diff.txt | 6 | ||||
-rw-r--r-- | runtime/doc/fold.txt | 6 | ||||
-rw-r--r-- | runtime/doc/options.txt | 39 | ||||
-rw-r--r-- | runtime/lua/vim/_meta/options.lua | 24 | ||||
-rw-r--r-- | src/nvim/digraph.c | 2 | ||||
-rw-r--r-- | src/nvim/eval.c | 139 | ||||
-rw-r--r-- | src/nvim/eval/userfunc.c | 78 | ||||
-rw-r--r-- | src/nvim/eval/vars.c | 2 | ||||
-rw-r--r-- | src/nvim/ex_docmd.c | 2 | ||||
-rw-r--r-- | src/nvim/ex_eval.c | 6 | ||||
-rw-r--r-- | src/nvim/indent.c | 2 | ||||
-rw-r--r-- | src/nvim/mapping.c | 2 | ||||
-rw-r--r-- | src/nvim/ops.c | 2 | ||||
-rw-r--r-- | src/nvim/options.lua | 24 | ||||
-rw-r--r-- | src/nvim/path.c | 5 | ||||
-rw-r--r-- | src/nvim/regexp.c | 2 | ||||
-rw-r--r-- | src/nvim/runtime.c | 2 | ||||
-rw-r--r-- | src/nvim/statusline.c | 4 | ||||
-rw-r--r-- | src/nvim/textformat.c | 2 | ||||
-rw-r--r-- | test/old/testdir/test_fold.vim | 26 |
20 files changed, 310 insertions, 65 deletions
diff --git a/runtime/doc/diff.txt b/runtime/doc/diff.txt index 2f174a404e..c9de54342e 100644 --- a/runtime/doc/diff.txt +++ b/runtime/doc/diff.txt @@ -369,6 +369,9 @@ Additionally, 'diffexpr' should take care of "icase" and "iwhite" in the 'diffopt' option. 'diffexpr' cannot change the value of 'lines' and 'columns'. +The advantage of using a function call without arguments is that it is faster, +see |expr-option-function|. + Example (this does almost the same as 'diffexpr' being empty): > set diffexpr=MyDiff() @@ -434,6 +437,9 @@ will have the same effect. These variables are set to the file names used: v:fname_diff patch file v:fname_out resulting patched file +The advantage of using a function call without arguments is that it is faster, +see |expr-option-function|. + Example (this does the same as 'patchexpr' being empty): > set patchexpr=MyPatch() diff --git a/runtime/doc/fold.txt b/runtime/doc/fold.txt index 8f7393f5e3..b844e0ed85 100644 --- a/runtime/doc/fold.txt +++ b/runtime/doc/fold.txt @@ -69,8 +69,6 @@ method. The value of the 'foldexpr' option is evaluated to get the foldlevel of a line. Examples: This will create a fold for all consecutive lines that start with a tab: > :set foldexpr=getline(v:lnum)[0]==\"\\t\" -This will call a function to compute the fold level: > - :set foldexpr=MyFoldLevel(v:lnum) This will make a fold out of paragraphs separated by blank lines: > :set foldexpr=getline(v:lnum)=~'^\\s*$'&&getline(v:lnum+1)=~'\\S'?'<1':1 This does the same: > @@ -79,6 +77,10 @@ This does the same: > Note that backslashes must be used to escape characters that ":set" handles differently (space, backslash, double quote, etc., see |option-backslash|). +The most efficient is to call a function without arguments: > + :set foldexpr=MyFoldLevel() +The function must use v:lnum. See |expr-option-function|. + These are the conditions with which the expression is evaluated: - The current buffer and window are set for the line. - The variable "v:lnum" is set to the line number. diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 875767283a..bc2a8ae263 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -400,6 +400,21 @@ Set using a variable with lambda expression: > let L = {a, b, c -> MyTagFunc(a, b , c)} let &tagfunc = L +Calling a function in an expr option *expr-option-function* + +The value of a few options, such as 'foldexpr', is an expression that is +evaluated to get a value. The evaluation can have quite a bit of overhead. +One way to minimize the overhead, and also to keep the option value very +simple, is to define a function and set the option to call it without +arguments. A |v:lua-call| can also be used. Example: >vim + lua << EOF + function _G.MyFoldFunc() + -- ... compute fold level for line v:lnum + return level + end + EOF + set foldexpr=v:lua.MyFoldFunc() + Setting the filetype @@ -1307,6 +1322,9 @@ A jump table for the options with a short description can be found at |Q_op|. v:fname_out name of the output file Note that v:fname_in and v:fname_out will never be the same. + The advantage of using a function call without arguments is that it is + faster, see |expr-option-function|. + If the 'charconvert' expression starts with s: or |<SID>|, then it is replaced with the script ID (|local-function|). Example: >vim set charconvert=s:MyConvert() @@ -2773,6 +2791,9 @@ A jump table for the options with a short description can be found at |Q_op|. < This will invoke the mylang#Format() function in the autoload/mylang.vim file in 'runtimepath'. |autoload| + The advantage of using a function call without arguments is that it is + faster, see |expr-option-function|. + The expression is also evaluated when 'textwidth' is set and adding text beyond that limit. This happens under the same conditions as when internal formatting is used. Make sure the cursor is kept in the @@ -3408,11 +3429,14 @@ A jump table for the options with a short description can be found at |Q_op|. If the expression starts with s: or |<SID>|, then it is replaced with the script ID (|local-function|). Example: >vim - setlocal includeexpr=s:MyIncludeExpr(v:fname) - setlocal includeexpr=<SID>SomeIncludeExpr(v:fname) + setlocal includeexpr=s:MyIncludeExpr() + setlocal includeexpr=<SID>SomeIncludeExpr() < Otherwise, the expression is evaluated in the context of the script where the option was set, thus script-local items are available. + It is more efficient if the value is just a function call without + arguments, see |expr-option-function|. + The expression will be evaluated in the |sandbox| when set from a modeline, see |sandbox-option|. This option cannot be set in a modeline when 'modelineexpr' is off. @@ -3475,6 +3499,9 @@ A jump table for the options with a short description can be found at |Q_op|. < Otherwise, the expression is evaluated in the context of the script where the option was set, thus script-local items are available. + The advantage of using a function call without arguments is that it is + faster, see |expr-option-function|. + The expression must return the number of spaces worth of indent. It can return "-1" to keep the current indent (this means 'autoindent' is used for the indent). @@ -5915,9 +5942,11 @@ A jump table for the options with a short description can be found at |Q_op|. The file is used for all languages. expr:{expr} Evaluate expression {expr}. Use a function to avoid - trouble with spaces. |v:val| holds the badly spelled - word. The expression must evaluate to a List of - Lists, each with a suggestion and a score. + trouble with spaces. Best is to call a function + without arguments, see |expr-option-function|. + |v:val| holds the badly spelled word. The expression + must evaluate to a List of Lists, each with a + suggestion and a score. Example: [['the', 33], ['that', 44]] ~ Set 'verbose' and use |z=| to see the scores that the diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index ebb6ee2329..10c888548c 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -786,6 +786,9 @@ vim.bo.channel = vim.o.channel --- v:fname_out name of the output file --- Note that v:fname_in and v:fname_out will never be the same. --- +--- The advantage of using a function call without arguments is that it is +--- faster, see `expr-option-function`. +--- --- If the 'charconvert' expression starts with s: or `<SID>`, then it is --- replaced with the script ID (`local-function`). Example: --- @@ -2521,6 +2524,9 @@ vim.wo.fdt = vim.wo.foldtext --- This will invoke the mylang#Format() function in the --- autoload/mylang.vim file in 'runtimepath'. `autoload` --- +--- The advantage of using a function call without arguments is that it is +--- faster, see `expr-option-function`. +--- --- The expression is also evaluated when 'textwidth' is set and adding --- text beyond that limit. This happens under the same conditions as --- when internal formatting is used. Make sure the cursor is kept in the @@ -3286,12 +3292,15 @@ vim.go.inc = vim.go.include --- the script ID (`local-function`). Example: --- --- ```vim ---- setlocal includeexpr=s:MyIncludeExpr(v:fname) ---- setlocal includeexpr=<SID>SomeIncludeExpr(v:fname) +--- setlocal includeexpr=s:MyIncludeExpr() +--- setlocal includeexpr=<SID>SomeIncludeExpr() --- ``` --- Otherwise, the expression is evaluated in the context of the script --- where the option was set, thus script-local items are available. --- +--- It is more efficient if the value is just a function call without +--- arguments, see `expr-option-function`. +--- --- The expression will be evaluated in the `sandbox` when set from a --- modeline, see `sandbox-option`. --- This option cannot be set in a modeline when 'modelineexpr' is off. @@ -3366,6 +3375,9 @@ vim.go.is = vim.go.incsearch --- Otherwise, the expression is evaluated in the context of the script --- where the option was set, thus script-local items are available. --- +--- The advantage of using a function call without arguments is that it is +--- faster, see `expr-option-function`. +--- --- The expression must return the number of spaces worth of indent. It --- can return "-1" to keep the current indent (this means 'autoindent' is --- used for the indent). @@ -6314,9 +6326,11 @@ vim.bo.spo = vim.bo.spelloptions --- The file is used for all languages. --- --- expr:{expr} Evaluate expression {expr}. Use a function to avoid ---- trouble with spaces. `v:val` holds the badly spelled ---- word. The expression must evaluate to a List of ---- Lists, each with a suggestion and a score. +--- trouble with spaces. Best is to call a function +--- without arguments, see `expr-option-function|. +--- |v:val` holds the badly spelled word. The expression +--- must evaluate to a List of Lists, each with a +--- suggestion and a score. --- Example: --- [['the', 33], ['that', 44]] ~ --- Set 'verbose' and use `z=` to see the scores that the diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c index 26fb77df30..8149c5964f 100644 --- a/src/nvim/digraph.c +++ b/src/nvim/digraph.c @@ -2198,7 +2198,7 @@ bool get_keymap_str(win_T *wp, char *fmt, char *buf, int len) curwin = wp; STRCPY(buf, "b:keymap_name"); // must be writable emsg_skip++; - char *s = p = eval_to_string(buf, false); + char *s = p = eval_to_string(buf, false, false); emsg_skip--; curbuf = old_curbuf; curwin = old_curwin; diff --git a/src/nvim/eval.c b/src/nvim/eval.c index b8c3df5688..b0f7aefc7b 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -703,7 +703,7 @@ int eval_charconvert(const char *const enc_from, const char *const enc_to, } bool err = false; - if (eval_to_bool(p_ccv, &err, NULL, false)) { + if (eval_to_bool(p_ccv, &err, NULL, false, true)) { err = true; } @@ -732,7 +732,7 @@ void eval_diff(const char *const origfile, const char *const newfile, const char } // errors are ignored - typval_T *tv = eval_expr(p_dex, NULL); + typval_T *tv = eval_expr_ext(p_dex, NULL, true); tv_free(tv); set_vim_var_string(VV_FNAME_IN, NULL, -1); @@ -754,7 +754,7 @@ void eval_patch(const char *const origfile, const char *const difffile, const ch } // errors are ignored - typval_T *tv = eval_expr(p_pex, NULL); + typval_T *tv = eval_expr_ext(p_pex, NULL, true); tv_free(tv); set_vim_var_string(VV_FNAME_IN, NULL, -1); @@ -783,7 +783,8 @@ void fill_evalarg_from_eap(evalarg_T *evalarg, exarg_T *eap, bool skip) /// @param skip only parse, don't execute /// /// @return true or false. -bool eval_to_bool(char *arg, bool *error, exarg_T *eap, bool skip) +bool eval_to_bool(char *arg, bool *error, exarg_T *eap, const bool skip, + const bool use_simple_function) { typval_T tv; bool retval = false; @@ -794,7 +795,9 @@ bool eval_to_bool(char *arg, bool *error, exarg_T *eap, bool skip) if (skip) { emsg_skip++; } - if (eval0(arg, &tv, eap, &evalarg) == FAIL) { + int r = use_simple_function ? eval0_simple_funccal(arg, &tv, eap, &evalarg) + : eval0(arg, &tv, eap, &evalarg); + if (r == FAIL) { *error = true; } else { *error = false; @@ -1042,14 +1045,17 @@ static char *typval2string(typval_T *tv, bool join_list) /// @param join_list when true convert a List into a sequence of lines. /// /// @return pointer to allocated memory, or NULL for failure. -char *eval_to_string_eap(char *arg, bool join_list, exarg_T *eap) +char *eval_to_string_eap(char *arg, const bool join_list, exarg_T *eap, + const bool use_simple_function) { typval_T tv; char *retval; evalarg_T evalarg; fill_evalarg_from_eap(&evalarg, eap, eap != NULL && eap->skip); - if (eval0(arg, &tv, NULL, &evalarg) == FAIL) { + int r = use_simple_function ? eval0_simple_funccal(arg, &tv, NULL, &evalarg) + : eval0(arg, &tv, NULL, &evalarg); + if (r == FAIL) { retval = NULL; } else { retval = typval2string(&tv, join_list); @@ -1060,16 +1066,16 @@ char *eval_to_string_eap(char *arg, bool join_list, exarg_T *eap) return retval; } -char *eval_to_string(char *arg, bool join_list) +char *eval_to_string(char *arg, const bool join_list, const bool use_simple_function) { - return eval_to_string_eap(arg, join_list, NULL); + return eval_to_string_eap(arg, join_list, NULL, use_simple_function); } /// Call eval_to_string() without using current local variables and using /// textlock. /// /// @param use_sandbox when true, use the sandbox. -char *eval_to_string_safe(char *arg, const bool use_sandbox) +char *eval_to_string_safe(char *arg, const bool use_sandbox, const bool use_simple_function) { char *retval; funccal_entry_T funccal_entry; @@ -1079,7 +1085,7 @@ char *eval_to_string_safe(char *arg, const bool use_sandbox) sandbox++; } textlock++; - retval = eval_to_string(arg, false); + retval = eval_to_string(arg, false, use_simple_function); if (use_sandbox) { sandbox--; } @@ -1092,15 +1098,22 @@ char *eval_to_string_safe(char *arg, const bool use_sandbox) /// Evaluates "expr" silently. /// /// @return -1 for an error. -varnumber_T eval_to_number(char *expr) +varnumber_T eval_to_number(char *expr, const bool use_simple_function) { typval_T rettv; varnumber_T retval; char *p = skipwhite(expr); + int r = NOTDONE; emsg_off++; - if (eval1(&p, &rettv, &EVALARG_EVALUATE) == FAIL) { + if (use_simple_function) { + r = may_call_simple_func(expr, &rettv); + } + if (r == NOTDONE) { + r = eval1(&p, &rettv, &EVALARG_EVALUATE); + } + if (r == FAIL) { retval = -1; } else { retval = tv_get_number_chk(&rettv, NULL); @@ -1117,12 +1130,26 @@ varnumber_T eval_to_number(char *expr) /// NULL when there is an error. typval_T *eval_expr(char *arg, exarg_T *eap) { + return eval_expr_ext(arg, eap, false); +} + +static typval_T *eval_expr_ext(char *arg, exarg_T *eap, const bool use_simple_function) +{ typval_T *tv = xmalloc(sizeof(*tv)); evalarg_T evalarg; fill_evalarg_from_eap(&evalarg, eap, eap != NULL && eap->skip); - if (eval0(arg, tv, eap, &evalarg) == FAIL) { + int r = NOTDONE; + + if (use_simple_function) { + r = eval0_simple_funccal(arg, tv, eap, &evalarg); + } + if (r == NOTDONE) { + r = eval0(arg, tv, eap, &evalarg); + } + + if (r == FAIL) { XFREE_CLEAR(tv); } @@ -1208,7 +1235,11 @@ list_T *eval_spell_expr(char *badword, char *expr) current_sctx = *ctx; } - if (eval1(&p, &rettv, &EVALARG_EVALUATE) == OK) { + int r = may_call_simple_func(p, &rettv); + if (r == NOTDONE) { + r = eval1(&p, &rettv, &EVALARG_EVALUATE); + } + if (r == OK) { if (rettv.v_type != VAR_LIST) { tv_clear(&rettv); } else { @@ -1349,7 +1380,7 @@ int eval_foldexpr(win_T *wp, int *cp) const sctx_T saved_sctx = current_sctx; const bool use_sandbox = was_set_insecurely(wp, kOptFoldexpr, OPT_LOCAL); - char *arg = wp->w_p_fde; + char *arg = skipwhite(wp->w_p_fde); current_sctx = wp->w_p_script_ctx[WV_FDE].script_ctx; emsg_off++; @@ -1361,7 +1392,9 @@ int eval_foldexpr(win_T *wp, int *cp) typval_T tv; varnumber_T retval; - if (eval0(arg, &tv, NULL, &EVALARG_EVALUATE) == FAIL) { + // Evaluate the expression. If the expression is "FuncName()" call the + // function directly. + if (eval0_simple_funccal(arg, &tv, NULL, &EVALARG_EVALUATE) == FAIL) { retval = 0; } else { // If the result is a number, just return the number. @@ -1407,7 +1440,7 @@ Object eval_foldtext(win_T *wp) typval_T tv; Object retval; - if (eval0(arg, &tv, NULL, &EVALARG_EVALUATE) == FAIL) { + if (eval0_simple_funccal(arg, &tv, NULL, &EVALARG_EVALUATE) == FAIL) { retval = STRING_OBJ(NULL_STRING); } else { if (tv.v_type == VAR_LIST) { @@ -1428,6 +1461,31 @@ Object eval_foldtext(win_T *wp) return retval; } +/// Find the end of a variable or function name. Unlike find_name_end() this +/// does not recognize magic braces. +/// When "use_namespace" is true recognize "b:", "s:", etc. +/// Return a pointer to just after the name. Equal to "arg" if there is no +/// valid name. +static const char *to_name_end(const char *arg, bool use_namespace) +{ + // Quick check for valid starting character. + if (!eval_isnamec1(*arg)) { + return arg; + } + + const char *p; + for (p = arg + 1; *p != NUL && eval_isnamec(*p); MB_PTR_ADV(p)) { + // Include a namespace such as "s:var" and "v:var". But "n:" is not + // and can be used in slice "[n:]". + if (*p == ':' && (p != arg + 1 + || !use_namespace + || vim_strchr("bgstvw", *arg) == NULL)) { + break; + } + } + return p; +} + /// Get an Dict lval variable that can be assigned a value to: "name", /// "name[expr]", "name[expr][expr]", "name.key", "name.key[expr]" etc. /// "name" points to the start of the name. @@ -2499,9 +2557,10 @@ void clear_evalarg(evalarg_T *evalarg, exarg_T *eap) } } -/// The "evaluate" argument: When false, the argument is only parsed but not -/// executed. The function may return OK, but the rettv will be of type -/// VAR_UNKNOWN. The function still returns FAIL for a syntax error. +/// The "eval" functions have an "evalarg" argument: When NULL or +/// "evalarg->eval_flags" does not have EVAL_EVALUATE, then the argument is only +/// parsed but not executed. The functions may return OK, but the rettv will be +/// of type VAR_UNKNOWN. The functions still returns FAIL for a syntax error. /// Handle zero level expression. /// This calls eval1() and handles error message and nextcmd. @@ -2560,6 +2619,42 @@ int eval0(char *arg, typval_T *rettv, exarg_T *eap, evalarg_T *const evalarg) return ret; } +/// If "arg" is a simple function call without arguments then call it and return +/// the result. Otherwise return NOTDONE. +static int may_call_simple_func(const char *arg, typval_T *rettv) +{ + const char *parens = strstr(arg, "()"); + int r = NOTDONE; + + // If the expression is "FuncName()" then we can skip a lot of overhead. + if (parens != NULL && *skipwhite(parens + 2) == NUL) { + if (strnequal(arg, "v:lua.", 6)) { + const char *p = arg + 6; + if (skip_luafunc_name(p) == parens) { + r = call_simple_luafunc(p, (size_t)(parens - p), rettv); + } + } else { + const char *p = strncmp(arg, "<SNR>", 5) == 0 ? skipdigits(arg + 5) : arg; + if (to_name_end(p, true) == parens) { + r = call_simple_func(arg, (size_t)(parens - arg), rettv); + } + } + } + return r; +} + +/// 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 r = may_call_simple_func(arg, rettv); + + if (r == NOTDONE) { + r = eval0(arg, rettv, eap, evalarg); + } + return r; +} + /// Handle top level expression: /// expr2 ? expr1 : expr1 /// expr2 ?? expr1 @@ -7372,7 +7467,7 @@ static char *make_expanded_name(const char *in_start, char *expr_start, char *ex char c1 = *in_end; *in_end = NUL; - char *temp_result = eval_to_string(expr_start + 1, false); + char *temp_result = eval_to_string(expr_start + 1, false, false); if (temp_result != NULL) { retval = xmalloc(strlen(temp_result) + (size_t)(expr_start - in_start) + (size_t)(in_end - expr_end) + 1); diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index ab8e67016f..f7ce5334f0 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -1561,12 +1561,12 @@ varnumber_T callback_call_retnr(Callback *callback, int argcount, typval_T *argv /// Give an error message for the result of a function. /// Nothing if "error" is FCERR_NONE. -static void user_func_error(int error, const char *name, funcexe_T *funcexe) +static void user_func_error(int error, const char *name, bool found_var) FUNC_ATTR_NONNULL_ARG(2) { switch (error) { case FCERR_UNKNOWN: - if (funcexe->fe_found_var) { + if (found_var) { semsg(_(e_not_callable_type_str), name); } else { emsg_funcname(e_unknown_function_str, name); @@ -1686,12 +1686,9 @@ int call_func(const char *funcname, int len, typval_T *rettv, int argcount_in, t } if (error == FCERR_NONE && funcexe->fe_evaluate) { - char *rfname = fname; - - // Ignore "g:" before a function name. - if (fp == NULL && fname[0] == 'g' && fname[1] == ':') { - rfname = fname + 2; - } + // Skip "g:" before a function name. + bool is_global = fp == NULL && fname[0] == 'g' && fname[1] == ':'; + char *rfname = is_global ? fname + 2 : fname; rettv->v_type = VAR_NUMBER; // default rettv is number zero rettv->vval.v_number = 0; @@ -1765,7 +1762,7 @@ theend: // Report an error unless the argument evaluation or function call has been // cancelled due to an aborting error, an interrupt, or an exception. if (!aborting()) { - user_func_error(error, (name != NULL) ? name : funcname, funcexe); + user_func_error(error, (name != NULL) ? name : funcname, funcexe->fe_found_var); } // clear the copies made from the partial @@ -1779,6 +1776,67 @@ theend: return ret; } +int call_simple_luafunc(const char *funcname, size_t len, typval_T *rettv) + FUNC_ATTR_NONNULL_ALL +{ + typval_T argvars[1]; + argvars[0].v_type = VAR_UNKNOWN; + nlua_typval_call(funcname, len, argvars, 0, rettv); + return OK; +} + +/// Call a function without arguments, partial or dict. +/// This is like call_func() when the call is only "FuncName()". +/// To be used by "expr" options. +/// Returns NOTDONE when the function could not be found. +/// +/// @param funcname name of the function +/// @param len length of "name" +/// @param rettv return value goes here +int call_simple_func(const char *funcname, size_t len, typval_T *rettv) + FUNC_ATTR_NONNULL_ALL +{ + int ret = FAIL; + + rettv->v_type = VAR_NUMBER; // default rettv is number zero + rettv->vval.v_number = 0; + + // Make a copy of the name, an option can be changed in the function. + char *name = xstrnsave(funcname, len); + + int error = FCERR_NONE; + char *tofree = NULL; + char fname_buf[FLEN_FIXED + 1]; + char *fname = fname_trans_sid(name, fname_buf, &tofree, &error); + + // Skip "g:" before a function name. + bool is_global = fname[0] == 'g' && fname[1] == ':'; + char *rfname = is_global ? fname + 2 : fname; + + ufunc_T *fp = find_func(rfname); + if (fp == NULL) { + ret = NOTDONE; + } else if (fp != NULL && (fp->uf_flags & FC_DELETED)) { + error = FCERR_DELETED; + } else if (fp != NULL) { + typval_T argvars[1]; + argvars[0].v_type = VAR_UNKNOWN; + funcexe_T funcexe = FUNCEXE_INIT; + funcexe.fe_evaluate = true; + + error = call_user_func_check(fp, 0, argvars, rettv, &funcexe, NULL); + if (error == FCERR_NONE) { + ret = OK; + } + } + + user_func_error(error, name, false); + xfree(tofree); + xfree(name); + + return ret; +} + char *printable_func_name(ufunc_T *fp) { return fp->uf_name_exp != NULL ? fp->uf_name_exp : fp->uf_name; @@ -3248,7 +3306,7 @@ static int ex_defer_inner(char *name, char **arg, const partial_T *const partial if (ufunc != NULL) { int error = check_user_func_argcount(ufunc, argcount); if (error != FCERR_UNKNOWN) { - user_func_error(error, name, NULL); + user_func_error(error, name, false); r = FAIL; } } diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c index 2dca942bb5..3a5d056a9a 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -89,7 +89,7 @@ char *eval_one_expr_in_str(char *p, garray_T *gap, bool evaluate) } if (evaluate) { *block_end = NUL; - char *expr_val = eval_to_string(block_start, false); + char *expr_val = eval_to_string(block_start, false, false); *block_end = '}'; if (expr_val == NULL) { return NULL; diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index e384627fec..2142199c8a 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -4644,7 +4644,7 @@ static void ex_colorscheme(exarg_T *eap) char *expr = xstrdup("g:colors_name"); emsg_off++; - char *p = eval_to_string(expr, false); + char *p = eval_to_string(expr, false, false); emsg_off--; xfree(expr); diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index 6e7c1ff21c..f9936dd88e 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -849,7 +849,7 @@ void ex_if(exarg_T *eap) bool skip = CHECK_SKIP; bool error; - bool result = eval_to_bool(eap->arg, &error, eap, skip); + bool result = eval_to_bool(eap->arg, &error, eap, skip, false); if (!skip && !error) { if (result) { @@ -944,7 +944,7 @@ void ex_else(exarg_T *eap) if (skip && *eap->arg != '"' && ends_excmd(*eap->arg)) { semsg(_(e_invexpr2), eap->arg); } else { - result = eval_to_bool(eap->arg, &error, eap, skip); + result = eval_to_bool(eap->arg, &error, eap, skip, false); } // When throwing error exceptions, we want to throw always the first @@ -990,7 +990,7 @@ void ex_while(exarg_T *eap) int skip = CHECK_SKIP; if (eap->cmdidx == CMD_while) { // ":while bool-expr" - result = eval_to_bool(eap->arg, &error, eap, skip); + result = eval_to_bool(eap->arg, &error, eap, skip, false); } else { // ":for var in list-expr" evalarg_T evalarg; fill_evalarg_from_eap(&evalarg, eap, skip); diff --git a/src/nvim/indent.c b/src/nvim/indent.c index 66cb443e4d..2f994036ad 100644 --- a/src/nvim/indent.c +++ b/src/nvim/indent.c @@ -1161,7 +1161,7 @@ int get_expr_indent(void) // Need to make a copy, the 'indentexpr' option could be changed while // evaluating it. char *inde_copy = xstrdup(curbuf->b_p_inde); - int indent = (int)eval_to_number(inde_copy); + int indent = (int)eval_to_number(inde_copy, true); xfree(inde_copy); if (use_sandbox) { diff --git a/src/nvim/mapping.c b/src/nvim/mapping.c index 7ca3f534b0..7c69ccbb1e 100644 --- a/src/nvim/mapping.c +++ b/src/nvim/mapping.c @@ -1656,7 +1656,7 @@ char *eval_map_expr(mapblock_T *mp, int c) api_clear_error(&err); } } else { - p = eval_to_string(expr, false); + p = eval_to_string(expr, false, false); xfree(expr); } expr_map_lock--; diff --git a/src/nvim/ops.c b/src/nvim/ops.c index a71317d75d..e418635d37 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -734,7 +734,7 @@ char *get_expr_line(void) } nested++; - char *rv = eval_to_string(expr_copy, true); + char *rv = eval_to_string(expr_copy, true, false); nested--; xfree(expr_copy); return rv; diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 7268a4351d..8506d0998a 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -1059,6 +1059,9 @@ return { v:fname_out name of the output file Note that v:fname_in and v:fname_out will never be the same. + The advantage of using a function call without arguments is that it is + faster, see |expr-option-function|. + If the 'charconvert' expression starts with s: or |<SID>|, then it is replaced with the script ID (|local-function|). Example: >vim set charconvert=s:MyConvert() @@ -3231,6 +3234,9 @@ return { < This will invoke the mylang#Format() function in the autoload/mylang.vim file in 'runtimepath'. |autoload| + The advantage of using a function call without arguments is that it is + faster, see |expr-option-function|. + The expression is also evaluated when 'textwidth' is set and adding text beyond that limit. This happens under the same conditions as when internal formatting is used. Make sure the cursor is kept in the @@ -4165,11 +4171,14 @@ return { If the expression starts with s: or |<SID>|, then it is replaced with the script ID (|local-function|). Example: >vim - setlocal includeexpr=s:MyIncludeExpr(v:fname) - setlocal includeexpr=<SID>SomeIncludeExpr(v:fname) + setlocal includeexpr=s:MyIncludeExpr() + setlocal includeexpr=<SID>SomeIncludeExpr() < Otherwise, the expression is evaluated in the context of the script where the option was set, thus script-local items are available. + It is more efficient if the value is just a function call without + arguments, see |expr-option-function|. + The expression will be evaluated in the |sandbox| when set from a modeline, see |sandbox-option|. This option cannot be set in a modeline when 'modelineexpr' is off. @@ -4249,6 +4258,9 @@ return { < Otherwise, the expression is evaluated in the context of the script where the option was set, thus script-local items are available. + The advantage of using a function call without arguments is that it is + faster, see |expr-option-function|. + The expression must return the number of spaces worth of indent. It can return "-1" to keep the current indent (this means 'autoindent' is used for the indent). @@ -7949,9 +7961,11 @@ return { The file is used for all languages. expr:{expr} Evaluate expression {expr}. Use a function to avoid - trouble with spaces. |v:val| holds the badly spelled - word. The expression must evaluate to a List of - Lists, each with a suggestion and a score. + trouble with spaces. Best is to call a function + without arguments, see |expr-option-function|. + |v:val| holds the badly spelled word. The expression + must evaluate to a List of Lists, each with a + suggestion and a score. Example: [['the', 33], ['that', 44]] ~ Set 'verbose' and use |z=| to see the scores that the diff --git a/src/nvim/path.c b/src/nvim/path.c index adea7ff0f7..67acc6e51b 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -1398,7 +1398,7 @@ static int expand_backtick(garray_T *gap, char *pat, int flags) char *cmd = xmemdupz(pat + 1, strlen(pat) - 2); if (*cmd == '=') { // `={expr}`: Expand expression - buffer = eval_to_string(cmd + 1, true); + buffer = eval_to_string(cmd + 1, true, false); } else { buffer = get_cmd_output(cmd, NULL, (flags & EW_SILENT) ? kShellOptSilent : 0, NULL); } @@ -1694,7 +1694,8 @@ static char *eval_includeexpr(const char *const ptr, const size_t len) current_sctx = curbuf->b_p_script_ctx[BV_INEX].script_ctx; char *res = eval_to_string_safe(curbuf->b_p_inex, - was_set_insecurely(curwin, kOptIncludeexpr, OPT_LOCAL)); + was_set_insecurely(curwin, kOptIncludeexpr, OPT_LOCAL), + true); set_vim_var_string(VV_FNAME, NULL, 0); current_sctx = save_sctx; diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index 40c95eb221..e79c0c6259 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -2194,7 +2194,7 @@ static int vim_regsub_both(char *source, typval_T *expr, char *dest, int destlen } tv_clear(&rettv); } else { - eval_result[nested] = eval_to_string(source + 2, true); + eval_result[nested] = eval_to_string(source + 2, true, false); } nesting--; diff --git a/src/nvim/runtime.c b/src/nvim/runtime.c index 2cae79c0fd..3aa558f305 100644 --- a/src/nvim/runtime.c +++ b/src/nvim/runtime.c @@ -1100,7 +1100,7 @@ static int load_pack_plugin(bool opt, char *fname) // If runtime/filetype.lua wasn't loaded yet, the scripts will be // found when it loads. - if (opt && eval_to_number(cmd) > 0) { + if (opt && eval_to_number(cmd, false) > 0) { do_cmdline_cmd("augroup filetypedetect"); vim_snprintf(pat, len, ftpat, ffname); gen_expand_wildcards_and_cb(1, &pat, EW_FILE, true, source_callback_vim_lua, NULL); diff --git a/src/nvim/statusline.c b/src/nvim/statusline.c index e94e33eebc..099ed37c12 100644 --- a/src/nvim/statusline.c +++ b/src/nvim/statusline.c @@ -954,7 +954,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, OptIndex op }; set_var(S_LEN("g:statusline_winid"), &tv, false); - usefmt = eval_to_string_safe(fmt + 2, use_sandbox); + usefmt = eval_to_string_safe(fmt + 2, use_sandbox, false); if (usefmt == NULL) { usefmt = fmt; } @@ -1429,7 +1429,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, OptIndex op } // Note: The result stored in `t` is unused. - str = eval_to_string_safe(out_p, use_sandbox); + str = eval_to_string_safe(out_p, use_sandbox, false); curwin = save_curwin; curbuf = save_curbuf; diff --git a/src/nvim/textformat.c b/src/nvim/textformat.c index 13607da043..96907362dd 100644 --- a/src/nvim/textformat.c +++ b/src/nvim/textformat.c @@ -869,7 +869,7 @@ int fex_format(linenr_T lnum, long count, int c) if (use_sandbox) { sandbox++; } - int r = (int)eval_to_number(fex); + int r = (int)eval_to_number(fex, true); if (use_sandbox) { sandbox--; } diff --git a/test/old/testdir/test_fold.vim b/test/old/testdir/test_fold.vim index 36f72f5e01..a0eb3afdbb 100644 --- a/test/old/testdir/test_fold.vim +++ b/test/old/testdir/test_fold.vim @@ -386,6 +386,32 @@ func Test_foldexpr_no_interrupt_addsub() set foldmethod& foldexpr& endfunc +" Fold function defined in another script +func Test_foldexpr_compiled() + throw 'Skipped: Vim9 script is N/A' + new + let lines =<< trim END + vim9script + def FoldFunc(): number + return v:lnum + enddef + + set foldmethod=expr + set foldexpr=s:FoldFunc() + END + call writefile(lines, 'XfoldExpr', 'D') + source XfoldExpr + + call setline(1, ['one', 'two', 'three']) + redraw + call assert_equal(1, foldlevel(1)) + call assert_equal(2, foldlevel(2)) + call assert_equal(3, foldlevel(3)) + + bwipe! + set foldmethod& foldexpr& +endfunc + func Check_foldlevels(expected) call assert_equal(a:expected, map(range(1, line('$')), 'foldlevel(v:val)')) endfunc |