Skip to content

fix(util): skip replace_termcodes for plain lhs (fixes Cyrillic 'р' bug #169)#1055

Open
muratovv wants to merge 1 commit into
folke:mainfrom
muratovv:fix/issue-169-cyrillic-norm
Open

fix(util): skip replace_termcodes for plain lhs (fixes Cyrillic 'р' bug #169)#1055
muratovv wants to merge 1 commit into
folke:mainfrom
muratovv:fix/issue-169-cyrillic-norm

Conversation

@muratovv
Copy link
Copy Markdown

Summary

Fixes #169 — long-standing bug where Cyrillic mappings containing the letter 'р' (U+0440) require a phantom keypress and render with artifacts like `рþX → +1 keymap` in the popup.

Root cause

`M.norm(lhs)` in `lua/which-key/util.lua` always normalizes through:

```lua
vim.fn.keytrans(vim.api.nvim_replace_termcodes(lhs, true, true, true))
```

The third `true` flag (`special`) makes `replace_termcodes` treat any raw `0x80` byte as a stray `K_SPECIAL` marker and pad it with `X` disambiguation (bytes `FE 58`).

For UTF-8 sequences whose continuation byte is `0x80` — most visibly Cyrillic `р` (`D1 80`) — this corrupts the lhs.

Trace for `"пр"` (bytes `D0 BF D1 80`):

step result
`Util.t("пр")` `D0 BF D1 80 FE 58` (`"прX"`)
`keytrans(...)` `"прþX"` (7 bytes)
`Util.keys(...)` `{"п", "р", "þ", "X"}` — 4 entries, not 2
`Triggers.add` registers `vim.keymap.set(mode, "прþX", …)` → vim ambiguity-waits for phantom `X`

Cyrillic `р` is the only single Cyrillic letter affected — every other character in the alphabet has a continuation byte `!= 0x80`. The issue extends to any user mapping whose lhs contains a `р` (e.g. `р`, `gр`, `пр`, etc.).

Fix

Skip `replace_termcodes` when `lhs` has no `<...>` notation. Plain text (latin / unicode / cyrillic) has nothing for `replace_termcodes` to expand, so the round-trip is a no-op except for this K_SPECIAL corruption.

```lua
function M.norm(lhs)
if M.cache.norm[lhs] then return M.cache.norm[lhs] end
if not lhs:find("<", 1, true) then
M.cache.norm[lhs] = lhs
else
M.cache.norm[lhs] = vim.fn.keytrans(M.t(lhs))
end
return M.cache.norm[lhs]
end
```

5 lines of behavioral change. Strings containing `<` (``, ``, ``, ``, etc.) still go through the existing path, preserving termcode handling.

Before / after

Headless probe (`Util.keys` direct call):

input before after
`"пр"` `{"п", "р", "þ", "X"}` ❌ `{"п", "р"}` ✅
`"пд"` `{"п", "д"}` ✅ `{"п", "д"}` ✅
`"gh"` `{"g", "h"}` ✅ `{"g", "h"}` ✅
`""` `{""}` ✅ `{""}` ✅

User-visible: popup popup hints work correctly for Cyrillic prefixes; mappings fire on the first keypress; no `рþX` artifact.

Tests

Added 3 cases to `tests/util_spec.lua` (`"пр"`, `"пд"`, `"шгр"`). All 59 existing tests still pass.

```
$ ./scripts/test
tests/buf_spec.lua: o
tests/layout_spec.lua: oooooooooooooooooooo
tests/mappings_spec.lua: oooooooo
tests/util_spec.lua: oooooooooooooooooooooooooooooo

Fails (0) and Notes (0)
```

Notes

  • Tested locally on a real LazyVim config with Cyrillic shortcut layer (`пр`/`пд`/`шг`) — both popup hints and mapping firing work as expected.
  • This sidesteps the workaround `desc = "which_key_ignore"` that the community has been recommending for years (it silences the popup at the cost of discoverability).

…ke#169

`M.norm` always normalized through `nvim_replace_termcodes(lhs, ..., special=true)`.
The `special` flag makes replace_termcodes treat any raw 0x80 byte as a stray
K_SPECIAL marker and pad it with `<fe>X` disambiguation (bytes FE 58).

For UTF-8 sequences whose continuation byte is 0x80 — most visibly Cyrillic
'р' (U+0440 = D1 80) — this corrupts the lhs. Trace for "пр" (D0 BF D1 80):

  1. Util.t("пр")     → D0 BF D1 80 FE 58   ("пр<fe>X")
  2. keytrans(...)    → "прþX"  (7 bytes)
  3. Util.keys(...)   → {"п", "р", "þ", "X"}   ← 4 entries, not 2
  4. Triggers.add registers vim.keymap.set(mode, "прþX", ...)
     → vim ambiguity-waits for phantom "X" after every "пр"

User-visible symptoms (folke#169): popup hint shows artifact "рþX → +1 keymap";
mapping fires only after extra keypress; auto-trigger swallows neighbouring
input. Cyrillic 'р' is the only single Cyrillic letter affected — other
characters have continuation bytes != 0x80.

Fix: bypass replace_termcodes when lhs has no `<...>` notation. Plain text
(latin / unicode / cyrillic) has nothing for replace_termcodes to expand,
and the round-trip would be a no-op except for this K_SPECIAL corruption.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m Medium PR (<50 lines changed)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Combination of langmap, 'which-key', and some keymap breaks langmap

1 participant