diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 5c245d8262..6542ab41f5 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -1860,72 +1860,11 @@ static bool concat_continued_line(garray_T *const ga, const int init_growsize, return true; } -/// Concatenate (possibly many) VimL lines starting with line continuations into -/// a growarray. @see concat_continued_line -/// -/// @note All parameters, excluding `ga`, accept expressions that are evaluated -/// once for each line; side-effects may be triggered many times! -/// -/// @param ga the growarray to append to -/// @param cond should evaluate to true if a next line exists -/// @param line should evaluate to the current line -/// @param next should handle fetching the next line when evaluated -#define CONCAT_CONTINUED_LINES(ga, cond, line, next) \ - do { \ - garray_T *const ga_ = (ga); \ - const int init_growsize_ = ga_->ga_growsize; \ - for (; (cond); (next)) { \ - const char_u *const line_ = (line); \ - if (!concat_continued_line(ga_, init_growsize_, line_, STRLEN(line_))) { \ - break; \ - } \ - } \ - } while (false) - 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; - } - garray_T ga; - ga_init(&ga, sizeof(char_u), 400); - ga_concat(&ga, ml_get(p->curr_lnum++)); - if (do_concat && vim_strchr(p_cpo, CPO_CONCAT) == NULL) { - CONCAT_CONTINUED_LINES(&ga, p->curr_lnum <= p->final_lnum, - ml_get(p->curr_lnum), p->curr_lnum++); - } - ga_append(&ga, NUL); - return ga.ga_data; -} - -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 @@ -2033,6 +1972,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() @@ -2490,9 +2463,12 @@ char_u *getsourceline(int c, void *cookie, int indent, bool do_concat) ga_init(&ga, (int)sizeof(char_u), 400); ga_concat(&ga, line); - CONCAT_CONTINUED_LINES(&ga, sp->nextline != NULL, sp->nextline, - (xfree(sp->nextline), - sp->nextline = get_one_sourceline(sp))); + while (sp->nextline != NULL + && concat_continued_line(&ga, 400, sp->nextline, + STRLEN(sp->nextline))) { + xfree(sp->nextline); + sp->nextline = get_one_sourceline(sp); + } ga_append(&ga, NUL); xfree(line); line = ga.ga_data; diff --git a/test/functional/ex_cmds/source_spec.lua b/test/functional/ex_cmds/source_spec.lua index 3cef412bb6..bdf6ae76d1 100644 --- a/test/functional/ex_cmds/source_spec.lua +++ b/test/functional/ex_cmds/source_spec.lua @@ -60,6 +60,15 @@ describe(':source', function() 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