aboutsummaryrefslogtreecommitdiff
path: root/test/functional/api
Commit message (Collapse)AuthorAge
* fix(options): out-of-range number in 'iskeyword' aborts Nvim (#42410)ump45nose4 days
| | | | | | | | | | | | Problem: A number in 'iskeyword', 'isfname' or 'isident' that overflows (e.g. via a modeline) hits abort() or an assert in getdigits_int(). Solution: Parse the numbers non-strictly so an out-of-range value gives E474. Fixes #42409 AI-assisted
* fix(options): extend dry-run validationLewis Russell5 days
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Problem: nvim_set_option_value() dry runs skip checks in did_set_* callbacks. This logic also prepares values needed to apply the option, such as tab widths or a compiled regexp. Moving the checks into validation callbacks needs a way to save that work for did_set_*. Solution: Store prepared values in optset_T so did_set_* can consume them after the option is stored. Dry runs perform the same validation, then free the prepared values without changing live state. This extends dry-run validation for: - 'cedit': reject invalid command-line window keys. - 'completeitemalign': require each completion column exactly once. - 'cursorlineopt': reject empty values and conflicting line modes. - 'encoding': accept only UTF-8 and its aliases. - 'fileencoding': reject lists and local changes with 'nomodifiable'. - 'messagesopt': require mandatory fields and enforce numeric limits. - 'spellcapcheck': return regexp errors, including under :silent!. - 'spellsuggest': reject conflicting suggestion modes. - 'varsofttabstop' and 'vartabstop': reject invalid tab widths. - 'wildmode': reject more than four completion stages. 'makeencoding' also uses the shared encoding preparation. Separating value preparation exposed a bug in 'vartabstop' and 'varsofttabstop': failed assignments could discard local tab widths, and global assignments could overwrite them. Fix this by preparing new widths separately and applying them only after a valid local assignment. AI-assisted
* fix(api): nvim_buf_call() from expression for window in non-current tabpage ↵Justin M. Keyes6 days
| | | | | | | | | | | | | | | | #42352 Problem: - nvim_buf_call() aborts (assert) when called from an expression evaluated for a window in another tabpage ('foldexpr', 'statusline'), because `curwin` is not in `curtab`. - After it returns, `curwin` is wrong if it switched windows. - `vim._with{win=…, keepcwd=true}` does not undo `:lcd` if `win` is in another tabpage. Solution: - Allow `curwin` outside `curtab`, and restore if it was outside `curtab` at `ctx_switch()`. - Find the target window in any tabpage when restoring its dirs.
* fix(api): nvim_set_option_value() "remove" ignores non-adjacent items #42351Justin M. Keyes6 days
| | | | | | | | | | | | | Problem: `nvim_set_option_value('fo', {'c','r','o'}, {operation='remove'})` does nothing on "jcqrol": the items are joined into one `:set-=` value, which only matches a contiguous substring. Removing map keys, e.g. {'eol','space'} from 'listchars', never matches. `vim.opt:remove{…}` inherits this. (Regression from f34ee3da80e8; 429338e74bf4 fixed the case where the flags are adjacent and in order.) Solution: Remove each item separately.
* fix(api): crash on invalid :tab address #42328glepnir7 days
| | | | | | | | Problem: nvim_parse_cmd("'btab", {}) and typing ":'btab" crash, parse_cmdline() continues with a NULL eap->cmd after an invalid :tab address. Solution: Fail if parse_command_modifiers() set an error.
* feat(api): nvim_chan_set()Justin M. Keyes11 days
| | | | | | | | | | | | | | | | | | | Problem: Only the stdio channel can "own" a server (self-exit on close). After :restart the TUI reconnects over a socket, so the restarted server does not have "self-exit" behavior when the channel closes (e.g. by UI disconnect, host-terminal close, etc.), which leaves a dangling server. Solution: - Introduce `nvim_chan_set`. (`nvim_set_client_info` doesn't fit: exposing it to Lua would require a `channel` param, which is not backwards-compatible, plus it's awkward because it doesn't merge info.) - Broadcast per-channel properties in the "restart" UI event as an opaque dict (a potentially useful pattern for other UI-events in the future). - UIs are expected to restore the channel properties on restart. - `nvim_get_chan_info()` reports "detach". - Remove `nvim__chan_set_detach()`.
* fix(terminal): nvim_open_term crash in zero-size window #42248Justin M. Keyes2026-10-06
| | | | | | | | | | | | | Problem: nvim_open_term() sizes the terminal from the current window, which may have 0 rows (bordered float, 'cmdheight' taking all rows). libvterm aborts when resizing from 0 rows. Solution: In `terminal_alloc()`, treat zero width/height as the pty default (80x24), like jobstart(term=true). Open question: nvim_win_get_height() on such a float returns -1 (winheight() returns 0).
* test: simplify functional test expectationsLewis Russell2026-10-05
| | | | | | | | | | | | Problem: Repeated default fields and field-by-field assertions obscure the values that each functional test is meant to check. Solution: Add eq_partial() to check selected record fields while preserving list length and order. Use local helpers to fill fixed expected defaults where tests need full equality. Keep explicit absence checks and test-specific values at the call sites. AI-assisted
* fix(rpc): redraw parse failure silently closes channel #42220Justin M. Keyes2026-10-03
| | | | | | | | | | | | | | | | | | | | | | | | | | | Problem: When `unpacker_parse_redraw()` fails, it closes the unpacker without setting `unpack_error`, so `chan_close_on_err()` gets a NULL message. A pending request fails with an empty error (`RPC: (null)`). c04d0734a728 fixed the crash that followed (advancing the closed unpacker), but not the silent close itself. Repro: Self-attach (`nvim_ui_attach` on a loopback `sockconnect()` channel) closes the channel without a visible reason, either while `nvim_ui_attach` is pending or at the next redraw. Solution: Set an error msg when `unpacker_parse_redraw()` fails. Note: Self-attach fails for accidental reasons, not by design: - An incoming "redraw" notif is always parsed "as a UI client" (`handle_ui_client_redraw`), whose `grid_line` buffer is only allocated in a UI client process. A loopback channel is both ends, so it parses its own redraw stream as if it were the TUI. - Even if parsing worked, `parse_msgpack()` drops redraw events unless `ui_client_attached`, and a `sockconnect()` client has no notification callback.
* test(api): self-attach test #42219CompileAndConquer2026-10-03
|
* fix(rpc): don't advance closed unpacker on self-attachLorenzo Piombini2026-10-02
|
* refactor(multicursor)!: drop nvim_mcursor #42156Justin M. Keyes2026-09-30
| | | | | | | | | | | | | | Problem: `nvim_mcursor` is not fully baked, and not strongly motivated, currently. The main programmatic interface for cursors is the "nvim.multicursor" extmarks namespace. Solution: Drop `nvim_mcursor`. Add an internal hook so that multicursor can react when extmarks are added to the "nvim.multicursor" namespace. TODO: If extmarks/namespaces are clumsy to work with, we should _fix that_ rather than adding wrappers.
* Merge pull request #41971 from bfredl/noisprintbfredl2026-09-22
|\ | | | | refactor(charset)!: delete 'isprint' and cleanup 'iskeyword' and friends
| * refactor(charset)!: delete 'isprint' and cleanup 'iskeyword' and friendsbfredl2026-09-22
| | | | | | | | | | | | | | | | | | | | | | | | | | In the `enc=utf-8` only world, the 'isprint' option is quite obsolete. The function is only consulted for decoded U+0080 to U+00FF chars whose printability is defined by the unicode charset, just like higher codepoints. Meanwhile, invalid `\x80` to `\xFF` bytes are always unprintable. Disentangle 'iskeyword', 'isfname' and 'isident' from each other by giving them all separate bitsets. Now these can be updated individually. These updaters can back out of errors gracefully, no need for extra panic logic in option[str].c to restore partially updated chartabs.
* | fix(api): preserve option trust during mergesLewis Russell2026-09-22
|/ | | | | | | | | | | | | Problem: API append, prepend, and remove operations reach the option setter as full replacements. This can clear an expression's untrusted flag and allow it to write files, unlike equivalent :set operations. Solution: Pass replacement status through the option setters so merges preserve the existing value's trust restrictions. AI-assisted
* refactor(api): reuse parse_cmdline in nvim_cmd #38508glepnir2026-09-22
| | | | | | | | | Problem: nvim_cmd() duplicates Ex parsing: it sets the address type and default range itself and builds the whole command line in build_cmdline_str(). Solution: Build only the range, command name and arguments, and let parse_cmdline() fill in exarg_T.
* 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
* refactor(options): move schema checks into validate_option_value()Lewis Russell2026-09-18
| | | | | | | | | | | | | Problem: Dict schema checks are mixed into individual option validators. Solution: Check schemas before calling option validators. Remove the redundant checks and callback entries. AI-assisted
* fix(options): extend nvim_set_option_value() dry-run validationLewis Russell2026-09-18
| | | | | | | | | | | | | | | | Problem: nvim_set_option_value() dry runs skip checks in did_set_* callbacks. Solution: Split validation out of these callbacks so dry runs and normal assignments use the same checks. Read 'mkspellmem' limits when building spell files. Keep the 'spellsuggest' limit unchanged when an assignment fails. AI-assisted
* fix(options): validate nvim_set_option_value() dry runsLewis Russell2026-09-18
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | Problem: nvim_set_option_value() with dry_run=true accepts invalid values such as foldmethod='nonsense'. Validation is mixed with side effects in the did_set_* callbacks, so dry runs skip it. Solution: Split value validation out of the did_set_* callbacks into separate validation_cb callbacks. Use these checks for both normal assignments and nvim_set_option_value() dry runs. Keep side effects in did_set_*. Replace direct foldenable/foldlevel assignments in C folding commands with setters. This gives C commands the same OptionSet and synchronization behavior as Lua assignments. Use normal setters for 'tabstop' and 'vartabstop' in :retab so C code indentation and folds based on indentation use the new tab widths. Direct assignments could leave them using the old widths. :retab temporarily turns off 'list' to calculate tab widths. Previously, :retab -1 reported an error and returned without restoring 'list'. Check the argument before turning 'list' off, so an invalid argument leaves the option unchanged. AI-assisted
* fix(lua): add missing param annotations (#41911)Lewis Russell2026-09-15
|
* feat(api): add ev.win to autocmd callback args #39826glepnir2026-09-09
| | | | | | | | Problem: Autocmd callbacks currently have to rely on curwin, which may already have changed by the time they run. This is especially noticeable for WinNew, WinClosed, WinResized and WinScrolled. Solution: Track the window in the autocmd context and expose it as ev.win. Pass it explicitly for the window events that need it.
* Merge pull request #41751 from bfredl/stringly_nilsbfredl2026-09-08
|\ | | | | test(ci): non fatal errors for [NULL] safety in vim_snprintf
| * fix(api): do not pull a fast one for nvim_del_augroup_by_idbfredl2026-09-08
| | | | | | | | | | | | | | | | This is a bit more boilerplate because of painful impedance mismatch (is augroup names the root source of truth? is ids the source of truth? who knows? what does truth even mean?), but show an error reflecting what the caller actually tried (they passed in an invalid id, not a null string)
* | fix(statusline): "%.0" renders garbage #41685Volodymyr Chernetskyi2026-09-06
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Problem: `getdigits_int(pp, strict, def)` returns `def` only when the number fails to parse or overflows, never when the text parses to a literal `0`, so `%.0` leaves `maxwid` at 0. Both render paths then break down: `l > maxwid` is always true and `while (l >= maxwid)` consumes the whole string, so `%.0f` comes out as `<` and `%.0l` as `0>3`. Vim guards for this right after the same call. Nvim had the guard too until 3344cffe7b ("getdigits: introduce `strict`, `def` parameters"), which folded it into the new `def` argument and so changed what a literal zero means. The neighbouring `minwid` call was translated correctly. Solution: Fall back to 50 when the parsed width is not positive, as Vim does. AI-assisted
* | fix(api): leaked error message in nvim_win_set_buf() #41688Volodymyr Chernetskyi2026-09-04
| | | | | | | | | | | | | | | | | | | | | | | | | | | | Problem: Both handle lookups write into the same `Error` before either one is checked, and `api_set_error()` unconditionally `xmalloc`s `err->msg` without freeing what is already there. When both handles are invalid, the message allocated for the window is silently overwritten by the one for the buffer and leaked. Solution: Check the window before looking up the buffer. As a side effect the reported error no longer names the buffer when the window was wrong too. AI-assisted
* | fix(api): leaked error message in nvim_eval_statusline() #41690Volodymyr Chernetskyi2026-09-04
| | | | | | | | | | | | | | | | | | | | | | | | Problem: `find_window_by_handle()` returns NULL only after `VALIDATE_INT` has already set `err`, so the `api_set_error()` that followed allocated a second message over the first pointer and leaked it. Solution: Drop the redundant call and keep the helper's message, which is already set and better worded. AI-assisted
* | fix(editor): shada "force" load clears v:oldfiles #41662Justin M. Keyes2026-09-03
|/ | | | | | | | | | | | Problem: `shada_read()` rebuilds `v:oldfiles` for any "forced" read, which first *clears* the list. For an in-memory caller such as multicursor, the list is never repopulated. So `v:oldfiles` is empty after `Q` + edit. Solution: Rebuild `v:oldfiles` only when `kShaDaGetOldfiles` is requested. `shada_read_everything()` (`:rshada[!]`, startup) always requests it. old bug from 411a06c8b6e9
* feat(multicursor): MC HAMMER #41587Justin M. Keyes2026-09-01
| | | | | | | | | | | | | | | | | | | | | Other (squashed) commits: fix(tui): emit ui_send output atomically with the frame Problem: tui_ui_send() writes directly to the TTY, bypassing the output buffer. Sequences sent via nvim_ui_send() (e.g. kitty multiple-cursors, or visual-dot-repeat) always arrive in a separate TTY write from the frame they were computed for. This manifests as "tearing", or e.g. in the case of multicursor the terminal renders text with stale cursor overlays. Solution: - tui_ui_send(): while a frame is being assembled (pending invalid regions or buffered output), buffer instead of writing directly. - Out-of-frame sends (tty queries, clear-on-disable) still write immediately. - mcursor.lua: emit the terminal-cursor update at the end of the redraw cycle (`on_end`, when screen positions are final) instead of vim.schedule().
* fix(buffer): keep trailing slash significant in URI names #41577Jaehwang Jung2026-09-01
| | | | | | | | | | | | | Problem: Literal path comparison ignored one trailing slash for every buffer name, including URIs. Generic URI syntax does not make a non-empty path equivalent to the same path with a trailing slash, so distinct URI buffers collapsed into one. Solution: Require equal lengths when comparing URI buffer names, while retaining trailing-separator normalization for filesystem paths. AI-assisted
* fix(cmdwin): allow a user to switch to other buffers #41199Rob Pilling2026-08-28
|
* feat(editor): undo restores cursor position #41520Justin M. Keyes2026-08-27
| | | | | | | | | | | | | | | | | | Problem: Undo places the cursor wherever the cursor happened to sit at "save time" (`uh_cursor` is sampled lazily on the first change). Examples: - `i` preserves, but `a` does not - `diw`, `atest<Esc>`, `d^` abandon the original position - `D`, `o` restore it (by accident). Solution: `composite` tracks the pending atom (and its `origin`) across frames. A `stuffed` continuation frame inherits the `origin` + prepped redo. Store `origin` info in the undo header, so undo can restore it. - Not for a mid-command undo break (i_CTRL-G_u). - Undoing a mapping restores where the mapping started (which technically may be different than where the "edit" started).
* test: fix wrong order of args to eq(), neq() #41499zeertzjq2026-08-26
|
* fix(lifecycle): late signal kills Nvim mid-teardownJustin M. Keyes2026-08-19
| | | | | | | | | | | | | | | | | | | | Problem: A deadly signal arriving during teardown can kill Nvim while it is preserving swapfiles. `os_exit()` ignores deadly signals via `signal_reject_deadly()`, but `signal_teardown()` => `uv_signal_stop()` resets them to the default behavior, so SIGHUP arriving after that kills the process: [Process exited 129] // 128 + SIGHUP This is a race when closing a pty: kernel sends SIGHUP to foreground process group *and* the reads return EOF, so `chanclose()` on a TUI job prepares to exit twice. This means it is unpredictable whether Nvim exits 1 or is terminated. Solution: Ignore deadly signals once the watchers are closed. Only SIGKILL interrupts it now.
* fix(extmarks): redo of a mark created during an edit #41324Justin M. Keyes2026-08-15
| | | | | | | | | | | | | | | | | Problem: A mark created by `nvim_buf_set_extmark()` while an undo block is open never comes back on redo. Analysis: Undo deletes the text it covers and collapses the range; redo replays the splices, which re-insert the text but cannot re-expand the mark. `extmark_set()` records a position only for a mark it moves, not for one it creates. Solution: Record the created position for redo; undo leaves the mark to the splice replay, since it did not exist before the edit. Each side of a paired mark gets its own entry. Redo also revives a mark that undo invalidated, else its position returns but its highlight does not.
* fix(extmarks): undo-redo of a mark explicitly moved during an edit #41252Justin M. Keyes2026-08-10
| | | | | | | | | | | | | | Problem: A mark moved by nvim_buf_set_extmark() during an edit is misplaced by undo and redo. Only splices ("edits") are recorded, and replaying them reproduces the shifts they caused, never the explicit set: the mark ends up wherever the text pushed it. Solution: When an open undo block moves an existing mark, record both positions. Undo restores the pre-set position, redo re-applies the set. Partially reverts 18334a4a0cd9 ; ExtmarkSavePos.row/col were unused because nothing recorded an explicit move, but now `extmark_set()` does.
* refactor(ruler)!: replace C implementation with 'rulerformat' expressionSébastien Hoffmann2026-08-04
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Problem: The default 'ruler' is implemented in C instead of the 'statusline' DSL. Solution: Replace the C implementation with a default 'rulerformat' expression. This is a continuation of #1248 and #33036. Advantages: - configuration is more discoverable, the default being a useful example - users and plugins can augment the default - code reuse and less C code to maintain - ui2: due to the use of an item group with `minwid`, it can expand instead of truncating when the content gets too long, which is particularly useful for locales with long translations of Top/Bot/All Implementation details: As is the case for 'statusline', when trying to set 'rulerformat' to an empty string, the default expression is restored instead, mimicking how previously the default C implementation would have been activated. Just like before, `:set rulerformat=` and `:set rulerformat&` have the same effect, and the ruler is disabled with `:set noruler`. The default expression uses an item group with `%=`, unlike the fallback in the previous default statusline `%-14.(%l,%c%V%) %P`, because the total width and how it is configured is immediately clear without documentation, it is a more useful pattern in general that works when both sides have flexible width, and it also works for vim, which is useful for configuration sharing/reuse. A truncation marker `%<` is added at the end to mimic how at small screen widths, the scroll percentage would disappear first, so that the cursor position can remain fully visible. BREAKING CHANGES: - `&rulerformat` can no longer be set to an empty string - ui2: the default ruler is no longer of fixed width, but can expand - at very small screen widths (< 36 columns) - ui2: it will no longer try to shrink white-space before truncating - it truncates gradually from the right, whereas previously, the scroll percentage would disappear all at once - l10n can no longer add a space after the comma between line and column (this was only done for one language: Ukrainian)
* fix(ruler)!: consistent width in last line and statuslineSébastien Hoffmann2026-08-04
| | | | | | | | | | | | | | | | | | | | | | | | | | | Problem: Traditionally, the ruler in the last line is one cell shorter than in the statusline, leaving the last cell of the screen blank. According to code comments, this is in order to prevent unwanted scrolling on "some" (unspecified, but presumably ancient) terminals. Berkeley vi is more specific in its `vs_modeline` function: dumb terminals with hardware scroll, SunOS 4.1.1 and Ultrix 4.2 curses. (n)curses still has a similar limitation in `(w)addstr`, but apparently only for historical reasons. Maintaining the different widths leads to awkward inconsistencies when the ruler is configured with 'rulerformat', except for the special case where it contains a top-level `%=`. Shifting the ruler in the last line to the left would be a solution, but the empty cell at the end doesn't seem to be relevant anymore. Solution: extend the ruler in the last line all the way to the right edge of the screen, just like in the statusline. The exact same amount of place will be available to the rest of the UI as before. BREAKING CHANGE: - the default ruler width is now 18 cells - the last cell of the screen is no longer empty Closes #41076
* feat(options): lua closure/function optionsJustin M. Keyes2026-07-31
| | | | | | | | | | | | | | | Problem: Cannot assign Lua functions/closures to "func" ('completefunc', 'tagfun', …) or "expr" ('foldexpr', 'indentexpr', …) options. Solution: - Store "func"/"expr" options as `Callback` instead of string. - Delete oceans of copy-pasted code. - BREAKING: LuaRef returned via RPC/Vimscript is now represented as `"<Lua N: file:line>"` (like what `:map` shows) instead of `nil`. - Note: `man.vim` still uses `v:lua` string, bc it's a vimscript ftplugin. Helped-by: Lewis Russell <lewis6991@gmail.com>
* feat(exmode): "1q:", :exmode #41010Justin M. Keyes2026-07-27
| | | | | | | | | Problem: Want `gQ` for _le multicursor_. Solution: - Don't use `gQ` for exmode. - Introduce `:exmode`. - Introduce `[count]q:` as an alias to `:exmode`.
* 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) - mul