Problem:
Using nested `vim.Pos` objects to represent each `vim.Range` object
requires 3 tables for each `vim.Range`, which may be undesirable in
performance critical code. Using key-value tables performs worse than
using array-like tables (lists).
Solution:
Use array-like indices for the internal fields of both `vim.Pos` and
`vim.Range` objects. Use a metatable to allow users to access them like
if they were key-value tables.
---
Problem:
The `vim.Pos` conversion interface for `extmark` indexing does not take
into account the difference in how a position on top of a newline is
represented in `vim.Pos` and `extmark`.
- `vim.Pos`: for a newline at the end of row `n`, `row` takes the value
`n + 1` and `col` takes the value `0`.
- `extmark`: for a newline at the end of for `n`, `row` takes the value
`n` and `col` takes the value `#row_text`.
Solution:
Handle this in the `extmark` interface.
---
Problem:
Not all `to_xxx` interfaces have wrapping objects like `to_lsp`.
Solution:
Return unwrapped values in `to_xxx` interfaces where it makes sense.
Accept unwrapped values in "from" interfaces where it makes sense.
---
Problem:
`start` and `end` positions have different semantics, so they can't be
compared. `vim.Range` relies on comparing the `end` and `start` of two
ranges to decide which one is greater, which doesn't work as expected
because this of the different semantics.
For example, for the ranges:
local a = {
start = { row = 0, col = 22, },
end_ = { row = 0, col = 24, },
}
local b = {
start = { row = 0, col = 17, },
end_ = { row = 0, col = 22, },
}
in this code:
local foo, bar = "foo", "bar"
-- |---||-|
-- b a
The range `b` is smaller than the range `a`, but the current
implementation compares `b._end` (`col = 22`) and `a.start` (`col = 22`)
and concludes that, since `b.col` is not smaller than `a.col`, `b`
should be greater than `a`.
Solution:
- Use a `to_inclusive_pos` to normalize end positions inside of
`vim.Range` whenever a comparison between a start and an end position
is necessary.
Problem:
failures in s390x CI.
Solution:
- runtime/lua/man.lua: parse_path() can return nil but 3 callers didn't handle it.
- skip some tests on s390x.
TODO:
- TODO: why "build/bin/xxd is not executable" on s390x?
- TODO: other failures, not addressed (see below).
OTHER FAILURES:
FAILED test/functional/treesitter/fold_spec.lua @ 87: treesitter foldexpr recomputes fold levels after lines are added/removed
test/functional/treesitter/fold_spec.lua:95: Expected objects to be the same.
Passed in:
(table: 0x4013c18940) {
[1] = '0'
[2] = '0'
[3] = '0'
*[4] = '0'
[5] = '0'
...
Expected:
(table: 0x4005acf900) {
[1] = '0'
[2] = '0'
[3] = '>1'
*[4] = '1'
[5] = '1'
...
stack traceback:
(tail call): ?
test/functional/treesitter/fold_spec.lua:95: in function <test/functional/treesitter/fold_spec.lua:87>
FAILED test/functional/treesitter/select_spec.lua @ 52: treesitter incremental-selection works
test/functional/treesitter/select_spec.lua:63: Expected objects to be the same.
Passed in:
(string) 'bar(2)'
Expected:
(string) 'foo(1)'
stack traceback:
(tail call): ?
test/functional/treesitter/select_spec.lua:63: in function <test/functional/treesitter/select_spec.lua:52>
FAILED test/functional/treesitter/select_spec.lua @ 69: treesitter incremental-selection repeat
test/functional/treesitter/select_spec.lua:82: Expected objects to be the same.
Passed in:
(string) '2'
Expected:
(string) '4'
stack traceback:
(tail call): ?
test/functional/treesitter/select_spec.lua:82: in function <test/functional/treesitter/select_spec.lua:69>
FAILED test/functional/treesitter/select_spec.lua @ 98: treesitter incremental-selection history
test/functional/treesitter/select_spec.lua:111: Expected objects to be the same.
Passed in:
(string) 'bar(2)'
Expected:
(string) 'foo(1)'
stack traceback:
(tail call): ?
test/functional/treesitter/select_spec.lua:111: in function <test/functional/treesitter/select_spec.lua:98>
FAILED test/functional/treesitter/select_spec.lua @ 186: treesitter incremental-selection with injections works
test/functional/treesitter/select_spec.lua:201: Expected objects to be the same.
Passed in:
(string) 'lua'
Expected:
(string) 'foo'
stack traceback:
(tail call): ?
test/functional/treesitter/select_spec.lua:201: in function <test/functional/treesitter/select_spec.lua:186>
FAILED test/functional/treesitter/select_spec.lua @ 216: treesitter incremental-selection with injections ignores overlapping nodes
test/functional/treesitter/select_spec.lua:231: Expected objects to be the same.
Passed in:
(string) ' )'
Expected:
(string) ' foo('
stack traceback:
(tail call): ?
test/functional/treesitter/select_spec.lua:231: in function <test/functional/treesitter/select_spec.lua:216>
FAILED test/functional/treesitter/select_spec.lua @ 307: treesitter incremental-selection with injections handles disjointed trees
test/functional/treesitter/select_spec.lua:337: Expected objects to be the same.
Passed in:
(string) 'int'
Expected:
(string) '1}'
stack traceback:
(tail call): ?
test/functional/treesitter/select_spec.lua:337: in function <test/functional/treesitter/select_spec.lua:307>
ERROR test/functional/treesitter/parser_spec.lua @ 562: treesitter parser API can run async parses with string parsers
test/functional/treesitter/parser_spec.lua:565: attempt to index a nil value
stack traceback:
test/functional/testnvim/exec_lua.lua:124: in function <test/functional/testnvim/exec_lua.lua:105>
(tail call): ?
(tail call): ?
test/functional/treesitter/parser_spec.lua:563: in function <test/functional/treesitter/parser_spec.lua:562>
FAILED test/functional/core/job_spec.lua @ 1157: jobs jobstop() kills entire process tree #6530
test/functional/core/job_spec.lua:1244: retry() attempts: 94
test/functional/core/job_spec.lua:1246: Expected objects to be the same.
Passed in:
(table: 0x401dd74b30) {
[name] = 'sleep <defunct>'
[pid] = 33579
[ppid] = 1 }
Expected:
(userdata) 'vim.NIL'
stack traceback:
test/testutil.lua:89: in function 'retry'
test/functional/core/job_spec.lua:1244: in function <test/functional/core/job_spec.lua:1157>
Problem:
Non-trivial to write output of vim.net.request to buffer. Requires extra
code in plugin/net.lua which can't be reused by other plugin authors.
```
vim.net.request('https://neovim.io', {}, function(err, res)
if not err then
local buf = vim.api.nvim_create_buf(true, false)
if res then
local lines = vim.split(res.body, '\n', { plain = true })
vim.api.nvim_buf_set_lines(buf, 0, -1, true, lines)
end
end
end)
```
Solution:
Accept an optional `outbuf` argument to indicate the buffer to write output
to, similar to `outpath`.
vim.net.request('https://neovim.io', { outbuf = buf })
Other fixes / followups:
- Make plugin/net.lua smaller
- Return objection with close() method
- vim.net.request.Opts class
- vim.validate single calls
- Use (''):format(...) instead of `..`
Problem:
`vim.keymap.*.Opts.buf` allows `boolean` aliases for more widely
used `integer?` values, `true` -> `0` and `false` -> `nil`. This
conversion is unnecessary and can be handled at call sites.
Solution:
As a follow-up to deprecating the `buffer` option, drop support for
boolean values for the new `buf` option. The deprecated `buffer`
continues to support booleans for backward compatibility.
Problem:
libmpack encodes boundary values -129 and -32769 with wrong integer
sizes:
- -129 as int8 instead of int16
- -32769 as int16 instead of int32
because the boundary checks compare against the wrong values (e.g., lo
< 0xffffff7f instead of lo < 0xffffff80). This caused data corruption:
-129 would decode as 127.
Solution:
Fix off-by-one errors in the two's complement boundary constants:
0xffffff80 (-128, min int8) and 0xffff8000 (-32768, min int16).
Fixes#37202
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The `buffer` option remains functional but is now undocumented.
Providing both will raise an error. Since providing `buf` was disallowed
before, there is no code that will break due to using `buffer` alongside
`buf`.
Problem:
Checking the extension of a file is done often, e.g. in Nvim's codebase
for differentiating Lua and Vimscript files in the runtime. The current
way to do this in Lua is (1) a Lua pattern match, which has pitfalls
such as not considering filenames starting with a dot, or (2)
fnamemodify() which is both hard to discover and hard to use / read if
not very familiar with the possible modifiers.
vim.fs.ext() returns the file extension including the leading dot of
the extension. Similar to the "file extension" implementation of many
other stdlibs (including fnamemodify(file, ":e")), a leading dot
doesn't indicate the start of the extension. E.g.: the .git folder in a
repository doesn't have the extension .git, but it simply has no
extension, similar to a folder named git or any other filename without
dot(s).
Problem:
When `to_vim_value[info.metatype](info, value)` is called, a list value
such as `{'eob:a'}` is treated like a map, which generates `1:eob:a`.
Note: commands like `:lua vim.opt.wildmode={'longest:full'}` are not an
issue because only cases harcoded in `key_value_options` have metatype `map`.
Solution:
Check for array type and use the same logic as in array metatypes.
Problem:
The `float` field of vim.diagnostic.config can be a function,
but diagnostic.open_float() does not handle it correctly.
Solution:
Add handling for it in open_float().
Problem: 'showcmd' buffer is being populated for :norm commands, which
can result in a recursive uv_run() when called from a msg_show
vim.ui_attach callback for a shell message.
Solution: The 'showcmd' buffer is never displayed while executing a
:normal command so prevent unnecessary work, avoiding the crash.
Problem: Statusline component of diagnostics allows only the default
format "sign:count".
Solution: Extend vim.diagnostic.Opts.Status to allow a custom signs
or formatting function that provides the status presentation.
Problem:
In autocmd examples, using "args" as the event-object name is vague and
may be confused with a user-command.
Solution:
Use "ev" as the conventional event-object name.
Problem: nvim_win_set_config may merge configs despite failing to configure a
split, and without applying necessary side-effects (like setting style=minimal
options). Plus, autocommands may apply a different config after the merge,
causing side-effects to apply for an outdated config.
Solution: merge configs last, only on success. Include fields only relevant to
splits. Properly set _cmdline_offset for splits.
Maybe better to disallow _cmdline_offset for splits instead, as the pum is
relative to cmdline_row anyway? (I didn't want to change behaviour too much)
Also use expect_unchanged in an unrelated test to quash a warning.
Problem:
vim.fs.joinpath treats empty string as a path segment
(it adds a path separator for each empty item):
print(vim.fs.joinpath('', 'after/lsp', '')) -- '/after/lsp/'
print(vim.fs.joinpath('', '')) -- '/'
Especially problematic if the empty segment is the first segment, as
that converts the path to an absolute path.
Solution:
Ignore empty (length of 0) path segments.
Benchmark:
local function test(func)
local t = vim.uv.hrtime()
for _ = 1, 100000, 1 do
func('', 'this/is', 'a/very/long/path', '', 'it', 'really', 'is')
end
print(math.floor((vim.uv.hrtime() - t) / 1e6), 'ms')
end
- with Iter():filter() --> 370 ms
- building new segments table --> 208 ms
- with vim.tbl_filter --> 232 ms
- Instead of gsub split on `/` in all parts --> 1870 ms
Problem: Newlines emitted with ext_messages intended to position
the message/prompt on the message grid.
Solution: Don't emit these newlines with ext_messages, followup to 4260f73e.
Define a CMake target for every subdirectory of test/functional that
contains functional tests, and a functionaltest_parallel target that
depends on all those targets, allowing multiple test runners to run in
parallel.
On CI, use at most 2 parallel test runners, as using more may increase
system load and make tests unstable.
Problem: Logic determining messages belonging to the last command to
show with "g<" does not flush pending messages. This can
result in clearing the temporary message history before a
message still belonging to the previous command was emitted.
Solution: Flush pending messages when marking the end of messages
belonging to previous command.
Problem: if init_prompt replaces the prompt line at the ': mark, it calls
inserted_bytes with the wrong lnum.
Solution: use the correct lnum. Call appended_lines_mark instead when appending
the prompt at the end.
Problem: prompt_setprompt does not adjust extmarks or trigger on_bytes
buffer-updates when fixing the prompt line.
Solution: adjust them, trigger on_bytes.
Notably, hides extmarks when replacing the entire line (and clearing user
input). Otherwise, when just replacing the prompt text, hides extmarks there,
but moves those after (in the user input area) to the correct spot.
Problem: prompt_setprompt calls coladvance with a byte column, but it expects a
screen column. on_lines buffer-updates aren't fired when fixing the prompt line.
Solution: don't use coladvance. Call changed_lines, which also simplifies the
redraw logic. (and calls changed_cline_bef_curs if needed; added test checks
this)
Unlike https://github.com/neovim/neovim/pull/37743/changes#r2775398744, this
means &modified is set by prompt_setprompt if it fixes the prompt line.
Not setting &modified is inconsistent anyway -- even init_prompt sets it if it
fixes the prompt line.
Problem:
`vim.diagnostic.fromqflist` ignores lines that are `item.valid == 0` (see
`getqflist`). Many qflists have messages that span multiple lines, which look
like this:
collection/src/Modelling/CdOd/Central.hs|496 col 80| error: [GHC-83865]
|| • Couldn't match expected type: InstanceWithForm
|| (FilePath
|| -> SelectValidCdInstWithForm
...
calling `vim.diagnostic.fromqflist(vim.fn.getqflist)` gets a diagnostic message
like this:
error: [GHC-83865]
only the first line is kept, but often, the remaing lines are useful as well.
Solution:
Introduce `merge_lines` option, which "squashes" lines from invalid qflist items
into the error message of the previous valid item, so that we get this
diagnostic message instead:
error: [GHC-83865]
• Couldn't match expected type: InstanceWithForm
(FilePath
-> SelectValidCdInstWithForm
Problem: wait() checks condition twice on each interval.
Solution: Don't schedule the due callback. Also fix memory leak when
Nvim exits while waiting.
No test that the condition isn't checked twice, as testing for that can
be flaky when there are libuv events from other sources.
Problem:
Iter:peek() only works if the iterator is a |list-iterator| (internally, an `ArrayIter`).
However, it is possible to implement :peek() support for any iterator.
Solution:
- add `_peeked` buffer for lookahead without actually consuming values
- `peek()` now works for function, pairs(), and array iterators
- `skip(predicate)` stops at the first non matching element without consuming it
- keep existing optimized behavior for `ArrayIter` to maintain backward compatibility
- use `pack`/`unpack` to support iterators that return multiple values
Problem:
`vim.json.decode()` could not parse JSONC (JSON with Comments)
extension, which is commonly used in configuration files.
Solution:
Introduce an `skip_comments` option, which is disabled by default. When
enabled, allows JavaScript-style comments within JSON data.
Problem:
Using vim.defer_fn() just before Nvim exit leaks luv handles.
Solution:
Make vim.schedule() return an error message if scheduling failed.
Make vim.defer_fn() close timer if vim.schedule() failed.
Problem: Fast context for msg_show event inhibits vim.ui_attach from
displaying a stream of messages from a single command.
Solution: Remove fast context from msg_show events emitted as a result
of explicit API/command calls. The fast context was originally
introduced to prevent issues with internal messages.
Problem:
vim.glob.to_lpeg() errors when patterns contain numeric literals
(like the '1' in '.ps*1') because LPeg interprets numeric strings
as indexed grammar rule references. For example:
vim.glob.to_lpeg('.ps*1')
E5108: Lua: rule '1' undefined in given grammar
Solution:
Prefix all rule names with '_' in the end_seg() function to prevent
literal numbers from being interpreted as LPeg indexed rules. This
ensures pattern components like '1', '2', etc. are treated as
regular rule names rather than special references.
Problem
LSP Related Information line and column numbers are 0-based. Displaying
them this way can confuse the user, since vim line/col numbers are
typically displayed 1-based.
Solution
Display the line and column numbers as 1-based.
Problem: The function get_logical_pos did not account for the possibility that a diagnostic might not have an associated extmark, leading to potential errors or incorrect behavior.
Solution: Add a check for diagnostic._extmark_id and return the logical positions directly if it does not exist.
Problem: vim.keymap.del has 'modes' as it's first argument while vim.keymap.set
has 'mode' as it's first argument despite both 'mode' and 'modes' taking in the
same type input of String or String[].
Solution: Updated vim.keymap.set docs to refer to it's first argument
as 'modes'.
Problem:
After bc0635a9fc `vim.wait()` rejects floats
and NaN values.
Solution:
Restore the prior behavior, while still supporting `math.huge`. Update
tests to cover float case.
Problem:
`nlua_wait()` uses `luaL_checkinteger()` which doesn't support
`math.huge` since it's double type. On PUC Lua this fails with
'number has no integer representation' error and on LuaJIT this
overflows int.
Solution:
Use `luaL_checknumber()` and handle `math.huge`.
Problem:
MessagePack fixstr format supports string lengths 0-31, but mpack_str()
only used fixstr for lengths < 20. Strings of 20-31 bytes were
incorrectly encoded as str8 (2-byte header) instead of fixstr (1-byte
header).
Solution:
Change the condition from `len < 20` to `len < 32` to match the
MessagePack specification.
This fix affects message timing which exposed a pre-existing race
condition in the channels_spec PTY test. The test now uses a helper
function to accumulate partial PTY reads, making it more robust.
Fixes#32784
Problem:
`diagnostic.status()` is configured via `config.signs`, but users may
want diagnostics only in statusline, not in the gutter (signs).
Solution:
Add `config.status`.