diff options
author | Michael Ennen <mike.ennen@gmail.com> | 2016-10-24 23:53:07 -0700 |
---|---|---|
committer | James McCoy <jamessan@jamessan.com> | 2016-12-12 10:17:34 -0500 |
commit | 521e45f2a8c0619335288accdda0f0aaa1fc6513 (patch) | |
tree | c9f188f26ae7738a2dc2e71e3c816cdf62d5c151 | |
parent | 75c18b6aaa8430596fa10466dc7918047b13ff2b (diff) | |
download | rneovim-521e45f2a8c0619335288accdda0f0aaa1fc6513.tar.gz rneovim-521e45f2a8c0619335288accdda0f0aaa1fc6513.tar.bz2 rneovim-521e45f2a8c0619335288accdda0f0aaa1fc6513.zip |
vim-patch:7.4.1559
Problem: Passing cookie to a callback is clumsy.
Solution: Change function() to take arguments and return a partial.
https://github.com/vim/vim/commit/1735bc988c546cc962c5f94792815b4d7cb79710
-rw-r--r-- | runtime/doc/eval.txt | 41 | ||||
-rw-r--r-- | src/nvim/api/private/helpers.c | 4 | ||||
-rw-r--r-- | src/nvim/api/vim.c | 2 | ||||
-rw-r--r-- | src/nvim/eval.c | 410 | ||||
-rw-r--r-- | src/nvim/eval.lua | 2 | ||||
-rw-r--r-- | src/nvim/eval/encode.c | 20 | ||||
-rw-r--r-- | src/nvim/eval/typval_encode.h | 9 | ||||
-rw-r--r-- | src/nvim/eval_defs.h | 13 | ||||
-rw-r--r-- | src/nvim/normal.c | 2 | ||||
-rw-r--r-- | src/nvim/testdir/test_alot.vim | 1 | ||||
-rw-r--r-- | src/nvim/testdir/test_partial.vim | 43 | ||||
-rw-r--r-- | src/nvim/version.c | 2 |
12 files changed, 451 insertions, 98 deletions
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 53f14c7d47..f377ebdf31 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -1913,7 +1913,8 @@ foldlevel({lnum}) Number fold level at {lnum} foldtext() String line displayed for closed fold foldtextresult({lnum}) String text for closed fold at {lnum} foreground() Number bring the Vim window to the foreground -function({name}) Funcref reference to function {name} +function({name} [, {arglist}] [, {dict}]) + Funcref reference to function {name} garbagecollect([{atexit}]) none free memory, breaking cyclic references get({list}, {idx} [, {def}]) any get item {idx} from {list} or {def} get({dict}, {key} [, {def}]) any get item {key} from {dict} or {def} @@ -3483,10 +3484,46 @@ foreground() Move the Vim window to the foreground. Useful when sent from {only in the Win32 GUI and console version} -function({name}) *function()* *E700* + *function()* *E700* *E922* *E929* +function({name} [, {arglist}] [, {dict}]) Return a |Funcref| variable that refers to function {name}. {name} can be a user defined function or an internal function. + When {arglist} or {dict} is present this creates a partial. + That mans the argument list and/or the dictionary is stored in + the Funcref and will be used when the Funcref is called. + + The arguments are passed to the function in front of other + arguments. Example: > + func Callback(arg1, arg2, name) + ... + let Func = function('Callback', ['one', 'two']) + ... + call Func('name') +< Invokes the function as with: > + call Callback('one', 'two', 'name') + +< The Dictionary is only useful when calling a "dict" function. + In that case the {dict} is passed in as "self". Example: > + function Callback() dict + echo "called for " . self.name + endfunction + ... + let context = {"name": "example"} + let Func = function('Callback', context) + ... + call Func() " will echo: called for example + +< The argument list and the Dictionary can be combined: > + function Callback(arg1, count) dict + ... + let context = {"name": "example"} + let Func = function('Callback', ['one'], context) + ... + call Func(500) +< Invokes the function as with: > + call context.Callback('one', 500) + garbagecollect([{atexit}]) *garbagecollect()* Cleanup unused |Lists| and |Dictionaries| that have circular diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 72db7c0782..0acdff3232 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -360,6 +360,9 @@ void set_option_to(void *to, int type, String name, Object value, Error *err) #define TYPVAL_ENCODE_CONV_FUNC(fun) \ TYPVAL_ENCODE_CONV_NIL() +#define TYPVAL_ENCODE_CONV_PARTIAL(partial) \ + TYPVAL_ENCODE_CONV_NIL() + #define TYPVAL_ENCODE_CONV_EMPTY_LIST() \ kv_push(edata->stack, ARRAY_OBJ(((Array) { .capacity = 0, .size = 0 }))) @@ -482,6 +485,7 @@ TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(static, object, EncodedData *const, edata) #undef TYPVAL_ENCODE_CONV_NUMBER #undef TYPVAL_ENCODE_CONV_FLOAT #undef TYPVAL_ENCODE_CONV_FUNC +#undef TYPVAL_ENCODE_CONV_PARTIAL #undef TYPVAL_ENCODE_CONV_EMPTY_LIST #undef TYPVAL_ENCODE_CONV_LIST_START #undef TYPVAL_ENCODE_CONV_EMPTY_DICT diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index e59e955aed..b17b59f7a6 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -225,7 +225,7 @@ Object nvim_call_function(String fname, Array args, Error *err) &rettv, (int) args.size, vim_args, curwin->w_cursor.lnum, curwin->w_cursor.lnum, &dummy, true, - NULL); + NULL, NULL); if (r == FAIL) { api_set_error(err, Exception, _("Error calling function.")); } diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 2a2f94f629..6dd0b8cb4a 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -1208,7 +1208,7 @@ int call_vim_function( rettv->v_type = VAR_UNKNOWN; /* clear_tv() uses this */ ret = call_func(func, (int)STRLEN(func), rettv, argc, argvars, curwin->w_cursor.lnum, curwin->w_cursor.lnum, - &doesrange, true, NULL); + &doesrange, true, NULL, NULL); if (safe) { --sandbox; restore_funccal(save_funccalp); @@ -2455,6 +2455,7 @@ static int tv_op(typval_T *tv1, typval_T *tv2, char_u *op) switch (tv1->v_type) { case VAR_DICT: case VAR_FUNC: + case VAR_PARTIAL: case VAR_SPECIAL: break; @@ -2756,8 +2757,9 @@ void ex_call(exarg_T *eap) typval_T rettv; linenr_T lnum; int doesrange; - int failed = FALSE; + int failed = false; funcdict_T fudi; + partial_T *partial; if (eap->skip) { /* trans_function_name() doesn't work well when skipping, use eval0() @@ -2786,7 +2788,7 @@ void ex_call(exarg_T *eap) /* If it is the name of a variable of type VAR_FUNC use its contents. */ len = (int)STRLEN(tofree); - name = deref_func_name(tofree, &len, FALSE); + name = deref_func_name(tofree, &len, &partial, false); /* Skip white space to allow ":call func ()". Not good, but required for * backward compatibility. */ @@ -2817,9 +2819,9 @@ void ex_call(exarg_T *eap) } arg = startarg; if (get_func_tv(name, (int)STRLEN(name), &rettv, &arg, - eap->line1, eap->line2, &doesrange, - !eap->skip, fudi.fd_dict) == FAIL) { - failed = TRUE; + eap->line1, eap->line2, &doesrange, + !eap->skip, partial, fudi.fd_dict) == FAIL) { + failed = true; break; } @@ -3182,6 +3184,7 @@ static void item_lock(typval_T *tv, int deep, int lock) case VAR_FLOAT: case VAR_STRING: case VAR_FUNC: + case VAR_PARTIAL: case VAR_SPECIAL: break; case VAR_UNKNOWN: @@ -3754,7 +3757,9 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate) if (type == TYPE_NEQUAL) n1 = !n1; } - } else if (rettv->v_type == VAR_FUNC || var2.v_type == VAR_FUNC) { + } else if (rettv->v_type == VAR_FUNC || var2.v_type == VAR_FUNC + || rettv->v_type == VAR_PARTIAL + || var2.v_type == VAR_PARTIAL) { if (rettv->v_type != var2.v_type || (type != TYPE_EQUAL && type != TYPE_NEQUAL)) { if (rettv->v_type != var2.v_type) @@ -3764,16 +3769,21 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate) clear_tv(rettv); clear_tv(&var2); return FAIL; + } else if (rettv->v_type == VAR_PARTIAL) { + // Partials are only equal when identical. + n1 = rettv->vval.v_partial != NULL + && rettv->vval.v_partial == var2.vval.v_partial; } else { /* Compare two Funcrefs for being equal or unequal. */ if (rettv->vval.v_string == NULL - || var2.vval.v_string == NULL) - n1 = FALSE; - else - n1 = STRCMP(rettv->vval.v_string, - var2.vval.v_string) == 0; - if (type == TYPE_NEQUAL) - n1 = !n1; + || var2.vval.v_string == NULL) { + n1 = false; + } else { + n1 = STRCMP(rettv->vval.v_string, var2.vval.v_string) == 0; + } + } + if (type == TYPE_NEQUAL) { + n1 = !n1; } } /* @@ -4308,14 +4318,15 @@ static int eval7( ret = FAIL; } else { if (**arg == '(') { // recursive! + partial_T *partial; // If "s" is the name of a variable of type VAR_FUNC // use its contents. - s = deref_func_name(s, &len, !evaluate); + s = deref_func_name(s, &len, &partial, !evaluate); // Invoke the function. ret = get_func_tv(s, len, rettv, arg, - curwin->w_cursor.lnum, curwin->w_cursor.lnum, - &len, evaluate, NULL); + curwin->w_cursor.lnum, curwin->w_cursor.lnum, + &len, evaluate, partial, NULL); // If evaluate is false rettv->v_type was not set in // get_func_tv, but it's needed in handle_subscript() to parse @@ -4418,7 +4429,8 @@ eval_index ( char_u *key = NULL; switch (rettv->v_type) { - case VAR_FUNC: { + case VAR_FUNC: + case VAR_PARTIAL: { if (verbose) { EMSG(_("E695: Cannot index a Funcref")); } @@ -4638,6 +4650,7 @@ eval_index ( break; case VAR_SPECIAL: case VAR_FUNC: + case VAR_PARTIAL: case VAR_FLOAT: case VAR_UNKNOWN: break; // Not evaluating, skipping over subscript @@ -5177,6 +5190,10 @@ tv_equal ( && tv2->vval.v_string != NULL && STRCMP(tv1->vval.v_string, tv2->vval.v_string) == 0; + case VAR_PARTIAL: + return tv1->vval.v_partial != NULL + && tv1->vval.v_partial == tv2->vval.v_partial; + case VAR_NUMBER: return tv1->vval.v_number == tv2->vval.v_number; @@ -6059,6 +6076,7 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, } case VAR_FUNC: + case VAR_PARTIAL: case VAR_UNKNOWN: case VAR_SPECIAL: case VAR_FLOAT: @@ -6797,12 +6815,18 @@ static VimLFuncDef *find_internal_func(const char *const name) /* * Check if "name" is a variable of type VAR_FUNC. If so, return the function * name it contains, otherwise return "name". + * If "name" is of type VAR_PARTIAL also return "partial" */ -static char_u *deref_func_name(char_u *name, int *lenp, int no_autoload) +static char_u *deref_func_name( + char_u *name, int *lenp, + partial_T **partial, int no_autoload +) { dictitem_T *v; int cc; + *partial = NULL; + cc = name[*lenp]; name[*lenp] = NUL; v = find_var(name, NULL, no_autoload); @@ -6816,6 +6840,16 @@ static char_u *deref_func_name(char_u *name, int *lenp, int no_autoload) return v->di_tv.vval.v_string; } + if (v != NULL && v->di_tv.v_type == VAR_PARTIAL) { + *partial = v->di_tv.vval.v_partial; + if (*partial == NULL) { + *lenp = 0; + return (char_u *)""; // just in case + } + *lenp = (int)STRLEN((*partial)->pt_name); + return (*partial)->pt_name; + } + return name; } @@ -6833,7 +6867,8 @@ get_func_tv ( linenr_T lastline, /* last line of range */ int *doesrange, /* return: function handled range */ int evaluate, - dict_T *selfdict /* Dictionary for "self" */ + partial_T *partial, // for extra arguments + dict_T *selfdict // Dictionary for "self" ) { char_u *argp; @@ -6845,10 +6880,11 @@ get_func_tv ( * Get the arguments. */ argp = *arg; - while (argcount < MAX_FUNC_ARGS) { - argp = skipwhite(argp + 1); /* skip the '(' or ',' */ - if (*argp == ')' || *argp == ',' || *argp == NUL) + while (argcount < MAX_FUNC_ARGS - (partial == NULL ? 0 : partial->pt_argc)) { + argp = skipwhite(argp + 1); // skip the '(' or ',' + if (*argp == ')' || *argp == ',' || *argp == NUL) { break; + } if (eval1(&argp, &argvars[argcount], evaluate) == FAIL) { ret = FAIL; break; @@ -6862,14 +6898,16 @@ get_func_tv ( else ret = FAIL; - if (ret == OK) + if (ret == OK) { ret = call_func(name, len, rettv, argcount, argvars, - firstline, lastline, doesrange, evaluate, selfdict); - else if (!aborting()) { - if (argcount == MAX_FUNC_ARGS) + firstline, lastline, doesrange, evaluate, + partial, selfdict); + } else if (!aborting()) { + if (argcount == MAX_FUNC_ARGS) { emsg_funcname(N_("E740: Too many arguments for function %s"), name); - else + } else { emsg_funcname(N_("E116: Invalid arguments for function %s"), name); + } } while (--argcount >= 0) @@ -6886,18 +6924,19 @@ get_func_tv ( * Also returns OK when an error was encountered while executing the function. */ int -call_func ( - char_u *funcname, /* name of the function */ - int len, /* length of "name" */ - typval_T *rettv, /* return value goes here */ - int argcount, /* number of "argvars" */ - typval_T *argvars, /* vars for arguments, must have "argcount" - PLUS ONE elements! */ - linenr_T firstline, /* first line of range */ - linenr_T lastline, /* last line of range */ - int *doesrange, /* return: function handled range */ +call_func( + char_u *funcname, // name of the function + int len, // length of "name" + typval_T *rettv, // return value goes here + int argcount_in, // number of "argvars" + typval_T *argvars_in, // vars for arguments, must have "argcount" + // PLUS ONE elements! + linenr_T firstline, // first line of range + linenr_T lastline, // last line of range + int *doesrange, // return: function handled range int evaluate, - dict_T *selfdict /* Dictionary for "self" */ + partial_T *partial, // optional, can be NULL + dict_T *selfdict_in // Dictionary for "self" ) { int ret = FAIL; @@ -6908,6 +6947,7 @@ call_func ( #define ERROR_DICT 4 #define ERROR_NONE 5 #define ERROR_OTHER 6 +#define ERROR_BOTH 7 int error = ERROR_NONE; int llen; ufunc_T *fp; @@ -6915,9 +6955,14 @@ call_func ( char_u fname_buf[FLEN_FIXED + 1]; char_u *fname; char_u *name; - - /* Make a copy of the name, if it comes from a funcref variable it could - * be changed or deleted in the called function. */ + int argcount = argcount_in; + typval_T *argvars = argvars_in; + dict_T *selfdict = selfdict_in; + typval_T argv[MAX_FUNC_ARGS + 1]; // used when "partial" is not NULL + int argv_clear = 0; + + // Make a copy of the name, if it comes from a funcref variable it could + // be changed or deleted in the called function. name = vim_strnsave(funcname, len); /* @@ -6953,6 +6998,27 @@ call_func ( *doesrange = FALSE; + if (partial != NULL) { + if (partial->pt_dict != NULL) { + if (selfdict_in != NULL) { + error = ERROR_BOTH; + } + selfdict = partial->pt_dict; + } + if (error == ERROR_NONE && partial->pt_argc > 0) { + int i; + + for (argv_clear = 0; argv_clear < partial->pt_argc; argv_clear++) { + copy_tv(&partial->pt_argv[argv_clear], &argv[argv_clear]); + } + for (i = 0; i < argcount_in; i++) { + argv[i + argv_clear] = argvars_in[i]; + } + argvars = argv; + argcount = partial->pt_argc + argcount_in; + } + } + /* execute the function if no errors detected and executing */ if (evaluate && error == ERROR_NONE) { @@ -7056,11 +7122,19 @@ call_func ( emsg_funcname(N_("E725: Calling dict function without Dictionary: %s"), name); break; + case ERROR_BOTH: + emsg_funcname(N_("E924: can't have both a \"self\" dict and " + "a partial: %s"), name); + break; } } - if (fname != name && fname != fname_buf) + while (argv_clear > 0) { + clear_tv(&argv[--argv_clear]); + } + if (fname != name && fname != fname_buf) { xfree(fname); + } xfree(name); return ret; @@ -7823,7 +7897,8 @@ static void f_byteidxcomp(typval_T *argvars, typval_T *rettv, FunPtr fptr) byteidx(argvars, rettv, TRUE); } -int func_call(char_u *name, typval_T *args, dict_T *selfdict, typval_T *rettv) +int func_call(char_u *name, typval_T *args, partial_T *partial, + dict_T *selfdict, typval_T *rettv) { listitem_T *item; typval_T argv[MAX_FUNC_ARGS + 1]; @@ -7833,7 +7908,7 @@ int func_call(char_u *name, typval_T *args, dict_T *selfdict, typval_T *rettv) for (item = args->vval.v_list->lv_first; item != NULL; item = item->li_next) { - if (argc == MAX_FUNC_ARGS) { + if (argc == MAX_FUNC_ARGS - (partial == NULL ? 0 : partial->pt_argc)) { EMSG(_("E699: Too many arguments")); break; } @@ -7843,10 +7918,11 @@ int func_call(char_u *name, typval_T *args, dict_T *selfdict, typval_T *rettv) copy_tv(&item->li_tv, &argv[argc++]); } - if (item == NULL) + if (item == NULL) { r = call_func(name, (int)STRLEN(name), rettv, argc, argv, - curwin->w_cursor.lnum, curwin->w_cursor.lnum, - &dummy, TRUE, selfdict); + curwin->w_cursor.lnum, curwin->w_cursor.lnum, + &dummy, true, partial, selfdict); + } /* Free the arguments. */ while (argc > 0) @@ -7855,12 +7931,11 @@ int func_call(char_u *name, typval_T *args, dict_T *selfdict, typval_T *rettv) return r; } -/* - * "call(func, arglist)" function - */ +/// "call(func, arglist [, dict])" function static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char_u *func; + partial_T *partial = NULL; dict_T *selfdict = NULL; if (argvars[1].v_type != VAR_LIST) { @@ -7870,12 +7945,17 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[1].vval.v_list == NULL) return; - if (argvars[0].v_type == VAR_FUNC) + if (argvars[0].v_type == VAR_FUNC) { func = argvars[0].vval.v_string; - else + } else if (argvars[0].v_type == VAR_PARTIAL) { + partial = argvars[0].vval.v_partial; + func = partial->pt_name; + } else { func = get_tv_string(&argvars[0]); - if (*func == NUL) - return; /* type error or empty name */ + } + if (*func == NUL) { + return; // type error or empty name + } if (argvars[2].v_type != VAR_UNKNOWN) { if (argvars[2].v_type != VAR_DICT) { @@ -7885,7 +7965,7 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) selfdict = argvars[2].vval.v_dict; } - (void)func_call(func, &argvars[1], selfdict, rettv); + (void)func_call(func, &argvars[1], partial, selfdict, rettv); } /* @@ -8467,6 +8547,9 @@ static void f_empty(typval_T *argvars, typval_T *rettv, FunPtr fptr) n = argvars[0].vval.v_string == NULL || *argvars[0].vval.v_string == NUL; break; + case VAR_PARTIAL: + n = false; + break; case VAR_NUMBER: n = argvars[0].vval.v_number == 0; break; @@ -9342,6 +9425,7 @@ static void f_foreground(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char_u *s; + char_u *name; s = get_tv_string(&argvars[0]); if (s == NULL || *s == NUL || ascii_isdigit(*s)) @@ -9354,23 +9438,112 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) char sid_buf[25]; int off = *s == 's' ? 2 : 5; - /* Expand s: and <SID> into <SNR>nr_, so that the function can - * also be called from another script. Using trans_function_name() - * would also work, but some plugins depend on the name being - * printable text. */ - sprintf(sid_buf, "<SNR>%" PRId64 "_", (int64_t)current_SID); - rettv->vval.v_string = xmalloc(STRLEN(sid_buf) + STRLEN(s + off) + 1); - STRCPY(rettv->vval.v_string, sid_buf); - STRCAT(rettv->vval.v_string, s + off); - } else - rettv->vval.v_string = vim_strsave(s); - rettv->v_type = VAR_FUNC; + // Expand s: and <SID> into <SNR>nr_, so that the function can + // also be called from another script. Using trans_function_name() + // would also work, but some plugins depend on the name being + // printable text. + snprintf(sid_buf, sizeof(sid_buf), "<SNR>%" PRId64 "_", + (int64_t)current_SID); + name = xmalloc((int)(STRLEN(sid_buf) + STRLEN(s + off) + 1)); + if (name != NULL) { + STRCPY(name, sid_buf); + STRCAT(name, s + off); + } + } else { + name = vim_strsave(s); + } + + if (argvars[1].v_type != VAR_UNKNOWN) { + partial_T *pt; + int dict_idx = 0; + int arg_idx = 0; + + if (argvars[2].v_type != VAR_UNKNOWN) { + // function(name, [args], dict) + arg_idx = 1; + dict_idx = 2; + } else if (argvars[1].v_type == VAR_DICT) { + // function(name, dict) + dict_idx = 1; + } else { + // function(name, [args]) + arg_idx = 1; + } + if (dict_idx > 0 && (argvars[dict_idx].v_type != VAR_DICT + || argvars[dict_idx].vval.v_dict == NULL)) { + EMSG(_("E922: expected a dict")); + xfree(name); + return; + } + if (arg_idx > 0 && (argvars[arg_idx].v_type != VAR_LIST + || argvars[arg_idx].vval.v_list == NULL)) { + EMSG(_("E923: Second argument of function() must be a list or a dict")); + xfree(name); + return; + } + + pt = (partial_T *)xcalloc(1, sizeof(partial_T)); + if (pt != NULL) { + if (arg_idx > 0) { + list_T *list = argvars[arg_idx].vval.v_list; + listitem_T *li; + int i = 0; + + pt->pt_argv = (typval_T *)xmalloc(sizeof(typval_T) * list->lv_len); + if (pt->pt_argv == NULL) { + xfree(pt); + xfree(name); + return; + } else { + pt->pt_argc = list->lv_len; + for (li = list->lv_first; li != NULL; li = li->li_next) { + copy_tv(&li->li_tv, &pt->pt_argv[i++]); + } + } + } + + if (dict_idx > 0) { + pt->pt_dict = argvars[dict_idx].vval.v_dict; + (pt->pt_dict->dv_refcount)++; + } + + pt->pt_refcount = 1; + pt->pt_name = name; + func_ref(pt->pt_name); + } + rettv->v_type = VAR_PARTIAL; + rettv->vval.v_partial = pt; + } else { + rettv->v_type = VAR_FUNC; + rettv->vval.v_string = name; + func_ref(name); + } } } -/* - * "garbagecollect()" function - */ +static void partial_free(partial_T *pt) +{ + int i; + + for (i = 0; i < pt->pt_argc; i++) { + clear_tv(&pt->pt_argv[i]); + } + xfree(pt->pt_argv); + func_unref(pt->pt_name); + xfree(pt->pt_name); + xfree(pt); +} + +// Unreference a closure: decrement the reference count and free it when it +// becomes zero. +void partial_unref(partial_T *pt) +{ + if (pt != NULL && --pt->pt_refcount <= 0) { + partial_free(pt); + } +} + +/// "garbagecollect()" function static void f_garbagecollect(typval_T *argvars, typval_T *rettv, FunPtr fptr) { /* This is postponed until we are back at the toplevel, because we may be @@ -11971,6 +12144,7 @@ static void f_len(typval_T *argvars, typval_T *rettv, FunPtr fptr) case VAR_SPECIAL: case VAR_FLOAT: case VAR_FUNC: + case VAR_PARTIAL: EMSG(_("E701: Invalid type for len()")); break; } @@ -15035,6 +15209,7 @@ typedef struct { bool item_compare_numbers; bool item_compare_float; char_u *item_compare_func; + partial_T *item_compare_partial; dict_T *item_compare_selfdict; int item_compare_func_err; } sortinfo_T; @@ -15141,6 +15316,8 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero) typval_T rettv; typval_T argv[3]; int dummy; + char_u *func_name; + partial_T *partial = sortinfo->item_compare_partial; // shortcut after failure in previous call; compare all items equal if (sortinfo->item_compare_func_err) { @@ -15150,16 +15327,22 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero) si1 = (sortItem_T *)s1; si2 = (sortItem_T *)s2; + if (partial == NULL) { + func_name = sortinfo->item_compare_func; + } else { + func_name = partial->pt_name; + } + // Copy the values. This is needed to be able to set v_lock to VAR_FIXED // in the copy without changing the original list items. copy_tv(&si1->item->li_tv, &argv[0]); copy_tv(&si2->item->li_tv, &argv[1]); rettv.v_type = VAR_UNKNOWN; // clear_tv() uses this - res = call_func(sortinfo->item_compare_func, - (int)STRLEN(sortinfo->item_compare_func), + res = call_func(func_name, + (int)STRLEN(func_name), &rettv, 2, argv, 0L, 0L, &dummy, true, - sortinfo->item_compare_selfdict); + partial, sortinfo->item_compare_selfdict); clear_tv(&argv[0]); clear_tv(&argv[1]); @@ -15235,12 +15418,15 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) info.item_compare_numbers = false; info.item_compare_float = false; info.item_compare_func = NULL; + info.item_compare_partial = NULL; info.item_compare_selfdict = NULL; if (argvars[1].v_type != VAR_UNKNOWN) { /* optional second argument: {func} */ if (argvars[1].v_type == VAR_FUNC) { info.item_compare_func = argvars[1].vval.v_string; + } else if (argvars[1].v_type == VAR_PARTIAL) { + info.item_compare_partial = argvars[1].vval.v_partial; } else { int error = FALSE; @@ -15300,14 +15486,16 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) info.item_compare_func_err = false; // Test the compare function. - if (info.item_compare_func != NULL + if ((info.item_compare_func != NULL + || info.item_compare_partial != NULL) && item_compare2_not_keeping_zero(&ptrs[0], &ptrs[1]) == ITEM_COMPARE_FAIL) { EMSG(_("E702: Sort compare function failed")); } else { // Sort the array with item pointers. qsort(ptrs, (size_t)len, sizeof (sortItem_T), - (info.item_compare_func == NULL ? + (info.item_compare_func == NULL + && info.item_compare_partial == NULL ? item_compare_not_keeping_zero : item_compare2_not_keeping_zero)); @@ -15328,7 +15516,8 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) // f_uniq(): ptrs will be a stack of items to remove. info.item_compare_func_err = false; - if (info.item_compare_func != NULL) { + if (info.item_compare_func != NULL + || info.item_compare_partial != NULL) { item_compare_func_ptr = item_compare2_keeping_zero; } else { item_compare_func_ptr = item_compare_keeping_zero; @@ -16865,6 +17054,7 @@ static void f_type(typval_T *argvars, typval_T *rettv, FunPtr fptr) } break; } + case VAR_PARTIAL: n = 8; break; case VAR_UNKNOWN: { EMSG2(_(e_intern2), "f_type(UNKNOWN)"); break; @@ -18052,11 +18242,13 @@ handle_subscript ( char_u *s; int len; typval_T functv; + partial_T *pt = NULL; while (ret == OK && (**arg == '[' || (**arg == '.' && rettv->v_type == VAR_DICT) - || (**arg == '(' && (!evaluate || rettv->v_type == VAR_FUNC))) + || (**arg == '(' && (!evaluate || rettv->v_type == VAR_FUNC + || rettv->v_type == VAR_PARTIAL))) && !ascii_iswhite(*(*arg - 1))) { if (**arg == '(') { /* need to copy the funcref so that we can clear rettv */ @@ -18064,13 +18256,19 @@ handle_subscript ( functv = *rettv; rettv->v_type = VAR_UNKNOWN; - /* Invoke the function. Recursive! */ - s = functv.vval.v_string; - } else + // Invoke the function. Recursive! + if (rettv->v_type == VAR_PARTIAL) { + pt = functv.vval.v_partial; + s = pt->pt_name; + } else { + s = functv.vval.v_string; + } + } else { s = (char_u *)""; + } ret = get_func_tv(s, (int)STRLEN(s), rettv, arg, - curwin->w_cursor.lnum, curwin->w_cursor.lnum, - &len, evaluate, selfdict); + curwin->w_cursor.lnum, curwin->w_cursor.lnum, + &len, evaluate, pt, selfdict); /* Clear the funcref afterwards, so that deleting it while * evaluating the arguments is possible (see test55). */ @@ -18118,6 +18316,9 @@ void free_tv(typval_T *varp) case VAR_STRING: xfree(varp->vval.v_string); break; + case VAR_PARTIAL: + partial_unref(varp->vval.v_partial); + break; case VAR_LIST: list_unref(varp->vval.v_list); break; @@ -18182,6 +18383,12 @@ void free_tv(typval_T *varp) tv->v_lock = VAR_UNLOCKED; \ } while (0) +#define TYPVAL_ENCODE_CONV_PARTIAL(partial) \ + do { \ + partial_unref(partial); \ + tv->v_lock = VAR_UNLOCKED; \ + } while (0) + #define TYPVAL_ENCODE_CONV_EMPTY_LIST() \ do { \ list_unref(tv->vval.v_list); \ @@ -18258,6 +18465,7 @@ TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(static, nothing, void *, ignored) #undef TYPVAL_ENCODE_CONV_STR_STRING #undef TYPVAL_ENCODE_CONV_EXT_STRING #undef TYPVAL_ENCODE_CONV_FUNC +#undef TYPVAL_ENCODE_CONV_PARTIAL #undef TYPVAL_ENCODE_CONV_EMPTY_LIST #undef TYPVAL_ENCODE_CONV_EMPTY_DICT #undef TYPVAL_ENCODE_CONV_LIST_START @@ -18315,6 +18523,7 @@ long get_tv_number_chk(typval_T *varp, int *denote) EMSG(_("E805: Using a Float as a Number")); break; case VAR_FUNC: + case VAR_PARTIAL: EMSG(_("E703: Using a Funcref as a Number")); break; case VAR_STRING: @@ -18362,6 +18571,7 @@ static float_T get_tv_float(typval_T *varp) return varp->vval.v_float; break; case VAR_FUNC: + case VAR_PARTIAL: EMSG(_("E891: Using a Funcref as a Float")); break; case VAR_STRING: @@ -18458,6 +18668,7 @@ static char_u *get_tv_string_buf_chk(const typval_T *varp, char_u *buf) sprintf((char *)buf, "%" PRId64, (int64_t)varp->vval.v_number); return buf; case VAR_FUNC: + case VAR_PARTIAL: EMSG(_("E729: using Funcref as a String")); break; case VAR_LIST: @@ -18793,11 +19004,11 @@ list_one_var_a ( msg_puts(name); msg_putchar(' '); msg_advance(22); - if (type == VAR_NUMBER) + if (type == VAR_NUMBER) { msg_putchar('#'); - else if (type == VAR_FUNC) + } else if (type == VAR_FUNC || type == VAR_PARTIAL) { msg_putchar('*'); - else if (type == VAR_LIST) { + } else if (type == VAR_LIST) { msg_putchar('['); if (*string == '[') ++string; @@ -18810,8 +19021,9 @@ list_one_var_a ( msg_outtrans(string); - if (type == VAR_FUNC) + if (type == VAR_FUNC || type == VAR_PARTIAL) { msg_puts((char_u *)"()"); + } if (*first) { msg_clr_eos(); *first = FALSE; @@ -18849,8 +19061,10 @@ set_var ( } v = find_var_in_ht(ht, 0, varname, TRUE); - if (tv->v_type == VAR_FUNC && var_check_func_name(name, v == NULL)) + if ((tv->v_type == VAR_FUNC || tv->v_type == VAR_PARTIAL) + && var_check_func_name(name, v == NULL)) { return; + } if (v != NULL) { // existing variable, need to clear the value @@ -19050,6 +19264,14 @@ void copy_tv(typval_T *from, typval_T *to) } } break; + case VAR_PARTIAL: + if (from->vval.v_partial == NULL) { + to->vval.v_partial = NULL; + } else { + to->vval.v_partial = from->vval.v_partial; + (to->vval.v_partial->pt_refcount)++; + } + break; case VAR_LIST: if (from->vval.v_list != NULL) { to->vval.v_list->lv_refcount++; @@ -19102,6 +19324,7 @@ int var_item_copy(const vimconv_T *const conv, case VAR_NUMBER: case VAR_FLOAT: case VAR_FUNC: + case VAR_PARTIAL: case VAR_SPECIAL: copy_tv(from, to); break; @@ -19956,6 +20179,7 @@ trans_function_name ( char_u sid_buf[20]; int len; lval_T lv; + partial_T *partial; if (fdp != NULL) memset(fdp, 0, sizeof(funcdict_T)); @@ -20030,14 +20254,17 @@ trans_function_name ( /* Check if the name is a Funcref. If so, use the value. */ if (lv.ll_exp_name != NULL) { len = (int)STRLEN(lv.ll_exp_name); - name = deref_func_name(lv.ll_exp_name, &len, flags & TFN_NO_AUTOLOAD); - if (name == lv.ll_exp_name) + name = deref_func_name(lv.ll_exp_name, &len, &partial, + flags & TFN_NO_AUTOLOAD); + if (name == lv.ll_exp_name) { name = NULL; + } } else { len = (int)(end - *pp); - name = deref_func_name(*pp, &len, flags & TFN_NO_AUTOLOAD); - if (name == *pp) + name = deref_func_name(*pp, &len, &partial, flags & TFN_NO_AUTOLOAD); + if (name == *pp) { name = NULL; + } } if (name != NULL) { name = vim_strsave(name); @@ -22301,6 +22528,7 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments) curwin->w_cursor.lnum, &dummy, true, + NULL, NULL); list_unref(arguments); diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 61a5438d8c..a0671cac1f 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -103,7 +103,7 @@ return { foldtext={}, foldtextresult={args=1}, foreground={}, - ['function']={args=1}, + ['function']={args={1, 3}}, garbagecollect={args={0, 1}}, get={args={2, 3}}, getbufline={args={2, 3}}, diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index 869eae74a3..57507f4430 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -315,6 +315,13 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, ga_append(gap, ')'); \ } while (0) +#define TYPVAL_ENCODE_CONV_PARTIAL(partial) \ + do { \ + ga_concat(gap, "partial("); \ + TYPVAL_ENCODE_CONV_STRING(partial, STRLEN(partial)); \ + ga_append(gap, ')'); \ + } while (0) + #define TYPVAL_ENCODE_CONV_EMPTY_LIST() \ ga_concat(gap, "[]") @@ -661,6 +668,12 @@ static inline int convert_to_json_string(garray_T *const gap, "attempt to dump function reference"), \ mpstack, objname) +#undef TYPVAL_ENCODE_CONV_PARTIAL +#define TYPVAL_ENCODE_CONV_PARTIAL(partial) \ + return conv_error(_("E474: Error while dumping %s, %s: " \ + "attempt to dump partial"), \ + mpstack, objname) + /// Check whether given key can be used in json_encode() /// /// @param[in] tv Key to check. @@ -718,6 +731,7 @@ TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(static, json, garray_T *const, gap) #undef TYPVAL_ENCODE_CONV_NUMBER #undef TYPVAL_ENCODE_CONV_FLOAT #undef TYPVAL_ENCODE_CONV_FUNC +#undef TYPVAL_ENCODE_CONV_PARTIAL #undef TYPVAL_ENCODE_CONV_EMPTY_LIST #undef TYPVAL_ENCODE_CONV_LIST_START #undef TYPVAL_ENCODE_CONV_EMPTY_DICT @@ -846,6 +860,11 @@ char *encode_tv2json(typval_T *tv, size_t *len) "attempt to dump function reference"), \ mpstack, objname) +#define TYPVAL_ENCODE_CONV_PARTIAL(partial) \ + return conv_error(_("E951: Error while dumping %s, %s: " \ + "attempt to dump partial"), \ + mpstack, objname) + #define TYPVAL_ENCODE_CONV_EMPTY_LIST() \ msgpack_pack_array(packer, 0) @@ -902,6 +921,7 @@ TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(, msgpack, msgpack_packer *const, packer) #undef TYPVAL_ENCODE_CONV_NUMBER #undef TYPVAL_ENCODE_CONV_FLOAT #undef TYPVAL_ENCODE_CONV_FUNC +#undef TYPVAL_ENCODE_CONV_PARTIAL #undef TYPVAL_ENCODE_CONV_EMPTY_LIST #undef TYPVAL_ENCODE_CONV_LIST_START #undef TYPVAL_ENCODE_CONV_EMPTY_DICT diff --git a/src/nvim/eval/typval_encode.h b/src/nvim/eval/typval_encode.h index 868877bd47..ce3bb0ee08 100644 --- a/src/nvim/eval/typval_encode.h +++ b/src/nvim/eval/typval_encode.h @@ -69,6 +69,11 @@ /// /// @param fun Function name. +/// @def TYPVAL_ENCODE_CONV_PARTIAL +/// @brief Macros used to convert a partial +/// +/// @param partial Partial name. + /// @def TYPVAL_ENCODE_CONV_EMPTY_LIST /// @brief Macros used to convert an empty list /// @@ -248,6 +253,10 @@ static int name##_convert_one_value(firstargtype firstargname, \ TYPVAL_ENCODE_CONV_FUNC(tv->vval.v_string); \ break; \ } \ + case VAR_PARTIAL: { \ + TYPVAL_ENCODE_CONV_PARTIAL(tv->vval.v_partial); \ + break; \ + } \ case VAR_LIST: { \ if (tv->vval.v_list == NULL || tv->vval.v_list->lv_len == 0) { \ TYPVAL_ENCODE_CONV_EMPTY_LIST(); \ diff --git a/src/nvim/eval_defs.h b/src/nvim/eval_defs.h index 91d544074f..a33ac10e61 100644 --- a/src/nvim/eval_defs.h +++ b/src/nvim/eval_defs.h @@ -15,6 +15,7 @@ typedef double float_T; typedef struct listvar_S list_T; typedef struct dictvar_S dict_T; +typedef struct partial_S partial_T; /// Special variable values typedef enum { @@ -35,7 +36,8 @@ typedef enum { VAR_UNKNOWN = 0, ///< Unknown (unspecified) value. VAR_NUMBER, ///< Number, .v_number is used. VAR_STRING, ///< String, .v_string is used. - VAR_FUNC, ///< Function referene, .v_string is used for function name. + VAR_FUNC, ///< Function reference, .v_string is used as function name. + VAR_PARTIAL, ///< Partial, .v_partial is used. VAR_LIST, ///< List, .v_list is used. VAR_DICT, ///< Dictionary, .v_dict is used. VAR_FLOAT, ///< Floating-point value, .v_float is used. @@ -54,6 +56,7 @@ typedef struct { char_u *v_string; ///< String, for VAR_STRING and VAR_FUNC, can be NULL. list_T *v_list; ///< List for VAR_LIST, can be NULL. dict_T *v_dict; ///< Dictionary for VAR_DICT, can be NULL. + partial_T *v_partial; ///< Closure: function with args. } vval; ///< Actual value. } typval_T; @@ -144,6 +147,14 @@ struct dictvar_S { QUEUE watchers; ///< Dictionary key watchers set by user code. }; +struct partial_S { + int pt_refcount; ///< Reference count. + char_u *pt_name; ///< Function name. + int pt_argc; ///< Number of arguments. + typval_T *pt_argv; ///< Arguments in allocated array. + dict_T *pt_dict; ///< Dict for "self". +}; + // structure used for explicit stack while garbage collecting hash tables typedef struct ht_stack_S { hashtab_T *ht; diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 312777d3b9..88ebdef9b3 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -2466,7 +2466,7 @@ do_mouse ( (int) strlen(tab_page_click_defs[mouse_col].func), &rettv, ARRAY_SIZE(argv), argv, curwin->w_cursor.lnum, curwin->w_cursor.lnum, - &doesrange, true, NULL); + &doesrange, true, NULL, NULL); clear_tv(&rettv); break; } diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index c79d9380d4..5da122c45d 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -14,6 +14,7 @@ source test_matchadd_conceal_utf8.vim source test_menu.vim source test_messages.vim source test_options.vim +source test_partial.vim source test_popup.vim source test_regexp_utf8.vim source test_statusline.vim diff --git a/src/nvim/testdir/test_partial.vim b/src/nvim/testdir/test_partial.vim new file mode 100644 index 0000000000..061f839668 --- /dev/null +++ b/src/nvim/testdir/test_partial.vim @@ -0,0 +1,43 @@ +" Test binding arguments to a Funcref. + +func MyFunc(arg1, arg2, arg3) + return a:arg1 . '/' . a:arg2 . '/' . a:arg3 +endfunc + +func MySort(up, one, two) + if a:one == a:two + return 0 + endif + if a:up + return a:one > a:two + endif + return a:one < a:two +endfunc + +func Test_partial_args() + let Cb = function('MyFunc', ["foo", "bar"]) + call assert_equal("foo/bar/xxx", Cb("xxx")) + call assert_equal("foo/bar/yyy", call(Cb, ["yyy"])) + + let Sort = function('MySort', [1]) + call assert_equal([1, 2, 3], sort([3, 1, 2], Sort)) + let Sort = function('MySort', [0]) + call assert_equal([3, 2, 1], sort([3, 1, 2], Sort)) +endfunc + +func MyDictFunc(arg1, arg2) dict + return self.name . '/' . a:arg1 . '/' . a:arg2 +endfunc + +func Test_partial_dict() + let dict = {'name': 'hello'} + let Cb = function('MyDictFunc', ["foo", "bar"], dict) + call assert_equal("hello/foo/bar", Cb()) + call assert_fails('Cb("xxx")', 'E492:') + let Cb = function('MyDictFunc', ["foo"], dict) + call assert_equal("hello/foo/xxx", Cb("xxx")) + call assert_fails('Cb()', 'E492:') + let Cb = function('MyDictFunc', dict) + call assert_equal("hello/xxx/yyy", Cb("xxx", "yyy")) + call assert_fails('Cb()', 'E492:') +endfunc diff --git a/src/nvim/version.c b/src/nvim/version.c index 348bf3c014..aac3ba44a2 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -883,7 +883,7 @@ static int included_patches[] = { // 1562 NA // 1561 NA // 1560 NA - // 1559, + 1559, 1558, 1557, // 1556 NA |