| Commit message (Collapse) | Author | Age |
| |
|
|
|
|
|
|
|
|
|
|
| |
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
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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
|
| |
|
|
|
|
|
|
|
|
| |
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`.
|
| |
|
|
|
|
|
|
|
| |
Problem:
`SessionWritePre` also fires for `:mk{view,vimrc,exrc}`. Single-window
saving should not invoke whole-session preparation
Solution:
Emit `SessionWritePre` only for `:mksession`
|
| |
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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
|
| |
|
|
|
|
|
|
|
|
| |
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
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
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
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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
|
| |
|
|
|
|
|
|
|
|
| |
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.
|
| | |
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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...
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
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().
|
| |
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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`.
|
| | |
|
| | |
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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
|
| |
|
|
|
|
|
|
|
| |
Problem:
Want `gQ` for _le multicursor_.
Solution:
- Don't use `gQ` for exmode.
- Introduce `:exmode`.
- Introduce `[count]q:` as an alias to `:exmode`.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
| |
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): 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.
|
| |
|
| |