changeset 20891:4bdc07beeadb v8.2.0997

patch 8.2.0997: cannot execute a register containing line continuation Commit: https://github.com/vim/vim/commit/856c1110c1cf0d6e44e387b70732ca4b4c8ef0f2 Author: Bram Moolenaar <Bram@vim.org> 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)
author Bram Moolenaar <Bram@vim.org>
date Wed, 17 Jun 2020 22:00:05 +0200
parents 2ac788c89015
children c98abb80d60d
files runtime/doc/repeat.txt src/register.c src/testdir/test_registers.vim src/version.c
diffstat 4 files changed, 169 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- 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.
 
--- 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 :@<register>
+	    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);
--- 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
--- 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,