vim-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 vim/vim#6272)
856c1110c1

According to #2542 the "Future:" part was removed intentionally.
Use size_t in more places to reduce type casts.
This commit is contained in:
zeertzjq 2022-02-23 15:56:37 +08:00
parent f24121ad96
commit 165cf1b48e
3 changed files with 150 additions and 1 deletions

View File

@ -161,6 +161,11 @@ Q Repeat the last recorded register [count] times.
result of evaluating the expression is executed as an result of evaluating the expression is executed as an
Ex command. Ex command.
Mappings are not recognized in these commands. 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.
*:@:* *:@:*
:[addr]@: Repeat last command-line. First set cursor at line :[addr]@: Repeat last command-line. First set cursor at line

View File

@ -1023,6 +1023,60 @@ static int stuff_yank(int regname, char_u *p)
static int execreg_lastc = NUL; static int execreg_lastc = NUL;
/// 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.
///
/// @param lines list of lines in the register
/// @param 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, size_t *idx)
{
size_t i = *idx;
assert(i > 0);
const size_t cmd_end = i;
garray_T ga;
ga_init(&ga, (int)sizeof(char_u), 400);
char_u *p;
// 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;
}
}
const size_t cmd_start = i;
// join all the lines
ga_concat(&ga, (char *)lines[cmd_start]);
for (size_t 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) {
ga_set_growsize(&ga, MIN(ga.ga_len, 8000));
}
ga_concat(&ga, (char *)(p + 1));
}
}
ga_append(&ga, NUL);
char_u *str = vim_strsave(ga.ga_data);
ga_clear(&ga);
*idx = i;
return str;
}
/// Execute a yank register: copy it into the stuff buffer /// Execute a yank register: copy it into the stuff buffer
/// ///
/// @param colon insert ':' before each line /// @param colon insert ':' before each line
@ -1111,7 +1165,21 @@ int do_execreg(int regname, int colon, int addcr, int silent)
return FAIL; return FAIL;
} }
} }
escaped = vim_strsave_escape_ks(reg->y_array[i]);
// Handle line-continuation for :@<register>
char_u *str = reg->y_array[i];
bool free_str = false;
if (colon && i > 0) {
p = skipwhite(str);
if (*p == '\\' || (p[0] == '"' && p[1] == '\\' && p[2] == ' ')) {
str = execreg_line_continuation(reg->y_array, &i);
free_str = true;
}
}
escaped = vim_strsave_escape_ks(str);
if (free_str) {
xfree(str);
}
retval = ins_typebuf(escaped, remap, 0, true, silent); retval = ins_typebuf(escaped, remap, 0, true, silent);
xfree(escaped); xfree(escaped);
if (retval == FAIL) { if (retval == FAIL) {

View File

@ -482,6 +482,82 @@ func Test_v_register()
bwipe! bwipe!
endfunc 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
func Test_ve_blockpaste() func Test_ve_blockpaste()
new new
set ve=all set ve=all