Skip to content

Commit

Permalink
feat(:source, nvim_exec): :verbose line numbers for anon vim script
Browse files Browse the repository at this point in the history
Support showing line numbers for Vim script sourced anonymously via :source (no
args) and nvim_exec. For buffer :sources from a range, adjust the line number
from the line number of the range start.

Do not offset the line number shown from anonymous scripts with the line number
of an enclosing script. For example, this could produce incorrect numbers in
situations where we have escaped NLs:

```vim
echo "hi"
call nvim_exec("\n\n\nset cul", 1)
verbose set cul?
```
Would print "line 6" (preferably "line 5"), but it's actually on line 2...

It's better to just show the line number of the anonymous script only; :verbose
will only show the name of that one anyway.

Fix the order of nlua_set_sctx in set_option_sctx_idx; the current order will
erroneously offset the line number from the Lua script... Added a test.
  • Loading branch information
seandewar committed Mar 29, 2022
1 parent 1fd5ded commit 4426f48
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 22 deletions.
25 changes: 17 additions & 8 deletions src/nvim/ex_cmds2.c
Original file line number Diff line number Diff line change
Expand Up @@ -1786,6 +1786,7 @@ static FILE *fopen_noinh_readbin(char *filename)
typedef struct {
char_u *buf;
size_t offset;
linenr_T sourcing_lnum;
} GetStrLineCookie;

/// Get one full line from a sourced string (in-memory, no file).
Expand All @@ -1795,7 +1796,7 @@ typedef struct {
/// some error.
static char_u *get_str_line(int c, void *cookie, int indent, bool do_concat)
{
GetStrLineCookie *p = cookie;
GetStrLineCookie *const p = cookie;
if (STRLEN(p->buf) <= p->offset) {
return NULL;
}
Expand All @@ -1804,13 +1805,15 @@ static char_u *get_str_line(int c, void *cookie, int indent, bool do_concat)
garray_T ga;
ga_init(&ga, sizeof(char_u), 400);
ga_concat_len(&ga, (const char *)line, (size_t)(eol - line));
sourcing_lnum = ++p->sourcing_lnum;
if (do_concat && vim_strchr(p_cpo, CPO_CONCAT) == NULL) {
while (eol[0] != NUL) {
line = eol + 1;
const char_u *const next_eol = skip_to_newline(line);
if (!concat_continued_line(&ga, 400, line, (size_t)(next_eol - line))) {
break;
}
p->sourcing_lnum++;
eol = next_eol;
}
}
Expand Down Expand Up @@ -1873,7 +1876,7 @@ static int source_using_linegetter(void *cookie, LineGetter fgetline, const char
const sctx_T save_current_sctx = current_sctx;
current_sctx.sc_sid = sid;
current_sctx.sc_seq = sid > 0 ? ++last_current_SID_seq : 0;
current_sctx.sc_lnum = save_sourcing_lnum;
current_sctx.sc_lnum = 0;
funccal_entry_T entry;
save_funccal(&entry);
const int retval = do_cmdline(NULL, fgetline, cookie,
Expand Down Expand Up @@ -1906,21 +1909,23 @@ static void cmd_source_buffer(const exarg_T *const eap)
ga_append(&ga, NL);
}
((char_u *)ga.ga_data)[ga.ga_len - 1] = NUL;
const GetStrLineCookie cookie = {
GetStrLineCookie cookie = {
.buf = ga.ga_data,
.offset = 0,
.sourcing_lnum = 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)");
nlua_source_using_linegetter(get_str_line, &cookie, ":source (no file)");
} else {
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 = sid_new(false);
map_put(handle_T, scid_T)(&buf_sids, curbuf->handle, sid);
}
source_using_linegetter((void *)&cookie, get_str_line, ":source (no file)", sid);
cookie.sourcing_lnum = eap->line1 - 1;
source_using_linegetter(&cookie, get_str_line, ":source (no file)", sid);
}
ga_clear(&ga);
}
Expand All @@ -1933,6 +1938,7 @@ int do_source_str(const char *cmd, const char *traceback_name)
GetStrLineCookie cookie = {
.buf = (char_u *)cmd,
.offset = 0,
.sourcing_lnum = 0,
};
const scid_T sid = sid_new(sid_is_from_lua(current_sctx.sc_sid));
return source_using_linegetter(&cookie, get_str_line, traceback_name, sid);
Expand Down Expand Up @@ -2358,9 +2364,12 @@ void free_scriptnames(void)

linenr_T get_sourced_lnum(LineGetter fgetline, void *cookie)
{
return fgetline == getsourceline
? ((struct source_cookie *)cookie)->sourcing_lnum
: sourcing_lnum;
if (fgetline == getsourceline) {
return ((struct source_cookie *)cookie)->sourcing_lnum;
} else if (fgetline == get_str_line) {
return ((GetStrLineCookie *)cookie)->sourcing_lnum;
}
return sourcing_lnum;
}


Expand Down
4 changes: 2 additions & 2 deletions src/nvim/option.c
Original file line number Diff line number Diff line change
Expand Up @@ -3887,15 +3887,15 @@ static void set_option_sctx_idx(int opt_idx, int opt_flags, sctx_T script_ctx)
{
int both = (opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0;
int indir = (int)options[opt_idx].indir;
nlua_set_sctx(&script_ctx);
const LastSet last_set = {
LastSet last_set = {
.script_ctx = {
script_ctx.sc_sid,
script_ctx.sc_seq,
script_ctx.sc_lnum + sourcing_lnum
},
current_channel_id
};
nlua_set_sctx(&last_set.script_ctx);

// Remember where the option was set. For local options need to do that
// in the buffer or window structure.
Expand Down
71 changes: 65 additions & 6 deletions test/functional/api/vim_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ local NIL = helpers.NIL
local clear, nvim, eq, neq = helpers.clear, helpers.nvim, helpers.eq, helpers.neq
local command = helpers.command
local exec = helpers.exec
local exec_capture = helpers.exec_capture
local eval = helpers.eval
local expect = helpers.expect
local funcs = helpers.funcs
Expand Down Expand Up @@ -92,8 +93,8 @@ describe('API', function()

it(':verbose set {option}?', function()
nvim('exec', 'set nowrap', false)
eq('nowrap\n\tLast set from anonymous :source (script id 1)',
nvim('exec', 'verbose set wrap?', true))
eq('nowrap\n\tLast set from anonymous :source (script id 1) line 1',
nvim('exec', 'verbose set wrap?', true))
end)

it('multiline input', function()
Expand Down Expand Up @@ -240,13 +241,13 @@ describe('API', function()
local sourcing_fname = tmpname()
write_file(sourcing_fname, 'call nvim_exec("source '..fname..'", v:false)\n')
meths.exec('set verbose=2', false)
local traceback_output = 'line 0: sourcing "'..sourcing_fname..'"\n'..
'line 0: sourcing "'..fname..'"\n'..
local traceback_output = 'line 1: sourcing "'..sourcing_fname..'"\n'..
'line 1: sourcing "'..fname..'"\n'..
'hello\n'..
'finished sourcing '..fname..'\n'..
'continuing in nvim_exec() called at '..sourcing_fname..':1\n'..
'finished sourcing '..sourcing_fname..'\n'..
'continuing in nvim_exec() called at nvim_exec():0'
'continuing in nvim_exec() called at nvim_exec():1'
eq(traceback_output,
meths.exec('call nvim_exec("source '..sourcing_fname..'", v:false)', true))
os.remove(fname)
Expand Down Expand Up @@ -279,7 +280,7 @@ describe('API', function()
]]}
end)

it('does\'t display messages when output=true', function()
it('doesn\'t display messages when output=true', function()
local screen = Screen.new(40, 8)
screen:attach()
screen:set_default_attr_ids({
Expand All @@ -297,6 +298,64 @@ describe('API', function()
|
]]}
end)

it(':verbose shows correct line numbers', function()
-- Should work like test_vimscript.vim's Test_function_defined_line
exec [[
" F1
func F1()
" F2
func F2()
"
"
"
return
endfunc
" F3
execute "func F3()\n\n\n\nreturn\nendfunc"
" F4
execute "func F4()\n
\\n
\\n
\\n
\return\n
\endfunc"
endfunc
" F5
execute "func F5()\n\n\n\nreturn\nendfunc"
" F6
execute "func F6()\n
\\n
\\n
\\n
\return\n
\endfunc"
call F1()
]]
matches('line 2\n', exec_capture('verbose func F1'))
matches('line 4\n', exec_capture('verbose func F2'))
matches('line 11\n', exec_capture('verbose func F3'))
matches('line 13\n', exec_capture('verbose func F4'))
matches('line 21\n', exec_capture('verbose func F5'))
matches('line 23\n', exec_capture('verbose func F6'))

-- Should only show line numbers of the relevant script.
-- (from within the inner nvim_exec, in this case)
matches('line 1$', exec_capture [[
" intentional comment
call nvim_exec("set cursorline\n\nset hlsearch", 0)
verbose set cursorline?
]])
matches('line 3$', exec_capture('verbose set hlsearch?'))

-- Works from an anonymous Lua context with 'verbose' >= 1.
exec('set verbose=1')
exec_lua [[
-- intentional comment
vim.api.nvim_exec("set cursorline", false)
]]
matches('line 1$', exec_capture('set cursorline?'))
end)
end)

describe('nvim_command', function()
Expand Down
12 changes: 10 additions & 2 deletions test/functional/ex_cmds/source_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ local exec_lua = helpers.exec_lua
local eval = helpers.eval
local exec_capture = helpers.exec_capture
local neq = helpers.neq
local matches = helpers.matches

describe(':source', function()
before_each(function()
Expand All @@ -26,7 +27,7 @@ describe(':source', function()
\ k: "v"
"\ (o_o)
\ }
let c = expand("<SID>")
let c = expand("<SID>") | set cursorline
let s:s = 0zbeef.cafe
let d = s:s]])

Expand All @@ -35,6 +36,7 @@ describe(':source', function()
eq("{'k': 'v'}", meths.exec('echo b', true))
eq("<SNR>1_", meths.exec('echo c', true))
eq("0zBEEFCAFE", meths.exec('echo d', true))
matches('line 6$', exec_capture('verbose set cursorline?'))

exec('set cpoptions+=C')
eq('Vim(let):E15: Invalid expression: #{', exc_exec('source'))
Expand All @@ -43,7 +45,7 @@ describe(':source', function()
it('selection in current buffer', function()
insert([[
let a = 2
let a = 3
let a = 3 | set cursorline
let a = 4
let b = #{
"\ (>_<)
Expand All @@ -58,13 +60,19 @@ describe(':source', function()
feed('ggjV')
feed_command(':source')
eq('3', meths.exec('echo a', true))
matches('line 2$', exec_capture('verbose set cursorline?'))

-- Disable 'cursorline' to make sure the LastSet line nr is changed.
feed_command('set nocursorline')
eq('nocursorline', exec_capture('verbose set cursorline?'))

-- Source from 2nd line to end of file
feed('ggjVG')
feed_command(':source')
eq('4', meths.exec('echo a', true))
eq("{'K': 'V'}", meths.exec('echo b', true))
eq("<SNR>1_C()", meths.exec('echo D()', true))
matches('line 2$', exec_capture('verbose set cursorline?'))

-- Source last line only
feed_command(':$source')
Expand Down
8 changes: 4 additions & 4 deletions test/functional/legacy/assert_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,10 @@ describe('assert function:', function()
call assert_true('', 'file two')
]])
expected_errors({
"nvim_exec(): equal assertion failed: Expected 1 but got 100",
"nvim_exec(): true assertion failed: Expected False but got 'true'",
"nvim_exec(): false assertion failed: Expected True but got 'false'",
"nvim_exec(): file two: Expected True but got ''",
"nvim_exec() line 1: equal assertion failed: Expected 1 but got 100",
"nvim_exec() line 2: true assertion failed: Expected False but got 'true'",
"nvim_exec() line 3: false assertion failed: Expected True but got 'false'",
"nvim_exec() line 1: file two: Expected True but got ''",
})
end)
end)
Expand Down

0 comments on commit 4426f48

Please sign in to comment.