Merge #15671 backport: :source and nvim_exec fixes

This commit is contained in:
Justin M. Keyes 2021-09-15 06:10:48 -07:00 committed by GitHub
commit 942b16adf7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 180 additions and 73 deletions

View File

@ -1446,15 +1446,29 @@ void getvcols(win_T *wp, pos_T *pos1, pos_T *pos2, colnr_T *left,
/// skipwhite: skip over ' ' and '\t'.
///
/// @param[in] q String to skip in.
/// @param[in] p String to skip in.
///
/// @return Pointer to character after the skipped whitespace.
char_u *skipwhite(const char_u *q)
char_u *skipwhite(const char_u *const p)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
FUNC_ATTR_NONNULL_RET
{
const char_u *p = q;
while (ascii_iswhite(*p)) {
return skipwhite_len(p, STRLEN(p));
}
/// Like `skipwhite`, but skip up to `len` characters.
/// @see skipwhite
///
/// @param[in] p String to skip in.
/// @param[in] len Max length to skip.
///
/// @return Pointer to character after the skipped whitespace, or the `len`-th
/// character in the string.
char_u *skipwhite_len(const char_u *p, size_t len)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
FUNC_ATTR_NONNULL_RET
{
for (; len > 0 && ascii_iswhite(*p); len--) {
p++;
}
return (char_u *)p;
@ -1600,6 +1614,18 @@ char_u* skiptowhite_esc(char_u *p) {
return p;
}
/// Skip over text until '\n' or NUL.
///
/// @param[in] p Text to skip over.
///
/// @return Pointer to the next '\n' or NUL character.
char_u *skip_to_newline(const char_u *const p)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
FUNC_ATTR_NONNULL_RET
{
return (char_u *)xstrchrnul((const char *)p, NL);
}
/// Gets a number from a string and skips over it, signalling overflow.
///
/// @param[out] pp A pointer to a pointer to char_u.

View File

@ -2635,44 +2635,42 @@ static void cmd_source(char_u *fname, exarg_T *eap)
}
}
/// Concatenate VimL line if it starts with a line continuation into a growarray
/// (excluding the continuation chars and leading whitespace)
///
/// @note Growsize of the growarray may be changed to speed up concatenations!
///
/// @param ga the growarray to append to
/// @param init_growsize the starting growsize value of the growarray
/// @param p pointer to the beginning of the line to consider
/// @param len the length of this line
///
/// @return true if this line did begin with a continuation (the next line
/// should also be considered, if it exists); false otherwise
static bool concat_continued_line(garray_T *const ga, const int init_growsize,
const char_u *const p, size_t len)
FUNC_ATTR_NONNULL_ALL
{
const char_u *const line = skipwhite_len(p, len);
len -= (size_t)(line - p);
// Skip lines starting with '\" ', concat lines starting with '\'
if (len >= 3 && STRNCMP(line, "\"\\ ", 3) == 0) {
return true;
} else if (len == 0 || line[0] != '\\') {
return false;
}
if (ga->ga_len > init_growsize) {
ga_set_growsize(ga, MAX(ga->ga_len, 8000));
}
ga_concat_len(ga, (const char *)line + 1, len - 1);
return true;
}
typedef struct {
linenr_T curr_lnum;
const linenr_T final_lnum;
} GetBufferLineCookie;
/// Get one line from the current selection in the buffer.
/// Called by do_cmdline() when it's called from cmd_source_buffer().
///
/// @return pointer to allocated line, or NULL for end-of-file or
/// some error.
static char_u *get_buffer_line(int c, void *cookie, int indent, bool do_concat)
{
GetBufferLineCookie *p = cookie;
if (p->curr_lnum > p->final_lnum) {
return NULL;
}
char_u *curr_line = ml_get(p->curr_lnum);
p->curr_lnum++;
return (char_u *)xstrdup((const char *)curr_line);
}
static void cmd_source_buffer(const exarg_T *eap)
FUNC_ATTR_NONNULL_ALL
{
GetBufferLineCookie cookie = {
.curr_lnum = eap->line1,
.final_lnum = eap->line2,
};
if (curbuf != NULL && curbuf->b_fname
&& path_with_extension((const char *)curbuf->b_fname, "lua")) {
nlua_source_using_linegetter(get_buffer_line, (void *)&cookie,
":source (no file)");
} else {
source_using_linegetter((void *)&cookie, get_buffer_line,
":source (no file)");
}
}
/// ":source" and associated commands.
///
/// @return address holding the next breakpoint line for a source cookie
@ -2725,17 +2723,27 @@ typedef struct {
static char_u *get_str_line(int c, void *cookie, int indent, bool do_concat)
{
GetStrLineCookie *p = cookie;
size_t i = p->offset;
if (strlen((char *)p->buf) <= p->offset) {
if (STRLEN(p->buf) <= p->offset) {
return NULL;
}
while (!(p->buf[i] == '\n' || p->buf[i] == '\0')) {
i++;
const char_u *line = p->buf + p->offset;
const char_u *eol = skip_to_newline(line);
garray_T ga;
ga_init(&ga, sizeof(char_u), 400);
ga_concat_len(&ga, (const char *)line, (size_t)(eol - line));
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;
}
eol = next_eol;
}
}
size_t line_length = i - p->offset;
char_u *buf = xmemdupz(p->buf + p->offset, line_length);
p->offset = i + 1;
return buf;
ga_append(&ga, NUL);
p->offset = (size_t)(eol - p->buf) + 1;
return ga.ga_data;
}
static int source_using_linegetter(void *cookie,
@ -2770,6 +2778,40 @@ static int source_using_linegetter(void *cookie,
return retval;
}
static void cmd_source_buffer(const exarg_T *const eap)
FUNC_ATTR_NONNULL_ALL
{
if (curbuf == NULL) {
return;
}
garray_T ga;
ga_init(&ga, sizeof(char_u), 400);
const linenr_T final_lnum = eap->line2;
// Copy the contents to be executed.
for (linenr_T curr_lnum = eap->line1; curr_lnum <= final_lnum; curr_lnum++) {
// Adjust growsize to current length to speed up concatenating many lines.
if (ga.ga_len > 400) {
ga_set_growsize(&ga, MAX(ga.ga_len, 8000));
}
ga_concat(&ga, ml_get(curr_lnum));
ga_append(&ga, NL);
}
((char_u *)ga.ga_data)[ga.ga_len - 1] = NUL;
const GetStrLineCookie cookie = {
.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)");
} else {
source_using_linegetter((void *)&cookie, get_str_line,
":source (no file)");
}
ga_clear(&ga);
}
/// Executes lines in `src` as Ex commands.
///
/// @see do_source()
@ -3227,26 +3269,11 @@ char_u *getsourceline(int c, void *cookie, int indent, bool do_concat)
ga_init(&ga, (int)sizeof(char_u), 400);
ga_concat(&ga, line);
if (*p == '\\') {
ga_concat(&ga, p + 1);
}
for (;; ) {
while (sp->nextline != NULL
&& concat_continued_line(&ga, 400, sp->nextline,
STRLEN(sp->nextline))) {
xfree(sp->nextline);
sp->nextline = get_one_sourceline(sp);
if (sp->nextline == NULL) {
break;
}
p = skipwhite(sp->nextline);
if (*p == '\\') {
// Adjust the growsize to the current length to speed up
// concatenating many lines.
if (ga.ga_len > 400) {
ga_set_growsize(&ga, (ga.ga_len > 8000) ? 8000 : ga.ga_len);
}
ga_concat(&ga, p + 1);
} else if (p[0] != '"' || p[1] != '\\' || p[2] != ' ') {
break;
}
}
ga_append(&ga, NUL);
xfree(line);

View File

@ -115,6 +115,19 @@ describe('API', function()
nvim('exec','autocmd BufAdd * :let x1 = "Hello"', false)
nvim('command', 'new foo')
eq('Hello', request('nvim_eval', 'g:x1'))
-- Line continuations
nvim('exec', [[
let abc = #{
\ a: 1,
"\ b: 2,
\ c: 3
\ }]], false)
eq({a = 1, c = 3}, request('nvim_eval', 'g:abc'))
-- 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'))
end)
it('non-ASCII input', function()

View File

@ -8,6 +8,8 @@ local feed = helpers.feed
local feed_command = helpers.feed_command
local write_file = helpers.write_file
local exec = helpers.exec
local exc_exec = helpers.exc_exec
local exec_lua = helpers.exec_lua
local eval = helpers.eval
local exec_capture = helpers.exec_capture
local neq = helpers.neq
@ -18,16 +20,30 @@ describe(':source', function()
end)
it('current buffer', function()
insert('let a = 2')
insert([[
let a = 2
let b = #{
\ k: "v"
"\ (o_o)
\ }]])
command('source')
eq('2', meths.exec('echo a', true))
eq("{'k': 'v'}", meths.exec('echo b', true))
exec('set cpoptions+=C')
eq('Vim(let):E15: Invalid expression: #{', exc_exec('source'))
end)
it('selection in current buffer', function()
insert(
'let a = 2\n'..
'let a = 3\n'..
'let a = 4\n')
insert([[
let a = 2
let a = 3
let a = 4
let b = #{
"\ (>_<)
\ K: "V"
\ }]])
-- Source the 2nd line only
feed('ggjV')
@ -38,13 +54,26 @@ describe(':source', function()
feed('ggjVG')
feed_command(':source')
eq('4', meths.exec('echo a', true))
eq("{'K': 'V'}", meths.exec('echo b', true))
exec('set cpoptions+=C')
eq('Vim(let):E15: Invalid expression: #{', exc_exec("'<,'>source"))
end)
it('does not break if current buffer is modified while sourced', function()
insert [[
bwipeout!
let a = 123
]]
command('source')
eq('123', meths.exec('echo a', true))
end)
it('multiline heredoc command', function()
insert(
'lua << EOF\n'..
'y = 4\n'..
'EOF\n')
insert([[
lua << EOF
y = 4
EOF]])
command('source')
eq('4', meths.exec('echo luaeval("y")', true))
@ -67,13 +96,21 @@ describe(':source', function()
vim.g.b = 5
vim.g.b = 6
vim.g.b = 7
a = [=[
"\ a
\ b]=]
]])
command('edit '..test_file)
feed('ggjV')
feed_command(':source')
eq(6, eval('g:b'))
feed('GVkk')
feed_command(':source')
eq(' "\\ a\n \\ b', exec_lua('return _G.a'))
os.remove(test_file)
end)
@ -84,12 +121,16 @@ describe(':source', function()
vim.g.c = 10
vim.g.c = 11
vim.g.c = 12
a = [=[
\ 1
"\ 2]=]
]])
command('edit '..test_file)
feed_command(':source')
eq(12, eval('g:c'))
eq(' \\ 1\n "\\ 2', exec_lua('return _G.a'))
os.remove(test_file)
end)