paste: break lines at CR, CRLF #10877

Some terminals helpfully translate \n to \r.

fix #10872
ref #10223
This commit is contained in:
Justin M. Keyes 2019-08-29 23:45:02 +02:00 committed by GitHub
parent 00d46f6328
commit 9f81acc076
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 97 additions and 49 deletions

View File

@ -239,13 +239,14 @@ GUIs can paste by calling |nvim_paste()|.
PASTE BEHAVIOR ~
Paste always inserts text after the cursor. In cmdline-mode only the first
line is pasted, to avoid accidentally executing many commands. Use the
|cmdline-window| if you really want to paste multiple lines to the cmdline.
When pasting a huge amount of text, screen updates are throttled and the
Paste inserts text after the cursor. Lines break at <NL>, <CR>, and <CR><NL>.
When pasting a huge amount of text, screen-updates are throttled and the
message area shows a "..." pulse.
In cmdline-mode only the first line is pasted, to avoid accidentally executing
many commands. Use the |cmdline-window| if you really want to paste multiple
lines to the cmdline.
You can implement a custom paste handler by redefining |vim.paste()|.
Example: >

View File

@ -745,19 +745,32 @@ String ga_take_string(garray_T *ga)
return str;
}
/// Creates "readfile()-style" ArrayOf(String).
/// Creates "readfile()-style" ArrayOf(String) from a binary string.
///
/// - NUL bytes are replaced with NL (form-feed).
/// - If last line ends with NL an extra empty list item is added.
Array string_to_array(const String input)
/// - Lines break at \n (NL/LF/line-feed).
/// - NUL bytes are replaced with NL.
/// - If the last byte is a linebreak an extra empty list item is added.
///
/// @param input Binary string
/// @param crlf Also break lines at CR and CRLF.
/// @return [allocated] String array
Array string_to_array(const String input, bool crlf)
{
Array ret = ARRAY_DICT_INIT;
for (size_t i = 0; i < input.size; i++) {
const char *start = input.data + i;
const char *end = xmemscan(start, NL, input.size - i);
const size_t line_len = (size_t)(end - start);
const char *end = start;
size_t line_len = 0;
for (; line_len < input.size - i; line_len++) {
end = start + line_len;
if (*end == NL || (crlf && *end == CAR)) {
break;
}
}
i += line_len;
if (crlf && *end == CAR && i + 1 < input.size && *(end + 1) == NL) {
i += 1; // Advance past CRLF.
}
String s = {
.size = line_len,
.data = xmemdupz(start, line_len),
@ -766,8 +779,8 @@ Array string_to_array(const String input)
ADD(ret, STRING_OBJ(s));
// If line ends at end-of-buffer, add empty final item.
// This is "readfile()-style", see also ":help channel-lines".
if (i + 1 == input.size && end[0] == NL) {
ADD(ret, STRING_OBJ(cchar_to_string(NUL)));
if (i + 1 == input.size && (*end == NL || (crlf && *end == CAR))) {
ADD(ret, STRING_OBJ(STRING_INIT));
}
}

View File

@ -1247,7 +1247,7 @@ Boolean nvim_paste(String data, Integer phase, Error *err)
// Skip remaining chunks. Report error only once per "stream".
goto theend;
}
Array lines = string_to_array(data);
Array lines = string_to_array(data, true);
ADD(args, ARRAY_OBJ(lines));
ADD(args, INTEGER_OBJ(phase));
rv = nvim_execute_lua(STATIC_CSTR_AS_STRING("return vim.paste(...)"), args,

View File

@ -381,8 +381,7 @@ describe('API', function()
line 2
line 3
]])
-- Cursor follows the paste.
eq({0,4,1,0}, funcs.getpos('.'))
eq({0,4,1,0}, funcs.getpos('.')) -- Cursor follows the paste.
eq(false, nvim('get_option', 'paste'))
command('%delete _')
-- Without final "\n".
@ -391,8 +390,38 @@ describe('API', function()
line 1
line 2
line 3]])
-- Cursor follows the paste.
eq({0,3,6,0}, funcs.getpos('.'))
command('%delete _')
-- CRLF #10872
nvim('paste', 'line 1\r\nline 2\r\nline 3\r\n', -1)
expect([[
line 1
line 2
line 3
]])
eq({0,4,1,0}, funcs.getpos('.'))
command('%delete _')
-- CRLF without final "\n".
nvim('paste', 'line 1\r\nline 2\r\nline 3\r', -1)
expect([[
line 1
line 2
line 3
]])
eq({0,4,1,0}, funcs.getpos('.'))
command('%delete _')
-- CRLF without final "\r\n".
nvim('paste', 'line 1\r\nline 2\r\nline 3', -1)
expect([[
line 1
line 2
line 3]])
eq({0,3,6,0}, funcs.getpos('.'))
command('%delete _')
-- Various other junk.
nvim('paste', 'line 1\r\n\r\rline 2\nline 3\rline 4\r', -1)
expect('line 1\n\n\nline 2\nline 3\nline 4\n')
eq({0,7,1,0}, funcs.getpos('.'))
eq(false, nvim('get_option', 'paste'))
end)
it('vim.paste() failure', function()

View File

@ -227,13 +227,24 @@ describe('TUI', function()
expect_child_buf_lines({''})
end)
it('paste: normal-mode', function()
it('paste: normal-mode (+CRLF #10872)', function()
feed_data(':set ruler')
wait_for_mode('c')
feed_data('\n')
wait_for_mode('n')
local expected = {'line 1', ' line 2', 'ESC:\027 / CR: \013'}
local expected_lf = {'line 1', 'ESC:\027 / CR: \rx'}
local expected_crlf = {'line 1', 'ESC:\027 / CR: ', 'x'}
local expected_grid1 = [[
line 1 |
ESC:{11:^[} / CR: |
{1:x} |
{4:~ }|
{5:[No Name] [+] 3,1 All}|
|
{3:-- TERMINAL --} |
]]
local expected_attr = {
[1] = {reverse = true},
[3] = {bold = true},
[4] = {foreground = tonumber('0x00000c')},
[5] = {bold = true, reverse = true},
@ -241,36 +252,30 @@ describe('TUI', function()
[12] = {reverse = true, foreground = tonumber('0x000051')},
}
-- "bracketed paste"
feed_data('\027[200~'..table.concat(expected,'\n')..'\027[201~')
screen:expect{
grid=[[
line 1 |
line 2 |
ESC:{11:^[} / CR: {12:^}{11:M} |
{4:~ }|
{5:[No Name] [+] 3,13-14 All}|
|
{3:-- TERMINAL --} |
]],
attr_ids=expected_attr}
feed_data('\027[200~'..table.concat(expected_lf,'\n')..'\027[201~')
screen:expect{grid=expected_grid1, attr_ids=expected_attr}
-- Dot-repeat/redo.
feed_data('.')
screen:expect{
grid=[[
line 2 |
ESC:{11:^[} / CR: {11:^M}line 1 |
line 2 |
ESC:{11:^[} / CR: {12:^}{11:M} |
{5:[No Name] [+] 5,13-14 Bot}|
ESC:{11:^[} / CR: |
xline 1 |
ESC:{11:^[} / CR: |
{1:x} |
{5:[No Name] [+] 5,1 Bot}|
|
{3:-- TERMINAL --} |
]],
attr_ids=expected_attr}
-- Undo.
feed_data('u')
expect_child_buf_lines(expected)
expect_child_buf_lines(expected_crlf)
feed_data('u')
expect_child_buf_lines({''})
-- CRLF input
feed_data('\027[200~'..table.concat(expected_lf,'\r\n')..'\027[201~')
screen:expect{grid=expected_grid1, attr_ids=expected_attr}
expect_child_buf_lines(expected_crlf)
end)
it('paste: cmdline-mode inserts 1 line', function()
@ -347,7 +352,7 @@ describe('TUI', function()
]]}
-- Start pasting...
feed_data('\027[200~line 1\nline 2\n')
wait_for_mode('n')
expect_child_buf_lines({'foo',''})
screen:expect{any='paste: Error executing lua'}
-- Remaining chunks are discarded after vim.paste() failure.
feed_data('line 3\nline 4\n')
@ -413,11 +418,6 @@ describe('TUI', function()
]]}
end)
-- TODO
it('paste: other modes', function()
-- Other modes act like CTRL-C + paste.
end)
it('paste: exactly 64 bytes #10311', function()
local expected = string.rep('z', 64)
feed_data('i')
@ -523,10 +523,6 @@ describe('TUI', function()
]])
end)
-- TODO
it('paste: handles missing "stop paste" code', function()
end)
it('allows termguicolors to be set at runtime', function()
screen:set_option('rgb', true)
screen:set_default_attr_ids({
@ -580,7 +576,7 @@ describe('TUI', function()
end)
it('is included in nvim_list_uis()', function()
feed_data(':echo map(nvim_list_uis(), {k,v -> sort(items(filter(v, {k,v -> k[:3] !=# "ext_" })))})\013')
feed_data(':echo map(nvim_list_uis(), {k,v -> sort(items(filter(v, {k,v -> k[:3] !=# "ext_" })))})\r')
screen:expect([=[
|
{4:~ }|

View File

@ -455,6 +455,12 @@ local SUBTBL = {
'\\030', '\\031',
}
-- Formats Lua value `v`.
--
-- TODO(justinmk): redundant with vim.inspect() ?
--
-- "Nice table formatting similar to screen:snapshot_util()".
-- Commit: 520c0b91a528
function module.format_luav(v, indent, opts)
opts = opts or {}
local linesep = '\n'
@ -533,6 +539,9 @@ function module.format_luav(v, indent, opts)
return ret
end
-- Like Python repr(), "{!r}".format(s)
--
-- Commit: 520c0b91a528
function module.format_string(fmt, ...)
local i = 0
local args = {...}