From 0c63e41b5dfde4dd055aed9e671f5db6380d0af9 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 15 Jun 2026 08:03:40 +0800 Subject: [PATCH 1/2] vim-patch:9.2.0629: 0x80 and 0x9b byte not unescaped when check for valid abbr Problem: 0x80 and 0x9b byte not unescaped when checking for valid abbr (Mao-Yining) Solution: Use mb_unescape() (zeertzjq). fixes: vim/vim#20506 closes: vim/vim#20508 https://github.com/vim/vim/commit/1958c991a8663749f2a56009cc65694d292a2a87 --- src/nvim/mapping.c | 15 +++++++++++---- test/old/testdir/test_mapping.vim | 20 +++++++++++++++++++- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/nvim/mapping.c b/src/nvim/mapping.c index c9b8763354..a922d42b1f 100644 --- a/src/nvim/mapping.c +++ b/src/nvim/mapping.c @@ -628,17 +628,24 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, // vi-compatible way. int same = -1; - const int first = vim_iswordp(lhs); + const char *p = lhs; + const char *p_char = mb_unescape(&p); + if (p_char == NULL) { + p_char = p++; + } + const int first = vim_iswordp(p_char); int last = first; - const char *p = lhs + utfc_ptr2len(lhs); int n = 1; while (p < lhs + len) { n++; // nr of (multi-byte) chars - last = vim_iswordp(p); // type of last char + p_char = mb_unescape(&p); + if (p_char == NULL) { + p_char = p++; + } + last = vim_iswordp(p_char); // type of last char if (same == -1 && last != first) { same = n - 1; // count of same char type } - p += utfc_ptr2len(p); } if (last && n > 2 && same >= 0 && same < n - 1) { retval = 1; diff --git a/test/old/testdir/test_mapping.vim b/test/old/testdir/test_mapping.vim index 2c6730bfed..78ff9850ab 100644 --- a/test/old/testdir/test_mapping.vim +++ b/test/old/testdir/test_mapping.vim @@ -7,11 +7,29 @@ source term_util.vim func Test_abbreviation() new - " abbreviation with 0x80 should work + + " abbreviation with 0x80 (full-id) inoreab чкпр vim call feedkeys("Goчкпр \", "xt") call assert_equal('vim ', getline('$')) iunab чкпр + + " abbreviation with 0x80 (non-id) + inoreab abc⁀ abc^ + inoreab ⁀ ^ + call feedkeys("Goabc⁀ def⁀ ⁀ \", "xt") + call assert_equal('abc^ def⁀ ^ ', getline('$')) + iunab abc⁀ + iunab ⁀ + + " abbreviation with 0x9b (non-id) + inoreab abc; abc; + inoreab ; ; + call feedkeys("Goabc; def; ; \", "xt") + call assert_equal('abc; def; ; ', getline('$')) + iunab abc; + iunab ; + bwipe! endfunc From 2fe91d02259198bb50a4dce0a80da76dbfede86a Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 15 Jun 2026 08:06:50 +0800 Subject: [PATCH 2/2] vim-patch:9.2.0645: Composing chars no longer accepted in end-id abbr Problem: Composing chars are no longer accepted in end-id abbreviation (after 9.2.0629). Solution: Unescape all chars using vim_unescape_csi() instead of using mb_unescape() on individual chars, so that mb_ptr2len() and MB_PTR_ADV() can still be used. closes: vim/vim#20514 https://github.com/vim/vim/commit/23a84d28a8c4acfda1d01bde2b5366a496431d88 --- src/nvim/keycodes.c | 4 +++- src/nvim/mapping.c | 26 ++++++++++++-------------- test/old/testdir/test_mapping.vim | 12 ++++++++++++ 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/nvim/keycodes.c b/src/nvim/keycodes.c index ef067f68e2..9827604d67 100644 --- a/src/nvim/keycodes.c +++ b/src/nvim/keycodes.c @@ -850,7 +850,8 @@ char *vim_strsave_escape_ks(char *p) /// Remove escaping from K_SPECIAL characters. Reverse of /// vim_strsave_escape_ks(). Works in-place. -void vim_unescape_ks(char *p) +/// Returns the number of bytes in the unescaped string. +size_t vim_unescape_ks(char *p) { uint8_t *s = (uint8_t *)p; uint8_t *d = (uint8_t *)p; @@ -864,4 +865,5 @@ void vim_unescape_ks(char *p) } } *d = NUL; + return (size_t)((char *)d - p); } diff --git a/src/nvim/mapping.c b/src/nvim/mapping.c index a922d42b1f..3bebbb62eb 100644 --- a/src/nvim/mapping.c +++ b/src/nvim/mapping.c @@ -628,29 +628,28 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, // vi-compatible way. int same = -1; - const char *p = lhs; - const char *p_char = mb_unescape(&p); - if (p_char == NULL) { - p_char = p++; - } - const int first = vim_iswordp(p_char); + char keys_unescaped[MAXMAPLEN + 1]; + xmemcpyz(keys_unescaped, lhs, (size_t)len); + size_t keys_unescaped_len = vim_unescape_ks(keys_unescaped); + const char *p = keys_unescaped; + + const int first = vim_iswordp(p); int last = first; + MB_PTR_ADV(p); int n = 1; - while (p < lhs + len) { + while (p < keys_unescaped + keys_unescaped_len) { n++; // nr of (multi-byte) chars - p_char = mb_unescape(&p); - if (p_char == NULL) { - p_char = p++; - } - last = vim_iswordp(p_char); // type of last char + last = vim_iswordp(p); // type of last char if (same == -1 && last != first) { same = n - 1; // count of same char type } + MB_PTR_ADV(p); } if (last && n > 2 && same >= 0 && same < n - 1) { retval = 1; goto theend; } + // An abbreviation cannot contain white space. for (n = 0; n < len; n++) { if (ascii_iswhite(lhs[n])) { @@ -1537,8 +1536,7 @@ bool check_abbr(int c, char *ptr, int col, int mincol) if (strchr(mp->m_keys, K_SPECIAL) != NULL) { // Might have K_SPECIAL escaped mp->m_keys. q = xstrdup(mp->m_keys); - vim_unescape_ks(q); - qlen = (int)strlen(q); + qlen = (int)vim_unescape_ks(q); } // find entries with right mode and keys int match = (mp->m_mode & State) diff --git a/test/old/testdir/test_mapping.vim b/test/old/testdir/test_mapping.vim index 78ff9850ab..5505d0f4d4 100644 --- a/test/old/testdir/test_mapping.vim +++ b/test/old/testdir/test_mapping.vim @@ -30,6 +30,18 @@ func Test_abbreviation() iunab abc; iunab ; + " abbreviation with composing chars (end-id) + inoreab ..ã a^~ + inoreab ..β̃ β^~ + inoreab ..π̃ π^~ + inoreab ..Λ̃ Λ^~ + call feedkeys("Go..ã ..β̃ ..π̃ ..Λ̃ \", "xt") + call assert_equal('a^~ β^~ π^~ Λ^~ ', getline('$')) + iunab ..ã + iunab ..β̃ + iunab ..π̃ + iunab ..Λ̃ + bwipe! endfunc