summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2022-10-01 15:32:46 +0100
committerBram Moolenaar <Bram@vim.org>2022-10-01 15:32:46 +0100
commit87b4e5c5db9d1cfd6f2e79656e1a6cff3c69d15f (patch)
treea437917654ad79edd9764b4f67af33ce01228d6a
parent145d1fd91041bd2a22a11eef0357702e420796e2 (diff)
patch 9.0.0632: calling a function from an "expr" option has overheadv9.0.0632
Problem: Calling a function from an "expr" option has too much overhead. Solution: Add call_simple_func() and use it for 'foldexpr'
-rw-r--r--runtime/doc/fold.txt6
-rw-r--r--runtime/doc/vim9.txt15
-rw-r--r--src/eval.c19
-rw-r--r--src/proto/userfunc.pro3
-rw-r--r--src/testdir/test_fold.vim25
-rw-r--r--src/userfunc.c80
-rw-r--r--src/version.c2
-rw-r--r--src/vim9execute.c5
8 files changed, 144 insertions, 11 deletions
diff --git a/runtime/doc/fold.txt b/runtime/doc/fold.txt
index 93efcbb664..f11ca0812d 100644
--- a/runtime/doc/fold.txt
+++ b/runtime/doc/fold.txt
@@ -74,8 +74,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: >
@@ -84,6 +82,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 compiled 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/vim9.txt b/runtime/doc/vim9.txt
index b73011a092..15e9a702bc 100644
--- a/runtime/doc/vim9.txt
+++ b/runtime/doc/vim9.txt
@@ -1410,6 +1410,21 @@ to a Vim9 function:
'three'
]
+
+Calling a function in an expr option ~
+ *expr-option-function*
+A few options, such as 'foldexpr', are an expresison 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
+defined a compiled function and set the option to call it without arguments.
+Example: >
+ vim9script
+ def MyFoldFunc(): any
+ ... compute fold level for line v:lnum
+ return level
+ enddef
+ set foldexpr=s:MyFoldFunc()
+
==============================================================================
4. Types *vim9-types*
diff --git a/src/eval.c b/src/eval.c
index 2330bd6a10..aeb8fee7ad 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -899,13 +899,14 @@ eval_foldexpr(win_T *wp, int *cp)
{
char_u *arg;
typval_T tv;
+ int r = NOTDONE;
varnumber_T retval;
char_u *s;
sctx_T saved_sctx = current_sctx;
int use_sandbox = was_set_insecurely((char_u *)"foldexpr",
OPT_LOCAL);
- arg = wp->w_p_fde;
+ arg = skipwhite(wp->w_p_fde);
current_sctx = wp->w_p_script_ctx[WV_FDE];
++emsg_off;
@@ -913,7 +914,21 @@ eval_foldexpr(win_T *wp, int *cp)
++sandbox;
++textlock;
*cp = NUL;
- if (eval0(arg, &tv, NULL, &EVALARG_EVALUATE) == FAIL)
+
+ // If the expression is "FuncName()" then we can skip a lot of overhead.
+ char_u *parens = (char_u *)strstr((char *)arg, "()");
+ if (parens != NULL && *skipwhite(parens + 2) == NUL)
+ {
+ char_u *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);
+
+ if (r == FAIL)
retval = 0;
else
{
diff --git a/src/proto/userfunc.pro b/src/proto/userfunc.pro
index f509523dba..ee17049ce6 100644
--- a/src/proto/userfunc.pro
+++ b/src/proto/userfunc.pro
@@ -36,8 +36,9 @@ int func_call(char_u *name, typval_T *args, partial_T *partial, dict_T *selfdict
int get_callback_depth(void);
int call_callback(callback_T *callback, int len, typval_T *rettv, int argcount, typval_T *argvars);
varnumber_T call_callback_retnr(callback_T *callback, int argcount, typval_T *argvars);
-void user_func_error(int error, char_u *name, funcexe_T *funcexe);
+void user_func_error(int error, char_u *name, int found_var);
int call_func(char_u *funcname, int len, typval_T *rettv, int argcount_in, typval_T *argvars_in, funcexe_T *funcexe);
+int call_simple_func(char_u *funcname, int len, typval_T *rettv);
char_u *printable_func_name(ufunc_T *fp);
char_u *trans_function_name(char_u **pp, int *is_global, int skip, int flags, funcdict_T *fdp, partial_T **partial, type_T **type);
char_u *get_scriptlocal_funcname(char_u *funcname);
diff --git a/src/testdir/test_fold.vim b/src/testdir/test_fold.vim
index d15f607ae0..9b8cd7310d 100644
--- a/src/testdir/test_fold.vim
+++ b/src/testdir/test_fold.vim
@@ -249,6 +249,31 @@ func Test_foldexpr_no_interrupt_addsub()
set foldmethod& foldexpr&
endfunc
+" Fold function defined in another script
+func Test_foldexpr_compiled()
+ 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
diff --git a/src/userfunc.c b/src/userfunc.c
index 92b5203c1c..14d1c72bee 100644
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -3447,12 +3447,12 @@ call_callback_retnr(
* Nothing if "error" is FCERR_NONE.
*/
void
-user_func_error(int error, char_u *name, funcexe_T *funcexe)
+user_func_error(int error, char_u *name, int found_var)
{
switch (error)
{
case FCERR_UNKNOWN:
- if (funcexe->fe_found_var)
+ if (found_var)
emsg_funcname(e_not_callable_type_str, name);
else
emsg_funcname(e_unknown_function_str, name);
@@ -3702,7 +3702,8 @@ theend:
* 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
while (argv_clear > 0)
@@ -3714,6 +3715,77 @@ 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.
+ */
+ int
+call_simple_func(
+ char_u *funcname, // name of the function
+ int len, // length of "name" or -1 to use strlen()
+ typval_T *rettv) // return value goes here
+{
+ int ret = FAIL;
+ int error = FCERR_NONE;
+ char_u fname_buf[FLEN_FIXED + 1];
+ char_u *tofree = NULL;
+ char_u *name;
+ char_u *fname;
+ char_u *rfname;
+ int is_global = FALSE;
+ ufunc_T *fp;
+
+ 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.
+ name = vim_strnsave(funcname, len);
+ if (name == NULL)
+ return ret;
+
+ fname = fname_trans_sid(name, fname_buf, &tofree, &error);
+
+ // Skip "g:" before a function name.
+ if (fname[0] == 'g' && fname[1] == ':')
+ {
+ is_global = TRUE;
+ rfname = fname + 2;
+ }
+ else
+ rfname = fname;
+ fp = find_func(rfname, is_global);
+ if (fp != NULL && !is_global && in_vim9script()
+ && func_requires_g_prefix(fp))
+ // In Vim9 script g: is required to find a global non-autoload
+ // function.
+ fp = NULL;
+ 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];
+ funcexe_T funcexe;
+
+ argvars[0].v_type = VAR_UNKNOWN;
+ CLEAR_FIELD(funcexe);
+ 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);
+ vim_free(tofree);
+ vim_free(name);
+
+ return ret;
+}
+
char_u *
printable_func_name(ufunc_T *fp)
{
@@ -5676,7 +5748,7 @@ ex_defer_inner(
if (error != FCERR_UNKNOWN)
{
- user_func_error(error, name, NULL);
+ user_func_error(error, name, FALSE);
r = FAIL;
}
}
diff --git a/src/version.c b/src/version.c
index 354a570fc6..894840e086 100644
--- a/src/version.c
+++ b/src/version.c
@@ -700,6 +700,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 632,
+/**/
631,
/**/
630,
diff --git a/src/vim9execute.c b/src/vim9execute.c
index ddcbe2c76b..df18f9ebb5 100644
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -1267,7 +1267,8 @@ call_ufunc(
if (error != FCERR_NONE)
{
- user_func_error(error, printable_func_name(ufunc), &funcexe);
+ user_func_error(error, printable_func_name(ufunc),
+ funcexe.fe_found_var);
return FAIL;
}
if (did_emsg > did_emsg_before)
@@ -4244,7 +4245,7 @@ exec_instructions(ectx_T *ectx)
if (jump)
ectx->ec_iidx = iptr->isn_arg.whileloop.while_end;
- // Store the current funccal count, may be used by
+ // Store the current funcref count, may be used by
// ISN_ENDLOOP later
tv = STACK_TV_VAR(
iptr->isn_arg.whileloop.while_funcref_idx);