diff options
-rw-r--r-- | src/nvim/charset.c | 34 | ||||
-rw-r--r-- | src/nvim/ex_cmds2.c | 143 | ||||
-rw-r--r-- | test/functional/api/vim_spec.lua | 13 | ||||
-rw-r--r-- | test/functional/ex_cmds/source_spec.lua | 61 |
4 files changed, 179 insertions, 72 deletions
diff --git a/src/nvim/charset.c b/src/nvim/charset.c index e2d844a351..a58c665905 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -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. diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 9d500a8ddb..d9047d7a5a 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -2635,44 +2635,42 @@ static void cmd_source(char_u *fname, exarg_T *eap) } } -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(). +/// Concatenate VimL line if it starts with a line continuation into a growarray +/// (excluding the continuation chars and leading whitespace) /// -/// @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) +/// @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 { - 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)"); + 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; + /// ":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); diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 91d2745130..227f168298 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -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() diff --git a/test/functional/ex_cmds/source_spec.lua b/test/functional/ex_cmds/source_spec.lua index 37c97f519a..bdf6ae76d1 100644 --- a/test/functional/ex_cmds/source_spec.lua +++ b/test/functional/ex_cmds/source_spec.lua @@ -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) |