From edcaf1887ae60d78526abe73bd536af8ccfc2310 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 1 Jun 2026 09:04:30 +0800 Subject: [PATCH] vim-patch:partial:9.2.0573: Vim9: missing EX_WHOLE on some block keywords (#40071) Problem: Several Vim9 keywords lack EX_WHOLE and can be shortened in Vim9 script, inconsistent with endif/enddef/endfor/endwhile/ endtry which already have it. The error from :endd in a nested function also hardcodes "enddef" instead of reporting what the user typed. fullcommand("ho") returns "horizontal" even though :ho is below the documented 3-char minimum. Solution: Add EX_WHOLE to :class, :def, :endclass, :endinterface, :endenum, :public and :static. In get_function_body() pass the user-typed command to the error message. Force :ho to CMD_SIZE in find_ex_command() so fullcommand() reflects the modifier minimum. Extend tests and documentation accordingly (Peter Kenny). fixes: vim/vim#20032 closes: vim/vim#20191 https://github.com/vim/vim/commit/38d9a16eba8cdf3377e8b867da805bf369454108 Co-authored-by: Peter Kenny --- runtime/doc/change.txt | 22 +++++++++++----------- runtime/doc/tagsrch.txt | 4 ++-- runtime/doc/usr_20.txt | 8 ++++++++ runtime/doc/vimfn.txt | 12 ++++++++++-- runtime/lua/vim/_meta/vimfn.gen.lua | 12 ++++++++++-- src/nvim/eval.lua | 13 ++++++++++--- src/nvim/ex_docmd.c | 8 ++++++++ test/old/testdir/test_marks.vim | 7 +++++++ test/old/testdir/test_vimscript.vim | 27 +++++++++++++++++++++++++++ 9 files changed, 93 insertions(+), 20 deletions(-) diff --git a/runtime/doc/change.txt b/runtime/doc/change.txt index cb868a0a06..8048d19d26 100644 --- a/runtime/doc/change.txt +++ b/runtime/doc/change.txt @@ -62,17 +62,17 @@ For inserting text see |insert.txt|. :[range]d[elete] [x] Delete [range] lines (default: current line) [into register x]. Note these weird abbreviations: - :dl delete and list - :dell idem - :delel idem - :deletl idem - :deletel idem - :dp delete and print - :dep idem - :delp idem - :delep idem - :deletp idem - :deletep idem + :dl delete and list + :dell idem + :delel idem + :deletl idem + :deletel idem + :dp delete and print + :dep idem + :delp idem + :delep idem + :deletp idem + :deletep idem :[range]d[elete] [x] {count} Delete {count} lines, starting with [range] diff --git a/runtime/doc/tagsrch.txt b/runtime/doc/tagsrch.txt index 67b2e41fbc..9272d7d613 100644 --- a/runtime/doc/tagsrch.txt +++ b/runtime/doc/tagsrch.txt @@ -840,8 +840,8 @@ CTRL-W i Open a new window, with the cursor on the first line Like `[D` and `]D`, but search in [range] lines (default: whole file). See |:search-args| for [/] and [!]. - Note that `:dl` works like `:delete` with the "l" - flag, not `:dlist`. + Note: `:dl` works like `:delete` with the "l" flag, + not `:dlist`. *[_CTRL-D* [ CTRL-D Jump to the first macro definition that contains the diff --git a/runtime/doc/usr_20.txt b/runtime/doc/usr_20.txt index 51e2ead124..a86053377f 100644 --- a/runtime/doc/usr_20.txt +++ b/runtime/doc/usr_20.txt @@ -119,6 +119,14 @@ Some of the ":" commands are really long. We already mentioned that ":substitute" can be abbreviated to ":s". This is a generic mechanism, all ":" commands can be abbreviated. +The builtin function |fullcommand()| can be used to return an abbreviated +command's full name. For example, the following commands echo "edit", "echo", +and "echomsg": +>vim + :echo fullcommand('e') + :echo fullcommand('ec') + :echo fullcommand('echom') +< How short can a command get? There are 26 letters, and many more commands. For example, ":set" also starts with ":s", but ":s" doesn't start a ":set" command. Instead ":set" can be abbreviated to ":se". diff --git a/runtime/doc/vimfn.txt b/runtime/doc/vimfn.txt index 977aca65a3..1a5ba40ca1 100644 --- a/runtime/doc/vimfn.txt +++ b/runtime/doc/vimfn.txt @@ -2901,8 +2901,16 @@ fullcommand({name}) *fullcommand()* Returns an empty string if a command doesn't exist or if it's ambiguous (for user-defined commands). - For example `fullcommand('s')`, `fullcommand('sub')`, - `fullcommand(':%substitute')` all return "substitute". + Note: Command validation is not performed. Results depend on + Vim's internal command-specific identification rules. + Examples: +>vim + echo [fullcommand('s')] |" ['substitute'] + echo [fullcommand('sub')] |" ['substitute'] + echo [fullcommand(': mark word')] |" ['mark'] + echo [fullcommand(': markword')] |" [''] + echo [fullcommand('en')] |" ['endif'] +< Parameters: ~ • {name} (`string`) diff --git a/runtime/lua/vim/_meta/vimfn.gen.lua b/runtime/lua/vim/_meta/vimfn.gen.lua index b7184207d2..6b70685a28 100644 --- a/runtime/lua/vim/_meta/vimfn.gen.lua +++ b/runtime/lua/vim/_meta/vimfn.gen.lua @@ -2571,8 +2571,16 @@ function vim.fn.foreach(expr1, expr2) end --- Returns an empty string if a command doesn't exist or if it's --- ambiguous (for user-defined commands). --- ---- For example `fullcommand('s')`, `fullcommand('sub')`, ---- `fullcommand(':%substitute')` all return "substitute". +--- Note: Command validation is not performed. Results depend on +--- Vim's internal command-specific identification rules. +--- Examples: +--- >vim +--- echo [fullcommand('s')] |" ['substitute'] +--- echo [fullcommand('sub')] |" ['substitute'] +--- echo [fullcommand(': mark word')] |" ['mark'] +--- echo [fullcommand(': markword')] |" [''] +--- echo [fullcommand('en')] |" ['endif'] +--- < --- --- @param name string --- @return string diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index e4018cc6ea..5728b248e3 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -3230,9 +3230,16 @@ M.funcs = { Returns an empty string if a command doesn't exist or if it's ambiguous (for user-defined commands). - For example `fullcommand('s')`, `fullcommand('sub')`, - `fullcommand(':%substitute')` all return "substitute". - + Note: Command validation is not performed. Results depend on + Vim's internal command-specific identification rules. + Examples: + >vim + echo [fullcommand('s')] |" ['substitute'] + echo [fullcommand('sub')] |" ['substitute'] + echo [fullcommand(': mark word')] |" ['mark'] + echo [fullcommand(': markword')] |" [''] + echo [fullcommand('en')] |" ['endif'] + < ]=], name = 'fullcommand', params = { { 'name', 'string' } }, diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 425760f4ec..2a08f635fe 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -3269,6 +3269,14 @@ char *find_ex_command(exarg_T *eap, int *full) } } + // Force ":ho" to be unresolved. Without this, find_ex_command() + // matches it to CMD_horizontal (the only "ho*" entry), which makes + // fullcommand("ho") return "horizontal" even though ":ho" cannot be + // used as the modifier (cmdmods[] requires 3 chars, "hor"). + if (eap->cmdidx == CMD_horizontal && p - eap->cmd == 2) { + eap->cmdidx = CMD_SIZE; + } + return p; } diff --git a/test/old/testdir/test_marks.vim b/test/old/testdir/test_marks.vim index 44201cb289..59b7d9757a 100644 --- a/test/old/testdir/test_marks.vim +++ b/test/old/testdir/test_marks.vim @@ -253,7 +253,14 @@ func Test_marks_k_cmd() call setline(1, ['foo', 'bar', 'baz', 'qux']) 1,3kr call assert_equal([0, 3, 1, 0], getpos("'r")) + " whitespace before mark + 4k f + call assert_equal([0, 4, 1, 0], getpos("'f")) + :2 k g + call assert_equal([0, 2, 1, 0], getpos("'g")) bw! + call assert_fails(':kz7', 'E488: Trailing characters: z7') + call assert_fails(':execute ":k^"', 'E191: Argument must be a letter or forward/backward quote') endfunc " Test for file marks (A-Z) diff --git a/test/old/testdir/test_vimscript.vim b/test/old/testdir/test_vimscript.vim index 458eae9134..7d7cb7c535 100644 --- a/test/old/testdir/test_vimscript.vim +++ b/test/old/testdir/test_vimscript.vim @@ -7620,6 +7620,33 @@ func Test_catch_pattern_trailing_chars() bw! endfunc +" Test using fullcommand() {{{1 +func Test_builtin_fullcommand() + " :hor is the minimum abbreviation of :horizontal; :ho is invalid + call assert_equal('', fullcommand('ho')) + call assert_equal('horizontal', fullcommand('hor')) + + " :k takes one {a-zA-Z'} mark argument and optional whitespace + call assert_equal('k', fullcommand('k')) + call assert_equal('k', fullcommand(':k')) + call assert_equal('k', fullcommand('karrrrrgh!')) + + " :dl is "delete and list" in a legacy Vim script scope + call assert_equal('delete', fullcommand('dl')) + + " :s two and three letter commands + call assert_equal('substitute', fullcommand('sIr')) + call assert_equal('substitute', fullcommand('sIrarrrrrgh!')) + + " :finally + call assert_equal('finally', fullcommand('fina')) + " 'final' - returns 'final', a Vim9 script-exclusive keyword + " - is a valid shortening of :finally in legacy Vim script + "call assert_equal('final', fullcommand('final')) + call assert_equal('finally', fullcommand('finall')) + +endfunc + "------------------------------------------------------------------------------- " Modelines {{{1 " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker