aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/lua
</
Commit message (Collapse)AuthorAge
* [extregs] implement tab completion for ~ menu.extregsJosh Rahm16 hours
|
* [extregs] implemented aliases.Josh Rahm34 hours
|
* [extregs] switch to a hash map for an arbitrary number of extregs.Josh Rahm34 hours
|
* [feat] extregistersJosh Rahm34 hours
| | | | | | | | | | | | | | | | | | | | | Implements "extended registers" Extended registers provide access to a separate namespace of registers backed by custom lua callback functions. A new special register ("~) is introduced, which prompts the user for the name of an extregistr to yank or paste from. This enables file-backed or cloud-synced or other arbitrary register behavior. One can now use: `"~file_extreg<cr>yy` to yank into the "file_extreg" register. AI-assisted: Qwen3-Coder
* Merge pull request #42217 from bfredl/redirectingbfredl13 days
|\ | | | | fix(lua): regression with `print()` in headless mode when redirecting
| * fix(lua): regression with `print()` in headless modebfredl13 days
| | | | | | | | | | | | | | | | | | This is a bunch of hacks so we can override msg_puts_printf specifically while still allowing redirecting and "normal" message flow to happen, as this still has observable side-effects (even with no screen attached, screenstring() is still valid..) fixes #42218
* | fix(lua): vim._with({ buf, win }) assertion failure #42244Justin M. Keyes2026-10-05
|/ | | | | | | | | Problem: `vim._with({ buf = 0, win = 0 }, f)` fails an assert in ctx_switch(). vim._with() allows `buf` with `win` when `win` shows `buf`, but nlua_with() passes both, and ctx_switch() takes only one target. Solution: Ignore `buf` if `win` is given; the window implies its buffer.
* feat(lua): in script mode -l, use raw output for print()bfredl2026-10-02
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | consider the following test_script.lua : print 'hello\n' -- TWO new lines print 'this is \027[31mRED FOR AN AMAZING REASON\027[0m' print 'foo' print '' -- newline despite empty! print 'bar' -- a bit later: os.execute 'sleep 2' -- CPU goes BRR, cursor in column zero print 'more bar' before this PR, `nvim -l test_script.lua` had some weird behaviors compared to `lua test_script.lua` or the now removed `nvim -ll test_script.lua`. - one extra '\n' at the end of a print is ignored, one would need TWO '\n' for a single extra '\n' - an empty print'' is supposed to print an empty line - print'\027[31mRED' is being escaped so it does not work - the cursor gets stuck at the end of a message, instead of after emitting the new line, this looks like the CPU lag has something to do with the _last_ printed message which is likely not the case. I consider these behaviors to be unintentional (a consequence of stragglig through the message.c meta-machine, which is optimized for message handling in _an interative text editor_) and getting closer to plain lua behavior would be better. An alternative would be to restore the builtin print(), but our implemenation still has some benefits, like try `print(setmetatable({}, {__tostring=error})` in plain lua..
* fix(api): correct Lua command and completion typesLewis Russell2026-09-28
| | | | | | | | | | | Problem: Command inputs and parsed modifiers have different optional fields, and completion window IDs are annotated as floating-point values. Solution: Separate modifier input and return types, correct integer IDs, and update the metadata generators and their output. AI-assisted
* fix(lint): fix some warnings in clang-tidy 22bfredl2026-09-23
| | | | CI is currently using clang-tidy 21 but prepare for the future
* refactor(option): simplify error buffer handlingLewis Russell2026-09-21
| | | | | | | | | | | | | | | | | | | | | | | | | | Problem: Option validators and setters repeatedly pass error buffers and their sizes separately. Some setters return messages in static storage. Solution: - Add CharBuf for borrowed writable storage, with its length including space for a terminating NUL. - Move option error buffers to the callers that report errors. Preserve existing buffer sizes, truncation, and calls that do not need a message. - Consolidate option error formatting and the duplicated code that lists valid option values. - Return error messages directly from do_one_set_option(). - Remove unused arguments from get_option_newval() and set_option_value_for(). - Handle legacy TTY assignments in the existing callers and remove the redundant compatibility wrapper. - Reuse CharBuf for Tree-sitter query errors. - Pass CharBuf through the statusline and statuscolumn builders, retaining the buffer sizes provided by their callers, including title formatting. AI-assisted
* fix(strings): non fatal errors for [NULL] safety in vim_snprintfbfredl2026-09-08
| | | | | | | | | | | | | | | | | | | | | | | | | | | | context: vim_snprintf provides a "portable" replacement for a system vsnprintf(). However we rely on system vsnprintf() in places. This is quite arbitrary. I want to use system vsnprintf() only, and reduce the machinery in string.c for typval printf only, which will delet a lot of duplicated code. The biggest hurdle here is not "portably" (we only support sane c runtimes) but the following discrepancy: standard libc printf() considers NULL args to %s to be undefined behavior strictly while vim_snprintf() _defacto_ allows this by replacement to "[NULL]" IMO, this should be seen as "graceful" error handling (nicer than crashing on the user), not a valid means to have the string "[NULL]" intentionally be shoved in the users face. more robust code should explicitly check for NULL and replace it with a _appropriate_ fallback for the situation, depending on what a NULL string actually means in the context (unnamed buffer? anonymous namespace? global augroup?) soo, this PR provides the most _gently_ incremental nudge towards considering this case again as an error one could possible imagine. If sanitizers are complied in, this minor incursion gets printed into the sanitizers' log as a "report-and-continue" error with a traceback. This doesn't annoy the end user or "stop the world" in the test suite, but the CI will see the reported error and fail the build, just like other non-fatal CI errors.
* fix(lua): blast radius of broken `_G.debug` #41507Justin M. Keyes2026-08-27
| | | | | | | | | | | | | | | | | | | | | | | | Problem: `nlua_pcall()` references `_G.debug.traceback`. If user code deletes it or breaks it some other way, various Lua features are broken. _G.debug = nil vim.schedule(function() end) vim.wait(100) E5113: Lua chunk: attempt to index a nil value stack traceback: [C]: in function 'loop_poll' [string "vim/_core/editor"]:176: in function 'wait' crash.lua:3: in main chunk PANIC: unprotected error in call to Lua API (attempt to index a nil value) Solution: Check `_G.debug.traceback` before using it as errfunc. If it's broken, omit the traceback and say so in the error message. Note: We could cache `_G.debug` in LUA_REGISTRYINDEX on startup, but that would prevent plugins from providing custom functionality there (and we happen to do so in `tui_spec.lua` for example).
* docs: misc #41452Justin M. Keyes2026-08-23
| | | | | Co-authored-by: darkdi <rantovov5@gmail.com> Co-authored-by: Qiaoxi Guo <28090444+breadtitor@users.noreply.github.com> Co-authored-by: zaveshaa <zaveshaa@gmail.com>
* fix(lifecycle): Lua state not freed if free_all_mem() is re-entered #41366Justin M. Keyes2026-08-18
| | | | | | | | | | | | | | | | | | | | | | Problem: Leak reported by ASAN CI, always at Lua state init. ERROR: LeakSanitizer: detected memory leaks strbuf_init src/cjson/strbuf.c:62:22 json_create_config src/cjson/lua_cjson.c:635:5 lua_cjson_new src/cjson/lua_cjson.c:2060:5 nlua_state_add_stdlib src/nvim/lua/stdlib.c:840:3 nlua_state_init src/nvim/lua/executor.c:973:3 ... SUMMARY: AddressSanitizer: 1055 byte(s) leaked in 2 allocation(s). Analysis: `getout()` may run during `free_all_mem()` (that's why it checks `!entered_free_all_mem`). If that happens, the second `os_exit()` returns early from `free_all_mem()` then `exit()` is called and Lua state is never closed. Solution: Always call `nlua_free_all_mem()` in the early-return case.
* feat(cwd)!: :lcd! (bang), rearrange :bcd/:lcd/… scope precedence #41194Justin M. Keyes2026-08-07
| | | | | | | | | | | | | | | | Problem: - buf-local CWD scope is lower priority than :lcd, which is weird. ``` win > buf > tab > global ``` - No way to clear current CWD at a given scope. Solution: - Rerrange scope precedence to: ``` buf > win > tab > global ``` - Introduce "bang" variants (`:bcd!`/`:lcd!`/`:tcd!`) which clears the local CWD for the given scope.
* feat(vim._with): `keepcwd`Justin M. Keyes2026-08-06
|
* feat(cwd): support explicit chdir (:bcd/:tcd/…) in temp contextJustin M. Keyes2026-08-06
| | | | | | | | | | | | | | | | | | Problem: - Explicit `:bcd` (etc.) persists from `nvim_buf_call()` but not from an autocmd handler targeting a hidden buf (`LspAttach`, `TermRequest`, …), which needs a `vim.schedule()` workaround. - `vim._with()` is supposed to work as a "sandbox", discarding side-effects, but it leaks CWD changes: `:lcd` from a `win` context, any chdir from a visible-buffer context. Solution: - Explicit :cd/:tcd/:bcd during a temp context persists by default. - "Ambient" directory changes ('autochdir', existing win-local CWD, etc.) are still undone, as before. - Add `kCtxKeepDirs`: snapshot/restore the target's full CWD state (w/b/tp-local, global, cwd). Used by `vim._with()` and `'inccommand'`, which must not leak state.
* fix(options): ":set foo?" for Lua callbacks #41100Justin M. Keyes2026-08-01
| | | | | | | | | | | | | | | | | Problem: `:set foo=<tab>` and `:set foo?` for func/expr options set to a Lua function, always displays "v:lua". The existing "<Lua N: file:line>" form was avoided because the ref id N changes on every read of the same option. Solution: - Don't attempt to tab-complete Lua functions. - Show "<Lua file:line>" (or "<Lua>" if source file is unknown). Example: :set operatorfunc? operatorfunc=<Lua ~/.config/nvim/init.lua:42>
* refactor(options): drop OptVal, use ObjectJustin M. Keyes2026-07-29
| | | | | | | | | | | | | Problem: The object subsystem has an intermediate representation for no real reason. Besides the code cost, this also adds an extra (api <=> OptVal) conversion step, which is a (small) perf cost. Solution: We already have `Object`, so use it instead. - drop `OptVal`, `OptValData`, `OptValType`, and related boilerplate. - add `kObjectTypeUnset`.
* fix(lua): vim.regex():match_str abort in Luv callback #41008Justin M. Keyes2026-07-27
|
* refactor(exmode): Ex-mode as cmdwin + Lua #40991Justin M. Keyes2026-07-27
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Problem: POSIX-compatible Ex-mode requires special-cases all over the codebase to match various quirks that don't actually matter to users. - The main utility of *interactive* Ex-mode is its REPL behavior, and that can be achieved with `cmdwin`, which also gains extra UX benefits. - The main utility of *non-interactive* `nvim -es` is for shell scripting, where Ex-mode quirks are mostly unhelpful (e.g. the "Entering Ex mode" message). Solution: - Reimplement *interactive* Ex-mode as a "persistent, insert-mode cmdwin" in Lua. - "nvim -e/-E" is simply an alias to "gQ". - Reframe *non-interactive* Ex-mode (`nvim -es`) as "script mode". - Drop POSIX Ex-mode quirks. Improvements: - "nvim -V1 -es" output ends with a final newline! - "nvim -V1 -es" no longer shows the "Entering Ex mode" msg. (This was pointless noise, unwanted for scripting purposes.) - stdin is no longer typeahead. Scripts (":lua io.read()") can read stdin as data. - Empty line is a no-op: a stray blank line no longer moves the cursor (deviates from POSIX ex "+1"), no longer exits 1 at EOF (E501). Preserved behavior: - cursor starts at "$" - mode()=="cv" (for non-interactive) - multiline commands (:append/:function/heredoc pull continuation lines) - bare-range print - :print=>stdout - -V1=>stderr - CRLF input - continue-after-error and exit codes Dropped (regressed) POSIX behavior (non-interactive): - Event loop only ticks while/between commands, not while blocked waiting for a stdin line. - ":g/pat/visual...Q" - input()/getchar()/":s/x/y/c" no longer consume stdin lines as answers: Nvim stops at end-of-input, skipping the rest of the script, exit 0. Use ":lua io.read()" instead. - If users care about this they should use interactive Ex-mode (`gQ`). - ":@r" stops at end of the register instead of continuing to read cmdline input from stdin.
* feat(eval): declare more "fast" functions #40834Justin M. Keyes2026-07-19
|
* fix(lua): vim.keycode cleanup #40817Justin M. Keyes2026-07-18
|
* feat(lua): vim.keycode() gets structured parse result #38636altermo2026-07-18
| | | | | | | | | | | | | | | | | 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>`)
* Merge pull request #40628 from Rawan10101/wasm-runtime-bundlingbfredl2026-07-12
|\ | | | | build(wasm): bundle runtime files into MEMFS
| * build(wasm): bundle runtime files into MEMFSrawan101012026-07-12
| |
* | refactor(input): rename getchar.c => input.c #40698Justin M. Keyes2026-07-12
| | | | | | | | | | | | | | | | | | | | | | | | 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.
* | refactor: unify context-switching conceptsJustin M. Keyes2026-07-10
| |
* | refactor(lua): rename nlua_call_typvalJustin M. Keyes2026-07-06
| |
* | fix(lua): bufwrite message overridden by :redrawstatusJustin M. Keyes2026-07-06
|/ | | | | | | | | | | 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
* fix(lua): give nicer error message when trying to index vim.NILbfredl2026-06-29
| | | | | | | | | 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.
* fix(lua): reject process exit in fast callbacks #39937Barrett Ruth2026-06-27
| | | | | | | | | | | | 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.
* fix(treesitter): give each wasm parser its own TSWasmStore #40163Ryan Patterson2026-06-16
| | | | | | | | | | | | | | | | | 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
* feat(startup)!: "standalone" mode -ll is killbfredl2026-06-04
| | | | | | | | | | | | | | 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.
* Merge pull request #39920 from bfredl/luastringbfredl2026-05-25
|\ | | | | fix(lua): use even safer and even better error() message conversion
| * fix(lua): use even safer and even better error() message conversionbfredl2026-05-25
| | | | | | | | | | | | | | | | | | | | | | | | problem: this crashes neovim: ondska = setmetatable({}, {__tostring = error}) error(ondska) solution: make it not crash neovim
* | fix(api): fail fast if `has_lua_imp` retval looks wrong #39866Justin M. Keyes2026-05-20
|/
* refactor: simplify aucmd_restbuf usage #39879Justin M. Keyes2026-05-19
| | | | | | | | 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.
* fix(api): regressions with lua return values #39837bfredl2026-05-18
| | | | | fixes #39834 fixes #39833 fixes #39851
* vim-patch:9.2.0494: User commands cannot handle single args with spaces (#39854)zeertzjq2026-05-18
| | | | | | | | | | | | | | | | | | | | | | | | | | 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#20102 closes: vim/vim#20189 https://github.com/vim/vim/commit/f0e874a129a702457a383017b576eef1553f95d8 Co-authored-by: Maxim Kim <habamax@gmail.com>
* feat(api): nvim_buf_call, win_call can has multiple return values #39801bfredl2026-05-17
| | | | | | from the "because we can and it is not much code" department. (diffcount excluding tests is actually negative) fixes https://github.com/neovim/neovim/issues/39636#issuecomment-4397141270
* test: run Lua harness with nvim -lLewis Russell2026-05-13
| | | | | | | | | | | | | | | | | | 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
* fix(messages): fast context for for nvim_echo({kind}) callback #39755luukvbaal2026-05-12
| | | | | | | | 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.
* fix(style): some clang-tidy 22 fixesbfredl2026-05-11
| | | | | | | | | | | | | | | | | | | | | | 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..
* fix(treesitter): crash in ts_parser_delete after gc #39497Szymon Wilczek2026-05-07
| | | | | | | | | | | | | | 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>
* refactor(excmd): pass fargs to Lua for builtin cmds #39528Justin M. Keyes2026-05-02
| | | | | | | | | | | | | | 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`.
* fix: vim._with may silence all cmdline-errors #34301altermo2026-05-01
| | | | | | | | | | | | | | | | | | | | | | | | Problem: vim._with may silence all cmdline-errors if emsg_silent=true, silent=false. Steps to reproduce: `nvim --clean -u repro.lua`, then `:echoerr 123`, nothing is shown. local api, fn = vim.api, vim.fn -- api.nvim_create_autocmd("CursorMovedC", { api.nvim_create_autocmd("CmdlineChanged", { callback = function(args) if args.match ~= ":" then return end -- vim.cmd([[silent! ]]) vim._with({ emsg_silent = true }, function() -- return api.nvim_parse_cmd(fn.getcmdline(), {}) end) end, }) Solution: Force CMOD_SILENT if CMOD_ERRSILENT.
* fix(ui): z=, tselect with async vim.ui.selectJustin M. Keyes2026-04-30
| | | | | | | | | | | 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
* feat(ui): use vim.ui.select for :oldfiles, :recoverJustin M. Keyes2026-04-30