patch 8.2.2222: Vim9: cannot keep script variables when reloading

Problem:    Vim9: cannot keep script variables when reloading.
Solution:   Add the "noclear" argument to :vim9script.
diff --git a/src/ex_cmds.h b/src/ex_cmds.h
index 5e61c3e..75bb7fb 100644
--- a/src/ex_cmds.h
+++ b/src/ex_cmds.h
@@ -1680,7 +1680,7 @@
 	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,
+	EX_WORD1|EX_CMDWIN|EX_LOCK_OK,
 	ADDR_NONE),
 EXCMD(CMD_viusage,	"viusage",	ex_viusage,
 	EX_TRLBAR,
diff --git a/src/ex_docmd.c b/src/ex_docmd.c
index 69e3f12..156f0df 100644
--- a/src/ex_docmd.c
+++ b/src/ex_docmd.c
@@ -2594,7 +2594,7 @@
     // 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;
+	SCRIPT_ITEM(current_sctx.sc_sid)->sn_state = SN_STATE_HAD_COMMAND;
 
     /*
      * If the command just executed called do_cmdline(), any throw or ":return"
diff --git a/src/scriptfile.c b/src/scriptfile.c
index 327500b..38704a7 100644
--- a/src/scriptfile.c
+++ b/src/scriptfile.c
@@ -1320,43 +1320,27 @@
     if (sid > 0)
     {
 	hashtab_T	*ht;
-	int		is_vim9 = si->sn_version == SCRIPT_VERSION_VIM9;
+	int		todo;
+	hashitem_T	*hi;
+	dictitem_T	*di;
 
 	// loading the same script again
-	si->sn_had_command = FALSE;
+	si->sn_state = SN_STATE_RELOAD;
 	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.
+	// 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);
-	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);
+	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,8 +1374,10 @@
 	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;
     }
-    si->sn_script_seq = current_sctx.sc_seq;
 
 # ifdef FEAT_PROFILE
     if (do_profiling == PROF_YES)
diff --git a/src/structs.h b/src/structs.h
index ea994f8..93a6b0a 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -1821,7 +1821,7 @@
     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
+    int		sn_state;	// SN_STATE_ values
     char_u	*sn_save_cpo;	// 'cpo' value when :vim9script found
 
 # ifdef FEAT_PROFILE
@@ -1845,6 +1845,10 @@
 # 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 {
diff --git a/src/testdir/test_vim9_script.vim b/src/testdir/test_vim9_script.vim
index 1e64daa..cdd83c3 100644
--- a/src/testdir/test_vim9_script.vim
+++ b/src/testdir/test_vim9_script.vim
@@ -1158,6 +1158,53 @@
   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
diff --git a/src/version.c b/src/version.c
index ba5df92..f488d77 100644
--- a/src/version.c
+++ b/src/version.c
@@ -751,6 +751,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    2222,
+/**/
     2221,
 /**/
     2220,
diff --git a/src/vim9script.c b/src/vim9script.c
index 163b8a7..70efc40 100644
--- a/src/vim9script.c
+++ b/src/vim9script.c
@@ -32,6 +32,7 @@
     void
 ex_vim9script(exarg_T *eap)
 {
+    int		    sid = current_sctx.sc_sid;
     scriptitem_T    *si;
 
     if (!getline_equal(eap->getline, eap->cookie, getsourceline))
@@ -39,15 +40,35 @@
 	emsg(_(e_vim9script_can_only_be_used_in_script));
 	return;
     }
-    si = SCRIPT_ITEM(current_sctx.sc_sid);
-    if (si->sn_had_command)
+
+    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;
-    si->sn_had_command = TRUE;
 
     if (STRCMP(p_cpo, CPO_VIM) != 0)
     {
@@ -719,6 +740,9 @@
     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;
 }
 
 /*