]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0573: Vim9: missing EX_WHOLE on some block keywords v9.2.0573
authorPeter Kenny <github.com@k1w1.cyou>
Sun, 31 May 2026 19:14:21 +0000 (19:14 +0000)
committerChristian Brabandt <cb@256bit.org>
Sun, 31 May 2026 19:16:57 +0000 (19:16 +0000)
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 <github.com@k1w1.cyou>
Signed-off-by: Christian Brabandt <cb@256bit.org>
18 files changed:
runtime/doc/builtin.txt
runtime/doc/change.txt
runtime/doc/eval.txt
runtime/doc/repeat.txt
runtime/doc/tagsrch.txt
runtime/doc/userfunc.txt
runtime/doc/usr_20.txt
src/ex_cmds.h
src/ex_docmd.c
src/testdir/test_cmdmods.vim
src/testdir/test_marks.vim
src/testdir/test_vim9_class.vim
src/testdir/test_vim9_func.vim
src/testdir/test_vim9_interface.vim
src/testdir/test_vim9_script.vim
src/testdir/test_vimscript.vim
src/userfunc.c
src/version.c

index 0268d158e752fa3f3df1c5f19dccde57752ab45b..b415fc039f73cc0b6eda7fda37efb8bccc842422 100644 (file)
@@ -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()
 <
index d197ead5c8ea8cff5fbd6c1af1d37c076552bc9b..b3448bf892392c8a0d8158753331d484b8846fae 100644 (file)
@@ -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*
index a451c9f2fe60ffda6affdcb503c7d3fed379a0fc..dffeb3859f5b86a11cda2eb5c3da100467fafccc 100644 (file)
@@ -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
index ac7c78712ae1b3c8f7ddefea3688c8365126de37..95ccbb6687a38d12b86ac722e9211b4ece331dd4 100644 (file)
@@ -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:
index d278383863bacee1a13c0760b18edac42fc45080..e381d5fc08f664fdd7d9d736b6579c6564988e83 100644 (file)
@@ -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
index 6388d12e187795babedd3cf694adf9c397e1ce49..b0051fb27b6bfe311a8cb9b01cd52e0c4411e841 100644 (file)
@@ -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
index 2a28395411c9f6176ae4e2950036f3a6ba54ca34..f4cc5dd64c99c28d4ab0c15b3f4c11f54c065d05 100644 (file)
@@ -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".
index 1a3ff0985abdf1636f2af24bd6c4ac27a9a008db..5dfb824764807eeba5752a5cd79d1cc2d921f39f 100644 (file)
@@ -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,
index 11ea59e3b06f7c36e328bbc31a08db614a23c72f..e716613ec24e0a9cd428715bac4657d73cb8178e 100644 (file)
@@ -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
index 3b0deab7b595c6e3a56c8176983976d2cfdba809..7efe6e311158150d35e82258c2fe606fcbb6857a 100644 (file)
@@ -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'))
index ed09221449054b4ae4b93558ce96bf4f16fce390..61bfade6272ea4df132052025268ec2130f54f8d 100644 (file)
@@ -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)
index 9f060a9f47e10e867fdafaf5bdb8c675cf246104..7d725e3745f2ec256b7e3174e7ac20130ea3bc5c 100644 (file)
@@ -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
index 549145f2f19e208e4e6bf8ceafd3afa4b32e4974..a2faec225d02d8dfebdd811e7b78c2792ca1a117 100644 (file)
@@ -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()
index 0c99e0bc71a79c5772d8849fefda266e1a2d279d..f576e28475617adf2f9f993c4beed6f32eb5dee0 100644 (file)
@@ -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
index fcb585f0265c5538631f98aeacc55225387189ac..2f54f6da62b484eb9029fe13dada149bd9217652 100644 (file)
@@ -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\n", 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
index 49f34a578c12fb11e1edf6d569d9254ba0c2bcac..a07ed6eadf46662d0532fcb9df1902efe6ec5b5c 100644 (file)
@@ -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
index ff5cf76a0eba9925c66ef499def70ec26b760737..66b55449e3db2b1487eafe8063c0e0e20e134a44 100644 (file)
@@ -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;
index ac58fbdf80b598dc750fc5f79a2dc76bf37f7edc..53c557e432a015c04aaed09d42d464200ebaedcd 100644 (file)
@@ -729,6 +729,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    573,
 /**/
     572,
 /**/