Problems:
1. Can't get individual parts of a key-chord separately: modifiers, key.
2. Can't separate a key-combo into individual key-chords.
Solution:
Enhance `vim.keycode()` to optionally return a structured parse result
as a list of key-chords:
- `key_raw` the key-chord (problem 2)
- `mod` the modifiers of `key_raw` (problem 1)
- `key_orig` the key part of the key-chord, only here if differing from `key`
(this doesn't solve any of the above mentioned problems, but it may provide
useful and it's (in terms of code) free)
- `key` a normalized version of `key_orig` (solving problem 1), example(the
first is `key_orig` and second is `key`): `lt` and `<`, `Bar` and `|` (in
`<C-Bar>`)
Problem:
`getchar.c` is a quirky name and doesn't align with our existing
`os/input.c` and `tui/input.c` modules, which encompass the same topic
at different layers.
Solution:
Rename `getchar.c` => `input.c` for discoverability.
Introduce `dialog.c` for functionality related to showing confirm
prompts, dialogs, etc.
Problem:
bufwrite message overridden by :redrawstatus command within Progress callback.
Solution:
- don't use globally-shared IObuf.
- use vim_snprintf to deduplicate `nlua_call_luaeval`, `nlua_call_vlua`.
fix https://github.com/neovim/neovim/issues/40616
The added test shows the context, one expected a JSON field
to be a Object but it was a null value
pros: shows `vim.NIL` instead of `a userdata`
cons: the context `field 'foo'` is lost. I think this is generated
with internal magic which is hard to replicate.
Problem:
`os.exit()` in `nvim -l` exits through normal teardown. But, as #39783 shows,
when it is called from a libuv callback, teardown polling the main loop
when inside `uv_run()` can trip the recursive poll guard and cause a crash.
Fast callbacks are not safe to teardown, so it's better to schedule an exit out
of a callback rather than convolutedly handle this as I tried before.
Solution:
Reject `os.exit()` from fast callbacks with `E5560` error.
All wasm parsers shared a single global TSWasmStore via
ts_parser_set_wasm_store(). A TSWasmStore is owned by exactly one parser:
ts_parser_delete() frees it through ts_wasm_store_delete(). So the first
wasm parser collected freed the shared store, leaving the global pointer
dangling, and creating the next wasm parser dereferenced freed memory in
ts_wasm_store_reset() -> wasmtime_store_context(), crashing with SIGSEGV.
Give each parser its own store via ts_wasm_store_new() instead. This is
the 1:1 store-per-parser model intended by tree-sitter (see
tree-sitter/tree-sitter#3454): the global store remains only as the
language loader, ts_parser_delete() cleanly frees each parser's own
store, and the wasm engine is shared safely because ts_wasm_store_new()
clones its engine reference internally.
AI-assisted: Claude Code
This was originally used because we needed a lua interpreter to run the
standalone _busted_ lua application (except we used fuckery behind the
scenes to fake its binary dependencies with our own binary
dependencies). Now we longer do that, we instead run `test.harness` as a
standard nvim -l script. as this mode is no longer used in tests it
risks going to bitrot anyway.
NB: -ll mode still had some theoretical benefits like quicker
initialization and more "native" printing behavior, etc.
But we can incrementally work on "nvim -l" mode instead
of maintaining a separate special thing.
Problem:
`aucmd_restbuf` must be guarded in case `aucmd_prepbuf` wasn't called.
Solution:
Update `aucmd_restbuf` to be a no-op if `aucmd_prepbuf` wasn't called.
This requires `aco` to be zero-initialized.
Problem: User commands cannot handle single args with spaces
Solution: Add the -nargs=_ attribute (Maxim Kim)
-nargs=_ allow user commands to have a single argument with spaces.
For example given the following Test command and TestComplete function:
```
vim9script
def TestComplete(A: string, _: string, _: number): list<string>
var all = ["qqqq", "aaaa", "qq aa"]
return all->matchfuzzy(A)
enddef
command! -nargs=_ -complete=customlist,TestComplete Test echo <q-args>
```
`:Test q a<tab>` should successfully complete `qq aa`
fixes: vim/vim#20102closes: vim/vim#20189f0e874a129
Co-authored-by: Maxim Kim <habamax@gmail.com>
Problem:
The Lua test harness still ran through standalone -ll mode, so tests
depended on the low-level Lua path instead of the regular Nvim Lua
environment. That also meant os.exit() coverage had to carry an ASAN
workaround because Lua's raw process exit skipped Nvim teardown and let
LeakSanitizer interfere with the observed exit code.
Solution:
Run the harness and related fixtures with nvim -l. Patch os.exit() in
the main Lua state to exit through getout(), so scripts observe normal
Nvim shutdown while standalone -ll remains available for generator-style
scripts. As a consequence, the startup test can assert os.exit() without
disabling leak detection.
AI-assisted: Codex
Problem: vim.ui_attach() callback for nvim_echo() call that spoofs an
internal message kind is executed in fast context.
Solution: Set msg_show callback |api-fast| context dynamically at
external message callsites, and for internal list_cmd",
"progress" and "shell*" messages.
CI currently uses clang-tidy 20, but this affects local builds
and CI is going to be upgraded sooner or later.
Some remaining systematic issues:
- clang-tidy warns agains any atoi() or atol() usage (because of no
error handling)
- functions which takes (char *fmt, char *only_string_arg) and expect
fmt to contain exactly one "%s" usage.
- error: initializing non-local variable with non-const expression depending on
uninitialized non-local variable (cppcoreguidelines-interfaces-global-init)
This is a much worse problem in C++ (hence C++ core guidelines) where
initialization is intermingled with arbitrary code execution. I
"think" in plain C, the linker will either resolve all these
deterministically or barf an error. But with some restructuring
we could make all static initialization actually static..
Problem:
parser_gc() calls ts_parser_delete() but leaves the userdata pointer
pointing to freed memory. If the GC finalizer runs at an unexpected time
(e.g. inside nvim_buf_get_lines #39411), a stale pointer could cause a crash.
Solution:
- NULL out `*ud` after ts_parser_delete() in parser_gc()
- Update parser_check() to handle NULL with a clear error message,
guarding all parser methods against UAF
Co-authored-by: Lewis Russell <lewis6991@gmail.com>
Signed-off-by: Szymon Wilczek <swilczek.lx@gmail.com>
Problem:
The fallback that tokenizes `eap->arg` by unescaped whitespace (when the
parser doesn't pre-split via `EX_EXPAND` etc.) lives in `nlua_do_ucmd`,
so only user-command callbacks got `eap.fargs`. Builtin commands routed
through `nlua_call_excmd` have to re-parse the args themselves
(e.g. `M.ex_lsp`).
Solution:
- Move the tokenization into `nlua_push_eap` so every Lua handler sees
`eap.fargs`. Keep only the `EX_NOSPC` override in `nlua_do_ucmd` (the
`nargs=1`/`?` case which is genuinely user-command-specific).
- Drop the re-parse in `M.ex_lsp`.
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()`).
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: :restart leads to ERR/WRN logging on Windows with --listen.
Solution: Add a log_level flag to vim._with() and use it to suppress
logging from serverstart()/serverstop() during restart.
Although `nlua_call_excmd` is semantically for implementing Ex-commands,
the `require()` should never fail, so that's a "Lua error".
But if the call itself fails (the later `semsg` call), that's an "Ex
cmd" error.
Problem:
Too much boilerplate needed to use Lua to impl an excmd or f_xx
function.
Solution:
- Add `nlua_call_vimfn` which takes the args typval, executes
Lua, and returns a typval.
- refactor(excmd): lua impl for :log, :lsp
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.
Problem: Treesitter highlighting regressed on 32-bit builds because ranges that should cover the whole buffer were corrupted when passed into Lua.
Solution: Round-trip those range values through Lua and validate them so treesitter sees the same ranges on 32 and 64-bit builds.
This PR creates a C function `nts_parser_parse_buf()`
which is like `ts_parser_parse_string()` but instead can be passed
an nvim buffer number to parse.
Problem:
`nvim_echo(…, {id=…})` accepts user-defined id as a string or integer.
Generated ids are always higher than last highest msg-id used. Thus
plugins may accidentally advance the integer id "address space", which,
at minimum, could lead to confusion when troubleshooting, or in the
worst case, could overflow or "exhaust" the id address space.
There's no use-case for it, and it could be the mildly confusing, so we
should just disallow it.
Solution:
Disallow *integer* user-defined message-id.
Only allow *string* user-defined message-id.
using the GNU compiler we just get a bunch of const warnings we can fix.
clang, however, gets really upset that the standard library suddenly
starts using a lot of c11 features, despite us being in -std=gnu99 mode.
Basically, _GNU_SOURCE which we set is taken as a _carte blanche_ by the
glibc headers to do whatever they please, and thus we must inform clang
that everything is still OK.
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:
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: There are still ways to run into textlock errors with
vim.ui_attach callbacks trying to display a UI event.
Solution: Disregard textlock again during vim.ui_attach() callbacks
(also when scheduled). Partially revert 3277dc3b; avoiding
to flush while textlock is set is still helpful.
Problem:
We want to encourage implementing core features in Lua instead of C, but
it's clumsy because:
- Core Lua code (built into `nvim` so it is available even if VIMRUNTIME
is missing/invalid) requires manually updating CMakeLists.txt, or
stuffing it into `_editor.lua`.
- Core Lua modules are not organized similar to C modules, `_editor.lua`
is getting too big.
Solution:
- Introduce `_core/` where core Lua code can live. All Lua modules added
there will automatically be included as bytecode in the `nvim` binary.
- Move these core modules into `_core/*`:
```
_defaults.lua
_editor.lua
_options.lua
_system.lua
shared.lua
```
TODO:
- Move `_extui/ => _core/ui2/`
Problem: Converting a funcref to a string leaves out "g:", causing the
meaning of the name depending on the context.
Solution: Prepend "g:" for a global function.
c4ec338fb8
Co-authored-by: Bram Moolenaar <Bram@vim.org>
Co-authored-by: Jan Edmund Lazo <jan.lazo@mail.utoronto.ca>