Problem:
Regression from c822a2657c: `vim.wait(0)` does not call `loop_poll`,
so `vim.wait(1)` is needed to "yield" from Lua.
Solution:
- Ensure that `vim._core.loop_poll()` is always called, even when `time=0`.
- Document how to interrupt Lua code (ctrl-c).
ref https://github.com/neovim/neovim/issues/6800
Problem: <Esc> in a Select-mode tabstop leaves the session and highlight active.
CursorMoved isn’t triggered since the cursor doesn’t move.
Solution: use ModeChanged (s:n) instead. Defer with vim.schedule() to avoid transient
s:n from jump().
Problem:
- Empty ranges have different `<`, `<=`, `has` and `intersect` semantics compared to regular ranges.
- `to_inclusive_pos` assumes that the end position of a range is exclusive, which is not true for empty ranges
Solution:
Special case empty ranges in these operations.
Problem:
For a given position, it is not easy to compare which of several other positions is closest to it.
Solution:
Add support for converting `vim.Pos` to a buffer byte offset.
This allows for sorting, e.g:
```lua
table.sort(positions, function(pos1, pos2)
return pos1:to_offset() < pos2:to_offset()
end
```
Or a binary search, e.g:
```lua
vim.list.bisect(positions, pos, { key = function(pos) return pos:to_offset() end })
```
Problem:
When using `vim.list.unique` or `vim.list.bisect`, if the `key` function is
complex, it can degrade performance, because it is invoked on every comparison
Solution:
The `key` interface convention is designed specifically to address this issue;
performance can be improved by memoizing its results.
Also added the shorthand use of the field name string as the key.
Problem:
After 55ceb31, z= and tselect don't work if `vim.ui.select` is an async
provider (especially terminal buffers).
Solution:
Drop the `vim.wait()` approach, use an async approach.
fix#39506
Problem:
followup to 55ceb314ca#39478
`:oldfiles` and swapfile `:recover` do not delegate to `vim.ui.select`.
Solution:
- Delegate to `vim.ui.select`.
- Fix a long-standing `recover_names` bug where `concat_fnames(dir_name,
files[i], true)` produced malformed `<dir>//<dir>/<file>` paths (also
fixes `swapfilelist()`).
Problem:
`:tselect` and `z=` (spell suggest) have their own bespoke select menus.
Solution:
- Delegate to `vim.ui.select` instead.
- Bonus:
- `:tselect` gains mouse support. `print_tag_list` didn't suport mouseclick.
This causes some minor regressions, which are not blockers:
- `z=` no longer draws the list right-left if 'rightleft' is set.
- TODO: can/should `vim.ui.select` / `vim.fn.inputlist()` handle that?
- `:tselect`
- No "column" headings (`# pri kind tag file`).
- No highlighting: (HLF_T: tag name, HLF_D: file, HLF_CM: extra fields).
- TODO: can `vim.ui.select()` support highlighted chunks (`[[text, hl_id], ...]`) ?
fix https://github.com/neovim/neovim/issues/25814
fix https://github.com/neovim/neovim/issues/31987
Move vim.wait into runtime/lua/vim/_core/editor.lua and replace
the C entrypoint with narrow vim._core helpers for polling, UI
flushing, and interrupt checks.
Keep the existing interval semantics by retaining the dummy timer that
wakes the loop while it is otherwise idle.
Update the docs to describe the success return values correctly, and
adjust the test expectation for the new vim.validate() callback error.
AI-assisted: Codex
Problem:
We should not use "\" (backslashes) except where absolutely required.
See references in https://github.com/neovim/neovim/pull/37729
Solution:
There is no reason to use "\" slashes in the trust db, so don't.
Problem:
`vim.secure.trust()` hashes an unchanged empty buffer as
a newline, so trusting an empty file by buffer never works.
Solution:
Hash unchanged empty-buffers `''` so buffer-based
trust matches the on-disk empty file.
Problem: Using `buf=0`/`win=0` context in `vim._with` should be
equivalent to using explicit buffer/window identifier respectively.
Solution: Explicitly adjust context in case of `buf=0` or `win=0`.
Problem: Calling `vim.filetype.match({ filename = '...', buf = ... })`
during startup results in an error due to not yet defined
`g:ft_ignore_pat`.
Solution: Add a guard to check `g:ft_ignore_pat` related properties only
if the variable is defined. This also allows to simplify other tests
which did not depend on `g:ft_ignore_pat` but required it explicitly
set to work.
Problem:
- Builtin "Vimscript" functions (f_xx) are mostly implemented in C.
Partly that's because there is some boilerplate required to call out
to Lua.
- Calls to `vim.fn.foo()` always marshall over the Lua <=> Vimscript
("typval") bridge, even if `fn.foo()` is implemented entirely in Lua:
```
Lua => typval => Object => Lua => Object => typval => Lua.
```
Solution:
Functions declared in eval.lua with `func_lua` are implemented in
entirely in Lua (`_core/vimfn.lua`).
- `gen_eval.lua` wires `func_lua` entries to `lua_wrapper`, which handles
the typval conversion for Vimscript callers (slow path).
- `nlua_call()` detects `func_lua` functions and calls the Lua
implementation directly. This eliminates all conversion overhead for
Lua callers (fast path).
- Validate at build-time that `func`, `func_float`, and `func_lua` are
mutually exclusive.
- Migrate `hostname()` as a toy example, to show the idea.
continues d0af4cd909.
This commit renames positional parameters. This is only "cosmetic", but
is intended to make it extra clear which name is preferred, since people
often copy existing code despite the guidelines in `:help dev-naming`.
Extract the diagnostic implementation from
runtime/lua/vim/diagnostic.lua into focused internal modules covering
config, display, float rendering, jump/list helpers, namespace and
storage management, severity/shared utilities, and statusline support.
Move the builtin handlers into runtime/lua/vim/diagnostic/handlers/ and
keep runtime/lua/vim/diagnostic.lua as the public facade that lazily
dispatches to the split modules. This preserves the external
vim.diagnostic API while making the implementation easier to navigate
and reason about.
AI-assisted: Codex
Replace the busted-based Lua test runner with a repo-local harness.
The new harness runs spec files directly under `nvim -ll`, ships its own
reporter and lightweight `luassert` shim, and keeps the helper/preload
flow used by the functional and unit test suites.
Keep the file boundary model shallow and busted-like by restoring `_G`,
`package.loaded`, `package.preload`, `arg`, and the process environment
between files, without carrying extra reset APIs or custom assertion
machinery.
Update the build and test entrypoints to use the new runner, add
black-box coverage for the harness itself, and drop the bundled
busted/luacheck dependency path.
AI-assisted: Codex
Problem: vim.VersionRange had no __eq metamethod, so comparing 2 distinct
but same value instances always returned false. In vim.pack.add this caused
redundant lockfile rewrites, even when the resulting lockfile content was
unchanged.
Solution: Add __eq metamethod on vim.VersionRange
Problem: `buf` is optional even though its needed to perform conversions
and the ordering of `(buf, row, col)` is not consistent.
Solution: make `buf` mandatory on `vim.range` and `vim.pos` and enforce
the `buf, row, col` ordering
Problem:
Opening .tar.gz or .zip URLs shows raw binary instead of using the archive plugins.
Solution:
Similar to the original netrw implementation, the autocmd should detect
archive URLs, download them to a temp file and the open them with
tar/zip handlers already bundled as vim plugins.
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.