aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/builtin.txt157
-rw-r--r--runtime/doc/eval.txt26
-rw-r--r--runtime/doc/lua.txt240
-rw-r--r--runtime/doc/news.txt1
-rw-r--r--runtime/doc/usr_41.txt2
-rw-r--r--runtime/lua/vim/_editor.lua14
-rw-r--r--runtime/lua/vim/_init_packages.lua1
-rw-r--r--runtime/lua/vim/iter.lua112
-rw-r--r--runtime/lua/vim/shared.lua105
-rwxr-xr-xscripts/gen_vimdoc.py3
-rw-r--r--[-rwxr-xr-x]src/nvim/CMakeLists.txt0
-rw-r--r--src/nvim/cmdexpand.c3
-rw-r--r--src/nvim/eval.c7
-rw-r--r--src/nvim/eval.lua10
-rw-r--r--src/nvim/eval/funcs.c692
-rw-r--r--src/nvim/eval/typval.c58
-rw-r--r--src/nvim/eval/userfunc.c80
-rw-r--r--src/nvim/eval/window.c84
-rw-r--r--[-rwxr-xr-x]src/nvim/generators/gen_api_ui_events.lua0
-rw-r--r--src/nvim/generators/gen_eval.lua1
-rw-r--r--src/nvim/globals.h1
-rw-r--r--src/nvim/mapping.c3
-rw-r--r--src/nvim/mbyte.c28
-rw-r--r--src/nvim/search.c6
-rw-r--r--src/nvim/sign.c27
-rw-r--r--src/nvim/strings.c711
-rw-r--r--src/nvim/window.c7
-rw-r--r--test/functional/lua/iter_spec.lua425
-rw-r--r--test/functional/lua/vim_spec.lua421
-rw-r--r--test/functional/vimscript/eval_spec.lua31
-rw-r--r--test/old/testdir/test_assert.vim10
-rw-r--r--test/old/testdir/test_charsearch.vim2
-rw-r--r--test/old/testdir/test_cmdline.vim2
-rw-r--r--test/old/testdir/test_expr.vim2
-rw-r--r--test/old/testdir/test_functions.vim476
-rw-r--r--test/old/testdir/test_listdict.vim2
-rw-r--r--test/old/testdir/test_maparg.vim2
-rw-r--r--test/old/testdir/test_matchfuzzy.vim8
-rw-r--r--test/old/testdir/test_partial.vim2
-rw-r--r--test/old/testdir/test_search_stat.vim2
-rw-r--r--test/old/testdir/test_signs.vim14
-rw-r--r--test/old/testdir/test_tagjump.vim8
-rw-r--r--test/old/testdir/test_timers.vim12
-rw-r--r--test/old/testdir/test_window_cmd.vim6
44 files changed, 2302 insertions, 1502 deletions
diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index 91531db656..b37ac117f3 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -60,17 +60,19 @@ browse({save}, {title}, {initdir}, {default})
String put up a file requester
browsedir({title}, {initdir}) String put up a directory requester
bufadd({name}) Number add a buffer to the buffer list
-bufexists({expr}) Number |TRUE| if buffer {expr} exists
-buflisted({expr}) Number |TRUE| if buffer {expr} is listed
-bufload({expr}) Number load buffer {expr} if not loaded yet
-bufloaded({expr}) Number |TRUE| if buffer {expr} is loaded
-bufname([{expr}]) String Name of the buffer {expr}
-bufnr([{expr} [, {create}]]) Number Number of the buffer {expr}
-bufwinid({expr}) Number |window-ID| of buffer {expr}
-bufwinnr({expr}) Number window number of buffer {expr}
+bufexists({buf}) Number |TRUE| if buffer {buf} exists
+buflisted({buf}) Number |TRUE| if buffer {buf} is listed
+bufload({buf}) Number load buffer {buf} if not loaded yet
+bufloaded({buf}) Number |TRUE| if buffer {buf} is loaded
+bufname([{buf}]) String Name of the buffer {buf}
+bufnr([{buf} [, {create}]]) Number Number of the buffer {buf}
+bufwinid({buf}) Number window ID of buffer {buf}
+bufwinnr({buf}) Number window number of buffer {buf}
byte2line({byte}) Number line number at byte count {byte}
-byteidx({expr}, {nr}) Number byte index of {nr}th char in {expr}
-byteidxcomp({expr}, {nr}) Number byte index of {nr}th char in {expr}
+byteidx({expr}, {nr} [, {utf16}])
+ Number byte index of {nr}th char in {expr}
+byteidxcomp({expr}, {nr} [, {utf16}])
+ Number byte index of {nr}th char in {expr}
call({func}, {arglist} [, {dict}])
any call {func} with arguments {arglist}
ceil({expr}) Float round {expr} up
@@ -80,7 +82,7 @@ chansend({id}, {data}) Number Writes {data} to channel
char2nr({expr} [, {utf8}]) Number ASCII/UTF-8 value of first char in {expr}
charclass({string}) Number character class of {string}
charcol({expr} [, {winid}]) Number column number of cursor or mark
-charidx({string}, {idx} [, {countcc}])
+charidx({string}, {idx} [, {countcc} [, {utf16}]])
Number char index of byte {idx} in {string}
chdir({dir}) String change current working directory
cindent({lnum}) Number C indent for line {lnum}
@@ -501,6 +503,8 @@ strptime({format}, {timestring})
strridx({haystack}, {needle} [, {start}])
Number last index of {needle} in {haystack}
strtrans({expr}) String translate string to make it printable
+strutf16len({string} [, {countcc}])
+ Number number of UTF-16 code units in {string}
strwidth({expr}) Number display cell length of the String {expr}
submatch({nr} [, {list}]) String or List
specific match in ":s" or substitute()
@@ -545,8 +549,11 @@ undofile({name}) String undo file name for {name}
undotree() List undo file tree
uniq({list} [, {func} [, {dict}]])
List remove adjacent duplicates from a list
+utf16idx({string}, {idx} [, {countcc} [, {charidx}]])
+ Number UTF-16 index of byte {idx} in {string}
values({dict}) List values in {dict}
-virtcol({expr}) Number screen column of cursor or mark
+virtcol({expr} [, {list}]) Number or List
+ screen column of cursor or mark
virtcol2col({winid}, {lnum}, {col})
Number byte index of a character on screen
visualmode([expr]) String last visual mode used
@@ -637,6 +644,7 @@ add({object}, {expr}) *add()*
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.
+ Also see `or()` and `xor()`.
Example: >
:let flag = and(bits, 0x80)
< Can also be used as a |method|: >
@@ -980,7 +988,7 @@ byte2line({byte}) *byte2line()*
Can also be used as a |method|: >
GetOffset()->byte2line()
-byteidx({expr}, {nr}) *byteidx()*
+byteidx({expr}, {nr} [, {utf16}]) *byteidx()*
Return byte index of the {nr}th character in the String
{expr}. Use zero for the first character, it then returns
zero.
@@ -990,6 +998,13 @@ byteidx({expr}, {nr}) *byteidx()*
length is added to the preceding base character. See
|byteidxcomp()| below for counting composing characters
separately.
+ When {utf16} is present and TRUE, {nr} is used as the UTF-16
+ index in the String {expr} instead of as the character index.
+ The UTF-16 index is the index in the string when it is encoded
+ with 16-bit words. If the specified UTF-16 index is in the
+ middle of a character (e.g. in a 4-byte character), then the
+ byte index of the first byte in the character is returned.
+ Refer to |string-offset-encoding| for more information.
Example : >
echo matchstr(str, ".", byteidx(str, 3))
< will display the fourth character. Another way to do the
@@ -1001,11 +1016,17 @@ byteidx({expr}, {nr}) *byteidx()*
If there are less than {nr} characters -1 is returned.
If there are exactly {nr} characters the length of the string
in bytes is returned.
-
+ See |charidx()| and |utf16idx()| for getting the character and
+ UTF-16 index respectively from the byte index.
+ Examples: >
+ echo byteidx('a😊😊', 2) returns 5
+ echo byteidx('a😊😊', 2, 1) returns 1
+ echo byteidx('a😊😊', 3, 1) returns 5
+<
Can also be used as a |method|: >
GetName()->byteidx(idx)
-byteidxcomp({expr}, {nr}) *byteidxcomp()*
+byteidxcomp({expr}, {nr} [, {utf16}]) *byteidxcomp()*
Like byteidx(), except that a composing character is counted
as a separate character. Example: >
let s = 'e' .. nr2char(0x301)
@@ -1129,27 +1150,36 @@ charcol({expr} [, {winid}]) *charcol()*
GetPos()->col()
<
*charidx()*
-charidx({string}, {idx} [, {countcc}])
+charidx({string}, {idx} [, {countcc} [, {utf16}]])
Return the character index of the byte at {idx} in {string}.
The index of the first character is zero.
If there are no multibyte characters the returned value is
equal to {idx}.
+
When {countcc} is omitted or |FALSE|, then composing characters
- are not counted separately, their byte length is
- added to the preceding base character.
+ are not counted separately, their byte length is added to the
+ preceding base character.
When {countcc} is |TRUE|, then composing characters are
counted as separate characters.
+
+ When {utf16} is present and TRUE, {idx} is used as the UTF-16
+ index in the String {expr} instead of as the byte index.
+
Returns -1 if the arguments are invalid or if {idx} is greater
than the index of the last byte in {string}. An error is
given if the first argument is not a string, the second
argument is not a number or when the third argument is present
and is not zero or one.
+
See |byteidx()| and |byteidxcomp()| for getting the byte index
- from the character index.
+ from the character index and |utf16idx()| for getting the
+ UTF-16 index from the character index.
+ Refer to |string-offset-encoding| for more information.
Examples: >
echo charidx('áb́ć', 3) returns 1
echo charidx('áb́ć', 6, 1) returns 4
echo charidx('áb́ć', 16) returns -1
+ echo charidx('a😊😊', 4, 0, 1) returns 2
<
Can also be used as a |method|: >
GetName()->charidx(idx)
@@ -1969,7 +1999,7 @@ exists({expr}) The result is a Number, which is |TRUE| if {expr} is
Can also be used as a |method|: >
Varname()->exists()
-exp({expr}) *exp()*
+exp({expr}) *exp()*
Return the exponential of {expr} as a |Float| in the range
[0, inf].
{expr} must evaluate to a |Float| or a |Number|.
@@ -2525,7 +2555,7 @@ funcref({name} [, {arglist}] [, {dict}])
Can also be used as a |method|: >
GetFuncname()->funcref([arg])
<
- *function()* *partial* *E700* *E922* *E923*
+ *function()* *partial* *E700* *E923*
function({name} [, {arglist}] [, {dict}])
Return a |Funcref| variable that refers to function {name}.
{name} can be the name of a user defined function or an
@@ -8330,6 +8360,28 @@ strtrans({string}) *strtrans()*
Can also be used as a |method|: >
GetString()->strtrans()
+strutf16len({string} [, {countcc}]) *strutf16len()*
+ The result is a Number, which is the number of UTF-16 code
+ units in String {string} (after converting it to UTF-16).
+
+ When {countcc} is TRUE, composing characters are counted
+ separately.
+ When {countcc} is omitted or FALSE, composing characters are
+ ignored.
+
+ Returns zero on error.
+
+ Also see |strlen()| and |strcharlen()|.
+ Examples: >
+ echo strutf16len('a') returns 1
+ echo strutf16len('©') returns 1
+ echo strutf16len('😊') returns 2
+ echo strutf16len('ą́') returns 1
+ echo strutf16len('ą́', v:true) returns 3
+
+ Can also be used as a |method|: >
+ GetText()->strutf16len()
+<
strwidth({string}) *strwidth()*
The result is a Number, which is the number of display cells
String {string} occupies. A Tab character is counted as one
@@ -9061,6 +9113,34 @@ uniq({list} [, {func} [, {dict}]]) *uniq()* *E882*
Can also be used as a |method|: >
mylist->uniq()
+<
+ *utf16idx()*
+utf16idx({string}, {idx} [, {countcc} [, {charidx}]])
+ Same as |charidx()| but returns the UTF-16 index of the byte
+ at {idx} in {string} (after converting it to UTF-16).
+
+ When {charidx} is present and TRUE, {idx} is used as the
+ character index in the String {string} instead of as the byte
+ index.
+ An {idx} in the middle of a UTF-8 sequence is rounded upwards
+ to the end of that sequence.
+
+ See |byteidx()| and |byteidxcomp()| for getting the byte index
+ from the UTF-16 index and |charidx()| for getting the
+ character index from the UTF-16 index.
+ Refer to |string-offset-encoding| for more information.
+ Examples: >
+ echo utf16idx('a😊😊', 3) returns 2
+ echo utf16idx('a😊😊', 7) returns 4
+ echo utf16idx('a😊😊', 1, 0, 1) returns 2
+ echo utf16idx('a😊😊', 2, 0, 1) returns 4
+ echo utf16idx('aą́c', 6) returns 2
+ echo utf16idx('aą́c', 6, 1) returns 4
+ echo utf16idx('a😊😊', 9) returns -1
+<
+ Can also be used as a |method|: >
+ GetName()->utf16idx(idx)
+
values({dict}) *values()*
Return a |List| with all the values of {dict}. The |List| is
@@ -9070,7 +9150,7 @@ values({dict}) *values()*
Can also be used as a |method|: >
mydict->values()
-virtcol({expr}) *virtcol()*
+virtcol({expr} [, {list}]) *virtcol()*
The result is a Number, which is the screen column of the file
position given with {expr}. That is, the last screen position
occupied by the character at that position, when the screen
@@ -9079,13 +9159,17 @@ virtcol({expr}) *virtcol()*
the <Tab>. For example, for a <Tab> in column 1, with 'ts'
set to 8, it returns 8. |conceal| is ignored.
For the byte position use |col()|.
+
For the use of {expr} see |col()|.
- When 'virtualedit' is used {expr} can be [lnum, col, off], where
- "off" is the offset in screen columns from the start of the
- character. E.g., a position within a <Tab> or after the last
- character. When "off" is omitted zero is used.
- When Virtual editing is active in the current mode, a position
- beyond the end of the line can be returned. |'virtualedit'|
+
+ When 'virtualedit' is used {expr} can be [lnum, col, off],
+ where "off" is the offset in screen columns from the start of
+ the character. E.g., a position within a <Tab> or after the
+ last character. When "off" is omitted zero is used. When
+ Virtual editing is active in the current mode, a position
+ beyond the end of the line can be returned. Also see
+ |'virtualedit'|
+
The accepted positions are:
. the cursor position
$ the end of the cursor line (the result is the
@@ -9097,11 +9181,22 @@ virtcol({expr}) *virtcol()*
cursor is the end). When not in Visual mode
returns the cursor position. Differs from |'<| in
that it's updated right away.
+
+ If {list} is present and non-zero then virtcol() returns a List
+ with the first and last screen position occupied by the
+ character.
+
Note that only marks in the current file can be used.
Examples: >
- virtcol(".") with text "foo^Lbar", with cursor on the "^L", returns 5
- virtcol("$") with text "foo^Lbar", returns 9
- virtcol("'t") with text " there", with 't at 'h', returns 6
+ " With text "foo^Lbar" and cursor on the "^L":
+
+ virtcol(".") " returns 5
+ virtcol(".", 1) " returns [4, 5]
+ virtcol("$") " returns 9
+
+ " With text " there", with 't at 'h':
+
+ virtcol("'t") " returns 6
< The first column is 1. 0 is returned for an error.
A more advanced example that echoes the maximum length of
all lines: >
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index f80ca5346c..0c18fd5b4e 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -1433,6 +1433,32 @@ Examples: >
echo $"The square root of {{9}} is {sqrt(9)}"
< The square root of {9} is 3.0 ~
+ *string-offset-encoding*
+A string consists of multiple characters. UTF-8 uses one byte for ASCII
+characters, two bytes for other latin characters and more bytes for other
+characters.
+
+A string offset can count characters or bytes. Other programs may use
+UTF-16 encoding (16-bit words) and an offset of UTF-16 words. Some functions
+use byte offsets, usually for UTF-8 encoding. Other functions use character
+offsets, in which case the encoding doesn't matter.
+
+The different offsets for the string "a©😊" are below:
+
+ UTF-8 offsets:
+ [0]: 61, [1]: C2, [2]: A9, [3]: F0, [4]: 9F, [5]: 98, [6]: 8A
+ UTF-16 offsets:
+ [0]: 0061, [1]: 00A9, [2]: D83D, [3]: DE0A
+ UTF-32 (character) offsets:
+ [0]: 00000061, [1]: 000000A9, [2]: 0001F60A
+
+You can use the "g8" and "ga" commands on a character to see the
+decimal/hex/octal values.
+
+The functions |byteidx()|, |utf16idx()| and |charidx()| can be used to convert
+between these indices. The functions |strlen()|, |strutf16len()| and
+|strcharlen()| return the number of bytes, UTF-16 code units and characters in
+a string respectively.
------------------------------------------------------------------------------
option *expr-option* *E112* *E113*
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index 8e68e9a792..9d4272c906 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -1404,6 +1404,23 @@ inspect({object}, {options}) *vim.inspect()*
• https://github.com/kikito/inspect.lua
• https://github.com/mpeterv/vinspect
+keycode({str}) *vim.keycode()*
+ Translate keycodes.
+
+ Example: >lua
+ local k = vim.keycode
+ vim.g.mapleader = k'<bs>'
+<
+
+ Parameters: ~
+ • {str} string String to be converted.
+
+ Return: ~
+ string
+
+ See also: ~
+ • |nvim_replace_termcodes()|
+
lua_omnifunc({find_start}, {_}) *vim.lua_omnifunc()*
Omnifunc for completing lua values from from the runtime lua interpreter,
similar to the builtin completion for the `:lua` command.
@@ -1653,26 +1670,6 @@ endswith({s}, {suffix}) *vim.endswith()*
Return: ~
(boolean) `true` if `suffix` is a suffix of `s`
-filter({f}, {src}, {...}) *vim.filter()*
- Filter a table or iterator.
-
- This is a convenience function that performs: >lua
-
- vim.iter(src):filter(f):totable()
-<
-
- Parameters: ~
- • {f} function(...):bool Filter function. Accepts the current
- iterator or table values as arguments and returns true if those
- values should be kept in the final table
- • {src} table|function Table or iterator function to filter
-
- Return: ~
- (table)
-
- See also: ~
- • |Iter:filter()|
-
gsplit({s}, {sep}, {opts}) *vim.gsplit()*
Splits a string at each instance of a separator.
@@ -1718,64 +1715,6 @@ is_callable({f}) *vim.is_callable()*
Return: ~
(boolean) `true` if `f` is callable, else `false`
-iter({src}, {...}) *vim.iter()*
- Create an Iter |lua-iter| object from a table or iterator.
-
- The input value can be a table or a function iterator (see |luaref-in|).
-
- This function wraps the input value into an interface which allows
- chaining multiple pipeline stages in an efficient manner. Each pipeline
- stage receives as input the output values from the prior stage. The values
- used in the first stage of the pipeline depend on the type passed to this
- function:
-
- • List tables pass only the value of each element
- • Non-list tables pass both the key and value of each element
- • Function iterators pass all of the values returned by their respective
- function
-
- Examples: >lua
-
- local it = vim.iter({ 1, 2, 3, 4, 5 })
- it:map(function(v)
- return v * 3
- end)
- it:rev()
- it:skip(2)
- it:totable()
- -- { 9, 6, 3 }
-
- vim.iter(ipairs({ 1, 2, 3, 4, 5 })):map(function(i, v)
- if i > 2 then return v end
- end):totable()
- -- { 3, 4, 5 }
-
- local it = vim.iter(vim.gsplit('1,2,3,4,5', ','))
- it:map(function(s) return tonumber(s) end)
- for i, d in it:enumerate() do
- print(string.format("Column %d is %d", i, d))
- end
- -- Column 1 is 1
- -- Column 2 is 2
- -- Column 3 is 3
- -- Column 4 is 4
- -- Column 5 is 5
-
- vim.iter({ a = 1, b = 2, c = 3, z = 26 }):any(function(k, v)
- return k == 'z'
- end)
- -- true
-<
-
- Parameters: ~
- • {src} table|function Table or iterator.
-
- Return: ~
- Iter |lua-iter|
-
- See also: ~
- • |lua-iter|
-
list_contains({t}, {value}) *vim.list_contains()*
Checks if a list-like table (integer keys without gaps) contains `value`.
@@ -1818,26 +1757,6 @@ list_slice({list}, {start}, {finish}) *vim.list_slice()*
Return: ~
(list) Copy of table sliced from start to finish (inclusive)
-map({f}, {src}, {...}) *vim.map()*
- Map and filter a table or iterator.
-
- This is a convenience function that performs: >lua
-
- vim.iter(src):map(f):totable()
-<
-
- Parameters: ~
- • {f} function(...):?any Map function. Accepts the current iterator
- or table values as arguments and returns one or more new
- values. Nil values are removed from the final table.
- • {src} table|function Table or iterator function to filter
-
- Return: ~
- (table)
-
- See also: ~
- • |Iter:map()|
-
pesc({s}) *vim.pesc()*
Escapes magic chars in |lua-patterns|.
@@ -2099,20 +2018,6 @@ tbl_values({t}) *vim.tbl_values()*
Return: ~
(list) List of values
-totable({f}, {...}) *vim.totable()*
- Collect an iterator into a table.
-
- This is a convenience function that performs: >lua
-
- vim.iter(f):totable()
-<
-
- Parameters: ~
- • {f} (function) Iterator function
-
- Return: ~
- (table)
-
trim({s}) *vim.trim()*
Trim whitespace (Lua pattern "%s") from both sides of a string.
@@ -2933,6 +2838,79 @@ range({spec}) *vim.version.range()*
==============================================================================
Lua module: iter *lua-iter*
+
+The *vim.iter* module provides a generic "iterator" interface over tables
+and iterator functions.
+
+*vim.iter()* wraps its table or function argument into an *Iter* object
+with methods (such as |Iter:filter()| and |Iter:map()|) that transform the
+underlying source data. These methods can be chained together to create
+iterator "pipelines". Each pipeline stage receives as input the output
+values from the prior stage. The values used in the first stage of the
+pipeline depend on the type passed to this function:
+
+• List tables pass only the value of each element
+• Non-list tables pass both the key and value of each element
+• Function iterators pass all of the values returned by their respective
+ function
+
+Examples: >lua
+
+ local it = vim.iter({ 1, 2, 3, 4, 5 })
+ it:map(function(v)
+ return v * 3
+ end)
+ it:rev()
+ it:skip(2)
+ it:totable()
+ -- { 9, 6, 3 }
+
+ vim.iter(ipairs({ 1, 2, 3, 4, 5 })):map(function(i, v)
+ if i > 2 then return v end
+ end):totable()
+ -- { 3, 4, 5 }
+
+ local it = vim.iter(vim.gsplit('1,2,3,4,5', ','))
+ it:map(function(s) return tonumber(s) end)
+ for i, d in it:enumerate() do
+ print(string.format("Column %d is %d", i, d))
+ end
+ -- Column 1 is 1
+ -- Column 2 is 2
+ -- Column 3 is 3
+ -- Column 4 is 4
+ -- Column 5 is 5
+
+ vim.iter({ a = 1, b = 2, c = 3, z = 26 }):any(function(k, v)
+ return k == 'z'
+ end)
+ -- true
+
+<
+
+In addition to the |vim.iter()| function, the |vim.iter| module provides
+convenience functions like |vim.iter.filter()| and |vim.iter.totable()|.
+
+filter({f}, {src}, {...}) *vim.iter.filter()*
+ Filter a table or iterator.
+
+ This is a convenience function that performs: >lua
+
+ vim.iter(src):filter(f):totable()
+<
+
+ Parameters: ~
+ • {f} function(...):bool Filter function. Accepts the current
+ iterator or table values as arguments and returns true if those
+ values should be kept in the final table
+ • {src} table|function Table or iterator function to filter
+
+ Return: ~
+ (table)
+
+ See also: ~
+ • |Iter:filter()|
+
Iter:all({self}, {pred}) *Iter:all()*
Return true if all of the items in the iterator match the given predicate.
@@ -2971,9 +2949,7 @@ Iter:enumerate({self}) *Iter:enumerate()*
vim.iter(ipairs(t))
<
- over
-
- >lua
+ over >lua
vim.iter(t):enumerate()
<
@@ -3331,16 +3307,38 @@ Iter:totable({self}) *Iter:totable()*
Return: ~
(table)
-new({src}, {...}) *new()*
- Create a new Iter object from a table or iterator.
+map({f}, {src}, {...}) *vim.iter.map()*
+ Map and filter a table or iterator.
+
+ This is a convenience function that performs: >lua
+
+ vim.iter(src):map(f):totable()
+<
Parameters: ~
- • {src} table|function Table or iterator to drain values from
+ • {f} function(...):?any Map function. Accepts the current iterator
+ or table values as arguments and returns one or more new
+ values. Nil values are removed from the final table.
+ • {src} table|function Table or iterator function to filter
Return: ~
- Iter
+ (table)
-next() *next()*
- TODO: Documentation
+ See also: ~
+ • |Iter:map()|
+
+totable({f}, {...}) *vim.iter.totable()*
+ Collect an iterator into a table.
+
+ This is a convenience function that performs: >lua
+
+ vim.iter(f):totable()
+<
+
+ Parameters: ~
+ • {f} (function) Iterator function
+
+ Return: ~
+ (table)
vim:tw=78:ts=8:sw=4:sts=4:et:ft=help:norl:
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index 3f07dd2e66..2a776ea30a 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -37,6 +37,7 @@ The following new APIs or features were added.
• |vim.iter()| provides a generic iterator interface for tables and Lua
iterators |luaref-in|.
+• Added |vim.keycode()| for translating keycodes in a string.
==============================================================================
CHANGED FEATURES *news-changed*
diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt
index 89111535ca..8e1b72eadc 100644
--- a/runtime/doc/usr_41.txt
+++ b/runtime/doc/usr_41.txt
@@ -621,6 +621,7 @@ String manipulation: *string-functions*
strlen() length of a string in bytes
strcharlen() length of a string in characters
strchars() number of characters in a string
+ strutf16len() number of UTF-16 code units in a string
strwidth() size of string when displayed
strdisplaywidth() size of string when displayed, deals with tabs
setcellwidths() set character cell width overrides
@@ -636,6 +637,7 @@ String manipulation: *string-functions*
byteidx() byte index of a character in a string
byteidxcomp() like byteidx() but count composing characters
charidx() character index of a byte in a string
+ utf16idx() UTF-16 index of a byte in a string
repeat() repeat a string multiple times
eval() evaluate a string expression
execute() execute an Ex command and get the output
diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua
index c922ec93db..20e813d77c 100644
--- a/runtime/lua/vim/_editor.lua
+++ b/runtime/lua/vim/_editor.lua
@@ -829,6 +829,20 @@ function vim.print(...)
return ...
end
+--- Translate keycodes.
+---
+--- Example:
+--- <pre>lua
+--- local k = vim.keycode
+--- vim.g.mapleader = k'<bs>'
+--- </pre>
+--- @param str string String to be converted.
+--- @return string
+--- @see |nvim_replace_termcodes()|
+function vim.keycode(str)
+ return vim.api.nvim_replace_termcodes(str, true, true, true)
+end
+
function vim._cs_remote(rcid, server_addr, connect_error, args)
local function connection_failure_errmsg(consequence)
local explanation
diff --git a/runtime/lua/vim/_init_packages.lua b/runtime/lua/vim/_init_packages.lua
index 2cf2e91a8c..5db258c011 100644
--- a/runtime/lua/vim/_init_packages.lua
+++ b/runtime/lua/vim/_init_packages.lua
@@ -55,6 +55,7 @@ vim._submodules = {
inspect = true,
version = true,
fs = true,
+ iter = true,
}
-- These are for loading runtime modules in the vim namespace lazily.
diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua
index 2545853b41..c2e2c5bd9f 100644
--- a/runtime/lua/vim/iter.lua
+++ b/runtime/lua/vim/iter.lua
@@ -1,4 +1,56 @@
---- Iterator implementation.
+---@defgroup lua-iter
+---
+--- The \*vim.iter\* module provides a generic "iterator" interface over tables and iterator
+--- functions.
+---
+--- \*vim.iter()\* wraps its table or function argument into an \*Iter\* object with methods (such
+--- as |Iter:filter()| and |Iter:map()|) that transform the underlying source data. These methods
+--- can be chained together to create iterator "pipelines". Each pipeline stage receives as input
+--- the output values from the prior stage. The values used in the first stage of the pipeline
+--- depend on the type passed to this function:
+---
+--- - List tables pass only the value of each element
+--- - Non-list tables pass both the key and value of each element
+--- - Function iterators pass all of the values returned by their respective
+--- function
+---
+--- Examples:
+--- <pre>lua
+--- local it = vim.iter({ 1, 2, 3, 4, 5 })
+--- it:map(function(v)
+--- return v * 3
+--- end)
+--- it:rev()
+--- it:skip(2)
+--- it:totable()
+--- -- { 9, 6, 3 }
+---
+--- vim.iter(ipairs({ 1, 2, 3, 4, 5 })):map(function(i, v)
+--- if i > 2 then return v end
+--- end):totable()
+--- -- { 3, 4, 5 }
+---
+--- local it = vim.iter(vim.gsplit('1,2,3,4,5', ','))
+--- it:map(function(s) return tonumber(s) end)
+--- for i, d in it:enumerate() do
+--- print(string.format("Column %d is %d", i, d))
+--- end
+--- -- Column 1 is 1
+--- -- Column 2 is 2
+--- -- Column 3 is 3
+--- -- Column 4 is 4
+--- -- Column 5 is 5
+---
+--- vim.iter({ a = 1, b = 2, c = 3, z = 26 }):any(function(k, v)
+--- return k == 'z'
+--- end)
+--- -- true
+--- </pre>
+---
+--- In addition to the |vim.iter()| function, the |vim.iter| module provides convenience functions
+--- like |vim.iter.filter()| and |vim.iter.totable()|.
+
+local M = {}
---@class Iter
local Iter = {}
@@ -733,7 +785,6 @@ end
--- </pre>
---
--- over
----
--- <pre>lua
--- vim.iter(t):enumerate()
--- </pre>
@@ -776,6 +827,7 @@ end
---
---@param src table|function Table or iterator to drain values from
---@return Iter
+---@private
function Iter.new(src, ...)
local it = {}
if type(src) == 'table' then
@@ -807,6 +859,7 @@ function Iter.new(src, ...)
end
end
+ ---@private
function it.next()
return fn(src(s, var))
end
@@ -832,4 +885,57 @@ function ListIter.new(t)
return it
end
-return Iter
+--- Collect an iterator into a table.
+---
+--- This is a convenience function that performs:
+--- <pre>lua
+--- vim.iter(f):totable()
+--- </pre>
+---
+---@param f function Iterator function
+---@return table
+function M.totable(f, ...)
+ return Iter.new(f, ...):totable()
+end
+
+--- Filter a table or iterator.
+---
+--- This is a convenience function that performs:
+--- <pre>lua
+--- vim.iter(src):filter(f):totable()
+--- </pre>
+---
+---@see |Iter:filter()|
+---
+---@param f function(...):bool Filter function. Accepts the current iterator or table values as
+--- arguments and returns true if those values should be kept in the
+--- final table
+---@param src table|function Table or iterator function to filter
+---@return table
+function M.filter(f, src, ...)
+ return Iter.new(src, ...):filter(f):totable()
+end
+
+--- Map and filter a table or iterator.
+---
+--- This is a convenience function that performs:
+--- <pre>lua
+--- vim.iter(src):map(f):totable()
+--- </pre>
+---
+---@see |Iter:map()|
+---
+---@param f function(...):?any Map function. Accepts the current iterator or table values as
+--- arguments and returns one or more new values. Nil values are removed
+--- from the final table.
+---@param src table|function Table or iterator function to filter
+---@return table
+function M.map(f, src, ...)
+ return Iter.new(src, ...):map(f):totable()
+end
+
+return setmetatable(M, {
+ __call = function(_, ...)
+ return Iter.new(...)
+ end,
+})
diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua
index 5f4ea8822b..a55deb1415 100644
--- a/runtime/lua/vim/shared.lua
+++ b/runtime/lua/vim/shared.lua
@@ -881,109 +881,4 @@ function vim.defaulttable(create)
})
end
---- Create an Iter |lua-iter| object from a table or iterator.
----
---- The input value can be a table or a function iterator (see |luaref-in|).
----
---- This function wraps the input value into an interface which allows chaining
---- multiple pipeline stages in an efficient manner. Each pipeline stage
---- receives as input the output values from the prior stage. The values used in
---- the first stage of the pipeline depend on the type passed to this function:
----
---- - List tables pass only the value of each element
---- - Non-list tables pass both the key and value of each element
---- - Function iterators pass all of the values returned by their respective
---- function
----
---- Examples:
---- <pre>lua
---- local it = vim.iter({ 1, 2, 3, 4, 5 })
---- it:map(function(v)
---- return v * 3
---- end)
---- it:rev()
---- it:skip(2)
---- it:totable()
---- -- { 9, 6, 3 }
----
---- vim.iter(ipairs({ 1, 2, 3, 4, 5 })):map(function(i, v)
---- if i > 2 then return v end
---- end):totable()
---- -- { 3, 4, 5 }
----
---- local it = vim.iter(vim.gsplit('1,2,3,4,5', ','))
---- it:map(function(s) return tonumber(s) end)
---- for i, d in it:enumerate() do
---- print(string.format("Column %d is %d", i, d))
---- end
---- -- Column 1 is 1
---- -- Column 2 is 2
---- -- Column 3 is 3
---- -- Column 4 is 4
---- -- Column 5 is 5
----
---- vim.iter({ a = 1, b = 2, c = 3, z = 26 }):any(function(k, v)
---- return k == 'z'
---- end)
---- -- true
---- </pre>
----
----@see |lua-iter|
----
----@param src table|function Table or iterator.
----@return Iter @|lua-iter|
-function vim.iter(src, ...)
- local Iter = require('vim.iter')
- return Iter.new(src, ...)
-end
-
---- Collect an iterator into a table.
----
---- This is a convenience function that performs:
---- <pre>lua
---- vim.iter(f):totable()
---- </pre>
----
----@param f function Iterator function
----@return table
-function vim.totable(f, ...)
- return vim.iter(f, ...):totable()
-end
-
---- Filter a table or iterator.
----
---- This is a convenience function that performs:
---- <pre>lua
---- vim.iter(src):filter(f):totable()
---- </pre>
----
----@see |Iter:filter()|
----
----@param f function(...):bool Filter function. Accepts the current iterator or table values as
---- arguments and returns true if those values should be kept in the
---- final table
----@param src table|function Table or iterator function to filter
----@return table
-function vim.filter(f, src, ...)
- return vim.iter(src, ...):filter(f):totable()
-end
-
---- Map and filter a table or iterator.
----
---- This is a convenience function that performs:
---- <pre>lua
---- vim.iter(src):map(f):totable()
---- </pre>
----
----@see |Iter:map()|
----
----@param f function(...):?any Map function. Accepts the current iterator or table values as
---- arguments and returns one or more new values. Nil values are removed
---- from the final table.
----@param src table|function Table or iterator function to filter
----@return table
-function vim.map(f, src, ...)
- return vim.iter(src, ...):map(f):totable()
-end
-
return vim
diff --git a/scripts/gen_vimdoc.py b/scripts/gen_vimdoc.py
index 52d03d9746..d0686e92bc 100755
--- a/scripts/gen_vimdoc.py
+++ b/scripts/gen_vimdoc.py
@@ -188,7 +188,7 @@ CONFIG = {
f'*vim.{name}()*'
if fstem.lower() == '_editor'
else f'*{name}()*'
- if fstem in ('iter.lua')
+ if name[0].isupper()
else f'*{fstem}.{name}()*'),
'module_override': {
# `shared` functions are exposed on the `vim` module.
@@ -202,6 +202,7 @@ CONFIG = {
'fs': 'vim.fs',
'secure': 'vim.secure',
'version': 'vim.version',
+ 'iter': 'vim.iter',
},
'append_only': [
'shared.lua',
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt
index 28ba4e2ed3..28ba4e2ed3 100755..100644
--- a/src/nvim/CMakeLists.txt
+++ b/src/nvim/CMakeLists.txt
diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c
index a400be6039..5f135ff7b0 100644
--- a/src/nvim/cmdexpand.c
+++ b/src/nvim/cmdexpand.c
@@ -3466,8 +3466,7 @@ void f_getcompletion(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
int options = WILD_SILENT | WILD_USE_NL | WILD_ADD_SLASH
| WILD_NO_BEEP | WILD_HOME_REPLACE;
- if (argvars[1].v_type != VAR_STRING) {
- semsg(_(e_invarg2), "type must be a string");
+ if (tv_check_for_string_arg(argvars, 1) == FAIL) {
return;
}
const char *const type = tv_get_string(&argvars[1]);
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 35738cdfa9..b3618c1811 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -5357,8 +5357,7 @@ void common_function(typval_T *argvars, typval_T *rettv, bool is_funcref)
arg_idx = 1;
}
if (dict_idx > 0) {
- if (argvars[dict_idx].v_type != VAR_DICT) {
- emsg(_("E922: expected a dict"));
+ if (tv_check_for_dict_arg(argvars, dict_idx) == FAIL) {
xfree(name);
goto theend;
}
@@ -6003,7 +6002,7 @@ void add_timer_info_all(typval_T *rettv)
tv_list_alloc_ret(rettv, map_size(&timers));
timer_T *timer;
map_foreach_value(&timers, timer, {
- if (!timer->stopped) {
+ if (!timer->stopped || timer->refcount > 1) {
add_timer_info(rettv, timer);
}
})
@@ -8726,7 +8725,7 @@ int typval_compare(typval_T *typ1, typval_T *typ2, exprtype_T type, bool ic)
const bool type_is = type == EXPR_IS || type == EXPR_ISNOT;
if (type_is && typ1->v_type != typ2->v_type) {
- // For "is" a different type always means false, for "notis"
+ // For "is" a different type always means false, for "isnot"
// it means true.
n1 = type == EXPR_ISNOT;
} else if (typ1->v_type == VAR_BLOB || typ2->v_type == VAR_BLOB) {
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index 5babfa4809..09705148d0 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -65,8 +65,8 @@ return {
bufwinid={args=1, base=1},
bufwinnr={args=1, base=1},
byte2line={args=1, base=1},
- byteidx={args=2, base=1, fast=true},
- byteidxcomp={args=2, base=1, fast=true},
+ byteidx={args={2, 3}, base=1, fast=true},
+ byteidxcomp={args={2, 3}, base=1, fast=true},
call={args={2, 3}, base=1},
ceil={args=1, base=1, float_func="ceil"},
changenr={},
@@ -75,7 +75,7 @@ return {
char2nr={args={1, 2}, base=1, fast=true},
charclass={args=1, base=1},
charcol={args={1, 2}, base=1},
- charidx={args={2, 3}, base=1},
+ charidx={args={2, 4}, base=1},
chdir={args=1, base=1},
cindent={args=1, base=1},
clearmatches={args={0, 1}, base=1},
@@ -397,6 +397,7 @@ return {
strptime={args=2, base=1},
strridx={args={2, 3}, base=1},
strtrans={args=1, base=1, fast=true},
+ strutf16len={args={1, 2}, base=1},
strwidth={args=1, base=1, fast=true},
submatch={args={1, 2}, base=1},
substitute={args=4, base=1},
@@ -435,8 +436,9 @@ return {
undofile={args=1, base=1},
undotree={},
uniq={args={1, 3}, base=1},
+ utf16idx={args={2, 4}, base=1},
values={args=1, base=1},
- virtcol={args=1, base=1},
+ virtcol={args={1, 2}, base=1},
virtcol2col={args=3, base=1},
visualmode={args={0, 1}},
wait={args={2,3}},
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index d903d498e7..5c9d39b91f 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -149,8 +149,6 @@ PRAGMA_DIAG_POP
static const char *e_listblobarg = N_("E899: Argument of %s must be a List or Blob");
static const char *e_invalwindow = N_("E957: Invalid window number");
static const char *e_reduceempty = N_("E998: Reduce of an empty %s with no initial value");
-static const char e_using_number_as_bool_nr[]
- = N_("E1023: Using a Number as a Bool: %d");
static const char e_missing_function_argument[]
= N_("E1132: Missing function argument");
@@ -529,41 +527,6 @@ static void f_byte2line(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
}
-static void byteidx(typval_T *argvars, typval_T *rettv, int comp)
-{
- const char *const str = tv_get_string_chk(&argvars[0]);
- varnumber_T idx = tv_get_number_chk(&argvars[1], NULL);
- rettv->vval.v_number = -1;
- if (str == NULL || idx < 0) {
- return;
- }
-
- const char *t = str;
- for (; idx > 0; idx--) {
- if (*t == NUL) { // EOL reached.
- return;
- }
- if (comp) {
- t += utf_ptr2len(t);
- } else {
- t += utfc_ptr2len(t);
- }
- }
- rettv->vval.v_number = (varnumber_T)(t - str);
-}
-
-/// "byteidx()" function
-static void f_byteidx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- byteidx(argvars, rettv, false);
-}
-
-/// "byteidxcomp()" function
-static void f_byteidxcomp(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- byteidx(argvars, rettv, true);
-}
-
/// "call(func, arglist [, dict])" function
static void f_call(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -597,8 +560,7 @@ static void f_call(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
dict_T *selfdict = NULL;
if (argvars[2].v_type != VAR_UNKNOWN) {
- if (argvars[2].v_type != VAR_DICT) {
- emsg(_(e_dictreq));
+ if (tv_check_for_dict_arg(argvars, 2) == FAIL) {
if (owned) {
func_unref(func);
}
@@ -789,53 +751,6 @@ static void f_charcol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
get_col(argvars, rettv, true);
}
-/// "charidx()" function
-static void f_charidx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- rettv->vval.v_number = -1;
-
- if (argvars[0].v_type != VAR_STRING
- || argvars[1].v_type != VAR_NUMBER
- || (argvars[2].v_type != VAR_UNKNOWN
- && argvars[2].v_type != VAR_NUMBER
- && argvars[2].v_type != VAR_BOOL)) {
- emsg(_(e_invarg));
- return;
- }
-
- const char *str = tv_get_string_chk(&argvars[0]);
- varnumber_T idx = tv_get_number_chk(&argvars[1], NULL);
- if (str == NULL || idx < 0) {
- return;
- }
- int countcc = 0;
- if (argvars[2].v_type != VAR_UNKNOWN) {
- countcc = (int)tv_get_number(&argvars[2]);
- }
- if (countcc < 0 || countcc > 1) {
- semsg(_(e_using_number_as_bool_nr), countcc);
- return;
- }
-
- int (*ptr2len)(const char *);
- if (countcc) {
- ptr2len = utf_ptr2len;
- } else {
- ptr2len = utfc_ptr2len;
- }
-
- const char *p;
- int len;
- for (p = str, len = 0; p <= str + idx; len++) {
- if (*p == NUL) {
- return;
- }
- p += ptr2len(p);
- }
-
- rettv->vval.v_number = len > 0 ? len - 1 : 0;
-}
-
/// "chdir(dir)" function
static void f_chdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -3115,14 +3030,12 @@ static void f_glob2regpat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// "gettext()" function
static void f_gettext(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- if (argvars[0].v_type != VAR_STRING
- || argvars[0].vval.v_string == NULL
- || *argvars[0].vval.v_string == NUL) {
- semsg(_(e_invarg2), tv_get_string(&argvars[0]));
- } else {
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = xstrdup(_(argvars[0].vval.v_string));
+ if (tv_check_for_nonempty_string_arg(argvars, 0) == FAIL) {
+ return;
}
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = xstrdup(_(argvars[0].vval.v_string));
}
/// "has()" function
@@ -3453,34 +3366,6 @@ static void f_hostname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
rettv->vval.v_string = xstrdup(hostname);
}
-/// iconv() function
-static void f_iconv(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- vimconv_T vimconv;
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
-
- const char *const str = tv_get_string(&argvars[0]);
- char buf1[NUMBUFLEN];
- char *const from = enc_canonize(enc_skip((char *)tv_get_string_buf(&argvars[1], buf1)));
- char buf2[NUMBUFLEN];
- char *const to = enc_canonize(enc_skip((char *)tv_get_string_buf(&argvars[2], buf2)));
- vimconv.vc_type = CONV_NONE;
- convert_setup(&vimconv, from, to);
-
- // If the encodings are equal, no conversion needed.
- if (vimconv.vc_type == CONV_NONE) {
- rettv->vval.v_string = xstrdup(str);
- } else {
- rettv->vval.v_string = string_convert(&vimconv, (char *)str, NULL);
- }
-
- convert_setup(&vimconv, NULL, NULL);
- xfree(from);
- xfree(to);
-}
-
/// "indent()" function
static void f_indent(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -7359,8 +7244,7 @@ static void f_setcharpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
static void f_setcharsearch(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- if (argvars[0].v_type != VAR_DICT) {
- emsg(_(e_dictreq));
+ if (tv_check_for_dict_arg(argvars, 0) == FAIL) {
return;
}
@@ -7631,8 +7515,7 @@ static void f_settagstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
// second argument: dict with items to set in the tag stack
- if (argvars[1].v_type != VAR_DICT) {
- emsg(_(e_dictreq));
+ if (tv_check_for_dict_arg(argvars, 1) == FAIL) {
return;
}
dict_T *d = argvars[1].vval.v_dict;
@@ -7644,7 +7527,9 @@ static void f_settagstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
// default is to replace the stack.
if (argvars[2].v_type == VAR_UNKNOWN) {
// action = 'r';
- } else if (argvars[2].v_type == VAR_STRING) {
+ } else if (tv_check_for_string_arg(argvars, 2) == FAIL) {
+ return;
+ } else {
const char *actstr;
actstr = tv_get_string_chk(&argvars[2]);
if (actstr == NULL) {
@@ -7657,9 +7542,6 @@ static void f_settagstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
semsg(_(e_invact2), actstr);
return;
}
- } else {
- emsg(_(e_stringreq));
- return;
}
if (set_tagstack(wp, d, action) == OK) {
@@ -8050,60 +7932,6 @@ static void f_str2float(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
rettv->v_type = VAR_FLOAT;
}
-/// "str2list()" function
-static void f_str2list(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- tv_list_alloc_ret(rettv, kListLenUnknown);
- const char *p = tv_get_string(&argvars[0]);
-
- for (; *p != NUL; p += utf_ptr2len(p)) {
- tv_list_append_number(rettv->vval.v_list, utf_ptr2char(p));
- }
-}
-
-/// "str2nr()" function
-static void f_str2nr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- int base = 10;
- int what = 0;
-
- if (argvars[1].v_type != VAR_UNKNOWN) {
- base = (int)tv_get_number(&argvars[1]);
- if (base != 2 && base != 8 && base != 10 && base != 16) {
- emsg(_(e_invarg));
- return;
- }
- if (argvars[2].v_type != VAR_UNKNOWN && tv_get_bool(&argvars[2])) {
- what |= STR2NR_QUOTE;
- }
- }
-
- char *p = skipwhite(tv_get_string(&argvars[0]));
- bool isneg = (*p == '-');
- if (*p == '+' || *p == '-') {
- p = skipwhite(p + 1);
- }
- switch (base) {
- case 2:
- what |= STR2NR_BIN | STR2NR_FORCE;
- break;
- case 8:
- what |= STR2NR_OCT | STR2NR_OOCT | STR2NR_FORCE;
- break;
- case 16:
- what |= STR2NR_HEX | STR2NR_FORCE;
- break;
- }
- varnumber_T n;
- vim_str2nr(p, NULL, NULL, what, &n, NULL, 0, false, NULL);
- // Text after the number is silently ignored.
- if (isneg) {
- rettv->vval.v_number = -n;
- } else {
- rettv->vval.v_number = n;
- }
-}
-
/// "strftime({format}[, {time}])" function
static void f_strftime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -8154,235 +7982,6 @@ static void f_strftime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
xfree(enc);
}
-/// "strgetchar()" function
-static void f_strgetchar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- rettv->vval.v_number = -1;
-
- const char *const str = tv_get_string_chk(&argvars[0]);
- if (str == NULL) {
- return;
- }
- bool error = false;
- varnumber_T charidx = tv_get_number_chk(&argvars[1], &error);
- if (error) {
- return;
- }
-
- const size_t len = strlen(str);
- size_t byteidx = 0;
-
- while (charidx >= 0 && byteidx < len) {
- if (charidx == 0) {
- rettv->vval.v_number = utf_ptr2char(str + byteidx);
- break;
- }
- charidx--;
- byteidx += (size_t)utf_ptr2len(str + byteidx);
- }
-}
-
-/// "stridx()" function
-static void f_stridx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- rettv->vval.v_number = -1;
-
- char buf[NUMBUFLEN];
- const char *const needle = tv_get_string_chk(&argvars[1]);
- const char *haystack = tv_get_string_buf_chk(&argvars[0], buf);
- const char *const haystack_start = haystack;
- if (needle == NULL || haystack == NULL) {
- return; // Type error; errmsg already given.
- }
-
- if (argvars[2].v_type != VAR_UNKNOWN) {
- bool error = false;
-
- const ptrdiff_t start_idx = (ptrdiff_t)tv_get_number_chk(&argvars[2],
- &error);
- if (error || start_idx >= (ptrdiff_t)strlen(haystack)) {
- return;
- }
- if (start_idx >= 0) {
- haystack += start_idx;
- }
- }
-
- const char *pos = strstr(haystack, needle);
- if (pos != NULL) {
- rettv->vval.v_number = (varnumber_T)(pos - haystack_start);
- }
-}
-
-/// "string()" function
-static void f_string(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = encode_tv2string(&argvars[0], NULL);
-}
-
-/// "strlen()" function
-static void f_strlen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- rettv->vval.v_number = (varnumber_T)strlen(tv_get_string(&argvars[0]));
-}
-
-static void strchar_common(typval_T *argvars, typval_T *rettv, bool skipcc)
-{
- const char *s = tv_get_string(&argvars[0]);
- varnumber_T len = 0;
- int (*func_mb_ptr2char_adv)(const char **pp);
-
- func_mb_ptr2char_adv = skipcc ? mb_ptr2char_adv : mb_cptr2char_adv;
- while (*s != NUL) {
- func_mb_ptr2char_adv(&s);
- len++;
- }
- rettv->vval.v_number = len;
-}
-
-/// "strcharlen()" function
-static void f_strcharlen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- strchar_common(argvars, rettv, true);
-}
-
-/// "strchars()" function
-static void f_strchars(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- int skipcc = false;
-
- if (argvars[1].v_type != VAR_UNKNOWN) {
- skipcc = (int)tv_get_bool(&argvars[1]);
- }
- if (skipcc < 0 || skipcc > 1) {
- semsg(_(e_using_number_as_bool_nr), skipcc);
- } else {
- strchar_common(argvars, rettv, skipcc);
- }
-}
-
-/// "strdisplaywidth()" function
-static void f_strdisplaywidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- const char *const s = tv_get_string(&argvars[0]);
- int col = 0;
-
- if (argvars[1].v_type != VAR_UNKNOWN) {
- col = (int)tv_get_number(&argvars[1]);
- }
-
- rettv->vval.v_number = (varnumber_T)(linetabsize_col(col, (char *)s) - col);
-}
-
-/// "strwidth()" function
-static void f_strwidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- const char *const s = tv_get_string(&argvars[0]);
-
- rettv->vval.v_number = (varnumber_T)mb_string2cells(s);
-}
-
-/// "strcharpart()" function
-static void f_strcharpart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- const char *const p = tv_get_string(&argvars[0]);
- const size_t slen = strlen(p);
-
- int nbyte = 0;
- bool error = false;
- varnumber_T nchar = tv_get_number_chk(&argvars[1], &error);
- if (!error) {
- if (nchar > 0) {
- while (nchar > 0 && (size_t)nbyte < slen) {
- nbyte += utf_ptr2len(p + nbyte);
- nchar--;
- }
- } else {
- nbyte = (int)nchar;
- }
- }
- int len = 0;
- if (argvars[2].v_type != VAR_UNKNOWN) {
- int charlen = (int)tv_get_number(&argvars[2]);
- while (charlen > 0 && nbyte + len < (int)slen) {
- int off = nbyte + len;
-
- if (off < 0) {
- len += 1;
- } else {
- len += utf_ptr2len(p + off);
- }
- charlen--;
- }
- } else {
- len = (int)slen - nbyte; // default: all bytes that are available.
- }
-
- // Only return the overlap between the specified part and the actual
- // string.
- if (nbyte < 0) {
- len += nbyte;
- nbyte = 0;
- } else if ((size_t)nbyte > slen) {
- nbyte = (int)slen;
- }
- if (len < 0) {
- len = 0;
- } else if (nbyte + len > (int)slen) {
- len = (int)slen - nbyte;
- }
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = xstrndup(p + nbyte, (size_t)len);
-}
-
-/// "strpart()" function
-static void f_strpart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- bool error = false;
-
- const char *const p = tv_get_string(&argvars[0]);
- const size_t slen = strlen(p);
-
- varnumber_T n = tv_get_number_chk(&argvars[1], &error);
- varnumber_T len;
- if (error) {
- len = 0;
- } else if (argvars[2].v_type != VAR_UNKNOWN) {
- len = tv_get_number(&argvars[2]);
- } else {
- len = (varnumber_T)slen - n; // Default len: all bytes that are available.
- }
-
- // Only return the overlap between the specified part and the actual
- // string.
- if (n < 0) {
- len += n;
- n = 0;
- } else if (n > (varnumber_T)slen) {
- n = (varnumber_T)slen;
- }
- if (len < 0) {
- len = 0;
- } else if (n + len > (varnumber_T)slen) {
- len = (varnumber_T)slen - n;
- }
-
- if (argvars[2].v_type != VAR_UNKNOWN && argvars[3].v_type != VAR_UNKNOWN) {
- int off;
-
- // length in characters
- for (off = (int)n; off < (int)slen && len > 0; len--) {
- off += utfc_ptr2len(p + off);
- }
- len = off - n;
- }
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = xmemdupz(p + n, (size_t)len);
-}
-
/// "strptime({format}, {timestring})" function
static void f_strptime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -8415,56 +8014,6 @@ static void f_strptime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
xfree(enc);
}
-/// "strridx()" function
-static void f_strridx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- char buf[NUMBUFLEN];
- const char *const needle = tv_get_string_chk(&argvars[1]);
- const char *const haystack = tv_get_string_buf_chk(&argvars[0], buf);
-
- rettv->vval.v_number = -1;
- if (needle == NULL || haystack == NULL) {
- return; // Type error; errmsg already given.
- }
-
- const size_t haystack_len = strlen(haystack);
- ptrdiff_t end_idx;
- if (argvars[2].v_type != VAR_UNKNOWN) {
- // Third argument: upper limit for index.
- end_idx = (ptrdiff_t)tv_get_number_chk(&argvars[2], NULL);
- if (end_idx < 0) {
- return; // Can never find a match.
- }
- } else {
- end_idx = (ptrdiff_t)haystack_len;
- }
-
- const char *lastmatch = NULL;
- if (*needle == NUL) {
- // Empty string matches past the end.
- lastmatch = haystack + end_idx;
- } else {
- for (const char *rest = haystack; *rest != NUL; rest++) {
- rest = strstr(rest, needle);
- if (rest == NULL || rest > haystack + end_idx) {
- break;
- }
- lastmatch = rest;
- }
- }
-
- if (lastmatch != NULL) {
- rettv->vval.v_number = (varnumber_T)(lastmatch - haystack);
- }
-}
-
-/// "strtrans()" function
-static void f_strtrans(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = transstr(tv_get_string(&argvars[0]), true);
-}
-
/// "submatch()" function
static void f_submatch(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -8940,14 +8489,15 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// "timer_info([timer])" function
static void f_timer_info(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
+ tv_list_alloc_ret(rettv, kListLenUnknown);
+
+ if (tv_check_for_opt_number_arg(argvars, 0) == FAIL) {
+ return;
+ }
+
if (argvars[0].v_type != VAR_UNKNOWN) {
- if (argvars[0].v_type != VAR_NUMBER) {
- emsg(_(e_number_exp));
- return;
- }
- tv_list_alloc_ret(rettv, 1);
timer_T *timer = find_timer_by_nr(tv_get_number(&argvars[0]));
- if (timer != NULL && !timer->stopped) {
+ if (timer != NULL && (!timer->stopped || timer->refcount > 1)) {
add_timer_info(rettv, timer);
}
} else {
@@ -8987,11 +8537,10 @@ static void f_timer_start(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
if (argvars[2].v_type != VAR_UNKNOWN) {
- dict_T *dict = argvars[2].vval.v_dict;
- if (argvars[2].v_type != VAR_DICT || dict == NULL) {
- semsg(_(e_invarg2), tv_get_string(&argvars[2]));
+ if (tv_check_for_nonnull_dict_arg(argvars, 2) == FAIL) {
return;
}
+ dict_T *dict = argvars[2].vval.v_dict;
dictitem_T *const di = tv_dict_find(dict, S_LEN("repeat"));
if (di != NULL) {
repeat = (int)tv_get_number(&di->di_tv);
@@ -9012,8 +8561,7 @@ static void f_timer_start(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// "timer_stop(timerid)" function
static void f_timer_stop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- if (argvars[0].v_type != VAR_NUMBER) {
- emsg(_(e_number_exp));
+ if (tv_check_for_number_arg(argvars, 0) == FAIL) {
return;
}
@@ -9030,186 +8578,6 @@ static void f_timer_stopall(typval_T *argvars, typval_T *unused, EvalFuncData fp
timer_stop_all();
}
-/// "tolower(string)" function
-static void f_tolower(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = strcase_save(tv_get_string(&argvars[0]), false);
-}
-
-/// "toupper(string)" function
-static void f_toupper(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = strcase_save(tv_get_string(&argvars[0]), true);
-}
-
-/// "tr(string, fromstr, tostr)" function
-static void f_tr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- char buf[NUMBUFLEN];
- char buf2[NUMBUFLEN];
-
- const char *in_str = tv_get_string(&argvars[0]);
- const char *fromstr = tv_get_string_buf_chk(&argvars[1], buf);
- const char *tostr = tv_get_string_buf_chk(&argvars[2], buf2);
-
- // Default return value: empty string.
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
- if (fromstr == NULL || tostr == NULL) {
- return; // Type error; errmsg already given.
- }
- garray_T ga;
- ga_init(&ga, (int)sizeof(char), 80);
-
- // fromstr and tostr have to contain the same number of chars.
- bool first = true;
- while (*in_str != NUL) {
- const char *cpstr = in_str;
- const int inlen = utfc_ptr2len(in_str);
- int cplen = inlen;
- int idx = 0;
- int fromlen;
- for (const char *p = fromstr; *p != NUL; p += fromlen) {
- fromlen = utfc_ptr2len(p);
- if (fromlen == inlen && strncmp(in_str, p, (size_t)inlen) == 0) {
- int tolen;
- for (p = tostr; *p != NUL; p += tolen) {
- tolen = utfc_ptr2len(p);
- if (idx-- == 0) {
- cplen = tolen;
- cpstr = p;
- break;
- }
- }
- if (*p == NUL) { // tostr is shorter than fromstr.
- goto error;
- }
- break;
- }
- idx++;
- }
-
- if (first && cpstr == in_str) {
- // Check that fromstr and tostr have the same number of
- // (multi-byte) characters. Done only once when a character
- // of in_str doesn't appear in fromstr.
- first = false;
- int tolen;
- for (const char *p = tostr; *p != NUL; p += tolen) {
- tolen = utfc_ptr2len(p);
- idx--;
- }
- if (idx != 0) {
- goto error;
- }
- }
-
- ga_grow(&ga, cplen);
- memmove((char *)ga.ga_data + ga.ga_len, cpstr, (size_t)cplen);
- ga.ga_len += cplen;
-
- in_str += inlen;
- }
-
- // add a terminating NUL
- ga_append(&ga, NUL);
-
- rettv->vval.v_string = ga.ga_data;
- return;
-error:
- semsg(_(e_invarg2), fromstr);
- ga_clear(&ga);
-}
-
-/// "trim({expr})" function
-static void f_trim(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- char buf1[NUMBUFLEN];
- char buf2[NUMBUFLEN];
- const char *head = tv_get_string_buf_chk(&argvars[0], buf1);
- const char *mask = NULL;
- const char *prev;
- const char *p;
- int dir = 0;
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
- if (head == NULL) {
- return;
- }
-
- if (argvars[1].v_type != VAR_UNKNOWN && argvars[1].v_type != VAR_STRING) {
- semsg(_(e_invarg2), tv_get_string(&argvars[1]));
- return;
- }
-
- if (argvars[1].v_type == VAR_STRING) {
- mask = tv_get_string_buf_chk(&argvars[1], buf2);
- if (argvars[2].v_type != VAR_UNKNOWN) {
- bool error = false;
- // leading or trailing characters to trim
- dir = (int)tv_get_number_chk(&argvars[2], &error);
- if (error) {
- return;
- }
- if (dir < 0 || dir > 2) {
- semsg(_(e_invarg2), tv_get_string(&argvars[2]));
- return;
- }
- }
- }
-
- int c1;
- if (dir == 0 || dir == 1) {
- // Trim leading characters
- while (*head != NUL) {
- c1 = utf_ptr2char(head);
- if (mask == NULL) {
- if (c1 > ' ' && c1 != 0xa0) {
- break;
- }
- } else {
- for (p = mask; *p != NUL; MB_PTR_ADV(p)) {
- if (c1 == utf_ptr2char(p)) {
- break;
- }
- }
- if (*p == NUL) {
- break;
- }
- }
- MB_PTR_ADV(head);
- }
- }
-
- const char *tail = head + strlen(head);
- if (dir == 0 || dir == 2) {
- // Trim trailing characters
- for (; tail > head; tail = prev) {
- prev = tail;
- MB_PTR_BACK(head, prev);
- c1 = utf_ptr2char(prev);
- if (mask == NULL) {
- if (c1 > ' ' && c1 != 0xa0) {
- break;
- }
- } else {
- for (p = mask; *p != NUL; MB_PTR_ADV(p)) {
- if (c1 == utf_ptr2char(p)) {
- break;
- }
- }
- if (*p == NUL) {
- break;
- }
- }
- }
- }
- rettv->vval.v_string = xstrnsave(head, (size_t)(tail - head));
-}
-
/// "type(expr)" function
static void f_type(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -9279,10 +8647,11 @@ static void f_undotree(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
tv_dict_add_list(dict, S_LEN("entries"), u_eval_tree(curbuf->b_u_oldhead));
}
-/// "virtcol(string)" function
+/// "virtcol(string, bool)" function
static void f_virtcol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- colnr_T vcol = 0;
+ colnr_T vcol_start = 0;
+ colnr_T vcol_end = 0;
int fnum = curbuf->b_fnum;
pos_T *fp = var2fpos(&argvars[0], false, &fnum, false);
@@ -9297,11 +8666,18 @@ static void f_virtcol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
fp->col = (colnr_T)len;
}
}
- getvvcol(curwin, fp, NULL, NULL, &vcol);
- vcol++;
+ getvvcol(curwin, fp, &vcol_start, NULL, &vcol_end);
+ vcol_start++;
+ vcol_end++;
}
- rettv->vval.v_number = vcol;
+ if (argvars[1].v_type != VAR_UNKNOWN && tv_get_bool(&argvars[1])) {
+ tv_list_alloc_ret(rettv, 2);
+ tv_list_append_number(rettv->vval.v_list, vcol_start);
+ tv_list_append_number(rettv->vval.v_list, vcol_end);
+ } else {
+ rettv->vval.v_number = vcol_end;
+ }
}
/// "visualmode()" function
diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c
index 357ef6414d..6556e274ab 100644
--- a/src/nvim/eval/typval.c
+++ b/src/nvim/eval/typval.c
@@ -50,6 +50,8 @@ static const char e_number_required_for_argument_nr[]
= N_("E1210: Number required for argument %d");
static const char e_list_required_for_argument_nr[]
= N_("E1211: List required for argument %d");
+static const char e_bool_required_for_argument_nr[]
+ = N_("E1212: Bool required for argument %d");
static const char e_string_or_list_required_for_argument_nr[]
= N_("E1222: String or List required for argument %d");
static const char e_list_or_blob_required_for_argument_nr[]
@@ -60,6 +62,8 @@ static const char e_invalid_value_for_blob_nr[]
= N_("E1239: Invalid value for blob: %d");
static const char e_string_or_function_required_for_argument_nr[]
= N_("E1256: String or function required for argument %d");
+static const char e_non_null_dict_required_for_argument_nr[]
+ = N_("E1297: Non-NULL Dictionary required for argument %d");
bool tv_in_free_unref_items = false;
@@ -1232,8 +1236,7 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort)
if (argvars[2].v_type != VAR_UNKNOWN) {
// optional third argument: {dict}
- if (argvars[2].v_type != VAR_DICT) {
- emsg(_(e_dictreq));
+ if (tv_check_for_dict_arg(argvars, 2) == FAIL) {
goto theend;
}
info.item_compare_selfdict = argvars[2].vval.v_dict;
@@ -2993,10 +2996,10 @@ void f_values(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// "has_key()" function
void f_has_key(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- if (argvars[0].v_type != VAR_DICT) {
- emsg(_(e_dictreq));
+ if (tv_check_for_dict_arg(argvars, 0) == FAIL) {
return;
}
+
if (argvars[0].vval.v_dict == NULL) {
return;
}
@@ -3999,6 +4002,14 @@ int tv_check_for_nonempty_string_arg(const typval_T *const args, const int idx)
return OK;
}
+/// Check for an optional string argument at "idx"
+int tv_check_for_opt_string_arg(const typval_T *const args, const int idx)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ return (args[idx].v_type == VAR_UNKNOWN
+ || tv_check_for_string_arg(args, idx) != FAIL) ? OK : FAIL;
+}
+
/// Give an error and return FAIL unless "args[idx]" is a number.
int tv_check_for_number_arg(const typval_T *const args, const int idx)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
@@ -4018,6 +4029,31 @@ int tv_check_for_opt_number_arg(const typval_T *const args, const int idx)
|| tv_check_for_number_arg(args, idx) != FAIL) ? OK : FAIL;
}
+/// Give an error and return FAIL unless "args[idx]" is a bool.
+int tv_check_for_bool_arg(const typval_T *const args, const int idx)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ if (args[idx].v_type != VAR_BOOL
+ && !(args[idx].v_type == VAR_NUMBER
+ && (args[idx].vval.v_number == 0
+ || args[idx].vval.v_number == 1))) {
+ semsg(_(e_bool_required_for_argument_nr), idx + 1);
+ return FAIL;
+ }
+ return OK;
+}
+
+/// Check for an optional bool argument at "idx".
+/// Return FAIL if the type is wrong.
+int tv_check_for_opt_bool_arg(const typval_T *const args, const int idx)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ if (args[idx].v_type == VAR_UNKNOWN) {
+ return OK;
+ }
+ return tv_check_for_bool_arg(args, idx);
+}
+
/// Give an error and return FAIL unless "args[idx]" is a blob.
int tv_check_for_blob_arg(const typval_T *const args, const int idx)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
@@ -4051,6 +4087,20 @@ int tv_check_for_dict_arg(const typval_T *const args, const int idx)
return OK;
}
+/// Give an error and return FAIL unless "args[idx]" is a non-NULL dict.
+int tv_check_for_nonnull_dict_arg(const typval_T *const args, const int idx)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ if (tv_check_for_dict_arg(args, idx) == FAIL) {
+ return FAIL;
+ }
+ if (args[idx].vval.v_dict == NULL) {
+ semsg(_(e_non_null_dict_required_for_argument_nr), idx + 1);
+ return FAIL;
+ }
+ return OK;
+}
+
/// Check for an optional dict argument at "idx"
int tv_check_for_opt_dict_arg(const typval_T *const args, const int idx)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c
index b71e6c9cff..854a0732ab 100644
--- a/src/nvim/eval/userfunc.c
+++ b/src/nvim/eval/userfunc.c
@@ -77,6 +77,8 @@ static const char *e_funcexts = N_("E122: Function %s already exists, add ! to r
static const char *e_funcdict = N_("E717: Dictionary entry already exists");
static const char *e_funcref = N_("E718: Funcref required");
static const char *e_nofunc = N_("E130: Unknown function: %s");
+static const char e_function_list_was_modified[]
+ = N_("E454: Function list was modified");
static const char e_no_white_space_allowed_before_str_str[]
= N_("E1068: No white space allowed before '%s': %s");
static const char e_missing_heredoc_end_marker_str[]
@@ -1752,14 +1754,33 @@ char *printable_func_name(ufunc_T *fp)
return fp->uf_name_exp != NULL ? fp->uf_name_exp : fp->uf_name;
}
+/// When "prev_ht_changed" does not equal "ht_changed" give an error and return
+/// true. Otherwise return false.
+static int function_list_modified(const int prev_ht_changed)
+{
+ if (prev_ht_changed != func_hashtab.ht_changed) {
+ emsg(_(e_function_list_was_modified));
+ return true;
+ }
+ return false;
+}
+
/// List the head of the function: "name(arg1, arg2)".
///
/// @param[in] fp Function pointer.
/// @param[in] indent Indent line.
/// @param[in] force Include bang "!" (i.e.: "function!").
-static void list_func_head(ufunc_T *fp, int indent, bool force)
+static int list_func_head(ufunc_T *fp, bool indent, bool force)
{
+ const int prev_ht_changed = func_hashtab.ht_changed;
+
msg_start();
+
+ // a callback at the more prompt may have deleted the function
+ if (function_list_modified(prev_ht_changed)) {
+ return FAIL;
+ }
+
if (indent) {
msg_puts(" ");
}
@@ -1805,6 +1826,8 @@ static void list_func_head(ufunc_T *fp, int indent, bool force)
if (p_verbose > 0) {
last_set_msg(fp->uf_script_ctx);
}
+
+ return OK;
}
/// Get a function name, translating "<SID>" and "<SNR>".
@@ -2085,7 +2108,7 @@ char *save_function_name(char **name, bool skip, int flags, funcdict_T *fudi)
/// Otherwise functions matching "regmatch".
static void list_functions(regmatch_T *regmatch)
{
- const int changed = func_hashtab.ht_changed;
+ const int prev_ht_changed = func_hashtab.ht_changed;
size_t todo = func_hashtab.ht_used;
const hashitem_T *const ht_array = func_hashtab.ht_array;
@@ -2098,9 +2121,10 @@ static void list_functions(regmatch_T *regmatch)
&& !func_name_refcount(fp->uf_name))
: (!isdigit((uint8_t)(*fp->uf_name))
&& vim_regexec(regmatch, fp->uf_name, 0))) {
- list_func_head(fp, false, false);
- if (changed != func_hashtab.ht_changed) {
- emsg(_("E454: function list was modified"));
+ if (list_func_head(fp, false, false) == FAIL) {
+ return;
+ }
+ if (function_list_modified(prev_ht_changed)) {
return;
}
}
@@ -2229,27 +2253,37 @@ void ex_function(exarg_T *eap)
if (!eap->skip && !got_int) {
fp = find_func(name);
if (fp != NULL) {
- list_func_head(fp, !eap->forceit, eap->forceit);
- for (int j = 0; j < fp->uf_lines.ga_len && !got_int; j++) {
- if (FUNCLINE(fp, j) == NULL) {
- continue;
- }
- msg_putchar('\n');
- if (!eap->forceit) {
- msg_outnum((long)j + 1);
- if (j < 9) {
- msg_putchar(' ');
+ // Check no function was added or removed from a callback, e.g. at
+ // the more prompt. "fp" may then be invalid.
+ const int prev_ht_changed = func_hashtab.ht_changed;
+
+ if (list_func_head(fp, !eap->forceit, eap->forceit) == OK) {
+ for (int j = 0; j < fp->uf_lines.ga_len && !got_int; j++) {
+ if (FUNCLINE(fp, j) == NULL) {
+ continue;
}
- if (j < 99) {
- msg_putchar(' ');
+ msg_putchar('\n');
+ if (!eap->forceit) {
+ msg_outnum((long)j + 1);
+ if (j < 9) {
+ msg_putchar(' ');
+ }
+ if (j < 99) {
+ msg_putchar(' ');
+ }
+ if (function_list_modified(prev_ht_changed)) {
+ break;
+ }
+ }
+ msg_prt_line(FUNCLINE(fp, j), false);
+ line_breakcheck(); // show multiple lines at a time!
+ }
+ if (!got_int) {
+ msg_putchar('\n');
+ if (!function_list_modified(prev_ht_changed)) {
+ msg_puts(eap->forceit ? "endfunction" : " endfunction");
}
}
- msg_prt_line(FUNCLINE(fp, j), false);
- line_breakcheck(); // show multiple lines at a time!
- }
- if (!got_int) {
- msg_putchar('\n');
- msg_puts(eap->forceit ? "endfunction" : " endfunction");
}
} else {
emsg_funcname(N_("E123: Undefined function: %s"), name);
diff --git a/src/nvim/eval/window.c b/src/nvim/eval/window.c
index 25de236d90..9976c56879 100644
--- a/src/nvim/eval/window.c
+++ b/src/nvim/eval/window.c
@@ -651,8 +651,7 @@ void f_win_splitmove(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
dict_T *d;
dictitem_T *di;
- if (argvars[2].v_type != VAR_DICT || argvars[2].vval.v_dict == NULL) {
- emsg(_(e_invarg));
+ if (tv_check_for_nonnull_dict_arg(argvars, 2) == FAIL) {
return;
}
@@ -796,51 +795,50 @@ void f_winrestcmd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// "winrestview()" function
void f_winrestview(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- dict_T *dict = argvars[0].vval.v_dict;
+ if (tv_check_for_nonnull_dict_arg(argvars, 0) == FAIL) {
+ return;
+ }
- if (argvars[0].v_type != VAR_DICT || dict == NULL) {
- emsg(_(e_invarg));
- } else {
- dictitem_T *di;
- if ((di = tv_dict_find(dict, S_LEN("lnum"))) != NULL) {
- curwin->w_cursor.lnum = (linenr_T)tv_get_number(&di->di_tv);
- }
- if ((di = tv_dict_find(dict, S_LEN("col"))) != NULL) {
- curwin->w_cursor.col = (colnr_T)tv_get_number(&di->di_tv);
- }
- if ((di = tv_dict_find(dict, S_LEN("coladd"))) != NULL) {
- curwin->w_cursor.coladd = (colnr_T)tv_get_number(&di->di_tv);
- }
- if ((di = tv_dict_find(dict, S_LEN("curswant"))) != NULL) {
- curwin->w_curswant = (colnr_T)tv_get_number(&di->di_tv);
- curwin->w_set_curswant = false;
- }
- if ((di = tv_dict_find(dict, S_LEN("topline"))) != NULL) {
- set_topline(curwin, (linenr_T)tv_get_number(&di->di_tv));
- }
- if ((di = tv_dict_find(dict, S_LEN("topfill"))) != NULL) {
- curwin->w_topfill = (int)tv_get_number(&di->di_tv);
- }
- if ((di = tv_dict_find(dict, S_LEN("leftcol"))) != NULL) {
- curwin->w_leftcol = (colnr_T)tv_get_number(&di->di_tv);
- }
- if ((di = tv_dict_find(dict, S_LEN("skipcol"))) != NULL) {
- curwin->w_skipcol = (colnr_T)tv_get_number(&di->di_tv);
- }
+ dict_T *dict = argvars[0].vval.v_dict;
+ dictitem_T *di;
+ if ((di = tv_dict_find(dict, S_LEN("lnum"))) != NULL) {
+ curwin->w_cursor.lnum = (linenr_T)tv_get_number(&di->di_tv);
+ }
+ if ((di = tv_dict_find(dict, S_LEN("col"))) != NULL) {
+ curwin->w_cursor.col = (colnr_T)tv_get_number(&di->di_tv);
+ }
+ if ((di = tv_dict_find(dict, S_LEN("coladd"))) != NULL) {
+ curwin->w_cursor.coladd = (colnr_T)tv_get_number(&di->di_tv);
+ }
+ if ((di = tv_dict_find(dict, S_LEN("curswant"))) != NULL) {
+ curwin->w_curswant = (colnr_T)tv_get_number(&di->di_tv);
+ curwin->w_set_curswant = false;
+ }
+ if ((di = tv_dict_find(dict, S_LEN("topline"))) != NULL) {
+ set_topline(curwin, (linenr_T)tv_get_number(&di->di_tv));
+ }
+ if ((di = tv_dict_find(dict, S_LEN("topfill"))) != NULL) {
+ curwin->w_topfill = (int)tv_get_number(&di->di_tv);
+ }
+ if ((di = tv_dict_find(dict, S_LEN("leftcol"))) != NULL) {
+ curwin->w_leftcol = (colnr_T)tv_get_number(&di->di_tv);
+ }
+ if ((di = tv_dict_find(dict, S_LEN("skipcol"))) != NULL) {
+ curwin->w_skipcol = (colnr_T)tv_get_number(&di->di_tv);
+ }
- check_cursor();
- win_new_height(curwin, curwin->w_height);
- win_new_width(curwin, curwin->w_width);
- changed_window_setting();
+ check_cursor();
+ win_new_height(curwin, curwin->w_height);
+ win_new_width(curwin, curwin->w_width);
+ changed_window_setting();
- if (curwin->w_topline <= 0) {
- curwin->w_topline = 1;
- }
- if (curwin->w_topline > curbuf->b_ml.ml_line_count) {
- curwin->w_topline = curbuf->b_ml.ml_line_count;
- }
- check_topfill(curwin, true);
+ if (curwin->w_topline <= 0) {
+ curwin->w_topline = 1;
+ }
+ if (curwin->w_topline > curbuf->b_ml.ml_line_count) {
+ curwin->w_topline = curbuf->b_ml.ml_line_count;
}
+ check_topfill(curwin, true);
}
/// "winsaveview()" function
diff --git a/src/nvim/generators/gen_api_ui_events.lua b/src/nvim/generators/gen_api_ui_events.lua
index e2af5f8d44..e2af5f8d44 100755..100644
--- a/src/nvim/generators/gen_api_ui_events.lua
+++ b/src/nvim/generators/gen_api_ui_events.lua
diff --git a/src/nvim/generators/gen_eval.lua b/src/nvim/generators/gen_eval.lua
index e574efdf99..7d531bc228 100644
--- a/src/nvim/generators/gen_eval.lua
+++ b/src/nvim/generators/gen_eval.lua
@@ -35,6 +35,7 @@ hashpipe:write([[
#include "nvim/quickfix.h"
#include "nvim/runtime.h"
#include "nvim/search.h"
+#include "nvim/strings.h"
#include "nvim/sign.h"
#include "nvim/testing.h"
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index 0c7f55714c..159bfc202f 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -1003,6 +1003,7 @@ EXTERN const char e_fnametoolong[] INIT(= N_("E856: Filename too long"));
EXTERN const char e_float_as_string[] INIT(= N_("E806: using Float as a String"));
EXTERN const char e_inval_string[] INIT(= N_("E908: using an invalid value as a String"));
EXTERN const char e_cannot_edit_other_buf[] INIT(= N_("E788: Not allowed to edit another buffer now"));
+EXTERN const char e_using_number_as_bool_nr[] INIT(= N_("E1023: Using a Number as a Bool: %d"));
EXTERN const char e_not_callable_type_str[] INIT(= N_("E1085: Not a callable type: %s"));
EXTERN const char e_cmdmap_err[] INIT(= N_("E5520: <Cmd> mapping must end with <CR>"));
diff --git a/src/nvim/mapping.c b/src/nvim/mapping.c
index c1565a84f5..f88c0deb87 100644
--- a/src/nvim/mapping.c
+++ b/src/nvim/mapping.c
@@ -2204,8 +2204,7 @@ void f_mapset(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
const int mode = get_map_mode((char **)&which, 0);
const bool is_abbr = tv_get_number(&argvars[1]) != 0;
- if (argvars[2].v_type != VAR_DICT) {
- emsg(_(e_dictreq));
+ if (tv_check_for_dict_arg(argvars, 2) == FAIL) {
return;
}
dict_T *d = argvars[2].vval.v_dict;
diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c
index ab787524a9..78204b22a8 100644
--- a/src/nvim/mbyte.c
+++ b/src/nvim/mbyte.c
@@ -2361,6 +2361,34 @@ static char *iconv_string(const vimconv_T *const vcp, const char *str, size_t sl
return result;
}
+/// iconv() function
+void f_iconv(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ vimconv_T vimconv;
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+
+ const char *const str = tv_get_string(&argvars[0]);
+ char buf1[NUMBUFLEN];
+ char *const from = enc_canonize(enc_skip((char *)tv_get_string_buf(&argvars[1], buf1)));
+ char buf2[NUMBUFLEN];
+ char *const to = enc_canonize(enc_skip((char *)tv_get_string_buf(&argvars[2], buf2)));
+ vimconv.vc_type = CONV_NONE;
+ convert_setup(&vimconv, from, to);
+
+ // If the encodings are equal, no conversion needed.
+ if (vimconv.vc_type == CONV_NONE) {
+ rettv->vval.v_string = xstrdup(str);
+ } else {
+ rettv->vval.v_string = string_convert(&vimconv, (char *)str, NULL);
+ }
+
+ convert_setup(&vimconv, NULL, NULL);
+ xfree(from);
+ xfree(to);
+}
+
/// Setup "vcp" for conversion from "from" to "to".
/// The names must have been made canonical with enc_canonize().
/// vcp->vc_type must have been initialized to CONV_NONE.
diff --git a/src/nvim/search.c b/src/nvim/search.c
index 694c4cad52..50e72eee86 100644
--- a/src/nvim/search.c
+++ b/src/nvim/search.c
@@ -2772,8 +2772,7 @@ void f_searchcount(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
dictitem_T *di;
bool error = false;
- if (argvars[0].v_type != VAR_DICT || argvars[0].vval.v_dict == NULL) {
- emsg(_(e_dictreq));
+ if (tv_check_for_nonnull_dict_arg(argvars, 0) == FAIL) {
return;
}
dict = argvars[0].vval.v_dict;
@@ -3348,8 +3347,7 @@ static void do_fuzzymatch(const typval_T *const argvars, typval_T *const rettv,
bool matchseq = false;
long max_matches = 0;
if (argvars[2].v_type != VAR_UNKNOWN) {
- if (argvars[2].v_type != VAR_DICT || argvars[2].vval.v_dict == NULL) {
- emsg(_(e_dictreq));
+ if (tv_check_for_nonnull_dict_arg(argvars, 2) == FAIL) {
return;
}
diff --git a/src/nvim/sign.c b/src/nvim/sign.c
index 50a55ddd5c..5d3c6e9a54 100644
--- a/src/nvim/sign.c
+++ b/src/nvim/sign.c
@@ -2017,8 +2017,7 @@ void f_sign_define(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
return;
}
- if (argvars[1].v_type != VAR_UNKNOWN && argvars[1].v_type != VAR_DICT) {
- emsg(_(e_dictreq));
+ if (tv_check_for_opt_dict_arg(argvars, 1) == FAIL) {
return;
}
@@ -2061,12 +2060,10 @@ void f_sign_getplaced(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
if (argvars[1].v_type != VAR_UNKNOWN) {
- dict_T *dict;
- if (argvars[1].v_type != VAR_DICT
- || ((dict = argvars[1].vval.v_dict) == NULL)) {
- emsg(_(e_dictreq));
+ if (tv_check_for_nonnull_dict_arg(argvars, 1) == FAIL) {
return;
}
+ dict_T *dict = argvars[1].vval.v_dict;
if ((di = tv_dict_find(dict, "lnum", -1)) != NULL) {
// get signs placed at this line
lnum = (linenr_T)tv_get_number_chk(&di->di_tv, &notanum);
@@ -2263,11 +2260,11 @@ void f_sign_place(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
rettv->vval.v_number = -1;
- if (argvars[4].v_type != VAR_UNKNOWN
- && (argvars[4].v_type != VAR_DICT
- || ((dict = argvars[4].vval.v_dict) == NULL))) {
- emsg(_(e_dictreq));
- return;
+ if (argvars[4].v_type != VAR_UNKNOWN) {
+ if (tv_check_for_nonnull_dict_arg(argvars, 4) == FAIL) {
+ return;
+ }
+ dict = argvars[4].vval.v_dict;
}
rettv->vval.v_number = sign_place_from_dict(&argvars[0], &argvars[1], &argvars[2], &argvars[3],
@@ -2409,16 +2406,12 @@ void f_sign_unplace(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
rettv->vval.v_number = -1;
- if (argvars[0].v_type != VAR_STRING) {
- emsg(_(e_invarg));
+ if (tv_check_for_string_arg(argvars, 0) == FAIL
+ || tv_check_for_opt_dict_arg(argvars, 1) == FAIL) {
return;
}
if (argvars[1].v_type != VAR_UNKNOWN) {
- if (argvars[1].v_type != VAR_DICT) {
- emsg(_(e_dictreq));
- return;
- }
dict = argvars[1].vval.v_dict;
}
diff --git a/src/nvim/strings.c b/src/nvim/strings.c
index 14aa4ddc1a..e8c04aa5c7 100644
--- a/src/nvim/strings.c
+++ b/src/nvim/strings.c
@@ -19,6 +19,7 @@
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/ex_docmd.h"
+#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/macros.h"
#include "nvim/math.h"
@@ -26,6 +27,7 @@
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/option.h"
+#include "nvim/plines.h"
#include "nvim/strings.h"
#include "nvim/types.h"
#include "nvim/vim.h"
@@ -1499,3 +1501,712 @@ char *strrep(const char *src, const char *what, const char *rep)
return ret;
}
+
+static void byteidx(typval_T *argvars, typval_T *rettv, int comp)
+{
+ rettv->vval.v_number = -1;
+
+ const char *const str = tv_get_string_chk(&argvars[0]);
+ varnumber_T idx = tv_get_number_chk(&argvars[1], NULL);
+ if (str == NULL || idx < 0) {
+ return;
+ }
+
+ varnumber_T utf16idx = false;
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ utf16idx = tv_get_bool(&argvars[2]);
+ if (utf16idx < 0 || utf16idx > 1) {
+ semsg(_(e_using_number_as_bool_nr), utf16idx);
+ return;
+ }
+ }
+
+ int (*ptr2len)(const char *);
+ if (comp) {
+ ptr2len = utf_ptr2len;
+ } else {
+ ptr2len = utfc_ptr2len;
+ }
+
+ const char *t = str;
+ for (; idx > 0; idx--) {
+ if (*t == NUL) { // EOL reached.
+ return;
+ }
+ if (utf16idx) {
+ const int clen = ptr2len(t);
+ const int c = (clen > 1) ? utf_ptr2char(t) : *t;
+ if (c > 0xFFFF) {
+ idx--;
+ }
+ }
+ if (idx > 0) {
+ t += ptr2len(t);
+ }
+ }
+ rettv->vval.v_number = (varnumber_T)(t - str);
+}
+
+/// "byteidx()" function
+void f_byteidx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ byteidx(argvars, rettv, false);
+}
+
+/// "byteidxcomp()" function
+void f_byteidxcomp(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ byteidx(argvars, rettv, true);
+}
+
+/// "charidx()" function
+void f_charidx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->vval.v_number = -1;
+
+ if (tv_check_for_string_arg(argvars, 0) == FAIL
+ || tv_check_for_number_arg(argvars, 1) == FAIL
+ || tv_check_for_opt_bool_arg(argvars, 2) == FAIL
+ || (argvars[2].v_type != VAR_UNKNOWN
+ && tv_check_for_opt_bool_arg(argvars, 3) == FAIL)) {
+ return;
+ }
+
+ const char *const str = tv_get_string_chk(&argvars[0]);
+ varnumber_T idx = tv_get_number_chk(&argvars[1], NULL);
+ if (str == NULL || idx < 0) {
+ return;
+ }
+
+ varnumber_T countcc = false;
+ varnumber_T utf16idx = false;
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ countcc = tv_get_bool(&argvars[2]);
+ if (argvars[3].v_type != VAR_UNKNOWN) {
+ utf16idx = tv_get_bool(&argvars[3]);
+ }
+ }
+
+ int (*ptr2len)(const char *);
+ if (countcc) {
+ ptr2len = utf_ptr2len;
+ } else {
+ ptr2len = utfc_ptr2len;
+ }
+
+ const char *p;
+ int len;
+ for (p = str, len = 0; utf16idx ? idx >= 0 : p <= str + idx; len++) {
+ if (*p == NUL) {
+ return;
+ }
+ if (utf16idx) {
+ idx--;
+ const int clen = ptr2len(p);
+ const int c = (clen > 1) ? utf_ptr2char(p) : *p;
+ if (c > 0xFFFF) {
+ idx--;
+ }
+ }
+ p += ptr2len(p);
+ }
+
+ rettv->vval.v_number = len > 0 ? len - 1 : 0;
+}
+
+/// "str2list()" function
+void f_str2list(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ tv_list_alloc_ret(rettv, kListLenUnknown);
+ const char *p = tv_get_string(&argvars[0]);
+
+ for (; *p != NUL; p += utf_ptr2len(p)) {
+ tv_list_append_number(rettv->vval.v_list, utf_ptr2char(p));
+ }
+}
+
+/// "str2nr()" function
+void f_str2nr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ int base = 10;
+ int what = 0;
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ base = (int)tv_get_number(&argvars[1]);
+ if (base != 2 && base != 8 && base != 10 && base != 16) {
+ emsg(_(e_invarg));
+ return;
+ }
+ if (argvars[2].v_type != VAR_UNKNOWN && tv_get_bool(&argvars[2])) {
+ what |= STR2NR_QUOTE;
+ }
+ }
+
+ char *p = skipwhite(tv_get_string(&argvars[0]));
+ bool isneg = (*p == '-');
+ if (*p == '+' || *p == '-') {
+ p = skipwhite(p + 1);
+ }
+ switch (base) {
+ case 2:
+ what |= STR2NR_BIN | STR2NR_FORCE;
+ break;
+ case 8:
+ what |= STR2NR_OCT | STR2NR_OOCT | STR2NR_FORCE;
+ break;
+ case 16:
+ what |= STR2NR_HEX | STR2NR_FORCE;
+ break;
+ }
+ varnumber_T n;
+ vim_str2nr(p, NULL, NULL, what, &n, NULL, 0, false, NULL);
+ // Text after the number is silently ignored.
+ if (isneg) {
+ rettv->vval.v_number = -n;
+ } else {
+ rettv->vval.v_number = n;
+ }
+}
+
+/// "strgetchar()" function
+void f_strgetchar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->vval.v_number = -1;
+
+ const char *const str = tv_get_string_chk(&argvars[0]);
+ if (str == NULL) {
+ return;
+ }
+ bool error = false;
+ varnumber_T charidx = tv_get_number_chk(&argvars[1], &error);
+ if (error) {
+ return;
+ }
+
+ const size_t len = strlen(str);
+ size_t byteidx = 0;
+
+ while (charidx >= 0 && byteidx < len) {
+ if (charidx == 0) {
+ rettv->vval.v_number = utf_ptr2char(str + byteidx);
+ break;
+ }
+ charidx--;
+ byteidx += (size_t)utf_ptr2len(str + byteidx);
+ }
+}
+
+/// "stridx()" function
+void f_stridx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->vval.v_number = -1;
+
+ char buf[NUMBUFLEN];
+ const char *const needle = tv_get_string_chk(&argvars[1]);
+ const char *haystack = tv_get_string_buf_chk(&argvars[0], buf);
+ const char *const haystack_start = haystack;
+ if (needle == NULL || haystack == NULL) {
+ return; // Type error; errmsg already given.
+ }
+
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ bool error = false;
+
+ const ptrdiff_t start_idx = (ptrdiff_t)tv_get_number_chk(&argvars[2],
+ &error);
+ if (error || start_idx >= (ptrdiff_t)strlen(haystack)) {
+ return;
+ }
+ if (start_idx >= 0) {
+ haystack += start_idx;
+ }
+ }
+
+ const char *pos = strstr(haystack, needle);
+ if (pos != NULL) {
+ rettv->vval.v_number = (varnumber_T)(pos - haystack_start);
+ }
+}
+
+/// "string()" function
+void f_string(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = encode_tv2string(&argvars[0], NULL);
+}
+
+/// "strlen()" function
+void f_strlen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->vval.v_number = (varnumber_T)strlen(tv_get_string(&argvars[0]));
+}
+
+static void strchar_common(typval_T *argvars, typval_T *rettv, bool skipcc)
+{
+ const char *s = tv_get_string(&argvars[0]);
+ varnumber_T len = 0;
+ int (*func_mb_ptr2char_adv)(const char **pp);
+
+ func_mb_ptr2char_adv = skipcc ? mb_ptr2char_adv : mb_cptr2char_adv;
+ while (*s != NUL) {
+ func_mb_ptr2char_adv(&s);
+ len++;
+ }
+ rettv->vval.v_number = len;
+}
+
+/// "strcharlen()" function
+void f_strcharlen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ strchar_common(argvars, rettv, true);
+}
+
+/// "strchars()" function
+void f_strchars(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ int skipcc = false;
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ skipcc = (int)tv_get_bool(&argvars[1]);
+ }
+ if (skipcc < 0 || skipcc > 1) {
+ semsg(_(e_using_number_as_bool_nr), skipcc);
+ } else {
+ strchar_common(argvars, rettv, skipcc);
+ }
+}
+
+/// "strutf16len()" function
+void f_strutf16len(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->vval.v_number = -1;
+
+ if (tv_check_for_string_arg(argvars, 0) == FAIL
+ || tv_check_for_opt_bool_arg(argvars, 1) == FAIL) {
+ return;
+ }
+
+ varnumber_T countcc = false;
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ countcc = tv_get_bool(&argvars[1]);
+ }
+
+ const char *s = tv_get_string(&argvars[0]);
+ varnumber_T len = 0;
+ int (*func_mb_ptr2char_adv)(const char **pp);
+
+ func_mb_ptr2char_adv = countcc ? mb_cptr2char_adv : mb_ptr2char_adv;
+ while (*s != NUL) {
+ const int ch = func_mb_ptr2char_adv(&s);
+ if (ch > 0xFFFF) {
+ len++;
+ }
+ len++;
+ }
+ rettv->vval.v_number = len;
+}
+
+/// "strdisplaywidth()" function
+void f_strdisplaywidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ const char *const s = tv_get_string(&argvars[0]);
+ int col = 0;
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ col = (int)tv_get_number(&argvars[1]);
+ }
+
+ rettv->vval.v_number = (varnumber_T)(linetabsize_col(col, (char *)s) - col);
+}
+
+/// "strwidth()" function
+void f_strwidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ const char *const s = tv_get_string(&argvars[0]);
+
+ rettv->vval.v_number = (varnumber_T)mb_string2cells(s);
+}
+
+/// "strcharpart()" function
+void f_strcharpart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ const char *const p = tv_get_string(&argvars[0]);
+ const size_t slen = strlen(p);
+
+ int nbyte = 0;
+ bool error = false;
+ varnumber_T nchar = tv_get_number_chk(&argvars[1], &error);
+ if (!error) {
+ if (nchar > 0) {
+ while (nchar > 0 && (size_t)nbyte < slen) {
+ nbyte += utf_ptr2len(p + nbyte);
+ nchar--;
+ }
+ } else {
+ nbyte = (int)nchar;
+ }
+ }
+ int len = 0;
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ int charlen = (int)tv_get_number(&argvars[2]);
+ while (charlen > 0 && nbyte + len < (int)slen) {
+ int off = nbyte + len;
+
+ if (off < 0) {
+ len += 1;
+ } else {
+ len += utf_ptr2len(p + off);
+ }
+ charlen--;
+ }
+ } else {
+ len = (int)slen - nbyte; // default: all bytes that are available.
+ }
+
+ // Only return the overlap between the specified part and the actual
+ // string.
+ if (nbyte < 0) {
+ len += nbyte;
+ nbyte = 0;
+ } else if ((size_t)nbyte > slen) {
+ nbyte = (int)slen;
+ }
+ if (len < 0) {
+ len = 0;
+ } else if (nbyte + len > (int)slen) {
+ len = (int)slen - nbyte;
+ }
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = xstrndup(p + nbyte, (size_t)len);
+}
+
+/// "strpart()" function
+void f_strpart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ bool error = false;
+
+ const char *const p = tv_get_string(&argvars[0]);
+ const size_t slen = strlen(p);
+
+ varnumber_T n = tv_get_number_chk(&argvars[1], &error);
+ varnumber_T len;
+ if (error) {
+ len = 0;
+ } else if (argvars[2].v_type != VAR_UNKNOWN) {
+ len = tv_get_number(&argvars[2]);
+ } else {
+ len = (varnumber_T)slen - n; // Default len: all bytes that are available.
+ }
+
+ // Only return the overlap between the specified part and the actual
+ // string.
+ if (n < 0) {
+ len += n;
+ n = 0;
+ } else if (n > (varnumber_T)slen) {
+ n = (varnumber_T)slen;
+ }
+ if (len < 0) {
+ len = 0;
+ } else if (n + len > (varnumber_T)slen) {
+ len = (varnumber_T)slen - n;
+ }
+
+ if (argvars[2].v_type != VAR_UNKNOWN && argvars[3].v_type != VAR_UNKNOWN) {
+ int off;
+
+ // length in characters
+ for (off = (int)n; off < (int)slen && len > 0; len--) {
+ off += utfc_ptr2len(p + off);
+ }
+ len = off - n;
+ }
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = xmemdupz(p + n, (size_t)len);
+}
+
+/// "strridx()" function
+void f_strridx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ char buf[NUMBUFLEN];
+ const char *const needle = tv_get_string_chk(&argvars[1]);
+ const char *const haystack = tv_get_string_buf_chk(&argvars[0], buf);
+
+ rettv->vval.v_number = -1;
+ if (needle == NULL || haystack == NULL) {
+ return; // Type error; errmsg already given.
+ }
+
+ const size_t haystack_len = strlen(haystack);
+ ptrdiff_t end_idx;
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ // Third argument: upper limit for index.
+ end_idx = (ptrdiff_t)tv_get_number_chk(&argvars[2], NULL);
+ if (end_idx < 0) {
+ return; // Can never find a match.
+ }
+ } else {
+ end_idx = (ptrdiff_t)haystack_len;
+ }
+
+ const char *lastmatch = NULL;
+ if (*needle == NUL) {
+ // Empty string matches past the end.
+ lastmatch = haystack + end_idx;
+ } else {
+ for (const char *rest = haystack; *rest != NUL; rest++) {
+ rest = strstr(rest, needle);
+ if (rest == NULL || rest > haystack + end_idx) {
+ break;
+ }
+ lastmatch = rest;
+ }
+ }
+
+ if (lastmatch != NULL) {
+ rettv->vval.v_number = (varnumber_T)(lastmatch - haystack);
+ }
+}
+
+/// "strtrans()" function
+void f_strtrans(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = transstr(tv_get_string(&argvars[0]), true);
+}
+
+/// "utf16idx()" function
+void f_utf16idx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->vval.v_number = -1;
+
+ if (tv_check_for_string_arg(argvars, 0) == FAIL
+ || tv_check_for_opt_number_arg(argvars, 1) == FAIL
+ || tv_check_for_opt_bool_arg(argvars, 2) == FAIL
+ || (argvars[2].v_type != VAR_UNKNOWN
+ && tv_check_for_opt_bool_arg(argvars, 3) == FAIL)) {
+ return;
+ }
+
+ const char *const str = tv_get_string_chk(&argvars[0]);
+ varnumber_T idx = tv_get_number_chk(&argvars[1], NULL);
+ if (str == NULL || idx < 0) {
+ return;
+ }
+
+ varnumber_T countcc = false;
+ varnumber_T charidx = false;
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ countcc = tv_get_bool(&argvars[2]);
+ if (argvars[3].v_type != VAR_UNKNOWN) {
+ charidx = tv_get_bool(&argvars[3]);
+ }
+ }
+
+ int (*ptr2len)(const char *);
+ if (countcc) {
+ ptr2len = utf_ptr2len;
+ } else {
+ ptr2len = utfc_ptr2len;
+ }
+
+ const char *p;
+ int len;
+ for (p = str, len = 0; charidx ? idx >= 0 : p <= str + idx; len++) {
+ if (*p == NUL) {
+ return;
+ }
+ const int clen = ptr2len(p);
+ const int c = (clen > 1) ? utf_ptr2char(p) : *p;
+ if (c > 0xFFFF) {
+ len++;
+ }
+ p += ptr2len(p);
+ if (charidx) {
+ idx--;
+ }
+ }
+
+ rettv->vval.v_number = len > 0 ? len - 1 : 0;
+}
+
+/// "tolower(string)" function
+void f_tolower(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = strcase_save(tv_get_string(&argvars[0]), false);
+}
+
+/// "toupper(string)" function
+void f_toupper(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = strcase_save(tv_get_string(&argvars[0]), true);
+}
+
+/// "tr(string, fromstr, tostr)" function
+void f_tr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ char buf[NUMBUFLEN];
+ char buf2[NUMBUFLEN];
+
+ const char *in_str = tv_get_string(&argvars[0]);
+ const char *fromstr = tv_get_string_buf_chk(&argvars[1], buf);
+ const char *tostr = tv_get_string_buf_chk(&argvars[2], buf2);
+
+ // Default return value: empty string.
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+ if (fromstr == NULL || tostr == NULL) {
+ return; // Type error; errmsg already given.
+ }
+ garray_T ga;
+ ga_init(&ga, (int)sizeof(char), 80);
+
+ // fromstr and tostr have to contain the same number of chars.
+ bool first = true;
+ while (*in_str != NUL) {
+ const char *cpstr = in_str;
+ const int inlen = utfc_ptr2len(in_str);
+ int cplen = inlen;
+ int idx = 0;
+ int fromlen;
+ for (const char *p = fromstr; *p != NUL; p += fromlen) {
+ fromlen = utfc_ptr2len(p);
+ if (fromlen == inlen && strncmp(in_str, p, (size_t)inlen) == 0) {
+ int tolen;
+ for (p = tostr; *p != NUL; p += tolen) {
+ tolen = utfc_ptr2len(p);
+ if (idx-- == 0) {
+ cplen = tolen;
+ cpstr = p;
+ break;
+ }
+ }
+ if (*p == NUL) { // tostr is shorter than fromstr.
+ goto error;
+ }
+ break;
+ }
+ idx++;
+ }
+
+ if (first && cpstr == in_str) {
+ // Check that fromstr and tostr have the same number of
+ // (multi-byte) characters. Done only once when a character
+ // of in_str doesn't appear in fromstr.
+ first = false;
+ int tolen;
+ for (const char *p = tostr; *p != NUL; p += tolen) {
+ tolen = utfc_ptr2len(p);
+ idx--;
+ }
+ if (idx != 0) {
+ goto error;
+ }
+ }
+
+ ga_grow(&ga, cplen);
+ memmove((char *)ga.ga_data + ga.ga_len, cpstr, (size_t)cplen);
+ ga.ga_len += cplen;
+
+ in_str += inlen;
+ }
+
+ // add a terminating NUL
+ ga_append(&ga, NUL);
+
+ rettv->vval.v_string = ga.ga_data;
+ return;
+error:
+ semsg(_(e_invarg2), fromstr);
+ ga_clear(&ga);
+}
+
+/// "trim({expr})" function
+void f_trim(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ char buf1[NUMBUFLEN];
+ char buf2[NUMBUFLEN];
+ const char *head = tv_get_string_buf_chk(&argvars[0], buf1);
+ const char *mask = NULL;
+ const char *prev;
+ const char *p;
+ int dir = 0;
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+ if (head == NULL) {
+ return;
+ }
+
+ if (tv_check_for_opt_string_arg(argvars, 1) == FAIL) {
+ return;
+ }
+
+ if (argvars[1].v_type == VAR_STRING) {
+ mask = tv_get_string_buf_chk(&argvars[1], buf2);
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ bool error = false;
+ // leading or trailing characters to trim
+ dir = (int)tv_get_number_chk(&argvars[2], &error);
+ if (error) {
+ return;
+ }
+ if (dir < 0 || dir > 2) {
+ semsg(_(e_invarg2), tv_get_string(&argvars[2]));
+ return;
+ }
+ }
+ }
+
+ int c1;
+ if (dir == 0 || dir == 1) {
+ // Trim leading characters
+ while (*head != NUL) {
+ c1 = utf_ptr2char(head);
+ if (mask == NULL) {
+ if (c1 > ' ' && c1 != 0xa0) {
+ break;
+ }
+ } else {
+ for (p = mask; *p != NUL; MB_PTR_ADV(p)) {
+ if (c1 == utf_ptr2char(p)) {
+ break;
+ }
+ }
+ if (*p == NUL) {
+ break;
+ }
+ }
+ MB_PTR_ADV(head);
+ }
+ }
+
+ const char *tail = head + strlen(head);
+ if (dir == 0 || dir == 2) {
+ // Trim trailing characters
+ for (; tail > head; tail = prev) {
+ prev = tail;
+ MB_PTR_BACK(head, prev);
+ c1 = utf_ptr2char(prev);
+ if (mask == NULL) {
+ if (c1 > ' ' && c1 != 0xa0) {
+ break;
+ }
+ } else {
+ for (p = mask; *p != NUL; MB_PTR_ADV(p)) {
+ if (c1 == utf_ptr2char(p)) {
+ break;
+ }
+ }
+ if (*p == NUL) {
+ break;
+ }
+ }
+ }
+ }
+ rettv->vval.v_string = xstrnsave(head, (size_t)(tail - head));
+}
diff --git a/src/nvim/window.c b/src/nvim/window.c
index f2c68730ea..81e1fd59c1 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -3742,12 +3742,7 @@ static void frame_add_hsep(const frame_T *frp)
{
if (frp->fr_layout == FR_LEAF) {
win_T *wp = frp->fr_win;
- if (wp->w_hsep_height == 0) {
- if (wp->w_height > 0) { // don't make it negative
- wp->w_height++;
- }
- wp->w_hsep_height = 1;
- }
+ wp->w_hsep_height = 1;
} else if (frp->fr_layout == FR_ROW) {
// Handle all the frames in the row.
FOR_ALL_FRAMES(frp, frp->fr_child) {
diff --git a/test/functional/lua/iter_spec.lua b/test/functional/lua/iter_spec.lua
new file mode 100644
index 0000000000..6e1ecc2f7e
--- /dev/null
+++ b/test/functional/lua/iter_spec.lua
@@ -0,0 +1,425 @@
+local helpers = require('test.functional.helpers')(after_each)
+local eq = helpers.eq
+local matches = helpers.matches
+local pcall_err = helpers.pcall_err
+
+describe('vim.iter', function()
+ it('filter()', function()
+ local function odd(v)
+ return v % 2 ~= 0
+ end
+
+ local t = { 1, 2, 3, 4, 5 }
+ eq({ 1, 3, 5 }, vim.iter(t):filter(odd):totable())
+ eq({ 2, 4 }, vim.iter(t):filter(function(v) return not odd(v) end):totable())
+ eq({}, vim.iter(t):filter(function(v) return v > 5 end):totable())
+
+ do
+ local it = vim.iter(ipairs(t))
+ it:filter(function(i, v) return i > 1 and v < 5 end)
+ it:map(function(_, v) return v * 2 end)
+ eq({ 4, 6, 8 }, it:totable())
+ end
+
+ local it = vim.iter(string.gmatch('the quick brown fox', '%w+'))
+ eq({'the', 'fox'}, it:filter(function(s) return #s <= 3 end):totable())
+ end)
+
+ it('map()', function()
+ local t = { 1, 2, 3, 4, 5 }
+ eq(
+ { 2, 4, 6, 8, 10 },
+ vim
+ .iter(t)
+ :map(function(v)
+ return 2 * v
+ end)
+ :totable()
+ )
+
+ local it = vim.gsplit(
+ [[
+ Line 1
+ Line 2
+ Line 3
+ Line 4
+ ]],
+ '\n'
+ )
+
+ eq(
+ { 'Lion 2', 'Lion 4' },
+ vim
+ .iter(it)
+ :map(function(s)
+ local lnum = s:match('(%d+)')
+ if lnum and tonumber(lnum) % 2 == 0 then
+ return vim.trim(s:gsub('Line', 'Lion'))
+ end
+ end)
+ :totable()
+ )
+ end)
+
+ it('for loops', function()
+ local t = {1, 2, 3, 4, 5}
+ local acc = 0
+ for v in vim.iter(t):map(function(v) return v * 3 end) do
+ acc = acc + v
+ end
+ eq(45, acc)
+ end)
+
+ it('totable()', function()
+ do
+ local it = vim.iter({1, 2, 3}):map(function(v) return v, v*v end)
+ eq({{1, 1}, {2, 4}, {3, 9}}, it:totable())
+ end
+
+ do
+ local it = vim.iter(string.gmatch('1,4,lol,17,blah,2,9,3', '%d+')):map(tonumber)
+ eq({1, 4, 17, 2, 9, 3}, it:totable())
+ end
+ end)
+
+ it('next()', function()
+ local it = vim.iter({1, 2, 3}):map(function(v) return 2 * v end)
+ eq(2, it:next())
+ eq(4, it:next())
+ eq(6, it:next())
+ eq(nil, it:next())
+ end)
+
+ it('rev()', function()
+ eq({3, 2, 1}, vim.iter({1, 2, 3}):rev():totable())
+
+ local it = vim.iter(string.gmatch("abc", "%w"))
+ matches('rev%(%) requires a list%-like table', pcall_err(it.rev, it))
+ end)
+
+ it('skip()', function()
+ do
+ local t = {4, 3, 2, 1}
+ eq(t, vim.iter(t):skip(0):totable())
+ eq({3, 2, 1}, vim.iter(t):skip(1):totable())
+ eq({2, 1}, vim.iter(t):skip(2):totable())
+ eq({1}, vim.iter(t):skip(#t - 1):totable())
+ eq({}, vim.iter(t):skip(#t):totable())
+ eq({}, vim.iter(t):skip(#t + 1):totable())
+ end
+
+ do
+ local function skip(n)
+ return vim.iter(vim.gsplit('a|b|c|d', '|')):skip(n):totable()
+ end
+ eq({'a', 'b', 'c', 'd'}, skip(0))
+ eq({'b', 'c', 'd'}, skip(1))
+ eq({'c', 'd'}, skip(2))
+ eq({'d'}, skip(3))
+ eq({}, skip(4))
+ eq({}, skip(5))
+ end
+ end)
+
+ it('skipback()', function()
+ do
+ local t = {4, 3, 2, 1}
+ eq(t, vim.iter(t):skipback(0):totable())
+ eq({4, 3, 2}, vim.iter(t):skipback(1):totable())
+ eq({4, 3}, vim.iter(t):skipback(2):totable())
+ eq({4}, vim.iter(t):skipback(#t - 1):totable())
+ eq({}, vim.iter(t):skipback(#t):totable())
+ eq({}, vim.iter(t):skipback(#t + 1):totable())
+ end
+
+ local it = vim.iter(vim.gsplit('a|b|c|d', '|'))
+ matches('skipback%(%) requires a list%-like table', pcall_err(it.skipback, it, 0))
+ end)
+
+ it('slice()', function()
+ local t = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
+ eq({3, 4, 5, 6, 7}, vim.iter(t):slice(3, 7):totable())
+ eq({}, vim.iter(t):slice(6, 5):totable())
+ eq({}, vim.iter(t):slice(0, 0):totable())
+ eq({1}, vim.iter(t):slice(1, 1):totable())
+ eq({1, 2}, vim.iter(t):slice(1, 2):totable())
+ eq({10}, vim.iter(t):slice(10, 10):totable())
+ eq({8, 9, 10}, vim.iter(t):slice(8, 11):totable())
+ end)
+
+ it('nth()', function()
+ do
+ local t = {4, 3, 2, 1}
+ eq(nil, vim.iter(t):nth(0))
+ eq(4, vim.iter(t):nth(1))
+ eq(3, vim.iter(t):nth(2))
+ eq(2, vim.iter(t):nth(3))
+ eq(1, vim.iter(t):nth(4))
+ eq(nil, vim.iter(t):nth(5))
+ end
+
+ do
+ local function nth(n)
+ return vim.iter(vim.gsplit('a|b|c|d', '|')):nth(n)
+ end
+ eq(nil, nth(0))
+ eq('a', nth(1))
+ eq('b', nth(2))
+ eq('c', nth(3))
+ eq('d', nth(4))
+ eq(nil, nth(5))
+ end
+ end)
+
+ it('nthback()', function()
+ do
+ local t = {4, 3, 2, 1}
+ eq(nil, vim.iter(t):nthback(0))
+ eq(1, vim.iter(t):nthback(1))
+ eq(2, vim.iter(t):nthback(2))
+ eq(3, vim.iter(t):nthback(3))
+ eq(4, vim.iter(t):nthback(4))
+ eq(nil, vim.iter(t):nthback(5))
+ end
+
+ local it = vim.iter(vim.gsplit('a|b|c|d', '|'))
+ matches('skipback%(%) requires a list%-like table', pcall_err(it.nthback, it, 1))
+ end)
+
+ it('any()', function()
+ local function odd(v)
+ return v % 2 ~= 0
+ end
+
+ do
+ local t = { 4, 8, 9, 10 }
+ eq(true, vim.iter(t):any(odd))
+ end
+
+ do
+ local t = { 4, 8, 10 }
+ eq(false, vim.iter(t):any(odd))
+ end
+
+ do
+ eq(true, vim.iter(vim.gsplit('a|b|c|d', '|')):any(function(s) return s == 'd' end))
+ eq(false, vim.iter(vim.gsplit('a|b|c|d', '|')):any(function(s) return s == 'e' end))
+ end
+ end)
+
+ it('all()', function()
+ local function odd(v)
+ return v % 2 ~= 0
+ end
+
+ do
+ local t = { 3, 5, 7, 9 }
+ eq(true, vim.iter(t):all(odd))
+ end
+
+ do
+ local t = { 3, 5, 7, 10 }
+ eq(false, vim.iter(t):all(odd))
+ end
+
+ do
+ eq(true, vim.iter(vim.gsplit('a|a|a|a', '|')):all(function(s) return s == 'a' end))
+ eq(false, vim.iter(vim.gsplit('a|a|a|b', '|')):all(function(s) return s == 'a' end))
+ end
+ end)
+
+ it('last()', function()
+ local s = 'abcdefghijklmnopqrstuvwxyz'
+ eq('z', vim.iter(vim.split(s, '')):last())
+ eq('z', vim.iter(vim.gsplit(s, '')):last())
+ end)
+
+ it('enumerate()', function()
+ local it = vim.iter(vim.gsplit('abc', '')):enumerate()
+ eq({1, 'a'}, {it:next()})
+ eq({2, 'b'}, {it:next()})
+ eq({3, 'c'}, {it:next()})
+ eq({}, {it:next()})
+ end)
+
+ it('peek()', function()
+ do
+ local it = vim.iter({ 3, 6, 9, 12 })
+ eq(3, it:peek())
+ eq(3, it:peek())
+ eq(3, it:next())
+ end
+
+ do
+ local it = vim.iter(vim.gsplit('hi', ''))
+ matches('peek%(%) requires a list%-like table', pcall_err(it.peek, it))
+ end
+ end)
+
+ it('find()', function()
+ local t = {3, 6, 9, 12}
+ eq(12, vim.iter(t):find(12))
+ eq(nil, vim.iter(t):find(15))
+ eq(12, vim.iter(t):find(function(v) return v % 4 == 0 end))
+
+ do
+ local it = vim.iter(t)
+ local pred = function(v) return v % 3 == 0 end
+ eq(3, it:find(pred))
+ eq(6, it:find(pred))
+ eq(9, it:find(pred))
+ eq(12, it:find(pred))
+ eq(nil, it:find(pred))
+ end
+
+ do
+ local it = vim.iter(vim.gsplit('AbCdE', ''))
+ local pred = function(s) return s:match('[A-Z]') end
+ eq('A', it:find(pred))
+ eq('C', it:find(pred))
+ eq('E', it:find(pred))
+ eq(nil, it:find(pred))
+ end
+ end)
+
+ it('rfind()', function()
+ local t = {1, 2, 3, 2, 1}
+ do
+ local it = vim.iter(t)
+ eq(1, it:rfind(1))
+ eq(1, it:rfind(1))
+ eq(nil, it:rfind(1))
+ end
+
+ do
+ local it = vim.iter(t):enumerate()
+ local pred = function(i) return i % 2 ~= 0 end
+ eq({5, 1}, {it:rfind(pred)})
+ eq({3, 3}, {it:rfind(pred)})
+ eq({1, 1}, {it:rfind(pred)})
+ eq(nil, it:rfind(pred))
+ end
+
+ do
+ local it = vim.iter(vim.gsplit('AbCdE', ''))
+ matches('rfind%(%) requires a list%-like table', pcall_err(it.rfind, it, 'E'))
+ end
+ end)
+
+ it('nextback()', function()
+ do
+ local it = vim.iter({ 1, 2, 3, 4 })
+ eq(4, it:nextback())
+ eq(3, it:nextback())
+ eq(2, it:nextback())
+ eq(1, it:nextback())
+ eq(nil, it:nextback())
+ eq(nil, it:nextback())
+ end
+
+ do
+ local it = vim.iter(vim.gsplit('hi', ''))
+ matches('nextback%(%) requires a list%-like table', pcall_err(it.nextback, it))
+ end
+ end)
+
+ it('peekback()', function()
+ do
+ local it = vim.iter({ 1, 2, 3, 4 })
+ eq(4, it:peekback())
+ eq(4, it:peekback())
+ eq(4, it:nextback())
+ end
+
+ do
+ local it = vim.iter(vim.gsplit('hi', ''))
+ matches('peekback%(%) requires a list%-like table', pcall_err(it.peekback, it))
+ end
+ end)
+
+ it('fold()', function()
+ local t = {1, 2, 3, 4, 5}
+ eq(115, vim.iter(t):fold(100, function(acc, v) return acc + v end))
+ eq({5, 4, 3, 2, 1}, vim.iter(t):fold({}, function(acc, v)
+ table.insert(acc, 1, v)
+ return acc
+ end))
+ end)
+
+ it('handles map-like tables', function()
+ local it = vim.iter({ a = 1, b = 2, c = 3 }):map(function(k, v)
+ if v % 2 ~= 0 then
+ return k:upper(), v * 2
+ end
+ end)
+
+ local t = it:fold({}, function(t, k, v)
+ t[k] = v
+ return t
+ end)
+ eq({ A = 2, C = 6 }, t)
+ end)
+
+ it('handles table values mid-pipeline', function()
+ local map = {
+ item = {
+ file = 'test',
+ },
+ item_2 = {
+ file = 'test',
+ },
+ item_3 = {
+ file = 'test',
+ },
+ }
+
+ local output = vim.iter(map):map(function(key, value)
+ return { [key] = value.file }
+ end):totable()
+
+ table.sort(output, function(a, b)
+ return next(a) < next(b)
+ end)
+
+ eq({
+ { item = 'test' },
+ { item_2 = 'test' },
+ { item_3 = 'test' },
+ }, output)
+ end)
+
+ it('handles nil values', function()
+ local t = {1, 2, 3, 4, 5}
+ do
+ local it = vim.iter(t):enumerate():map(function(i, v)
+ if i % 2 == 0 then
+ return nil, v*v
+ end
+ return v, nil
+ end)
+ eq({
+ { [1] = 1 },
+ { [2] = 4 },
+ { [1] = 3 },
+ { [2] = 16 },
+ { [1] = 5 },
+ }, it:totable())
+ end
+
+ do
+ local it = vim.iter(ipairs(t)):map(function(i, v)
+ if i % 2 == 0 then
+ return nil, v*v
+ end
+ return v, nil
+ end)
+ eq({
+ { [1] = 1 },
+ { [2] = 4 },
+ { [1] = 3 },
+ { [2] = 16 },
+ { [1] = 5 },
+ }, it:totable())
+ end
+ end)
+end)
diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua
index 42927f7e1b..53c21fd668 100644
--- a/test/functional/lua/vim_spec.lua
+++ b/test/functional/lua/vim_spec.lua
@@ -3031,427 +3031,6 @@ describe('lua stdlib', function()
eq(false, if_nil(d, c))
eq(NIL, if_nil(a))
end)
-
- describe('vim.iter', function()
- it('filter()', function()
- local function odd(v)
- return v % 2 ~= 0
- end
-
- local t = { 1, 2, 3, 4, 5 }
- eq({ 1, 3, 5 }, vim.iter(t):filter(odd):totable())
- eq({ 2, 4 }, vim.iter(t):filter(function(v) return not odd(v) end):totable())
- eq({}, vim.iter(t):filter(function(v) if v > 5 then return v end end):totable())
-
- do
- local it = vim.iter(ipairs(t))
- it:filter(function(i, v) return i > 1 and v < 5 end)
- it:map(function(_, v) return v * 2 end)
- eq({ 4, 6, 8 }, it:totable())
- end
-
- local it = vim.iter(string.gmatch('the quick brown fox', '%w+'))
- eq({'the', 'fox'}, it:filter(function(s) return #s <= 3 end):totable())
- end)
-
- it('map()', function()
- local t = { 1, 2, 3, 4, 5 }
- eq(
- { 2, 4, 6, 8, 10 },
- vim
- .iter(t)
- :map(function(v)
- return 2 * v
- end)
- :totable()
- )
-
- local it = vim.gsplit(
- [[
- Line 1
- Line 2
- Line 3
- Line 4
- ]],
- '\n'
- )
-
- eq(
- { 'Lion 2', 'Lion 4' },
- vim
- .iter(it)
- :map(function(s)
- local lnum = s:match('(%d+)')
- if lnum and tonumber(lnum) % 2 == 0 then
- return vim.trim(s:gsub('Line', 'Lion'))
- end
- end)
- :totable()
- )
- end)
-
- it('for loops', function()
- local t = {1, 2, 3, 4, 5}
- local acc = 0
- for v in vim.iter(t):map(function(v) return v * 3 end) do
- acc = acc + v
- end
- eq(45, acc)
- end)
-
- it('totable()', function()
- do
- local it = vim.iter({1, 2, 3}):map(function(v) return v, v*v end)
- eq({{1, 1}, {2, 4}, {3, 9}}, it:totable())
- end
-
- do
- local it = vim.iter(string.gmatch('1,4,lol,17,blah,2,9,3', '%d+')):map(tonumber)
- eq({1, 4, 17, 2, 9, 3}, it:totable())
- end
- end)
-
- it('next()', function()
- local it = vim.iter({1, 2, 3}):map(function(v) return 2 * v end)
- eq(2, it:next())
- eq(4, it:next())
- eq(6, it:next())
- eq(nil, it:next())
- end)
-
- it('rev()', function()
- eq({3, 2, 1}, vim.iter({1, 2, 3}):rev():totable())
-
- local it = vim.iter(string.gmatch("abc", "%w"))
- matches('rev%(%) requires a list%-like table', pcall_err(it.rev, it))
- end)
-
- it('skip()', function()
- do
- local t = {4, 3, 2, 1}
- eq(t, vim.iter(t):skip(0):totable())
- eq({3, 2, 1}, vim.iter(t):skip(1):totable())
- eq({2, 1}, vim.iter(t):skip(2):totable())
- eq({1}, vim.iter(t):skip(#t - 1):totable())
- eq({}, vim.iter(t):skip(#t):totable())
- eq({}, vim.iter(t):skip(#t + 1):totable())
- end
-
- do
- local function skip(n)
- return vim.iter(vim.gsplit('a|b|c|d', '|')):skip(n):totable()
- end
- eq({'a', 'b', 'c', 'd'}, skip(0))
- eq({'b', 'c', 'd'}, skip(1))
- eq({'c', 'd'}, skip(2))
- eq({'d'}, skip(3))
- eq({}, skip(4))
- eq({}, skip(5))
- end
- end)
-
- it('skipback()', function()
- do
- local t = {4, 3, 2, 1}
- eq(t, vim.iter(t):skipback(0):totable())
- eq({4, 3, 2}, vim.iter(t):skipback(1):totable())
- eq({4, 3}, vim.iter(t):skipback(2):totable())
- eq({4}, vim.iter(t):skipback(#t - 1):totable())
- eq({}, vim.iter(t):skipback(#t):totable())
- eq({}, vim.iter(t):skipback(#t + 1):totable())
- end
-
- local it = vim.iter(vim.gsplit('a|b|c|d', '|'))
- matches('skipback%(%) requires a list%-like table', pcall_err(it.skipback, it, 0))
- end)
-
- it('slice()', function()
- local t = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
- eq({3, 4, 5, 6, 7}, vim.iter(t):slice(3, 7):totable())
- eq({}, vim.iter(t):slice(6, 5):totable())
- eq({}, vim.iter(t):slice(0, 0):totable())
- eq({1}, vim.iter(t):slice(1, 1):totable())
- eq({1, 2}, vim.iter(t):slice(1, 2):totable())
- eq({10}, vim.iter(t):slice(10, 10):totable())
- eq({8, 9, 10}, vim.iter(t):slice(8, 11):totable())
- end)
-
- it('nth()', function()
- do
- local t = {4, 3, 2, 1}
- eq(nil, vim.iter(t):nth(0))
- eq(4, vim.iter(t):nth(1))
- eq(3, vim.iter(t):nth(2))
- eq(2, vim.iter(t):nth(3))
- eq(1, vim.iter(t):nth(4))
- eq(nil, vim.iter(t):nth(5))
- end
-
- do
- local function nth(n)
- return vim.iter(vim.gsplit('a|b|c|d', '|')):nth(n)
- end
- eq(nil, nth(0))
- eq('a', nth(1))
- eq('b', nth(2))
- eq('c', nth(3))
- eq('d', nth(4))
- eq(nil, nth(5))
- end
- end)
-
- it('nthback()', function()
- do
- local t = {4, 3, 2, 1}
- eq(nil, vim.iter(t):nthback(0))
- eq(1, vim.iter(t):nthback(1))
- eq(2, vim.iter(t):nthback(2))
- eq(3, vim.iter(t):nthback(3))
- eq(4, vim.iter(t):nthback(4))
- eq(nil, vim.iter(t):nthback(5))
- end
-
- local it = vim.iter(vim.gsplit('a|b|c|d', '|'))
- matches('skipback%(%) requires a list%-like table', pcall_err(it.nthback, it, 1))
- end)
-
- it('any()', function()
- local function odd(v)
- return v % 2 ~= 0
- end
-
- do
- local t = { 4, 8, 9, 10 }
- eq(true, vim.iter(t):any(odd))
- end
-
- do
- local t = { 4, 8, 10 }
- eq(false, vim.iter(t):any(odd))
- end
-
- do
- eq(true, vim.iter(vim.gsplit('a|b|c|d', '|')):any(function(s) return s == 'd' end))
- eq(false, vim.iter(vim.gsplit('a|b|c|d', '|')):any(function(s) return s == 'e' end))
- end
- end)
-
- it('all()', function()
- local function odd(v)
- return v % 2 ~= 0
- end
-
- do
- local t = { 3, 5, 7, 9 }
- eq(true, vim.iter(t):all(odd))
- end
-
- do
- local t = { 3, 5, 7, 10 }
- eq(false, vim.iter(t):all(odd))
- end
-
- do
- eq(true, vim.iter(vim.gsplit('a|a|a|a', '|')):all(function(s) return s == 'a' end))
- eq(false, vim.iter(vim.gsplit('a|a|a|b', '|')):all(function(s) return s == 'a' end))
- end
- end)
-
- it('last()', function()
- local s = 'abcdefghijklmnopqrstuvwxyz'
- eq('z', vim.iter(vim.split(s, '')):last())
- eq('z', vim.iter(vim.gsplit(s, '')):last())
- end)
-
- it('enumerate()', function()
- local it = vim.iter(vim.gsplit('abc', '')):enumerate()
- eq({1, 'a'}, {it:next()})
- eq({2, 'b'}, {it:next()})
- eq({3, 'c'}, {it:next()})
- eq({}, {it:next()})
- end)
-
- it('peek()', function()
- do
- local it = vim.iter({ 3, 6, 9, 12 })
- eq(3, it:peek())
- eq(3, it:peek())
- eq(3, it:next())
- end
-
- do
- local it = vim.iter(vim.gsplit('hi', ''))
- matches('peek%(%) requires a list%-like table', pcall_err(it.peek, it))
- end
- end)
-
- it('find()', function()
- local t = {3, 6, 9, 12}
- eq(12, vim.iter(t):find(12))
- eq(nil, vim.iter(t):find(15))
- eq(12, vim.iter(t):find(function(v) return v % 4 == 0 end))
-
- do
- local it = vim.iter(t)
- local pred = function(v) return v % 3 == 0 end
- eq(3, it:find(pred))
- eq(6, it:find(pred))
- eq(9, it:find(pred))
- eq(12, it:find(pred))
- eq(nil, it:find(pred))
- end
-
- do
- local it = vim.iter(vim.gsplit('AbCdE', ''))
- local pred = function(s) return s:match('[A-Z]') end
- eq('A', it:find(pred))
- eq('C', it:find(pred))
- eq('E', it:find(pred))
- eq(nil, it:find(pred))
- end
- end)
-
- it('rfind()', function()
- local t = {1, 2, 3, 2, 1}
- do
- local it = vim.iter(t)
- eq(1, it:rfind(1))
- eq(1, it:rfind(1))
- eq(nil, it:rfind(1))
- end
-
- do
- local it = vim.iter(t):enumerate()
- local pred = function(i) return i % 2 ~= 0 end
- eq({5, 1}, {it:rfind(pred)})
- eq({3, 3}, {it:rfind(pred)})
- eq({1, 1}, {it:rfind(pred)})
- eq(nil, it:rfind(pred))
- end
-
- do
- local it = vim.iter(vim.gsplit('AbCdE', ''))
- matches('rfind%(%) requires a list%-like table', pcall_err(it.rfind, it, 'E'))
- end
- end)
-
- it('nextback()', function()
- do
- local it = vim.iter({ 1, 2, 3, 4 })
- eq(4, it:nextback())
- eq(3, it:nextback())
- eq(2, it:nextback())
- eq(1, it:nextback())
- eq(nil, it:nextback())
- eq(nil, it:nextback())
- end
-
- do
- local it = vim.iter(vim.gsplit('hi', ''))
- matches('nextback%(%) requires a list%-like table', pcall_err(it.nextback, it))
- end
- end)
-
- it('peekback()', function()
- do
- local it = vim.iter({ 1, 2, 3, 4 })
- eq(4, it:peekback())
- eq(4, it:peekback())
- eq(4, it:peekback())
- end
-
- do
- local it = vim.iter(vim.gsplit('hi', ''))
- matches('peekback%(%) requires a list%-like table', pcall_err(it.peekback, it))
- end
- end)
-
- it('fold()', function()
- local t = {1, 2, 3, 4, 5}
- eq(115, vim.iter(t):fold(100, function(acc, v) return acc + v end))
- eq({5, 4, 3, 2, 1}, vim.iter(t):fold({}, function(acc, v)
- table.insert(acc, 1, v)
- return acc
- end))
- end)
-
- it('handles map-like tables', function()
- local it = vim.iter({ a = 1, b = 2, c = 3 }):map(function(k, v)
- if v % 2 ~= 0 then
- return k:upper(), v * 2
- end
- end)
-
- local t = it:fold({}, function(t, k, v)
- t[k] = v
- return t
- end)
- eq({ A = 2, C = 6 }, t)
- end)
-
- it('handles table values mid-pipeline', function()
- local map = {
- item = {
- file = 'test',
- },
- item_2 = {
- file = 'test',
- },
- item_3 = {
- file = 'test',
- },
- }
-
- local output = vim.iter(map):map(function(key, value)
- return { [key] = value.file }
- end):totable()
-
- table.sort(output, function(a, b)
- return next(a) < next(b)
- end)
-
- eq({
- { item = 'test' },
- { item_2 = 'test' },
- { item_3 = 'test' },
- }, output)
- end)
-
- it('handles nil values', function()
- local t = {1, 2, 3, 4, 5}
- do
- local it = vim.iter(t):enumerate():map(function(i, v)
- if i % 2 == 0 then
- return nil, v*v
- end
- return v, nil
- end)
- eq({
- { [1] = 1 },
- { [2] = 4 },
- { [1] = 3 },
- { [2] = 16 },
- { [1] = 5 },
- }, it:totable())
- end
-
- do
- local it = vim.iter(ipairs(t)):map(function(i, v)
- if i % 2 == 0 then
- return nil, v*v
- end
- return v, nil
- end)
- eq({
- { [1] = 1 },
- { [2] = 4 },
- { [1] = 3 },
- { [2] = 16 },
- { [1] = 5 },
- }, it:totable())
- end
- end)
- end)
end)
describe('lua: builtin modules', function()
diff --git a/test/functional/vimscript/eval_spec.lua b/test/functional/vimscript/eval_spec.lua
index b411b1e379..b3f2c1bfeb 100644
--- a/test/functional/vimscript/eval_spec.lua
+++ b/test/functional/vimscript/eval_spec.lua
@@ -191,11 +191,10 @@ describe('listing functions using :function', function()
endfunction]]):format(num), exec_capture(('function <lambda>%s'):format(num)))
end)
- -- FIXME: If the same function is deleted, the crash still happens. #20790
it('does not crash if another function is deleted while listing', function()
local screen = Screen.new(80, 24)
screen:attach()
- matches('Vim%(function%):E454: function list was modified', pcall_err(exec_lua, [=[
+ matches('Vim%(function%):E454: Function list was modified$', pcall_err(exec_lua, [=[
vim.cmd([[
func Func1()
endfunc
@@ -219,6 +218,34 @@ describe('listing functions using :function', function()
]=]))
assert_alive()
end)
+
+ it('does not crash if the same function is deleted while listing', function()
+ local screen = Screen.new(80, 24)
+ screen:attach()
+ matches('Vim%(function%):E454: Function list was modified$', pcall_err(exec_lua, [=[
+ vim.cmd([[
+ func Func1()
+ endfunc
+ func Func2()
+ endfunc
+ func Func3()
+ endfunc
+ ]])
+
+ local ns = vim.api.nvim_create_namespace('test')
+
+ vim.ui_attach(ns, { ext_messages = true }, function(event, _, content)
+ if event == 'msg_show' and content[1][2] == 'function Func1()' then
+ vim.cmd('delfunc Func2')
+ end
+ end)
+
+ vim.cmd('function')
+
+ vim.ui_detach(ns)
+ ]=]))
+ assert_alive()
+ end)
end)
it('no double-free in garbage collection #16287', function()
diff --git a/test/old/testdir/test_assert.vim b/test/old/testdir/test_assert.vim
index 431908e95c..4386492339 100644
--- a/test/old/testdir/test_assert.vim
+++ b/test/old/testdir/test_assert.vim
@@ -337,6 +337,16 @@ func Test_assert_with_msg()
call remove(v:errors, 0)
endfunc
+func Test_override()
+ throw 'Skipped: Nvim does not support test_override()'
+ call test_override('char_avail', 1)
+ eval 1->test_override('redraw')
+ call test_override('ALL', 0)
+ call assert_fails("call test_override('xxx', 1)", 'E475:')
+ call assert_fails("call test_override('redraw', 'yes')", 'E474:')
+ call assert_fails("call test_override('redraw', 'yes')", 'E1210:')
+endfunc
+
func Test_mouse_position()
let save_mouse = &mouse
set mouse=a
diff --git a/test/old/testdir/test_charsearch.vim b/test/old/testdir/test_charsearch.vim
index f8f3e958ca..8d6b405743 100644
--- a/test/old/testdir/test_charsearch.vim
+++ b/test/old/testdir/test_charsearch.vim
@@ -39,7 +39,7 @@ func Test_charsearch()
call setcharsearch({'char' : ''})
call assert_equal('', getcharsearch().char)
- call assert_fails("call setcharsearch([])", 'E715:')
+ call assert_fails("call setcharsearch([])", 'E1206:')
enew!
endfunc
diff --git a/test/old/testdir/test_cmdline.vim b/test/old/testdir/test_cmdline.vim
index 9d506a66c0..9ed49b44c0 100644
--- a/test/old/testdir/test_cmdline.vim
+++ b/test/old/testdir/test_cmdline.vim
@@ -652,7 +652,7 @@ func Test_getcompletion()
call assert_fails("call getcompletion('\\\\@!\\\\@=', 'buffer')", 'E871:')
call assert_fails('call getcompletion("", "burp")', 'E475:')
- call assert_fails('call getcompletion("abc", [])', 'E475:')
+ call assert_fails('call getcompletion("abc", [])', 'E1174:')
endfunc
" Test for getcompletion() with "fuzzy" in 'wildoptions'
diff --git a/test/old/testdir/test_expr.vim b/test/old/testdir/test_expr.vim
index dee7266bb5..ff3dfb83cb 100644
--- a/test/old/testdir/test_expr.vim
+++ b/test/old/testdir/test_expr.vim
@@ -105,7 +105,7 @@ func Test_dict()
END
call CheckLegacyAndVim9Success(lines)
- call CheckLegacyAndVim9Failure(["VAR i = has_key([], 'a')"], ['E715:', 'E1013:', 'E1206:'])
+ call CheckLegacyAndVim9Failure(["VAR i = has_key([], 'a')"], ['E1206:', 'E1013:', 'E1206:'])
endfunc
func Test_strgetchar()
diff --git a/test/old/testdir/test_functions.vim b/test/old/testdir/test_functions.vim
index 7d3d74caad..b934f7fac2 100644
--- a/test/old/testdir/test_functions.vim
+++ b/test/old/testdir/test_functions.vim
@@ -1064,19 +1064,14 @@ func Test_byte2line_line2byte()
bw!
endfunc
-" Test for byteidx() and byteidxcomp() functions
+" Test for byteidx() using a character index
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))
@@ -1084,18 +1079,184 @@ func Test_byteidx()
call assert_equal(4, b->byteidx(2))
call assert_equal(5, b->byteidx(3))
call assert_equal(-1, b->byteidx(4))
+
+ " string with multiple composing characters
+ let str = '-ą́-ą́'
+ call assert_equal(0, byteidx(str, 0))
+ call assert_equal(1, byteidx(str, 1))
+ call assert_equal(6, byteidx(str, 2))
+ call assert_equal(7, byteidx(str, 3))
+ call assert_equal(12, byteidx(str, 4))
+ call assert_equal(-1, byteidx(str, 5))
+
+ " empty string
+ call assert_equal(0, byteidx('', 0))
+ call assert_equal(-1, byteidx('', 1))
+
+ " error cases
call assert_fails("call byteidx([], 0)", 'E730:')
+ call assert_fails("call byteidx('abc', [])", 'E745:')
+endfunc
+
+" Test for byteidxcomp() using a character index
+func Test_byteidxcomp()
+ let a = '.é.' " one char of two bytes
+ call assert_equal(0, byteidxcomp(a, 0))
+ call assert_equal(1, byteidxcomp(a, 1))
+ call assert_equal(3, byteidxcomp(a, 2))
+ call assert_equal(4, byteidxcomp(a, 3))
+ call assert_equal(-1, byteidxcomp(a, 4))
+ let b = '.é.' " normal e with composing char
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))
+
+ " string with multiple composing characters
+ let str = '-ą́-ą́'
+ call assert_equal(0, byteidxcomp(str, 0))
+ call assert_equal(1, byteidxcomp(str, 1))
+ call assert_equal(2, byteidxcomp(str, 2))
+ call assert_equal(4, byteidxcomp(str, 3))
+ call assert_equal(6, byteidxcomp(str, 4))
+ call assert_equal(7, byteidxcomp(str, 5))
+ call assert_equal(8, byteidxcomp(str, 6))
+ call assert_equal(10, byteidxcomp(str, 7))
+ call assert_equal(12, byteidxcomp(str, 8))
+ call assert_equal(-1, byteidxcomp(str, 9))
+
+ " empty string
+ call assert_equal(0, byteidxcomp('', 0))
+ call assert_equal(-1, byteidxcomp('', 1))
+
+ " error cases
call assert_fails("call byteidxcomp([], 0)", 'E730:')
+ call assert_fails("call byteidxcomp('abc', [])", 'E745:')
endfunc
-" Test for charidx()
+" Test for byteidx() using a UTF-16 index
+func Test_byteidx_from_utf16_index()
+ " string with single byte characters
+ let str = "abc"
+ for i in range(3)
+ call assert_equal(i, byteidx(str, i, v:true))
+ endfor
+ call assert_equal(3, byteidx(str, 3, v:true))
+ call assert_equal(-1, byteidx(str, 4, v:true))
+
+ " string with two byte characters
+ let str = "a©©b"
+ call assert_equal(0, byteidx(str, 0, v:true))
+ call assert_equal(1, byteidx(str, 1, v:true))
+ call assert_equal(3, byteidx(str, 2, v:true))
+ call assert_equal(5, byteidx(str, 3, v:true))
+ call assert_equal(6, byteidx(str, 4, v:true))
+ call assert_equal(-1, byteidx(str, 5, v:true))
+
+ " string with two byte characters
+ let str = "a😊😊b"
+ call assert_equal(0, byteidx(str, 0, v:true))
+ call assert_equal(1, byteidx(str, 1, v:true))
+ call assert_equal(1, byteidx(str, 2, v:true))
+ call assert_equal(5, byteidx(str, 3, v:true))
+ call assert_equal(5, byteidx(str, 4, v:true))
+ call assert_equal(9, byteidx(str, 5, v:true))
+ call assert_equal(10, byteidx(str, 6, v:true))
+ call assert_equal(-1, byteidx(str, 7, v:true))
+
+ " string with composing characters
+ let str = '-á-b́'
+ call assert_equal(0, byteidx(str, 0, v:true))
+ call assert_equal(1, byteidx(str, 1, v:true))
+ call assert_equal(4, byteidx(str, 2, v:true))
+ call assert_equal(5, byteidx(str, 3, v:true))
+ call assert_equal(8, byteidx(str, 4, v:true))
+ call assert_equal(-1, byteidx(str, 5, v:true))
+
+ " string with multiple composing characters
+ let str = '-ą́-ą́'
+ call assert_equal(0, byteidx(str, 0, v:true))
+ call assert_equal(1, byteidx(str, 1, v:true))
+ call assert_equal(6, byteidx(str, 2, v:true))
+ call assert_equal(7, byteidx(str, 3, v:true))
+ call assert_equal(12, byteidx(str, 4, v:true))
+ call assert_equal(-1, byteidx(str, 5, v:true))
+
+ " empty string
+ call assert_equal(0, byteidx('', 0, v:true))
+ call assert_equal(-1, byteidx('', 1, v:true))
+
+ " error cases
+ call assert_fails('call byteidx(str, 0, [])', 'E745:')
+endfunc
+
+" Test for byteidxcomp() using a UTF-16 index
+func Test_byteidxcomp_from_utf16_index()
+ " string with single byte characters
+ let str = "abc"
+ for i in range(3)
+ call assert_equal(i, byteidxcomp(str, i, v:true))
+ endfor
+ call assert_equal(3, byteidxcomp(str, 3, v:true))
+ call assert_equal(-1, byteidxcomp(str, 4, v:true))
+
+ " string with two byte characters
+ let str = "a©©b"
+ call assert_equal(0, byteidxcomp(str, 0, v:true))
+ call assert_equal(1, byteidxcomp(str, 1, v:true))
+ call assert_equal(3, byteidxcomp(str, 2, v:true))
+ call assert_equal(5, byteidxcomp(str, 3, v:true))
+ call assert_equal(6, byteidxcomp(str, 4, v:true))
+ call assert_equal(-1, byteidxcomp(str, 5, v:true))
+
+ " string with two byte characters
+ let str = "a😊😊b"
+ call assert_equal(0, byteidxcomp(str, 0, v:true))
+ call assert_equal(1, byteidxcomp(str, 1, v:true))
+ call assert_equal(1, byteidxcomp(str, 2, v:true))
+ call assert_equal(5, byteidxcomp(str, 3, v:true))
+ call assert_equal(5, byteidxcomp(str, 4, v:true))
+ call assert_equal(9, byteidxcomp(str, 5, v:true))
+ call assert_equal(10, byteidxcomp(str, 6, v:true))
+ call assert_equal(-1, byteidxcomp(str, 7, v:true))
+
+ " string with composing characters
+ let str = '-á-b́'
+ call assert_equal(0, byteidxcomp(str, 0, v:true))
+ call assert_equal(1, byteidxcomp(str, 1, v:true))
+ call assert_equal(2, byteidxcomp(str, 2, v:true))
+ call assert_equal(4, byteidxcomp(str, 3, v:true))
+ call assert_equal(5, byteidxcomp(str, 4, v:true))
+ call assert_equal(6, byteidxcomp(str, 5, v:true))
+ call assert_equal(8, byteidxcomp(str, 6, v:true))
+ call assert_equal(-1, byteidxcomp(str, 7, v:true))
+ call assert_fails('call byteidxcomp(str, 0, [])', 'E745:')
+
+ " string with multiple composing characters
+ let str = '-ą́-ą́'
+ call assert_equal(0, byteidxcomp(str, 0, v:true))
+ call assert_equal(1, byteidxcomp(str, 1, v:true))
+ call assert_equal(2, byteidxcomp(str, 2, v:true))
+ call assert_equal(4, byteidxcomp(str, 3, v:true))
+ call assert_equal(6, byteidxcomp(str, 4, v:true))
+ call assert_equal(7, byteidxcomp(str, 5, v:true))
+ call assert_equal(8, byteidxcomp(str, 6, v:true))
+ call assert_equal(10, byteidxcomp(str, 7, v:true))
+ call assert_equal(12, byteidxcomp(str, 8, v:true))
+ call assert_equal(-1, byteidxcomp(str, 9, v:true))
+
+ " empty string
+ call assert_equal(0, byteidxcomp('', 0, v:true))
+ call assert_equal(-1, byteidxcomp('', 1, v:true))
+
+ " error cases
+ call assert_fails('call byteidxcomp(str, 0, [])', 'E745:')
+endfunc
+
+" Test for charidx() using a byte index
func Test_charidx()
let a = 'xáb́y'
call assert_equal(0, charidx(a, 0))
@@ -1104,22 +1265,256 @@ func Test_charidx()
call assert_equal(3, charidx(a, 7))
call assert_equal(-1, charidx(a, 8))
call assert_equal(-1, charidx(a, -1))
- call assert_equal(-1, charidx('', 0))
- call assert_equal(-1, charidx(v:_null_string, 0))
" count composing characters
- call assert_equal(0, charidx(a, 0, 1))
- call assert_equal(2, charidx(a, 2, 1))
- call assert_equal(3, charidx(a, 4, 1))
- call assert_equal(5, charidx(a, 7, 1))
- call assert_equal(-1, charidx(a, 8, 1))
+ call assert_equal(0, a->charidx(0, 1))
+ call assert_equal(2, a->charidx(2, 1))
+ call assert_equal(3, a->charidx(4, 1))
+ call assert_equal(5, a->charidx(7, 1))
+ call assert_equal(-1, a->charidx(8, 1))
+
+ " empty string
+ call assert_equal(-1, charidx('', 0))
call assert_equal(-1, charidx('', 0, 1))
- call assert_fails('let x = charidx([], 1)', 'E474:')
- call assert_fails('let x = charidx("abc", [])', 'E474:')
- call assert_fails('let x = charidx("abc", 1, [])', 'E474:')
- call assert_fails('let x = charidx("abc", 1, -1)', 'E1023:')
- call assert_fails('let x = charidx("abc", 1, 2)', 'E1023:')
+ " error cases
+ call assert_equal(-1, charidx(v:_null_string, 0))
+ call assert_fails('let x = charidx([], 1)', 'E1174:')
+ call assert_fails('let x = charidx("abc", [])', 'E1210:')
+ call assert_fails('let x = charidx("abc", 1, [])', 'E1212:')
+ call assert_fails('let x = charidx("abc", 1, -1)', 'E1212:')
+ call assert_fails('let x = charidx("abc", 1, 2)', 'E1212:')
+endfunc
+
+" Test for charidx() using a UTF-16 index
+func Test_charidx_from_utf16_index()
+ " string with single byte characters
+ let str = "abc"
+ for i in range(3)
+ call assert_equal(i, charidx(str, i, v:false, v:true))
+ endfor
+ call assert_equal(-1, charidx(str, 3, v:false, v:true))
+
+ " string with two byte characters
+ let str = "a©©b"
+ call assert_equal(0, charidx(str, 0, v:false, v:true))
+ call assert_equal(1, charidx(str, 1, v:false, v:true))
+ call assert_equal(2, charidx(str, 2, v:false, v:true))
+ call assert_equal(3, charidx(str, 3, v:false, v:true))
+ call assert_equal(-1, charidx(str, 4, v:false, v:true))
+
+ " string with four byte characters
+ let str = "a😊😊b"
+ call assert_equal(0, charidx(str, 0, v:false, v:true))
+ call assert_equal(1, charidx(str, 1, v:false, v:true))
+ call assert_equal(1, charidx(str, 2, v:false, v:true))
+ call assert_equal(2, charidx(str, 3, v:false, v:true))
+ call assert_equal(2, charidx(str, 4, v:false, v:true))
+ call assert_equal(3, charidx(str, 5, v:false, v:true))
+ call assert_equal(-1, charidx(str, 6, v:false, v:true))
+
+ " string with composing characters
+ let str = '-á-b́'
+ for i in str->strcharlen()->range()
+ call assert_equal(i, charidx(str, i, v:false, v:true))
+ endfor
+ call assert_equal(-1, charidx(str, 4, v:false, v:true))
+ for i in str->strchars()->range()
+ call assert_equal(i, charidx(str, i, v:true, v:true))
+ endfor
+ call assert_equal(-1, charidx(str, 6, v:true, v:true))
+
+ " string with multiple composing characters
+ let str = '-ą́-ą́'
+ for i in str->strcharlen()->range()
+ call assert_equal(i, charidx(str, i, v:false, v:true))
+ endfor
+ call assert_equal(-1, charidx(str, 4, v:false, v:true))
+ for i in str->strchars()->range()
+ call assert_equal(i, charidx(str, i, v:true, v:true))
+ endfor
+ call assert_equal(-1, charidx(str, 8, v:true, v:true))
+
+ " empty string
+ call assert_equal(-1, charidx('', 0, v:false, v:true))
+ call assert_equal(-1, charidx('', 0, v:true, v:true))
+
+ " error cases
+ call assert_equal(-1, charidx('', 0, v:false, v:true))
+ call assert_equal(-1, charidx('', 0, v:true, v:true))
+ call assert_equal(-1, charidx(v:_null_string, 0, v:false, v:true))
+ call assert_fails('let x = charidx("abc", 1, v:false, [])', 'E1212:')
+ call assert_fails('let x = charidx("abc", 1, v:true, [])', 'E1212:')
+endfunc
+
+" Test for utf16idx() using a byte index
+func Test_utf16idx_from_byteidx()
+ " UTF-16 index of a string with single byte characters
+ let str = "abc"
+ for i in range(3)
+ call assert_equal(i, utf16idx(str, i))
+ endfor
+ call assert_equal(-1, utf16idx(str, 3))
+
+ " UTF-16 index of a string with two byte characters
+ let str = 'a©©b'
+ call assert_equal(0, str->utf16idx(0))
+ call assert_equal(1, str->utf16idx(1))
+ call assert_equal(1, str->utf16idx(2))
+ call assert_equal(2, str->utf16idx(3))
+ call assert_equal(2, str->utf16idx(4))
+ call assert_equal(3, str->utf16idx(5))
+ call assert_equal(-1, str->utf16idx(6))
+
+ " UTF-16 index of a string with four byte characters
+ let str = 'a😊😊b'
+ call assert_equal(0, utf16idx(str, 0))
+ call assert_equal(2, utf16idx(str, 1))
+ call assert_equal(2, utf16idx(str, 2))
+ call assert_equal(2, utf16idx(str, 3))
+ call assert_equal(2, utf16idx(str, 4))
+ call assert_equal(4, utf16idx(str, 5))
+ call assert_equal(4, utf16idx(str, 6))
+ call assert_equal(4, utf16idx(str, 7))
+ call assert_equal(4, utf16idx(str, 8))
+ call assert_equal(5, utf16idx(str, 9))
+ call assert_equal(-1, utf16idx(str, 10))
+
+ " UTF-16 index of a string with composing characters
+ let str = '-á-b́'
+ call assert_equal(0, utf16idx(str, 0))
+ call assert_equal(1, utf16idx(str, 1))
+ call assert_equal(1, utf16idx(str, 2))
+ call assert_equal(1, utf16idx(str, 3))
+ call assert_equal(2, utf16idx(str, 4))
+ call assert_equal(3, utf16idx(str, 5))
+ call assert_equal(3, utf16idx(str, 6))
+ call assert_equal(3, utf16idx(str, 7))
+ call assert_equal(-1, utf16idx(str, 8))
+ call assert_equal(0, utf16idx(str, 0, v:true))
+ call assert_equal(1, utf16idx(str, 1, v:true))
+ call assert_equal(2, utf16idx(str, 2, v:true))
+ call assert_equal(2, utf16idx(str, 3, v:true))
+ call assert_equal(3, utf16idx(str, 4, v:true))
+ call assert_equal(4, utf16idx(str, 5, v:true))
+ call assert_equal(5, utf16idx(str, 6, v:true))
+ call assert_equal(5, utf16idx(str, 7, v:true))
+ call assert_equal(-1, utf16idx(str, 8, v:true))
+
+ " string with multiple composing characters
+ let str = '-ą́-ą́'
+ call assert_equal(0, utf16idx(str, 0))
+ call assert_equal(1, utf16idx(str, 1))
+ call assert_equal(1, utf16idx(str, 2))
+ call assert_equal(1, utf16idx(str, 3))
+ call assert_equal(1, utf16idx(str, 4))
+ call assert_equal(1, utf16idx(str, 5))
+ call assert_equal(2, utf16idx(str, 6))
+ call assert_equal(3, utf16idx(str, 7))
+ call assert_equal(3, utf16idx(str, 8))
+ call assert_equal(3, utf16idx(str, 9))
+ call assert_equal(3, utf16idx(str, 10))
+ call assert_equal(3, utf16idx(str, 11))
+ call assert_equal(-1, utf16idx(str, 12))
+ call assert_equal(0, utf16idx(str, 0, v:true))
+ call assert_equal(1, utf16idx(str, 1, v:true))
+ call assert_equal(2, utf16idx(str, 2, v:true))
+ call assert_equal(2, utf16idx(str, 3, v:true))
+ call assert_equal(3, utf16idx(str, 4, v:true))
+ call assert_equal(3, utf16idx(str, 5, v:true))
+ call assert_equal(4, utf16idx(str, 6, v:true))
+ call assert_equal(5, utf16idx(str, 7, v:true))
+ call assert_equal(6, utf16idx(str, 8, v:true))
+ call assert_equal(6, utf16idx(str, 9, v:true))
+ call assert_equal(7, utf16idx(str, 10, v:true))
+ call assert_equal(7, utf16idx(str, 11, v:true))
+ call assert_equal(-1, utf16idx(str, 12, v:true))
+
+ " empty string
+ call assert_equal(-1, utf16idx('', 0))
+ call assert_equal(-1, utf16idx('', 0, v:true))
+
+ " error cases
+ call assert_equal(-1, utf16idx("", 0))
+ call assert_equal(-1, utf16idx("abc", -1))
+ call assert_equal(-1, utf16idx(v:_null_string, 0))
+ call assert_fails('let l = utf16idx([], 0)', 'E1174:')
+ call assert_fails('let l = utf16idx("ab", [])', 'E1210:')
+ call assert_fails('let l = utf16idx("ab", 0, [])', 'E1212:')
+endfunc
+
+" Test for utf16idx() using a character index
+func Test_utf16idx_from_charidx()
+ let str = "abc"
+ for i in str->strcharlen()->range()
+ call assert_equal(i, utf16idx(str, i, v:false, v:true))
+ endfor
+ call assert_equal(-1, utf16idx(str, 3, v:false, v:true))
+
+ " UTF-16 index of a string with two byte characters
+ let str = "a©©b"
+ for i in str->strcharlen()->range()
+ call assert_equal(i, utf16idx(str, i, v:false, v:true))
+ endfor
+ call assert_equal(-1, utf16idx(str, 4, v:false, v:true))
+
+ " UTF-16 index of a string with four byte characters
+ let str = "a😊😊b"
+ call assert_equal(0, utf16idx(str, 0, v:false, v:true))
+ call assert_equal(2, utf16idx(str, 1, v:false, v:true))
+ call assert_equal(4, utf16idx(str, 2, v:false, v:true))
+ call assert_equal(5, utf16idx(str, 3, v:false, v:true))
+ call assert_equal(-1, utf16idx(str, 4, v:false, v:true))
+
+ " UTF-16 index of a string with composing characters
+ let str = '-á-b́'
+ for i in str->strcharlen()->range()
+ call assert_equal(i, utf16idx(str, i, v:false, v:true))
+ endfor
+ call assert_equal(-1, utf16idx(str, 4, v:false, v:true))
+ for i in str->strchars()->range()
+ call assert_equal(i, utf16idx(str, i, v:true, v:true))
+ endfor
+ call assert_equal(-1, utf16idx(str, 6, v:true, v:true))
+
+ " string with multiple composing characters
+ let str = '-ą́-ą́'
+ for i in str->strcharlen()->range()
+ call assert_equal(i, utf16idx(str, i, v:false, v:true))
+ endfor
+ call assert_equal(-1, utf16idx(str, 4, v:false, v:true))
+ for i in str->strchars()->range()
+ call assert_equal(i, utf16idx(str, i, v:true, v:true))
+ endfor
+ call assert_equal(-1, utf16idx(str, 8, v:true, v:true))
+
+ " empty string
+ call assert_equal(-1, utf16idx('', 0, v:false, v:true))
+ call assert_equal(-1, utf16idx('', 0, v:true, v:true))
+
+ " error cases
+ call assert_equal(-1, utf16idx(v:_null_string, 0, v:true, v:true))
+ call assert_fails('let l = utf16idx("ab", 0, v:false, [])', 'E1212:')
+endfunc
+
+" Test for strutf16len()
+func Test_strutf16len()
+ call assert_equal(3, strutf16len('abc'))
+ call assert_equal(3, 'abc'->strutf16len(v:true))
+ call assert_equal(4, strutf16len('a©©b'))
+ call assert_equal(4, strutf16len('a©©b', v:true))
+ call assert_equal(6, strutf16len('a😊😊b'))
+ call assert_equal(6, strutf16len('a😊😊b', v:true))
+ call assert_equal(4, strutf16len('-á-b́'))
+ call assert_equal(6, strutf16len('-á-b́', v:true))
+ call assert_equal(4, strutf16len('-ą́-ą́'))
+ call assert_equal(8, strutf16len('-ą́-ą́', v:true))
+ call assert_equal(0, strutf16len(''))
+
+ " error cases
+ call assert_fails('let l = strutf16len([])', 'E1174:')
+ call assert_fails('let l = strutf16len("a", [])', 'E1212:')
+ call assert_equal(0, strutf16len(v:_null_string))
endfunc
func Test_count()
@@ -1605,7 +2000,7 @@ func Test_trim()
call assert_fails('eval trim(" vim ", " ", [])', 'E745:')
call assert_fails('eval trim(" vim ", " ", -1)', 'E475:')
call assert_fails('eval trim(" vim ", " ", 3)', 'E475:')
- call assert_fails('eval trim(" vim ", 0)', 'E475:')
+ call assert_fails('eval trim(" vim ", 0)', 'E1174:')
let chars = join(map(range(1, 0x20) + [0xa0], {n -> n->nr2char()}), '')
call assert_equal("x", trim(chars . "x" . chars))
@@ -2007,7 +2402,7 @@ func Test_call()
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:')
+ call assert_fails("call call('Mylen', [], 0)", 'E1206:')
call assert_fails('call foo', 'E107:')
" These once caused a crash.
@@ -2567,7 +2962,7 @@ endfunc
" Test for gettext()
func Test_gettext()
- call assert_fails('call gettext(1)', 'E475:')
+ call assert_fails('call gettext(1)', 'E1174:')
endfunc
func Test_builtin_check()
@@ -2607,4 +3002,41 @@ func Test_builtin_check()
endfunc
+" Test for virtcol()
+func Test_virtcol()
+ enew!
+ call setline(1, "the\tquick\tbrown\tfox")
+ norm! 4|
+ call assert_equal(8, virtcol('.'))
+ call assert_equal(8, virtcol('.', v:false))
+ call assert_equal([4, 8], virtcol('.', v:true))
+ bwipe!
+endfunc
+
+func Test_delfunc_while_listing()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ set nocompatible
+ for i in range(1, 999)
+ exe 'func ' .. 'MyFunc' .. i .. '()'
+ endfunc
+ endfor
+ au CmdlineLeave : call timer_start(0, {-> execute('delfunc MyFunc622')})
+ END
+ call writefile(lines, 'Xfunctionclear', 'D')
+ let buf = RunVimInTerminal('-S Xfunctionclear', {'rows': 12})
+
+ " This was using freed memory. The height of the terminal must be so that
+ " the next function to be listed with "j" is the one that is deleted in the
+ " timer callback, tricky!
+ call term_sendkeys(buf, ":func /MyFunc\<CR>")
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, "j")
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, "\<CR>")
+
+ call StopVimInTerminal(buf)
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/test/old/testdir/test_listdict.vim b/test/old/testdir/test_listdict.vim
index cbed71bb0a..0ff3582da9 100644
--- a/test/old/testdir/test_listdict.vim
+++ b/test/old/testdir/test_listdict.vim
@@ -699,7 +699,7 @@ func Test_reverse_sort_uniq()
call assert_fails('call reverse("")', 'E899:')
call assert_fails('call uniq([1, 2], {x, y -> []})', 'E745:')
- call assert_fails("call sort([1, 2], function('min'), 1)", "E715:")
+ call assert_fails("call sort([1, 2], function('min'), 1)", "E1206:")
call assert_fails("call sort([1, 2], function('invalid_func'))", "E700:")
call assert_fails("call sort([1, 2], function('min'))", "E118:")
endfunc
diff --git a/test/old/testdir/test_maparg.vim b/test/old/testdir/test_maparg.vim
index 19130c1569..12670671dd 100644
--- a/test/old/testdir/test_maparg.vim
+++ b/test/old/testdir/test_maparg.vim
@@ -270,7 +270,7 @@ func Test_mapset()
bwipe!
call assert_fails('call mapset([], v:false, {})', 'E730:')
- call assert_fails('call mapset("i", 0, "")', 'E715:')
+ call assert_fails('call mapset("i", 0, "")', 'E1206:')
call assert_fails('call mapset("i", 0, {})', 'E460:')
endfunc
diff --git a/test/old/testdir/test_matchfuzzy.vim b/test/old/testdir/test_matchfuzzy.vim
index b46550fbc3..be5c629cf5 100644
--- a/test/old/testdir/test_matchfuzzy.vim
+++ b/test/old/testdir/test_matchfuzzy.vim
@@ -67,7 +67,7 @@ func Test_matchfuzzy()
call assert_equal([{'id' : 6, 'val' : 'camera'}], matchfuzzy(l, 'cam', {'key' : 'val'}))
call assert_equal([], matchfuzzy(l, 'day', {'text_cb' : {v -> v.val}}))
call assert_equal([], matchfuzzy(l, 'day', {'key' : 'val'}))
- call assert_fails("let x = matchfuzzy(l, 'cam', 'random')", 'E715:')
+ call assert_fails("let x = matchfuzzy(l, 'cam', 'random')", 'E1206:')
call assert_equal([], matchfuzzy(l, 'day', {'text_cb' : {v -> []}}))
call assert_equal([], matchfuzzy(l, 'day', {'text_cb' : {v -> 1}}))
call assert_fails("let x = matchfuzzy(l, 'day', {'text_cb' : {a, b -> 1}})", 'E119:')
@@ -76,7 +76,7 @@ func Test_matchfuzzy()
" call assert_fails("let x = matchfuzzy(l, 'cam', {'text_cb' : []})", 'E921:')
call assert_fails("let x = matchfuzzy(l, 'cam', {'text_cb' : []})", 'E6000:')
call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : []})", 'E730:')
- call assert_fails("let x = matchfuzzy(l, 'cam', v:_null_dict)", 'E715:')
+ call assert_fails("let x = matchfuzzy(l, 'cam', v:_null_dict)", 'E1297:')
call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : v:_null_string})", 'E475:')
" Nvim doesn't have null functions
" call assert_fails("let x = matchfuzzy(l, 'foo', {'text_cb' : test_null_function()})", 'E475:')
@@ -144,7 +144,7 @@ func Test_matchfuzzypos()
\ matchfuzzypos(l, 'cam', {'key' : 'val'}))
call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'text_cb' : {v -> v.val}}))
call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'key' : 'val'}))
- call assert_fails("let x = matchfuzzypos(l, 'cam', 'random')", 'E715:')
+ call assert_fails("let x = matchfuzzypos(l, 'cam', 'random')", 'E1206:')
call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'text_cb' : {v -> []}}))
call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'text_cb' : {v -> 1}}))
call assert_fails("let x = matchfuzzypos(l, 'day', {'text_cb' : {a, b -> 1}})", 'E119:')
@@ -153,7 +153,7 @@ func Test_matchfuzzypos()
" call assert_fails("let x = matchfuzzypos(l, 'cam', {'text_cb' : []})", 'E921:')
call assert_fails("let x = matchfuzzypos(l, 'cam', {'text_cb' : []})", 'E6000:')
call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : []})", 'E730:')
- call assert_fails("let x = matchfuzzypos(l, 'cam', v:_null_dict)", 'E715:')
+ call assert_fails("let x = matchfuzzypos(l, 'cam', v:_null_dict)", 'E1297:')
call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : v:_null_string})", 'E475:')
" Nvim doesn't have null functions
" call assert_fails("let x = matchfuzzypos(l, 'foo', {'text_cb' : test_null_function()})", 'E475:')
diff --git a/test/old/testdir/test_partial.vim b/test/old/testdir/test_partial.vim
index 302bc22d93..8b62e4a0e5 100644
--- a/test/old/testdir/test_partial.vim
+++ b/test/old/testdir/test_partial.vim
@@ -86,7 +86,7 @@ func Test_partial_dict()
call assert_equal("Hello", dict.tr())
call assert_fails("let F=function('setloclist', 10)", "E923:")
- call assert_fails("let F=function('setloclist', [], [])", "E922:")
+ call assert_fails("let F=function('setloclist', [], [])", "E1206:")
endfunc
func Test_partial_implicit()
diff --git a/test/old/testdir/test_search_stat.vim b/test/old/testdir/test_search_stat.vim
index 1b2d854829..8dfc850956 100644
--- a/test/old/testdir/test_search_stat.vim
+++ b/test/old/testdir/test_search_stat.vim
@@ -259,7 +259,7 @@ func Test_search_stat()
endfunc
func Test_searchcount_fails()
- call assert_fails('echo searchcount("boo!")', 'E715:')
+ call assert_fails('echo searchcount("boo!")', 'E1206:')
call assert_fails('echo searchcount({"timeout" : []})', 'E745:')
call assert_fails('echo searchcount({"maxcount" : []})', 'E745:')
call assert_fails('echo searchcount({"pattern" : []})', 'E730:')
diff --git a/test/old/testdir/test_signs.vim b/test/old/testdir/test_signs.vim
index 129f1c1a0c..1e972371a5 100644
--- a/test/old/testdir/test_signs.vim
+++ b/test/old/testdir/test_signs.vim
@@ -463,7 +463,7 @@ func Test_sign_funcs()
call assert_fails('call sign_define("sign4", {"text" : "===>"})', 'E239:')
" call assert_fails('call sign_define("sign5", {"text" : ""})', 'E239:')
call assert_fails('call sign_define({})', 'E731:')
- call assert_fails('call sign_define("sign6", [])', 'E715:')
+ call assert_fails('call sign_define("sign6", [])', 'E1206:')
" Tests for sign_getdefined()
call assert_equal([], sign_getdefined("none"))
@@ -490,8 +490,7 @@ func Test_sign_funcs()
" Tests for invalid arguments to sign_place()
call assert_fails('call sign_place([], "", "mySign", 1)', 'E745:')
call assert_fails('call sign_place(5, "", "mySign", -1)', 'E158:')
- call assert_fails('call sign_place(-1, "", "sign1", "Xsign", [])',
- \ 'E715:')
+ call assert_fails('call sign_place(-1, "", "sign1", "Xsign", [])', 'E1206:')
call assert_fails('call sign_place(-1, "", "sign1", "Xsign",
\ {"lnum" : 30})', 'E474:')
call assert_fails('call sign_place(10, "", "xsign1x", "Xsign",
@@ -526,7 +525,7 @@ func Test_sign_funcs()
call assert_fails("call sign_getplaced('dummy.sign')", 'E158:')
call assert_fails('call sign_getplaced("&")', 'E158:')
call assert_fails('call sign_getplaced(-1)', 'E158:')
- call assert_fails('call sign_getplaced("Xsign", [])', 'E715:')
+ call assert_fails('call sign_getplaced("Xsign", [])', 'E1206:')
call assert_equal([{'bufnr' : bufnr(''), 'signs' : []}],
\ sign_getplaced('Xsign', {'lnum' : 1000000}))
call assert_fails("call sign_getplaced('Xsign', {'lnum' : []})",
@@ -549,7 +548,7 @@ func Test_sign_funcs()
\ {'id' : 20, 'buffer' : '&'})", 'E158:')
call assert_fails("call sign_unplace('g1',
\ {'id' : 20, 'buffer' : 200})", 'E158:')
- call assert_fails("call sign_unplace('g1', 'mySign')", 'E715:')
+ call assert_fails("call sign_unplace('g1', 'mySign')", 'E1206:')
call sign_unplace('*')
@@ -701,7 +700,7 @@ func Test_sign_group()
call assert_equal([], sign_getplaced(bnum, {'group' : '*'})[0].signs)
" Error case
- call assert_fails("call sign_unplace({})", 'E474:')
+ call assert_fails("call sign_unplace({})", 'E1174:')
" Place a sign in the global group and try to delete it using a group
call assert_equal(5, sign_place(5, '', 'sign1', bnum, {'lnum' : 10}))
@@ -1568,8 +1567,7 @@ func Test_sign_priority()
\ s[0].signs)
" Error case
- call assert_fails("call sign_place(1, 'g1', 'sign1', 'Xsign',
- \ [])", 'E715:')
+ call assert_fails("call sign_place(1, 'g1', 'sign1', 'Xsign', [])", 'E1206:')
call assert_fails("call sign_place(1, 'g1', 'sign1', 'Xsign',
\ {'priority' : []})", 'E745:')
call sign_unplace('*')
diff --git a/test/old/testdir/test_tagjump.vim b/test/old/testdir/test_tagjump.vim
index be60a3535c..f330bca125 100644
--- a/test/old/testdir/test_tagjump.vim
+++ b/test/old/testdir/test_tagjump.vim
@@ -405,10 +405,10 @@ func Test_getsettagstack()
" Error cases
call assert_equal({}, gettagstack(100))
call assert_equal(-1, settagstack(100, {'items' : []}))
- call assert_fails('call settagstack(1, [1, 10])', 'E715')
- call assert_fails("call settagstack(1, {'items' : 10})", 'E714')
- call assert_fails("call settagstack(1, {'items' : []}, 10)", 'E928')
- call assert_fails("call settagstack(1, {'items' : []}, 'b')", 'E962')
+ call assert_fails('call settagstack(1, [1, 10])', 'E1206:')
+ call assert_fails("call settagstack(1, {'items' : 10})", 'E714:')
+ call assert_fails("call settagstack(1, {'items' : []}, 10)", 'E1174:')
+ call assert_fails("call settagstack(1, {'items' : []}, 'b')", 'E962:')
call assert_equal(-1, settagstack(0, v:_null_dict))
set tags=Xtags
diff --git a/test/old/testdir/test_timers.vim b/test/old/testdir/test_timers.vim
index f94ee6c9f3..37226c7efb 100644
--- a/test/old/testdir/test_timers.vim
+++ b/test/old/testdir/test_timers.vim
@@ -93,7 +93,13 @@ func Test_timer_info()
call timer_stop(id)
call assert_equal([], timer_info(id))
- call assert_fails('call timer_info("abc")', 'E39:')
+ call assert_fails('call timer_info("abc")', 'E1210:')
+
+ " check repeat count inside the callback
+ let g:timer_repeat = []
+ let tid = timer_start(10, {tid -> execute("call add(g:timer_repeat, timer_info(tid)[0].repeat)")}, #{repeat: 3})
+ call WaitForAssert({-> assert_equal([2, 1, 0], g:timer_repeat)})
+ unlet g:timer_repeat
endfunc
func Test_timer_stopall()
@@ -228,9 +234,9 @@ func Test_timer_errors()
sleep 50m
call assert_equal(3, g:call_count)
- call assert_fails('call timer_start(100, "MyHandler", "abc")', 'E475:')
+ call assert_fails('call timer_start(100, "MyHandler", "abc")', 'E1206:')
call assert_fails('call timer_start(100, [])', 'E921:')
- call assert_fails('call timer_stop("abc")', 'E39:')
+ call assert_fails('call timer_stop("abc")', 'E1210:')
endfunc
func FuncWithCaughtError(timer)
diff --git a/test/old/testdir/test_window_cmd.vim b/test/old/testdir/test_window_cmd.vim
index 88135199fe..9320d67498 100644
--- a/test/old/testdir/test_window_cmd.vim
+++ b/test/old/testdir/test_window_cmd.vim
@@ -735,7 +735,7 @@ func Test_relative_cursor_position_in_one_line_window()
only!
bwipe!
- call assert_fails('call winrestview(v:_null_dict)', 'E474:')
+ call assert_fails('call winrestview(v:_null_dict)', 'E1297:')
endfunc
func Test_relative_cursor_position_after_move_and_resize()
@@ -936,7 +936,7 @@ func Test_winrestview()
call assert_equal(view, winsaveview())
bwipe!
- call assert_fails('call winrestview(v:_null_dict)', 'E474:')
+ call assert_fails('call winrestview(v:_null_dict)', 'E1297:')
endfunc
func Test_win_splitmove()
@@ -967,7 +967,7 @@ func Test_win_splitmove()
call assert_equal(bufname(winbufnr(2)), 'b')
call assert_equal(bufname(winbufnr(3)), 'a')
call assert_equal(bufname(winbufnr(4)), 'd')
- call assert_fails('call win_splitmove(winnr(), winnr("k"), v:_null_dict)', 'E474:')
+ call assert_fails('call win_splitmove(winnr(), winnr("k"), v:_null_dict)', 'E1297:')
only | bd
call assert_fails('call win_splitmove(winnr(), 123)', 'E957:')