Skip to content

Commit 2b32700

Browse files
committed
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.
1 parent b0ac4ea commit 2b32700

8 files changed

Lines changed: 161 additions & 48 deletions

File tree

runtime/doc/vim9.txt

Lines changed: 60 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ THIS IS STILL UNDER DEVELOPMENT - ANYTHING CAN BREAK - ANYTHING CAN CHANGE
2525

2626
==============================================================================
2727

28-
1. What is Vim9 script? *vim9-script*
28+
1. What is Vim9 script? *Vim9-script*
2929

3030
THIS IS STILL UNDER DEVELOPMENT - ANYTHING CAN BREAK - ANYTHING CAN CHANGE
3131

@@ -112,7 +112,12 @@ In Vi # is a command to list text with numbers. In Vim9 script you can use
112112
101 number
113113
114114
To improve readability there must be a space between a command and the #
115-
that starts a comment.
115+
that starts a comment: >
116+
var = value # comment
117+
var = value# error!
118+
119+
In legacy script # is also used for the alternate file name. In Vim9 script
120+
you need to use %% instead. Instead of ## use %%% (stands for all arguments).
116121

117122

118123
Vim9 functions ~
@@ -193,6 +198,45 @@ You can use an autoload function if needed, or call a legacy function and have
193198
|FuncUndefined| triggered there.
194199

195200

201+
Reloading a Vim9 script clears functions and variables by default ~
202+
*vim9-reload*
203+
When loading a legacy Vim script a second time nothing is removed, the
204+
commands will replace existing variables and functions and create new ones.
205+
206+
When loading a Vim9 script a second time all existing script-local functions
207+
and variables are deleted, thus you start with a clean slate. This is useful
208+
if you are developing a plugin and want to try a new version. If you renamed
209+
something you don't have to worry about the old name still hanging around.
210+
211+
If you do want to keep items, use: >
212+
vimscript noclear
213+
214+
You want to use this in scripts that use a `finish` command to bail out at
215+
some point when loaded again. E.g. when a buffer local option is set: >
216+
vimscript noclear
217+
setlocal completefunc=SomeFunc
218+
if exists('*SomeFunc') | finish | endif
219+
def g:SomeFunc()
220+
....
221+
222+
There is one gotcha: If a compiled function is replaced and it is called from
223+
another compiled function that is not replaced, it will try to call the
224+
function from before it was replaced, which no longer exists. This doesn't
225+
work: >
226+
vimscript noclear
227+
228+
def ReplaceMe()
229+
echo 'function redefined every time'
230+
enddef
231+
232+
if exists('s:loaded') | finish | endif
233+
var s:loaded = true
234+
235+
def NotReplaced()
236+
ReplaceMe() # Error if ReplaceMe() was redefined
237+
enddef
238+
239+
196240
Variable declarations with :var, :final and :const ~
197241
*vim9-declaration* *:var*
198242
Local variables need to be declared with `:var`. Local constants need to be
@@ -340,7 +384,7 @@ When using `function()` the resulting type is "func", a function with any
340384
number of arguments and any return type. The function can be defined later.
341385

342386

343-
Lamba using => instead of -> ~
387+
Lambda using => instead of -> ~
344388

345389
In legacy script there can be confusion between using "->" for a method call
346390
and for a lambda. Also, when a "{" is found the parser needs to figure out if
@@ -351,7 +395,7 @@ To avoid these problems Vim9 script uses a different syntax for a lambda,
351395
which is similar to Javascript: >
352396
var Lambda = (arg) => expression
353397
354-
No line break is allowed in the arguments of a lambda up to and includeing the
398+
No line break is allowed in the arguments of a lambda up to and including the
355399
"=>". This is OK: >
356400
filter(list, (k, v) =>
357401
v > 0)
@@ -369,9 +413,9 @@ Additionally, a lambda can contain statements in {}: >
369413
}
370414
NOT IMPLEMENTED YET
371415

372-
Note that the "{" must be followed by white space, otherwise it is assumed to
373-
be the start of a dictionary: >
374-
var Lambda = (arg) => {key: 42}
416+
To avoid the "{" of a dictionary literal to be recognized as a statement block
417+
wrap it in parenthesis: >
418+
var Lambda = (arg) => ({key: 42})
375419
376420
377421
Automatic line continuation ~
@@ -737,18 +781,24 @@ prefix and they do not need to exist (they can be deleted any time).
737781
Limitations ~
738782

739783
Local variables will not be visible to string evaluation. For example: >
740-
def EvalString(): list<string>
784+
def MapList(): list<string>
741785
var list = ['aa', 'bb', 'cc', 'dd']
742786
return range(1, 2)->map('list[v:val]')
743787
enddef
744788
745789
The map argument is a string expression, which is evaluated without the
746790
function scope. Instead, use a lambda: >
747-
def EvalString(): list<string>
791+
def MapList(): list<string>
748792
var list = ['aa', 'bb', 'cc', 'dd']
749-
return range(1, 2)->map({ _, v -> list[v] })
793+
return range(1, 2)->map(( _, v) => list[v])
750794
enddef
751795
796+
The same is true for commands that are not compiled, such as `:global`.
797+
For these the backtick expansion can be used. Example: >
798+
def Replace()
799+
var newText = 'blah'
800+
g/pattern/s/^/`=newText`/
801+
enddef
752802
753803
==============================================================================
754804

src/ex_cmds.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1680,7 +1680,7 @@ EXCMD(CMD_vimgrepadd, "vimgrepadd", ex_vimgrep,
16801680
EX_RANGE|EX_BANG|EX_NEEDARG|EX_EXTRA|EX_NOTRLCOM|EX_TRLBAR|EX_XFILE|EX_LOCK_OK,
16811681
ADDR_OTHER),
16821682
EXCMD(CMD_vim9script, "vim9script", ex_vim9script,
1683-
EX_CMDWIN|EX_LOCK_OK,
1683+
EX_WORD1|EX_CMDWIN|EX_LOCK_OK,
16841684
ADDR_NONE),
16851685
EXCMD(CMD_viusage, "viusage", ex_viusage,
16861686
EX_TRLBAR,

src/ex_docmd.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2594,7 +2594,7 @@ do_one_cmd(
25942594
// Set flag that any command was executed, used by ex_vim9script().
25952595
if (getline_equal(ea.getline, ea.cookie, getsourceline)
25962596
&& current_sctx.sc_sid > 0)
2597-
SCRIPT_ITEM(current_sctx.sc_sid)->sn_had_command = TRUE;
2597+
SCRIPT_ITEM(current_sctx.sc_sid)->sn_state = SN_STATE_HAD_COMMAND;
25982598

25992599
/*
26002600
* If the command just executed called do_cmdline(), any throw or ":return"

src/scriptfile.c

Lines changed: 18 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1320,43 +1320,27 @@ do_source(
13201320
if (sid > 0)
13211321
{
13221322
hashtab_T *ht;
1323-
int is_vim9 = si->sn_version == SCRIPT_VERSION_VIM9;
1323+
int todo;
1324+
hashitem_T *hi;
1325+
dictitem_T *di;
13241326

13251327
// loading the same script again
1326-
si->sn_had_command = FALSE;
1328+
si->sn_state = SN_STATE_RELOAD;
13271329
si->sn_version = 1;
13281330
current_sctx.sc_sid = sid;
13291331

1330-
// In Vim9 script all script-local variables are removed when reloading
1331-
// the same script. In legacy script they remain but "const" can be
1332-
// set again.
1332+
// Script-local variables remain but "const" can be set again.
1333+
// In Vim9 script variables will be cleared when "vim9script" is
1334+
// encountered without the "noclear" argument.
13331335
ht = &SCRIPT_VARS(sid);
1334-
if (is_vim9)
1335-
{
1336-
hashtab_free_contents(ht);
1337-
hash_init(ht);
1338-
}
1339-
else
1340-
{
1341-
int todo = (int)ht->ht_used;
1342-
hashitem_T *hi;
1343-
dictitem_T *di;
1344-
1345-
for (hi = ht->ht_array; todo > 0; ++hi)
1346-
if (!HASHITEM_EMPTY(hi))
1347-
{
1348-
--todo;
1349-
di = HI2DI(hi);
1350-
di->di_flags |= DI_FLAGS_RELOAD;
1351-
}
1352-
}
1353-
1354-
// old imports and script variables are no longer valid
1355-
free_imports_and_script_vars(sid);
1356-
1357-
// in Vim9 script functions are marked deleted
1358-
if (is_vim9)
1359-
delete_script_functions(sid);
1336+
todo = (int)ht->ht_used;
1337+
for (hi = ht->ht_array; todo > 0; ++hi)
1338+
if (!HASHITEM_EMPTY(hi))
1339+
{
1340+
--todo;
1341+
di = HI2DI(hi);
1342+
di->di_flags |= DI_FLAGS_RELOAD;
1343+
}
13601344
}
13611345
else
13621346
{
@@ -1390,8 +1374,10 @@ do_source(
13901374
fname_exp = vim_strsave(si->sn_name); // used for autocmd
13911375
if (ret_sid != NULL)
13921376
*ret_sid = current_sctx.sc_sid;
1377+
1378+
// Used to check script variable index is still valid.
1379+
si->sn_script_seq = current_sctx.sc_seq;
13931380
}
1394-
si->sn_script_seq = current_sctx.sc_seq;
13951381

13961382
# ifdef FEAT_PROFILE
13971383
if (do_profiling == PROF_YES)

src/structs.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1821,7 +1821,7 @@ typedef struct
18211821
int sn_last_block_id; // Unique ID for each script block
18221822

18231823
int sn_version; // :scriptversion
1824-
int sn_had_command; // TRUE if any command was executed
1824+
int sn_state; // SN_STATE_ values
18251825
char_u *sn_save_cpo; // 'cpo' value when :vim9script found
18261826

18271827
# ifdef FEAT_PROFILE
@@ -1845,6 +1845,10 @@ typedef struct
18451845
# endif
18461846
} scriptitem_T;
18471847

1848+
#define SN_STATE_NEW 0 // newly loaded script, nothing done
1849+
#define SN_STATE_RELOAD 1 // script loaded before, nothing done
1850+
#define SN_STATE_HAD_COMMAND 9 // a command was executed
1851+
18481852
// Struct passed through eval() functions.
18491853
// See EVALARG_EVALUATE for a fixed value with eval_flags set to EVAL_EVALUATE.
18501854
typedef struct {

src/testdir/test_vim9_script.vim

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1158,6 +1158,53 @@ def Run_Test_import_fails_on_command_line()
11581158
StopVimInTerminal(buf)
11591159
enddef
11601160

1161+
def Test_vim9script_reload_noclear()
1162+
var lines =<< trim END
1163+
vim9script noclear
1164+
g:loadCount += 1
1165+
var s:reloaded = 'init'
1166+
1167+
def Again(): string
1168+
return 'again'
1169+
enddef
1170+
1171+
if exists('s:loaded') | finish | endif
1172+
var s:loaded = true
1173+
1174+
var s:notReloaded = 'yes'
1175+
s:reloaded = 'first'
1176+
def g:Values(): list<string>
1177+
return [s:reloaded, s:notReloaded, Once()]
1178+
enddef
1179+
def g:CallAgain(): string
1180+
return Again()
1181+
enddef
1182+
1183+
def Once(): string
1184+
return 'once'
1185+
enddef
1186+
END
1187+
writefile(lines, 'XReloaded')
1188+
g:loadCount = 0
1189+
source XReloaded
1190+
assert_equal(1, g:loadCount)
1191+
assert_equal(['first', 'yes', 'once'], g:Values())
1192+
assert_equal('again', g:CallAgain())
1193+
source XReloaded
1194+
assert_equal(2, g:loadCount)
1195+
assert_equal(['init', 'yes', 'once'], g:Values())
1196+
assert_fails('call g:CallAgain()', 'E933:')
1197+
source XReloaded
1198+
assert_equal(3, g:loadCount)
1199+
assert_equal(['init', 'yes', 'once'], g:Values())
1200+
assert_fails('call g:CallAgain()', 'E933:')
1201+
1202+
delete('Xreloaded')
1203+
delfunc g:Values
1204+
delfunc g:CallAgain
1205+
unlet g:loadCount
1206+
enddef
1207+
11611208
def Test_vim9script_reload_import()
11621209
var lines =<< trim END
11631210
vim9script

src/version.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,8 @@ static char *(features[]) =
750750

751751
static int included_patches[] =
752752
{ /* Add new patch number below this line */
753+
/**/
754+
2222,
753755
/**/
754756
2221,
755757
/**/

src/vim9script.c

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,22 +32,43 @@ in_vim9script(void)
3232
void
3333
ex_vim9script(exarg_T *eap)
3434
{
35+
int sid = current_sctx.sc_sid;
3536
scriptitem_T *si;
3637

3738
if (!getline_equal(eap->getline, eap->cookie, getsourceline))
3839
{
3940
emsg(_(e_vim9script_can_only_be_used_in_script));
4041
return;
4142
}
42-
si = SCRIPT_ITEM(current_sctx.sc_sid);
43-
if (si->sn_had_command)
43+
44+
si = SCRIPT_ITEM(sid);
45+
if (si->sn_state == SN_STATE_HAD_COMMAND)
4446
{
4547
emsg(_(e_vim9script_must_be_first_command_in_script));
4648
return;
4749
}
50+
if (!IS_WHITE_OR_NUL(*eap->arg) && STRCMP(eap->arg, "noclear") != 0)
51+
{
52+
semsg(_(e_invarg2), eap->arg);
53+
return;
54+
}
55+
if (si->sn_state == SN_STATE_RELOAD && IS_WHITE_OR_NUL(*eap->arg))
56+
{
57+
hashtab_T *ht = &SCRIPT_VARS(sid);
58+
59+
// Reloading a script without the "noclear" argument: clear
60+
// script-local variables and functions.
61+
hashtab_free_contents(ht);
62+
hash_init(ht);
63+
delete_script_functions(sid);
64+
65+
// old imports and script variables are no longer valid
66+
free_imports_and_script_vars(sid);
67+
}
68+
si->sn_state = SN_STATE_HAD_COMMAND;
69+
4870
current_sctx.sc_version = SCRIPT_VERSION_VIM9;
4971
si->sn_version = SCRIPT_VERSION_VIM9;
50-
si->sn_had_command = TRUE;
5172

5273
if (STRCMP(p_cpo, CPO_VIM) != 0)
5374
{
@@ -719,6 +740,9 @@ free_all_script_vars(scriptitem_T *si)
719740
hash_init(ht);
720741

721742
ga_clear(&si->sn_var_vals);
743+
744+
// existing commands using script variable indexes are no longer valid
745+
si->sn_script_seq = current_sctx.sc_seq;
722746
}
723747

724748
/*

0 commit comments

Comments
 (0)