patch 9.1.0343: 'showcmd' wrong for partial mapping with multibyte

Commit: 
https://github.com/vim/vim/commit/acdfb8a97995e0f81832207e39564ba795281108
Author: zeertzjq <zeert...@outlook.com>
Date:   Wed Apr 17 21:28:54 2024 +0200

    patch 9.1.0343: 'showcmd' wrong for partial mapping with multibyte
    
    Problem:  'showcmd' is wrong for partial mapping with multibyte char,
              and isn't very readable with modifyOtherKeys.
    Solution: Decode multibyte char and merge modifiers into the char.
              (zeertzjq)
    
    This improves the following situations:
    - Multibyte chars whose individual bytes are considered unprintable are
      now shown properly in 'showcmd' area.
    - Ctrl-W with modifyOtherKeys now shows ^W in 'showcmd' area.
    
    The following situation may still need improvement:
    - If the char is a special key or has modifiers that cannot be merged
      into it, internal keycodes are shown in 'showcmd' area like before.
      This applies to keys typed in Normal mode commands as well, and it's
      hard to decide how to make it more readable due to the limited space
      taken by 'showcmd', so I'll leave it for later.
    
    closes: #14572
    
    Signed-off-by: zeertzjq <zeert...@outlook.com>
    Signed-off-by: Christian Brabandt <c...@256bit.org>

diff --git a/src/getchar.c b/src/getchar.c
index c8dde2209..783146fe1 100644
--- a/src/getchar.c
+++ b/src/getchar.c
@@ -1281,6 +1281,80 @@ del_typebuf(int len, int offset)
        typebuf.tb_change_cnt = 1;
 }
 
+typedef struct
+{
+    int                prev_c;
+    char_u     buf[MB_MAXBYTES * 3 + 4];
+    size_t     buflen;
+    unsigned   pending;
+    int                in_special;
+    int                in_mbyte;
+} gotchars_state_T;
+
+/*
+ * Add a single byte to a recording or 'showcmd'.
+ * Return TRUE if a full key has been received, FALSE otherwise.
+ */
+    static int
+gotchars_add_byte(gotchars_state_T *state, char_u byte)
+{
+    int                c = state->buf[state->buflen++] = byte;
+    int                retval = FALSE;
+
+    if (state->pending > 0)
+       state->pending--;
+
+    // When receiving a special key sequence, store it until we have all
+    // the bytes and we can decide what to do with it.
+    if ((state->pending == 0 || state->in_mbyte)
+           && (c == K_SPECIAL
+#ifdef FEAT_GUI
+               || c == CSI
+#endif
+              ))
+    {
+       state->pending += 2;
+       if (!state->in_mbyte)
+           state->in_special = TRUE;
+    }
+
+    if (state->pending > 0)
+       goto ret_false;
+
+    if (!state->in_mbyte)
+    {
+       if (state->in_special)
+       {
+           state->in_special = FALSE;
+           if (state->prev_c == KS_MODIFIER)
+               // When receiving a modifier, wait for the modified key.
+               goto ret_false;
+           c = TO_SPECIAL(state->prev_c, c);
+           if (c == K_FOCUSGAINED || c == K_FOCUSLOST)
+               // Drop K_FOCUSGAINED and K_FOCUSLOST, they are not useful
+               // in a recording.
+               state->buflen = 0;
+       }
+       // When receiving a multibyte character, store it until we have all
+       // the bytes, so that it won't be split between two buffer blocks,
+       // and delete_buff_tail() will work properly.
+       state->pending = MB_BYTE2LEN_CHECK(c) - 1;
+       if (state->pending > 0)
+       {
+           state->in_mbyte = TRUE;
+           goto ret_false;
+       }
+    }
+    else
+       // Stored all bytes of a multibyte character.
+       state->in_mbyte = FALSE;
+
+    retval = TRUE;
+ret_false:
+    state->prev_c = c;
+    return retval;
+}
+
 /*
  * Write typed characters to script file.
  * If recording is on put the character in the record buffer.
@@ -1290,78 +1364,26 @@ gotchars(char_u *chars, int len)
 {
     char_u             *s = chars;
     size_t             i;
-    int                        c = NUL;
-    static int         prev_c = NUL;
-    static char_u      buf[MB_MAXBYTES * 3 + 4];
-    static size_t      buflen = 0;
-    static unsigned    pending = 0;
-    static int         in_special = FALSE;
-    static int         in_mbyte = FALSE;
     int                        todo = len;
+    static gotchars_state_T state;
 
-    for (; todo--; prev_c = c)
+    while (todo-- > 0)
     {
-       c = buf[buflen++] = *s++;
-       if (pending > 0)
-           pending--;
-
-       // When receiving a special key sequence, store it until we have all
-       // the bytes and we can decide what to do with it.
-       if ((pending == 0 || in_mbyte)
-               && (c == K_SPECIAL
-#ifdef FEAT_GUI
-                   || c == CSI
-#endif
-                  ))
-       {
-           pending += 2;
-           if (!in_mbyte)
-               in_special = TRUE;
-       }
-
-       if (pending > 0)
+       if (!gotchars_add_byte(&state, *s++))
            continue;
 
-       if (!in_mbyte)
-       {
-           if (in_special)
-           {
-               in_special = FALSE;
-               if (prev_c == KS_MODIFIER)
-                   // When receiving a modifier, wait for the modified key.
-                   continue;
-               c = TO_SPECIAL(prev_c, c);
-               if (c == K_FOCUSGAINED || c == K_FOCUSLOST)
-                   // Drop K_FOCUSGAINED and K_FOCUSLOST, they are not useful
-                   // in a recording.
-                   buflen = 0;
-           }
-           // When receiving a multibyte character, store it until we have all
-           // the bytes, so that it won't be split between two buffer blocks,
-           // and delete_buff_tail() will work properly.
-           pending = MB_BYTE2LEN_CHECK(c) - 1;
-           if (pending > 0)
-           {
-               in_mbyte = TRUE;
-               continue;
-           }
-       }
-       else
-           // Stored all bytes of a multibyte character.
-           in_mbyte = FALSE;
-
        // Handle one byte at a time; no translation to be done.
-       for (i = 0; i < buflen; ++i)
-           updatescript(buf[i]);
+       for (i = 0; i < state.buflen; ++i)
+           updatescript(state.buf[i]);
 
        if (reg_recording != 0)
        {
-           buf[buflen] = NUL;
-           add_buff(&recordbuff, buf, (long)buflen);
+           state.buf[state.buflen] = NUL;
+           add_buff(&recordbuff, state.buf, (long)state.buflen);
            // remember how many chars were last recorded
-           last_recorded_len += buflen;
+           last_recorded_len += state.buflen;
        }
-       buflen = 0;
+       state.buflen = 0;
     }
 
     may_sync_undo();
@@ -1750,6 +1772,67 @@ merge_modifyOtherKeys(int c_arg, int *modifiers)
     return c;
 }
 
+/*
+ * Add a single byte to 'showcmd' for a partially matched mapping.
+ * Call add_to_showcmd() if a full key has been received.
+ */
+    static void
+add_byte_to_showcmd(char_u byte)
+{
+    static gotchars_state_T state;
+    char_u     *ptr;
+    int                modifiers = 0;
+    int                c = NUL;
+
+    if (!p_sc || msg_silent != 0)
+       return;
+
+    if (!gotchars_add_byte(&state, byte))
+       return;
+
+    state.buf[state.buflen] = NUL;
+    state.buflen = 0;
+
+    ptr = state.buf;
+    if (ptr[0] == K_SPECIAL && ptr[1] == KS_MODIFIER && ptr[2] != NUL)
+    {
+       modifiers = ptr[2];
+       ptr += 3;
+    }
+
+    if (*ptr != NUL)
+    {
+       char_u          *mb_ptr = mb_unescape(&ptr);
+
+       c = mb_ptr != NULL ? (*mb_ptr2char)(mb_ptr) : *ptr++;
+       if (c <= 0x7f)
+       {
+           // Merge modifiers into the key to make the result more readable.
+           int         modifiers_after = modifiers;
+           int         mod_c = merge_modifyOtherKeys(c, &modifiers_after);
+
+           if (modifiers_after == 0)
+           {
+               modifiers = 0;
+               c = mod_c;
+           }
+       }
+    }
+
+    // TODO: is there a more readable and yet compact representation of
+    // modifiers and special keys?
+    if (modifiers != 0)
+    {
+       add_to_showcmd(K_SPECIAL);
+       add_to_showcmd(KS_MODIFIER);
+       add_to_showcmd(modifiers);
+    }
+    if (c != NUL)
+       add_to_showcmd(c);
+    while (*ptr != NUL)
+       add_to_showcmd(*ptr++);
+}
+
 /*
  * Get the next input character.
  * Can return a special key or a multi-byte character.
@@ -3582,7 +3665,7 @@ vgetorpeek(int advance)
                        if (typebuf.tb_len > SHOWCMD_COLS)
                            showcmd_idx = typebuf.tb_len - SHOWCMD_COLS;
                        while (showcmd_idx < typebuf.tb_len)
-                           (void)add_to_showcmd(
+                           add_byte_to_showcmd(
                               typebuf.tb_buf[typebuf.tb_off + showcmd_idx++]);
                        curwin->w_wcol = old_wcol;
                        curwin->w_wrow = old_wrow;
diff --git a/src/normal.c b/src/normal.c
index cfe60eec3..fef282647 100644
--- a/src/normal.c
+++ b/src/normal.c
@@ -1708,6 +1708,7 @@ add_to_showcmd(int c)
     int                extra_len;
     int                overflow;
     int                i;
+    char_u     mbyte_buf[MB_MAXBYTES];
     static int ignore[] =
     {
 #ifdef FEAT_GUI
@@ -1739,9 +1740,17 @@ add_to_showcmd(int c)
            if (ignore[i] == c)
                return FALSE;
 
-    p = transchar(c);
-    if (*p == ' ')
-       STRCPY(p, "<20>");
+    if (c <= 0x7f || !vim_isprintc(c))
+    {
+       p = transchar(c);
+       if (*p == ' ')
+           STRCPY(p, "<20>");
+    }
+    else
+    {
+       mbyte_buf[(*mb_char2bytes)(c, mbyte_buf)] = NUL;
+       p = mbyte_buf;
+    }
     old_len = (int)STRLEN(showcmd_buf);
     extra_len = (int)STRLEN(p);
     overflow = old_len + extra_len - SHOWCMD_COLS;
@@ -1810,7 +1819,7 @@ pop_showcmd(void)
     static void
 display_showcmd(void)
 {
-    int            len = (int)STRLEN(showcmd_buf);
+    int            len = vim_strsize(showcmd_buf);
 
     showcmd_is_clear = (len == 0);
     cursor_off();
diff --git a/src/testdir/test_mapping.vim b/src/testdir/test_mapping.vim
index 71d90468b..309be7122 100644
--- a/src/testdir/test_mapping.vim
+++ b/src/testdir/test_mapping.vim
@@ -1807,6 +1807,48 @@ func Test_map_after_timed_out_nop()
   call StopVimInTerminal(buf)
 endfunc
 
+" Test 'showcmd' behavior with a partial mapping
+func Test_showcmd_part_map()
+  CheckRunVimInTerminal
+
+  let lines =<< trim eval END
+    set notimeout showcmd
+    nnoremap ,a <Ignore>
+    nnoremap ;a <Ignore>
+    nnoremap Àa <Ignore>
+    nnoremap Ëa <Ignore>
+    nnoremap βa <Ignore>
+    nnoremap ωa <Ignore>
+    nnoremap …a <Ignore>
+    nnoremap <C-W>a <Ignore>
+  END
+  call writefile(lines, 'Xtest_showcmd_part_map', 'D')
+  let buf = RunVimInTerminal('-S Xtest_showcmd_part_map', #{rows: 6})
+
+  call term_sendkeys(buf, ":set noruler | echo\<CR>")
+  call WaitForAssert({-> assert_equal('', term_getline(buf, 6))})
+
+  for c in [',', ';', 'À', 'Ë', 'β', 'ω', '…']
+    call term_sendkeys(buf, c)
+    call WaitForAssert({-> assert_equal(c, trim(term_getline(buf, 6)))})
+    call term_sendkeys(buf, "\<C-C>:echo\<CR>")
+    call WaitForAssert({-> assert_equal('', term_getline(buf, 6))})
+  endfor
+
+  call term_sendkeys(buf, "\<C-W>")
+  call WaitForAssert({-> assert_equal('^W', trim(term_getline(buf, 6)))})
+  call term_sendkeys(buf, "\<C-C>:echo\<CR>")
+  call WaitForAssert({-> assert_equal('', term_getline(buf, 6))})
+
+  " Use feedkeys() as terminal buffer cannot forward this
+  call term_sendkeys(buf, ':call feedkeys("\<*C-W>", "m")' .. " | echo\<CR>")
+  call WaitForAssert({-> assert_equal('^W', trim(term_getline(buf, 6)))})
+  call term_sendkeys(buf, "\<C-C>:echo\<CR>")
+  call WaitForAssert({-> assert_equal('', term_getline(buf, 6))})
+
+  call StopVimInTerminal(buf)
+endfunc
+
 func Test_using_past_typeahead()
   nnoremap :00 0
   exe "norm :set \x80\xfb0=0\<CR>"
diff --git a/src/version.c b/src/version.c
index 68b58b0b4..3c1eae66a 100644
--- a/src/version.c
+++ b/src/version.c
@@ -704,6 +704,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    343,
 /**/
     342,
 /**/

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to vim_dev+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/E1rxBDP-00GRbL-Rs%40256bit.org.

Raspunde prin e-mail lui