0
0
mirror of https://github.com/vim/vim.git synced 2025-09-02 21:13:50 -04:00

patch 7.4.2044

Problem:    filter() and map() either require a string or defining a function.
Solution:   Support lambda, a short way to define a function that evaluates an
            expression. (Yasuhiro Matsumoto, Ken Takata)
This commit is contained in:
Bram Moolenaar 2016-07-15 21:25:08 +02:00
parent 93431df9eb
commit 069c1e7fa9
7 changed files with 428 additions and 80 deletions

View File

@ -1,4 +1,4 @@
*eval.txt* For Vim version 7.4. Last change: 2016 Jul 09 *eval.txt* For Vim version 7.4. Last change: 2016 Jul 15
VIM REFERENCE MANUAL by Bram Moolenaar VIM REFERENCE MANUAL by Bram Moolenaar
@ -140,9 +140,10 @@ You will not get an error if you try to change the type of a variable.
1.2 Function references ~ 1.2 Function references ~
*Funcref* *E695* *E718* *Funcref* *E695* *E718*
A Funcref variable is obtained with the |function()| function. It can be used A Funcref variable is obtained with the |function()| function or created with
in an expression in the place of a function name, before the parenthesis the lambda expression |expr-lambda|. It can be used in an expression in the
around the arguments, to invoke the function it refers to. Example: > place of a function name, before the parenthesis around the arguments, to
invoke the function it refers to. Example: >
:let Fn = function("MyFunc") :let Fn = function("MyFunc")
:echo Fn() :echo Fn()
@ -694,6 +695,7 @@ Expression syntax summary, from least to most significant:
@r contents of register 'r' @r contents of register 'r'
function(expr1, ...) function call function(expr1, ...) function call
func{ti}on(expr1, ...) function call with curly braces func{ti}on(expr1, ...) function call with curly braces
{args -> expr1} lambda expression
".." indicates that the operations in this level can be concatenated. ".." indicates that the operations in this level can be concatenated.
@ -1207,6 +1209,42 @@ function(expr1, ...) function call
See below |functions|. See below |functions|.
lambda expression *expr-lambda* *lambda*
-----------------
{args -> expr1} lambda expression
A lambda expression creates a new unnamed function which returns the result of
evaluating |expr1|. Lambda expressions are differ from |user-functions| in
the following ways:
1. The body of the lambda expression is an |expr1| and not a sequence of |Ex|
commands.
2. The prefix "a:" is optional for arguments. E.g.: >
:let F = {arg1, arg2 -> arg1 - arg2}
:echo F(5, 2)
< 3
The arguments are optional. Example: >
:let F = {-> 'error function'}
:echo F()
< error function
Examples for using a lambda expression with |sort()|, |map()| and |filter()|: >
:echo map([1, 2, 3], {idx, val -> val + 1})
< [2, 3, 4] >
:echo sort([3,7,2,1,4], {a, b -> a - b})
< [1, 2, 3, 4, 7]
The lambda expression is also useful for Channel, Job and timer: >
:let timer = timer_start(500,
\ {-> execute("echo 'Handler called'", "")},
\ {'repeat': 3})
< Handler called
Handler called
Handler called
Note how execute() is used to execute an Ex command. That's ugly though.
============================================================================== ==============================================================================
3. Internal variable *internal-variables* *E461* 3. Internal variable *internal-variables* *E461*
@ -3278,7 +3316,8 @@ execute({command} [, {silent}]) *execute()*
"silent" `:silent` used "silent" `:silent` used
"silent!" `:silent!` used "silent!" `:silent!` used
The default is 'silent'. Note that with "silent!", unlike The default is 'silent'. Note that with "silent!", unlike
`:redir`, error messages are dropped. `:redir`, error messages are dropped. When using an external
command the screen may be messed up, use `system()` instead.
*E930* *E930*
It is not possible to use `:redir` anywhere in {command}. It is not possible to use `:redir` anywhere in {command}.
@ -7202,7 +7241,8 @@ system({expr} [, {input}]) *system()* *E677*
in a way |writefile()| does with {binary} set to "b" (i.e. in a way |writefile()| does with {binary} set to "b" (i.e.
with a newline between each list item with newlines inside with a newline between each list item with newlines inside
list items converted to NULs). list items converted to NULs).
Pipes are not used.
Pipes are not used, the 'shelltemp' option is not used.
When prepended by |:silent| the shell will not be set to When prepended by |:silent| the shell will not be set to
cooked mode. This is meant to be used for commands that do cooked mode. This is meant to be used for commands that do
@ -8204,10 +8244,10 @@ can be 0). "a:000" is set to a |List| that contains these arguments. Note
that "a:1" is the same as "a:000[0]". that "a:1" is the same as "a:000[0]".
*E742* *E742*
The a: scope and the variables in it cannot be changed, they are fixed. The a: scope and the variables in it cannot be changed, they are fixed.
However, if a |List| or |Dictionary| is used, you can change their contents. However, if a composite type is used, such as |List| or |Dictionary| , you can
Thus you can pass a |List| to a function and have the function add an item to change their contents. Thus you can pass a |List| to a function and have the
it. If you want to make sure the function cannot change a |List| or function add an item to it. If you want to make sure the function cannot
|Dictionary| use |:lockvar|. change a |List| or |Dictionary| use |:lockvar|.
When not using "...", the number of arguments in a function call must be equal When not using "...", the number of arguments in a function call must be equal
to the number of named arguments. When using "...", the number of arguments to the number of named arguments. When using "...", the number of arguments
@ -8219,9 +8259,8 @@ until the matching |:endfunction|. It is allowed to define another function
inside a function body. inside a function body.
*local-variables* *local-variables*
Inside a function variables can be used. These are local variables, which Inside a function local variables can be used. These will disappear when the
will disappear when the function returns. Global variables need to be function returns. Global variables need to be accessed with "g:".
accessed with "g:".
Example: > Example: >
:function Table(title, ...) :function Table(title, ...)

View File

@ -2046,6 +2046,7 @@ test_arglist \
test_join \ test_join \
test_json \ test_json \
test_jumps \ test_jumps \
test_lambda \
test_langmap \ test_langmap \
test_largefile \ test_largefile \
test_lispwords \ test_lispwords \

View File

@ -457,6 +457,8 @@ static dict_T *dict_copy(dict_T *orig, int deep, int copyID);
static long dict_len(dict_T *d); static long dict_len(dict_T *d);
static char_u *dict2string(typval_T *tv, int copyID, int restore_copyID); static char_u *dict2string(typval_T *tv, int copyID, int restore_copyID);
static int get_dict_tv(char_u **arg, typval_T *rettv, int evaluate); static int get_dict_tv(char_u **arg, typval_T *rettv, int evaluate);
static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, int *varargs, int skip);
static int get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate);
static char_u *echo_string_core(typval_T *tv, char_u **tofree, char_u *numbuf, int copyID, int echo_style, int restore_copyID, int dict_val); static char_u *echo_string_core(typval_T *tv, char_u **tofree, char_u *numbuf, int copyID, int echo_style, int restore_copyID, int dict_val);
static char_u *echo_string(typval_T *tv, char_u **tofree, char_u *numbuf, int copyID); static char_u *echo_string(typval_T *tv, char_u **tofree, char_u *numbuf, int copyID);
static char_u *string_quote(char_u *str, int function); static char_u *string_quote(char_u *str, int function);
@ -5261,9 +5263,12 @@ eval7(
break; break;
/* /*
* Lambda: {arg, arg -> expr}
* Dictionary: {key: val, key: val} * Dictionary: {key: val, key: val}
*/ */
case '{': ret = get_dict_tv(arg, rettv, evaluate); case '{': ret = get_lambda_tv(arg, rettv, evaluate);
if (ret == NOTDONE)
ret = get_dict_tv(arg, rettv, evaluate);
break; break;
/* /*
@ -8110,6 +8115,202 @@ failret:
return OK; return OK;
} }
/* Get function arguments. */
static int
get_function_args(
char_u **argp,
char_u endchar,
garray_T *newargs,
int *varargs,
int skip)
{
int mustend = FALSE;
char_u *arg = *argp;
char_u *p = arg;
int c;
int i;
if (newargs != NULL)
ga_init2(newargs, (int)sizeof(char_u *), 3);
if (varargs != NULL)
*varargs = FALSE;
/*
* Isolate the arguments: "arg1, arg2, ...)"
*/
while (*p != endchar)
{
if (p[0] == '.' && p[1] == '.' && p[2] == '.')
{
if (varargs != NULL)
*varargs = TRUE;
p += 3;
mustend = TRUE;
}
else
{
arg = p;
while (ASCII_ISALNUM(*p) || *p == '_')
++p;
if (arg == p || isdigit(*arg)
|| (p - arg == 9 && STRNCMP(arg, "firstline", 9) == 0)
|| (p - arg == 8 && STRNCMP(arg, "lastline", 8) == 0))
{
if (!skip)
EMSG2(_("E125: Illegal argument: %s"), arg);
break;
}
if (newargs != NULL && ga_grow(newargs, 1) == FAIL)
return FAIL;
if (newargs != NULL)
{
c = *p;
*p = NUL;
arg = vim_strsave(arg);
if (arg == NULL)
goto err_ret;
/* Check for duplicate argument name. */
for (i = 0; i < newargs->ga_len; ++i)
if (STRCMP(((char_u **)(newargs->ga_data))[i], arg) == 0)
{
EMSG2(_("E853: Duplicate argument name: %s"), arg);
vim_free(arg);
goto err_ret;
}
((char_u **)(newargs->ga_data))[newargs->ga_len] = arg;
newargs->ga_len++;
*p = c;
}
if (*p == ',')
++p;
else
mustend = TRUE;
}
p = skipwhite(p);
if (mustend && *p != endchar)
{
if (!skip)
EMSG2(_(e_invarg2), *argp);
break;
}
}
++p; /* skip the ')' */
*argp = p;
return OK;
err_ret:
if (newargs != NULL)
ga_clear_strings(newargs);
return FAIL;
}
/*
* Parse a lambda expression and get a Funcref from "*arg".
* Return OK or FAIL. Returns NOTDONE for dict or {expr}.
*/
static int
get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate)
{
garray_T newargs;
garray_T newlines;
ufunc_T *fp = NULL;
int varargs;
int ret;
char_u name[20];
char_u *start = skipwhite(*arg + 1);
char_u *s, *e;
static int lambda_no = 0;
ga_init(&newargs);
ga_init(&newlines);
/* First, check if this is a lambda expression. "->" must exists. */
ret = get_function_args(&start, '-', NULL, NULL, TRUE);
if (ret == FAIL || *start != '>')
return NOTDONE;
/* Parse the arguments again. */
*arg = skipwhite(*arg + 1);
ret = get_function_args(arg, '-', &newargs, &varargs, FALSE);
if (ret == FAIL || **arg != '>')
goto errret;
/* Get the start and the end of the expression. */
*arg = skipwhite(*arg + 1);
s = *arg;
ret = skip_expr(arg);
if (ret == FAIL)
goto errret;
e = *arg;
*arg = skipwhite(*arg);
if (**arg != '}')
goto errret;
++*arg;
if (evaluate)
{
int len;
char_u *p;
fp = (ufunc_T *)alloc((unsigned)(sizeof(ufunc_T) + 20));
if (fp == NULL)
goto errret;
sprintf((char*)name, "<lambda>%d", ++lambda_no);
ga_init2(&newlines, (int)sizeof(char_u *), 1);
if (ga_grow(&newlines, 1) == FAIL)
goto errret;
/* Add "return " before the expression.
* TODO: Support multiple expressions. */
len = 7 + e - s + 1;
p = (char_u *)alloc(len);
if (p == NULL)
goto errret;
((char_u **)(newlines.ga_data))[newlines.ga_len++] = p;
STRCPY(p, "return ");
STRNCPY(p + 7, s, e - s);
p[7 + e - s] = NUL;
fp->uf_refcount = 1;
STRCPY(fp->uf_name, name);
hash_add(&func_hashtab, UF2HIKEY(fp));
fp->uf_args = newargs;
fp->uf_lines = newlines;
#ifdef FEAT_PROFILE
fp->uf_tml_count = NULL;
fp->uf_tml_total = NULL;
fp->uf_tml_self = NULL;
fp->uf_profiling = FALSE;
if (prof_def_func())
func_do_profile(fp);
#endif
fp->uf_varargs = TRUE;
fp->uf_flags = 0;
fp->uf_calls = 0;
fp->uf_script_ID = current_SID;
rettv->vval.v_string = vim_strsave(name);
rettv->v_type = VAR_FUNC;
}
else
ga_clear_strings(&newargs);
return OK;
errret:
ga_clear_strings(&newargs);
ga_clear_strings(&newlines);
vim_free(fp);
return FAIL;
}
static char * static char *
get_var_special_name(int nr) get_var_special_name(int nr)
{ {
@ -9321,7 +9522,8 @@ call_func(
call_user_func(fp, argcount, argvars, rettv, call_user_func(fp, argcount, argvars, rettv,
firstline, lastline, firstline, lastline,
(fp->uf_flags & FC_DICT) ? selfdict : NULL); (fp->uf_flags & FC_DICT) ? selfdict : NULL);
if (--fp->uf_calls <= 0 && isdigit(*fp->uf_name) if (--fp->uf_calls <= 0 && (isdigit(*fp->uf_name)
|| STRNCMP(fp->uf_name, "<lambda>", 8) == 0)
&& fp->uf_refcount <= 0) && fp->uf_refcount <= 0)
/* Function was unreferenced while being used, free it /* Function was unreferenced while being used, free it
* now. */ * now. */
@ -24275,7 +24477,6 @@ find_option_end(char_u **arg, int *opt_flags)
ex_function(exarg_T *eap) ex_function(exarg_T *eap)
{ {
char_u *theline; char_u *theline;
int i;
int j; int j;
int c; int c;
int saved_did_emsg; int saved_did_emsg;
@ -24287,7 +24488,6 @@ ex_function(exarg_T *eap)
garray_T newargs; garray_T newargs;
garray_T newlines; garray_T newlines;
int varargs = FALSE; int varargs = FALSE;
int mustend = FALSE;
int flags = 0; int flags = 0;
ufunc_T *fp; ufunc_T *fp;
int indent; int indent;
@ -24468,7 +24668,6 @@ ex_function(exarg_T *eap)
} }
p = skipwhite(p + 1); p = skipwhite(p + 1);
ga_init2(&newargs, (int)sizeof(char_u *), 3);
ga_init2(&newlines, (int)sizeof(char_u *), 3); ga_init2(&newlines, (int)sizeof(char_u *), 3);
if (!eap->skip) if (!eap->skip)
@ -24498,66 +24697,8 @@ ex_function(exarg_T *eap)
EMSG(_("E862: Cannot use g: here")); EMSG(_("E862: Cannot use g: here"));
} }
/* if (get_function_args(&p, ')', &newargs, &varargs, eap->skip) == FAIL)
* Isolate the arguments: "arg1, arg2, ...)" goto errret_2;
*/
while (*p != ')')
{
if (p[0] == '.' && p[1] == '.' && p[2] == '.')
{
varargs = TRUE;
p += 3;
mustend = TRUE;
}
else
{
arg = p;
while (ASCII_ISALNUM(*p) || *p == '_')
++p;
if (arg == p || isdigit(*arg)
|| (p - arg == 9 && STRNCMP(arg, "firstline", 9) == 0)
|| (p - arg == 8 && STRNCMP(arg, "lastline", 8) == 0))
{
if (!eap->skip)
EMSG2(_("E125: Illegal argument: %s"), arg);
break;
}
if (ga_grow(&newargs, 1) == FAIL)
goto erret;
c = *p;
*p = NUL;
arg = vim_strsave(arg);
if (arg == NULL)
goto erret;
/* Check for duplicate argument name. */
for (i = 0; i < newargs.ga_len; ++i)
if (STRCMP(((char_u **)(newargs.ga_data))[i], arg) == 0)
{
EMSG2(_("E853: Duplicate argument name: %s"), arg);
vim_free(arg);
goto erret;
}
((char_u **)(newargs.ga_data))[newargs.ga_len] = arg;
*p = c;
newargs.ga_len++;
if (*p == ',')
++p;
else
mustend = TRUE;
}
p = skipwhite(p);
if (mustend && *p != ')')
{
if (!eap->skip)
EMSG2(_(e_invarg2), eap->arg);
break;
}
}
if (*p != ')')
goto erret;
++p; /* skip the ')' */
/* find extra arguments "range", "dict" and "abort" */ /* find extra arguments "range", "dict" and "abort" */
for (;;) for (;;)
@ -24926,6 +25067,7 @@ ex_function(exarg_T *eap)
erret: erret:
ga_clear_strings(&newargs); ga_clear_strings(&newargs);
errret_2:
ga_clear_strings(&newlines); ga_clear_strings(&newlines);
ret_free: ret_free:
vim_free(skip_until); vim_free(skip_until);
@ -25740,7 +25882,9 @@ func_unref(char_u *name)
{ {
ufunc_T *fp; ufunc_T *fp;
if (name != NULL && isdigit(*name)) if (name == NULL)
return;
else if (isdigit(*name))
{ {
fp = find_func(name); fp = find_func(name);
if (fp == NULL) if (fp == NULL)
@ -25758,6 +25902,18 @@ func_unref(char_u *name)
func_free(fp); func_free(fp);
} }
} }
else if (STRNCMP(name, "<lambda>", 8) == 0)
{
/* fail silently, when lambda function isn't found. */
fp = find_func(name);
if (fp != NULL && --fp->uf_refcount <= 0)
{
/* Only delete it when it's not being used. Otherwise it's done
* when "uf_calls" becomes zero. */
if (fp->uf_calls == 0)
func_free(fp);
}
}
} }
/* /*
@ -25768,7 +25924,9 @@ func_ref(char_u *name)
{ {
ufunc_T *fp; ufunc_T *fp;
if (name != NULL && isdigit(*name)) if (name == NULL)
return;
else if (isdigit(*name))
{ {
fp = find_func(name); fp = find_func(name);
if (fp == NULL) if (fp == NULL)
@ -25776,6 +25934,13 @@ func_ref(char_u *name)
else else
++fp->uf_refcount; ++fp->uf_refcount;
} }
else if (STRNCMP(name, "<lambda>", 8) == 0)
{
/* fail silently, when lambda function isn't found. */
fp = find_func(name);
if (fp != NULL)
++fp->uf_refcount;
}
} }
/* /*
@ -25801,6 +25966,7 @@ call_user_func(
int fixvar_idx = 0; /* index in fixvar[] */ int fixvar_idx = 0; /* index in fixvar[] */
int i; int i;
int ai; int ai;
int islambda = FALSE;
char_u numbuf[NUMBUFLEN]; char_u numbuf[NUMBUFLEN];
char_u *name; char_u *name;
size_t len; size_t len;
@ -25834,6 +26000,9 @@ call_user_func(
fc->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name, (linenr_T)0); fc->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name, (linenr_T)0);
fc->dbg_tick = debug_tick; fc->dbg_tick = debug_tick;
if (STRNCMP(fp->uf_name, "<lambda>", 8) == 0)
islambda = TRUE;
/* /*
* Note about using fc->fixvar[]: This is an array of FIXVAR_CNT variables * Note about using fc->fixvar[]: This is an array of FIXVAR_CNT variables
* with names up to VAR_SHORT_LEN long. This avoids having to alloc/free * with names up to VAR_SHORT_LEN long. This avoids having to alloc/free
@ -25891,10 +26060,17 @@ call_user_func(
(varnumber_T)lastline); (varnumber_T)lastline);
for (i = 0; i < argcount; ++i) for (i = 0; i < argcount; ++i)
{ {
int addlocal = FALSE;
dictitem_T *v2;
ai = i - fp->uf_args.ga_len; ai = i - fp->uf_args.ga_len;
if (ai < 0) if (ai < 0)
{
/* named argument a:name */ /* named argument a:name */
name = FUNCARG(fp, i); name = FUNCARG(fp, i);
if (islambda)
addlocal = TRUE;
}
else else
{ {
/* "..." argument a:1, a:2, etc. */ /* "..." argument a:1, a:2, etc. */
@ -25905,6 +26081,9 @@ call_user_func(
{ {
v = &fc->fixvar[fixvar_idx++].var; v = &fc->fixvar[fixvar_idx++].var;
v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
if (addlocal)
v2 = v;
} }
else else
{ {
@ -25913,6 +26092,18 @@ call_user_func(
if (v == NULL) if (v == NULL)
break; break;
v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX | DI_FLAGS_ALLOC; v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX | DI_FLAGS_ALLOC;
if (addlocal)
{
v2 = (dictitem_T *)alloc((unsigned)(sizeof(dictitem_T)
+ STRLEN(name)));
if (v2 == NULL)
{
vim_free(v);
break;
}
v2->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX | DI_FLAGS_ALLOC;
}
} }
STRCPY(v->di_key, name); STRCPY(v->di_key, name);
hash_add(&fc->l_avars.dv_hashtab, DI2HIKEY(v)); hash_add(&fc->l_avars.dv_hashtab, DI2HIKEY(v));
@ -25922,6 +26113,16 @@ call_user_func(
v->di_tv = argvars[i]; v->di_tv = argvars[i];
v->di_tv.v_lock = VAR_FIXED; v->di_tv.v_lock = VAR_FIXED;
/* Named arguments can be accessed without the "a:" prefix in lambda
* expressions. Add to the l: dict. */
if (addlocal)
{
STRCPY(v2->di_key, name);
copy_tv(&v->di_tv, &v2->di_tv);
v2->di_tv.v_lock = VAR_FIXED;
hash_add(&fc->l_vars.dv_hashtab, DI2HIKEY(v2));
}
if (ai >= 0 && ai < MAX_FUNC_ARGS) if (ai >= 0 && ai < MAX_FUNC_ARGS)
{ {
list_append(&fc->l_varlist, &fc->l_listitems[ai]); list_append(&fc->l_varlist, &fc->l_listitems[ai]);

View File

@ -19,6 +19,7 @@ source test_goto.vim
source test_help_tagjump.vim source test_help_tagjump.vim
source test_join.vim source test_join.vim
source test_jumps.vim source test_jumps.vim
source test_lambda.vim
source test_lispwords.vim source test_lispwords.vim
source test_matchstrpos.vim source test_matchstrpos.vim
source test_menu.vim source test_menu.vim

View File

@ -95,6 +95,18 @@ func Ch_communicate(port)
endif endif
call assert_equal('got it', g:Ch_responseMsg) call assert_equal('got it', g:Ch_responseMsg)
" Using lambda.
let g:Ch_responseMsg = ''
call ch_sendexpr(handle, 'hello!', {'callback': {a, b -> Ch_requestHandler(a, b)}})
call WaitFor('exists("g:Ch_responseHandle")')
if !exists('g:Ch_responseHandle')
call assert_false(1, 'g:Ch_responseHandle was not set')
else
call assert_equal(handle, g:Ch_responseHandle)
unlet g:Ch_responseHandle
endif
call assert_equal('got it', g:Ch_responseMsg)
" Collect garbage, tests that our handle isn't collected. " Collect garbage, tests that our handle isn't collected.
call test_garbagecollect_now() call test_garbagecollect_now()
@ -1069,6 +1081,32 @@ func Test_read_in_close_cb()
endtry endtry
endfunc endfunc
func Test_out_cb_lambda()
if !has('job')
return
endif
call ch_log('Test_out_cb_lambda()')
let job = job_start(s:python . " test_channel_pipe.py",
\ {'out_cb': {ch, msg -> execute("let g:Ch_outmsg = 'lambda: ' . msg")},
\ 'out_mode': 'json',
\ 'err_cb': {ch, msg -> execute(":let g:Ch_errmsg = 'lambda: ' . msg")},
\ 'err_mode': 'json'})
call assert_equal("run", job_status(job))
try
let g:Ch_outmsg = ''
let g:Ch_errmsg = ''
call ch_sendraw(job, "echo [0, \"hello\"]\n")
call ch_sendraw(job, "echoerr [0, \"there\"]\n")
call WaitFor('g:Ch_outmsg != ""')
call assert_equal("lambda: hello", g:Ch_outmsg)
call WaitFor('g:Ch_errmsg != ""')
call assert_equal("lambda: there", g:Ch_errmsg)
finally
call job_stop(job)
endtry
endfunc
"""""""""" """"""""""
let g:Ch_unletResponse = '' let g:Ch_unletResponse = ''
@ -1285,6 +1323,24 @@ func Test_collapse_buffers()
bwipe! bwipe!
endfunc endfunc
function Ch_test_close_lambda(port)
let handle = ch_open('localhost:' . a:port, s:chopt)
if ch_status(handle) == "fail"
call assert_false(1, "Can't open channel")
return
endif
let g:Ch_close_ret = ''
call ch_setoptions(handle, {'close_cb': {ch -> execute("let g:Ch_close_ret = 'closed'")}})
call assert_equal('', ch_evalexpr(handle, 'close me'))
call WaitFor('"closed" == g:Ch_close_ret')
call assert_equal('closed', g:Ch_close_ret)
endfunc
func Test_close_lambda()
call ch_log('Test_close_lambda()')
call s:run_server('Ch_test_close_lambda')
endfunc
" Uncomment this to see what happens, output is in src/testdir/channellog. " Uncomment this to see what happens, output is in src/testdir/channellog.
call ch_logfile('channellog', 'w') call ch_logfile('channellog', 'w')

View File

@ -0,0 +1,48 @@
function! Test_lambda_with_filter()
let s:x = 2
call assert_equal([2, 3], filter([1, 2, 3], {i, v -> v >= s:x}))
endfunction
function! Test_lambda_with_map()
let s:x = 1
call assert_equal([2, 3, 4], map([1, 2, 3], {i, v -> v + s:x}))
endfunction
function! Test_lambda_with_sort()
call assert_equal([1, 2, 3, 4, 7], sort([3,7,2,1,4], {a, b -> a - b}))
endfunction
function! Test_lambda_with_timer()
if !has('timers')
return
endif
let s:n = 0
let s:timer_id = 0
function! s:Foo()
"let n = 0
let s:timer_id = timer_start(50, {-> execute("let s:n += 1 | echo s:n")}, {"repeat": -1})
endfunction
call s:Foo()
sleep 200ms
" do not collect lambda
call test_garbagecollect_now()
let m = s:n
sleep 200ms
call timer_stop(s:timer_id)
call assert_true(m > 1)
call assert_true(s:n > m + 1)
call assert_true(s:n < 9)
endfunction
function! Test_lambda_with_partial()
let l:Cb = function({... -> ['zero', a:1, a:2, a:3]}, ['one', 'two'])
call assert_equal(['zero', 'one', 'two', 'three'], l:Cb('three'))
endfunction
function Test_lambda_fails()
call assert_equal(3, {a, b -> a + b}(1, 2))
call assert_fails('echo {a, a -> a + a}(1, 2)', 'E15:')
call assert_fails('echo {a, b -> a + b)}(1, 2)', 'E15:')
endfunc

View File

@ -758,6 +758,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 */
/**/
2044,
/**/ /**/
2043, 2043,
/**/ /**/