diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index c3cde2f15a..c421d3991c 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -1939,6 +1939,29 @@ static char_u *get_str_line(int c, void *cookie, int indent, bool do_concat) 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) { 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; const sctx_T save_current_sctx = current_sctx; - current_sctx.sc_sid = SID_STR; + new_script_item(NULL, ¤t_sctx.sc_sid); current_sctx.sc_seq = 0; current_sctx.sc_lnum = save_sourcing_lnum; 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 *firstline = NULL; int retval = FAIL; - static scid_T last_current_SID = 0; static int last_current_SID_seq = 0; int save_debug_break_level = debug_break_level; 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) { - current_sctx.sc_sid = ++last_current_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; + si = new_script_item(fname_exp, ¤t_sctx.sc_sid); fname_exp = vim_strsave(si->sn_name); // used for autocmd if (file_id_ok) { si->file_id_valid = true; @@ -2199,9 +2213,6 @@ int do_source(char_u *fname, int check_other, int is_vimrc) } else { 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) { @@ -2375,16 +2386,21 @@ char_u *get_scriptname(LastSet last_set, bool *should_free) case SID_LUA: return (char_u *)_("Lua"); case SID_API_CLIENT: - vim_snprintf((char *)IObuff, IOSIZE, - _("API client (channel id %" PRIu64 ")"), - last_set.channel_id); + snprintf((char *)IObuff, IOSIZE, _("API client (channel id %" PRIu64 ")"), last_set.channel_id); return IObuff; case SID_STR: 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; - return home_replace_save(NULL, - SCRIPT_ITEM(last_set.script_ctx.sc_sid).sn_name); + return home_replace_save(NULL, sname); + } } } diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 182e88083a..435949379b 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -335,7 +335,7 @@ EXTERN int garbage_collect_at_exit INIT(= false); #define SID_WINLAYOUT -7 // changing window size #define SID_LUA -8 // for Lua scripts/chunks #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. EXTERN sctx_T current_sctx INIT(= { 0 COMMA 0 COMMA 0 }); diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 6b644ed519..f878a50a26 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -89,7 +89,7 @@ describe('API', function() it(':verbose set {option}?', function() 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)) end) @@ -132,6 +132,29 @@ describe('API', function() -- try no spaces before continuations to catch off-by-one error nvim('exec', 'let ab = #{\n\\a: 98,\n"\\ b: 2\n\\}', false) 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 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) it('non-ASCII input', function() diff --git a/test/functional/ex_cmds/source_spec.lua b/test/functional/ex_cmds/source_spec.lua index bdf6ae76d1..8077b22e56 100644 --- a/test/functional/ex_cmds/source_spec.lua +++ b/test/functional/ex_cmds/source_spec.lua @@ -25,11 +25,14 @@ describe(':source', function() let b = #{ \ k: "v" "\ (o_o) - \ }]]) + \ } + let s:s = 0zbeef.cafe + let c = s:s]]) command('source') eq('2', meths.exec('echo a', true)) eq("{'k': 'v'}", meths.exec('echo b', true)) + eq("0zBEEFCAFE", meths.exec('echo c', true)) exec('set cpoptions+=C') eq('Vim(let):E15: Invalid expression: #{', exc_exec('source')) @@ -43,7 +46,11 @@ describe(':source', function() let b = #{ "\ (>_<) \ K: "V" - \ }]]) + \ } + function! s:C() abort + return expand("") .. "C()" + endfunction + let D = {-> s:C()}]]) -- Source the 2nd line only feed('ggjV') @@ -55,6 +62,11 @@ describe(':source', function() feed_command(':source') eq('4', meths.exec('echo a', true)) eq("{'K': 'V'}", meths.exec('echo b', true)) + eq("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') eq('Vim(let):E15: Invalid expression: #{', exc_exec("'<,'>source"))