aboutsummaryrefslogtreecommitdiff
path: root/test/functional/ex_cmds
Commit message (Collapse)AuthorAge
* 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
* test(env): "uv_os_getenv failed: -4071 EINVAL" #42187Justin M. Keyes2026-10-01
| | | | | | | | | | | | | | | | | | | | | | | Problem: Windows CI fails since f83acfd327e3756c65f69f170055fe18a751f5f2 : the `:grep` test greps the contents of `test/*`, which depends on whatever is in the test files. After that commit, quickfix expands `"$⨯"` in diagnostic_spec.lua as part of a file name, which logs an error: $NVIM_LOG_FILE: D:/a/neovim/neovim/build/nvim.log_ex_cmds ERR 2026-09-30T17:34:03.034 T190.2776.0 os_getenv:98: uv_os_getenv(�) failed: -4071 EINVAL Analysis: `expand_env_esc()` has buggy logic: it reads the name bytewise (not UTF8-aware), so expand_env("$é") looks up "\xC3". while (c-- > 0 && *tail != NUL && vim_isIDc((uint8_t)(*tail))) { *var++ = *tail++; } Solution: In the `:grep` test, grep test/functional/fixtures/bigfile.txt instead of the entire `test/*` contents. This is faster and more predictable.
* vim-patch:9.2.1114: Visual selection is not highlighted at the cursor ↵Takumi Katase2026-09-27
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | position #42105 vim-patch:9.2.1114: Visual selection is not highlighted at the cursor position Problem: In a terminal the character under the cursor is normally not shown as part of the visual selection. Solution: Don't set "noinvcur" in win_line() (Shane Harper). The problem did not occur in the GUI, only in a terminal. The name "noinvcur" ("don't invert the cursor") was misleading: the variable did not affect the cursor, but whether the character under the cursor could be displayed as part of the visual selection. The "noinvcur" handling may have been meant to help during a visual selection when Visual is shown in reverse video and the terminal draws the cursor by inverting the character under it, as otherwise the character under the cursor would not look highlighted like the rest of the selection. But Vim cannot know in general how a terminal draws the cursor, and when either the cursor is not drawn by inverting the character under it or Visual is not shown in reverse video, the handling just left a character of the visual selection unhighlighted. closes: vim/vim#21317 https://github.com/vim/vim/commit/d10aab4ba6602f8f2f2576d2d62ceb63869db6fb Co-authored-by: Shane Harper <shane@shaneharper.net> 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(session): limit `SessionWritePost` to `:mksession`Barrett Ruth2026-09-12
| | | | | | | | | | Problem: `SessionWritePost` also fires for `:mk{view,vimrc,exrc}` without updating `v:this_session`. Plugins appending session data can modify a previously saved session when only a view is written. Solution: Emit `SessionWritePost` only for `:mksession`.
* fix(session): limit `SessionWritePre` to `:mksession`Barrett Ruth2026-09-12
| | | | | | | | | Problem: `SessionWritePre` also fires for `:mk{view,vimrc,exrc}`. Single-window saving should not invoke whole-session preparation Solution: Emit `SessionWritePre` only for `:mksession`
* fix(write): crash when pre-write autocmd changes cwd #41840Shantanu Raj2026-09-11
| | | | | | | | | | Problem: A :cd, or a window-context restore (nvim_win_call(), win_execute()), in a pre-write autocmd clears the short name of a buffer outside the current directory. buf_write then copies a NULL filename and crashes. Solution: Rebind short-name aliases to b_fname, which retains the effective filename. Add a regression for a BufWritePre autocmd changing cwd away from the file.
* perf(help): generate helptags with LPegLewis Russell2026-09-10
| | | | | | | | | | | | | | | | Problem: Generating helptags builds and queries a full vimdoc syntax tree. This is much slower than the previous scanner and also happens before discovering that the tags file cannot be written. Solution: Extract tags with an LPeg grammar, reading each file once and skipping example blocks and ordinary text. Open the output first so unwritable directories can return immediately. This restores legacy handling of tags inside inline code spans. On an arm64 Mac, the 5.36 MB runtime corpus takes 24 ms instead of 604 ms, with identical generated tags. AI-assisted
* fix(help): resolve generic and nullable type referencesLewis Russell2026-09-07
| | | | | | | | | | Problem: K on generic and nullable type annotations can jump to the wrong help tag. Generated See references repeat the type names. Solution: Strip generic arguments and nullable suffixes when resolving help tags. Remove the redundant generated See references. AI-assisted
* fix(menu): menu_get() files terminal mode menus under the tooltip key #41687Volodymyr Chernetskyi2026-09-04
| | | | | | | | | | | | | Problem: `menu_mode_chars` includes the two-character `"tl"` value. The hardcoded `1` is the `key_len` argument of `tv_dict_add_dict()`, which truncated it to `"t"`, the designator for a tooltip. Scripts keying on the documented mode designators therefore miss terminal mode entries and misread them as tooltips. Solution: Pass the real length of the designator. AI-assisted
* fix(menu): menu_get() cannot tell :noremenu from :menu <script> #41686Volodymyr Chernetskyi2026-09-04
| | | | | | | | | | | | | | | | | | | | Problem: `REMAP_NONE` is -1 and `REMAP_SCRIPT` is -2. These are enumerated values, not bit flags, so `&` is the wrong operator: `x & -1` is `x`, and `-1 & -2` is -2, which is truthy. Both kinds of non-remapping menu therefore reported `noremap` and `sid` as 1 and became indistinguishable. The sibling `menu_get_info()` already compares with `==`. Solution: Compare rather than mask. The expectations in menu_spec.lua had the conflated values baked in, so 22 entries from plain `*noremenu` commands now report `sid = 0`, and the two from `nnoremenu <script> Export.Script` now report `noremap = 0`. AI-assisted
* fix(cwd): flicker after temp context-switch #41561 #41582Justin M. Keyes2026-08-31
| | | | | | | | | | Problem: `shorten_fnames()` always redraws the statusline/tabline, even if no buffer name changed. Since b296666e a temp context-switch (`win_execute()`, `vim._with{win=}`) restores the CWD, so every such switch flickers the message area. Solution: Redraw only if `shorten_buf_fname()` actually changed a name.
* test: fix wrong order of args to eq(), neq() #41499zeertzjq2026-08-26
|
* fix(help): :helptags on CRLF helpfilesJustin M. Keyes2026-08-25
| | | | | | | | | | | | | | | | | | | Problem: The vimdoc parser does not treat "\r" as whitespace, so in a CRLF helpfile the codeblock rule fails and "*" pairs inside examples become tags. On Windows this generates duplicate "." and "/" tags: D:/a/neovim/neovim/build/bin/nvim.exe -u NONE -i NONE -e --headless -c "helptags ++t doc" -c "exe 'cquit' !empty(v:errmsg)"" Error in command line: E154: Duplicate tag "." in gui.txt and repeat.txt E154: Duplicate tag "/" in pattern.txt and usr_08.txt E154: Duplicate tag "/" in usr_08.txt and pattern.txt Note: The old C parser was unaffected because it read helpfiles in text mode (`os_fopen(…, "r")`). Solution: Strip "\r" before parsing.
* fix(help): :helptags regressionsJustin M. Keyes2026-08-25
| | | | | | | | | | | | | | | | | | | | | | | | | | Problem: Parent commit regressed some behavior of the old C helptags-gen impl: - helpfiles in sub-directories are named by basename, so :help fails - "help-tags" always names "tags", never "tags-nl" - E150 E151 E152 E153 are never reported - existing tags file is not overwritten if no tags were found - a duplicate tag aborts the run, skipping the remaining directories - `*.TXT` and `*.FRX` (uppercase) are not recognized as helpfiles - tree-sitter-vimdoc accepts tags that the C parser rejected: `*a|b*` (breaks |links|) and unterminated `*tag` - PUC Lua "<" compares with (localized) `strcoll()`, so the tags file is not sorted by byte value (E432) - requiring `vim.treesitter` at load time breaks :help itself, not just :helptags, where the module is unavailable - `vim.pack` runs :helptags for plugins that have no "doc/" directory, so every install/update emits E150 Solution: - Fail the build if generating helptags reports any `v:errmsg`. - Restore old behavior: tags generated for runtime/doc are now identical to those from the C implementation. Errors are non-fatal messages instead of exceptions, so all directories are still processed. - Load treesitter lazily, report a plain error if parser is missing.
* feat(help): generate :helptags using TreesitterYochem van Rosmalen2026-08-25
| | | | | | | | | | | | | | | | | | | | Problem: Tags are manually parsed in C which is not flexible and prone to errors. Extending the help system to allow for other formats (e.g. Markdown) would require a large rewrite in the C core, while with Treesitter it only needs a query update. Solution: Use the power of treesitter to extract the tags from helpfiles. - build: set `$VIMRUNTIME` when generating helptags, like `cmake/Util.cmake` already does for other generators. - fix(help): only accept tags delimited by whitespace. The old C parser only accepted a `*tag*` preceded by start-of-line or whitespace and followed by whitespace or end-of-line. The vimdoc parser also captures tags followed by other text, e.g. `*$XDG_STATE_HOME*/.../logs` in starting.txt, which caused an E154 duplicate tag error for docs that were previously fine.
* fix(cwd): E812 when a message opens a window during a file read #41458Justin M. Keyes2026-08-23
| | | | | | | | | | | | | | | | | | | | | Problem: When ui2 is enabled, opening an already-open file in another Neovim instance results in the error `E812: Autocommands changed buffer…`. Analysis: On E812 the file is not loaded. The default SwapExists handler notifies `W325: Ignoring swapfile…`, and ui2 shows that message by opening a window, which is a temp context switch. `ctx_dirs_restore()` re-shortens every buffer name on the way out, and `shorten_buf_fname()` always frees and reallocates `b_sfname`. `readfile()` aliases `b_fname` across `check_need_swap()` and compares the pointer to detect a rename. Regression by b296666e419c, which replaced the `cs_save_sfname` restore (that kept curbuf's pointer) with `shorten_fnames(true)`. Solution: Keep the allocation in `shorten_buf_fname()` when the short name is unchanged. Pointer stability is what the E200/E201/E812 guards actually assert.
* fix(cwd): stale buffer names after temp context-switch #41433Justin M. Keyes2026-08-22
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | Problem: 1. `ctx_dirs_restore()` is the only chdir site that doesn't re-shorten buffer names; `post_chdir()`, `update_cwd()` and `do_autochdir()` all call `shorten_fnames(true)`. So after a temp window-context switch that moved the CWD, every buffer's `b_fname` is still relative to the other directory. ui2 renders messages in a float, and entering it is such a switch, so with a `:bcd` in a `BufReadPost` handler 'statusline' "%f" shows ".config/nvim/init.lua" while the CWD is already `~/.config/nvim`, and :write resolves the name against it: ``` E212: Can't open file for writing: no such file or directory ``` 2. `msg_multihl()` leaves `msg_ext_id` pointing at the caller's storage when nothing was emitted (e.g. 'msg_silent'): the reset only ran on a flush that produced chunks. The next message then ships a dead stack frame as its msg_show id. After ":silent write" the id is buf_write()'s `msg_id[MAXPATHL + 32]`: ``` id = "\0\0\0\0\0\0\0\0\29\0(<C6>k\24R\17p<C7><C7><C7>\1\0\0\0..." ``` Solution: 1. `shorten_fnames(true)` after restoring the CWD. Drop `cs_save_sfname`, which was a partial workaround for the same bug. 2. Release the id in `msg_multihl()`, where it is set and the caller's frame is still alive. Stop reading `.data.integer` out of a String union member.
* fix(write): :write can target a garbage filename #41432Justin M. Keyes2026-08-22
| | | | | | | | | | | | | | | | | | | | | | | | | Problem: `buf_write()` captures the buffer's `fname`/`sfname`/`ffname`, then emits its progress-message before opening the file. This may run user code synchronously: the `Progress` autocmd, and the `msg_show` handler of an in-process UI (ui2). Either can change the CWD, and `shorten_fnames()` then frees/reallocs every buffer's short name. The rest of `buf_write()` reads the freed name... "foldtext()" [New] 41L, 997B written E212: Can't open file for writing: illegal byte sequence ASAN, with ui2 enabled and a `BufEnter` handler that runs `:lcd`: READ path_skip_sep <- path_tail <- match_file_list <- buf_write FREE shorten_buf_fname <- shorten_fnames <- update_cwd <- set_curbuf <- win_set_buf <- nvim_open_win <- ui2 msg_show handler <- ui_call_msg_show <- msg_ext_ui_flush <- buf_write Solution: Copy the names after the `*Pre` autocmds. Note: `readfile()` has the same shape, but its messages pass no progress-id, so they skip `msg_progress()`. Safe, for now...
* 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(cwd): nvim_win_set_buf of :bcd buf, changes caller CWD #41329Justin M. Keyes2026-08-15
| | | | | | | | | | | | | | | | | | | | | | | Problem: Setting a :bcd buffer into another window, modifies the caller's CWD. local b = vim.api.nvim_create_buf(true, true) vim.api.nvim_buf_call(b, function() vim.cmd.bcd('..') end) vim.cmd('vsplit') vim.api.nvim_win_set_buf(vim.fn.win_getid(2), b) :echo haslocaldir(0) haslocaldir(-1,0) haslocaldir(-1,-1,0) 0 0 0 :echo getcwd() ==# getcwd(-1,-1) 0 Analysis: `ctx_dirs_save` only saves CWD if it predicts the switch can change it. But win_set_buf() replaces the target window's buffer *after* the switch, which cannot be "predicted" from `ctx_dirs_save`. Solution: Always snapshot whenever the switch enters another window. Skipping `os_dirname` was a micro-optimization.
* fix(cwd): nvim_win_set_buf changes global CWD #41328Justin M. Keyes2026-08-15
| | | | | | | | | | | | | | | | | | | | | Problem: nvim_win_set_buf() on a non-current win, while the current win has a win-local dir, changes the global CWD: :vsplit | lcd .. :call nvim_win_set_buf(other_win, buf) :wincmd l :verbose pwd [global] /parent " expected: the initial cwd Analysis: `globaldir` is where to return when no local dir applies; NULL means the process CWD is already there. Switching to a window with no local dir makes update_cwd() chdir back to `globaldir` and clear it. kCtxKeepCwd restores the process CWD but not that bookkeeping, so the restored window-local dir is mistaken for the global one. Solution: Save/restore `globaldir` with the CWD.
* fix(messages): `inputlist()` offers the mouse when it cannot be used (#41280)Barrett Ruth2026-08-14
| | | | | | | | | | Problem: `inputlist()` advertises "click with the mouse" purely because it implements click selection, so under the default `'mouse'` of "nvi" it offers a click that command-line mode never receives. Solution: Only offer the mouse when `'mouse'` covers command-line mode, the same condition `:help inputlist()` already documents.
* fix(cwd): :bcd lost by nvim_win_set_buf, nvim_open_win #41218Erdiansyah2026-08-07
| | | | | | | | | | | | Problem: :bcd (buffer-local directory) is not preserved after `nvim_open_win` or `nvim_win_set_buf` Analysis: set_curbuf() ends with update_cwd(), which falls back to os_chdir(globaldir) when the target buffer has no b_localdir. Solution: Pass kCtxKeepCwd to ctx_switch().
* fix(cwd): keep buffer-local dir when re-editing #41215Justin M. Keyes2026-08-07
| | | | | | | | | | | Problem: Re-editing a buffer (`:edit!`, re-reading a dir.lua buffer, etc.) drops its `:bcd` directory, so the CWD falls back to the global one. Whereas other buffer-local state (`b:` vars, local options) survives a reload. Solution: Don't clear buf dir in `buf_freeall()`; `do_ecmd()` calls that when reloading/re-editing. `free_buffer_stuff()` still clears them when a buffer is freed or reused for another file.
* 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(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(:bcd): do not "inherit" buffer-local dirJustin M. Keyes2026-08-04
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | Problem: Buffer-local CWD (:bcd) is "sticky", similar to window-local CWD (:lcd). But this contradicts one of its main benefits: per-buffer "project root" for LSP, OSC7. Other problems: - A buffer created with :edit/:enew/:new silently inherits b_localdir (and b_prevdir) from the previous buffer. - curbuf_reusable() refuses to recycle a scratch buffer that has `b_localdir`. - After :new/:vnew/:tabnew the CWD sticks to previous buffer's `b_localdir` even though the new curbuf has none, so :new is not equivalent to ":split | enew", and getcwd() disagrees with haslocaldir(). - Requires "which buffer spawned this buffer" semantics that no other buffer-local state has. Solution: Drop sticky/inherit behavior of buffer-local CWD (:bcd). - do_ecmd: always apply the new curbuf's dir (`fix_current_dir`), like `do_autochdir` already does. :tabnew from a :bcd buffer now reverts to global CWD (and fires DirChanged), same as :tabnew from a :lcd window. - curbuf_reusable(): recycling a scratch buffer frees its b_localdir. To get sticky/inherit behavior of CWD, use `:lcd`.
* fix(cwd): validate getcwd(…, -1)Justin M. Keyes2026-08-04
|
* refactor(editor): cleanup change-directory (:bcd) logicJustin M. Keyes2026-08-04
|
* feat(editor): `:bcd` changes buffer-local directorysaher2026-08-04
| | | | | | | | | | | | | | | | | | Problem: No way to set a buffer-local directory. Use-cases: - "Root dir" for LSP (and the "project" concept). - `:terminal` OSC 7 Solution: Add `:bcd` command. - Extend `getcwd()` to take a third arg; `getcwd(-1, -1, bunfr)` returns the buffer-local working directory. - Buffer-local directories have less priority than window-local ones, and more priority than tab-local ones. Co-authored-by: Justin M. Keyes <justinkz@gmail.com>
* 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(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) - 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.
* fix(buffer): name becomes empty when it is the cwd #40980Barrett Ruth2026-07-26
| | | | | | | | | Problem: Shortening the name of a buffer whose full name is the current directory + path sep yields empty string -> buf unnamed. This doesn't interact well with things like `:mksession`, which for example could `:badd` with no arg -> E471. Solution: Keep the full path when shortening yields an empty name, as `path_try_shorten_fname()` already does.
* fix(help): handle escape edge cases #40901phanium2026-07-22
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | * fix(help): preserve multi-letter CTRL modifiers in :help subject Problem: After PR 39537 normalized `CTRL-U-default` by excluding `-` from the trigger char class, the regex `(CTRL%-[^{])([^-_\\])` also fires between `CTRL-S` and the `H` of the `SHIFT` modifier in tags like `c_CTRL-SHIFT-V`, splitting it into the non-existent `c_CTRL-S_HIFT-V`. Solution: Exclude uppercase letters from the char class so a multi-letter modifier following `CTRL-X` (e.g. `SHIFT`, `ALT`) is no longer split. The `-` exclusion from 39537 is preserved so `i_CTRL-U-default` still works. * fix(help): escape ~ in expr-=~? exception to keep regex valid Problem: The `expr-=~?` exception in the tag alias table maps to `=~?`, but in Vim's regex engine `~` refers to the previous substitute pattern. With no prior substitute the pattern fails to compile, so `:help expr-=~?` returns E149 even though the tag exists. Solution: Escape `~` as \\\~` so the alias expands to a valid regex (`=\\\~?`) that substring-matches the `expr-=~?` tag. Other `expr-X?` entries are unaffected since they don't contain `~`. * fix(help): don't insert _ before CTRL- when preceded by - Problem: The rule `([^_])CTRL%- -> %1_CTRL-` inserts an underscore between any non-underscore char and `CTRL-`, which wrongly rewrites tags like `map-CTRL-C` and `telnet-CTRL-]` (where `CTRL-` is literal text in the tag, not a key chord). Solution: Exclude `-` from the trigger char class so a hyphen immediately before `CTRL-` is preserved. The existing behavior for `iCTRL-GCTRL-J` -> `i_CTRL-G_CTRL-J` is unaffected because the `G` before `CTRL-J` is not `-`. * fix(help): only treat ^X as caret notation for valid control chars Problem: The regex `%^([^_])` converts any `^X` to `CTRL-X`, including `:set^=` where `^=` is literal text in the tag, not a caret-notation control character. Solution: Restrict the transformation to caret-notation chars (`%a` or one of `?@[\\]^{}`). This matches the C reference behavior where `^X` is only converted when X is alphanumeric or one of the control-notation punctuation chars. `{` is retained so `^{char}` still maps to the `CTRL-{char}` placeholder tag.
* test(harness): migrate away from magic globalsJustin M. Keyes2026-07-21
| | |