diff options
-rw-r--r-- | src/getchar.c | 207 | ||||
-rw-r--r-- | src/normal.c | 17 | ||||
-rw-r--r-- | src/testdir/test_mapping.vim | 42 | ||||
-rw-r--r-- | src/version.c | 2 |
4 files changed, 202 insertions, 66 deletions
diff --git a/src/getchar.c b/src/getchar.c index c8dde22096..783146fe1a 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(); @@ -1751,6 +1773,67 @@ merge_modifyOtherKeys(int c_arg, int *modifiers) } /* + * 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. * Can return NUL when called recursively, use safe_vgetc() if that's not @@ -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 cfe60eec32..fef282647f 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 71d90468ba..309be71226 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 68b58b0b4e..3c1eae66a5 100644 --- a/src/version.c +++ b/src/version.c @@ -705,6 +705,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 343, +/**/ 342, /**/ 341, |