patch 9.2.0573: Vim9: missing EX_WHOLE on some block keywords
Commit:
https://github.com/vim/vim/commit/38d9a16eba8cdf3377e8b867da805bf369454108
Author: Peter Kenny <githu...@k1w1.cyou>
Date: Sun May 31 19:14:21 2026 +0000
patch 9.2.0573: Vim9: missing EX_WHOLE on some block keywords
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: #20032
closes: #20191
Signed-off-by: Peter Kenny <githu...@k1w1.cyou>
Signed-off-by: Christian Brabandt <
c...@256bit.org>
diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index 0268d158e..b415fc039 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -1,4 +1,4 @@
-*builtin.txt* For Vim version 9.2. Last change: 2026 May 21
+*builtin.txt* For Vim version 9.2. Last change: 2026 May 31
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -3672,14 +3672,22 @@ fullcommand({name} [, {vim9}]) *fullcommand()*
ambiguous (for user-defined commands) or cannot be shortened
this way. |vim9-no-shorten|
- Without the {vim9} argument uses the current script version.
- If {vim9} is present and FALSE then legacy script rules are
- used. When {vim9} is present and TRUE then Vim9 rules are
- used, e.g. "en" is not a short form of "endif".
-
- For example `fullcommand('s')`, `fullcommand('sub')`,
- `fullcommand(':%substitute')` all return "substitute".
+ Without the {vim9} argument, the current script version is
+ used. When {vim9} is present and FALSE, legacy script rules
+ are used. When {vim9} is present and TRUE, Vim9 rules are
+ used (e.g., "en" is not a short form of "endif").
+ 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']
+ echo [fullcommand('en', v:true)] |" ['']
+<
Can also be used as a |method|: >
GetName()->fullcommand()
<
diff --git a/runtime/doc/change.txt b/runtime/doc/change.txt
index d197ead5c..b3448bf89 100644
--- a/runtime/doc/change.txt
+++ b/runtime/doc/change.txt
@@ -1,4 +1,4 @@
-*change.txt* For Vim version 9.2. Last change: 2026 Mar 31
+*change.txt* For Vim version 9.2. Last change: 2026 May 31
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -75,18 +75,21 @@ For inserting text see |insert.txt|.
*:d* *:de* *:del* *:delete* *:dl* *:dp*
:[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
+ Note these weird abbreviations applicable only to
+ legacy Vim script:
+ :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
+ Warning: These give |E492| in |Vim9| script and `:dl`
+ executes as `:dlist`.
:[range]d[elete] [x] {count}
Delete {count} lines, starting with [range]
@@ -798,7 +801,8 @@ out then. Example: >
:%s/TESTING
This deletes "TESTING" from all lines, but only one per line.
*E1270*
-For compatibility with Vi these two exceptions are allowed in legacy script:
+For compatibility with Vi these two exceptions are allowed in legacy Vim
+script:
"\/{string}/" and "\?{string}?" do the same as "//{string}/r".
"\&{string}&" does the same as "//{string}/".
*pattern-delimiter* *E146* *E1241* *E1242*
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index a451c9f2f..dffeb3859 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -1,4 +1,4 @@
-*eval.txt* For Vim version 9.2. Last change: 2026 May 21
+*eval.txt* For Vim version 9.2. Last change: 2026 May 31
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -3384,7 +3384,7 @@ text...
s: script-local variables
l: local function variables
v: Vim variables.
- This does not work in Vim9 script. |vim9-declaration|
+ This does not work in Vim9 script. |vim9-declaration|
:let List the values of all variables. The type of the
variable is indicated before the value:
@@ -3660,7 +3660,7 @@ text...
all nested `:try`s inside the loop. The outermost
`:endtry` then jumps back to the start of the loop.
- In |Vim9| script `:cont` is the shortest form, to
+ In |Vim9| script `:continue` cannot be shortened, to
improve script readability.
*:break* *:brea* *E587*
:brea[k] When used inside a `:while` or `:for` loop, skips to
diff --git a/runtime/doc/repeat.txt b/runtime/doc/repeat.txt
index ac7c78712..95ccbb668 100644
--- a/runtime/doc/repeat.txt
+++ b/runtime/doc/repeat.txt
@@ -1,4 +1,4 @@
-*repeat.txt* For Vim version 9.2. Last change: 2026 Feb 14
+*repeat.txt* For Vim version 9.2. Last change: 2026 May 31
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -454,6 +454,9 @@ For writing a Vim script, see chapter 41 of the user manual |usr_41.txt|.
nested ":try"s in the script. The outermost ":endtry"
then stops sourcing the script.
+ In |Vim9| script `:finish` cannot be shortened, to
+ improve script readability.
+
All commands and command sequences can be repeated by putting them in a named
register and then executing it. There are two ways to get the commands in the
register:
diff --git a/runtime/doc/tagsrch.txt b/runtime/doc/tagsrch.txt
index d27838386..e381d5fc0 100644
--- a/runtime/doc/tagsrch.txt
+++ b/runtime/doc/tagsrch.txt
@@ -1,4 +1,4 @@
-*tagsrch.txt* For Vim version 9.2. Last change: 2026 May 17
+*tagsrch.txt* For Vim version 9.2. Last change: 2026 May 31
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -833,8 +833,9 @@ 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: In legacy Vim script, `:dl` works like
+ `:delete` with the "l" flag, not `:dlist`, whereas in
+ |Vim9| script `:dl` does work like `:dlist`.
*[_CTRL-D*
[ CTRL-D Jump to the first macro definition that contains the
diff --git a/runtime/doc/userfunc.txt b/runtime/doc/userfunc.txt
index 6388d12e1..b0051fb27 100644
--- a/runtime/doc/userfunc.txt
+++ b/runtime/doc/userfunc.txt
@@ -1,4 +1,4 @@
-*userfunc.txt* For Vim version 9.2. Last change: 2026 Feb 14
+*userfunc.txt* For Vim version 9.2. Last change: 2026 May 31
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -27,13 +27,13 @@ make them script-local. If you do use a global function then avoid obvious,
short names. A good habit is to start the function name with the name of the
script, e.g., "HTMLcolor()".
-In legacy script it is also possible to use curly braces, see
+In legacy Vim script it is also possible to use curly braces, see
|curly-braces-names|.
The |autoload| facility is useful to define a function only when it's called.
*local-function*
-A function local to a legacy script must start with "s:". A local script
+A function local to a legacy Vim script must start with "s:". A local script
function can only be called from within the script and from functions, user
commands and autocommands defined in the script. It is also possible to call
the function from a mapping defined in the script, but then |<SID>| must be
@@ -195,9 +195,19 @@ See |:verbose-cmd| for more information.
When a function ends without an explicit ":return",
the number 0 is returned.
- In a :def function *E1095* is given if unreachable
- code follows after the `:return`.
- In legacy script there is no check for unreachable
+ In |Vim9| script:
+ - `:return` cannot be shortened, and
+ - *E1095* is given if unreachable code follows after
+ the `:return`. For example:
+>vim9
+ vim9script
+ var L: func = (): bool => {
+ return false
+ echo 'no' # E1095: Unreachable code after :return
+ }
+ echo L()
+<
+ In legacy Vim script there is no check for unreachable
lines, thus there is no warning if commands follow
`:return`. Also, there is no check if the following
line contains a valid command. Forgetting the line
diff --git a/runtime/doc/usr_20.txt b/runtime/doc/usr_20.txt
index 2a2839541..f4cc5dd64 100644
--- a/runtime/doc/usr_20.txt
+++ b/runtime/doc/usr_20.txt
@@ -1,9 +1,7 @@
-*usr_20.txt* For Vim version 9.2. Last change: 2026 Feb 14
-
+*usr_20.txt* For Vim version 9.2. Last change: 2026 May 31
VIM USER MANUAL by Bram Moolenaar
-
Typing command-line commands quickly
@@ -116,9 +114,18 @@ command. It's like deleting the ":" or "/" that the line starts with.
*20.2* Command line abbreviations
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.
-
+":substitute" can be abbreviated to ":s". This is a generic mechanism, and
+most ":" commands can be abbreviated. However, in Vim9 script some commands
+cannot be shortened to improve readability - see |vim9-no-shorten|.
+
+The builtin function |fullcommand()| can be used to return an abbreviated
+command's full name. For example, the following commands echo "edit", "echo",
+and "echowindow":
+>vim
+ :echo fullcommand('e')
+ :echo fullcommand('ec')
+ :echo fullcommand('echow')
+<
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/src/ex_cmds.h b/src/ex_cmds.h
index 1a3ff0985..5dfb82476 100644
--- a/src/ex_cmds.h
+++ b/src/ex_cmds.h
@@ -362,7 +362,7 @@ EXCMD(CMD_clast, "clast", ex_cc,
EX_RANGE|EX_COUNT|EX_TRLBAR|EX_BANG,
ADDR_UNSIGNED),
EXCMD(CMD_class, "class", ex_class,
- EX_EXTRA|EX_CMDWIN|EX_LOCK_OK|EX_EXPORT,
+ EX_EXTRA|EX_CMDWIN|EX_LOCK_OK|EX_WHOLE|EX_EXPORT,
ADDR_NONE),
EXCMD(CMD_close, "close", ex_close,
EX_BANG|EX_RANGE|EX_COUNT|EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK,
@@ -470,7 +470,7 @@ EXCMD(CMD_debuggreedy, "debuggreedy", ex_debuggreedy,
EX_RANGE|EX_ZEROR|EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK,
ADDR_OTHER),
EXCMD(CMD_def, "def", ex_function,
- EX_EXTRA|EX_BANG|EX_SBOXOK|EX_CMDWIN|EX_LOCK_OK|EX_EXPORT,
+ EX_EXTRA|EX_BANG|EX_SBOXOK|EX_CMDWIN|EX_LOCK_OK|EX_WHOLE|EX_EXPORT,
ADDR_NONE),
EXCMD(CMD_defcompile, "defcompile", ex_defcompile,
EX_SBOXOK|EX_CMDWIN|EX_LOCK_OK|EX_TRLBAR|EX_EXTRA,
@@ -575,16 +575,16 @@ EXCMD(CMD_endif, "endif", ex_endif,
EX_TRLBAR|EX_SBOXOK|EX_CMDWIN|EX_LOCK_OK|EX_WHOLE,
ADDR_NONE),
EXCMD(CMD_endinterface, "endinterface", ex_wrongmodifier,
- EX_EXTRA|EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK,
+ EX_EXTRA|EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK|EX_WHOLE,
ADDR_NONE),
EXCMD(CMD_endclass, "endclass", ex_wrongmodifier,
- EX_EXTRA|EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK,
+ EX_EXTRA|EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK|EX_WHOLE,
ADDR_NONE),
EXCMD(CMD_enddef, "enddef", ex_endfunction,
EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK|EX_WHOLE,
ADDR_NONE),
EXCMD(CMD_endenum, "endenum", ex_wrongmodifier,
- EX_EXTRA|EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK,
+ EX_EXTRA|EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK|EX_WHOLE,
ADDR_NONE),
EXCMD(CMD_endfunction, "endfunction", ex_endfunction,
EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK,
@@ -1229,7 +1229,7 @@ EXCMD(CMD_put, "put", ex_put,
EX_RANGE|EX_WHOLEFOLD|EX_BANG|EX_REGSTR|EX_TRLBAR|EX_ZEROR|EX_CMDWIN|EX_LOCK_OK|EX_MODIFY,
ADDR_LINES),
EXCMD(CMD_public, "public", ex_wrongmodifier,
- EX_EXTRA|EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK,
+ EX_EXTRA|EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK|EX_WHOLE,
ADDR_NONE),
EXCMD(CMD_pwd, "pwd", ex_pwd,
EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK,
@@ -1508,7 +1508,7 @@ EXCMD(CMD_startreplace, "startreplace", ex_startinsert,
EX_BANG|EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK,
ADDR_NONE),
EXCMD(CMD_static, "static", ex_wrongmodifier,
- EX_EXTRA|EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK,
+ EX_EXTRA|EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK|EX_WHOLE,
ADDR_NONE),
EXCMD(CMD_stopinsert, "stopinsert", ex_stopinsert,
EX_BANG|EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK,
diff --git a/src/ex_docmd.c b/src/ex_docmd.c
index 11ea59e3b..e716613ec 100644
--- a/src/ex_docmd.c
+++ b/src/ex_docmd.c
@@ -4025,6 +4025,13 @@ find_ex_command(
if (eap->cmdidx == CMD_final && p - eap->cmd == 4 && !vim9)
eap->cmdidx = CMD_finally;
+ // 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;
+
#ifdef FEAT_EVAL
if (eap->cmdidx < CMD_SIZE
&& vim9
diff --git a/src/testdir/test_cmdmods.vim b/src/testdir/test_cmdmods.vim
index 3b0deab7b..7efe6e311 100644
--- a/src/testdir/test_cmdmods.vim
+++ b/src/testdir/test_cmdmods.vim
@@ -50,12 +50,18 @@ def Test_cmdmods_array()
enddef
def Test_keep_cmdmods_names()
- # :k only available in legacy script
- legacy call assert_equal('k', fullcommand(':k'))
- legacy call assert_equal('k', fullcommand(':ke'))
- # single character commands not supported in Vim9
- assert_equal('', fullcommand(':k'))
- assert_equal('keepmarks', fullcommand(':ke'))
+ # :k is only available in legacy Vim script
+ assert_equal('k', fullcommand(':k', false))
+ # many single character commands are not supported in Vim9 script, incl. :k
+ assert_equal('', fullcommand(':k', true))
+ # :k{a-zA-Z'} in legacy Vim script
+ assert_equal('k', fullcommand(':ka', false))
+ assert_equal('', fullcommand(':ka', true))
+ # :ke is an exception - it is 'keepmarks', not 'k', in Vim9 script
+ assert_equal('k', fullcommand(':ke', false))
+ assert_equal('keepmarks', fullcommand(':ke', true))
+ # :kee* shortenings
+ assert_equal('keepmarks', fullcommand(':kee', false))
assert_equal('keepmarks', fullcommand(':kee'))
assert_equal('keepmarks', fullcommand(':keep'))
assert_equal('keepmarks', fullcommand(':keepm'))
@@ -63,14 +69,17 @@ def Test_keep_cmdmods_names()
assert_equal('keepmarks', fullcommand(':keepmar'))
assert_equal('keepmarks', fullcommand(':keepmark'))
assert_equal('keepmarks', fullcommand(':keepmarks'))
+ assert_equal('keepalt', fullcommand(':keepa', false))
assert_equal('keepalt', fullcommand(':keepa'))
assert_equal('keepalt', fullcommand(':keepal'))
assert_equal('keepalt', fullcommand(':keepalt'))
+ assert_equal('keepjumps', fullcommand(':keepj', false))
assert_equal('keepjumps', fullcommand(':keepj'))
assert_equal('keepjumps', fullcommand(':keepju'))
assert_equal('keepjumps', fullcommand(':keepjum'))
assert_equal('keepjumps', fullcommand(':keepjump'))
assert_equal('keepjumps', fullcommand(':keepjumps'))
+ assert_equal('keeppatterns', fullcommand(':keepp', false))
assert_equal('keeppatterns', fullcommand(':keepp'))
assert_equal('keeppatterns', fullcommand(':keeppa'))
assert_equal('keeppatterns', fullcommand(':keeppat'))
diff --git a/src/testdir/test_marks.vim b/src/testdir/test_marks.vim
index ed0922144..61bfade62 100644
--- a/src/testdir/test_marks.vim
+++ b/src/testdir/test_marks.vim
@@ -254,7 +254,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/src/testdir/test_vim9_class.vim b/src/testdir/test_vim9_class.vim
index 9f060a9f4..7d725e374 100644
--- a/src/testdir/test_vim9_class.vim
+++ b/src/testdir/test_vim9_class.vim
@@ -42,7 +42,8 @@ def Test_class_basic()
END
v9.CheckSourceFailure(lines, 'E475: Invalid argument: classy Something', 2)
- # The complete "endclass" should be specified.
+ # Test for "endclass" cannot be shortened. Test_shortened_invalid_vim9() in
+ # test_vim9_script.vim has complete coverage (:endc to :endclas)
lines =<< trim END
vim9script
class Something
@@ -50,7 +51,7 @@ def Test_class_basic()
END
v9.CheckSourceFailure(lines, 'E1065: Command cannot be shortened: endcl', 3)
- # "endclass" cannot be shortened (variant incl. whitespace and colon)
+ # "endclass" cannot be shortened (variant incl. colon-whitespace)
lines =<< trim END
vim9script
class Something
@@ -1361,13 +1362,14 @@ def Test_instance_variable_access()
echo Foo.new()
.Add(1).Add(2).x
echo Foo.new()
- .Add(1)
+ .Add(1)
.Add(2)
.x
END
v9.CheckSourceSuccess(lines)
- # Test for "public" cannot be abbreviated
+ # Test for "public" cannot be shortened. Test_shortened_invalid_vim9() in
+ # test_vim9_script.vim has complete coverage (:pub to :publi)
lines =<< trim END
vim9script
class Something
@@ -1460,7 +1462,8 @@ enddef
" Test for class variable access
def Test_class_variable_access()
- # Test for "static" cannot be abbreviated
+ # Test for "static" cannot be shortened. Test_shortened_invalid_vim9() in
+ # test_vim9_script.vim has complete coverage (:stat and :stati)
var lines =<< trim END
vim9script
class Something
@@ -2951,7 +2954,8 @@ def Test_abstract_class()
END
v9.CheckSourceFailure(lines, 'E1316: Class can only be defined in Vim9 script', 1)
- # Test for "abstract" cannot be abbreviated
+ # Test for "abstract" cannot be shortened. Test_shortened_invalid_vim9() in
+ # test_vim9_script.vim has complete coverage (:abs to :abstrac)
lines =<< trim END
vim9script
abs class A
@@ -5580,15 +5584,6 @@ def Test_abstract_method()
END
v9.CheckSourceFailure(lines, 'E1404: Abstract cannot be used in an interface', 3)
- # Abbreviate the "abstract" keyword
- lines =<< trim END
- vim9script
- class A
- abs def Foo()
- endclass
- END
- v9.CheckSourceFailure(lines, 'E1065: Command cannot be shortened: abs def Foo()', 3)
-
# Use "abstract" with a member variable
lines =<< trim END
vim9script
diff --git a/src/testdir/test_vim9_func.vim b/src/testdir/test_vim9_func.vim
index 549145f2f..a2faec225 100644
--- a/src/testdir/test_vim9_func.vim
+++ b/src/testdir/test_vim9_func.vim
@@ -389,6 +389,12 @@ def Test_endfunc_enddef()
enddef there
END
v9.CheckScriptFailure(lines, 'E1173: Text found after enddef: there', 6)
+
+ lines =<< trim END
+ def ShortEnddef()
+ endd
+ END
+ v9.CheckScriptFailure(lines, 'E1065: Command cannot be shortened: endd', 2)
enddef
def Test_missing_endfunc_enddef()
diff --git a/src/testdir/test_vim9_interface.vim b/src/testdir/test_vim9_interface.vim
index 0c99e0bc7..f576e2847 100644
--- a/src/testdir/test_vim9_interface.vim
+++ b/src/testdir/test_vim9_interface.vim
@@ -86,7 +86,7 @@ def Test_interface_basics()
END
v9.CheckSourceFailure(lines, 'E1065: Command cannot be shortened: endin', 3)
- # "endinterface" cannot be shortened (variant incl. whitespace and colon)
+ # "endinterface" cannot be shortened (variant incl. colon-whitespace)
lines =<< trim END
vim9script
interface Short
diff --git a/src/testdir/test_vim9_script.vim b/src/testdir/test_vim9_script.vim
index fcb585f02..2f54f6da6 100644
--- a/src/testdir/test_vim9_script.vim
+++ b/src/testdir/test_vim9_script.vim
@@ -3,6 +3,7 @@
import './util/vim9.vim' as v9
source util/screendump.vim
+" Test for has('vim9script')
def Test_vim9script_feature()
# example from the help, here the feature is always present
var lines =<< trim END
@@ -1216,7 +1217,7 @@ def Test_error_in_catch()
v9.CheckDefExecFailure(lines, 'E684:', 4)
enddef
-" :while at the very start of a function that :continue jumps to
+" Test for :while at the very start of a function that :continue jumps to
def s:TryContinueFunc()
while g:Count < 2
g:sequence ..= 't'
@@ -1330,8 +1331,8 @@ def Test_nocatch_throw_silenced()
source XthrowSilenced
enddef
-" g:DeletedFunc() is found when compiling Test_try_catch_throw() and then
-" deleted, this should give a runtime error.
+" Test for g:DeletedFunc() is found when compiling Test_try_catch_throw() and
+" then deleted, this should give a runtime error.
def DeletedFunc(): list<any>
return ['delete me']
enddef
@@ -1522,7 +1523,7 @@ def Try_catch_skipped()
endif
enddef
-" The skipped try/endtry was updating the wrong instruction.
+" Test for when the skipped try/endtry was updating the wrong instruction.
def Test_try_catch_skipped()
var instr = execute('disassemble Try_catch_skipped')
assert_match("NEWLIST size 0
", instr)
@@ -4376,62 +4377,144 @@ def Run_test_reject_declaration()
g:StopVimInTerminal(buf)
enddef
-def Test_minimal_command_name_length()
- var names = [
- 'cons',
- 'brea',
- 'cat',
- 'catc',
- 'con',
- 'cont',
- 'conti',
- 'contin',
- 'continu',
- 'el',
- 'els',
- 'elsei',
- 'endfo',
- 'en',
- 'end',
- 'endi',
- 'endw',
- 'endt',
- 'endtr',
- 'exp',
- 'expo',
- 'expor',
- 'fina',
- 'finall',
- 'fini',
- 'finis',
- 'imp',
- 'impo',
- 'impor',
- 'retu',
- 'retur',
- 'th',
- 'thr',
- 'thro',
- 'wh',
- 'whi',
- 'whil',
- ]
- for name in names
- v9.CheckDefAndScriptFailure([name .. ' '], 'E1065:')
+" Test shortened commands that are invalid in Vim9 script
+def Test_shortened_invalid_vim9()
+ # Many Vim9 script commands cannot be shortened/abbreviated.
+ # SHORTENED is a list of dicts, each with a single key (the exact shortened
+ # command) and a list value with four items:
+ # [0] list<string> Lines passed to the check function (without 'vim9script'
+ # for SourceFailure lines)
+ # [1] number Line number where the error is expected ('vimscript',
+ # which is not in the list, is line 1 in the
+ # 'SourceFailure' and 'DefFailure lines, so needs to be
+ # included in the count)
+ # [2] string 'DefAndScriptFailure', 'SourceFailure', or 'DefFailure'
+ # specifying the applicable 'Check' function to call
+ const SHORTENED: list<dict<list<any>>> = [
+ # abstract
+ {abs: [['abs class A'], 1, 'DefAndScriptFailure']},
+ {abst: [['abst class A'], 1, 'DefAndScriptFailure']},
+ {abstr: [['abstr class A'], 1, 'DefAndScriptFailure']},
+ {abstra: [['abstra class A'], 1, 'DefAndScriptFailure']},
+ {abstrac: [['abstrac class A'], 1, 'DefAndScriptFailure']},
+ # break
+ {brea: [['for k in range(0, 2)', 'brea', 'endfor'], 2, 'DefAndScriptFailure']},
+ # catch
+ {cat: [['try', 'echo 0', 'cat'], 3, 'DefAndScriptFailure']},
+ {catc: [['try', 'echo 0', 'catc'], 3, 'DefAndScriptFailure']},
+ # class - n/a because :clas is :clast
+ # const
+ {cons: [['cons C = 0'], 1, 'DefAndScriptFailure']},
+ # continue
+ {con: [['var n: number', 'while n < 9', '++n', 'con'], 4, 'DefAndScriptFailure']},
+ {cont: [['var n: number', 'while n < 9', '++n', 'cont'], 4, 'DefAndScriptFailure']},
+ {conti: [['var n: number', 'while n < 9', '++n', 'conti'], 4, 'DefAndScriptFailure']},
+ {contin: [['var n: number', 'while n < 9', '++n', 'contin'], 4, 'DefAndScriptFailure']},
+ {continu: [['var n: number', 'while n < 9', '++n', 'continu'], 4, 'DefAndScriptFailure']},
+ # def has no applicable shortened form (:de is :delete)
+ # else
+ {els: [['if true', 'els'], 2, 'DefAndScriptFailure']},
+ # elseif
+ {elsei: [['if true', 'elsei false'], 2, 'DefAndScriptFailure']},
+ # endclass
+ {endc: [['class C', 'endc'], 3, 'SourceFailure']},
+ {endcl: [['class C', 'endcl'], 3, 'SourceFailure']},
+ {endcla: [['class C', 'endcla'], 3, 'SourceFailure']},
+ {endclas: [['class C', 'endclas'], 3, 'SourceFailure']},
+ # enddef
+ # (NB: The separate DefFailure check tests them nested -
+ # DefAndScriptFailure cannot be used for testing :endd[e])
+ {endd: [['def D()', 'endd'], 3, 'SourceFailure']},
+ {endde: [['def D()', 'endde'], 3, 'SourceFailure']},
+ {endd: [['var R: func = (): bool => {', 'def D()', 'endd', '}'], 4, 'DefFailure']},
+ {endde: [['var R: func = (): bool => {', 'def D()', 'endde', '}'], 4, 'DefFailure']},
+ # endenum
+ {ende: [['enum E', 'ende'], 3, 'SourceFailure']},
+ {enden: [['enum E', 'enden'], 3, 'SourceFailure']},
+ {endenu: [['enum E', 'endenu'], 3, 'SourceFailure']},
+ # endfor
+ {endfo: [['for n in range(0, 2)', 'endfo'], 2, 'DefAndScriptFailure']},
+ # endif
+ {en: [['if true', 'en'], 2, 'DefAndScriptFailure']},
+ {end: [['if true', 'end'], 2, 'DefAndScriptFailure']},
+ {endi: [['if true', 'endi'], 2, 'DefAndScriptFailure']},
+ # endinterface
+ {endin: [['interface I', 'endin'], 3, 'SourceFailure']},
+ {endint: [['interface I', 'endint'], 3, 'SourceFailure']},
+ {endinte: [['interface I', 'endinte'], 3, 'SourceFailure']},
+ {endinter: [['interface I', 'endinter'], 3, 'SourceFailure']},
+ {endinterf: [['interface I', 'endinterf'], 3, 'SourceFailure']},
+ {endinterfa: [['interface I', 'endinterfa'], 3, 'SourceFailure']},
+ {endinterfac: [['interface I', 'endinterfac'], 3, 'SourceFailure']},
+ # endtry
+ {endt: [['try', 'echo 0', 'endt'], 3, 'DefAndScriptFailure']},
+ {endtr: [['try', 'echo 0', 'endtr'], 3, 'DefAndScriptFailure']},
+ # endwhile
+ {endw: [['var n = 9', 'while n > 0', '--n', 'endw'], 4, 'DefAndScriptFailure']},
+ {endwh: [['var n = 9', 'while n > 0', '--n', 'endwh'], 4, 'DefAndScriptFailure']},
+ {endwhi: [['var n = 9', 'while n > 0', '--n', 'endwhi'], 4, 'DefAndScriptFailure']},
+ {endwhil: [['var n = 9', 'while n > 0', '--n', 'endwhil'], 4, 'DefAndScriptFailure']},
+ # enum
+ {enu: [['enu E', 'endenum'], 2, 'SourceFailure']},
+ # export
+ {exp: [['exp var b: bool'], 2, 'SourceFailure']},
+ {expo: [['expo var b: bool'], 2, 'SourceFailure']},
+ {expor: [['expor var b: bool'], 2, 'SourceFailure']},
+ # final has no applicable shortened form (because :fina is short for :finally)
+ # finally
+ {fina: [['try', '# Do something', 'fina'], 3, 'DefAndScriptFailure']},
+ # finish
+ {fini: [['fini'], 1, 'DefAndScriptFailure']},
+ # import
+ {imp: [['imp $"{$VIMRUNTIME}/autoload/ccomplete.vim"'], 2, 'SourceFailure']},
+ {impo: [['impo $"{$VIMRUNTIME}/autoload/ccomplete.vim"'], 2, 'SourceFailure']},
+ {impor: [['impor $"{$VIMRUNTIME}/autoload/ccomplete.vim"'], 2, 'SourceFailure']},
+ # interface
+ {inte: [['inte I', 'endinterface'], 2, 'SourceFailure']},
+ {inter: [['inter I', 'endinterface'], 2, 'SourceFailure']},
+ {interf: [['interf I', 'endinterface'], 2, 'SourceFailure']},
+ {interfa: [['interfa I', 'endinterface'], 2, 'SourceFailure']},
+ {interfac: [['interfac I', 'endinterface'], 2, 'SourceFailure']},
+ # public
+ {pub: [['class P', 'pub var b: bool', 'endclass'], 3, 'SourceFailure']},
+ {publ: [['class P', 'publ var b: bool', 'endclass'], 3, 'SourceFailure']},
+ {publi: [['class P', 'publi var b: bool', 'endclass'], 3, 'SourceFailure']},
+ # return (NB: line is 0 - for CheckDefAndScriptFailure the first line of the Vim9 script lambda function is considered 0)
+ {retu: [['var R: func = (): bool => {', 'retu false', '}'], 0, 'DefAndScriptFailure']},
+ {retur: [['var R: func = (): bool => {', 'retur false', '}'], 0, 'DefAndScriptFailure']},
+ # static
+ {stat: [['class S', 'stat var b: bool', 'endclass'], 3, 'SourceFailure']},
+ {stati: [['class S', 'stati var b: bool', 'endclass'], 3, 'SourceFailure']},
+ # this
+ {thi: [['thi'], 1, 'DefAndScriptFailure']},
+ # throw
+ {th: [['try', 'th 9', 'catch 9', 'echo "Should give E1065"', 'thr'], 2, 'DefAndScriptFailure']},
+ {thr: [['try', 'thr 9', 'catch 9', 'echo "Should give E1065"', 'thr'], 2, 'DefAndScriptFailure']},
+ {thro: [['try', 'thro 9', 'catch 9', 'echo "Should give E1065"', 'thro'], 2, 'DefAndScriptFailure']},
+ # type
+ {ty: [['ty ListOfBools = list<bool>'], 1, 'DefAndScriptFailure']},
+ {typ: [['typ ListOfBools = list<bool>'], 1, 'DefAndScriptFailure']},
+ # var
+ {va: [['va b: bool'], 1, 'DefAndScriptFailure']},
+ # while
+ {wh: [['var n = 9', 'wh n > 0', '--n', 'endwhile'], 2, 'DefAndScriptFailure']},
+ {whi: [['var n = 9', 'whi n > 0', '--n', 'endwhile'], 2, 'DefAndScriptFailure']},
+ {whil: [['var n = 9', 'whil n > 0', '--n', 'endwhile'], 2, 'DefAndScriptFailure']},
+ ]
+ for short in SHORTENED
+ const CMD: string = short->keys()[0]
+ const LINES: list<string> = short[CMD][0]
+ const E1065: string = "E1065: Command cannot be shortened: " .. CMD
+ const LNUM: number = short[CMD][1]
+ const CHECK: string = short[CMD][2]
+ if CHECK == 'SourceFailure'
+ v9.CheckSourceFailure(['vim9script', LINES]->flattennew(), E1065, LNUM)
+ elseif CHECK == 'DefAndScriptFailure'
+ v9.CheckDefAndScriptFailure(LINES, E1065, LNUM)
+ elseif CHECK == 'DefFailure'
+ v9.CheckDefFailure(LINES, E1065, LNUM)
+ endif
endfor
-
- var lines =<< trim END
- vim9script
- def SomeFunc()
- endd
- END
- v9.CheckScriptFailure(lines, 'E1065:')
- lines =<< trim END
- vim9script
- def SomeFunc()
- endde
- END
- v9.CheckScriptFailure(lines, 'E1065:')
enddef
def Test_unset_any_variable()
@@ -5792,27 +5875,6 @@ def Test_type_func_with_void()
v9.CheckSourceFailure(lines, 'E1031: Cannot use void value', 4)
enddef
-" Keep this last, it messes up highlighting.
-def Test_substitute_cmd()
- new
- setline(1, 'something')
- :substitute(some(other(
- assert_equal('otherthing', getline(1))
- bwipe!
-
- # also when the context is Vim9 script
- var lines =<< trim END
- vim9script
- new
- setline(1, 'something')
- :substitute(some(other(
- assert_equal('otherthing', getline(1))
- bwipe!
- END
- writefile(lines, 'Xvim9lines', 'D')
- source Xvim9lines
-enddef
-
def Test_call_stack_string()
CheckScreendump
var lines =<< trim END
@@ -5994,4 +6056,66 @@ def Test_if_false_elseif_true_still_takes_elseif()
v9.CheckScriptSuccess(lines)
enddef
+" Test for correct fullcommand() outputs: return the correct command (or '')
+def Test_builtin_fullcommand()
+ # :hor is the minimum abbreviation of :horizontal; :ho is invalid
+ assert_equal('', fullcommand('ho', true))
+ assert_equal('horizontal', fullcommand('hor', true))
+
+ # :k is an invalid one-letter command in Vim9 script
+ assert_equal('', fullcommand('k', true))
+ assert_equal('', fullcommand(':k', true))
+ assert_equal('', fullcommand('karrrrrgh!', true))
+ assert_equal('k', fullcommand('k', false))
+ assert_equal('k', fullcommand(':k', false))
+ assert_equal('k', fullcommand('karrrrrgh!', false))
+
+ # :dl is "delete and list" in legacy Vim script but, because :dl itself is
+ # invalid in Vim9 script, :dl is 'dlist' in Vim9 script
+ assert_equal('delete', fullcommand('dl', v:false))
+ assert_equal('dlist', fullcommand('dl', v:true))
+
+ # Substitute :s two and three letter commands in legacy Vim script are
+ # invalid in Vim9 script
+ assert_equal('', fullcommand('sIr', true))
+ assert_equal('', fullcommand('sIrarrrrrgh!', true))
+ assert_equal('substitute', fullcommand('sIr', false))
+ assert_equal('substitute', fullcommand('sIrarrrrrgh!', false))
+
+ # Three :s? commands are exceptionss, returning different commands depending
+ # on whether the scope is legacy Vim script or Vim9 script
+ assert_equal('scriptnames', fullcommand('sc', true))
+ assert_equal('simalt', fullcommand('si', true))
+ assert_equal('srewind', fullcommand('sr', true))
+ assert_equal('substitute', fullcommand('sc', false))
+ assert_equal('substitute', fullcommand('si', false))
+ assert_equal('substitute', fullcommand('sr', false))
+
+ # :finally cannot be shortened in Vim9 script but :final should return 'final'
+ assert_equal('', fullcommand('fina', true))
+ assert_equal('final', fullcommand('final', true))
+ assert_equal('', fullcommand('finall', true))
+enddef
+
+" Keep this last, it messes up highlighting.
+def Test_substitute_cmd()
+ new
+ setline(1, 'something')
+ :substitute(some(other(
+ assert_equal('otherthing', getline(1))
+ bwipe!
+
+ # also when the context is Vim9 script
+ var lines =<< trim END
+ vim9script
+ new
+ setline(1, 'something')
+ :substitute(some(other(
+ assert_equal('otherthing', getline(1))
+ bwipe!
+ END
+ writefile(lines, 'Xvim9lines', 'D')
+ source Xvim9lines
+enddef
+
" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
diff --git a/src/testdir/test_vimscript.vim b/src/testdir/test_vimscript.vim
index 49f34a578..a07ed6ead 100644
--- a/src/testdir/test_vimscript.vim
+++ b/src/testdir/test_vimscript.vim
@@ -7702,6 +7702,33 @@ func Test_function_long_generic_name()
delfunc TestFunc
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
diff --git a/src/userfunc.c b/src/userfunc.c
index ff5cf76a0..66b55449e 100644
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -1132,7 +1132,7 @@ get_function_body(
{
if (!nesting_inline[nesting] && nesting_def[nesting]
&& p < cmd + 6)
- semsg(_(e_command_cannot_be_shortened_str), "enddef");
+ semsg(_(e_command_cannot_be_shortened_str), cmd);
if (nesting-- == 0)
{
char_u *nextcmd = NULL;
diff --git a/src/version.c b/src/version.c
index ac58fbdf8..53c557e43 100644
--- a/src/version.c
+++ b/src/version.c
@@ -729,6 +729,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 573,
/**/
572,
/**/