mirror of
https://github.com/vim/vim.git
synced 2025-09-24 03:44:06 -04:00
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'
This commit is contained in:
@@ -74,8 +74,6 @@ method. The value of the 'foldexpr' option is evaluated to get the foldlevel
|
|||||||
of a line. Examples:
|
of a line. Examples:
|
||||||
This will create a fold for all consecutive lines that start with a tab: >
|
This will create a fold for all consecutive lines that start with a tab: >
|
||||||
:set foldexpr=getline(v:lnum)[0]==\"\\t\"
|
: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: >
|
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
|
:set foldexpr=getline(v:lnum)=~'^\\s*$'&&getline(v:lnum+1)=~'\\S'?'<1':1
|
||||||
This does the same: >
|
This does the same: >
|
||||||
@@ -84,6 +82,10 @@ This does the same: >
|
|||||||
Note that backslashes must be used to escape characters that ":set" handles
|
Note that backslashes must be used to escape characters that ":set" handles
|
||||||
differently (space, backslash, double quote, etc., see |option-backslash|).
|
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:
|
These are the conditions with which the expression is evaluated:
|
||||||
- The current buffer and window are set for the line.
|
- The current buffer and window are set for the line.
|
||||||
- The variable "v:lnum" is set to the line number.
|
- The variable "v:lnum" is set to the line number.
|
||||||
|
@@ -1410,6 +1410,21 @@ to a Vim9 function:
|
|||||||
'three'
|
'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*
|
4. Types *vim9-types*
|
||||||
|
19
src/eval.c
19
src/eval.c
@@ -899,13 +899,14 @@ eval_foldexpr(win_T *wp, int *cp)
|
|||||||
{
|
{
|
||||||
char_u *arg;
|
char_u *arg;
|
||||||
typval_T tv;
|
typval_T tv;
|
||||||
|
int r = NOTDONE;
|
||||||
varnumber_T retval;
|
varnumber_T retval;
|
||||||
char_u *s;
|
char_u *s;
|
||||||
sctx_T saved_sctx = current_sctx;
|
sctx_T saved_sctx = current_sctx;
|
||||||
int use_sandbox = was_set_insecurely((char_u *)"foldexpr",
|
int use_sandbox = was_set_insecurely((char_u *)"foldexpr",
|
||||||
OPT_LOCAL);
|
OPT_LOCAL);
|
||||||
|
|
||||||
arg = wp->w_p_fde;
|
arg = skipwhite(wp->w_p_fde);
|
||||||
current_sctx = wp->w_p_script_ctx[WV_FDE];
|
current_sctx = wp->w_p_script_ctx[WV_FDE];
|
||||||
|
|
||||||
++emsg_off;
|
++emsg_off;
|
||||||
@@ -913,7 +914,21 @@ eval_foldexpr(win_T *wp, int *cp)
|
|||||||
++sandbox;
|
++sandbox;
|
||||||
++textlock;
|
++textlock;
|
||||||
*cp = NUL;
|
*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;
|
retval = 0;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@@ -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 get_callback_depth(void);
|
||||||
int call_callback(callback_T *callback, int len, typval_T *rettv, int argcount, typval_T *argvars);
|
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);
|
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_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 *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 *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);
|
char_u *get_scriptlocal_funcname(char_u *funcname);
|
||||||
|
@@ -249,6 +249,31 @@ func Test_foldexpr_no_interrupt_addsub()
|
|||||||
set foldmethod& foldexpr&
|
set foldmethod& foldexpr&
|
||||||
endfunc
|
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)
|
func Check_foldlevels(expected)
|
||||||
call assert_equal(a:expected, map(range(1, line('$')), 'foldlevel(v:val)'))
|
call assert_equal(a:expected, map(range(1, line('$')), 'foldlevel(v:val)'))
|
||||||
endfunc
|
endfunc
|
||||||
|
@@ -3447,12 +3447,12 @@ call_callback_retnr(
|
|||||||
* Nothing if "error" is FCERR_NONE.
|
* Nothing if "error" is FCERR_NONE.
|
||||||
*/
|
*/
|
||||||
void
|
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)
|
switch (error)
|
||||||
{
|
{
|
||||||
case FCERR_UNKNOWN:
|
case FCERR_UNKNOWN:
|
||||||
if (funcexe->fe_found_var)
|
if (found_var)
|
||||||
emsg_funcname(e_not_callable_type_str, name);
|
emsg_funcname(e_not_callable_type_str, name);
|
||||||
else
|
else
|
||||||
emsg_funcname(e_unknown_function_str, name);
|
emsg_funcname(e_unknown_function_str, name);
|
||||||
@@ -3702,7 +3702,8 @@ theend:
|
|||||||
* cancelled due to an aborting error, an interrupt, or an exception.
|
* cancelled due to an aborting error, an interrupt, or an exception.
|
||||||
*/
|
*/
|
||||||
if (!aborting())
|
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
|
// clear the copies made from the partial
|
||||||
while (argv_clear > 0)
|
while (argv_clear > 0)
|
||||||
@@ -3714,6 +3715,77 @@ theend:
|
|||||||
return ret;
|
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 *
|
char_u *
|
||||||
printable_func_name(ufunc_T *fp)
|
printable_func_name(ufunc_T *fp)
|
||||||
{
|
{
|
||||||
@@ -5676,7 +5748,7 @@ ex_defer_inner(
|
|||||||
|
|
||||||
if (error != FCERR_UNKNOWN)
|
if (error != FCERR_UNKNOWN)
|
||||||
{
|
{
|
||||||
user_func_error(error, name, NULL);
|
user_func_error(error, name, FALSE);
|
||||||
r = FAIL;
|
r = FAIL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -699,6 +699,8 @@ static char *(features[]) =
|
|||||||
|
|
||||||
static int included_patches[] =
|
static int included_patches[] =
|
||||||
{ /* Add new patch number below this line */
|
{ /* Add new patch number below this line */
|
||||||
|
/**/
|
||||||
|
632,
|
||||||
/**/
|
/**/
|
||||||
631,
|
631,
|
||||||
/**/
|
/**/
|
||||||
|
@@ -1267,7 +1267,8 @@ call_ufunc(
|
|||||||
|
|
||||||
if (error != FCERR_NONE)
|
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;
|
return FAIL;
|
||||||
}
|
}
|
||||||
if (did_emsg > did_emsg_before)
|
if (did_emsg > did_emsg_before)
|
||||||
@@ -4244,7 +4245,7 @@ exec_instructions(ectx_T *ectx)
|
|||||||
if (jump)
|
if (jump)
|
||||||
ectx->ec_iidx = iptr->isn_arg.whileloop.while_end;
|
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
|
// ISN_ENDLOOP later
|
||||||
tv = STACK_TV_VAR(
|
tv = STACK_TV_VAR(
|
||||||
iptr->isn_arg.whileloop.while_funcref_idx);
|
iptr->isn_arg.whileloop.while_funcref_idx);
|
||||||
|
Reference in New Issue
Block a user