aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorzeertzjq <zeertzjq@outlook.com>2024-08-01 10:13:45 +0800
committerzeertzjq <zeertzjq@outlook.com>2024-08-02 11:56:51 +0800
commitf7fde0173af95925e7324b7d3c09776173dab8a7 (patch)
treec7a54472837afe4baf13e66855265cc8a003805a
parent48e4589eaded3213956aa9ddbcc0aa6971a974e5 (diff)
downloadrneovim-f7fde0173af95925e7324b7d3c09776173dab8a7.tar.gz
rneovim-f7fde0173af95925e7324b7d3c09776173dab8a7.tar.bz2
rneovim-f7fde0173af95925e7324b7d3c09776173dab8a7.zip
vim-patch:9.0.0632: calling a function from an "expr" option has overhead
Problem: Calling a function from an "expr" option has too much overhead. Solution: Add call_simple_func() and use it for 'foldexpr' https://github.com/vim/vim/commit/87b4e5c5db9d1cfd6f2e79656e1a6cff3c69d15f Cherry-pick a call_func() change from patch 8.2.1343. Add expr-option-function docs to options.txt. Co-authored-by: Bram Moolenaar <Bram@vim.org>
-rw-r--r--runtime/doc/fold.txt6
-rw-r--r--runtime/doc/options.txt8
-rw-r--r--src/nvim/eval.c44
-rw-r--r--src/nvim/eval/userfunc.c69
-rw-r--r--test/old/testdir/test_fold.vim26
5 files changed, 139 insertions, 14 deletions
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..a48d020a34 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -400,6 +400,14 @@ 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.
+
Setting the filetype
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index b8c3df5688..c8451b7a28 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -1349,7 +1349,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++;
@@ -1360,8 +1360,23 @@ int eval_foldexpr(win_T *wp, int *cp)
*cp = NUL;
typval_T tv;
+ int r = NOTDONE;
+
+ // If the expression is "FuncName()" then we can skip a lot of overhead.
+ char *parens = strstr(arg, "()");
+ if (parens != NULL && *skipwhite(parens + 2) == NUL) {
+ char *p = strncmp(arg, "<SNR>", 5) == 0 ? skipdigits(arg + 5) : arg;
+ if (to_name_end(p, true) == parens) {
+ r = call_simple_func(arg, (int)(parens - arg), &tv);
+ }
+ }
+
+ if (r == NOTDONE) {
+ r = eval0(arg, &tv, NULL, &EVALARG_EVALUATE);
+ }
+
varnumber_T retval;
- if (eval0(arg, &tv, NULL, &EVALARG_EVALUATE) == FAIL) {
+ if (r == FAIL) {
retval = 0;
} else {
// If the result is a number, just return the number.
@@ -1428,6 +1443,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 char *to_name_end(char *arg, bool use_namespace)
+{
+ // Quick check for valid starting character.
+ if (!eval_isnamec1(*arg)) {
+ return arg;
+ }
+
+ 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.
diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c
index ab8e67016f..f7d1e7e0f8 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,58 @@ theend:
return ret;
}
+/// 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" or -1 to use strlen()
+/// @param rettv return value goes here
+int call_simple_func(const char *funcname, int 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, (size_t)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 +3297,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/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