To: vim_dev@googlegroups.com Subject: Patch 8.2.2222 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.2222 Problem: Vim9: cannot keep script variables when reloading. Solution: Add the "noclear" argument to :vim9script. Files: runtime/doc/vim9.txt, src/structs.h, src/scriptfile.c, src/vim9script.c, src/ex_cmds.h, src/ex_docmd.c, src/testdir/test_vim9_script.vim *** ../vim-8.2.2221/runtime/doc/vim9.txt 2020-12-24 15:13:35.850860411 +0100 --- runtime/doc/vim9.txt 2020-12-26 15:27:08.281901673 +0100 *************** *** 25,31 **** ============================================================================== ! 1. What is Vim9 script? *vim9-script* THIS IS STILL UNDER DEVELOPMENT - ANYTHING CAN BREAK - ANYTHING CAN CHANGE --- 25,31 ---- ============================================================================== ! 1. What is Vim9 script? *Vim9-script* THIS IS STILL UNDER DEVELOPMENT - ANYTHING CAN BREAK - ANYTHING CAN CHANGE *************** *** 112,118 **** 101 number To improve readability there must be a space between a command and the # ! that starts a comment. Vim9 functions ~ --- 112,123 ---- 101 number To improve readability there must be a space between a command and the # ! that starts a comment: > ! var = value # comment ! var = value# error! ! ! In legacy script # is also used for the alternate file name. In Vim9 script ! you need to use %% instead. Instead of ## use %%% (stands for all arguments). Vim9 functions ~ *************** *** 193,198 **** --- 198,242 ---- |FuncUndefined| triggered there. + Reloading a Vim9 script clears functions and variables by default ~ + *vim9-reload* + When loading a legacy Vim script a second time nothing is removed, the + commands will replace existing variables and functions and create new ones. + + When loading a Vim9 script a second time all existing script-local functions + and variables are deleted, thus you start with a clean slate. This is useful + if you are developing a plugin and want to try a new version. If you renamed + something you don't have to worry about the old name still hanging around. + + If you do want to keep items, use: > + vimscript noclear + + You want to use this in scripts that use a `finish` command to bail out at + some point when loaded again. E.g. when a buffer local option is set: > + vimscript noclear + setlocal completefunc=SomeFunc + if exists('*SomeFunc') | finish | endif + def g:SomeFunc() + .... + + There is one gotcha: If a compiled function is replaced and it is called from + another compiled function that is not replaced, it will try to call the + function from before it was replaced, which no longer exists. This doesn't + work: > + vimscript noclear + + def ReplaceMe() + echo 'function redefined every time' + enddef + + if exists('s:loaded') | finish | endif + var s:loaded = true + + def NotReplaced() + ReplaceMe() # Error if ReplaceMe() was redefined + enddef + + Variable declarations with :var, :final and :const ~ *vim9-declaration* *:var* Local variables need to be declared with `:var`. Local constants need to be *************** *** 340,346 **** number of arguments and any return type. The function can be defined later. ! Lamba using => instead of -> ~ In legacy script there can be confusion between using "->" for a method call and for a lambda. Also, when a "{" is found the parser needs to figure out if --- 384,390 ---- number of arguments and any return type. The function can be defined later. ! Lambda using => instead of -> ~ In legacy script there can be confusion between using "->" for a method call and for a lambda. Also, when a "{" is found the parser needs to figure out if *************** *** 351,357 **** which is similar to Javascript: > var Lambda = (arg) => expression ! No line break is allowed in the arguments of a lambda up to and includeing the "=>". This is OK: > filter(list, (k, v) => v > 0) --- 395,401 ---- which is similar to Javascript: > var Lambda = (arg) => expression ! No line break is allowed in the arguments of a lambda up to and including the "=>". This is OK: > filter(list, (k, v) => v > 0) *************** *** 369,377 **** } NOT IMPLEMENTED YET ! Note that the "{" must be followed by white space, otherwise it is assumed to ! be the start of a dictionary: > ! var Lambda = (arg) => {key: 42} Automatic line continuation ~ --- 413,421 ---- } NOT IMPLEMENTED YET ! To avoid the "{" of a dictionary literal to be recognized as a statement block ! wrap it in parenthesis: > ! var Lambda = (arg) => ({key: 42}) Automatic line continuation ~ *************** *** 737,754 **** Limitations ~ Local variables will not be visible to string evaluation. For example: > ! def EvalString(): list var list = ['aa', 'bb', 'cc', 'dd'] return range(1, 2)->map('list[v:val]') enddef The map argument is a string expression, which is evaluated without the function scope. Instead, use a lambda: > ! def EvalString(): list var list = ['aa', 'bb', 'cc', 'dd'] ! return range(1, 2)->map({ _, v -> list[v] }) enddef ============================================================================== --- 781,804 ---- Limitations ~ Local variables will not be visible to string evaluation. For example: > ! def MapList(): list var list = ['aa', 'bb', 'cc', 'dd'] return range(1, 2)->map('list[v:val]') enddef The map argument is a string expression, which is evaluated without the function scope. Instead, use a lambda: > ! def MapList(): list var list = ['aa', 'bb', 'cc', 'dd'] ! return range(1, 2)->map(( _, v) => list[v]) enddef + The same is true for commands that are not compiled, such as `:global`. + For these the backtick expansion can be used. Example: > + def Replace() + var newText = 'blah' + g/pattern/s/^/`=newText`/ + enddef ============================================================================== *** ../vim-8.2.2221/src/structs.h 2020-12-24 21:56:37.643479575 +0100 --- src/structs.h 2020-12-26 14:18:58.239440119 +0100 *************** *** 1821,1827 **** int sn_last_block_id; // Unique ID for each script block int sn_version; // :scriptversion ! int sn_had_command; // TRUE if any command was executed char_u *sn_save_cpo; // 'cpo' value when :vim9script found # ifdef FEAT_PROFILE --- 1821,1827 ---- int sn_last_block_id; // Unique ID for each script block int sn_version; // :scriptversion ! int sn_state; // SN_STATE_ values char_u *sn_save_cpo; // 'cpo' value when :vim9script found # ifdef FEAT_PROFILE *************** *** 1845,1850 **** --- 1845,1854 ---- # endif } scriptitem_T; + #define SN_STATE_NEW 0 // newly loaded script, nothing done + #define SN_STATE_RELOAD 1 // script loaded before, nothing done + #define SN_STATE_HAD_COMMAND 9 // a command was executed + // Struct passed through eval() functions. // See EVALARG_EVALUATE for a fixed value with eval_flags set to EVAL_EVALUATE. typedef struct { *** ../vim-8.2.2221/src/scriptfile.c 2020-12-24 21:56:37.647479568 +0100 --- src/scriptfile.c 2020-12-26 14:52:08.044775762 +0100 *************** *** 1320,1362 **** if (sid > 0) { hashtab_T *ht; ! int is_vim9 = si->sn_version == SCRIPT_VERSION_VIM9; // loading the same script again ! si->sn_had_command = FALSE; si->sn_version = 1; current_sctx.sc_sid = sid; ! // In Vim9 script all script-local variables are removed when reloading ! // the same script. In legacy script they remain but "const" can be ! // set again. ht = &SCRIPT_VARS(sid); ! if (is_vim9) ! { ! hashtab_free_contents(ht); ! hash_init(ht); ! } ! else ! { ! int todo = (int)ht->ht_used; ! hashitem_T *hi; ! dictitem_T *di; ! ! for (hi = ht->ht_array; todo > 0; ++hi) ! if (!HASHITEM_EMPTY(hi)) ! { ! --todo; ! di = HI2DI(hi); ! di->di_flags |= DI_FLAGS_RELOAD; ! } ! } ! ! // old imports and script variables are no longer valid ! free_imports_and_script_vars(sid); ! ! // in Vim9 script functions are marked deleted ! if (is_vim9) ! delete_script_functions(sid); } else { --- 1320,1346 ---- if (sid > 0) { hashtab_T *ht; ! int todo; ! hashitem_T *hi; ! dictitem_T *di; // loading the same script again ! si->sn_state = SN_STATE_RELOAD; si->sn_version = 1; current_sctx.sc_sid = sid; ! // Script-local variables remain but "const" can be set again. ! // In Vim9 script variables will be cleared when "vim9script" is ! // encountered without the "noclear" argument. ht = &SCRIPT_VARS(sid); ! todo = (int)ht->ht_used; ! for (hi = ht->ht_array; todo > 0; ++hi) ! if (!HASHITEM_EMPTY(hi)) ! { ! --todo; ! di = HI2DI(hi); ! di->di_flags |= DI_FLAGS_RELOAD; ! } } else { *************** *** 1390,1397 **** fname_exp = vim_strsave(si->sn_name); // used for autocmd if (ret_sid != NULL) *ret_sid = current_sctx.sc_sid; } - si->sn_script_seq = current_sctx.sc_seq; # ifdef FEAT_PROFILE if (do_profiling == PROF_YES) --- 1374,1383 ---- fname_exp = vim_strsave(si->sn_name); // used for autocmd if (ret_sid != NULL) *ret_sid = current_sctx.sc_sid; + + // Used to check script variable index is still valid. + si->sn_script_seq = current_sctx.sc_seq; } # ifdef FEAT_PROFILE if (do_profiling == PROF_YES) *** ../vim-8.2.2221/src/vim9script.c 2020-12-25 12:37:59.487073428 +0100 --- src/vim9script.c 2020-12-26 14:53:45.184435701 +0100 *************** *** 32,37 **** --- 32,38 ---- void ex_vim9script(exarg_T *eap) { + int sid = current_sctx.sc_sid; scriptitem_T *si; if (!getline_equal(eap->getline, eap->cookie, getsourceline)) *************** *** 39,53 **** emsg(_(e_vim9script_can_only_be_used_in_script)); return; } ! si = SCRIPT_ITEM(current_sctx.sc_sid); ! if (si->sn_had_command) { emsg(_(e_vim9script_must_be_first_command_in_script)); return; } current_sctx.sc_version = SCRIPT_VERSION_VIM9; si->sn_version = SCRIPT_VERSION_VIM9; - si->sn_had_command = TRUE; if (STRCMP(p_cpo, CPO_VIM) != 0) { --- 40,74 ---- emsg(_(e_vim9script_can_only_be_used_in_script)); return; } ! ! si = SCRIPT_ITEM(sid); ! if (si->sn_state == SN_STATE_HAD_COMMAND) { emsg(_(e_vim9script_must_be_first_command_in_script)); return; } + if (!IS_WHITE_OR_NUL(*eap->arg) && STRCMP(eap->arg, "noclear") != 0) + { + semsg(_(e_invarg2), eap->arg); + return; + } + if (si->sn_state == SN_STATE_RELOAD && IS_WHITE_OR_NUL(*eap->arg)) + { + hashtab_T *ht = &SCRIPT_VARS(sid); + + // Reloading a script without the "noclear" argument: clear + // script-local variables and functions. + hashtab_free_contents(ht); + hash_init(ht); + delete_script_functions(sid); + + // old imports and script variables are no longer valid + free_imports_and_script_vars(sid); + } + si->sn_state = SN_STATE_HAD_COMMAND; + current_sctx.sc_version = SCRIPT_VERSION_VIM9; si->sn_version = SCRIPT_VERSION_VIM9; if (STRCMP(p_cpo, CPO_VIM) != 0) { *************** *** 719,724 **** --- 740,748 ---- hash_init(ht); ga_clear(&si->sn_var_vals); + + // existing commands using script variable indexes are no longer valid + si->sn_script_seq = current_sctx.sc_seq; } /* *** ../vim-8.2.2221/src/ex_cmds.h 2020-12-21 19:59:04.573197707 +0100 --- src/ex_cmds.h 2020-12-26 14:30:06.909262005 +0100 *************** *** 1680,1686 **** EX_RANGE|EX_BANG|EX_NEEDARG|EX_EXTRA|EX_NOTRLCOM|EX_TRLBAR|EX_XFILE|EX_LOCK_OK, ADDR_OTHER), EXCMD(CMD_vim9script, "vim9script", ex_vim9script, ! EX_CMDWIN|EX_LOCK_OK, ADDR_NONE), EXCMD(CMD_viusage, "viusage", ex_viusage, EX_TRLBAR, --- 1680,1686 ---- EX_RANGE|EX_BANG|EX_NEEDARG|EX_EXTRA|EX_NOTRLCOM|EX_TRLBAR|EX_XFILE|EX_LOCK_OK, ADDR_OTHER), EXCMD(CMD_vim9script, "vim9script", ex_vim9script, ! EX_WORD1|EX_CMDWIN|EX_LOCK_OK, ADDR_NONE), EXCMD(CMD_viusage, "viusage", ex_viusage, EX_TRLBAR, *** ../vim-8.2.2221/src/ex_docmd.c 2020-12-25 19:25:41.742706213 +0100 --- src/ex_docmd.c 2020-12-26 14:19:21.735361343 +0100 *************** *** 2594,2600 **** // Set flag that any command was executed, used by ex_vim9script(). if (getline_equal(ea.getline, ea.cookie, getsourceline) && current_sctx.sc_sid > 0) ! SCRIPT_ITEM(current_sctx.sc_sid)->sn_had_command = TRUE; /* * If the command just executed called do_cmdline(), any throw or ":return" --- 2594,2600 ---- // Set flag that any command was executed, used by ex_vim9script(). if (getline_equal(ea.getline, ea.cookie, getsourceline) && current_sctx.sc_sid > 0) ! SCRIPT_ITEM(current_sctx.sc_sid)->sn_state = SN_STATE_HAD_COMMAND; /* * If the command just executed called do_cmdline(), any throw or ":return" *** ../vim-8.2.2221/src/testdir/test_vim9_script.vim 2020-12-25 17:36:23.710969861 +0100 --- src/testdir/test_vim9_script.vim 2020-12-26 15:11:24.260878931 +0100 *************** *** 1158,1163 **** --- 1158,1210 ---- StopVimInTerminal(buf) enddef + def Test_vim9script_reload_noclear() + var lines =<< trim END + vim9script noclear + g:loadCount += 1 + var s:reloaded = 'init' + + def Again(): string + return 'again' + enddef + + if exists('s:loaded') | finish | endif + var s:loaded = true + + var s:notReloaded = 'yes' + s:reloaded = 'first' + def g:Values(): list + return [s:reloaded, s:notReloaded, Once()] + enddef + def g:CallAgain(): string + return Again() + enddef + + def Once(): string + return 'once' + enddef + END + writefile(lines, 'XReloaded') + g:loadCount = 0 + source XReloaded + assert_equal(1, g:loadCount) + assert_equal(['first', 'yes', 'once'], g:Values()) + assert_equal('again', g:CallAgain()) + source XReloaded + assert_equal(2, g:loadCount) + assert_equal(['init', 'yes', 'once'], g:Values()) + assert_fails('call g:CallAgain()', 'E933:') + source XReloaded + assert_equal(3, g:loadCount) + assert_equal(['init', 'yes', 'once'], g:Values()) + assert_fails('call g:CallAgain()', 'E933:') + + delete('Xreloaded') + delfunc g:Values + delfunc g:CallAgain + unlet g:loadCount + enddef + def Test_vim9script_reload_import() var lines =<< trim END vim9script *** ../vim-8.2.2221/src/version.c 2020-12-26 12:06:50.584655332 +0100 --- src/version.c 2020-12-26 14:20:01.147229726 +0100 *************** *** 752,753 **** --- 752,755 ---- { /* Add new patch number below this line */ + /**/ + 2222, /**/ -- "Space is big. Really big. You just won't believe how vastly hugely mind- bogglingly big it is. I mean, you may think it's a long way down the road to the chemist, but that's just peanuts to space." -- Douglas Adams, "The Hitchhiker's Guide to the Galaxy" /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\ \\\ an exciting new programming language -- http://www.Zimbu.org /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///