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<string>
          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<string>
          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<string>
          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<string>
          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<string>
+       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 -- b...@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    ///

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to vim_dev+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/202012261441.0BQEf4vL3857844%40masaka.moolenaar.net.

Raspunde prin e-mail lui