feat(:source, nvim_exec): support script-local variables

Based on #13143 (and #11507) with changes:

 - Omit script_type_E. Use sn_name == NULL to determine anon items.
 - Keep SID_STR. Used by anon :source for .lua files (no item).
 - Show SID in get_scriptname output (:verbose set).
 - Factor item creation into new_script_item.
 - Leave sc_seq = 0 (anon scripts don't re-use the same item when re-sourced).
 - Add tests for anon :source.

Co-authored-by: Vikram Pal <vikrampal659@gmail.com>
Co-authored-by: Justin M. Keyes <justinkz@gmail.com>
This commit is contained in:
Sean Dewar 2021-10-09 22:13:11 +01:00
parent 6b9cb665fa
commit d4ed51eb44
No known key found for this signature in database
GPG Key ID: 08CC2C83AD41B581
4 changed files with 75 additions and 24 deletions

View File

@ -1939,6 +1939,29 @@ static char_u *get_str_line(int c, void *cookie, int indent, bool do_concat)
return ga.ga_data; return ga.ga_data;
} }
/// Create a new script item and allocate script-local vars. @see new_script_vars
///
/// @param name File name of the script. NULL for anonymous :source.
/// @param[out] sid_out SID of the new item.
/// @return pointer to the created script item.
static scriptitem_T *new_script_item(char_u *const name, scid_T *const sid_out)
{
static scid_T last_current_SID = 0;
const scid_T sid = ++last_current_SID;
if (sid_out != NULL) {
*sid_out = sid;
}
ga_grow(&script_items, (int)(sid - script_items.ga_len));
while (script_items.ga_len < sid) {
script_items.ga_len++;
SCRIPT_ITEM(script_items.ga_len).sn_name = NULL;
SCRIPT_ITEM(script_items.ga_len).sn_prof_on = false;
}
SCRIPT_ITEM(sid).sn_name = name;
new_script_vars(sid); // Allocate the local script variables to use for this script.
return &SCRIPT_ITEM(sid);
}
static int source_using_linegetter(void *cookie, LineGetter fgetline, const char *traceback_name) static int source_using_linegetter(void *cookie, LineGetter fgetline, const char *traceback_name)
{ {
char_u *save_sourcing_name = sourcing_name; char_u *save_sourcing_name = sourcing_name;
@ -1955,7 +1978,7 @@ static int source_using_linegetter(void *cookie, LineGetter fgetline, const char
sourcing_lnum = 0; sourcing_lnum = 0;
const sctx_T save_current_sctx = current_sctx; const sctx_T save_current_sctx = current_sctx;
current_sctx.sc_sid = SID_STR; new_script_item(NULL, &current_sctx.sc_sid);
current_sctx.sc_seq = 0; current_sctx.sc_seq = 0;
current_sctx.sc_lnum = save_sourcing_lnum; current_sctx.sc_lnum = save_sourcing_lnum;
funccal_entry_T entry; funccal_entry_T entry;
@ -2036,7 +2059,6 @@ int do_source(char_u *fname, int check_other, int is_vimrc)
char_u *fname_exp; char_u *fname_exp;
char_u *firstline = NULL; char_u *firstline = NULL;
int retval = FAIL; int retval = FAIL;
static scid_T last_current_SID = 0;
static int last_current_SID_seq = 0; static int last_current_SID_seq = 0;
int save_debug_break_level = debug_break_level; int save_debug_break_level = debug_break_level;
scriptitem_T *si = NULL; scriptitem_T *si = NULL;
@ -2183,15 +2205,7 @@ int do_source(char_u *fname, int check_other, int is_vimrc)
} }
} }
if (current_sctx.sc_sid == 0) { if (current_sctx.sc_sid == 0) {
current_sctx.sc_sid = ++last_current_SID; si = new_script_item(fname_exp, &current_sctx.sc_sid);
ga_grow(&script_items, (int)(current_sctx.sc_sid - script_items.ga_len));
while (script_items.ga_len < current_sctx.sc_sid) {
script_items.ga_len++;
SCRIPT_ITEM(script_items.ga_len).sn_name = NULL;
SCRIPT_ITEM(script_items.ga_len).sn_prof_on = false;
}
si = &SCRIPT_ITEM(current_sctx.sc_sid);
si->sn_name = fname_exp;
fname_exp = vim_strsave(si->sn_name); // used for autocmd fname_exp = vim_strsave(si->sn_name); // used for autocmd
if (file_id_ok) { if (file_id_ok) {
si->file_id_valid = true; si->file_id_valid = true;
@ -2199,9 +2213,6 @@ int do_source(char_u *fname, int check_other, int is_vimrc)
} else { } else {
si->file_id_valid = false; si->file_id_valid = false;
} }
// Allocate the local script variables to use for this script.
new_script_vars(current_sctx.sc_sid);
} }
if (l_do_profiling == PROF_YES) { if (l_do_profiling == PROF_YES) {
@ -2375,16 +2386,21 @@ char_u *get_scriptname(LastSet last_set, bool *should_free)
case SID_LUA: case SID_LUA:
return (char_u *)_("Lua"); return (char_u *)_("Lua");
case SID_API_CLIENT: case SID_API_CLIENT:
vim_snprintf((char *)IObuff, IOSIZE, snprintf((char *)IObuff, IOSIZE, _("API client (channel id %" PRIu64 ")"), last_set.channel_id);
_("API client (channel id %" PRIu64 ")"),
last_set.channel_id);
return IObuff; return IObuff;
case SID_STR: case SID_STR:
return (char_u *)_("anonymous :source"); return (char_u *)_("anonymous :source");
default: default: {
char_u *const sname = SCRIPT_ITEM(last_set.script_ctx.sc_sid).sn_name;
if (sname == NULL) {
snprintf((char *)IObuff, IOSIZE, _("anonymous :source (script id %d)"),
last_set.script_ctx.sc_sid);
return IObuff;
}
*should_free = true; *should_free = true;
return home_replace_save(NULL, return home_replace_save(NULL, sname);
SCRIPT_ITEM(last_set.script_ctx.sc_sid).sn_name); }
} }
} }

View File

@ -335,7 +335,7 @@ EXTERN int garbage_collect_at_exit INIT(= false);
#define SID_WINLAYOUT -7 // changing window size #define SID_WINLAYOUT -7 // changing window size
#define SID_LUA -8 // for Lua scripts/chunks #define SID_LUA -8 // for Lua scripts/chunks
#define SID_API_CLIENT -9 // for API clients #define SID_API_CLIENT -9 // for API clients
#define SID_STR -10 // for sourcing a string #define SID_STR -10 // for sourcing a string with no script item
// Script CTX being sourced or was sourced to define the current function. // Script CTX being sourced or was sourced to define the current function.
EXTERN sctx_T current_sctx INIT(= { 0 COMMA 0 COMMA 0 }); EXTERN sctx_T current_sctx INIT(= { 0 COMMA 0 COMMA 0 });

View File

@ -89,7 +89,7 @@ describe('API', function()
it(':verbose set {option}?', function() it(':verbose set {option}?', function()
nvim('exec', 'set nowrap', false) nvim('exec', 'set nowrap', false)
eq('nowrap\n\tLast set from anonymous :source', eq('nowrap\n\tLast set from anonymous :source (script id 1)',
nvim('exec', 'verbose set wrap?', true)) nvim('exec', 'verbose set wrap?', true))
end) end)
@ -132,6 +132,29 @@ describe('API', function()
-- try no spaces before continuations to catch off-by-one error -- try no spaces before continuations to catch off-by-one error
nvim('exec', 'let ab = #{\n\\a: 98,\n"\\ b: 2\n\\}', false) nvim('exec', 'let ab = #{\n\\a: 98,\n"\\ b: 2\n\\}', false)
eq({a = 98}, request('nvim_eval', 'g:ab')) eq({a = 98}, request('nvim_eval', 'g:ab'))
-- Script scope (s:)
eq('ahoy! script-scoped varrrrr', nvim('exec', [[
let s:pirate = 'script-scoped varrrrr'
function! s:avast_ye_hades(s) abort
return a:s .. ' ' .. s:pirate
endfunction
echo <sid>avast_ye_hades('ahoy!')
]], true))
eq('ahoy! script-scoped varrrrr', nvim('exec', [[
let s:pirate = 'script-scoped varrrrr'
function! Avast_ye_hades(s) abort
return a:s .. ' ' .. s:pirate
endfunction
echo nvim_exec('echo Avast_ye_hades(''ahoy!'')', 1)
]], true))
eq('Vim(call):E5555: API call: Vim(echo):E121: Undefined variable: s:pirate',
pcall_err(request, 'nvim_exec', [[
let s:pirate = 'script-scoped varrrrr'
call nvim_exec('echo s:pirate', 1)
]], false))
end) end)
it('non-ASCII input', function() it('non-ASCII input', function()

View File

@ -25,11 +25,14 @@ describe(':source', function()
let b = #{ let b = #{
\ k: "v" \ k: "v"
"\ (o_o) "\ (o_o)
\ }]]) \ }
let s:s = 0zbeef.cafe
let c = s:s]])
command('source') command('source')
eq('2', meths.exec('echo a', true)) eq('2', meths.exec('echo a', true))
eq("{'k': 'v'}", meths.exec('echo b', true)) eq("{'k': 'v'}", meths.exec('echo b', true))
eq("0zBEEFCAFE", meths.exec('echo c', true))
exec('set cpoptions+=C') exec('set cpoptions+=C')
eq('Vim(let):E15: Invalid expression: #{', exc_exec('source')) eq('Vim(let):E15: Invalid expression: #{', exc_exec('source'))
@ -43,7 +46,11 @@ describe(':source', function()
let b = #{ let b = #{
"\ (>_<) "\ (>_<)
\ K: "V" \ K: "V"
\ }]]) \ }
function! s:C() abort
return expand("<SID>") .. "C()"
endfunction
let D = {-> s:C()}]])
-- Source the 2nd line only -- Source the 2nd line only
feed('ggjV') feed('ggjV')
@ -55,6 +62,11 @@ describe(':source', function()
feed_command(':source') feed_command(':source')
eq('4', meths.exec('echo a', true)) eq('4', meths.exec('echo a', true))
eq("{'K': 'V'}", meths.exec('echo b', true)) eq("{'K': 'V'}", meths.exec('echo b', true))
eq("<SNR>3_C()", meths.exec('echo D()', true))
-- Source last line only
feed_command(':$source')
eq('Vim(echo):E117: Unknown function: s:C', exc_exec('echo D()'))
exec('set cpoptions+=C') exec('set cpoptions+=C')
eq('Vim(let):E15: Invalid expression: #{', exc_exec("'<,'>source")) eq('Vim(let):E15: Invalid expression: #{', exc_exec("'<,'>source"))