Mercurial > vim
view src/testdir/test_trycatch.vim @ 33399:95db67c7b754 v9.0.1958
patch 9.0.1958: cannot complete option values
Commit: https://github.com/vim/vim/commit/900894b09a95398dfc75599e9f0aa2ea25723384
Author: Yee Cheng Chin <ychin.git@gmail.com>
Date: Fri Sep 29 20:42:32 2023 +0200
patch 9.0.1958: cannot complete option values
Problem: cannot complete option values
Solution: Add completion functions for several options
Add cmdline tab-completion for setting string options
Add tab-completion for setting string options on the cmdline using
`:set=` (along with `:set+=` and `:set-=`).
The existing tab completion for setting options currently only works
when nothing is typed yet, and it only fills in with the existing value,
e.g. when the user does `:set diffopt=<Tab>` it will be completed to
`set diffopt=internal,filler,closeoff` and nothing else. This isn't too
useful as a user usually wants auto-complete to suggest all the possible
values, such as 'iblank', or 'algorithm:patience'.
For set= and set+=, this adds a new optional callback function for each
option that can be invoked when doing completion. This allows for each
option to have control over how completion works. For example, in
'diffopt', it will suggest the default enumeration, but if `algorithm:`
is selected, it will further suggest different algorithm types like
'meyers' and 'patience'. When using set=, the existing option value will
be filled in as the first choice to preserve the existing behavior. When
using set+= this won't happen as it doesn't make sense.
For flag list options (e.g. 'mouse' and 'guioptions'), completion will
take into account existing typed values (and in the case of set+=, the
existing option value) to make sure it doesn't suggest duplicates.
For set-=, there is a new `ExpandSettingSubtract` function which will
handle flag list and comma-separated options smartly, by only suggesting
values that currently exist in the option.
Note that Vim has some existing code that adds special handling for
'filetype', 'syntax', and misc dir options like 'backupdir'. This change
preserves them as they already work, instead of converting to the new
callback API for each option.
closes: #13182
Signed-off-by: Christian Brabandt <cb@256bit.org>
Co-authored-by: Yee Cheng Chin <ychin.git@gmail.com>
author | Christian Brabandt <cb@256bit.org> |
---|---|
date | Fri, 29 Sep 2023 20:45:04 +0200 |
parents | 695b50472e85 |
children |
line wrap: on
line source
" Test try-catch-finally exception handling " Most of this was formerly in test49. source check.vim source shared.vim import './vim9.vim' as v9 "------------------------------------------------------------------------------- " Test environment {{{1 "------------------------------------------------------------------------------- com! XpathINIT let g:Xpath = '' com! -nargs=1 -bar Xpath let g:Xpath = g:Xpath . <args> " Test 25: Executing :finally clauses on normal control flow {{{1 " " Control flow in a :try conditional should always fall through to its " :finally clause. A :finally clause of a :try conditional inside an " inactive conditional should never be executed. "------------------------------------------------------------------------------- func T25_F() let loops = 3 while loops > 0 Xpath 'a' . loops if loops >= 2 try Xpath 'b' . loops if loops == 2 try Xpath 'c' . loops finally Xpath 'd' . loops endtry endif finally Xpath 'e' . loops if loops == 2 try Xpath 'f' . loops final Xpath 'g' . loops endtry endif endtry endif Xpath 'h' . loops let loops = loops - 1 endwhile Xpath 'i' endfunc " Also try using "fina" and "final" and "finall" as abbreviations. func T25_G() if 1 try Xpath 'A' call T25_F() Xpath 'B' fina Xpath 'C' endtry else try Xpath 'D' finall Xpath 'E' endtry endif endfunc func Test_finally() XpathINIT call T25_G() call assert_equal('Aa3b3e3h3a2b2c2d2e2f2g2h2a1h1iBC', g:Xpath) endfunc "------------------------------------------------------------------------------- " Test 26: Executing :finally clauses after :continue or :break {{{1 " " For a :continue or :break dynamically enclosed in a :try/:endtry " region inside the next surrounding :while/:endwhile, if the " :continue/:break is before the :finally, the :finally clause is " executed first. If the :continue/:break is after the :finally, the " :finally clause is broken (like an :if/:endif region). "------------------------------------------------------------------------------- func T26_F() try let loops = 3 while loops > 0 try try if loops == 2 Xpath 'a' . loops let loops = loops - 1 continue elseif loops == 1 Xpath 'b' . loops break finish endif Xpath 'c' . loops endtry finally Xpath 'd' . loops endtry Xpath 'e' . loops let loops = loops - 1 endwhile Xpath 'f' finally Xpath 'g' let loops = 3 while loops > 0 try finally try if loops == 2 Xpath 'h' . loops let loops = loops - 1 continue elseif loops == 1 Xpath 'i' . loops break finish endif endtry Xpath 'j' . loops endtry Xpath 'k' . loops let loops = loops - 1 endwhile Xpath 'l' endtry Xpath 'm' endfunc func Test_finally_after_continue() XpathINIT call T26_F() call assert_equal('c3d3e3a2d1b1d1fgj3k3h2i1lm', g:Xpath) endfunc "------------------------------------------------------------------------------- " Test 32: Remembering the :return value on :finally {{{1 " " If a :finally clause is executed due to a :return specifying " a value, this is the value visible to the caller if not overwritten " by a new :return in the :finally clause. A :return without a value " in the :finally clause overwrites with value 0. "------------------------------------------------------------------------------- func T32_F() try Xpath 'a' try Xpath 'b' return "ABCD" Xpath 'c' finally Xpath 'd' endtry Xpath 'e' finally Xpath 'f' endtry Xpath 'g' endfunc func T32_G() try Xpath 'h' return 8 Xpath 'i' finally Xpath 'j' return 16 + strlen(T32_F()) Xpath 'k' endtry Xpath 'l' endfunc func T32_H() try Xpath 'm' return 32 Xpath 'n' finally Xpath 'o' return Xpath 'p' endtry Xpath 'q' endfunc func T32_I() try Xpath 'r' finally Xpath 's' return T32_G() + T32_H() + 64 Xpath 't' endtry Xpath 'u' endfunc func Test_finally_return() XpathINIT call assert_equal(84, T32_I()) call assert_equal('rshjabdfmo', g:Xpath) endfunc "------------------------------------------------------------------------------- " Test 33: :return under :execute or user command and :finally {{{1 " " A :return command may be executed under an ":execute" or from " a user command. Executing of :finally clauses and passing through " the return code works also then. "------------------------------------------------------------------------------- func T33_F() try RETURN 10 Xpath 'a' finally Xpath 'b' endtry Xpath 'c' endfunc func T33_G() try RETURN 20 Xpath 'd' finally Xpath 'e' RETURN 30 Xpath 'f' endtry Xpath 'g' endfunc func T33_H() try execute "try | return 40 | finally | return 50 | endtry" Xpath 'h' finally Xpath 'i' endtry Xpath 'j' endfunc func T33_I() try execute "try | return 60 | finally | return 70 | endtry" Xpath 'k' finally Xpath 'l' execute "try | return 80 | finally | return 90 | endtry" Xpath 'm' endtry Xpath 'n' endfunc func T33_J() try RETURN 100 Xpath 'o' finally Xpath 'p' return Xpath 'q' endtry Xpath 'r' endfunc func T33_K() try execute "try | return 110 | finally | return 120 | endtry" Xpath 's' finally Xpath 't' execute "try | return 130 | finally | return | endtry" Xpath 'u' endtry Xpath 'v' endfunc func T33_L() try return Xpath 'w' finally Xpath 'x' RETURN 140 Xpath 'y' endtry Xpath 'z' endfunc func T33_M() try return Xpath 'A' finally Xpath 'B' execute "try | return 150 | finally | return 160 | endtry" Xpath 'C' endtry Xpath 'D' endfunc func T33_N() RETURN 170 endfunc func T33_O() execute "try | return 180 | finally | return 190 | endtry" endfunc func Test_finally_cmd_return() command! -nargs=? RETURN \ try | return <args> | finally | return <args> * 2 | endtry XpathINIT call assert_equal(20, T33_F()) call assert_equal(60, T33_G()) call assert_equal(50, T33_H()) call assert_equal(90, T33_I()) call assert_equal(0, T33_J()) call assert_equal(0, T33_K()) call assert_equal(280, T33_L()) call assert_equal(160, T33_M()) call assert_equal(340, T33_N()) call assert_equal(190, T33_O()) call assert_equal('beilptxB', g:Xpath) delcommand RETURN endfunc "------------------------------------------------------------------------------- " Test 41: Skipped :throw finding next command {{{1 " " A :throw in an inactive conditional must not hide a following " command. "------------------------------------------------------------------------------- func T41_F() Xpath 'a' if 0 | throw 'never' | endif | Xpath 'b' Xpath 'c' endfunc func T41_G() Xpath 'd' while 0 | throw 'never' | endwhile | Xpath 'e' Xpath 'f' endfunc func T41_H() Xpath 'g' if 0 | try | throw 'never' | endtry | endif | Xpath 'h' Xpath 'i' endfunc func Test_throw_inactive_cond() XpathINIT try Xpath 'j' call T41_F() Xpath 'k' catch /.*/ Xpath 'l' call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint) endtry try Xpath 'm' call T41_G() Xpath 'n' catch /.*/ Xpath 'o' call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint) endtry try Xpath 'p' call T41_H() Xpath 'q' catch /.*/ Xpath 'r' call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint) endtry call assert_equal('jabckmdefnpghiq', g:Xpath) endfunc "------------------------------------------------------------------------------- " Test 42: Catching number and string exceptions {{{1 " " When a number is thrown, it is converted to a string exception. " Numbers and strings may be caught by specifying a regular exception " as argument to the :catch command. "------------------------------------------------------------------------------- func T42_F() try try Xpath 'a' throw 4711 Xpath 'b' catch /4711/ Xpath 'c' endtry try Xpath 'd' throw 4711 Xpath 'e' catch /^4711$/ Xpath 'f' endtry try Xpath 'g' throw 4711 Xpath 'h' catch /\d/ Xpath 'i' endtry try Xpath 'j' throw 4711 Xpath 'k' catch /^\d\+$/ Xpath 'l' endtry try Xpath 'm' throw "arrgh" Xpath 'n' catch /arrgh/ Xpath 'o' endtry try Xpath 'p' throw "arrgh" Xpath 'q' catch /^arrgh$/ Xpath 'r' endtry try Xpath 's' throw "arrgh" Xpath 't' catch /\l/ Xpath 'u' endtry try Xpath 'v' throw "arrgh" Xpath 'w' catch /^\l\+$/ Xpath 'x' endtry try try Xpath 'y' throw "ARRGH" Xpath 'z' catch /^arrgh$/ Xpath 'A' endtry catch /^\carrgh$/ Xpath 'B' endtry try Xpath 'C' throw "" Xpath 'D' catch /^$/ Xpath 'E' endtry catch /.*/ Xpath 'F' call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint) endtry endfunc func Test_catch_number_string() XpathINIT call T42_F() call assert_equal('acdfgijlmoprsuvxyBCE', g:Xpath) endfunc "------------------------------------------------------------------------------- " Test 43: Selecting the correct :catch clause {{{1 " " When an exception is thrown and there are multiple :catch clauses, " the first matching one is taken. "------------------------------------------------------------------------------- func T43_F() let loops = 3 while loops > 0 try if loops == 3 Xpath 'a' . loops throw "a" Xpath 'b' . loops elseif loops == 2 Xpath 'c' . loops throw "ab" Xpath 'd' . loops elseif loops == 1 Xpath 'e' . loops throw "abc" Xpath 'f' . loops endif catch /abc/ Xpath 'g' . loops catch /ab/ Xpath 'h' . loops catch /.*/ Xpath 'i' . loops catch /a/ Xpath 'j' . loops endtry let loops = loops - 1 endwhile Xpath 'k' endfunc func Test_multi_catch() XpathINIT call T43_F() call assert_equal('a3i3c2h2e1g1k', g:Xpath) endfunc "------------------------------------------------------------------------------- " Test 44: Missing or empty :catch patterns {{{1 " " A missing or empty :catch pattern means the same as /.*/, that is, " catches everything. To catch only empty exceptions, /^$/ must be " used. A :catch with missing, empty, or /.*/ argument also works " when followed by another command separated by a bar on the same " line. :catch patterns cannot be specified between ||. But other " pattern separators can be used instead of //. "------------------------------------------------------------------------------- func T44_F() try try Xpath 'a' throw "" catch /^$/ Xpath 'b' endtry try Xpath 'c' throw "" catch /.*/ Xpath 'd' endtry try Xpath 'e' throw "" catch // Xpath 'f' endtry try Xpath 'g' throw "" catch Xpath 'h' endtry try Xpath 'i' throw "oops" catch /^$/ Xpath 'j' catch /.*/ Xpath 'k' endtry try Xpath 'l' throw "arrgh" catch /^$/ Xpath 'm' catch // Xpath 'n' endtry try Xpath 'o' throw "brrr" catch /^$/ Xpath 'p' catch Xpath 'q' endtry try | Xpath 'r' | throw "x" | catch /.*/ | Xpath 's' | endtry try | Xpath 't' | throw "y" | catch // | Xpath 'u' | endtry while 1 try let caught = 0 let v:errmsg = "" " Extra try level: if ":catch" without arguments below raises " a syntax error because it misinterprets the "Xpath" as a pattern, " let it be caught by the ":catch /.*/" below. try try | Xpath 'v' | throw "z" | catch | Xpath 'w' | : endtry endtry catch /.*/ let caught = 1 call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint) finally if $VIMNOERRTHROW && v:errmsg != "" call assert_report(v:errmsg) endif if caught || $VIMNOERRTHROW && v:errmsg != "" Xpath 'x' endif break " discard error for $VIMNOERRTHROW endtry endwhile let cologne = 4711 try try Xpath 'y' throw "throw cologne" " Next lines catches all and throws 4711: catch |throw cologne| Xpath 'z' endtry catch /4711/ Xpath 'A' endtry try Xpath 'B' throw "plus" catch +plus+ Xpath 'C' endtry Xpath 'D' catch /.*/ Xpath 'E' call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint) endtry endfunc func Test_empty_catch() XpathINIT call T44_F() call assert_equal('abcdefghiklnoqrstuvwyABCD', g:Xpath) endfunc "------------------------------------------------------------------------------- " Test 45: Catching exceptions from nested :try blocks {{{1 " " When :try blocks are nested, an exception is caught by the innermost " try conditional that has a matching :catch clause. "------------------------------------------------------------------------------- func T45_F() let loops = 3 while loops > 0 try try try try if loops == 3 Xpath 'a' . loops throw "a" Xpath 'b' . loops elseif loops == 2 Xpath 'c' . loops throw "ab" Xpath 'd' . loops elseif loops == 1 Xpath 'e' . loops throw "abc" Xpath 'f' . loops endif catch /abc/ Xpath 'g' . loops endtry catch /ab/ Xpath 'h' . loops endtry catch /.*/ Xpath 'i' . loops endtry catch /a/ Xpath 'j' . loops endtry let loops = loops - 1 endwhile Xpath 'k' endfunc func Test_catch_from_nested_try() XpathINIT call T45_F() call assert_equal('a3i3c2h2e1g1k', g:Xpath) endfunc "------------------------------------------------------------------------------- " Test 46: Executing :finally after a :throw in nested :try {{{1 " " When an exception is thrown from within nested :try blocks, the " :finally clauses of the non-catching try conditionals should be " executed before the matching :catch of the next surrounding :try " gets the control. If this also has a :finally clause, it is " executed afterwards. "------------------------------------------------------------------------------- func T46_F() let sum = 0 try Xpath 'a' try Xpath 'b' try Xpath 'c' try Xpath 'd' throw "ABC" Xpath 'e' catch /xyz/ Xpath 'f' finally Xpath 'g' if sum != 0 Xpath 'h' endif let sum = sum + 1 endtry Xpath 'i' catch /123/ Xpath 'j' catch /321/ Xpath 'k' finally Xpath 'l' if sum != 1 Xpath 'm' endif let sum = sum + 2 endtry Xpath 'n' finally Xpath 'o' if sum != 3 Xpath 'p' endif let sum = sum + 4 endtry Xpath 'q' catch /ABC/ Xpath 'r' if sum != 7 Xpath 's' endif let sum = sum + 8 finally Xpath 't' if sum != 15 Xpath 'u' endif let sum = sum + 16 endtry Xpath 'v' if sum != 31 Xpath 'w' endif endfunc func Test_finally_after_throw() XpathINIT call T46_F() call assert_equal('abcdglortv', g:Xpath) endfunc "------------------------------------------------------------------------------- " Test 47: Throwing exceptions from a :catch clause {{{1 " " When an exception is thrown from a :catch clause, it should not be " caught by a :catch of the same :try conditional. After executing " the :finally clause (if present), surrounding try conditionals " should be checked for a matching :catch. "------------------------------------------------------------------------------- func T47_F() Xpath 'a' try Xpath 'b' try Xpath 'c' try Xpath 'd' throw "x1" Xpath 'e' catch /x1/ Xpath 'f' try Xpath 'g' throw "x2" Xpath 'h' catch /x1/ Xpath 'i' catch /x2/ Xpath 'j' try Xpath 'k' throw "x3" Xpath 'l' catch /x1/ Xpath 'm' catch /x2/ Xpath 'n' finally Xpath 'o' endtry Xpath 'p' catch /x3/ Xpath 'q' endtry Xpath 'r' catch /x1/ Xpath 's' catch /x2/ Xpath 't' catch /x3/ Xpath 'u' finally Xpath 'v' endtry Xpath 'w' catch /x1/ Xpath 'x' catch /x2/ Xpath 'y' catch /x3/ Xpath 'z' endtry Xpath 'A' catch /.*/ Xpath 'B' call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint) endtry Xpath 'C' endfunc func Test_throw_from_catch() XpathINIT call T47_F() call assert_equal('abcdfgjkovzAC', g:Xpath) endfunc "------------------------------------------------------------------------------- " Test 48: Throwing exceptions from a :finally clause {{{1 " " When an exception is thrown from a :finally clause, it should not be " caught by a :catch of the same :try conditional. Surrounding try " conditionals should be checked for a matching :catch. A previously " thrown exception is discarded. "------------------------------------------------------------------------------- func T48_F() try try try Xpath 'a' catch /x1/ Xpath 'b' finally Xpath 'c' throw "x1" Xpath 'd' endtry Xpath 'e' catch /x1/ Xpath 'f' endtry Xpath 'g' try try Xpath 'h' throw "x2" Xpath 'i' catch /x2/ Xpath 'j' catch /x3/ Xpath 'k' finally Xpath 'l' throw "x3" Xpath 'm' endtry Xpath 'n' catch /x2/ Xpath 'o' catch /x3/ Xpath 'p' endtry Xpath 'q' try try try Xpath 'r' throw "x4" Xpath 's' catch /x5/ Xpath 't' finally Xpath 'u' throw "x5" " discards 'x4' Xpath 'v' endtry Xpath 'w' catch /x4/ Xpath 'x' finally Xpath 'y' endtry Xpath 'z' catch /x5/ Xpath 'A' endtry Xpath 'B' catch /.*/ Xpath 'C' call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint) endtry Xpath 'D' endfunc func Test_throw_from_finally() XpathINIT call T48_F() call assert_equal('acfghjlpqruyABD', g:Xpath) endfunc "------------------------------------------------------------------------------- " Test 51: Throwing exceptions across :execute and user commands {{{1 " " A :throw command may be executed under an ":execute" or from " a user command. "------------------------------------------------------------------------------- func T51_F() command! -nargs=? THROW1 throw <args> | throw 1 command! -nargs=? THROW2 try | throw <args> | endtry | throw 2 command! -nargs=? THROW3 try | throw 3 | catch /3/ | throw <args> | endtry command! -nargs=? THROW4 try | throw 4 | finally | throw <args> | endtry try try try Xpath 'a' THROW1 "A" catch /A/ Xpath 'b' endtry catch /1/ Xpath 'c' endtry try try Xpath 'd' THROW2 "B" catch /B/ Xpath 'e' endtry catch /2/ Xpath 'f' endtry try try Xpath 'g' THROW3 "C" catch /C/ Xpath 'h' endtry catch /3/ Xpath 'i' endtry try try Xpath 'j' THROW4 "D" catch /D/ Xpath 'k' endtry catch /4/ Xpath 'l' endtry try try Xpath 'm' execute 'throw "E" | throw 5' catch /E/ Xpath 'n' endtry catch /5/ Xpath 'o' endtry try try Xpath 'p' execute 'try | throw "F" | endtry | throw 6' catch /F/ Xpath 'q' endtry catch /6/ Xpath 'r' endtry try try Xpath 's' execute'try | throw 7 | catch /7/ | throw "G" | endtry' catch /G/ Xpath 't' endtry catch /7/ Xpath 'u' endtry try try Xpath 'v' execute 'try | throw 8 | finally | throw "H" | endtry' catch /H/ Xpath 'w' endtry catch /8/ Xpath 'x' endtry catch /.*/ Xpath 'y' call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint) endtry Xpath 'z' delcommand THROW1 delcommand THROW2 delcommand THROW3 delcommand THROW4 endfunc func Test_throw_across_commands() XpathINIT call T51_F() call assert_equal('abdeghjkmnpqstvwz', g:Xpath) endfunc "------------------------------------------------------------------------------- " Test 69: :throw across :if, :elseif, :while {{{1 " " On an :if, :elseif, or :while command, an exception might be thrown " during evaluation of the expression to test. The exception can be " caught by the script. "------------------------------------------------------------------------------- func T69_throw(x) Xpath 'x' throw a:x endfunc func Test_throw_ifelsewhile() XpathINIT try try Xpath 'a' if 111 == T69_throw("if") + 111 Xpath 'b' else Xpath 'c' endif Xpath 'd' catch /^if$/ Xpath 'e' catch /.*/ Xpath 'f' call assert_report("if: " . v:exception . " in " . v:throwpoint) endtry try Xpath 'g' if v:false Xpath 'h' elseif 222 == T69_throw("elseif") + 222 Xpath 'i' else Xpath 'j' endif Xpath 'k' catch /^elseif$/ Xpath 'l' catch /.*/ Xpath 'm' call assert_report("elseif: " . v:exception . " in " . v:throwpoint) endtry try Xpath 'n' while 333 == T69_throw("while") + 333 Xpath 'o' break endwhile Xpath 'p' catch /^while$/ Xpath 'q' catch /.*/ Xpath 'r' call assert_report("while: " .. v:exception .. " in " .. v:throwpoint) endtry catch /^0$/ " default return value Xpath 's' call assert_report(v:throwpoint) catch /.*/ call assert_report(v:exception .. " in " .. v:throwpoint) Xpath 't' endtry call assert_equal('axegxlnxq', g:Xpath) endfunc "------------------------------------------------------------------------------- " Test 70: :throw across :return or :throw {{{1 " " On a :return or :throw command, an exception might be thrown during " evaluation of the expression to return or throw, respectively. The " exception can be caught by the script. "------------------------------------------------------------------------------- let T70_taken = "" func T70_throw(x, n) let g:T70_taken = g:T70_taken . "T" . a:n throw a:x endfunc func T70_F(x, y, n) let g:T70_taken = g:T70_taken . "F" . a:n return a:x + T70_throw(a:y, a:n) endfunc func T70_G(x, y, n) let g:T70_taken = g:T70_taken . "G" . a:n throw a:x . T70_throw(a:y, a:n) return a:x endfunc func Test_throwreturn() XpathINIT try try Xpath 'a' call T70_F(4711, "return", 1) Xpath 'b' catch /^return$/ Xpath 'c' catch /.*/ Xpath 'd' call assert_report("return: " .. v:exception .. " in " .. v:throwpoint) endtry try Xpath 'e' let var = T70_F(4712, "return-var", 2) Xpath 'f' catch /^return-var$/ Xpath 'g' catch /.*/ Xpath 'h' call assert_report("return-var: " . v:exception . " in " . v:throwpoint) finally unlet! var endtry try Xpath 'i' throw "except1" . T70_throw("throw1", 3) Xpath 'j' catch /^except1/ Xpath 'k' catch /^throw1$/ Xpath 'l' catch /.*/ Xpath 'm' call assert_report("throw1: " .. v:exception .. " in " .. v:throwpoint) endtry try Xpath 'n' call T70_G("except2", "throw2", 4) Xpath 'o' catch /^except2/ Xpath 'p' catch /^throw2$/ Xpath 'q' catch /.*/ Xpath 'r' call assert_report("throw2: " .. v:exception .. " in " .. v:throwpoint) endtry try Xpath 's' let var = T70_G("except3", "throw3", 5) Xpath 't' catch /^except3/ Xpath 'u' catch /^throw3$/ Xpath 'v' catch /.*/ Xpath 'w' call assert_report("throw3: " .. v:exception .. " in " .. v:throwpoint) finally unlet! var endtry call assert_equal('F1T1F2T2T3G4T4G5T5', g:T70_taken) Xpath 'x' catch /^0$/ " default return value Xpath 'y' call assert_report(v:throwpoint) catch /.*/ Xpath 'z' call assert_report('Caught' .. v:exception .. ' in ' .. v:throwpoint) endtry call assert_equal('acegilnqsvx', g:Xpath) endfunc "------------------------------------------------------------------------------- " Test 71: :throw across :echo variants and :execute {{{1 " " On an :echo, :echon, :echomsg, :echoerr, or :execute command, an " exception might be thrown during evaluation of the arguments to " be displayed or executed as a command, respectively. Any following " arguments are not evaluated, then. The exception can be caught by " the script. "------------------------------------------------------------------------------- let T71_taken = "" func T71_throw(x, n) let g:T71_taken = g:T71_taken . "T" . a:n throw a:x endfunc func T71_F(n) let g:T71_taken = g:T71_taken . "F" . a:n return "F" . a:n endfunc func Test_throw_echo() XpathINIT try try Xpath 'a' echo 'echo ' . T71_throw("echo-except", 1) . T71_F(1) Xpath 'b' catch /^echo-except$/ Xpath 'c' catch /.*/ Xpath 'd' call assert_report("echo: " .. v:exception .. " in " .. v:throwpoint) endtry try Xpath 'e' echon "echon " . T71_throw("echon-except", 2) . T71_F(2) Xpath 'f' catch /^echon-except$/ Xpath 'g' catch /.*/ Xpath 'h' call assert_report('echon: ' . v:exception . ' in ' . v:throwpoint) endtry try Xpath 'i' echomsg "echomsg " . T71_throw("echomsg-except", 3) . T71_F(3) Xpath 'j' catch /^echomsg-except$/ Xpath 'k' catch /.*/ Xpath 'l' call assert_report('echomsg: ' . v:exception . ' in ' . v:throwpoint) endtry try Xpath 'm' echoerr "echoerr " . T71_throw("echoerr-except", 4) . T71_F(4) Xpath 'n' catch /^echoerr-except$/ Xpath 'o' catch /Vim/ Xpath 'p' catch /echoerr/ Xpath 'q' catch /.*/ Xpath 'r' call assert_report('echoerr: ' . v:exception . ' in ' . v:throwpoint) endtry try Xpath 's' execute "echo 'execute " . T71_throw("execute-except", 5) . T71_F(5) "'" Xpath 't' catch /^execute-except$/ Xpath 'u' catch /.*/ Xpath 'v' call assert_report('execute: ' . v:exception . ' in ' . v:throwpoint) endtry call assert_equal('T1T2T3T4T5', g:T71_taken) Xpath 'w' catch /^0$/ " default return value Xpath 'x' call assert_report(v:throwpoint) catch /.*/ Xpath 'y' call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint) endtry call assert_equal('acegikmosuw', g:Xpath) endfunc "------------------------------------------------------------------------------- " Test 72: :throw across :let or :unlet {{{1 " " On a :let command, an exception might be thrown during evaluation " of the expression to assign. On an :let or :unlet command, the " evaluation of the name of the variable to be assigned or list or " deleted, respectively, may throw an exception. Any following " arguments are not evaluated, then. The exception can be caught by " the script. "------------------------------------------------------------------------------- let throwcount = 0 func T72_throw(x) let g:throwcount = g:throwcount + 1 throw a:x endfunc let T72_addpath = '' func T72_addpath(p) let g:T72_addpath = g:T72_addpath . a:p endfunc func Test_throw_let() XpathINIT try try let $VAR = 'old_value' Xpath 'a' let $VAR = 'let(' . T72_throw('var') . ')' Xpath 'b' catch /^var$/ Xpath 'c' finally call assert_equal('old_value', $VAR) endtry try let @a = 'old_value' Xpath 'd' let @a = 'let(' . T72_throw('reg') . ')' Xpath 'e' catch /^reg$/ try Xpath 'f' let @A = 'let(' . T72_throw('REG') . ')' Xpath 'g' catch /^REG$/ Xpath 'h' endtry finally call assert_equal('old_value', @a) call assert_equal('old_value', @A) endtry try let saved_gpath = &g:path let saved_lpath = &l:path Xpath 'i' let &path = 'let(' . T72_throw('opt') . ')' Xpath 'j' catch /^opt$/ try Xpath 'k' let &g:path = 'let(' . T72_throw('gopt') . ')' Xpath 'l' catch /^gopt$/ try Xpath 'm' let &l:path = 'let(' . T72_throw('lopt') . ')' Xpath 'n' catch /^lopt$/ Xpath 'o' endtry endtry finally call assert_equal(saved_gpath, &g:path) call assert_equal(saved_lpath, &l:path) let &g:path = saved_gpath let &l:path = saved_lpath endtry unlet! var1 var2 var3 try Xpath 'p' let var1 = 'let(' . T72_throw('var1') . ')' Xpath 'q' catch /^var1$/ Xpath 'r' finally call assert_true(!exists('var1')) endtry try let var2 = 'old_value' Xpath 's' let var2 = 'let(' . T72_throw('var2'). ')' Xpath 't' catch /^var2$/ Xpath 'u' finally call assert_equal('old_value', var2) endtry try Xpath 'v' let var{T72_throw('var3')} = 4711 Xpath 'w' catch /^var3$/ Xpath 'x' endtry try call T72_addpath('T1') let var{T72_throw('var4')} var{T72_addpath('T2')} | call T72_addpath('T3') call T72_addpath('T4') catch /^var4$/ call T72_addpath('T5') endtry try call T72_addpath('T6') unlet var{T72_throw('var5')} var{T72_addpath('T7')} \ | call T72_addpath('T8') call T72_addpath('T9') catch /^var5$/ call T72_addpath('T10') endtry call assert_equal('T1T5T6T10', g:T72_addpath) call assert_equal(11, g:throwcount) catch /.*/ call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint) endtry call assert_equal('acdfhikmoprsuvx', g:Xpath) endfunc "------------------------------------------------------------------------------- " Test 73: :throw across :function, :delfunction {{{1 " " The :function and :delfunction commands may cause an expression " specified in braces to be evaluated. During evaluation, an " exception might be thrown. The exception can be caught by the " script. "------------------------------------------------------------------------------- let T73_taken = '' func T73_throw(x, n) let g:T73_taken = g:T73_taken . 'T' . a:n throw a:x endfunc func T73_expr(x, n) let g:T73_taken = g:T73_taken . 'E' . a:n if a:n % 2 == 0 call T73_throw(a:x, a:n) endif return 2 - a:n % 2 endfunc func Test_throw_func() XpathINIT try try " Define function. Xpath 'a' function! F0() endfunction Xpath 'b' function! F{T73_expr('function-def-ok', 1)}() endfunction Xpath 'c' function! F{T73_expr('function-def', 2)}() endfunction Xpath 'd' catch /^function-def-ok$/ Xpath 'e' catch /^function-def$/ Xpath 'f' catch /.*/ call assert_report('def: ' . v:exception . ' in ' . v:throwpoint) endtry try " List function. Xpath 'g' function F0 Xpath 'h' function F{T73_expr('function-lst-ok', 3)} Xpath 'i' function F{T73_expr('function-lst', 4)} Xpath 'j' catch /^function-lst-ok$/ Xpath 'k' catch /^function-lst$/ Xpath 'l' catch /.*/ call assert_report('lst: ' . v:exception . ' in ' . v:throwpoint) endtry try " Delete function Xpath 'm' delfunction F0 Xpath 'n' delfunction F{T73_expr('function-del-ok', 5)} Xpath 'o' delfunction F{T73_expr('function-del', 6)} Xpath 'p' catch /^function-del-ok$/ Xpath 'q' catch /^function-del$/ Xpath 'r' catch /.*/ call assert_report('del: ' . v:exception . ' in ' . v:throwpoint) endtry call assert_equal('E1E2T2E3E4T4E5E6T6', g:T73_taken) catch /.*/ call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint) endtry call assert_equal('abcfghilmnor', g:Xpath) endfunc "------------------------------------------------------------------------------- " Test 74: :throw across builtin functions and commands {{{1 " " Some functions like exists(), searchpair() take expression " arguments, other functions or commands like substitute() or " :substitute cause an expression (specified in the regular " expression) to be evaluated. During evaluation an exception " might be thrown. The exception can be caught by the script. "------------------------------------------------------------------------------- let T74_taken = "" func T74_throw(x, n) let g:T74_taken = g:T74_taken . "T" . a:n throw a:x endfunc func T74_expr(x, n) let g:T74_taken = g:T74_taken . "E" . a:n call T74_throw(a:x . a:n, a:n) return "EXPR" endfunc func T74_skip(x, n) let g:T74_taken = g:T74_taken . "S" . a:n . "(" . line(".") let theline = getline(".") if theline =~ "skip" let g:T74_taken = g:T74_taken . "s)" return 1 elseif theline =~ "throw" let g:T74_taken = g:T74_taken . "t)" call T74_throw(a:x . a:n, a:n) else let g:T74_taken = g:T74_taken . ")" return 0 endif endfunc func T74_subst(x, n) let g:T74_taken = g:T74_taken . "U" . a:n . "(" . line(".") let theline = getline(".") if theline =~ "not" " T74_subst() should not be called for this line let g:T74_taken = g:T74_taken . "n)" call T74_throw(a:x . a:n, a:n) elseif theline =~ "throw" let g:T74_taken = g:T74_taken . "t)" call T74_throw(a:x . a:n, a:n) else let g:T74_taken = g:T74_taken . ")" return "replaced" endif endfunc func Test_throw_builtin_func() XpathINIT try try Xpath 'a' let result = exists('*{T74_expr("exists", 1)}') Xpath 'b' catch /^exists1$/ Xpath 'c' try let result = exists('{T74_expr("exists", 2)}') Xpath 'd' catch /^exists2$/ Xpath 'e' catch /.*/ call assert_report('exists2: ' . v:exception . ' in ' . v:throwpoint) endtry catch /.*/ call assert_report('exists1: ' . v:exception . ' in ' . v:throwpoint) endtry try let file = tempname() exec "edit" file call append(0, [ \ 'begin', \ 'xx', \ 'middle 3', \ 'xx', \ 'middle 5 skip', \ 'xx', \ 'middle 7 throw', \ 'xx', \ 'end']) normal! gg Xpath 'f' let result = searchpair("begin", "middle", "end", '', \ 'T74_skip("searchpair", 3)') Xpath 'g' let result = searchpair("begin", "middle", "end", '', \ 'T74_skip("searchpair", 4)') Xpath 'h' let result = searchpair("begin", "middle", "end", '', \ 'T74_skip("searchpair", 5)') Xpath 'i' catch /^searchpair[35]$/ Xpath 'j' catch /^searchpair4$/ Xpath 'k' catch /.*/ call assert_report('searchpair: ' . v:exception . ' in ' . v:throwpoint) finally bwipeout! call delete(file) endtry try let file = tempname() exec "edit" file call append(0, [ \ 'subst 1', \ 'subst 2', \ 'not', \ 'subst 4', \ 'subst throw', \ 'subst 6']) normal! gg Xpath 'l' 1,2substitute/subst/\=T74_subst("substitute", 6)/ try Xpath 'm' try let v:errmsg = "" 3substitute/subst/\=T74_subst("substitute", 7)/ finally if v:errmsg != "" " If exceptions are not thrown on errors, fake the error " exception in order to get the same execution path. throw "faked Vim(substitute)" endif endtry catch /Vim(substitute)/ " Pattern not found ('e' flag missing) Xpath 'n' 3substitute/subst/\=T74_subst("substitute", 8)/e Xpath 'o' endtry Xpath 'p' 4,6substitute/subst/\=T74_subst("substitute", 9)/ Xpath 'q' catch /^substitute[678]/ Xpath 'r' catch /^substitute9/ Xpath 's' finally bwipeout! call delete(file) endtry try Xpath 't' let var = substitute("sub", "sub", '\=T74_throw("substitute()y", 10)', '') Xpath 'u' catch /substitute()y/ Xpath 'v' catch /.*/ call assert_report('substitute()y: ' . v:exception . ' in ' \ . v:throwpoint) endtry try Xpath 'w' let var = substitute("not", "sub", '\=T74_throw("substitute()n", 11)', '') Xpath 'x' catch /substitute()n/ Xpath 'y' catch /.*/ call assert_report('substitute()n: ' . v:exception . ' in ' \ . v:throwpoint) endtry call assert_equal('E1T1E2T2S3(3)S4(5s)S4(7t)T4U6(1)U6(2)U9(4)U9(5t)T9T10', \ g:T74_taken) catch /.*/ call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint) endtry call assert_equal('acefgklmnopstvwx', g:Xpath) endfunc "------------------------------------------------------------------------------- " Test 75: Errors in builtin functions. {{{1 " " On an error in a builtin function called inside a :try/:endtry " region, the evaluation of the expression calling that function and " the command containing that expression are abandoned. The error can " be caught as an exception. " " A simple :call of the builtin function is a trivial case. If the " builtin function is called in the argument list of another function, " no further arguments are evaluated, and the other function is not " executed. If the builtin function is called from the argument of " a :return command, the :return command is not executed. If the " builtin function is called from the argument of a :throw command, " the :throw command is not executed. The evaluation of the " expression calling the builtin function is abandoned. "------------------------------------------------------------------------------- func T75_F1(arg1) Xpath 'a' endfunc func T75_F2(arg1, arg2) Xpath 'b' endfunc func T75_G() Xpath 'c' endfunc func T75_H() Xpath 'd' endfunc func T75_R() while 1 try let caught = 0 let v:errmsg = "" Xpath 'e' return append(1, "s") catch /E21/ let caught = 1 catch /.*/ Xpath 'f' finally Xpath 'g' if caught || $VIMNOERRTHROW && v:errmsg =~ 'E21' Xpath 'h' endif break " discard error for $VIMNOERRTHROW endtry endwhile Xpath 'i' endfunc func Test_builtin_func_error() XpathINIT try set noma " let append() fail with "E21" while 1 try let caught = 0 let v:errmsg = "" Xpath 'j' call append(1, "s") catch /E21/ let caught = 1 catch /.*/ Xpath 'k' finally Xpath 'l' if caught || $VIMNOERRTHROW && v:errmsg =~ 'E21' Xpath 'm' endif break " discard error for $VIMNOERRTHROW endtry endwhile while 1 try let caught = 0 let v:errmsg = "" Xpath 'n' call T75_F1('x' . append(1, "s")) catch /E21/ let caught = 1 catch /.*/ Xpath 'o' finally Xpath 'p' if caught || $VIMNOERRTHROW && v:errmsg =~ 'E21' Xpath 'q' endif break " discard error for $VIMNOERRTHROW endtry endwhile while 1 try let caught = 0 let v:errmsg = "" Xpath 'r' call T75_F2('x' . append(1, "s"), T75_G()) catch /E21/ let caught = 1 catch /.*/ Xpath 's' finally Xpath 't' if caught || $VIMNOERRTHROW && v:errmsg =~ 'E21' Xpath 'u' endif break " discard error for $VIMNOERRTHROW endtry endwhile call T75_R() while 1 try let caught = 0 let v:errmsg = "" Xpath 'v' throw "T" . append(1, "s") catch /E21/ let caught = 1 catch /^T.*/ Xpath 'w' catch /.*/ Xpath 'x' finally Xpath 'y' if caught || $VIMNOERRTHROW && v:errmsg =~ 'E21' Xpath 'z' endif break " discard error for $VIMNOERRTHROW endtry endwhile while 1 try let caught = 0 let v:errmsg = "" Xpath 'A' let x = "a" let x = x . "b" . append(1, "s") . T75_H() catch /E21/ let caught = 1 catch /.*/ Xpath 'B' finally Xpath 'C' if caught || $VIMNOERRTHROW && v:errmsg =~ 'E21' Xpath 'D' endif call assert_equal('a', x) break " discard error for $VIMNOERRTHROW endtry endwhile catch /.*/ call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint) finally set ma& endtry call assert_equal('jlmnpqrtueghivyzACD', g:Xpath) endfunc func Test_reload_in_try_catch() call writefile(['x'], 'Xreload', 'D') set autoread edit Xreload tabnew call writefile(['xx'], 'Xreload') augroup ReLoad au FileReadPost Xreload let x = doesnotexist au BufReadPost Xreload let x = doesnotexist augroup END try edit Xreload catch endtry tabnew tabclose tabclose autocmd! ReLoad set noautoread bwipe! Xreload endfunc " Test for errors with :catch, :throw, :finally {{{1 func Test_try_catch_errors() call assert_fails('throw |', 'E471:') call assert_fails("throw \n ", 'E471:') call assert_fails('catch abc', 'E654:') call assert_fails('try | let i = 1| finally | catch | endtry', 'E604:') call assert_fails('finally', 'E606:') call assert_fails('try | finally | finally | endtry', 'E607:') call assert_fails('try | for i in range(5) | endif | endtry', 'E580:') call assert_fails('try | while v:true | endtry', 'E170:') call assert_fails('try | if v:true | endtry', 'E171:') " this was using a negative index in cstack[] let lines =<< trim END try for if endwhile if finally END call v9.CheckScriptFailure(lines, 'E690:') let lines =<< trim END try for if endwhile if endtry END call v9.CheckScriptFailure(lines, 'E690:') endfunc " Test for verbose messages with :try :catch, and :finally {{{1 func Test_try_catch_verbose() " This test works only when the language is English CheckEnglish set verbose=14 " Test for verbose messages displayed when an exception is caught redir => msg try echo i catch /E121:/ finally endtry redir END let expected = [ \ 'Exception thrown: Vim(echo):E121: Undefined variable: i', '', \ 'Exception caught: Vim(echo):E121: Undefined variable: i', '', \ 'Exception finished: Vim(echo):E121: Undefined variable: i'] call assert_equal(expected, split(msg, "\n")) " Test for verbose messages displayed when an exception is discarded redir => msg try try throw 'abc' finally throw 'xyz' endtry catch endtry redir END let expected = [ \ 'Exception thrown: abc', '', \ 'Exception made pending: abc', '', \ 'Exception thrown: xyz', '', \ 'Exception discarded: abc', '', \ 'Exception caught: xyz', '', \ 'Exception finished: xyz'] call assert_equal(expected, split(msg, "\n")) " Test for messages displayed when :throw is resumed after :finally redir => msg try try throw 'abc' finally endtry catch endtry redir END let expected = [ \ 'Exception thrown: abc', '', \ 'Exception made pending: abc', '', \ 'Exception resumed: abc', '', \ 'Exception caught: abc', '', \ 'Exception finished: abc'] call assert_equal(expected, split(msg, "\n")) " Test for messages displayed when :break is resumed after :finally redir => msg for i in range(1) try break finally endtry endfor redir END let expected = [':break made pending', '', ':break resumed'] call assert_equal(expected, split(msg, "\n")) " Test for messages displayed when :continue is resumed after :finally redir => msg for i in range(1) try continue finally endtry endfor redir END let expected = [':continue made pending', '', ':continue resumed'] call assert_equal(expected, split(msg, "\n")) " Test for messages displayed when :return is resumed after :finally func Xtest() try return 'vim' finally endtry endfunc redir => msg call Xtest() redir END let expected = [ \ 'calling Xtest()', '', \ ':return vim made pending', '', \ ':return vim resumed', '', \ 'Xtest returning ''vim''', '', \ 'continuing in Test_try_catch_verbose'] call assert_equal(expected, split(msg, "\n")) delfunc Xtest " Test for messages displayed when :finish is resumed after :finally call writefile(['try', 'finish', 'finally', 'endtry'], 'Xscript') redir => msg source Xscript redir END let expected = [ \ ':finish made pending', '', \ ':finish resumed', '', \ 'finished sourcing Xscript', \ 'continuing in Test_try_catch_verbose'] call assert_equal(expected, split(msg, "\n")[1:]) call delete('Xscript') " Test for messages displayed when a pending :continue is discarded by an " exception in a finally handler redir => msg try for i in range(1) try continue finally throw 'abc' endtry endfor catch endtry redir END let expected = [ \ ':continue made pending', '', \ 'Exception thrown: abc', '', \ ':continue discarded', '', \ 'Exception caught: abc', '', \ 'Exception finished: abc'] call assert_equal(expected, split(msg, "\n")) set verbose& endfunc " Test for throwing an exception from a BufEnter autocmd {{{1 func Test_BufEnter_exception() augroup bufenter_exception au! autocmd BufEnter Xfile1 throw 'abc' augroup END let caught_abc = 0 try sp Xfile1 catch /^abc/ let caught_abc = 1 endtry call assert_equal(1, caught_abc) call assert_equal(1, winnr('$')) augroup bufenter_exception au! augroup END augroup! bufenter_exception %bwipe! " Test for recursively throwing exceptions in autocmds augroup bufenter_exception au! autocmd BufEnter Xfile1 throw 'bufenter' autocmd BufLeave Xfile1 throw 'bufleave' augroup END let ex_count = 0 try try sp Xfile1 catch /^bufenter/ let ex_count += 1 endtry catch /^bufleave/ let ex_count += 10 endtry call assert_equal(10, ex_count) call assert_equal(2, winnr('$')) augroup bufenter_exception au! augroup END augroup! bufenter_exception %bwipe! endfunc " Test for using try/catch when lines are joined by "|" or "\n" {{{1 func Test_try_catch_nextcmd() func Throw() throw "Failure" endfunc let lines =<< trim END try let s:x = Throw() catch let g:caught = 1 endtry END let g:caught = 0 call execute(lines) call assert_equal(1, g:caught) let g:caught = 0 call execute(join(lines, '|')) call assert_equal(1, g:caught) let g:caught = 0 call execute(join(lines, "\n")) call assert_equal(1, g:caught) unlet g:caught delfunc Throw endfunc " Test for using try/catch in a user command with a failing expression {{{1 func Test_user_command_try_catch() let lines =<< trim END function s:throw() abort throw 'error' endfunction command! Execute \ try \ | let s:x = s:throw() \ | catch \ | let g:caught = 'caught' \ | endtry let g:caught = 'no' Execute call assert_equal('caught', g:caught) END call writefile(lines, 'XtestTryCatch') source XtestTryCatch call delete('XtestTryCatch') unlet g:caught endfunc " Test for using throw in a called function with following error {{{1 func Test_user_command_throw_in_function_call() let lines =<< trim END function s:get_dict() abort throw 'my_error' endfunction try call s:get_dict().foo() catch /my_error/ let caught = 'yes' catch let caught = v:exception endtry call assert_equal('yes', caught) END call writefile(lines, 'XtestThrow') source XtestThrow call delete('XtestThrow') unlet g:caught endfunc " Test that after reporting an uncaught exception there is no error for a " missing :endif func Test_after_exception_no_endif_error() function Throw() throw "Failure" endfunction function Foo() if 1 call Throw() endif endfunction call assert_fails('call Foo()', ['E605:', 'E605:']) delfunc Throw delfunc Foo endfunc " Test for using throw in a called function with following endtry {{{1 func Test_user_command_function_call_with_endtry() let lines =<< trim END funct s:throw(msg) abort throw a:msg endfunc func s:main() abort try try throw 'err1' catch call s:throw('err2') | endtry catch let s:caught = 'yes' endtry endfunc call s:main() call assert_equal('yes', s:caught) END call writefile(lines, 'XtestThrow', 'D') source XtestThrow endfunc func ThisWillFail() endfunc " This was crashing prior to the fix in 8.2.3478. func Test_error_in_catch_and_finally() let lines =<< trim END try echo x catch for l in [] finally END call writefile(lines, 'XtestCatchAndFinally', 'D') try source XtestCatchAndFinally catch /E600:/ endtry endfunc " This was causing an illegal memory access func Test_leave_block_in_endtry_not_called() let lines =<< trim END vim9script try # for x in [] if endwhile if endtry END call writefile(lines, 'XtestEndtry', 'D') try source XtestEndtry catch /E171:/ endtry endfunc " Modeline {{{1 " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker