# HG changeset patch # User Bram Moolenaar # Date 1592424005 -7200 # Node ID 4bdc07beeadbdea66944447bc3338de2e147710d # Parent 2ac788c890153e40e893a026d4420dd5e8f81263 patch 8.2.0997: cannot execute a register containing line continuation Commit: https://github.com/vim/vim/commit/856c1110c1cf0d6e44e387b70732ca4b4c8ef0f2 Author: Bram Moolenaar Date: Wed Jun 17 21:47:23 2020 +0200 patch 8.2.0997: cannot execute a register containing line continuation Problem: Cannot execute a register containing line continuation. Solution: Concatenate lines where needed. (Yegappan Lakshmanan, closes #6272) diff --git a/runtime/doc/repeat.txt b/runtime/doc/repeat.txt --- a/runtime/doc/repeat.txt +++ b/runtime/doc/repeat.txt @@ -163,6 +163,11 @@ q Stops recording. (Implementation no result of evaluating the expression is executed as an Ex command. Mappings are not recognized in these commands. + When the |line-continuation| character (\) is present + at the beginning of a line in a linewise register, + then it is combined with the previous line. This is + useful for yanking and executing parts of a Vim + script. Future: Will execute the register for each line in the address range. diff --git a/src/register.c b/src/register.c --- a/src/register.c +++ b/src/register.c @@ -474,6 +474,73 @@ set_execreg_lastc(int lastc) } /* + * When executing a register as a series of ex-commands, if the + * line-continuation character is used for a line, then join it with one or + * more previous lines. Note that lines are processed backwards starting from + * the last line in the register. + * + * Arguments: + * lines - list of lines in the register + * idx - index of the line starting with \ or "\. Join this line with all the + * immediate predecessor lines that start with a \ and the first line + * that doesn't start with a \. Lines that start with a comment "\ + * character are ignored. + * + * Returns the concatenated line. The index of the line that should be + * processed next is returned in idx. + */ + static char_u * +execreg_line_continuation(char_u **lines, long *idx) +{ + garray_T ga; + long i = *idx; + char_u *p; + int cmd_start; + int cmd_end = i; + int j; + char_u *str; + + ga_init2(&ga, (int)sizeof(char_u), 400); + + // search backwards to find the first line of this command. + // Any line not starting with \ or "\ is the start of the + // command. + while (--i > 0) + { + p = skipwhite(lines[i]); + if (*p != '\\' && (p[0] != '"' || p[1] != '\\' || p[2] != ' ')) + break; + } + cmd_start = i; + + // join all the lines + ga_concat(&ga, lines[cmd_start]); + for (j = cmd_start + 1; j <= cmd_end; j++) + { + p = skipwhite(lines[j]); + if (*p == '\\') + { + // Adjust the growsize to the current length to + // speed up concatenating many lines. + if (ga.ga_len > 400) + { + if (ga.ga_len > 8000) + ga.ga_growsize = 8000; + else + ga.ga_growsize = ga.ga_len; + } + ga_concat(&ga, p + 1); + } + } + ga_append(&ga, NUL); + str = vim_strsave(ga.ga_data); + ga_clear(&ga); + + *idx = i; + return str; +} + +/* * Execute a yank register: copy it into the stuff buffer. * * Return FAIL for failure, OK otherwise. @@ -579,6 +646,8 @@ do_execreg( for (i = y_current->y_size; --i >= 0; ) { char_u *escaped; + char_u *str; + int free_str = FALSE; // insert NL between lines and after last line if type is MLINE if (y_current->y_type == MLINE || i < y_current->y_size - 1 @@ -587,7 +656,23 @@ do_execreg( if (ins_typebuf((char_u *)"\n", remap, 0, TRUE, silent) == FAIL) return FAIL; } - escaped = vim_strsave_escape_csi(y_current->y_array[i]); + + // Handle line-continuation for :@ + str = y_current->y_array[i]; + if (colon && i > 0) + { + p = skipwhite(str); + if (*p == '\\' || (p[0] == '"' && p[1] == '\\' && p[2] == ' ')) + { + str = execreg_line_continuation(y_current->y_array, &i); + if (str == NULL) + return FAIL; + free_str = TRUE; + } + } + escaped = vim_strsave_escape_csi(str); + if (free_str) + vim_free(str); if (escaped == NULL) return FAIL; retval = ins_typebuf(escaped, remap, 0, TRUE, silent); diff --git a/src/testdir/test_registers.vim b/src/testdir/test_registers.vim --- a/src/testdir/test_registers.vim +++ b/src/testdir/test_registers.vim @@ -557,4 +557,80 @@ func Test_v_register() bwipe! endfunc +" Test for executing the contents of a register as an Ex command with line +" continuation. +func Test_execute_reg_as_ex_cmd() + " Line continuation with just two lines + let code =<< trim END + let l = [ + \ 1] + END + let @r = code->join("\n") + let l = [] + @r + call assert_equal([1], l) + + " Line continuation with more than two lines + let code =<< trim END + let l = [ + \ 1, + \ 2, + \ 3] + END + let @r = code->join("\n") + let l = [] + @r + call assert_equal([1, 2, 3], l) + + " use comments interspersed with code + let code =<< trim END + let l = [ + "\ one + \ 1, + "\ two + \ 2, + "\ three + \ 3] + END + let @r = code->join("\n") + let l = [] + @r + call assert_equal([1, 2, 3], l) + + " use line continuation in the middle + let code =<< trim END + let a = "one" + let l = [ + \ 1, + \ 2] + let b = "two" + END + let @r = code->join("\n") + let l = [] + @r + call assert_equal([1, 2], l) + call assert_equal("one", a) + call assert_equal("two", b) + + " only one line with a \ + let @r = "\\let l = 1" + call assert_fails('@r', 'E10:') + + " only one line with a "\ + let @r = ' "\ let i = 1' + @r + call assert_false(exists('i')) + + " first line also begins with a \ + let @r = "\\let l = [\n\\ 1]" + call assert_fails('@r', 'E10:') + + " Test with a large number of lines + let @r = "let str = \n" + let @r ..= repeat(" \\ 'abcdefghijklmnopqrstuvwxyz' ..\n", 312) + let @r ..= ' \ ""' + @r + call assert_equal(repeat('abcdefghijklmnopqrstuvwxyz', 312), str) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -755,6 +755,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 997, +/**/ 996, /**/ 995,