vim-patch:9.1.1872: Cmdline history not updated when mapping <Up> and <CR> (#36334)

Problem:  Cmdline history not updated when mapping both <Up> and <CR>.
Solution: Consider the command typed when in Cmdline mode and there is
          no pending input (zeertzjq).

Although the existing behavior technically does match documentation, the
"completely come from mappings" part is a bit ambiguous, because one may
argue that the command doesn't completely come from mappings as long as
the user has typed a key in Cmdline mode.  I'm not entirely sure if this
change will cause problems, but it seems unlikely.

fixes: vim/vim#2771
related: neovim/neovim#36256
closes: vim/vim#18607

97b6e8b424
This commit is contained in:
zeertzjq
2025-10-26 22:19:32 +08:00
committed by GitHub
parent 07461bac27
commit 2407833ba1
4 changed files with 78 additions and 3 deletions

View File

@@ -1288,7 +1288,7 @@ and ":put" commands and with CTRL-R.
"@:" to repeat the previous command-line command. "@:" to repeat the previous command-line command.
The command-line is only stored in this register when at least The command-line is only stored in this register when at least
one character of it was typed. Thus it remains unchanged if one character of it was typed. Thus it remains unchanged if
the command was completely from a mapping. the command was executed completely from a mapping.
*quote_#* *quote#* *quote_#* *quote#*
6. Alternate file register "# 6. Alternate file register "#

View File

@@ -57,8 +57,8 @@ Notes:
- When you enter a command-line that is exactly the same as an older one, the - When you enter a command-line that is exactly the same as an older one, the
old one is removed (to avoid repeated commands moving older commands out of old one is removed (to avoid repeated commands moving older commands out of
the history). the history).
- Only commands that are typed are remembered. Ones that completely come from - Only commands that are typed are remembered. A command executed completely
mappings are not put in the history. from a mapping is not put in the history.
- All searches are put in the search history, including the ones that come - All searches are put in the search history, including the ones that come
from commands like "*" and "#". But for a mapping, only the last search is from commands like "*" and "#". But for a mapping, only the last search is
remembered (to avoid that long mappings trash the history). remembered (to avoid that long mappings trash the history).

View File

@@ -1038,6 +1038,13 @@ static int command_line_check(VimState *state)
// that occurs while typing a command should // that occurs while typing a command should
// cause the command not to be executed. // cause the command not to be executed.
if (stuff_empty() && typebuf.tb_len == 0) {
// There is no pending input from sources other than user input, so
// Vim is going to wait for the user to type a key. Consider the
// command line typed even if next key will trigger a mapping.
s->some_key_typed = true;
}
// Trigger SafeState if nothing is pending. // Trigger SafeState if nothing is pending.
may_trigger_safestate(s->xpc.xp_numfiles <= 0); may_trigger_safestate(s->xpc.xp_numfiles <= 0);

View File

@@ -2676,6 +2676,74 @@ func Test_recalling_cmdline()
cunmap <Plug>(save-cmdline) cunmap <Plug>(save-cmdline)
endfunc endfunc
func Test_recalling_cmdline_with_mappings()
CheckFeature cmdline_hist
cnoremap <F2> <Cmd>let g:cmdline = getcmdline()<CR>
cnoremap <CR> <CR>
cnoremap <Up> <Up>
let save_a = ['a', getreg('a'), getregtype('a')]
call feedkeys(":echo 'foo'\<CR>", 'tx')
call assert_equal("echo 'foo'", @:)
call feedkeys(":echo 'bar'\<CR>", 'tx')
call assert_equal("echo 'bar'", @:)
call assert_equal("echo 'bar'", histget(':', -1))
call assert_equal("echo 'foo'", histget(':', -2))
" This command comes completely from a mapping.
nmap <F3> :echo 'baz'<F2><CR>
call feedkeys("\<F3>", 'tx')
call assert_equal('baz', Screenline(&lines)->trim())
call assert_equal("echo 'baz'", g:cmdline)
call assert_equal("echo 'bar'", @:)
call assert_equal("echo 'bar'", histget(':', -1))
call assert_equal("echo 'foo'", histget(':', -2))
if has('unix')
new
call setline(1, ['aaa'])
setlocal formatprg=cat
" Formatting with non-typed "gq" should not change cmdline history.
normal! gqgq
call assert_equal(":.!cat", Screenline(&lines)->trim())
call assert_equal("echo 'bar'", @:)
call assert_equal("echo 'bar'", histget(':', -1))
call assert_equal("echo 'foo'", histget(':', -2))
bwipe!
endif
" This case can still be considered a typed command.
call timer_start(1, {-> feedkeys("\<CR>", 't')})
call feedkeys(":\<Up>\<Up>", 'tx!')
call assert_equal('foo', Screenline(&lines)->trim())
call assert_equal("echo 'foo'", @:)
call assert_equal("echo 'foo'", histget(':', -1))
call assert_equal("echo 'bar'", histget(':', -2))
call feedkeys(":\<Up>\<F2>\<Esc>", 'tx')
call assert_equal("echo 'foo'", g:cmdline)
call assert_equal("echo 'foo'", @:)
" A command from an executed register is also ignored in the history.
call feedkeys(':let @a=":echo ''zzz''\<cr>"' .. "\<CR>", 'tx')
call feedkeys(":norm @a\<cr>", 'tx')
call assert_equal('zzz', Screenline(&lines)->trim())
call assert_equal('norm @a', @:)
call assert_equal('norm @a', histget(':', -1))
call assert_equal('let @a=":echo ''zzz''\<cr>"', histget(':', -2))
call assert_equal("echo 'foo'", histget(':', -3))
call assert_equal("echo 'bar'", histget(':', -4))
unlet g:cmdline
call call('setreg', save_a)
cunmap <F2>
cunmap <CR>
cunmap <Up>
nunmap <F3>
endfunc
func Test_cmd_map_cmdlineChanged() func Test_cmd_map_cmdlineChanged()
let g:log = [] let g:log = []
cnoremap <F1> l<Cmd><CR>s cnoremap <F1> l<Cmd><CR>s