mirror of
https://github.com/neovim/neovim.git
synced 2026-07-31 12:49:11 +00:00
fix(help): handle escape edge cases #40901
* 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.
This commit is contained in:
@@ -39,7 +39,7 @@ local tag_exceptions = {
|
||||
['expr-<=?'] = '<=?',
|
||||
['expr-<?'] = '<?',
|
||||
['expr-==?'] = '==?',
|
||||
['expr-=~?'] = '=~?',
|
||||
['expr-=~?'] = '=\\~?',
|
||||
['expr->=?'] = '>=?',
|
||||
['expr->?'] = '>?',
|
||||
['expr-is?'] = 'is?',
|
||||
@@ -103,12 +103,16 @@ function M.escape_subject(word)
|
||||
|
||||
-- Change caret notation to 'CTRL-', except '^_'
|
||||
-- E.g. 'i^G^J' --> 'iCTRL-GCTRL-J'
|
||||
word = word:gsub('%^([^_])', 'CTRL-%1')
|
||||
-- Only treat '^' as control notation when followed by a caret-notation
|
||||
-- char (a letter or one of "?@[\]^{"); otherwise leave it literal so
|
||||
-- patterns like ':set^=' are not wrongly split. '{' is included so
|
||||
-- '^{char}' matches the 'CTRL-{char}' placeholder tag.
|
||||
word = word:gsub('%^([%a?@\\[\\%]{^])', 'CTRL-%1')
|
||||
-- Add underscores around 'CTRL-X' characters
|
||||
-- E.g. 'iCTRL-GCTRL-J' --> 'i_CTRL-G_CTRL-J'
|
||||
-- Only exception: 'CTRL-{character}'
|
||||
word = word:gsub('([^_])CTRL%-', '%1_CTRL-')
|
||||
word = word:gsub('(CTRL%-[^{])([^-_\\])', '%1_%2')
|
||||
word = word:gsub('([^-_])CTRL%-', '%1_CTRL-')
|
||||
word = word:gsub('(CTRL%-[^{])([^%u_\\-])', '%1_%2')
|
||||
|
||||
-- Skip function arguments
|
||||
-- E.g. 'abs({expr})' --> 'abs'
|
||||
|
||||
@@ -110,6 +110,7 @@ describe(':help', function()
|
||||
check_tag('help :[range]', '*:[range]*')
|
||||
check_tag('help [<space>', '[<Space>')
|
||||
check_tag('help ]_^D', ']_CTRL-D')
|
||||
check_tag('help :set^=', '*:set^=*')
|
||||
|
||||
check_tag([[help $HOME]], [[*$HOME*]])
|
||||
|
||||
@@ -132,6 +133,12 @@ describe(':help', function()
|
||||
check_tag([[help i^x^y]], '*i_CTRL-X_CTRL-Y*')
|
||||
check_tag([[help CTRL-\_CTRL-N]], [[*CTRL-\_CTRL-N*]])
|
||||
check_tag([[help i_CTRL-U-default]], [[*i_CTRL-U-default*]])
|
||||
check_tag([[help expr-=~?]], [[*expr-=~?*]])
|
||||
check_tag([[help map-CTRL-C]], '*map-CTRL-C*')
|
||||
check_tag([=[help telnet-CTRL-]]=], '*telnet-CTRL-]*')
|
||||
check_tag([[help c_CTRL-SHIFT-V]], '*c_CTRL-SHIFT-V*')
|
||||
check_tag([[help i_CTRL-SHIFT-V]], '*i_CTRL-SHIFT-V*')
|
||||
check_tag([[help c_CTRL-SHIFT-Q]], '*c_CTRL-SHIFT-Q*')
|
||||
|
||||
check_tag([[exe "help i\<C-\>\<C-G>"]], [[*i_CTRL-\_CTRL-G*]])
|
||||
check_tag([[exe "help \<C-V>"]], '*CTRL-V*')
|
||||
|
||||
Reference in New Issue
Block a user