diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index e02d80252f..87240831a2 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -669,12 +669,14 @@ Expression syntax summary, from least to most significant: expr8[expr1 : expr1] substring of a String or sublist of a |List| expr8.name entry in a |Dictionary| expr8(expr1, ...) function call with |Funcref| variable + expr8->name(expr1, ...) |method| call |expr9| number number constant "string" string constant, backslash is special 'string' string constant, ' is doubled [expr1, ...] |List| {expr1: expr1, ...} |Dictionary| + #{key: expr1, ...} |Dictionary| &option option value (expr1) nested expression variable internal variable @@ -939,9 +941,11 @@ expr8 *expr8* ----- This expression is either |expr9| or a sequence of the alternatives below, in any order. E.g., these are all possible: - expr9[expr1].name - expr9.name[expr1] - expr9(expr1, ...)[expr1].name + expr8[expr1].name + expr8.name[expr1] + expr8(expr1, ...)[expr1].name + expr8->(expr1, ...)[expr1] +Evaluation is always from left to right. expr8[expr1] item of String or |List| *expr-[]* *E111* @@ -1043,6 +1047,36 @@ expr8(expr1, ...) |Funcref| function call When expr8 is a |Funcref| type variable, invoke the function it refers to. +expr8->name([args]) method call *method* *->* +expr8->{lambda}([args]) + +For methods that are also available as global functions this is the same as: > + name(expr8 [, args]) +There can also be methods specifically for the type of "expr8". + +This allows for chaining, passing the value that one method returns to the +next method: > + mylist->filter(filterexpr)->map(mapexpr)->sort()->join() +< +Example of using a lambda: > + GetPercentage->{x -> x * 100}()->printf('%d%%') +< +When using -> the |expr7| operators will be applied first, thus: > + -1.234->string() +Is equivalent to: > + (-1.234)->string() +And NOT: > + -(1.234->string()) +< + *E274* +"->name(" must not contain white space. There can be white space before the +"->" and after the "(", thus you can split the lines like this: > + mylist + \ ->filter(filterexpr) + \ ->map(mapexpr) + \ ->sort() + \ ->join() +< *expr9* number @@ -2583,6 +2617,8 @@ abs({expr}) *abs()* echo abs(-4) < 4 + Can also be used as a |method|: > + Compute()->abs() acos({expr}) *acos()* Return the arc cosine of {expr} measured in radians, as a @@ -2595,6 +2631,8 @@ acos({expr}) *acos()* :echo acos(-0.5) < 2.094395 + Can also be used as a |method|: > + Compute()->acos() add({list}, {expr}) *add()* Append the item {expr} to |List| {list}. Returns the @@ -2605,12 +2643,16 @@ add({list}, {expr}) *add()* item. Use |extend()| to concatenate |Lists|. Use |insert()| to add an item at another position. + Can also be used as a |method|: > + mylist->add(val1)->add(val2) and({expr}, {expr}) *and()* Bitwise AND on the two arguments. The arguments are converted to a number. A List, Dict or Float argument causes an error. Example: > :let flag = and(bits, 0x80) +< Can also be used as a |method|: > + :let flag = bits->and(0x80) api_info() *api_info()* Returns Dictionary of |api-metadata|. @@ -2629,6 +2671,9 @@ append({lnum}, {text}) *append()* :let failed = append(line('$'), "# THE END") :let failed = append(0, ["Chapter 1", "the beginning"]) +< Can also be used as a |method| after a List: > + mylist->append(lnum) + appendbufline({expr}, {lnum}, {text}) *appendbufline()* Like |append()| but append the text in buffer {expr}. @@ -2647,8 +2692,10 @@ appendbufline({expr}, {lnum}, {text}) *appendbufline()* error message is given. Example: > :let failed = appendbufline(13, 0, "# THE START") < - *argc()* -argc([{winid}]) + Can also be used as a |method| after a List: > + mylist->appendbufline(buf, lnum) + +argc([{winid}]) *argc()* The result is the number of files in the argument list. See |arglist|. If {winid} is not supplied, the argument list of the current @@ -2702,6 +2749,9 @@ asin({expr}) *asin()* :echo asin(-0.5) < -0.523599 + Can also be used as a |method|: > + Compute()->asin() + assert_ functions are documented here: |assert-functions-details| @@ -2716,6 +2766,8 @@ atan({expr}) *atan()* :echo atan(-4.01) < -1.326405 + Can also be used as a |method|: > + Compute()->atan() atan2({expr1}, {expr2}) *atan2()* Return the arc tangent of {expr1} / {expr2}, measured in @@ -2727,6 +2779,8 @@ atan2({expr1}, {expr2}) *atan2()* :echo atan2(1, -1) < 2.356194 + Can also be used as a |method|: > + Compute()->atan2(1) *browse()* browse({save}, {title}, {initdir}, {default}) @@ -2737,8 +2791,8 @@ browse({save}, {title}, {initdir}, {default}) {title} title for the requester {initdir} directory to start browsing in {default} default file name - When the "Cancel" button is hit, something went wrong, or - browsing is not possible, an empty string is returned. + An empty string is returned when the "Cancel" button is hit, + something went wrong, or browsing is not possible. *browsedir()* browsedir({title}, {initdir}) @@ -2760,6 +2814,8 @@ bufadd({name}) *bufadd()* created buffer. When {name} is an empty string then a new buffer is always created. The buffer will not have' 'buflisted' set. +< Can also be used as a |method|: > + let bufnr = 'somename'->bufadd() bufexists({expr}) *bufexists()* The result is a Number, which is |TRUE| if a buffer called @@ -2783,11 +2839,17 @@ bufexists({expr}) *bufexists()* Use "bufexists(0)" to test for the existence of an alternate file name. + Can also be used as a |method|: > + let exists = 'somename'->bufexists() + buflisted({expr}) *buflisted()* The result is a Number, which is |TRUE| if a buffer called {expr} exists and is listed (has the 'buflisted' option set). The {expr} argument is used like with |bufexists()|. + Can also be used as a |method|: > + let listed = 'somename'->buflisted() + bufload({expr}) *bufload()* Ensure the buffer {expr} is loaded. When the buffer name refers to an existing file then the file is read. Otherwise @@ -2797,15 +2859,21 @@ bufload({expr}) *bufload()* there will be no dialog, the buffer will be loaded anyway. The {expr} argument is used like with |bufexists()|. + Can also be used as a |method|: > + eval 'somename'->bufload() + bufloaded({expr}) *bufloaded()* The result is a Number, which is |TRUE| if a buffer called {expr} exists and is loaded (shown in a window or hidden). The {expr} argument is used like with |bufexists()|. + Can also be used as a |method|: > + let loaded = 'somename'->bufloaded() + bufname([{expr}]) *bufname()* The result is the name of a buffer, as it is displayed by the ":ls" command. -+ If {expr} is omitted the current buffer is used. + If {expr} is omitted the current buffer is used. If {expr} is a Number, that buffer number's name is given. Number zero is the alternate buffer for the current window. If {expr} is a String, it is used as a |file-pattern| to match @@ -2824,6 +2892,9 @@ bufname([{expr}]) *bufname()* If the {expr} is a String, but you want to use it as a buffer number, force it to be a Number by adding zero to it: > :echo bufname("3" + 0) +< Can also be used as a |method|: > + echo bufnr->bufname() + < If the buffer doesn't exist, or doesn't have a name, an empty string is returned. > bufname("#") alternate buffer name @@ -2846,6 +2917,9 @@ bufnr([{expr} [, {create}]]) number necessarily exist, because ":bwipeout" may have removed them. Use bufexists() to test for the existence of a buffer. + Can also be used as a |method|: > + echo bufref->bufnr() + bufwinid({expr}) *bufwinid()* The result is a Number, which is the |window-ID| of the first window associated with buffer {expr}. For the use of {expr}, @@ -2856,18 +2930,22 @@ bufwinid({expr}) *bufwinid()* < Only deals with the current tab page. + Can also be used as a |method|: > + FindBuffer()->bufwinid() + bufwinnr({expr}) *bufwinnr()* - The result is a Number, which is the number of the first - window associated with buffer {expr}. For the use of {expr}, - see |bufname()| above. If buffer {expr} doesn't exist or - there is no such window, -1 is returned. Example: > + Like |bufwinid()| but return the window number instead of the + |window-ID|. + If buffer {expr} doesn't exist or there is no such window, -1 + is returned. Example: > echo "A window containing buffer 1 is " . (bufwinnr(1)) < The number can be used with |CTRL-W_w| and ":wincmd w" |:wincmd|. - Only deals with the current tab page. + Can also be used as a |method|: > + FindBuffer()->bufwinnr() byte2line({byte}) *byte2line()* Return the line number that contains the character at byte @@ -2877,6 +2955,9 @@ byte2line({byte}) *byte2line()* one. Also see |line2byte()|, |go| and |:goto|. + Can also be used as a |method|: > + GetOffset()->byte2line() + byteidx({expr}, {nr}) *byteidx()* Return byte index of the {nr}'th character in the string {expr}. Use zero for the first character, it then returns @@ -2899,6 +2980,9 @@ byteidx({expr}, {nr}) *byteidx()* If there are exactly {nr} characters the length of the string in bytes is returned. + Can also be used as a |method|: > + GetName()->byteidx(idx) + byteidxcomp({expr}, {nr}) *byteidxcomp()* Like byteidx(), except that a composing character is counted as a separate character. Example: > @@ -2912,6 +2996,9 @@ byteidxcomp({expr}, {nr}) *byteidxcomp()* Only works differently from byteidx() when 'encoding' is set to a Unicode encoding. + Can also be used as a |method|: > + GetName()->byteidxcomp(idx) + call({func}, {arglist} [, {dict}]) *call()* *E699* Call function {func} with the items in |List| {arglist} as arguments. @@ -2921,6 +3008,9 @@ call({func}, {arglist} [, {dict}]) *call()* *E699* {dict} is for functions with the "dict" attribute. It will be used to set the local variable "self". |Dictionary-function| + Can also be used as a |method|: > + GetFunc()->call([arg, arg], dict) + ceil({expr}) *ceil()* Return the smallest integral value greater than or equal to {expr} as a |Float| (round up). @@ -2933,6 +3023,9 @@ ceil({expr}) *ceil()* echo ceil(4.0) < 4.0 + Can also be used as a |method|: > + Compute()->ceil() + changenr() *changenr()* Return the number of the most recent change. This is the same number as what is displayed with |:undolist| and can be used @@ -2982,6 +3075,9 @@ char2nr({expr} [, {utf8}]) *char2nr()* A combining character is a separate character. |nr2char()| does the opposite. + Can also be used as a |method|: > + GetChar()->char2nr() + *charidx()* charidx({string}, {idx} [, {countcc}]) Return the character index of the byte at {idx} in {string}. @@ -3013,12 +3109,18 @@ cindent({lnum}) *cindent()* When {lnum} is invalid -1 is returned. See |C-indenting|. + Can also be used as a |method|: > + GetLnum()->cindent() + clearmatches([{win}]) *clearmatches()* Clears all matches previously defined for the current window by |matchadd()| and the |:match| commands. If {win} is specified, use the window with this number or window ID instead of the current window. + Can also be used as a |method|: > + GetWin()->clearmatches() +< *col()* col({expr}) The result is a Number, which is the byte index of the column position given with {expr}. The accepted positions are: @@ -3054,6 +3156,9 @@ col({expr}) The result is a Number, which is the byte index of the column \:set ve=all \:echo col(".") . "\n" \let &ve = save_ve + +< Can also be used as a |method|: > + GetPos()->col() < complete({startcol}, {matches}) *complete()* *E785* @@ -3085,6 +3190,10 @@ complete({startcol}, {matches}) *complete()* *E785* < This isn't very useful, but it shows how it works. Note that an empty string is returned to avoid a zero being inserted. + Can also be used as a |method|, the second argument is passed + in: > + GetMatches()->complete(col('.')) + complete_add({expr}) *complete_add()* Add {expr} to the list of matches. Only to be used by the function specified with the 'completefunc' option. @@ -3094,6 +3203,9 @@ complete_add({expr}) *complete_add()* See |complete-functions| for an explanation of {expr}. It is the same as one item in the list that 'omnifunc' would return. + Can also be used as a |method|: > + GetMoreMatches()->complete_add() + complete_check() *complete_check()* Check for a key typed while looking for completion matches. This is to be used when looking for matches takes some time. @@ -3154,6 +3266,9 @@ complete_info([{what}]) call complete_info(['mode']) " Get only 'mode' and 'pum_visible' call complete_info(['mode', 'pum_visible']) + +< Can also be used as a |method|: > + GetItems()->complete_info() < *confirm()* confirm({msg} [, {choices} [, {default} [, {type}]]]) @@ -3207,6 +3322,9 @@ confirm({msg} [, {choices} [, {default} [, {type}]]]) don't fit, a vertical layout is used anyway. For some systems the horizontal layout is always used. + Can also be used as a |method|in: > + BuildMessage()->confirm("&Yes\n&No") + *copy()* copy({expr}) Make a copy of {expr}. For Numbers and Strings this isn't different from using {expr} directly. @@ -3216,6 +3334,8 @@ copy({expr}) Make a copy of {expr}. For Numbers and Strings this isn't changing an item changes the contents of both |Lists|. A |Dictionary| is copied in a similar way as a |List|. Also see |deepcopy()|. + Can also be used as a |method|: > + mylist->copy() cos({expr}) *cos()* Return the cosine of {expr}, measured in radians, as a |Float|. @@ -3226,6 +3346,8 @@ cos({expr}) *cos()* :echo cos(-4.01) < -0.646043 + Can also be used as a |method|: > + Compute()->cos() cosh({expr}) *cosh()* Return the hyperbolic cosine of {expr} as a |Float| in the range @@ -3237,6 +3359,8 @@ cosh({expr}) *cosh()* :echo cosh(-0.5) < -1.127626 + Can also be used as a |method|: > + Compute()->cosh() count({comp}, {expr} [, {ic} [, {start}]]) *count()* Return the number of times an item with value {expr} appears @@ -3251,6 +3375,9 @@ count({comp}, {expr} [, {ic} [, {start}]]) *count()* occurrences of {expr} is returned. Zero is returned when {expr} is an empty string. + Can also be used as a |method|: > + mylist->count(val) + *cscope_connection()* cscope_connection([{num} , {dbpath} [, {prepend}]]) Checks for the existence of a |cscope| connection. If no @@ -3346,6 +3473,8 @@ cursor({list}) position within a or after the last character. Returns 0 when the position could be set, -1 otherwise. + Can also be used as a |method|: > + GetCursorPos()->cursor() deepcopy({expr}[, {noref}]) *deepcopy()* *E698* Make a copy of {expr}. For Numbers and Strings this isn't @@ -3367,6 +3496,9 @@ deepcopy({expr}[, {noref}]) *deepcopy()* *E698* {noref} set to 1 will fail. Also see |copy()|. + Can also be used as a |method|: > + GetObject()->deepcopy() + delete({fname} [, {flags}]) *delete()* Without {flags} or with {flags} empty: Deletes the file by the name {fname}. This also works when {fname} is a symbolic link. @@ -3384,6 +3516,9 @@ delete({fname} [, {flags}]) *delete()* operation was successful and -1/true when the deletion failed or partly failed. + Can also be used as a |method|: > + GetName()->delete() + deletebufline({expr}, {first}[, {last}]) *deletebufline()* Delete lines {first} to {last} (inclusive) from buffer {expr}. If {last} is omitted then delete line {first} only. @@ -3398,6 +3533,9 @@ deletebufline({expr}, {first}[, {last}]) *deletebufline()* when using |line()| this refers to the current buffer. Use "$" to refer to the last line in buffer {expr}. + Can also be used as a |method|: > + GetBuffer()->deletebufline(1) + dictwatcheradd({dict}, {pattern}, {callback}) *dictwatcheradd()* Adds a watcher to a dictionary. A dictionary watcher is identified by three components: @@ -3464,6 +3602,9 @@ diff_filler({lnum}) *diff_filler()* line, "'m" mark m, etc. Returns 0 if the current window is not in diff mode. + Can also be used as a |method|: > + GetLnum()->diff_filler() + diff_hlID({lnum}, {col}) *diff_hlID()* Returns the highlight ID for diff mode at line {lnum} column {col} (byte index). When the current line does not have a @@ -3475,11 +3616,16 @@ diff_hlID({lnum}, {col}) *diff_hlID()* The highlight ID can be used with |synIDattr()| to obtain syntax information about the highlighting. + Can also be used as a |method|: > + GetLnum()->diff_hlID(col) + empty({expr}) *empty()* Return the Number 1 if {expr} is empty, zero otherwise. A |List| or |Dictionary| is empty when it does not have any items. A Number is empty when its value is zero. Special variable is empty when it is |v:false| or |v:null|. + Can also be used as a |method|: > + mylist->empty() environ() *environ()* Return all of environment variables as dictionary. You can @@ -3504,6 +3650,9 @@ eval({string}) Evaluate {string} and return the result. Especially useful to them. Also works for |Funcref|s that refer to existing functions. + Can also be used as a |method|: > + argv->join()->eval() + eventhandler() *eventhandler()* Returns 1 when inside an event handler. That is that Vim got interrupted while waiting for the user to type a character, @@ -3661,12 +3810,18 @@ exp({expr}) *exp()* :echo exp(-1) < 0.367879 + Can also be used as a |method|: > + Compute()->exp() + debugbreak({pid}) *debugbreak()* Specifically used to interrupt a program being debugged. It will cause process {pid} to get a SIGTRAP. Behavior for other processes is undefined. See |terminal-debugger|. {Sends a SIGINT to a process {pid} other than MS-Windows} + Can also be used as a |method|: > + GetPid()->debugbreak() + expand({expr} [, {nosuf} [, {list}]]) *expand()* Expand wildcards and the following special keywords in {expr}. 'wildignorecase' applies. @@ -3795,6 +3950,8 @@ extend({expr1}, {expr2} [, {expr3}]) *extend()* fails. Returns {expr1}. + Can also be used as a |method|: > + mylist->extend(otherlist) feedkeys({string} [, {mode}]) *feedkeys()* Characters in {string} are queued for processing as if they @@ -3848,7 +4005,11 @@ filereadable({file}) *filereadable()* expression, which is used as a String. If you don't care about the file being readable you can use |glob()|. - + {file} is used as-is, you may want to expand wildcards first: > + echo filereadable('~/.vimrc') + 0 + echo filereadable(expand('~/.vimrc')) + 1 filewritable({file}) *filewritable()* The result is a Number, which is 1 when a file with the @@ -3904,6 +4065,8 @@ filter({expr1}, {expr2}) *filter()* Funcref errors inside a function are ignored, unless it was defined with the "abort" flag. + Can also be used as a |method|: > + mylist->filter(expr2) finddir({name} [, {path} [, {count}]]) *finddir()* Find directory {name} in {path}. Supports both downwards and @@ -3966,6 +4129,8 @@ float2nr({expr}) *float2nr()* echo float2nr(1.0e-100) < 0 + Can also be used as a |method|: > + Compute()->float2nr() floor({expr}) *floor()* Return the largest integral value less than or equal to @@ -3979,6 +4144,8 @@ floor({expr}) *floor()* echo floor(4.0) < 4.0 + Can also be used as a |method|: > + Compute()->floor() fmod({expr1}, {expr2}) *fmod()* Return the remainder of {expr1} / {expr2}, even if the @@ -3994,6 +4161,8 @@ fmod({expr1}, {expr2}) *fmod()* :echo fmod(-12.33, 1.22) < -0.13 + Can also be used as a |method|: > + Compute()->fmod(1.22) fnameescape({string}) *fnameescape()* Escape {string} for use as file name command argument. All @@ -4160,6 +4329,8 @@ get({list}, {idx} [, {default}]) *get()* Get item {idx} from |List| {list}. When this item is not available return {default}. Return zero when {default} is omitted. + Can also be used as a |method|: > + mylist->get(idx) get({dict}, {key} [, {default}]) Get item with key {key} from |Dictionary| {dict}. When this item is not available return {default}. Return zero when @@ -5171,6 +5342,9 @@ has_key({dict}, {key}) *has_key()* The result is a Number, which is TRUE if |Dictionary| {dict} has an entry with key {key}. FALSE otherwise. + Can also be used as a |method|: > + mydict->has_key(key) + haslocaldir([{winnr}[, {tabnr}]]) *haslocaldir()* The result is a Number, which is 1 when the tabpage or window has set a local path via |:tcd| or |:lcd|, otherwise 0. @@ -5514,6 +5688,9 @@ insert({list}, {item} [, {idx}]) *insert()* Note that when {item} is a |List| it is inserted as a single item. Use |extend()| to concatenate |Lists|. + Can also be used as a |method|: > + mylist->insert(item) + interrupt() *interrupt()* Interrupt script execution. It works more or less like the user typing CTRL-C, most commands won't execute and control @@ -5531,6 +5708,8 @@ invert({expr}) *invert()* Bitwise invert. The argument is converted to a number. A List, Dict or Float argument causes an error. Example: > :let bits = invert(bits) +< Can also be used as a |method|: > + :let bits = bits->invert() isdirectory({directory}) *isdirectory()* The result is a Number, which is |TRUE| when a directory @@ -5546,6 +5725,9 @@ isinf({expr}) *isinf()* :echo isinf(-1.0 / 0.0) < -1 + Can also be used as a |method|: > + Compute()->isinf() + islocked({expr}) *islocked()* *E786* The result is a Number, which is |TRUE| when {expr} is the name of a locked variable. @@ -5581,12 +5763,17 @@ items({dict}) *items()* |List| item is a list with two items: the key of a {dict} entry and the value of this entry. The |List| is in arbitrary order. + Can also be used as a |method|: > + mydict->items() isnan({expr}) *isnan()* Return |TRUE| if {expr} is a float with value NaN. > echo isnan(0.0 / 0.0) < 1 + Can also be used as a |method|: > + Compute()->isnan() + jobpid({job}) *jobpid()* Return the PID (process id) of |job-id| {job}. @@ -5714,6 +5901,9 @@ join({list} [, {sep}]) *join()* converted into a string like with |string()|. The opposite function is |split()|. + Can also be used as a |method|: > + mylist->join() + json_decode({expr}) *json_decode()* Convert {expr} from JSON object. Accepts |readfile()|-style list as the input, as well as regular string. May output any @@ -5744,8 +5934,10 @@ json_encode({expr}) *json_encode()* keys({dict}) *keys()* Return a |List| with all the keys of {dict}. The |List| is in arbitrary order. + Can also be used as a |method|: > + mydict->keys() - *len()* *E701* +< *len()* *E701* len({expr}) The result is a Number, which is the length of the argument. When {expr} is a String or a Number the length in bytes is used, as with |strlen()|. @@ -5756,7 +5948,10 @@ len({expr}) The result is a Number, which is the length of the argument. |Dictionary| is returned. Otherwise an error is given. - *libcall()* *E364* *E368* + Can also be used as a |method|: > + mylist->len() + +< *libcall()* *E364* *E368* libcall({libname}, {funcname}, {argument}) Call function {funcname} in the run-time library {libname} with single argument {argument}. @@ -5881,6 +6076,8 @@ log({expr}) *log()* :echo log(exp(5)) < 5.0 + Can also be used as a |method|: > + Compute()->log() log10({expr}) *log10()* Return the logarithm of Float {expr} to base 10 as a |Float|. @@ -5891,6 +6088,9 @@ log10({expr}) *log10()* :echo log10(0.01) < -2.0 + Can also be used as a |method|: > + Compute()->log10() + luaeval({expr}[, {expr}]) Evaluate Lua expression {expr} and return its result converted to Vim data structures. See |lua-eval| for more details. @@ -5939,6 +6139,8 @@ map({expr1}, {expr2}) *map()* Funcref errors inside a function are ignored, unless it was defined with the "abort" flag. + Can also be used as a |method|: > + mylist->map(expr2) maparg({name} [, {mode} [, {abbr} [, {dict}]]]) *maparg()* When {dict} is omitted or zero: Return the rhs of mapping @@ -6274,6 +6476,9 @@ max({expr}) Return the maximum value of all items in {expr}. items in {expr} cannot be used as a Number this results in an error. An empty |List| or |Dictionary| results in zero. + Can also be used as a |method|: > + mylist->max() + menu_get({path}, {modes}) *menu_get()* Returns a |List| of |Dictionaries| describing |menus| (defined by |:menu|, |:amenu|, …), including |hidden-menus|. @@ -6328,7 +6533,10 @@ min({expr}) Return the minimum value of all items in {expr}. items in {expr} cannot be used as a Number this results in an error. An empty |List| or |Dictionary| results in zero. - *mkdir()* *E739* + Can also be used as a |method|: > + mylist->min() + +< *mkdir()* *E739* mkdir({name} [, {path} [, {prot}]]) Create directory {name}. If {path} is "p" then intermediate directories are created as @@ -6523,7 +6731,8 @@ or({expr}, {expr}) *or()* to a number. A List, Dict or Float argument causes an error. Example: > :let bits = or(bits, 0x80) - +< Can also be used as a |method|: > + :let bits = bits->or(0x80) pathshorten({expr}) *pathshorten()* Shorten directory names in the path {expr} and return the @@ -6560,6 +6769,9 @@ pow({x}, {y}) *pow()* :echo pow(32, 0.20) < 2.0 + Can also be used as a |method|: > + Compute()->pow(3) + prevnonblank({lnum}) *prevnonblank()* Return the line number of the first line at or above {lnum} that is not blank. Example: > @@ -6576,7 +6788,11 @@ printf({fmt}, {expr1} ...) *printf()* < May result in: " 99: E42 asdfasdfasdfasdfasdfasdfasdfas" ~ - Often used items are: + When used as a |method| the base is passed as the second + argument: > + Compute()->printf("result: %d") + +< Often used items are: %s string %6S string right-aligned in 6 display cells %6s string right-aligned in 6 bytes @@ -7086,6 +7302,10 @@ remove({list}, {idx} [, {end}]) *remove()* Example: > :echo "last item: " . remove(mylist, -1) :call remove(mylist, 0, 9) + +< Can also be used as a |method|: > + mylist->remove(idx) + remove({dict}, {key}) Remove the entry from {dict} with key {key} and return it. Example: > @@ -7112,6 +7332,8 @@ repeat({expr}, {count}) *repeat()* :let longlist = repeat(['a', 'b'], 3) < Results in ['a', 'b', 'a', 'b', 'a', 'b']. + Can also be used as a |method|: > + mylist->repeat(count) resolve({filename}) *resolve()* *E655* On MS-Windows, when {filename} is a shortcut (a .lnk file), @@ -7131,6 +7353,8 @@ reverse({list}) Reverse the order of items in {list} in-place. Returns {list}. If you want a list to remain unmodified make a copy first: > :let revlist = reverse(copy(mylist)) +< Can also be used as a |method|: > + mylist->reverse() round({expr}) *round()* Round off {expr} to the nearest integral value and return it @@ -7145,6 +7369,9 @@ round({expr}) *round()* echo round(-4.5) < -5.0 + Can also be used as a |method|: > + Compute()->round() + rpcnotify({channel}, {event}[, {args}...]) *rpcnotify()* Sends {event} to {channel} via |RPC| and returns immediately. If {channel} is 0, the event is broadcast to all channels. @@ -8121,6 +8348,8 @@ sin({expr}) *sin()* :echo sin(-4.01) < 0.763301 + Can also be used as a |method|: > + Compute()->sin() sinh({expr}) *sinh()* Return the hyperbolic sine of {expr} as a |Float| in the range @@ -8132,6 +8361,9 @@ sinh({expr}) *sinh()* :echo sinh(-0.9) < -1.026517 + Can also be used as a |method|: > + Compute()->sinh() + sockconnect({mode}, {address}, {opts}) *sockconnect()* Connect a socket to an address. If {mode} is "pipe" then {address} should be the path of a named pipe. If {mode} is @@ -8210,7 +8442,10 @@ sort({list} [, {func} [, {dict}]]) *sort()* *E702* on numbers, text strings will sort next to each other, in the same order as they were originally. - Also see |uniq()|. + Can also be used as a |method|: > + mylist->sort() + +< Also see |uniq()|. Example: > func MyCompare(i1, i2) @@ -8303,6 +8538,8 @@ split({expr} [, {pattern} [, {keepempty}]]) *split()* :let items = split(line, ':', 1) < The opposite function is |join()|. + Can also be used as a |method|: > + GetString()->split() sqrt({expr}) *sqrt()* Return the non-negative square root of Float {expr} as a @@ -8316,6 +8553,8 @@ sqrt({expr}) *sqrt()* < nan "nan" may be different, it depends on system libraries. + Can also be used as a |method|: > + Compute()->sqrt() stdioopen({opts}) *stdioopen()* With |--headless| this opens stdin and stdout as a |channel|. @@ -8367,6 +8606,9 @@ str2float({expr}) *str2float()* 12.0. You can strip out thousands separators with |substitute()|: > let f = str2float(substitute(text, ',', '', 'g')) +< + Can also be used as a |method|: > + let f = text->substitute(',', '', 'g')->str2float() str2list({expr} [, {utf8}]) *str2list()* Return a list containing the number values which represent @@ -8381,12 +8623,18 @@ str2list({expr} [, {utf8}]) *str2list()* properly: > str2list("á") returns [97, 769] +< Can also be used as a |method|: > + GetString()->str2list() + str2nr({expr} [, {base}]) *str2nr()* Convert string {expr} to a number. {base} is the conversion base, it can be 2, 8, 10 or 16. + When {base} is omitted base 10 is used. This also means that a leading zero doesn't cause octal conversion to be used, as - with the default String to Number conversion. + with the default String to Number conversion. Example: > + let nr = str2nr('123') +< When {base} is 16 a leading "0x" or "0X" is ignored. With a different base the result will be zero. Similarly, when {base} is 8 a leading "0" is ignored, and when {base} is 2 a leading @@ -8505,6 +8753,9 @@ string({expr}) Return {expr} converted to a String. If {expr} is a Number, method, use |msgpackdump()| or |json_encode()| if you need to share data with other application. + Can also be used as a |method|: > + mylist->string() + *strlen()* strlen({expr}) The result is a Number, which is the length of the String {expr} in bytes. @@ -8514,6 +8765,9 @@ strlen({expr}) The result is a Number, which is the length of the String |strchars()|. Also see |len()|, |strdisplaywidth()| and |strwidth()|. + Can also be used as a |method|: > + GetString()->strlen() + strpart({src}, {start} [, {len} [, {chars}]]) *strpart()* The result is a String, which is part of {src}, starting from byte {start}, with the byte length {len}. @@ -8588,6 +8842,9 @@ strtrans({expr}) *strtrans()* < This displays a newline in register a as "^@" instead of starting a new line. + Can also be used as a |method|: > + GetString()->strtrans() + strwidth({expr}) *strwidth()* The result is a Number, which is the number of display cells String {expr} occupies. A Tab character is counted as one @@ -8596,6 +8853,9 @@ strwidth({expr}) *strwidth()* Ambiguous, this function's return value depends on 'ambiwidth'. Also see |strlen()|, |strdisplaywidth()| and |strchars()|. + Can also be used as a |method|: > + GetString()->strwidth() + submatch({nr} [, {list}]) *submatch()* *E935* Only for an expression in a |:substitute| command or substitute() function. @@ -8663,6 +8923,9 @@ substitute({expr}, {pat}, {sub}, {flags}) *substitute()* |submatch()| returns. Example: > :echo substitute(s, '%\(\x\x\)', {m -> '0x' . m[1]}, 'g') +< Can also be used as a |method|: > + GetString()->substitute(pat, sub, flags) + swapinfo({fname}) *swapinfo()* The result is a dictionary, which holds information about the swapfile {fname}. The available fields are: @@ -8747,12 +9010,18 @@ synIDattr({synID}, {what} [, {mode}]) *synIDattr()* cursor): > :echo synIDattr(synIDtrans(synID(line("."), col("."), 1)), "fg") < + Can also be used as a |method|: > + :echo synID(line("."), col("."), 1)->synIDtrans()->synIDattr("fg") + synIDtrans({synID}) *synIDtrans()* The result is a Number, which is the translated syntax ID of {synID}. This is the syntax group ID of what is being used to highlight the character. Highlight links given with ":highlight link" are followed. + Can also be used as a |method|: > + :echo synID(line("."), col("."), 1)->synIDtrans()->synIDattr("fg") + synconcealed({lnum}, {col}) *synconcealed()* The result is a |List| with currently three items: 1. The first item in the list is 0 if the character at the @@ -8849,6 +9118,8 @@ system({cmd} [, {input}]) *system()* *E677* Unlike ":!cmd" there is no automatic check for changed files. Use |:checktime| to force a check. + Can also be used as a |method|: > + :echo GetCmd()->system() systemlist({cmd} [, {input} [, {keepempty}]]) *systemlist()* Same as |system()|, but returns a |List| with lines (parts of @@ -8864,6 +9135,8 @@ systemlist({cmd} [, {input} [, {keepempty}]]) *systemlist()* < Returns an empty string on error. + Can also be used as a |method|: > + :echo GetCmd()->systemlist() tabpagebuflist([{arg}]) *tabpagebuflist()* The result is a |List|, where each item is the number of the @@ -8987,6 +9260,8 @@ tan({expr}) *tan()* :echo tan(-4.01) < -1.181502 + Can also be used as a |method|: > + Compute()->tan() tanh({expr}) *tanh()* Return the hyperbolic tangent of {expr} as a |Float| in the @@ -8998,6 +9273,8 @@ tanh({expr}) *tanh()* :echo tanh(-1) < -0.761594 + Can also be used as a |method|: > + Compute()->tanh() *timer_info()* timer_info([{id}]) @@ -9124,6 +9401,9 @@ trunc({expr}) *trunc()* echo trunc(4.0) < 4.0 + Can also be used as a |method|: > + Compute()->trunc() + type({expr}) *type()* The result is a Number representing the type of {expr}. Instead of using the number directly, it is better to use the @@ -9150,6 +9430,9 @@ type({expr}) *type()* < To check if the v:t_ variables exist use this: > :if exists('v:t_number') +< Can also be used as a |method|: > + mylist->type() + undofile({name}) *undofile()* Return the name of the undo file that would be used for a file with name {name} when writing. This uses the 'undodir' @@ -9212,10 +9495,15 @@ uniq({list} [, {func} [, {dict}]]) *uniq()* *E882* < The default compare function uses the string representation of each item. For the use of {func} and {dict} see |sort()|. + Can also be used as a |method|: > + mylist->uniq() + values({dict}) *values()* Return a |List| with all the values of {dict}. The |List| is in arbitrary order. + Can also be used as a |method|: > + mydict->values() virtcol({expr}) *virtcol()* The result is a Number, which is the screen column of the file @@ -9392,6 +9680,9 @@ winbufnr({nr}) The result is a Number, which is the number of the buffer When window {nr} doesn't exist, -1 is returned. Example: > :echo "The file in the current window is " . bufname(winbufnr(0)) +< + Can also be used as a |method|: > + FindWindow()->winbufnr()->bufname() < *wincol()* wincol() The result is a Number, which is the virtual column of the @@ -9606,6 +9897,8 @@ xor({expr}, {expr}) *xor()* to a number. A List, Dict or Float argument causes an error. Example: > :let bits = xor(bits, 0x80) +< Can also be used as a |method|: > + :let bits = bits->xor(0x80) < @@ -9943,7 +10236,9 @@ This function can then be called with: > The recursiveness of user functions is restricted with the |'maxfuncdepth'| option. -It is also possible to use `:eval`. It does not support a range. +It is also possible to use `:eval`. It does not support a range, but does +allow for method chaining, e.g.: > + eval GetList()->Filter()->append('$') AUTOMATICALLY LOADING FUNCTIONS ~ @@ -10686,7 +10981,7 @@ text... < *:eval* :eval {expr} Evaluate {expr} and discard the result. Example: > - :eval append(Filter(Getlist()), '$') + :eval Getlist()->Filter()->append('$') < The expression is supposed to have a side effect, since the resulting value is not used. In the example diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 5f874a59d7..3df57a3460 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -391,6 +391,10 @@ where the args are converted to Lua values. The expression > is equivalent to the Lua chunk > return somemod.func(...) +The `v:lua` prefix may be used to call Lua functions as |method|s. For +example: > + arg1->v:lua.somemod.func(arg2) + You can use `v:lua` in "func" options like 'tagfunc', 'omnifunc', etc. For example consider the following Lua omnifunc handler: > diff --git a/runtime/doc/testing.txt b/runtime/doc/testing.txt index ef8d6b5ea9..3b59dfa908 100644 --- a/runtime/doc/testing.txt +++ b/runtime/doc/testing.txt @@ -53,6 +53,9 @@ assert_beeps({cmd}) *assert_beeps()* Also see |assert_fails()|, |assert_nobeep()| and |assert-return|. + Can also be used as a |method|: > + GetCmd()->assert_beeps() +< *assert_equal()* assert_equal({expected}, {actual} [, {msg}]) When {expected} and {actual} are not equal an error message is @@ -69,7 +72,10 @@ assert_equal({expected}, {actual} [, {msg}]) < Will result in a string to be added to |v:errors|: test.vim line 12: Expected 'foo' but got 'bar' ~ - *assert_equalfile()* + Can also be used as a |method|: > + mylist->assert_equal([1, 2, 3]) + +< *assert_equalfile()* assert_equalfile({fname-one}, {fname-two}) When the files {fname-one} and {fname-two} do not contain exactly the same text an error message is added to |v:errors|. @@ -77,6 +83,9 @@ assert_equalfile({fname-one}, {fname-two}) When {fname-one} or {fname-two} does not exist the error will mention that. + Can also be used as a |method|: > + GetLog()->assert_equalfile('expected.log') + assert_exception({error} [, {msg}]) *assert_exception()* When v:exception does not contain the string {error} an error message is added to |v:errors|. Also see |assert-return|. @@ -97,6 +106,9 @@ assert_fails({cmd} [, {error} [, {msg}]]) *assert_fails()* Note that beeping is not considered an error, and some failing commands only beep. Use |assert_beeps()| for those. + Can also be used as a |method|: > + GetCmd()->assert_fails('E99:') + assert_false({actual} [, {msg}]) *assert_false()* When {actual} is not false an error message is added to |v:errors|, like with |assert_equal()|. @@ -106,6 +118,9 @@ assert_false({actual} [, {msg}]) *assert_false()* When {msg} is omitted an error in the form "Expected False but got {actual}" is produced. + Can also be used as a |method|: > + GetResult()->assert_false() + assert_inrange({lower}, {upper}, {actual} [, {msg}]) *assert_inrange()* This asserts number and |Float| values. When {actual} is lower than {lower} or higher than {upper} an error message is added @@ -134,6 +149,9 @@ assert_match({pattern}, {actual} [, {msg}]) < Will result in a string to be added to |v:errors|: test.vim line 12: Pattern '^f.*o$' does not match 'foobar' ~ + Can also be used as a |method|: > + getFile()->assert_match('foo.*') +< assert_nobeep({cmd}) *assert_nobeep()* Run {cmd} and add an error message to |v:errors| if it produces a beep or visual bell. @@ -145,16 +163,27 @@ assert_notequal({expected}, {actual} [, {msg}]) |v:errors| when {expected} and {actual} are equal. Also see |assert-return|. - *assert_notmatch()* + Can also be used as a |method|: > + mylist->assert_notequal([1, 2, 3]) + +< *assert_notmatch()* assert_notmatch({pattern}, {actual} [, {msg}]) The opposite of `assert_match()`: add an error message to |v:errors| when {pattern} matches {actual}. Also see |assert-return|. + Can also be used as a |method|: > + getFile()->assert_notmatch('bar.*') + + assert_report({msg}) *assert_report()* Report a test failure directly, using {msg}. Always returns one. + Can also be used as a |method|: > + GetMessage()->assert_report() + + assert_true({actual} [, {msg}]) *assert_true()* When {actual} is not true an error message is added to |v:errors|, like with |assert_equal()|. @@ -164,5 +193,8 @@ assert_true({actual} [, {msg}]) *assert_true()* When {msg} is omitted an error in the form "Expected True but got {actual}" is produced. + Can also be used as a |method|: > + GetResult()->assert_true() +< vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 90c43a1b04..2536249de5 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -608,12 +608,15 @@ static Object _call_function(String fn, Array args, dict_T *self, Error *err) recursive++; try_start(); typval_T rettv; - int dummy; + funcexe_T funcexe = FUNCEXE_INIT; + funcexe.firstline = curwin->w_cursor.lnum; + funcexe.lastline = curwin->w_cursor.lnum; + funcexe.evaluate = true; + funcexe.selfdict = self; // call_func() retval is deceptive, ignore it. Instead we set `msg_list` // (see above) to capture abort-causing non-exception errors. (void)call_func((char_u *)fn.data, (int)fn.size, &rettv, (int)args.size, - vim_args, NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, - &dummy, true, NULL, self); + vim_args, &funcexe); if (!try_end(err)) { rv = vim_to_object(&rettv); } diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 5e18a77b6d..5603fbb082 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -65,6 +65,8 @@ static char *e_missbrac = N_("E111: Missing ']'"); static char *e_dictrange = N_("E719: Cannot use [:] with a Dictionary"); static char *e_illvar = N_("E461: Illegal variable name: %s"); static char *e_cannot_mod = N_("E995: Cannot modify existing variable"); +static char *e_nowhitespace + = N_("E274: No white space allowed before parenthesis"); static char *e_invalwindow = N_("E957: Invalid window number"); static char *e_lock_unlock = N_("E940: Cannot lock or unlock variable %s"); @@ -736,15 +738,15 @@ int eval_expr_typval(const typval_T *expr, typval_T *argv, int argc, typval_T *rettv) FUNC_ATTR_NONNULL_ARG(1, 2, 4) { - int dummy; + funcexe_T funcexe = FUNCEXE_INIT; if (expr->v_type == VAR_FUNC) { const char_u *const s = expr->vval.v_string; if (s == NULL || *s == NUL) { return FAIL; } - if (call_func(s, -1, rettv, argc, argv, NULL, - 0L, 0L, &dummy, true, NULL, NULL) == FAIL) { + funcexe.evaluate = true; + if (call_func(s, -1, rettv, argc, argv, &funcexe) == FAIL) { return FAIL; } } else if (expr->v_type == VAR_PARTIAL) { @@ -753,8 +755,9 @@ int eval_expr_typval(const typval_T *expr, typval_T *argv, if (s == NULL || *s == NUL) { return FAIL; } - if (call_func(s, -1, rettv, argc, argv, NULL, - 0L, 0L, &dummy, true, partial, NULL) == FAIL) { + funcexe.evaluate = true; + funcexe.partial = partial; + if (call_func(s, -1, rettv, argc, argv, &funcexe) == FAIL) { return FAIL; } } else { @@ -1050,7 +1053,6 @@ int call_vim_function( ) FUNC_ATTR_NONNULL_ALL { - int doesrange; int ret; int len = (int)STRLEN(func); partial_T *pt = NULL; @@ -1066,9 +1068,12 @@ int call_vim_function( } rettv->v_type = VAR_UNKNOWN; // tv_clear() uses this. - ret = call_func(func, len, rettv, argc, argv, NULL, - curwin->w_cursor.lnum, curwin->w_cursor.lnum, - &doesrange, true, pt, NULL); + funcexe_T funcexe = FUNCEXE_INIT; + funcexe.firstline = curwin->w_cursor.lnum; + funcexe.lastline = curwin->w_cursor.lnum; + funcexe.evaluate = true; + funcexe.partial = pt; + ret = call_func(func, len, rettv, argc, argv, &funcexe); fail: if (ret == FAIL) { @@ -1724,7 +1729,9 @@ static const char *list_arg_vars(exarg_T *eap, const char *arg, int *first) } else { // handle d.key, l[idx], f(expr) const char *const arg_subsc = arg; - if (handle_subscript(&arg, &tv, true, true) == FAIL) { + if (handle_subscript(&arg, &tv, true, true, (const char_u *)name, + (const char_u **)&name) + == FAIL) { error = true; } else { if (arg == arg_subsc && len == 2 && name[1] == ':') { @@ -3142,6 +3149,65 @@ static int pattern_match(char_u *pat, char_u *text, bool ic) return matches; } +/// Handle a name followed by "(". Both for just "name(arg)" and for +/// "expr->name(arg)". +// +/// @param arg Points to "(", will be advanced +/// @param basetv "expr" for "expr->name(arg)" +// +/// @return OK or FAIL. +static int eval_func(char_u **const arg, char_u *const name, const int name_len, + typval_T *const rettv, const bool evaluate, + typval_T *const basetv) + FUNC_ATTR_NONNULL_ARG(1, 2, 4) +{ + char_u *s = name; + int len = name_len; + + if (!evaluate) { + check_vars((const char *)s, len); + } + + // If "s" is the name of a variable of type VAR_FUNC + // use its contents. + partial_T *partial; + s = deref_func_name((const char *)s, &len, &partial, !evaluate); + + // Need to make a copy, in case evaluating the arguments makes + // the name invalid. + s = xmemdupz(s, len); + + // Invoke the function. + funcexe_T funcexe = FUNCEXE_INIT; + funcexe.firstline = curwin->w_cursor.lnum; + funcexe.lastline = curwin->w_cursor.lnum; + funcexe.evaluate = evaluate; + funcexe.partial = partial; + funcexe.basetv = basetv; + int ret = get_func_tv(s, len, rettv, arg, &funcexe); + + xfree(s); + + // If evaluate is false rettv->v_type was not set in + // get_func_tv, but it's needed in handle_subscript() to parse + // what follows. So set it here. + if (rettv->v_type == VAR_UNKNOWN && !evaluate && **arg == '(') { + rettv->vval.v_string = (char_u *)tv_empty_string; + rettv->v_type = VAR_FUNC; + } + + // Stop the expression evaluation when immediately + // aborting on error, or when an interrupt occurred or + // an exception was thrown but not caught. + if (evaluate && aborting()) { + if (ret == OK) { + tv_clear(rettv); + } + ret = FAIL; + } + return ret; +} + // TODO(ZyX-I): move to eval/expressions /* @@ -3161,6 +3227,8 @@ int eval0(char_u *arg, typval_T *rettv, char_u **nextcmd, int evaluate) { int ret; char_u *p; + const int did_emsg_before = did_emsg; + const int called_emsg_before = called_emsg; p = skipwhite(arg); ret = eval1(&p, rettv, evaluate); @@ -3170,8 +3238,10 @@ int eval0(char_u *arg, typval_T *rettv, char_u **nextcmd, int evaluate) } // Report the invalid expression unless the expression evaluation has // been cancelled due to an aborting error, an interrupt, or an - // exception. - if (!aborting()) { + // exception, or we already gave a more specific error. + // Also check called_emsg for when using assert_fails(). + if (!aborting() && did_emsg == did_emsg_before + && called_emsg == called_emsg_before) { emsgf(_(e_invexpr2), arg); } ret = FAIL; @@ -3801,6 +3871,7 @@ static int eval6(char_u **arg, typval_T *rettv, int evaluate, int want_string) // + in front unary plus (ignored) // trailing [] subscript in String or List // trailing .name entry in Dictionary +// trailing ->name() method call // // "arg" must point to the first non-white of the expression. // "arg" is advanced to the next non-white after the recognized expression. @@ -3815,10 +3886,10 @@ static int eval7( { varnumber_T n; int len; - char_u *s; - char_u *start_leader, *end_leader; + char_u *s; + const char_u *start_leader, *end_leader; int ret = OK; - char_u *alias; + char_u *alias; // Initialise variable so that tv_clear() can't mistake this for a // string and free a string that isn't there. @@ -3968,44 +4039,7 @@ static int eval7( ret = FAIL; } else { if (**arg == '(') { // recursive! - partial_T *partial; - - if (!evaluate) { - check_vars((const char *)s, len); - } - - // If "s" is the name of a variable of type VAR_FUNC - // use its contents. - s = deref_func_name((const char *)s, &len, &partial, !evaluate); - - // Need to make a copy, in case evaluating the arguments makes - // the name invalid. - s = xmemdupz(s, len); - - // Invoke the function. - ret = get_func_tv(s, len, rettv, arg, - curwin->w_cursor.lnum, curwin->w_cursor.lnum, - &len, evaluate, partial, NULL); - - xfree(s); - - // If evaluate is false rettv->v_type was not set in - // get_func_tv, but it's needed in handle_subscript() to parse - // what follows. So set it here. - if (rettv->v_type == VAR_UNKNOWN && !evaluate && **arg == '(') { - rettv->vval.v_string = (char_u *)tv_empty_string; - rettv->v_type = VAR_FUNC; - } - - // Stop the expression evaluation when immediately - // aborting on error, or when an interrupt occurred or - // an exception was thrown but not caught. - if (evaluate && aborting()) { - if (ret == OK) { - tv_clear(rettv); - } - ret = FAIL; - } + ret = eval_func(arg, s, len, rettv, evaluate, NULL); } else if (evaluate) { ret = get_var_tv((const char *)s, len, rettv, NULL, true, false); } else { @@ -4019,51 +4053,230 @@ static int eval7( *arg = skipwhite(*arg); // Handle following '[', '(' and '.' for expr[expr], expr.name, - // expr(expr). + // expr(expr), expr->name(expr) if (ret == OK) { - ret = handle_subscript((const char **)arg, rettv, evaluate, true); + ret = handle_subscript((const char **)arg, rettv, evaluate, true, + start_leader, &end_leader); } // Apply logical NOT and unary '-', from right to left, ignore '+'. if (ret == OK && evaluate && end_leader > start_leader) { - bool error = false; - varnumber_T val = 0; - float_T f = 0.0; + ret = eval7_leader(rettv, start_leader, &end_leader); + } + return ret; +} - if (rettv->v_type == VAR_FLOAT) { - f = rettv->vval.v_float; - } else { - val = tv_get_number_chk(rettv, &error); - } - if (error) { - tv_clear(rettv); - ret = FAIL; - } else { - while (end_leader > start_leader) { - --end_leader; - if (*end_leader == '!') { - if (rettv->v_type == VAR_FLOAT) { - f = !f; - } else { - val = !val; - } - } else if (*end_leader == '-') { - if (rettv->v_type == VAR_FLOAT) { - f = -f; - } else { - val = -val; - } +/// Apply the leading "!" and "-" before an eval7 expression to "rettv". +/// Adjusts "end_leaderp" until it is at "start_leader". +/// @return OK on success, FAIL on failure. +static int eval7_leader(typval_T *const rettv, const char_u *const start_leader, + const char_u **const end_leaderp) + FUNC_ATTR_NONNULL_ALL +{ + const char_u *end_leader = *end_leaderp; + int ret = OK; + bool error = false; + varnumber_T val = 0; + float_T f = 0.0; + + if (rettv->v_type == VAR_FLOAT) { + f = rettv->vval.v_float; + } else { + val = tv_get_number_chk(rettv, &error); + } + if (error) { + tv_clear(rettv); + ret = FAIL; + } else { + while (end_leader > start_leader) { + end_leader--; + if (*end_leader == '!') { + if (rettv->v_type == VAR_FLOAT) { + f = !f; + } else { + val = !val; + } + } else if (*end_leader == '-') { + if (rettv->v_type == VAR_FLOAT) { + f = -f; + } else { + val = -val; } } - if (rettv->v_type == VAR_FLOAT) { - tv_clear(rettv); - rettv->vval.v_float = f; + } + if (rettv->v_type == VAR_FLOAT) { + tv_clear(rettv); + rettv->vval.v_float = f; + } else { + tv_clear(rettv); + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = val; + } + } + + *end_leaderp = end_leader; + return ret; +} + +/// Call the function referred to in "rettv". +/// @param lua_funcname If `rettv` refers to a v:lua function, this must point +/// to the name of the Lua function to call (after the +/// "v:lua." prefix). +/// @return OK on success, FAIL on failure. +static int call_func_rettv(char_u **const arg, + typval_T *const rettv, + const bool evaluate, + dict_T *const selfdict, + typval_T *const basetv, + const char_u *const lua_funcname) + FUNC_ATTR_NONNULL_ARG(1, 2) +{ + partial_T *pt = NULL; + typval_T functv; + const char_u *funcname; + bool is_lua = false; + + // need to copy the funcref so that we can clear rettv + if (evaluate) { + functv = *rettv; + rettv->v_type = VAR_UNKNOWN; + + // Invoke the function. Recursive! + if (functv.v_type == VAR_PARTIAL) { + pt = functv.vval.v_partial; + is_lua = is_luafunc(pt); + funcname = is_lua ? lua_funcname : partial_name(pt); + } else { + funcname = functv.vval.v_string; + } + } else { + funcname = (char_u *)""; + } + + funcexe_T funcexe = FUNCEXE_INIT; + funcexe.firstline = curwin->w_cursor.lnum; + funcexe.lastline = curwin->w_cursor.lnum; + funcexe.evaluate = evaluate; + funcexe.partial = pt; + funcexe.selfdict = selfdict; + funcexe.basetv = basetv; + const int ret = get_func_tv(funcname, is_lua ? *arg - funcname : -1, rettv, + (char_u **)arg, &funcexe); + + // Clear the funcref afterwards, so that deleting it while + // evaluating the arguments is possible (see test55). + if (evaluate) { + tv_clear(&functv); + } + + return ret; +} + +/// Evaluate "->method()". +/// @param verbose if true, give error messages. +/// @note "*arg" points to the '-'. +/// @return FAIL or OK. @note "*arg" is advanced to after the ')'. +static int eval_lambda(char_u **const arg, typval_T *const rettv, + const bool evaluate, const bool verbose) + FUNC_ATTR_NONNULL_ALL +{ + // Skip over the ->. + *arg += 2; + typval_T base = *rettv; + rettv->v_type = VAR_UNKNOWN; + + int ret = get_lambda_tv(arg, rettv, evaluate); + if (ret == NOTDONE) { + return FAIL; + } else if (**arg != '(') { + if (verbose) { + if (*skipwhite(*arg) == '(') { + EMSG(_(e_nowhitespace)); } else { - tv_clear(rettv); - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = val; + EMSG2(_(e_missingparen), "lambda"); } } + tv_clear(rettv); + ret = FAIL; + } else { + ret = call_func_rettv(arg, rettv, evaluate, NULL, &base, NULL); + } + + // Clear the funcref afterwards, so that deleting it while + // evaluating the arguments is possible (see test55). + if (evaluate) { + tv_clear(&base); + } + + return ret; +} + +/// Evaluate "->method()" or "->v:lua.method()". +/// @note "*arg" points to the '-'. +/// @return FAIL or OK. "*arg" is advanced to after the ')'. +static int eval_method(char_u **const arg, typval_T *const rettv, + const bool evaluate, const bool verbose) + FUNC_ATTR_NONNULL_ALL +{ + // Skip over the ->. + *arg += 2; + typval_T base = *rettv; + rettv->v_type = VAR_UNKNOWN; + + // Locate the method name. + int len; + char_u *name = *arg; + char_u *lua_funcname = NULL; + if (STRNCMP(name, "v:lua.", 6) == 0) { + lua_funcname = name + 6; + *arg = (char_u *)skip_luafunc_name((const char *)lua_funcname); + *arg = skipwhite(*arg); // to detect trailing whitespace later + len = *arg - lua_funcname; + } else { + char_u *alias; + len = get_name_len((const char **)arg, (char **)&alias, evaluate, true); + if (alias != NULL) { + name = alias; + } + } + + int ret; + if (len <= 0) { + if (verbose) { + if (lua_funcname == NULL) { + EMSG(_("E260: Missing name after ->")); + } else { + EMSG2(_(e_invexpr2), name); + } + } + ret = FAIL; + } else { + if (**arg != '(') { + if (verbose) { + EMSG2(_(e_missingparen), name); + } + ret = FAIL; + } else if (ascii_iswhite((*arg)[-1])) { + if (verbose) { + EMSG(_(e_nowhitespace)); + } + ret = FAIL; + } else if (lua_funcname != NULL) { + if (evaluate) { + rettv->v_type = VAR_PARTIAL; + rettv->vval.v_partial = vvlua_partial; + rettv->vval.v_partial->pt_refcount++; + } + ret = call_func_rettv(arg, rettv, evaluate, NULL, &base, lua_funcname); + } else { + ret = eval_func(arg, name, len, rettv, evaluate, &base); + } + } + + // Clear the funcref afterwards, so that deleting it while + // evaluating the arguments is possible (see test55). + if (evaluate) { + tv_clear(&base); } return ret; @@ -7255,10 +7468,12 @@ bool callback_call(Callback *const callback, const int argcount_in, abort(); } - int dummy; - return call_func(name, -1, rettv, argcount_in, argvars_in, - NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, &dummy, - true, partial, NULL); + funcexe_T funcexe = FUNCEXE_INIT; + funcexe.firstline = curwin->w_cursor.lnum; + funcexe.lastline = curwin->w_cursor.lnum; + funcexe.evaluate = true; + funcexe.partial = partial; + return call_func(name, -1, rettv, argcount_in, argvars_in, &funcexe); } static bool set_ref_in_callback(Callback *callback, int copyID, @@ -8393,13 +8608,23 @@ static bool tv_is_luafunc(typval_T *tv) return tv->v_type == VAR_PARTIAL && is_luafunc(tv->vval.v_partial); } -/// check the function name after "v:lua." -int check_luafunc_name(const char *str, bool paren) +/// Skips one character past the end of the name of a v:lua function. +/// @param p Pointer to the char AFTER the "v:lua." prefix. +/// @return Pointer to the char one past the end of the function's name. +const char *skip_luafunc_name(const char *p) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - const char *p = str; while (ASCII_ISALNUM(*p) || *p == '_' || *p == '.' || *p == '\'') { p++; } + return p; +} + +/// check the function name after "v:lua." +int check_luafunc_name(const char *const str, const bool paren) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + const char *const p = skip_luafunc_name(str); if (*p != (paren ? '(' : NUL)) { return 0; } else { @@ -8407,24 +8632,26 @@ int check_luafunc_name(const char *str, bool paren) } } -/// Handle expr[expr], expr[expr:expr] subscript and .name lookup. -/// Also handle function call with Funcref variable: func(expr) -/// Can all be combined: dict.func(expr)[idx]['func'](expr) +/// Handle: +/// - expr[expr], expr[expr:expr] subscript +/// - ".name" lookup +/// - function call with Funcref variable: func(expr) +/// - method call: var->method() +/// +/// Can all be combined in any order: dict.func(expr)[idx]['func'](expr)->len() int handle_subscript( const char **const arg, typval_T *rettv, - int evaluate, // do more than finding the end - int verbose // give error messages + int evaluate, // do more than finding the end + int verbose, // give error messages + const char_u *const start_leader, // start of '!' and '-' prefixes + const char_u **const end_leaderp // end of '!' and '-' prefixes ) { int ret = OK; - dict_T *selfdict = NULL; - const char_u *s; - int len; - typval_T functv; - int slen = 0; - bool lua = false; + dict_T *selfdict = NULL; + const char_u *lua_funcname = NULL; if (tv_is_luafunc(rettv)) { if (**arg != '.') { @@ -8433,55 +8660,28 @@ handle_subscript( } else { (*arg)++; - lua = true; - s = (char_u *)(*arg); - slen = check_luafunc_name(*arg, true); - if (slen == 0) { + lua_funcname = (char_u *)(*arg); + const int len = check_luafunc_name(*arg, true); + if (len == 0) { tv_clear(rettv); ret = FAIL; } - (*arg) += slen; + (*arg) += len; } } - while (ret == OK - && (**arg == '[' - || (**arg == '.' && rettv->v_type == VAR_DICT) - || (**arg == '(' && (!evaluate || tv_is_func(*rettv)))) - && !ascii_iswhite(*(*arg - 1))) { + && (((**arg == '[' || (**arg == '.' && rettv->v_type == VAR_DICT) + || (**arg == '(' && (!evaluate || tv_is_func(*rettv)))) + && !ascii_iswhite(*(*arg - 1))) + || (**arg == '-' && (*arg)[1] == '>'))) { if (**arg == '(') { - partial_T *pt = NULL; - // need to copy the funcref so that we can clear rettv - if (evaluate) { - functv = *rettv; - rettv->v_type = VAR_UNKNOWN; + ret = call_func_rettv((char_u **)arg, rettv, evaluate, selfdict, NULL, + lua_funcname); - // Invoke the function. Recursive! - if (functv.v_type == VAR_PARTIAL) { - pt = functv.vval.v_partial; - if (!lua) { - s = partial_name(pt); - } - } else { - s = functv.vval.v_string; - } - } else { - s = (char_u *)""; - } - ret = get_func_tv(s, lua ? slen : -1, rettv, (char_u **)arg, - 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). - if (evaluate) { - tv_clear(&functv); - } - - /* Stop the expression evaluation when immediately aborting on - * error, or when an interrupt occurred or an exception was thrown - * but not caught. */ + // Stop the expression evaluation when immediately aborting on + // error, or when an interrupt occurred or an exception was thrown + // but not caught. if (aborting()) { if (ret == OK) { tv_clear(rettv); @@ -8490,6 +8690,21 @@ handle_subscript( } tv_dict_unref(selfdict); selfdict = NULL; + } else if (**arg == '-') { + // Expression "-1.0->method()" applies the leader "-" before + // applying ->. + if (evaluate && *end_leaderp > start_leader) { + ret = eval7_leader(rettv, start_leader, end_leaderp); + } + if (ret == OK) { + if ((*arg)[2] == '{') { + // expr->{lambda}() + ret = eval_lambda((char_u **)arg, rettv, evaluate, verbose); + } else { + // expr->name() + ret = eval_method((char_u **)arg, rettv, evaluate, verbose); + } + } } else { // **arg == '[' || **arg == '.' tv_dict_unref(selfdict); if (rettv->v_type == VAR_DICT) { @@ -9274,6 +9489,7 @@ void ex_echo(exarg_T *eap) bool atstart = true; bool need_clear = true; const int did_emsg_before = did_emsg; + const int called_emsg_before = called_emsg; if (eap->skip) ++emsg_skip; @@ -9288,7 +9504,8 @@ void ex_echo(exarg_T *eap) // Report the invalid expression unless the expression evaluation // has been cancelled due to an aborting error, an interrupt, or an // exception. - if (!aborting() && did_emsg == did_emsg_before) { + if (!aborting() && did_emsg == did_emsg_before + && called_emsg == called_emsg_before) { EMSG2(_(e_invexpr2), p); } need_clr_eos = false; @@ -10409,19 +10626,11 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments, typval_T rettv = { .v_type = VAR_UNKNOWN, .v_lock = VAR_UNLOCKED }; tv_list_ref(arguments); - int dummy; - (void)call_func((const char_u *)func, - name_len, - &rettv, - 2, - argvars, - NULL, - curwin->w_cursor.lnum, - curwin->w_cursor.lnum, - &dummy, - true, - NULL, - NULL); + funcexe_T funcexe = FUNCEXE_INIT; + funcexe.firstline = curwin->w_cursor.lnum; + funcexe.lastline = curwin->w_cursor.lnum; + funcexe.evaluate = true; + (void)call_func((const char_u *)func, name_len, &rettv, 2, argvars, &funcexe); tv_list_unref(arguments); // Restore caller scope information @@ -10779,7 +10988,9 @@ bool var_exists(const char *var) n = get_var_tv(name, len, &tv, NULL, false, true) == OK; if (n) { // Handle d.key, l[idx], f(expr). - n = handle_subscript(&var, &tv, true, false) == OK; + n = handle_subscript(&var, &tv, true, false, (const char_u *)name, + (const char_u **)&name) + == OK; if (n) { tv_clear(&tv); } diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index eb20cd1bc8..faff29b268 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -5,6 +5,9 @@ -- args Number of arguments, list with maximum and minimum number of arguments -- or list with a minimum number of arguments only. Defaults to zero -- arguments. +-- base For methods: the argument to use as the base argument (1-indexed): +-- base->method() +-- Defaults to BASE_NONE (function cannot be used as a method). -- func Name of the C function which implements the VimL function. Defaults to -- `f_{funcname}`. @@ -12,111 +15,115 @@ local varargs = function(nr) return {nr} end +-- Usable with the base key: use the last function argument as the method base. +-- Value is from funcs.h file. "BASE_" prefix is omitted. +local LAST = "BASE_LAST" + return { funcs={ - abs={args=1}, - acos={args=1, func="float_op_wrapper", data="&acos"}, -- WJMc - add={args=2}, - ['and']={args=2}, + abs={args=1, base=1}, + acos={args=1, base=1, func="float_op_wrapper", data="&acos"}, -- WJMc + add={args=2, base=1}, + ['and']={args=2, base=1}, api_info={}, - append={args=2}, - appendbufline={args=3}, + append={args=2, base=LAST}, + appendbufline={args=3, base=LAST}, argc={args={0, 1}}, argidx={}, arglistid={args={0, 2}}, argv={args={0, 2}}, - asin={args=1, func="float_op_wrapper", data="&asin"}, -- WJMc - assert_beeps={args={1}}, - assert_equal={args={2, 3}}, - assert_equalfile={args={2, 3}}, + asin={args=1, base=1, func="float_op_wrapper", data="&asin"}, -- WJMc + assert_beeps={args={1}, base=1}, + assert_equal={args={2, 3}, base=2}, + assert_equalfile={args={2, 3}, base=1}, assert_exception={args={1, 2}}, - assert_fails={args={1, 3}}, - assert_false={args={1, 2}}, - assert_inrange={args={3, 4}}, - assert_match={args={2, 3}}, + assert_fails={args={1, 3}, base=1}, + assert_false={args={1, 2}, base=1}, + assert_inrange={args={3, 4}, base=3}, + assert_match={args={2, 3}, base=2}, assert_nobeep={args={1}}, - assert_notequal={args={2, 3}}, - assert_notmatch={args={2, 3}}, - assert_report={args=1}, - assert_true={args={1, 2}}, - atan={args=1, func="float_op_wrapper", data="&atan"}, - atan2={args=2}, + assert_notequal={args={2, 3}, base=2}, + assert_notmatch={args={2, 3}, base=2}, + assert_report={args=1, base=1}, + assert_true={args={1, 2}, base=1}, + atan={args=1, base=1, func="float_op_wrapper", data="&atan"}, + atan2={args=2, base=1}, browse={args=4}, browsedir={args=2}, - bufadd={args=1}, - bufexists={args=1}, - buffer_exists={args=1, func='f_bufexists'}, -- obsolete + bufadd={args=1, base=1}, + bufexists={args=1, base=1}, + buffer_exists={args=1, base=1, func='f_bufexists'}, -- obsolete buffer_name={args={0, 1}, func='f_bufname'}, -- obsolete buffer_number={args={0, 1}, func='f_bufnr'}, -- obsolete - buflisted={args=1}, - bufload={args=1}, - bufloaded={args=1}, - bufname={args={0, 1}}, - bufnr={args={0, 2}}, - bufwinid={args=1}, - bufwinnr={args=1}, - byte2line={args=1}, - byteidx={args=2}, - byteidxcomp={args=2}, - call={args={2, 3}}, - ceil={args=1, func="float_op_wrapper", data="&ceil"}, + buflisted={args=1, base=1}, + bufload={args=1, base=1}, + bufloaded={args=1, base=1}, + bufname={args={0, 1}, base=1}, + bufnr={args={0, 2}, base=1}, + bufwinid={args=1, base=1}, + bufwinnr={args=1, base=1}, + byte2line={args=1, base=1}, + byteidx={args=2, base=1}, + byteidxcomp={args=2, base=1}, + call={args={2, 3}, base=1}, + ceil={args=1, base=1, func="float_op_wrapper", data="&ceil"}, changenr={}, chanclose={args={1, 2}}, chansend={args=2}, - char2nr={args={1, 2}}, + char2nr={args={1, 2}, base=1}, charidx={args={2, 3}}, - cindent={args=1}, - clearmatches={args={0, 1}}, - col={args=1}, - complete={args=2}, - complete_add={args=1}, + cindent={args=1, base=1}, + clearmatches={args={0, 1}, base=1}, + col={args=1, base=1}, + complete={args=2, base=2}, + complete_add={args=1, base=1}, complete_check={}, - complete_info={args={0, 1}}, - confirm={args={1, 4}}, - copy={args=1}, - cos={args=1, func="float_op_wrapper", data="&cos"}, - cosh={args=1, func="float_op_wrapper", data="&cosh"}, - count={args={2, 4}}, + complete_info={args={0, 1}, base=1}, + confirm={args={1, 4}, base=1}, + copy={args=1, base=1}, + cos={args=1, base=1, func="float_op_wrapper", data="&cos"}, + cosh={args=1, base=1, func="float_op_wrapper", data="&cosh"}, + count={args={2, 4}, base=1}, cscope_connection={args={0, 3}}, ctxget={args={0, 1}}, ctxpop={}, ctxpush={args={0, 1}}, ctxset={args={1, 2}}, ctxsize={}, - cursor={args={1, 3}}, - debugbreak={args={1, 1}}, - deepcopy={args={1, 2}}, - delete={args={1,2}}, - deletebufline={args={2,3}}, + cursor={args={1, 3}, base=1}, + debugbreak={args={1, 1}, base=1}, + deepcopy={args={1, 2}, base=1}, + delete={args={1,2}, base=1}, + deletebufline={args={2,3}, base=1}, dictwatcheradd={args=3}, dictwatcherdel={args=3}, did_filetype={}, - diff_filler={args=1}, - diff_hlID={args=2}, - empty={args=1}, + diff_filler={args=1, base=1}, + diff_hlID={args=2, base=1}, + empty={args=1, base=1}, environ={}, escape={args=2}, - eval={args=1}, + eval={args=1, base=1}, eventhandler={}, executable={args=1}, execute={args={1, 2}}, exepath={args=1}, exists={args=1}, - exp={args=1, func="float_op_wrapper", data="&exp"}, + exp={args=1, base=1, func="float_op_wrapper", data="&exp"}, expand={args={1, 3}}, expandcmd={args=1}, - extend={args={2, 3}}, + extend={args={2, 3}, base=1}, feedkeys={args={1, 2}}, file_readable={args=1, func='f_filereadable'}, -- obsolete filereadable={args=1}, filewritable={args=1}, - filter={args=2}, + filter={args=2, base=1}, finddir={args={1, 3}}, findfile={args={1, 3}}, flatten={args={1, 2}}, - float2nr={args=1}, - floor={args=1, func="float_op_wrapper", data="&floor"}, - fmod={args=2}, + float2nr={args=1, base=1}, + floor={args=1, base=1, func="float_op_wrapper", data="&floor"}, + fmod={args=2, base=1}, fnameescape={args=1}, fnamemodify={args=2}, foldclosed={args=1}, @@ -128,7 +135,7 @@ return { funcref={args={1, 3}}, ['function']={args={1, 3}}, garbagecollect={args={0, 1}}, - get={args={2, 3}}, + get={args={2, 3}, base=1}, getbufinfo={args={0, 1}}, getbufline={args={2, 3}}, getbufvar={args={2, 3}}, @@ -173,7 +180,7 @@ return { glob2regpat={args=1}, globpath={args={2, 5}}, has={args=1}, - has_key={args=2}, + has_key={args=2, base=1}, haslocaldir={args={0,2}}, hasmapto={args={1, 3}}, highlightID={args=1, func='f_hlID'}, -- obsolete @@ -187,22 +194,22 @@ return { hostname={}, iconv={args=3}, indent={args=1}, - index={args={2, 4}}, + index={args={2, 4}, base=1}, input={args={1, 3}}, inputdialog={args={1, 3}}, inputlist={args=1}, inputrestore={}, inputsave={}, inputsecret={args={1, 2}}, - insert={args={2, 3}}, + insert={args={2, 3}, base=1}, interrupt={args=0}, - invert={args=1}, + invert={args=1, base=1}, isdirectory={args=1}, - isinf={args=1}, + isinf={args=1, base=1}, islocked={args=1}, - isnan={args=1}, + isnan={args=1, base=1}, id={args=1}, - items={args=1}, + items={args=1, base=1}, jobclose={args={1, 2}, func="f_chanclose"}, jobpid={args=1}, jobresize={args=3}, @@ -210,12 +217,12 @@ return { jobstart={args={1, 2}}, jobstop={args=1}, jobwait={args={1, 2}}, - join={args={1, 2}}, + join={args={1, 2}, base=1}, json_decode={args=1}, json_encode={args=1}, - keys={args=1}, + keys={args=1, base=1}, last_buffer_nr={}, -- obsolete - len={args=1}, + len={args=1, base=1}, libcall={args=3}, libcallnr={args=3}, line={args={1, 2}}, @@ -223,10 +230,10 @@ return { lispindent={args=1}, list2str={args={1, 2}}, localtime={}, - log={args=1, func="float_op_wrapper", data="&log"}, - log10={args=1, func="float_op_wrapper", data="&log10"}, + log={args=1, base=1, func="float_op_wrapper", data="&log"}, + log10={args=1, base=1, func="float_op_wrapper", data="&log10"}, luaeval={args={1, 2}}, - map={args=2}, + map={args=2, base=1}, maparg={args={1, 4}}, mapcheck={args={1, 3}}, match={args={2, 4}}, @@ -238,20 +245,20 @@ return { matchlist={args={2, 4}}, matchstr={args={2, 4}}, matchstrpos={args={2,4}}, - max={args=1}, + max={args=1, base=1}, menu_get={args={1, 2}}, - min={args=1}, + min={args=1, base=1}, mkdir={args={1, 3}}, mode={args={0, 1}}, msgpackdump={args=1}, msgpackparse={args=1}, nextnonblank={args=1}, nr2char={args={1, 2}}, - ['or']={args=2}, + ['or']={args=2, base=1}, pathshorten={args=1}, - pow={args=2}, + pow={args=2, base=1}, prevnonblank={args=1}, - printf={args=varargs(1)}, + printf={args=varargs(1), base=2}, prompt_getprompt={args=1}, prompt_setcallback={args={2, 2}}, prompt_setinterrupt={args={2, 2}}, @@ -270,12 +277,12 @@ return { reltime={args={0, 2}}, reltimefloat={args=1}, reltimestr={args=1}, - remove={args={2, 3}}, + remove={args={2, 3}, base=1}, rename={args=2}, - ['repeat']={args=2}, + ['repeat']={args=2, base=1}, resolve={args=1}, - reverse={args=1}, - round={args=1, func="float_op_wrapper", data="&round"}, + reverse={args=1, base=1}, + round={args=1, base=1, func="float_op_wrapper", data="&round"}, rpcnotify={args=varargs(2)}, rpcrequest={args=varargs(2)}, rpcstart={args={1, 2}}, @@ -324,19 +331,19 @@ return { sign_unplace={args={1, 2}}, sign_unplacelist={args={1}}, simplify={args=1}, - sin={args=1, func="float_op_wrapper", data="&sin"}, - sinh={args=1, func="float_op_wrapper", data="&sinh"}, + sin={args=1, base=1, func="float_op_wrapper", data="&sin"}, + sinh={args=1, base=1, func="float_op_wrapper", data="&sinh"}, sockconnect={args={2,3}}, - sort={args={1, 3}}, + sort={args={1, 3}, base=1}, soundfold={args=1}, stdioopen={args=1}, spellbadword={args={0, 1}}, spellsuggest={args={1, 3}}, - split={args={1, 3}}, - sqrt={args=1, func="float_op_wrapper", data="&sqrt"}, + split={args={1, 3}, base=1}, + sqrt={args=1, base=1, func="float_op_wrapper", data="&sqrt"}, stdpath={args=1}, - str2float={args=1}, - str2list={args={1, 2}}, + str2float={args=1, base=1}, + str2list={args={1, 2}, base=1}, str2nr={args={1, 2}}, strcharpart={args={2, 3}}, strchars={args={1,2}}, @@ -344,31 +351,31 @@ return { strftime={args={1, 2}}, strgetchar={args={2, 2}}, stridx={args={2, 3}}, - string={args=1}, - strlen={args=1}, + string={args=1, base=1}, + strlen={args=1, base=1}, strpart={args={2, 4}}, strptime={args=2}, strridx={args={2, 3}}, - strtrans={args=1}, - strwidth={args=1}, + strtrans={args=1, base=1}, + strwidth={args=1, base=1}, submatch={args={1, 2}}, - substitute={args=4}, + substitute={args=4, base=1}, swapinfo={args={1}}, swapname={args={1}}, synID={args=3}, - synIDattr={args={2, 3}}, - synIDtrans={args=1}, + synIDattr={args={2, 3}, base=1}, + synIDtrans={args=1, base=1}, synconcealed={args=2}, synstack={args=2}, - system={args={1, 2}}, - systemlist={args={1, 3}}, + system={args={1, 2}, base=1}, + systemlist={args={1, 3}, base=1}, tabpagebuflist={args={0, 1}}, tabpagenr={args={0, 1}}, tabpagewinnr={args={1, 2}}, tagfiles={}, taglist={args={1, 2}}, - tan={args=1, func="float_op_wrapper", data="&tan"}, - tanh={args=1, func="float_op_wrapper", data="&tanh"}, + tan={args=1, base=1, func="float_op_wrapper", data="&tan"}, + tanh={args=1, base=1, func="float_op_wrapper", data="&tanh"}, tempname={}, termopen={args={1, 2}}, test_garbagecollect_now={}, @@ -382,12 +389,12 @@ return { toupper={args=1}, tr={args=3}, trim={args={1,3}}, - trunc={args=1, func="float_op_wrapper", data="&trunc"}, - type={args=1}, + trunc={args=1, base=1, func="float_op_wrapper", data="&trunc"}, + type={args=1, base=1}, undofile={args=1}, undotree={}, - uniq={args={1, 3}}, - values={args=1}, + uniq={args={1, 3}, base=1}, + values={args=1, base=1}, virtcol={args=1}, visualmode={args={0, 1}}, wait={args={2,3}}, @@ -401,7 +408,7 @@ return { win_id2win={args=1}, win_screenpos={args=1}, win_splitmove={args={2, 3}}, - winbufnr={args=1}, + winbufnr={args=1, base=1}, wincol={}, windowsversion={}, winheight={args=1}, @@ -414,6 +421,6 @@ return { winwidth={args=1}, wordcount={}, writefile={args={2, 3}}, - xor={args=2}, + xor={args=2, base=1}, }, } diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 4e409cca50..8a1258efd4 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -175,6 +175,53 @@ const VimLFuncDef *find_internal_func(const char *const name) return find_internal_func_gperf(name, len); } +int call_internal_func(const char_u *const fname, const int argcount, + typval_T *const argvars, typval_T *const rettv) + FUNC_ATTR_NONNULL_ALL +{ + const VimLFuncDef *const fdef = find_internal_func((const char *)fname); + if (fdef == NULL) { + return ERROR_UNKNOWN; + } else if (argcount < fdef->min_argc) { + return ERROR_TOOFEW; + } else if (argcount > fdef->max_argc) { + return ERROR_TOOMANY; + } + argvars[argcount].v_type = VAR_UNKNOWN; + fdef->func(argvars, rettv, fdef->data); + return ERROR_NONE; +} + +/// Invoke a method for base->method(). +int call_internal_method(const char_u *const fname, const int argcount, + typval_T *const argvars, typval_T *const rettv, + typval_T *const basetv) + FUNC_ATTR_NONNULL_ALL +{ + const VimLFuncDef *const fdef = find_internal_func((const char *)fname); + if (fdef == NULL) { + return ERROR_UNKNOWN; + } else if (fdef->base_arg == BASE_NONE) { + return ERROR_NOTMETHOD; + } else if (argcount + 1 < fdef->min_argc) { + return ERROR_TOOFEW; + } else if (argcount + 1 > fdef->max_argc) { + return ERROR_TOOMANY; + } + + typval_T argv[MAX_FUNC_ARGS + 1]; + const ptrdiff_t base_index + = fdef->base_arg == BASE_LAST ? argcount : fdef->base_arg - 1; + memcpy(argv, argvars, base_index * sizeof(typval_T)); + argv[base_index] = *basetv; + memcpy(argv + base_index + 1, argvars + base_index, + (argcount - base_index) * sizeof(typval_T)); + argv[argcount + 1].v_type = VAR_UNKNOWN; + + fdef->func(argv, rettv, fdef->data); + return ERROR_NONE; +} + /* * Return TRUE for a non-zero Number and a non-empty String. */ @@ -9420,7 +9467,6 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero) int res; typval_T rettv; typval_T argv[3]; - int dummy; const char *func_name; partial_T *partial = sortinfo->item_compare_partial; @@ -9444,10 +9490,11 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero) tv_copy(TV_LIST_ITEM_TV(si2->item), &argv[1]); rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this - res = call_func((const char_u *)func_name, - -1, - &rettv, 2, argv, NULL, 0L, 0L, &dummy, true, - partial, sortinfo->item_compare_selfdict); + funcexe_T funcexe = FUNCEXE_INIT; + funcexe.evaluate = true; + funcexe.partial = partial; + funcexe.selfdict = sortinfo->item_compare_selfdict; + res = call_func((const char_u *)func_name, -1, &rettv, 2, argv, &funcexe); tv_clear(&argv[0]); tv_clear(&argv[1]); diff --git a/src/nvim/eval/funcs.h b/src/nvim/eval/funcs.h index a343290734..c6a0cb959e 100644 --- a/src/nvim/eval/funcs.h +++ b/src/nvim/eval/funcs.h @@ -9,11 +9,16 @@ typedef void (*FunPtr)(void); /// Prototype of C function that implements VimL function typedef void (*VimLFunc)(typval_T *args, typval_T *rvar, FunPtr data); +/// Special flags for base_arg @see VimLFuncDef +#define BASE_NONE 0 ///< Not a method (no base argument). +#define BASE_LAST UINT8_MAX ///< Use the last argument as the method base. + /// Structure holding VimL function definition typedef struct fst { char *name; ///< Name of the function. uint8_t min_argc; ///< Minimal number of arguments. uint8_t max_argc; ///< Maximal number of arguments. + uint8_t base_arg; ///< Method base arg # (1-indexed), BASE_NONE or BASE_LAST. VimLFunc func; ///< Function implementation. FunPtr data; ///< Userdata for function implementation. } VimLFuncDef; diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index deddec413b..4184e4d922 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -414,12 +414,7 @@ get_func_tv( int len, // length of "name" or -1 to use strlen() typval_T *rettv, char_u **arg, // argument, pointing to the '(' - linenr_T firstline, // first line of range - linenr_T lastline, // last line of range - int *doesrange, // return: function handled range - int evaluate, - partial_T *partial, // for extra arguments - dict_T *selfdict // Dictionary for "self" + funcexe_T *funcexe // various values ) { char_u *argp; @@ -431,12 +426,13 @@ get_func_tv( * Get the arguments. */ argp = *arg; - while (argcount < MAX_FUNC_ARGS - (partial == NULL ? 0 : partial->pt_argc)) { + while (argcount < MAX_FUNC_ARGS + - (funcexe->partial == NULL ? 0 : funcexe->partial->pt_argc)) { argp = skipwhite(argp + 1); // skip the '(' or ',' if (*argp == ')' || *argp == ',' || *argp == NUL) { break; } - if (eval1(&argp, &argvars[argcount], evaluate) == FAIL) { + if (eval1(&argp, &argvars[argcount], funcexe->evaluate) == FAIL) { ret = FAIL; break; } @@ -463,9 +459,7 @@ get_func_tv( ((typval_T **)funcargs.ga_data)[funcargs.ga_len++] = &argvars[i]; } } - ret = call_func(name, len, rettv, argcount, argvars, NULL, - firstline, lastline, doesrange, evaluate, - partial, selfdict); + ret = call_func(name, len, rettv, argcount, argvars, funcexe); funcargs.ga_len -= i; } else if (!aborting()) { @@ -1367,7 +1361,6 @@ int func_call(char_u *name, typval_T *args, partial_T *partial, { typval_T argv[MAX_FUNC_ARGS + 1]; int argc = 0; - int dummy; int r = 0; TV_LIST_ITER(args->vval.v_list, item, { @@ -1380,9 +1373,13 @@ int func_call(char_u *name, typval_T *args, partial_T *partial, tv_copy(TV_LIST_ITEM_TV(item), &argv[argc++]); }); - r = call_func(name, -1, rettv, argc, argv, NULL, - curwin->w_cursor.lnum, curwin->w_cursor.lnum, - &dummy, true, partial, selfdict); + funcexe_T funcexe = FUNCEXE_INIT; + funcexe.firstline = curwin->w_cursor.lnum; + funcexe.lastline = curwin->w_cursor.lnum; + funcexe.evaluate = true; + funcexe.partial = partial; + funcexe.selfdict = selfdict; + r = call_func(name, -1, rettv, argc, argv, &funcexe); func_call_skip_call: // Free the arguments. @@ -1402,6 +1399,9 @@ static void user_func_error(int error, const char_u *name) case ERROR_UNKNOWN: emsg_funcname(N_("E117: Unknown function: %s"), name); break; + case ERROR_NOTMETHOD: + emsg_funcname(N_("E276: Cannot use function as a method: %s"), name); + break; case ERROR_DELETED: emsg_funcname(N_("E933: Function was deleted: %s"), name); break; @@ -1423,12 +1423,25 @@ static void user_func_error(int error, const char_u *name) } } +/// Used by call_func to add a method base (if any) to a function argument list +/// as the first argument. @see call_func +static void argv_add_base(typval_T *const basetv, typval_T **const argvars, + int *const argcount, typval_T *const new_argvars, + int *const argv_base) + FUNC_ATTR_NONNULL_ARG(2, 3, 4, 5) +{ + if (basetv != NULL) { + // Method call: base->Method() + memmove(&new_argvars[1], *argvars, sizeof(typval_T) * (*argcount)); + new_argvars[0] = *basetv; + (*argcount)++; + *argvars = new_argvars; + *argv_base = 1; + } +} + /// Call a function with its resolved parameters /// -/// "argv_func", when not NULL, can be used to fill in arguments only when the -/// invoked function uses them. It is called like this: -/// new_argcount = argv_func(current_argcount, argv, called_func_argcount) -/// /// @return FAIL if function cannot be called, else OK (even if an error /// occurred while executing the function! Set `msg_list` to capture /// the error, see do_cmdline()). @@ -1440,15 +1453,9 @@ call_func( int argcount_in, // number of "argvars" typval_T *argvars_in, // vars for arguments, must have "argcount" // PLUS ONE elements! - ArgvFunc argv_func, // function to fill in argvars - linenr_T firstline, // first line of range - linenr_T lastline, // last line of range - int *doesrange, // [out] function handled range - bool evaluate, - partial_T *partial, // optional, can be NULL - dict_T *selfdict_in // Dictionary for "self" + funcexe_T *funcexe // more arguments ) - FUNC_ATTR_NONNULL_ARG(1, 3, 5, 9) + FUNC_ATTR_NONNULL_ARG(1, 3, 5, 6) { int ret = FAIL; int error = ERROR_NONE; @@ -1459,9 +1466,12 @@ call_func( char_u *name = NULL; 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 + dict_T *selfdict = funcexe->selfdict; + typval_T argv[MAX_FUNC_ARGS + 1]; // used when "partial" or + // "funcexe->basetv" is not NULL int argv_clear = 0; + int argv_base = 0; + partial_T *partial = funcexe->partial; // Initialize rettv so that it is safe for caller to invoke clear_tv(rettv) // even when call_func() returns FAIL. @@ -1480,14 +1490,15 @@ call_func( fname = fname_trans_sid(name, fname_buf, &tofree, &error); } - *doesrange = false; + if (funcexe->doesrange != NULL) { + *funcexe->doesrange = false; + } if (partial != NULL) { // When the function has a partial with a dict and there is a dict // argument, use the dict argument. That is backwards compatible. // When the dict was bound explicitly use the one from the partial. - if (partial->pt_dict != NULL - && (selfdict_in == NULL || !partial->pt_auto)) { + if (partial->pt_dict != NULL && (selfdict == NULL || !partial->pt_auto)) { selfdict = partial->pt_dict; } if (error == ERROR_NONE && partial->pt_argc > 0) { @@ -1506,7 +1517,7 @@ call_func( } } - if (error == ERROR_NONE && evaluate) { + if (error == ERROR_NONE && funcexe->evaluate) { char_u *rfname = fname; // Ignore "g:" before a function name. @@ -1521,7 +1532,12 @@ call_func( if (is_luafunc(partial)) { if (len > 0) { error = ERROR_NONE; + argv_add_base(funcexe->basetv, &argvars, &argcount, argv, &argv_base); nlua_typval_call((const char *)funcname, len, argvars, argcount, rettv); + } else { + // v:lua was called directly; show its name in the emsg + XFREE_CLEAR(name); + funcname = (const char_u *)"v:lua"; } } else if (fp != NULL || !builtin_function((const char *)rfname, -1)) { // User defined function. @@ -1549,13 +1565,16 @@ call_func( cfunc_T cb = fp->uf_cb; error = (*cb)(argcount, argvars, rettv, fp->uf_cb_state); } else if (fp != NULL) { - if (argv_func != NULL) { + if (funcexe->argv_func != NULL) { // postponed filling in the arguments, do it now - argcount = argv_func(argcount, argvars, argv_clear, - fp->uf_args.ga_len); + argcount = funcexe->argv_func(argcount, argvars, argv_clear, + fp->uf_args.ga_len); } - if (fp->uf_flags & FC_RANGE) { - *doesrange = true; + + argv_add_base(funcexe->basetv, &argvars, &argcount, argv, &argv_base); + + if (fp->uf_flags & FC_RANGE && funcexe->doesrange != NULL) { + *funcexe->doesrange = true; } if (argcount < fp->uf_args.ga_len - fp->uf_def_args.ga_len) { error = ERROR_TOOFEW; @@ -1565,25 +1584,20 @@ call_func( error = ERROR_DICT; } else { // Call the user function. - call_user_func(fp, argcount, argvars, rettv, firstline, lastline, + call_user_func(fp, argcount, argvars, rettv, funcexe->firstline, + funcexe->lastline, (fp->uf_flags & FC_DICT) ? selfdict : NULL); error = ERROR_NONE; } } + } else if (funcexe->basetv != NULL) { + // expr->method(): Find the method name in the table, call its + // implementation with the base as one of the arguments. + error = call_internal_method(fname, argcount, argvars, rettv, + funcexe->basetv); } else { // Find the function name in the table, call its implementation. - const VimLFuncDef *const fdef = find_internal_func((const char *)fname); - if (fdef != NULL) { - if (argcount < fdef->min_argc) { - error = ERROR_TOOFEW; - } else if (argcount > fdef->max_argc) { - error = ERROR_TOOMANY; - } else { - argvars[argcount].v_type = VAR_UNKNOWN; - fdef->func(argvars, rettv, fdef->data); - error = ERROR_NONE; - } - } + error = call_internal_func(fname, argcount, argvars, rettv); } /* * The function call (or "FuncUndefined" autocommand sequence) might @@ -1607,9 +1621,11 @@ theend: user_func_error(error, (name != NULL) ? name : funcname); } + // clear the copies made from the partial while (argv_clear > 0) { - tv_clear(&argv[--argv_clear]); + tv_clear(&argv[--argv_clear + argv_base]); } + xfree(tofree); xfree(name); @@ -2901,7 +2917,7 @@ void ex_call(exarg_T *eap) int len; typval_T rettv; linenr_T lnum; - int doesrange; + bool doesrange; bool failed = false; funcdict_T fudi; partial_T *partial = NULL; @@ -2947,7 +2963,7 @@ void ex_call(exarg_T *eap) rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this. if (*startarg != '(') { - EMSG2(_("E107: Missing parentheses: %s"), eap->arg); + EMSG2(_(e_missingparen), eap->arg); goto end; } @@ -2965,15 +2981,22 @@ void ex_call(exarg_T *eap) curwin->w_cursor.coladd = 0; } arg = startarg; - if (get_func_tv(name, -1, &rettv, &arg, - eap->line1, eap->line2, &doesrange, - true, partial, fudi.fd_dict) == FAIL) { + + funcexe_T funcexe = FUNCEXE_INIT; + funcexe.firstline = eap->line1; + funcexe.lastline = eap->line2; + funcexe.doesrange = &doesrange; + funcexe.evaluate = true; + funcexe.partial = partial; + funcexe.selfdict = fudi.fd_dict; + if (get_func_tv(name, -1, &rettv, &arg, &funcexe) == FAIL) { failed = true; break; } // Handle a function returning a Funcref, Dictionary or List. - if (handle_subscript((const char **)&arg, &rettv, true, true) + if (handle_subscript((const char **)&arg, &rettv, true, true, + (const char_u *)name, (const char_u **)&name) == FAIL) { failed = true; break; diff --git a/src/nvim/eval/userfunc.h b/src/nvim/eval/userfunc.h index e8ad0bf1da..3f111343d2 100644 --- a/src/nvim/eval/userfunc.h +++ b/src/nvim/eval/userfunc.h @@ -28,11 +28,37 @@ typedef enum { ERROR_OTHER, ERROR_BOTH, ERROR_DELETED, + ERROR_NOTMETHOD, } FnameTransError; +/// Used in funcexe_T. Returns the new argcount. typedef int (*ArgvFunc)(int current_argcount, typval_T *argv, int argskip, int called_func_argcount); +/// Structure passed between functions dealing with function call execution. +typedef struct { + ArgvFunc argv_func; ///< when not NULL, can be used to fill in arguments only + ///< when the invoked function uses them + linenr_T firstline; ///< first line of range + linenr_T lastline; ///< last line of range + bool *doesrange; ///< [out] if not NULL: function handled range + bool evaluate; ///< actually evaluate expressions + partial_T *partial; ///< for extra arguments + dict_T *selfdict; ///< Dictionary for "self" + typval_T *basetv; ///< base for base->method() +} funcexe_T; + +#define FUNCEXE_INIT (funcexe_T) { \ + .argv_func = NULL, \ + .firstline = 0, \ + .lastline = 0, \ + .doesrange = NULL, \ + .evaluate = false, \ + .partial = NULL, \ + .selfdict = NULL, \ + .basetv = NULL, \ +} + #define FUNCARG(fp, j) ((char_u **)(fp->uf_args.ga_data))[j] #define FUNCLINE(fp, j) ((char_u **)(fp->uf_lines.ga_data))[j] diff --git a/src/nvim/generators/gen_eval.lua b/src/nvim/generators/gen_eval.lua index 679895421a..945fa5099f 100644 --- a/src/nvim/generators/gen_eval.lua +++ b/src/nvim/generators/gen_eval.lua @@ -42,7 +42,7 @@ gperfpipe:write([[ %language=ANSI-C %global-table %readonly-tables -%define initializer-suffix ,0,0,NULL,NULL +%define initializer-suffix ,0,0,BASE_NONE,NULL,NULL %define word-array-name functions %define hash-function-name hash_internal_func_gperf %define lookup-function-name find_internal_func_gperf @@ -59,9 +59,10 @@ for name, def in pairs(funcs) do elseif #args == 1 then args[2] = 'MAX_FUNC_ARGS' end + local base = def.base or "BASE_NONE" local func = def.func or ('f_' .. name) local data = def.data or "NULL" - gperfpipe:write(('%s, %s, %s, &%s, (FunPtr)%s\n') - :format(name, args[1], args[2], func, data)) + gperfpipe:write(('%s, %s, %s, %s, &%s, (FunPtr)%s\n') + :format(name, args[1], args[2], base, func, data)) end gperfpipe:close() diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 96acca4ac7..2a72dbcd09 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -972,6 +972,7 @@ EXTERN char_u e_write[] INIT(= N_("E80: Error while writing")); EXTERN char_u e_zerocount[] INIT(= N_("E939: Positive count required")); EXTERN char_u e_usingsid[] INIT(= N_( "E81: Using not in a script context")); +EXTERN char_u e_missingparen[] INIT(= N_("E107: Missing parentheses: %s")); EXTERN char_u e_maxmempat[] INIT(= N_( "E363: pattern uses more memory than 'maxmempattern'")); EXTERN char_u e_emptybuf[] INIT(= N_("E749: empty buffer")); diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index cbc2273bc9..b00c4282be 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -785,13 +785,13 @@ int nlua_call(lua_State *lstate) try_start(); typval_T rettv; - int dummy; + funcexe_T funcexe = FUNCEXE_INIT; + funcexe.firstline = curwin->w_cursor.lnum; + funcexe.lastline = curwin->w_cursor.lnum; + funcexe.evaluate = true; // call_func() retval is deceptive, ignore it. Instead we set `msg_list` // (TRY_WRAP) to capture abort-causing non-exception errors. - (void)call_func(name, (int)name_len, &rettv, nargs, - vim_args, NULL, - curwin->w_cursor.lnum, curwin->w_cursor.lnum, - &dummy, true, NULL, NULL); + (void)call_func(name, (int)name_len, &rettv, nargs, vim_args, &funcexe); if (!try_end(&err)) { nlua_push_typval(lstate, &rettv, false); } diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 7b2f77a6f9..ffcf659d28 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -2531,12 +2531,12 @@ do_mouse ( } }; typval_T rettv; - int doesrange; - (void)call_func((char_u *)tab_page_click_defs[mouse_col].func, - -1, - &rettv, ARRAY_SIZE(argv), argv, NULL, - curwin->w_cursor.lnum, curwin->w_cursor.lnum, - &doesrange, true, NULL, NULL); + funcexe_T funcexe = FUNCEXE_INIT; + funcexe.firstline = curwin->w_cursor.lnum; + funcexe.lastline = curwin->w_cursor.lnum; + funcexe.evaluate = true; + (void)call_func((char_u *)tab_page_click_defs[mouse_col].func, -1, + &rettv, ARRAY_SIZE(argv), argv, &funcexe); tv_clear(&rettv); break; } diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index 6379174938..98a46cf781 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -6726,26 +6726,24 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, if (expr != NULL) { typval_T argv[2]; - int dummy; typval_T rettv; staticList10_T matchList = TV_LIST_STATIC10_INIT; - rettv.v_type = VAR_STRING; rettv.vval.v_string = NULL; argv[0].v_type = VAR_LIST; argv[0].vval.v_list = &matchList.sl_list; + funcexe_T funcexe = FUNCEXE_INIT; + funcexe.argv_func = fill_submatch_list; + funcexe.evaluate = true; if (expr->v_type == VAR_FUNC) { s = expr->vval.v_string; - call_func(s, -1, &rettv, 1, argv, - fill_submatch_list, 0L, 0L, &dummy, - true, NULL, NULL); + call_func(s, -1, &rettv, 1, argv, &funcexe); } else if (expr->v_type == VAR_PARTIAL) { partial_T *partial = expr->vval.v_partial; s = partial_name(partial); - call_func(s, -1, &rettv, 1, argv, - fill_submatch_list, 0L, 0L, &dummy, - true, partial, NULL); + funcexe.partial = partial; + call_func(s, -1, &rettv, 1, argv, &funcexe); } if (tv_list_len(&matchList.sl_list) > 0) { // fill_submatch_list() was called. diff --git a/src/nvim/testdir/sautest/autoload/foo.vim b/src/nvim/testdir/sautest/autoload/foo.vim index d7dcd5ce3d..298e7275d8 100644 --- a/src/nvim/testdir/sautest/autoload/foo.vim +++ b/src/nvim/testdir/sautest/autoload/foo.vim @@ -5,3 +5,7 @@ let foo#bar = {} func foo#bar.echo() let g:called_foo_bar_echo += 1 endfunc + +func foo#addFoo(head) + return a:head .. 'foo' +endfunc diff --git a/src/nvim/testdir/test_arglist.vim b/src/nvim/testdir/test_arglist.vim index a1ef8325ec..01d8f32893 100644 --- a/src/nvim/testdir/test_arglist.vim +++ b/src/nvim/testdir/test_arglist.vim @@ -90,8 +90,8 @@ func Test_argadd_empty_curbuf() call assert_equal('', bufname('%')) call assert_equal(1, line('$')) rew - call assert_notequal(curbuf, bufnr('%')) - call assert_equal('Xargadd', bufname('%')) + call assert_notequal(curbuf, '%'->bufnr()) + call assert_equal('Xargadd', '%'->bufname()) call assert_equal(2, line('$')) %argd diff --git a/src/nvim/testdir/test_assert.vim b/src/nvim/testdir/test_assert.vim index 1d114221dc..52f243aaea 100644 --- a/src/nvim/testdir/test_assert.vim +++ b/src/nvim/testdir/test_assert.vim @@ -7,7 +7,7 @@ func Test_assert_equalfile() let goodtext = ["one", "two", "three"] call writefile(goodtext, 'Xone') - call assert_equal(1, assert_equalfile('Xone', 'xyzxyz')) + call assert_equal(1, 'Xone'->assert_equalfile('xyzxyz')) call assert_match("E485: Can't read file xyzxyz", v:errors[0]) call remove(v:errors, 0) diff --git a/src/nvim/testdir/test_autoload.vim b/src/nvim/testdir/test_autoload.vim index 7396c227c9..b8c4fa251f 100644 --- a/src/nvim/testdir/test_autoload.vim +++ b/src/nvim/testdir/test_autoload.vim @@ -8,6 +8,8 @@ func Test_autoload_dict_func() call g:foo#bar.echo() call assert_equal(1, g:loaded_foo_vim) call assert_equal(1, g:called_foo_bar_echo) + + eval 'bar'->g:foo#addFoo()->assert_equal('barfoo') endfunc func Test_source_autoload() diff --git a/src/nvim/testdir/test_bufline.vim b/src/nvim/testdir/test_bufline.vim index e038bce08e..b4e8a0bc71 100644 --- a/src/nvim/testdir/test_bufline.vim +++ b/src/nvim/testdir/test_bufline.vim @@ -102,7 +102,7 @@ func Test_deletebufline() call assert_equal(0, deletebufline(b, 2, 8)) call assert_equal(['aaa'], getbufline(b, 1, 2)) exe "bd!" b - call assert_equal(1, deletebufline(b, 1)) + call assert_equal(1, b->deletebufline(1)) split Xtest call setline(1, ['a', 'b', 'c']) @@ -131,11 +131,11 @@ func Test_appendbufline_redraw() endif let lines =<< trim END new foo - let winnr=bufwinnr('foo') - let buf=bufnr('foo') + let winnr = 'foo'->bufwinnr() + let buf = bufnr('foo') wincmd p call appendbufline(buf, '$', range(1,200)) - exe winnr. 'wincmd w' + exe winnr .. 'wincmd w' norm! G wincmd p call deletebufline(buf, 1, '$') diff --git a/src/nvim/testdir/test_bufwintabinfo.vim b/src/nvim/testdir/test_bufwintabinfo.vim index cb7ab44798..4b5b55e6bf 100644 --- a/src/nvim/testdir/test_bufwintabinfo.vim +++ b/src/nvim/testdir/test_bufwintabinfo.vim @@ -18,7 +18,7 @@ function Test_getbufwintabinfo() let l = getbufinfo('%') call assert_equal(bufnr('%'), l[0].bufnr) call assert_equal('vim', l[0].variables.editor) - call assert_notequal(-1, index(l[0].windows, bufwinid('%'))) + call assert_notequal(-1, index(l[0].windows, '%'->bufwinid())) " Test for getbufinfo() with 'bufmodified' call assert_equal(0, len(getbufinfo({'bufmodified' : 1}))) diff --git a/src/nvim/testdir/test_cindent.vim b/src/nvim/testdir/test_cindent.vim index b6c2d1467e..562867f548 100644 --- a/src/nvim/testdir/test_cindent.vim +++ b/src/nvim/testdir/test_cindent.vim @@ -118,6 +118,16 @@ b = something(); bw! endfunc +func Test_cindent_func() + new + setlocal cindent + call setline(1, ['int main(void)', '{', 'return 0;', '}']) + call assert_equal(-1, cindent(0)) + call assert_equal(&sw, 3->cindent()) + call assert_equal(-1, cindent(line('$')+1)) + bwipe! +endfunc + " this was going beyond the end of the line. func Test_cindent_case() new diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim index f39cda7663..efa7f552e0 100644 --- a/src/nvim/testdir/test_diffmode.vim +++ b/src/nvim/testdir/test_diffmode.vim @@ -725,7 +725,7 @@ func Test_diff_filler() diffthis redraw - call assert_equal([0, 0, 0, 0, 0, 0, 0, 1, 0], map(range(-1, 7), 'diff_filler(v:val)')) + call assert_equal([0, 0, 0, 0, 0, 0, 0, 1, 0], map(range(-1, 7), 'v:val->diff_filler()')) wincmd w call assert_equal([0, 0, 0, 0, 2, 0, 0, 0], map(range(-1, 6), 'diff_filler(v:val)')) @@ -741,16 +741,16 @@ func Test_diff_hlID() diffthis redraw - call assert_equal(synIDattr(diff_hlID(-1, 1), "name"), "") + call diff_hlID(-1, 1)->synIDattr("name")->assert_equal("") call assert_equal(diff_hlID(1, 1), hlID("DiffChange")) - call assert_equal(synIDattr(diff_hlID(1, 1), "name"), "DiffChange") + call diff_hlID(1, 1)->synIDattr("name")->assert_equal("DiffChange") call assert_equal(diff_hlID(1, 2), hlID("DiffText")) - call assert_equal(synIDattr(diff_hlID(1, 2), "name"), "DiffText") - call assert_equal(synIDattr(diff_hlID(2, 1), "name"), "") + call diff_hlID(1, 2)->synIDattr("name")->assert_equal("DiffText") + call diff_hlID(2, 1)->synIDattr("name")->assert_equal("") call assert_equal(diff_hlID(3, 1), hlID("DiffAdd")) - call assert_equal(synIDattr(diff_hlID(3, 1), "name"), "DiffAdd") - call assert_equal(synIDattr(diff_hlID(4, 1), "name"), "") + call diff_hlID(3, 1)->synIDattr("name")->assert_equal("DiffAdd") + call diff_hlID(4, 1)->synIDattr("name")->assert_equal("") wincmd w call assert_equal(diff_hlID(1, 1), hlID("DiffChange")) diff --git a/src/nvim/testdir/test_float_func.vim b/src/nvim/testdir/test_float_func.vim index 154ef570e0..78675d7016 100644 --- a/src/nvim/testdir/test_float_func.vim +++ b/src/nvim/testdir/test_float_func.vim @@ -7,6 +7,8 @@ end func Test_abs() call assert_equal('1.23', string(abs(1.23))) call assert_equal('1.23', string(abs(-1.23))) + eval -1.23->abs()->string()->assert_equal('1.23') + call assert_equal('0.0', string(abs(0.0))) call assert_equal('0.0', string(abs(1.0/(1.0/0.0)))) call assert_equal('0.0', string(abs(-1.0/(1.0/0.0)))) @@ -22,6 +24,7 @@ endfunc func Test_sqrt() call assert_equal('0.0', string(sqrt(0.0))) call assert_equal('1.414214', string(sqrt(2.0))) + eval 2.0->sqrt()->string()->assert_equal('1.414214') call assert_equal("str2float('inf')", string(sqrt(1.0/0.0))) call assert_equal("str2float('nan')", string(sqrt(-1.0))) call assert_equal("str2float('nan')", string(sqrt(0.0/0.0))) @@ -31,6 +34,7 @@ endfunc func Test_log() call assert_equal('0.0', string(log(1.0))) call assert_equal('-0.693147', string(log(0.5))) + eval 0.5->log()->string()->assert_equal('-0.693147') call assert_equal("-str2float('inf')", string(log(0.0))) call assert_equal("str2float('nan')", string(log(-1.0))) call assert_equal("str2float('inf')", string(log(1.0/0.0))) @@ -42,6 +46,7 @@ func Test_log10() call assert_equal('0.0', string(log10(1.0))) call assert_equal('2.0', string(log10(100.0))) call assert_equal('2.079181', string(log10(120.0))) + eval 120.0->log10()->string()->assert_equal('2.079181') call assert_equal("-str2float('inf')", string(log10(0.0))) call assert_equal("str2float('nan')", string(log10(-1.0))) call assert_equal("str2float('inf')", string(log10(1.0/0.0))) @@ -53,6 +58,7 @@ func Test_exp() call assert_equal('1.0', string(exp(0.0))) call assert_equal('7.389056', string(exp(2.0))) call assert_equal('0.367879', string(exp(-1.0))) + eval -1.0->exp()->string()->assert_equal('0.367879') call assert_equal("str2float('inf')", string(exp(1.0/0.0))) call assert_equal('0.0', string(exp(-1.0/0.0))) call assert_equal("str2float('nan')", string(exp(0.0/0.0))) @@ -63,6 +69,7 @@ func Test_sin() call assert_equal('0.0', string(sin(0.0))) call assert_equal('0.841471', string(sin(1.0))) call assert_equal('-0.479426', string(sin(-0.5))) + eval -0.5->sin()->string()->assert_equal('-0.479426') call assert_equal("str2float('nan')", string(sin(0.0/0.0))) call assert_equal("str2float('nan')", string(sin(1.0/0.0))) call assert_equal('0.0', string(sin(1.0/(1.0/0.0)))) @@ -73,6 +80,8 @@ endfunc func Test_asin() call assert_equal('0.0', string(asin(0.0))) call assert_equal('1.570796', string(asin(1.0))) + eval 1.0->asin()->string()->assert_equal('1.570796') + call assert_equal('-0.523599', string(asin(-0.5))) call assert_equal("str2float('nan')", string(asin(1.1))) call assert_equal("str2float('nan')", string(asin(1.0/0.0))) @@ -84,6 +93,7 @@ func Test_sinh() call assert_equal('0.0', string(sinh(0.0))) call assert_equal('0.521095', string(sinh(0.5))) call assert_equal('-1.026517', string(sinh(-0.9))) + eval -0.9->sinh()->string()->assert_equal('-1.026517') call assert_equal("str2float('inf')", string(sinh(1.0/0.0))) call assert_equal("-str2float('inf')", string(sinh(-1.0/0.0))) call assert_equal("str2float('nan')", string(sinh(0.0/0.0))) @@ -94,6 +104,7 @@ func Test_cos() call assert_equal('1.0', string(cos(0.0))) call assert_equal('0.540302', string(cos(1.0))) call assert_equal('0.877583', string(cos(-0.5))) + eval -0.5->cos()->string()->assert_equal('0.877583') call assert_equal("str2float('nan')", string(cos(0.0/0.0))) call assert_equal("str2float('nan')", string(cos(1.0/0.0))) call assert_fails('call cos("")', 'E808:') @@ -103,6 +114,7 @@ func Test_acos() call assert_equal('1.570796', string(acos(0.0))) call assert_equal('0.0', string(acos(1.0))) call assert_equal('3.141593', string(acos(-1.0))) + eval -1.0->acos()->string()->assert_equal('3.141593') call assert_equal('2.094395', string(acos(-0.5))) call assert_equal("str2float('nan')", string(acos(1.1))) call assert_equal("str2float('nan')", string(acos(1.0/0.0))) @@ -113,6 +125,7 @@ endfunc func Test_cosh() call assert_equal('1.0', string(cosh(0.0))) call assert_equal('1.127626', string(cosh(0.5))) + eval 0.5->cosh()->string()->assert_equal('1.127626') call assert_equal("str2float('inf')", string(cosh(1.0/0.0))) call assert_equal("str2float('inf')", string(cosh(-1.0/0.0))) call assert_equal("str2float('nan')", string(cosh(0.0/0.0))) @@ -123,6 +136,7 @@ func Test_tan() call assert_equal('0.0', string(tan(0.0))) call assert_equal('0.546302', string(tan(0.5))) call assert_equal('-0.546302', string(tan(-0.5))) + eval -0.5->tan()->string()->assert_equal('-0.546302') call assert_equal("str2float('nan')", string(tan(1.0/0.0))) call assert_equal("str2float('nan')", string(cos(0.0/0.0))) call assert_equal('0.0', string(tan(1.0/(1.0/0.0)))) @@ -134,6 +148,7 @@ func Test_atan() call assert_equal('0.0', string(atan(0.0))) call assert_equal('0.463648', string(atan(0.5))) call assert_equal('-0.785398', string(atan(-1.0))) + eval -1.0->atan()->string()->assert_equal('-0.785398') call assert_equal('1.570796', string(atan(1.0/0.0))) call assert_equal('-1.570796', string(atan(-1.0/0.0))) call assert_equal("str2float('nan')", string(atan(0.0/0.0))) @@ -144,6 +159,7 @@ func Test_atan2() call assert_equal('-2.356194', string(atan2(-1, -1))) call assert_equal('2.356194', string(atan2(1, -1))) call assert_equal('0.0', string(atan2(1.0, 1.0/0.0))) + eval 1.0->atan2(1.0/0.0)->string()->assert_equal('0.0') call assert_equal('1.570796', string(atan2(1.0/0.0, 1.0))) call assert_equal("str2float('nan')", string(atan2(0.0/0.0, 1.0))) call assert_fails('call atan2("", -1)', 'E808:') @@ -154,6 +170,7 @@ func Test_tanh() call assert_equal('0.0', string(tanh(0.0))) call assert_equal('0.462117', string(tanh(0.5))) call assert_equal('-0.761594', string(tanh(-1.0))) + eval -1.0->tanh()->string()->assert_equal('-0.761594') call assert_equal('1.0', string(tanh(1.0/0.0))) call assert_equal('-1.0', string(tanh(-1.0/0.0))) call assert_equal("str2float('nan')", string(tanh(0.0/0.0))) @@ -164,6 +181,7 @@ func Test_fmod() call assert_equal('0.13', string(fmod(12.33, 1.22))) call assert_equal('-0.13', string(fmod(-12.33, 1.22))) call assert_equal("str2float('nan')", string(fmod(1.0/0.0, 1.0))) + eval (1.0/0.0)->fmod(1.0)->string()->assert_equal("str2float('nan')") " On Windows we get "nan" instead of 1.0, accept both. let res = string(fmod(1.0, 1.0/0.0)) if res != "str2float('nan')" @@ -177,6 +195,7 @@ endfunc func Test_pow() call assert_equal('1.0', string(pow(0.0, 0.0))) call assert_equal('8.0', string(pow(2.0, 3.0))) + eval 2.0->pow(3.0)->string()->assert_equal('8.0') call assert_equal("str2float('nan')", string(pow(2.0, 0.0/0.0))) call assert_equal("str2float('nan')", string(pow(0.0/0.0, 3.0))) call assert_equal("str2float('nan')", string(pow(0.0/0.0, 3.0))) @@ -192,6 +211,7 @@ func Test_str2float() call assert_equal('1.0', string(str2float(' 1.0 '))) call assert_equal('1.23', string(str2float('1.23'))) call assert_equal('1.23', string(str2float('1.23abc'))) + eval '1.23abc'->str2float()->string()->assert_equal('1.23') call assert_equal('1.0e40', string(str2float('1e40'))) call assert_equal('-1.23', string(str2float('-1.23'))) call assert_equal('1.23', string(str2float(' + 1.23 '))) @@ -228,6 +248,7 @@ func Test_float2nr() call assert_equal(1, float2nr(1.234)) call assert_equal(123, float2nr(1.234e2)) call assert_equal(12, float2nr(123.4e-1)) + eval 123.4e-1->float2nr()->assert_equal(12) let max_number = 1/0 let min_number = -max_number call assert_equal(max_number/2+1, float2nr(pow(2, 62))) @@ -242,6 +263,7 @@ func Test_floor() call assert_equal('2.0', string(floor(2.0))) call assert_equal('2.0', string(floor(2.11))) call assert_equal('2.0', string(floor(2.99))) + eval 2.99->floor()->string()->assert_equal('2.0') call assert_equal('-3.0', string(floor(-2.11))) call assert_equal('-3.0', string(floor(-2.99))) call assert_equal("str2float('nan')", string(floor(0.0/0.0))) @@ -255,6 +277,7 @@ func Test_ceil() call assert_equal('3.0', string(ceil(2.11))) call assert_equal('3.0', string(ceil(2.99))) call assert_equal('-2.0', string(ceil(-2.11))) + eval -2.11->ceil()->string()->assert_equal('-2.0') call assert_equal('-2.0', string(ceil(-2.99))) call assert_equal("str2float('nan')", string(ceil(0.0/0.0))) call assert_equal("str2float('inf')", string(ceil(1.0/0.0))) @@ -266,6 +289,7 @@ func Test_round() call assert_equal('2.0', string(round(2.1))) call assert_equal('3.0', string(round(2.5))) call assert_equal('3.0', string(round(2.9))) + eval 2.9->round()->string()->assert_equal('3.0') call assert_equal('-2.0', string(round(-2.1))) call assert_equal('-3.0', string(round(-2.5))) call assert_equal('-3.0', string(round(-2.9))) @@ -279,6 +303,7 @@ func Test_trunc() call assert_equal('2.0', string(trunc(2.1))) call assert_equal('2.0', string(trunc(2.5))) call assert_equal('2.0', string(trunc(2.9))) + eval 2.9->trunc()->string()->assert_equal('2.0') call assert_equal('-2.0', string(trunc(-2.1))) call assert_equal('-2.0', string(trunc(-2.5))) call assert_equal('-2.0', string(trunc(-2.9))) @@ -291,6 +316,7 @@ endfunc func Test_isinf() call assert_equal(1, isinf(1.0/0.0)) call assert_equal(-1, isinf(-1.0/0.0)) + eval (-1.0/0.0)->isinf()->assert_equal(-1) call assert_false(isinf(1.0)) call assert_false(isinf(0.0/0.0)) call assert_false(isinf('a')) @@ -302,6 +328,7 @@ func Test_isnan() call assert_true(isnan(0.0/0.0)) call assert_false(isnan(1.0)) call assert_false(isnan(1.0/0.0)) + eval (1.0/0.0)->isnan()->assert_false() call assert_false(isnan(-1.0/0.0)) call assert_false(isnan('a')) call assert_false(isnan([])) diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index 48f97be96b..6cb3e24201 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -852,7 +852,7 @@ func Test_byte2line_line2byte() set fileformat=mac call assert_equal([-1, -1, 1, 1, 2, 2, 2, 3, 3, -1], - \ map(range(-1, 8), 'byte2line(v:val)')) + \ map(range(-1, 8), 'v:val->byte2line()')) call assert_equal([-1, -1, 1, 3, 6, 8, -1], \ map(range(-1, 5), 'line2byte(v:val)')) @@ -875,6 +875,34 @@ func Test_byte2line_line2byte() bw! endfunc +func Test_byteidx() + let a = '.é.' " one char of two bytes + call assert_equal(0, byteidx(a, 0)) + call assert_equal(0, byteidxcomp(a, 0)) + call assert_equal(1, byteidx(a, 1)) + call assert_equal(1, byteidxcomp(a, 1)) + call assert_equal(3, byteidx(a, 2)) + call assert_equal(3, byteidxcomp(a, 2)) + call assert_equal(4, byteidx(a, 3)) + call assert_equal(4, byteidxcomp(a, 3)) + call assert_equal(-1, byteidx(a, 4)) + call assert_equal(-1, byteidxcomp(a, 4)) + + let b = '.é.' " normal e with composing char + call assert_equal(0, b->byteidx(0)) + call assert_equal(1, b->byteidx(1)) + call assert_equal(4, b->byteidx(2)) + call assert_equal(5, b->byteidx(3)) + call assert_equal(-1, b->byteidx(4)) + + call assert_equal(0, b->byteidxcomp(0)) + call assert_equal(1, b->byteidxcomp(1)) + call assert_equal(2, b->byteidxcomp(2)) + call assert_equal(4, b->byteidxcomp(3)) + call assert_equal(5, b->byteidxcomp(4)) + call assert_equal(-1, b->byteidxcomp(5)) +endfunc + " Test for charidx() func Test_charidx() let a = 'xáb́y' @@ -1065,7 +1093,7 @@ func Test_col() call assert_equal(7, col('$')) call assert_equal(4, col("'x")) call assert_equal(6, col("'Y")) - call assert_equal(2, col([1, 2])) + call assert_equal(2, [1, 2]->col()) call assert_equal(7, col([1, '$'])) call assert_equal(0, col('')) @@ -1413,13 +1441,13 @@ func Test_bufadd_bufload() call assert_equal([''], getbufline(buf, 1, '$')) let curbuf = bufnr('') - call writefile(['some', 'text'], 'otherName') - let buf = bufadd('otherName') + call writefile(['some', 'text'], 'XotherName') + let buf = 'XotherName'->bufadd() call assert_notequal(0, buf) - call assert_equal(1, bufexists('otherName')) + eval 'XotherName'->bufexists()->assert_equal(1) call assert_equal(0, getbufvar(buf, '&buflisted')) call assert_equal(0, bufloaded(buf)) - call bufload(buf) + eval buf->bufload() call assert_equal(1, bufloaded(buf)) call assert_equal(['some', 'text'], getbufline(buf, 1, '$')) call assert_equal(curbuf, bufnr('')) @@ -1439,8 +1467,9 @@ func Test_bufadd_bufload() call assert_equal(0, bufexists(buf2)) bwipe someName - bwipe otherName + bwipe XotherName call assert_equal(0, bufexists('someName')) + call delete('XotherName') endfunc func Test_readdir() @@ -1473,6 +1502,20 @@ func Test_readdir() call delete('Xdir', 'rf') endfunc +func Test_call() + call assert_equal(3, call('len', [123])) + call assert_equal(3, 'len'->call([123])) + call assert_fails("call call('len', 123)", 'E714:') + call assert_equal(0, call('', [])) + + function Mylen() dict + return len(self.data) + endfunction + let mydict = {'data': [0, 1, 2, 3], 'len': function("Mylen")} + eval mydict.len->call([], mydict)->assert_equal(4) + call assert_fails("call call('Mylen', [], 0)", 'E715:') +endfunc + " Test for the eval() function func Test_eval() call assert_fails("call eval('5 a')", 'E488:') diff --git a/src/nvim/testdir/test_hide.vim b/src/nvim/testdir/test_hide.vim index 128b8ff945..41b1a4ad7c 100644 --- a/src/nvim/testdir/test_hide.vim +++ b/src/nvim/testdir/test_hide.vim @@ -37,7 +37,7 @@ function Test_hide() " :hide as a command hide call assert_equal([orig_bname, orig_winnr], [bufname(''), winnr('$')]) - call assert_equal([1, 1], [buflisted('Xf1'), bufloaded('Xf1')]) + call assert_equal([1, 1], ['Xf1'->buflisted(), 'Xf1'->bufloaded()]) bwipeout! Xf1 new Xf1 diff --git a/src/nvim/testdir/test_lambda.vim b/src/nvim/testdir/test_lambda.vim index f026c8a55f..63bb4ae1ef 100644 --- a/src/nvim/testdir/test_lambda.vim +++ b/src/nvim/testdir/test_lambda.vim @@ -61,7 +61,7 @@ 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, a -> a + a}(1, 2)', 'E853:') call assert_fails('echo {a, b -> a + b)}(1, 2)', 'E15:') endfunc diff --git a/src/nvim/testdir/test_match.vim b/src/nvim/testdir/test_match.vim index 0fd76a23ea..505a052a19 100644 --- a/src/nvim/testdir/test_match.vim +++ b/src/nvim/testdir/test_match.vim @@ -242,7 +242,7 @@ func Test_matchaddpos_otherwin() \] call assert_equal(expect, savematches) - call clearmatches(winid) + eval winid->clearmatches() call assert_equal([], getmatches(winid)) call setmatches(savematches, winid) diff --git a/src/nvim/testdir/test_method.vim b/src/nvim/testdir/test_method.vim new file mode 100644 index 0000000000..7a6e6aa19d --- /dev/null +++ b/src/nvim/testdir/test_method.vim @@ -0,0 +1,159 @@ +" Tests for ->method() + +func Test_list_method() + let l = [1, 2, 3] + call assert_equal([1, 2, 3, 4], [1, 2, 3]->add(4)) + eval l->assert_equal(l) + eval l->assert_equal(l, 'wrong') + eval l->assert_notequal([3, 2, 1]) + eval l->assert_notequal([3, 2, 1], 'wrong') + call assert_equal(l, l->copy()) + call assert_equal(l, l->deepcopy()) + call assert_equal(1, l->count(2)) + call assert_false(l->empty()) + call assert_true([]->empty()) + call assert_equal(579, ['123', '+', '456']->join()->eval()) + call assert_equal([1, 2, 3, 4, 5], [1, 2, 3]->extend([4, 5])) + call assert_equal([1, 3], [1, 2, 3]->filter('v:val != 2')) + call assert_equal(2, l->get(1)) + call assert_equal(1, l->index(2)) + call assert_equal([0, 1, 2, 3], [1, 2, 3]->insert(0)) + call assert_fails('eval l->items()', 'E715:') + call assert_equal('1 2 3', l->join()) + call assert_fails('eval l->keys()', 'E715:') + call assert_equal(3, l->len()) + call assert_equal([2, 3, 4], [1, 2, 3]->map('v:val + 1')) + call assert_equal(3, l->max()) + call assert_equal(1, l->min()) + call assert_equal(2, [1, 2, 3]->remove(1)) + call assert_equal([1, 2, 3, 1, 2, 3], l->repeat(2)) + call assert_equal([3, 2, 1], [1, 2, 3]->reverse()) + call assert_equal([1, 2, 3, 4], [4, 2, 3, 1]->sort()) + call assert_equal('[1, 2, 3]', l->string()) + call assert_equal(v:t_list, l->type()) + call assert_equal([1, 2, 3], [1, 1, 2, 3, 3]->uniq()) + call assert_fails('eval l->values()', 'E715:') +endfunc + +func Test_dict_method() + let d = #{one: 1, two: 2, three: 3} + + call assert_equal(d, d->copy()) + call assert_equal(d, d->deepcopy()) + call assert_equal(1, d->count(2)) + call assert_false(d->empty()) + call assert_true({}->empty()) + call assert_equal(#{one: 1, two: 2, three: 3, four: 4}, d->extend(#{four: 4})) + call assert_equal(#{one: 1, two: 2, three: 3}, d->filter('v:val != 4')) + call assert_equal(2, d->get('two')) + " Nvim doesn't support Blobs yet; expect a different emsg + " call assert_fails("let x = d->index(2)", 'E897:') + " call assert_fails("let x = d->insert(0)", 'E899:') + call assert_fails("let x = d->index(2)", 'E714:') + call assert_fails("let x = d->insert(0)", 'E686:') + call assert_true(d->has_key('two')) + call assert_equal([['one', 1], ['two', 2], ['three', 3]], d->items()) + call assert_fails("let x = d->join()", 'E714:') + call assert_equal(['one', 'two', 'three'], d->keys()) + call assert_equal(3, d->len()) + call assert_equal(#{one: 2, two: 3, three: 4}, d->map('v:val + 1')) + call assert_equal(#{one: 1, two: 2, three: 3}, d->map('v:val - 1')) + call assert_equal(3, d->max()) + call assert_equal(1, d->min()) + call assert_equal(2, d->remove("two")) + let d.two = 2 + call assert_fails('let x = d->repeat(2)', 'E731:') + " Nvim doesn't support Blobs yet; expect a different emsg + " call assert_fails('let x = d->reverse()', 'E899:') + call assert_fails('let x = d->reverse()', 'E686:') + call assert_fails('let x = d->sort()', 'E686:') + call assert_equal("{'one': 1, 'two': 2, 'three': 3}", d->string()) + call assert_equal(v:t_dict, d->type()) + call assert_fails('let x = d->uniq()', 'E686:') + call assert_equal([1, 2, 3], d->values()) +endfunc + +func Test_string_method() + eval '1 2 3'->split()->assert_equal(['1', '2', '3']) + eval '1 2 3'->split()->map({i, v -> str2nr(v)})->assert_equal([1, 2, 3]) + eval 'ABC'->str2list()->assert_equal([65, 66, 67]) + eval 'ABC'->strlen()->assert_equal(3) + eval "a\rb\ec"->strtrans()->assert_equal('a^Mb^[c') + eval "aあb"->strwidth()->assert_equal(4) + eval 'abc'->substitute('b', 'x', '')->assert_equal('axc') + + eval 'abc'->printf('the %s arg')->assert_equal('the abc arg') +endfunc + +func Test_method_append() + new + eval ['one', 'two', 'three']->append(1) + call assert_equal(['', 'one', 'two', 'three'], getline(1, '$')) + + %del + let bnr = bufnr('') + wincmd w + eval ['one', 'two', 'three']->appendbufline(bnr, 1) + call assert_equal(['', 'one', 'two', 'three'], getbufline(bnr, 1, '$')) + + exe 'bwipe! ' .. bnr +endfunc + +func Test_method_funcref() + func Concat(one, two, three) + return a:one .. a:two .. a:three + endfunc + let FuncRef = function('Concat') + eval 'foo'->FuncRef('bar', 'tail')->assert_equal('foobartail') + + " not enough arguments + call assert_fails("eval 'foo'->FuncRef('bar')", 'E119:') + " too many arguments + call assert_fails("eval 'foo'->FuncRef('bar', 'tail', 'four')", 'E118:') + + let Partial = function('Concat', ['two']) + eval 'one'->Partial('three')->assert_equal('onetwothree') + + " not enough arguments + call assert_fails("eval 'one'->Partial()", 'E119:') + " too many arguments + call assert_fails("eval 'one'->Partial('three', 'four')", 'E118:') + + delfunc Concat +endfunc + +func Test_method_float() + eval 1.234->string()->assert_equal('1.234') + eval -1.234->string()->assert_equal('-1.234') +endfunc + +func Test_method_syntax() + eval [1, 2, 3] ->sort( ) + eval [1, 2, 3] + \ ->sort( + \ ) + call assert_fails('eval [1, 2, 3]-> sort()', 'E260:') + call assert_fails('eval [1, 2, 3]->sort ()', 'E274:') + call assert_fails('eval [1, 2, 3]-> sort ()', 'E260:') +endfunc + +func Test_method_lambda() + eval "text"->{x -> x .. " extended"}()->assert_equal('text extended') + eval "text"->{x, y -> x .. " extended " .. y}('more')->assert_equal('text extended more') + + call assert_fails('eval "text"->{x -> x .. " extended"} ()', 'E274:') + + " todo: lambda accepts more arguments than it consumes + " call assert_fails('eval "text"->{x -> x .. " extended"}("more")', 'E99:') + + " Nvim doesn't include test_refcount(). + " let l = [1, 2, 3] + " eval l->{x -> x}() + " call assert_equal(1, test_refcount(l)) +endfunc + +func Test_method_not_supported() + call assert_fails('eval 123->changenr()', 'E276:') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim index 06bdb1236a..710450293c 100644 --- a/src/nvim/testdir/test_popup.vim +++ b/src/nvim/testdir/test_popup.vim @@ -250,7 +250,7 @@ endfunc func Test_noinsert_complete() func! s:complTest1() abort - call complete(1, ['source', 'soundfold']) + eval ['source', 'soundfold']->complete(1) return '' endfunc @@ -403,7 +403,7 @@ func DummyCompleteFour(findstart, base) return 0 else call complete_add('four1') - call complete_add('four2') + eval 'four2'->complete_add() call complete_check() call complete_add('four3') call complete_add('four4') @@ -989,7 +989,7 @@ func GetCompleteInfo() if empty(g:compl_what) let g:compl_info = complete_info() else - let g:compl_info = complete_info(g:compl_what) + let g:compl_info = g:compl_what->complete_info() endif return '' endfunc diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim index 875e23894f..2344bac498 100644 --- a/src/nvim/testdir/test_syntax.vim +++ b/src/nvim/testdir/test_syntax.vim @@ -546,8 +546,8 @@ func Test_synstack_synIDtrans() call assert_equal([], synstack(1, 1)) norm f/ - call assert_equal(['cComment', 'cCommentStart'], map(synstack(line("."), col(".")), 'synIDattr(v:val, "name")')) - call assert_equal(['Comment', 'Comment'], map(synstack(line("."), col(".")), 'synIDattr(synIDtrans(v:val), "name")')) + eval synstack(line("."), col("."))->map('synIDattr(v:val, "name")')->assert_equal(['cComment', 'cCommentStart']) + eval synstack(line("."), col("."))->map('synIDattr(synIDtrans(v:val), "name")')->assert_equal(['Comment', 'Comment']) norm fA call assert_equal(['cComment'], map(synstack(line("."), col(".")), 'synIDattr(v:val, "name")')) diff --git a/src/nvim/testdir/test_system.vim b/src/nvim/testdir/test_system.vim index 6bbe714d19..7b8ee778cc 100644 --- a/src/nvim/testdir/test_system.vim +++ b/src/nvim/testdir/test_system.vim @@ -7,10 +7,10 @@ func Test_System() if !executable('echo') || !executable('cat') || !executable('wc') return endif - let out = system('echo 123') + let out = 'echo 123'->system() call assert_equal("123\n", out) - let out = systemlist('echo 123') + let out = 'echo 123'->systemlist() if &shell =~# 'cmd.exe$' call assert_equal(["123\r"], out) else diff --git a/src/nvim/testdir/test_user_func.vim b/src/nvim/testdir/test_user_func.vim index 7dcd92a54b..5231ef7b4f 100644 --- a/src/nvim/testdir/test_user_func.vim +++ b/src/nvim/testdir/test_user_func.vim @@ -47,7 +47,7 @@ func FuncWithRef(a) endfunc func Test_user_func() - let g:FuncRef=function("FuncWithRef") + let g:FuncRef = function("FuncWithRef") let g:counter = 0 inoremap ( ListItem() inoremap [ ListReset() @@ -62,6 +62,14 @@ func Test_user_func() call assert_equal(9, g:retval) call assert_equal(333, g:FuncRef(333)) + let g:retval = "nop" + call assert_equal('xxx4asdf', "xxx"->Table(4, "asdf")) + call assert_equal('fail', 45->Compute(0, "retval")) + call assert_equal('nop', g:retval) + call assert_equal('ok', 45->Compute(5, "retval")) + call assert_equal(9, g:retval) + " call assert_equal(333, 333->g:FuncRef()) + enew normal oXX+-XX @@ -150,6 +158,14 @@ func Test_default_arg() \ execute('func Args2')) endfunc +func s:addFoo(lead) + return a:lead .. 'foo' +endfunc + +func Test_user_method() + eval 'bar'->s:addFoo()->assert_equal('barfoo') +endfunc + func Test_failed_call_in_try() try | call UnknownFunc() | catch | endtry endfunc diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim index 5922660ef9..d5837e88c9 100644 --- a/src/nvim/testdir/test_vimscript.vim +++ b/src/nvim/testdir/test_vimscript.vim @@ -1372,6 +1372,7 @@ func Test_bitwise_functions() " and call assert_equal(127, and(127, 127)) call assert_equal(16, and(127, 16)) + eval 127->and(16)->assert_equal(16) call assert_equal(0, and(127, 128)) call assert_fails("call and(1.0, 1)", 'E805:') call assert_fails("call and([], 1)", 'E745:') @@ -1382,6 +1383,7 @@ func Test_bitwise_functions() " or call assert_equal(23, or(16, 7)) call assert_equal(15, or(8, 7)) + eval 8->or(7)->assert_equal(15) call assert_equal(123, or(0, 123)) call assert_fails("call or(1.0, 1)", 'E805:') call assert_fails("call or([], 1)", 'E745:') @@ -1392,6 +1394,7 @@ func Test_bitwise_functions() " xor call assert_equal(0, xor(127, 127)) call assert_equal(111, xor(127, 16)) + eval 127->xor(16)->assert_equal(111) call assert_equal(255, xor(127, 128)) call assert_fails("call xor(1.0, 1)", 'E805:') call assert_fails("call xor([], 1)", 'E745:') @@ -1401,6 +1404,7 @@ func Test_bitwise_functions() call assert_fails("call xor(1, {})", 'E728:') " invert call assert_equal(65408, and(invert(127), 65535)) + eval 127->invert()->and(65535)->assert_equal(65408) call assert_equal(65519, and(invert(16), 65535)) call assert_equal(65407, and(invert(128), 65535)) call assert_fails("call invert(1.0)", 'E805:') diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua index 997e600671..f3ab420603 100644 --- a/test/functional/core/startup_spec.lua +++ b/test/functional/core/startup_spec.lua @@ -247,9 +247,9 @@ describe('startup', function() [[" -u NONE -i NONE --cmd "set noruler" --cmd "let g:foo = g:bar"')]]) screen:expect([[ ^ | + | Error detected while processing pre-vimrc command line: | E121: Undefined variable: g:bar | - E15: Invalid expression: g:bar | Press ENTER or type command to continue | | ]]) diff --git a/test/functional/eval/null_spec.lua b/test/functional/eval/null_spec.lua index b1ceff9115..f866aca3ed 100644 --- a/test/functional/eval/null_spec.lua +++ b/test/functional/eval/null_spec.lua @@ -53,7 +53,7 @@ describe('NULL', function() -- Correct behaviour null_expr_test('can be indexed with error message for empty list', 'L[0]', - 'E684: list index out of range: 0\nE15: Invalid expression: L[0]', nil) + 'E684: list index out of range: 0', nil) null_expr_test('can be splice-indexed', 'L[:]', 0, {}) null_expr_test('is not locked', 'islocked("v:_null_list")', 0, 0) null_test('is accepted by :for', 'for x in L|throw x|endfor', 0) @@ -68,7 +68,7 @@ describe('NULL', function() null_expr_test('can be copied', 'copy(L)', 0, {}) null_expr_test('can be deepcopied', 'deepcopy(L)', 0, {}) null_expr_test('does not crash when indexed', 'L[1]', - 'E684: list index out of range: 1\nE15: Invalid expression: L[1]', nil) + 'E684: list index out of range: 1', nil) null_expr_test('does not crash call()', 'call("arglistid", L)', 0, 0) null_expr_test('does not crash col()', 'col(L)', 0, 0) null_expr_test('does not crash virtcol()', 'virtcol(L)', 0, 0) @@ -135,7 +135,7 @@ describe('NULL', function() end) describe('dict', function() it('does not crash when indexing NULL dict', function() - eq('\nE716: Key not present in Dictionary: "test"\nE15: Invalid expression: v:_null_dict.test', + eq('\nE716: Key not present in Dictionary: "test"', redir_exec('echo v:_null_dict.test')) end) null_expr_test('makes extend error out', 'extend(D, {})', 'E742: Cannot change value of extend() argument', 0) diff --git a/test/functional/legacy/assert_spec.lua b/test/functional/legacy/assert_spec.lua index 515d6d91b8..c2b22472bf 100644 --- a/test/functional/legacy/assert_spec.lua +++ b/test/functional/legacy/assert_spec.lua @@ -26,6 +26,14 @@ describe('assert function:', function() call('assert_beeps', 'normal 0') expected_errors({'command did not beep: normal 0'}) end) + + it('can be used as a method', function() + local tmpname = source [[ + call assert_equal(0, 'normal h'->assert_beeps()) + call assert_equal(1, 'normal 0'->assert_beeps()) + ]] + expected_errors({tmpname .. ' line 2: command did not beep: normal 0'}) + end) end) -- assert_equal({expected}, {actual}, [, {msg}]) @@ -133,6 +141,14 @@ describe('assert function:', function() call('assert_false', {}) expected_errors({'Expected False but got []'}) end) + + it('can be used as a method', function() + local tmpname = source [[ + call assert_equal(0, v:false->assert_false()) + call assert_equal(1, 123->assert_false()) + ]] + expected_errors({tmpname .. ' line 2: Expected False but got 123'}) + end) end) -- assert_true({actual}, [, {msg}]) @@ -148,6 +164,14 @@ describe('assert function:', function() eq(1, call('assert_true', 1.5)) expected_errors({'Expected True but got 1.5'}) end) + + it('can be used as a method', function() + local tmpname = source [[ + call assert_equal(0, v:true->assert_true()) + call assert_equal(1, 0->assert_true()) + ]] + expected_errors({tmpname .. ' line 2: Expected True but got 0'}) + end) end) describe('v:errors', function() @@ -223,6 +247,15 @@ describe('assert function:', function() call('assert_match', 'bar.*foo', 'foobar', 'wrong') expected_errors({"wrong: Pattern 'bar.*foo' does not match 'foobar'"}) end) + + it('can be used as a method', function() + local tmpname = source [[ + call assert_equal(1, 'foobar'->assert_match('bar.*foo', 'wrong')) + ]] + expected_errors({ + tmpname .. " line 1: wrong: Pattern 'bar.*foo' does not match 'foobar'" + }) + end) end) -- assert_notmatch({pat}, {text}[, {msg}]) @@ -237,6 +270,13 @@ describe('assert function:', function() call('assert_notmatch', 'foo', 'foobar') expected_errors({"Pattern 'foo' does match 'foobar'"}) end) + + it('can be used as a method', function() + local tmpname = source [[ + call assert_equal(1, 'foobar'->assert_notmatch('foo')) + ]] + expected_errors({tmpname .. " line 1: Pattern 'foo' does match 'foobar'"}) + end) end) -- assert_fails({cmd}, [, {error}]) @@ -267,6 +307,15 @@ describe('assert function:', function() eq(1, eval([[assert_fails('echo', '', 'echo command')]])) expected_errors({'command did not fail: echo command'}) end) + + it('can be used as a method', function() + local tmpname = source [[ + call assert_equal(1, 'echo'->assert_fails('', 'echo command')) + ]] + expected_errors({ + tmpname .. ' line 1: command did not fail: echo command' + }) + end) end) -- assert_inrange({lower}, {upper}, {actual}[, {msg}]) @@ -292,6 +341,15 @@ describe('assert function:', function() eq('Vim(call):E119: Not enough arguments for function: assert_inrange', exc_exec("call assert_inrange(1, 1)")) end) + + it('can be used as a method', function() + local tmpname = source [[ + call assert_equal(0, 5->assert_inrange(5, 7)) + call assert_equal(0, 7->assert_inrange(5, 7)) + call assert_equal(1, 8->assert_inrange(5, 7)) + ]] + expected_errors({tmpname .. ' line 3: Expected range 5 - 7, but got 8'}) + end) end) -- assert_report({msg}) @@ -302,6 +360,13 @@ describe('assert function:', function() command('call remove(v:errors, 0)') expected_empty() end) + + it('can be used as a method', function() + local tmpname = source [[ + call assert_equal(1, 'also wrong'->assert_report()) + ]] + expected_errors({tmpname .. ' line 1: also wrong'}) + end) end) -- assert_exception({cmd}, [, {error}]) diff --git a/test/functional/lua/luaeval_spec.lua b/test/functional/lua/luaeval_spec.lua index 2ec48777fd..8ef77faa0f 100644 --- a/test/functional/lua/luaeval_spec.lua +++ b/test/functional/lua/luaeval_spec.lua @@ -481,6 +481,21 @@ describe('v:lua', function() pcall_err(eval, 'v:lua.mymod.crashy()')) end) + it('works when called as a method', function() + eq(123, eval('110->v:lua.foo(13)')) + eq(true, exec_lua([[return _G.val == nil]])) + + eq(321, eval('300->v:lua.foo(21, "boop")')) + eq("boop", exec_lua([[return _G.val]])) + + eq(NIL, eval('"there"->v:lua.mymod.noisy()')) + eq("hey there", meths.get_current_line()) + eq({5, 10, 15, 20}, eval('[[1], [2, 3], [4]]->v:lua.vim.tbl_flatten()->map({_, v -> v * 5})')) + + eq("Vim:E5108: Error executing lua [string \"\"]:0: attempt to call global 'nonexistent' (a nil value)", + pcall_err(eval, '"huh?"->v:lua.mymod.crashy()')) + end) + it('works in :call', function() command(":call v:lua.mymod.noisy('command')") eq("hey command", meths.get_current_line()) @@ -518,8 +533,15 @@ describe('v:lua', function() eq("Vim:E15: Invalid expression: v:['lua'].foo()", pcall_err(eval, "v:['lua'].foo()")) eq("Vim(call):E461: Illegal variable name: v:['lua']", pcall_err(command, "call v:['lua'].baar()")) + eq("Vim:E117: Unknown function: v:lua", pcall_err(eval, "v:lua()")) eq("Vim(let):E46: Cannot change read-only variable \"v:['lua']\"", pcall_err(command, "let v:['lua'] = 'xx'")) eq("Vim(let):E46: Cannot change read-only variable \"v:lua\"", pcall_err(command, "let v:lua = 'xx'")) + + eq("Vim:E107: Missing parentheses: v:lua.func", pcall_err(eval, "'bad'->v:lua.func")) + eq("Vim:E274: No white space allowed before parenthesis", pcall_err(eval, "'bad'->v:lua.func ()")) + eq("Vim:E107: Missing parentheses: v:lua", pcall_err(eval, "'bad'->v:lua")) + eq("Vim:E117: Unknown function: v:lua", pcall_err(eval, "'bad'->v:lua()")) + eq("Vim:E15: Invalid expression: v:lua.()", pcall_err(eval, "'bad'->v:lua.()")) end) end) diff --git a/test/functional/provider/clipboard_spec.lua b/test/functional/provider/clipboard_spec.lua index e5e21f11a6..986db96a18 100644 --- a/test/functional/provider/clipboard_spec.lua +++ b/test/functional/provider/clipboard_spec.lua @@ -653,14 +653,12 @@ describe('clipboard (with fake clipboard.vim)', function() '', '', 'E121: Undefined variable: doesnotexist', - 'E15: Invalid expression: doesnotexist', }, 'v'}, eval("g:test_clip['*']")) feed_command(':echo "Howdy!"') eq({{ '', '', 'E121: Undefined variable: doesnotexist', - 'E15: Invalid expression: doesnotexist', '', 'Howdy!', }, 'v'}, eval("g:test_clip['*']"))