Skip to content

Commit

Permalink
feat(:source): reuse SID of sourced buffer
Browse files Browse the repository at this point in the history
Associate a unique SID for each anonymously :sourced Vim script buffer.
This means sourcing the same buffer will retain the same script-local variables.

Use anonymous :source for memory_usage_spec.lua's "function captures lvars"
test. Use a buffer :source, as the test requires the same SID to be used.
Remove "s:defined_func" logic; it isn't needed, and removing it matches Vim.
  • Loading branch information
seandewar committed Mar 30, 2022
1 parent cddd9a3 commit 48a7183
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 34 deletions.
36 changes: 20 additions & 16 deletions src/nvim/ex_cmds2.c
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ typedef struct scriptitem_S {
} scriptitem_T;

static scid_T last_current_SID = 0;
static int last_current_SID_seq = 0;
static PMap(scid_T) script_items = MAP_INIT;

/// Sorted growarray of SIDs associated with a :sourced script file.
Expand Down Expand Up @@ -1852,7 +1853,8 @@ scid_T script_new_sid(char_u *const fname)
return 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,
const scid_T sid)
{
char_u *save_sourcing_name = sourcing_name;
linenr_T save_sourcing_lnum = sourcing_lnum;
Expand All @@ -1868,15 +1870,13 @@ static int source_using_linegetter(void *cookie, LineGetter fgetline, const char
sourcing_lnum = 0;

const sctx_T save_current_sctx = current_sctx;
if (current_sctx.sc_sid != SID_LUA) {
current_sctx.sc_sid = ++last_current_SID;
}
current_sctx.sc_seq = 0;
current_sctx.sc_sid = sid;
current_sctx.sc_seq = sid > 0 ? ++last_current_SID_seq : 0;
current_sctx.sc_lnum = save_sourcing_lnum;
funccal_entry_T entry;
save_funccal(&entry);
int retval = do_cmdline(NULL, fgetline, cookie,
DOCMD_VERBOSE | DOCMD_NOWAIT | DOCMD_REPEAT);
const int retval = do_cmdline(NULL, fgetline, cookie,
DOCMD_VERBOSE | DOCMD_NOWAIT | DOCMD_REPEAT);
sourcing_lnum = save_sourcing_lnum;
sourcing_name = save_sourcing_name;
current_sctx = save_current_sctx;
Expand All @@ -1887,6 +1887,8 @@ static int source_using_linegetter(void *cookie, LineGetter fgetline, const char
static void cmd_source_buffer(const exarg_T *const eap)
FUNC_ATTR_NONNULL_ALL
{
static Map(handle_T, scid_T) buf_sids = MAP_INIT;

if (curbuf == NULL) {
return;
}
Expand All @@ -1907,13 +1909,16 @@ static void cmd_source_buffer(const exarg_T *const eap)
.buf = ga.ga_data,
.offset = 0,
};
if (curbuf->b_fname
&& path_with_extension((const char *)curbuf->b_fname, "lua")) {
nlua_source_using_linegetter(get_str_line, (void *)&cookie,
":source (no file)");
if (curbuf->b_fname && path_with_extension((const char *)curbuf->b_fname, "lua")) {
nlua_source_using_linegetter(get_str_line, (void *)&cookie, ":source (no file)");
} else {
source_using_linegetter((void *)&cookie, get_str_line,
":source (no file)");
scid_T sid = map_get(handle_T, scid_T)(&buf_sids, curbuf->handle);
if (sid == 0) {
// First time sourcing this buffer, use a new SID for it.
sid = ++last_current_SID;
map_put(handle_T, scid_T)(&buf_sids, curbuf->handle, sid);
}
source_using_linegetter((void *)&cookie, get_str_line, ":source (no file)", sid);
}
ga_clear(&ga);
}
Expand All @@ -1927,7 +1932,8 @@ int do_source_str(const char *cmd, const char *traceback_name)
.buf = (char_u *)cmd,
.offset = 0,
};
return source_using_linegetter((void *)&cookie, get_str_line, traceback_name);
const scid_T sid = current_sctx.sc_sid == SID_LUA ? SID_LUA : ++last_current_SID;
return source_using_linegetter((void *)&cookie, get_str_line, traceback_name, sid);
}

/// When fname is a 'lua' file nlua_exec_file() is invoked to source it.
Expand Down Expand Up @@ -2194,8 +2200,6 @@ int do_source(char *fname, int check_other, int is_vimrc)
static scriptitem_T *new_file_sctx(char_u *fname, sctx_T *ret_sctx)
FUNC_ATTR_NONNULL_ARG(1)
{
static int last_current_SID_seq = 0;

sctx_T script_sctx = { .sc_seq = ++last_current_SID_seq,
.sc_lnum = 0,
.sc_sid = 0 };
Expand Down
1 change: 1 addition & 0 deletions src/nvim/map.c
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ MAP_IMPL(uint64_t, ssize_t, SSIZE_INITIALIZER)
MAP_IMPL(uint64_t, uint64_t, DEFAULT_INITIALIZER)
MAP_IMPL(uint32_t, uint32_t, DEFAULT_INITIALIZER)
MAP_IMPL(handle_T, ptr_t, DEFAULT_INITIALIZER)
MAP_IMPL(handle_T, scid_T, DEFAULT_INITIALIZER)
MAP_IMPL(scid_T, ptr_t, DEFAULT_INITIALIZER)
#define MSGPACK_HANDLER_INITIALIZER { .fn = NULL, .fast = false }
MAP_IMPL(String, MsgpackRpcRequestHandler, MSGPACK_HANDLER_INITIALIZER)
Expand Down
1 change: 1 addition & 0 deletions src/nvim/map.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ MAP_DECLS(uint64_t, uint64_t)
MAP_DECLS(uint32_t, uint32_t)

MAP_DECLS(handle_T, ptr_t)
MAP_DECLS(handle_T, scid_T)
MAP_DECLS(scid_T, ptr_t)
MAP_DECLS(String, MsgpackRpcRequestHandler)
MAP_DECLS(HlEntry, int)
Expand Down
35 changes: 33 additions & 2 deletions test/functional/ex_cmds/source_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -64,16 +64,47 @@ describe(':source', function()
feed_command(':source')
eq('4', meths.exec('echo a', true))
eq("{'K': 'V'}", meths.exec('echo b', true))
eq("<SNR>3_C()", meths.exec('echo D()', true))
eq("<SNR>1_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()'))
eq("<SNR>1_C()", meths.exec('echo D()', true))

exec('set cpoptions+=C')
eq('Vim(let):E15: Invalid expression: #{', exc_exec("'<,'>source"))
end)

it('current buffer reuses SID', function()
insert [[
" also shouldn't cause a redefinition error when `:source`ing the
" same buffer twice (script context uses a new sequence number)
func s:Foo()
endfunc
let id = expand("<SID>")
]]
command('source')
eq("<SNR>1_", eval('g:id'))
command('source')
eq("<SNR>1_", eval('g:id'))

-- Ensure a new buffer has a different SID
command('new')
insert [[
let id = expand("<SID>")
]]
command('source')
eq("<SNR>2_", eval('g:id'))
command('source')
eq("<SNR>2_", eval('g:id'))

command('wincmd p')
command('source')
eq("<SNR>1_", eval('g:id'))

-- Scripts should be anonymous
eq("", exec_capture(':scriptnames'))
end)

it('does not break if current buffer is modified while sourced', function()
insert [[
bwipeout!
Expand Down
24 changes: 8 additions & 16 deletions test/functional/legacy/memory_usage_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ local source = helpers.source
local poke_eventloop = helpers.poke_eventloop
local uname = helpers.uname
local load_adjust = helpers.load_adjust
local write_file = helpers.write_file
local insert = helpers.insert
local isCI = helpers.isCI

local function isasan()
Expand Down Expand Up @@ -85,12 +85,6 @@ setmetatable(monitor_memory_usage,
end})

describe('memory usage', function()
local tmpfile = 'X_memory_usage'

after_each(function()
os.remove(tmpfile)
end)

local function check_result(tbl, status, result)
if not status then
print('')
Expand Down Expand Up @@ -143,23 +137,21 @@ describe('memory usage', function()
it('function capture lvars', function()
local pid = eval('getpid()')
local before = monitor_memory_usage(pid)
write_file(tmpfile, [[
if !exists('s:defined_func')
func s:f()
let x = l:
endfunc
endif
let s:defined_func = 1
-- :source from a buffer so that the same SID is used.
insert([[
func s:f()
let x = l:
endfunc
for _ in range(10000)
call s:f()
endfor
]])
feed_command('source '..tmpfile)
feed_command("source")
poke_eventloop()
local after = monitor_memory_usage(pid)
for _ = 1, 3 do
-- TODO: check_result fails if command() is used here. Why? #16064
feed_command('source '..tmpfile)
feed_command("source")
poke_eventloop()
end
local last = monitor_memory_usage(pid)
Expand Down

0 comments on commit 48a7183

Please sign in to comment.