From: Peter Kenny Date: Sun, 31 May 2026 19:14:21 +0000 (+0000) Subject: patch 9.2.0573: Vim9: missing EX_WHOLE on some block keywords X-Git-Tag: v9.2.0573^0 X-Git-Url: http://git.ipfire.org/gitweb/index.cgi?a=commitdiff_plain;h=38d9a16eba8cdf3377e8b867da805bf369454108;p=thirdparty%2Fvim.git 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 Signed-off-by: Christian Brabandt --- diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index 0268d158e7..b415fc039f 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 d197ead5c8..b3448bf892 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 a451c9f2fe..dffeb3859f 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 ac7c78712a..95ccbb6687 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 d278383863..e381d5fc08 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 6388d12e18..b0051fb27b 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 || 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 2a28395411..f4cc5dd64c 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 1a3ff0985a..5dfb824764 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 11ea59e3b0..e716613ec2 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 3b0deab7b5..7efe6e3111 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 ed09221449..61bfade627 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 9f060a9f47..7d725e3745 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 549145f2f1..a2faec225d 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 0c99e0bc71..f576e28475 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 fcb585f026..2f54f6da62 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 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 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>> = [ + # 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'], 1, 'DefAndScriptFailure']}, + {typ: [['typ ListOfBools = list'], 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 = 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 49f34a578c..a07ed6eadf 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 ff5cf76a0e..66b55449e3 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 ac58fbdf80..53c557e432 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, /**/