mirror of
https://github.com/neovim/neovim.git
synced 2025-09-07 11:58:17 +00:00
paste: break lines at CR, CRLF #10877
Some terminals helpfully translate \n to \r. fix #10872 ref #10223
This commit is contained in:
@@ -239,13 +239,14 @@ GUIs can paste by calling |nvim_paste()|.
|
|||||||
|
|
||||||
PASTE BEHAVIOR ~
|
PASTE BEHAVIOR ~
|
||||||
|
|
||||||
Paste always inserts text after the cursor. In cmdline-mode only the first
|
Paste inserts text after the cursor. Lines break at <NL>, <CR>, and <CR><NL>.
|
||||||
line is pasted, to avoid accidentally executing many commands. Use the
|
When pasting a huge amount of text, screen-updates are throttled and 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
|
|
||||||
message area shows a "..." pulse.
|
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()|.
|
You can implement a custom paste handler by redefining |vim.paste()|.
|
||||||
Example: >
|
Example: >
|
||||||
|
|
||||||
|
@@ -745,19 +745,32 @@ String ga_take_string(garray_T *ga)
|
|||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates "readfile()-style" ArrayOf(String).
|
/// Creates "readfile()-style" ArrayOf(String) from a binary string.
|
||||||
///
|
///
|
||||||
/// - NUL bytes are replaced with NL (form-feed).
|
/// - Lines break at \n (NL/LF/line-feed).
|
||||||
/// - If last line ends with NL an extra empty list item is added.
|
/// - NUL bytes are replaced with NL.
|
||||||
Array string_to_array(const String input)
|
/// - 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;
|
Array ret = ARRAY_DICT_INIT;
|
||||||
for (size_t i = 0; i < input.size; i++) {
|
for (size_t i = 0; i < input.size; i++) {
|
||||||
const char *start = input.data + i;
|
const char *start = input.data + i;
|
||||||
const char *end = xmemscan(start, NL, input.size - i);
|
const char *end = start;
|
||||||
const size_t line_len = (size_t)(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;
|
i += line_len;
|
||||||
|
if (crlf && *end == CAR && i + 1 < input.size && *(end + 1) == NL) {
|
||||||
|
i += 1; // Advance past CRLF.
|
||||||
|
}
|
||||||
String s = {
|
String s = {
|
||||||
.size = line_len,
|
.size = line_len,
|
||||||
.data = xmemdupz(start, line_len),
|
.data = xmemdupz(start, line_len),
|
||||||
@@ -766,8 +779,8 @@ Array string_to_array(const String input)
|
|||||||
ADD(ret, STRING_OBJ(s));
|
ADD(ret, STRING_OBJ(s));
|
||||||
// If line ends at end-of-buffer, add empty final item.
|
// If line ends at end-of-buffer, add empty final item.
|
||||||
// This is "readfile()-style", see also ":help channel-lines".
|
// This is "readfile()-style", see also ":help channel-lines".
|
||||||
if (i + 1 == input.size && end[0] == NL) {
|
if (i + 1 == input.size && (*end == NL || (crlf && *end == CAR))) {
|
||||||
ADD(ret, STRING_OBJ(cchar_to_string(NUL)));
|
ADD(ret, STRING_OBJ(STRING_INIT));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1247,7 +1247,7 @@ Boolean nvim_paste(String data, Integer phase, Error *err)
|
|||||||
// Skip remaining chunks. Report error only once per "stream".
|
// Skip remaining chunks. Report error only once per "stream".
|
||||||
goto theend;
|
goto theend;
|
||||||
}
|
}
|
||||||
Array lines = string_to_array(data);
|
Array lines = string_to_array(data, true);
|
||||||
ADD(args, ARRAY_OBJ(lines));
|
ADD(args, ARRAY_OBJ(lines));
|
||||||
ADD(args, INTEGER_OBJ(phase));
|
ADD(args, INTEGER_OBJ(phase));
|
||||||
rv = nvim_execute_lua(STATIC_CSTR_AS_STRING("return vim.paste(...)"), args,
|
rv = nvim_execute_lua(STATIC_CSTR_AS_STRING("return vim.paste(...)"), args,
|
||||||
|
@@ -381,8 +381,7 @@ describe('API', function()
|
|||||||
line 2
|
line 2
|
||||||
line 3
|
line 3
|
||||||
]])
|
]])
|
||||||
-- Cursor follows the paste.
|
eq({0,4,1,0}, funcs.getpos('.')) -- Cursor follows the paste.
|
||||||
eq({0,4,1,0}, funcs.getpos('.'))
|
|
||||||
eq(false, nvim('get_option', 'paste'))
|
eq(false, nvim('get_option', 'paste'))
|
||||||
command('%delete _')
|
command('%delete _')
|
||||||
-- Without final "\n".
|
-- Without final "\n".
|
||||||
@@ -391,8 +390,38 @@ describe('API', function()
|
|||||||
line 1
|
line 1
|
||||||
line 2
|
line 2
|
||||||
line 3]])
|
line 3]])
|
||||||
-- Cursor follows the paste.
|
|
||||||
eq({0,3,6,0}, funcs.getpos('.'))
|
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'))
|
eq(false, nvim('get_option', 'paste'))
|
||||||
end)
|
end)
|
||||||
it('vim.paste() failure', function()
|
it('vim.paste() failure', function()
|
||||||
|
@@ -227,13 +227,24 @@ describe('TUI', function()
|
|||||||
expect_child_buf_lines({''})
|
expect_child_buf_lines({''})
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('paste: normal-mode', function()
|
it('paste: normal-mode (+CRLF #10872)', function()
|
||||||
feed_data(':set ruler')
|
feed_data(':set ruler')
|
||||||
wait_for_mode('c')
|
wait_for_mode('c')
|
||||||
feed_data('\n')
|
feed_data('\n')
|
||||||
wait_for_mode('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 = {
|
local expected_attr = {
|
||||||
|
[1] = {reverse = true},
|
||||||
[3] = {bold = true},
|
[3] = {bold = true},
|
||||||
[4] = {foreground = tonumber('0x00000c')},
|
[4] = {foreground = tonumber('0x00000c')},
|
||||||
[5] = {bold = true, reverse = true},
|
[5] = {bold = true, reverse = true},
|
||||||
@@ -241,36 +252,30 @@ describe('TUI', function()
|
|||||||
[12] = {reverse = true, foreground = tonumber('0x000051')},
|
[12] = {reverse = true, foreground = tonumber('0x000051')},
|
||||||
}
|
}
|
||||||
-- "bracketed paste"
|
-- "bracketed paste"
|
||||||
feed_data('\027[200~'..table.concat(expected,'\n')..'\027[201~')
|
feed_data('\027[200~'..table.concat(expected_lf,'\n')..'\027[201~')
|
||||||
screen:expect{
|
screen:expect{grid=expected_grid1, attr_ids=expected_attr}
|
||||||
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}
|
|
||||||
-- Dot-repeat/redo.
|
-- Dot-repeat/redo.
|
||||||
feed_data('.')
|
feed_data('.')
|
||||||
screen:expect{
|
screen:expect{
|
||||||
grid=[[
|
grid=[[
|
||||||
line 2 |
|
ESC:{11:^[} / CR: |
|
||||||
ESC:{11:^[} / CR: {11:^M}line 1 |
|
xline 1 |
|
||||||
line 2 |
|
ESC:{11:^[} / CR: |
|
||||||
ESC:{11:^[} / CR: {12:^}{11:M} |
|
{1:x} |
|
||||||
{5:[No Name] [+] 5,13-14 Bot}|
|
{5:[No Name] [+] 5,1 Bot}|
|
||||||
|
|
|
|
||||||
{3:-- TERMINAL --} |
|
{3:-- TERMINAL --} |
|
||||||
]],
|
]],
|
||||||
attr_ids=expected_attr}
|
attr_ids=expected_attr}
|
||||||
-- Undo.
|
-- Undo.
|
||||||
feed_data('u')
|
feed_data('u')
|
||||||
expect_child_buf_lines(expected)
|
expect_child_buf_lines(expected_crlf)
|
||||||
feed_data('u')
|
feed_data('u')
|
||||||
expect_child_buf_lines({''})
|
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)
|
end)
|
||||||
|
|
||||||
it('paste: cmdline-mode inserts 1 line', function()
|
it('paste: cmdline-mode inserts 1 line', function()
|
||||||
@@ -347,7 +352,7 @@ describe('TUI', function()
|
|||||||
]]}
|
]]}
|
||||||
-- Start pasting...
|
-- Start pasting...
|
||||||
feed_data('\027[200~line 1\nline 2\n')
|
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'}
|
screen:expect{any='paste: Error executing lua'}
|
||||||
-- Remaining chunks are discarded after vim.paste() failure.
|
-- Remaining chunks are discarded after vim.paste() failure.
|
||||||
feed_data('line 3\nline 4\n')
|
feed_data('line 3\nline 4\n')
|
||||||
@@ -413,11 +418,6 @@ describe('TUI', function()
|
|||||||
]]}
|
]]}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- TODO
|
|
||||||
it('paste: other modes', function()
|
|
||||||
-- Other modes act like CTRL-C + paste.
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('paste: exactly 64 bytes #10311', function()
|
it('paste: exactly 64 bytes #10311', function()
|
||||||
local expected = string.rep('z', 64)
|
local expected = string.rep('z', 64)
|
||||||
feed_data('i')
|
feed_data('i')
|
||||||
@@ -523,10 +523,6 @@ describe('TUI', function()
|
|||||||
]])
|
]])
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- TODO
|
|
||||||
it('paste: handles missing "stop paste" code', function()
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('allows termguicolors to be set at runtime', function()
|
it('allows termguicolors to be set at runtime', function()
|
||||||
screen:set_option('rgb', true)
|
screen:set_option('rgb', true)
|
||||||
screen:set_default_attr_ids({
|
screen:set_default_attr_ids({
|
||||||
@@ -580,7 +576,7 @@ describe('TUI', function()
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
it('is included in nvim_list_uis()', function()
|
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([=[
|
screen:expect([=[
|
||||||
|
|
|
|
||||||
{4:~ }|
|
{4:~ }|
|
||||||
|
@@ -455,6 +455,12 @@ local SUBTBL = {
|
|||||||
'\\030', '\\031',
|
'\\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)
|
function module.format_luav(v, indent, opts)
|
||||||
opts = opts or {}
|
opts = opts or {}
|
||||||
local linesep = '\n'
|
local linesep = '\n'
|
||||||
@@ -533,6 +539,9 @@ function module.format_luav(v, indent, opts)
|
|||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Like Python repr(), "{!r}".format(s)
|
||||||
|
--
|
||||||
|
-- Commit: 520c0b91a528
|
||||||
function module.format_string(fmt, ...)
|
function module.format_string(fmt, ...)
|
||||||
local i = 0
|
local i = 0
|
||||||
local args = {...}
|
local args = {...}
|
||||||
|
Reference in New Issue
Block a user