mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +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
	 Justin M. Keyes
					Justin M. Keyes