mirror of
https://github.com/neovim/neovim.git
synced 2025-10-02 16:08:36 +00:00
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'
87b4e5c5db
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>
This commit is contained in:
@@ -69,8 +69,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: >
|
||||||
@@ -79,6 +77,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 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.
|
||||||
|
@@ -400,6 +400,14 @@ Set using a variable with lambda expression: >
|
|||||||
let L = {a, b, c -> MyTagFunc(a, b , c)}
|
let L = {a, b, c -> MyTagFunc(a, b , c)}
|
||||||
let &tagfunc = L
|
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
|
Setting the filetype
|
||||||
|
|
||||||
|
@@ -1349,7 +1349,7 @@ int eval_foldexpr(win_T *wp, int *cp)
|
|||||||
const sctx_T saved_sctx = current_sctx;
|
const sctx_T saved_sctx = current_sctx;
|
||||||
const bool use_sandbox = was_set_insecurely(wp, kOptFoldexpr, OPT_LOCAL);
|
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;
|
current_sctx = wp->w_p_script_ctx[WV_FDE].script_ctx;
|
||||||
|
|
||||||
emsg_off++;
|
emsg_off++;
|
||||||
@@ -1360,8 +1360,23 @@ int eval_foldexpr(win_T *wp, int *cp)
|
|||||||
*cp = NUL;
|
*cp = NUL;
|
||||||
|
|
||||||
typval_T tv;
|
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;
|
varnumber_T retval;
|
||||||
if (eval0(arg, &tv, NULL, &EVALARG_EVALUATE) == FAIL) {
|
if (r == FAIL) {
|
||||||
retval = 0;
|
retval = 0;
|
||||||
} else {
|
} else {
|
||||||
// If the result is a number, just return the number.
|
// If the result is a number, just return the number.
|
||||||
@@ -1428,6 +1443,31 @@ Object eval_foldtext(win_T *wp)
|
|||||||
return retval;
|
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",
|
/// 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[expr]", "name[expr][expr]", "name.key", "name.key[expr]" etc.
|
||||||
/// "name" points to the start of the name.
|
/// "name" points to the start of the name.
|
||||||
|
@@ -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.
|
/// Give an error message for the result of a function.
|
||||||
/// Nothing if "error" is FCERR_NONE.
|
/// 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)
|
FUNC_ATTR_NONNULL_ARG(2)
|
||||||
{
|
{
|
||||||
switch (error) {
|
switch (error) {
|
||||||
case FCERR_UNKNOWN:
|
case FCERR_UNKNOWN:
|
||||||
if (funcexe->fe_found_var) {
|
if (found_var) {
|
||||||
semsg(_(e_not_callable_type_str), name);
|
semsg(_(e_not_callable_type_str), name);
|
||||||
} else {
|
} else {
|
||||||
emsg_funcname(e_unknown_function_str, name);
|
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) {
|
if (error == FCERR_NONE && funcexe->fe_evaluate) {
|
||||||
char *rfname = fname;
|
// Skip "g:" before a function name.
|
||||||
|
bool is_global = fp == NULL && fname[0] == 'g' && fname[1] == ':';
|
||||||
// Ignore "g:" before a function name.
|
char *rfname = is_global ? fname + 2 : fname;
|
||||||
if (fp == NULL && fname[0] == 'g' && fname[1] == ':') {
|
|
||||||
rfname = fname + 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
rettv->v_type = VAR_NUMBER; // default rettv is number zero
|
rettv->v_type = VAR_NUMBER; // default rettv is number zero
|
||||||
rettv->vval.v_number = 0;
|
rettv->vval.v_number = 0;
|
||||||
@@ -1765,7 +1762,7 @@ theend:
|
|||||||
// Report an error unless the argument evaluation or function call has been
|
// Report an error unless the argument evaluation or function call has been
|
||||||
// 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
|
||||||
@@ -1779,6 +1776,58 @@ 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.
|
||||||
|
///
|
||||||
|
/// @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)
|
char *printable_func_name(ufunc_T *fp)
|
||||||
{
|
{
|
||||||
return fp->uf_name_exp != NULL ? fp->uf_name_exp : fp->uf_name;
|
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) {
|
if (ufunc != NULL) {
|
||||||
int error = check_user_func_argcount(ufunc, argcount);
|
int error = check_user_func_argcount(ufunc, argcount);
|
||||||
if (error != FCERR_UNKNOWN) {
|
if (error != FCERR_UNKNOWN) {
|
||||||
user_func_error(error, name, NULL);
|
user_func_error(error, name, false);
|
||||||
r = FAIL;
|
r = FAIL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -386,6 +386,32 @@ func Test_foldexpr_no_interrupt_addsub()
|
|||||||
set foldmethod& foldexpr&
|
set foldmethod& foldexpr&
|
||||||
endfunc
|
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)
|
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
|
||||||
|
Reference in New Issue
Block a user