From 9b1e1fbc9f795921afd25ba38be5c79dec8b04d2 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 6 Mar 2022 06:56:24 +0800 Subject: [PATCH 1/9] fix(paste): use getcmdtype() to determine whether in cmdline mode --- runtime/lua/vim/_editor.lua | 12 ++++++------ test/functional/api/vim_spec.lua | 6 ++++++ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index a0c60a7dcf..8000730795 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -156,21 +156,21 @@ do --- - 3: ends the paste (exactly once) ---@returns false if client should cancel the paste. function vim.paste(lines, phase) - local call = vim.api.nvim_call_function local now = vim.loop.now() - local mode = call('mode', {}):sub(1,1) + local mode = vim.api.nvim_get_mode().mode + local is_cmdline = vim.fn.getcmdtype() ~= '' if phase < 2 then -- Reset flags. tdots, tick, got_line1 = now, 0, false - elseif mode ~= 'c' then + elseif not is_cmdline then vim.api.nvim_command('undojoin') end - if mode == 'c' and not got_line1 then -- cmdline-mode: paste only 1 line. + if is_cmdline and not got_line1 then -- cmdline-mode: paste only 1 line. got_line1 = (#lines > 1) vim.api.nvim_set_option('paste', true) -- For nvim_input(). local line1 = lines[1]:gsub('<', ''):gsub('[\r\n\012\027]', ' ') -- Scrub. vim.api.nvim_input(line1) vim.api.nvim_set_option('paste', false) - elseif mode ~= 'c' then + elseif not is_cmdline then if phase < 2 and mode:find('^[vV\22sS\19]') then vim.api.nvim_command([[exe "normal! \"]]) vim.api.nvim_put(lines, 'c', false, true) @@ -178,7 +178,7 @@ do vim.api.nvim_put(lines, 'c', true, true) -- XXX: Normal-mode: workaround bad cursor-placement after first chunk. vim.api.nvim_command('normal! a') - elseif phase < 2 and mode == 'R' then + elseif phase < 2 and mode:find('^R') then local nchars = 0 for _, line in ipairs(lines) do nchars = nchars + line:len() diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index e945a6c706..384e8d11f2 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -738,6 +738,12 @@ describe('API', function() eeffgghh iijjkkll]]) end) + it('when searching in Visual mode', function() + feed('v/') + nvim('paste', 'aabbccdd', true, -1) + eq('aabbccdd', funcs.getcmdline()) + expect('') + end) it('crlf=false does not break lines at CR, CRLF', function() nvim('paste', 'line 1\r\n\r\rline 2\nline 3\rline 4\r', false, -1) expect('line 1\r\n\r\rline 2\nline 3\rline 4\r') From 2601e0873ff50ed804487dff00bd27e233709beb Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 6 Mar 2022 06:56:24 +0800 Subject: [PATCH 2/9] fix(paste): don't move cursor past the end of pasted text in Normal mode --- runtime/lua/vim/_editor.lua | 27 +++++++++++-------- test/functional/api/vim_spec.lua | 37 +++++++++++++++++++++++++++ test/functional/terminal/tui_spec.lua | 14 +++++----- 3 files changed, 61 insertions(+), 17 deletions(-) diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 8000730795..8e49b51cec 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -171,27 +171,34 @@ do vim.api.nvim_input(line1) vim.api.nvim_set_option('paste', false) elseif not is_cmdline then - if phase < 2 and mode:find('^[vV\22sS\19]') then - vim.api.nvim_command([[exe "normal! \"]]) + if mode:find('^i') or mode:find('^n?t') then -- Insert mode or Terminal buffer vim.api.nvim_put(lines, 'c', false, true) - elseif phase < 2 and not mode:find('^[iRt]') then - vim.api.nvim_put(lines, 'c', true, true) - -- XXX: Normal-mode: workaround bad cursor-placement after first chunk. - vim.api.nvim_command('normal! a') - elseif phase < 2 and mode:find('^R') then + elseif phase < 2 and mode:find('^R') and not mode:find('^Rv') then -- Replace mode + -- TODO: implement Replace mode streamed pasting + -- TODO: support Virtual Replace mode local nchars = 0 for _, line in ipairs(lines) do - nchars = nchars + line:len() + nchars = nchars + line:len() end local row, col = unpack(vim.api.nvim_win_get_cursor(0)) local bufline = vim.api.nvim_buf_get_lines(0, row-1, row, true)[1] local firstline = lines[1] firstline = bufline:sub(1, col)..firstline lines[1] = firstline + -- FIXME: #lines can be 0 lines[#lines] = lines[#lines]..bufline:sub(col + nchars + 1, bufline:len()) vim.api.nvim_buf_set_lines(0, row-1, row, false, lines) - else - vim.api.nvim_put(lines, 'c', false, true) + elseif mode:find('^[nvV\22sS\19]') then -- Normal or Visual or Select mode + if mode:find('^n') then -- Normal mode + vim.api.nvim_put(lines, 'c', true, false) + else -- Visual or Select mode + vim.api.nvim_command([[exe "normal! \"]]) + vim.api.nvim_put(lines, 'c', false, false) + end + -- put cursor at the end of the text instead of one character after it + vim.fn.setpos('.', vim.fn.getpos("']")) + else -- Don't know what to do in other modes + return false end end if phase ~= -1 and (now - tdots >= 100) then diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 384e8d11f2..3d57953a11 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -665,6 +665,43 @@ describe('API', function() feed('u') -- Undo. expect(expected1) end) + it('stream: Insert mode', function() + feed('i') + nvim('paste', 'aaaaaa', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('aaaaaabbbbbbccccccdddddd') + end) + it('stream: Normal mode on empty line', function() + nvim('paste', 'aaaaaa', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('aaaaaabbbbbbccccccdddddd') + feed('u') + expect('') + end) + it('stream: Normal mode not at the end of a line', function() + feed('i||0') + nvim('paste', 'aaaaaa', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('|aaaaaabbbbbbccccccdddddd|') + feed('u') + expect('||') + end) + it('stream: Normal mode at the end of a line', function() + feed('i||') + nvim('paste', 'aaaaaa', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('||aaaaaabbbbbbccccccdddddd') + feed('u') + expect('||') + end) it('non-streaming', function() -- With final "\n". nvim('paste', 'line 1\nline 2\nline 3\n', true, -1) diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index faf44fa01d..c37cde06ab 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -323,7 +323,7 @@ describe('TUI', function() feed_data('just paste it™') feed_data('\027[201~') screen:expect{grid=[[ - thisjust paste it™{1:3} is here | + thisjust paste it{1:™}3 is here | | {4:~ }| {4:~ }| @@ -379,7 +379,7 @@ describe('TUI', function() end) it('paste: normal-mode (+CRLF #10872)', function() - feed_data(':set ruler') + feed_data(':set ruler | echo') wait_for_mode('c') feed_data('\n') wait_for_mode('n') @@ -423,13 +423,13 @@ describe('TUI', function() expect_child_buf_lines(expected_crlf) feed_data('u') expect_child_buf_lines({''}) + feed_data(':echo') + wait_for_mode('c') + feed_data('\n') + wait_for_mode('n') -- CRLF input feed_data('\027[200~'..table.concat(expected_lf,'\r\n')..'\027[201~') - screen:expect{ - grid=expected_grid1:gsub( - ':set ruler *', - '3 fewer lines; before #1 0 seconds ago '), - attr_ids=expected_attr} + screen:expect{grid=expected_grid1, attr_ids=expected_attr} expect_child_buf_lines(expected_crlf) end) From bfb77544425b7cca372cb87f00ef6b6e87c5f6d5 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 6 Mar 2022 06:56:24 +0800 Subject: [PATCH 3/9] fix(paste): deal with eol and eof in Visual mode --- runtime/lua/vim/_editor.lua | 15 +++++++++++-- test/functional/api/vim_spec.lua | 36 ++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 8e49b51cec..6b1725c9ff 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -192,8 +192,19 @@ do if mode:find('^n') then -- Normal mode vim.api.nvim_put(lines, 'c', true, false) else -- Visual or Select mode - vim.api.nvim_command([[exe "normal! \"]]) - vim.api.nvim_put(lines, 'c', false, false) + vim.api.nvim_command([[exe "silent normal! \"]]) + local del_start = vim.fn.getpos("'[") + local cursor_pos = vim.fn.getpos('.') + if mode:find('^[VS]') then -- linewise + if cursor_pos[2] < del_start[2] then -- replacing lines at eof + -- create a new line + vim.api.nvim_put({''}, 'l', true, true) + end + vim.api.nvim_put(lines, 'c', false, false) + else + -- paste after cursor when replacing text at eol, otherwise paste before cursor + vim.api.nvim_put(lines, 'c', cursor_pos[3] < del_start[3], false) + end end -- put cursor at the end of the text instead of one character after it vim.fn.setpos('.', vim.fn.getpos("']")) diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 3d57953a11..eaee9211f4 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -702,6 +702,42 @@ describe('API', function() feed('u') expect('||') end) + it('stream: Visual mode either end not at the end of a line', function() + feed('i|xxxxxx|hvhk') + nvim('paste', 'aaaaaa', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('|aaaaaabbbbbbccccccdddddd|') + feed('u') + expect([[ + |xxx + xxx|]]) + end) + it('stream: Visual mode cursor at the end of a line', function() + feed('i||xxxxxxvko') + nvim('paste', 'aaaaaa', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('||aaaaaabbbbbbccccccdddddd') + feed('u') + expect([[ + ||xxx + xxx]]) + end) + it('stream: Visual mode other end at the end of a line', function() + feed('i||xxxxxxvk') + nvim('paste', 'aaaaaa', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('||aaaaaabbbbbbccccccdddddd') + feed('u') + expect([[ + ||xxx + xxx]]) + end) it('non-streaming', function() -- With final "\n". nvim('paste', 'line 1\nline 2\nline 3\n', true, -1) From 21ba2d81a848e7b85739fc3e9aa2eb0b5e35c879 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 6 Mar 2022 06:56:24 +0800 Subject: [PATCH 4/9] refactor(paste): do not print dots in cmdline mode --- runtime/lua/vim/_editor.lua | 104 +++++++++++++++++++----------------- 1 file changed, 54 insertions(+), 50 deletions(-) diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 6b1725c9ff..a7f8f0e7b6 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -157,60 +157,64 @@ do ---@returns false if client should cancel the paste. function vim.paste(lines, phase) local now = vim.loop.now() - local mode = vim.api.nvim_get_mode().mode - local is_cmdline = vim.fn.getcmdtype() ~= '' - if phase < 2 then -- Reset flags. + local is_first_chunk = phase < 2 + if is_first_chunk then -- Reset flags. tdots, tick, got_line1 = now, 0, false - elseif not is_cmdline then + end + -- Note: mode doesn't always start with "c" in cmdline mode, so use getcmdtype() instead. + if vim.fn.getcmdtype() ~= '' then -- cmdline-mode: paste only 1 line. + if not got_line1 then + got_line1 = (#lines > 1) + vim.api.nvim_set_option('paste', true) -- For nvim_input(). + local line1 = lines[1]:gsub('<', ''):gsub('[\r\n\012\027]', ' ') -- Scrub. + vim.api.nvim_input(line1) + vim.api.nvim_set_option('paste', false) + end + return true + end + local mode = vim.api.nvim_get_mode().mode + if not is_first_chunk then vim.api.nvim_command('undojoin') end - if is_cmdline and not got_line1 then -- cmdline-mode: paste only 1 line. - got_line1 = (#lines > 1) - vim.api.nvim_set_option('paste', true) -- For nvim_input(). - local line1 = lines[1]:gsub('<', ''):gsub('[\r\n\012\027]', ' ') -- Scrub. - vim.api.nvim_input(line1) - vim.api.nvim_set_option('paste', false) - elseif not is_cmdline then - if mode:find('^i') or mode:find('^n?t') then -- Insert mode or Terminal buffer - vim.api.nvim_put(lines, 'c', false, true) - elseif phase < 2 and mode:find('^R') and not mode:find('^Rv') then -- Replace mode - -- TODO: implement Replace mode streamed pasting - -- TODO: support Virtual Replace mode - local nchars = 0 - for _, line in ipairs(lines) do - nchars = nchars + line:len() - end - local row, col = unpack(vim.api.nvim_win_get_cursor(0)) - local bufline = vim.api.nvim_buf_get_lines(0, row-1, row, true)[1] - local firstline = lines[1] - firstline = bufline:sub(1, col)..firstline - lines[1] = firstline - -- FIXME: #lines can be 0 - lines[#lines] = lines[#lines]..bufline:sub(col + nchars + 1, bufline:len()) - vim.api.nvim_buf_set_lines(0, row-1, row, false, lines) - elseif mode:find('^[nvV\22sS\19]') then -- Normal or Visual or Select mode - if mode:find('^n') then -- Normal mode - vim.api.nvim_put(lines, 'c', true, false) - else -- Visual or Select mode - vim.api.nvim_command([[exe "silent normal! \"]]) - local del_start = vim.fn.getpos("'[") - local cursor_pos = vim.fn.getpos('.') - if mode:find('^[VS]') then -- linewise - if cursor_pos[2] < del_start[2] then -- replacing lines at eof - -- create a new line - vim.api.nvim_put({''}, 'l', true, true) - end - vim.api.nvim_put(lines, 'c', false, false) - else - -- paste after cursor when replacing text at eol, otherwise paste before cursor - vim.api.nvim_put(lines, 'c', cursor_pos[3] < del_start[3], false) - end - end - -- put cursor at the end of the text instead of one character after it - vim.fn.setpos('.', vim.fn.getpos("']")) - else -- Don't know what to do in other modes - return false + if mode:find('^i') or mode:find('^n?t') then -- Insert mode or Terminal buffer + vim.api.nvim_put(lines, 'c', false, true) + elseif phase < 2 and mode:find('^R') and not mode:find('^Rv') then -- Replace mode + -- TODO: implement Replace mode streamed pasting + -- TODO: support Virtual Replace mode + local nchars = 0 + for _, line in ipairs(lines) do + nchars = nchars + line:len() end + local row, col = unpack(vim.api.nvim_win_get_cursor(0)) + local bufline = vim.api.nvim_buf_get_lines(0, row-1, row, true)[1] + local firstline = lines[1] + firstline = bufline:sub(1, col)..firstline + lines[1] = firstline + -- FIXME: #lines can be 0 + lines[#lines] = lines[#lines]..bufline:sub(col + nchars + 1, bufline:len()) + vim.api.nvim_buf_set_lines(0, row-1, row, false, lines) + elseif mode:find('^[nvV\22sS\19]') then -- Normal or Visual or Select mode + if mode:find('^n') then -- Normal mode + vim.api.nvim_put(lines, 'c', true, false) + else -- Visual or Select mode + vim.api.nvim_command([[exe "silent normal! \"]]) + local del_start = vim.fn.getpos("'[") + local cursor_pos = vim.fn.getpos('.') + if mode:find('^[VS]') then -- linewise + if cursor_pos[2] < del_start[2] then -- replacing lines at eof + -- create a new line + vim.api.nvim_put({''}, 'l', true, true) + end + vim.api.nvim_put(lines, 'c', false, false) + else + -- paste after cursor when replacing text at eol, otherwise paste before cursor + vim.api.nvim_put(lines, 'c', cursor_pos[3] < del_start[3], false) + end + end + -- put cursor at the end of the text instead of one character after it + vim.fn.setpos('.', vim.fn.getpos("']")) + else -- Don't know what to do in other modes + return false end if phase ~= -1 and (now - tdots >= 100) then local dots = ('.'):rep(tick % 4) From fcc6f66cf2a67cf85e72727a08e19d0f800badb9 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 6 Mar 2022 06:56:24 +0800 Subject: [PATCH 5/9] fix(paste): avoid edges cases caused by empty chunk --- runtime/lua/vim/_editor.lua | 19 ++++++--- test/functional/api/vim_spec.lua | 67 +++++++++++++++++++++++++++++++- 2 files changed, 80 insertions(+), 6 deletions(-) diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index a7f8f0e7b6..42adda6e7f 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -128,7 +128,7 @@ local function inspect(object, options) -- luacheck: no unused end do - local tdots, tick, got_line1 = 0, 0, false + local tdots, tick, got_line1, undo_started = 0, 0, false, false --- Paste handler, invoked by |nvim_paste()| when a conforming UI --- (such as the |TUI|) pastes text into the editor. @@ -158,8 +158,17 @@ do function vim.paste(lines, phase) local now = vim.loop.now() local is_first_chunk = phase < 2 + local is_last_chunk = phase == -1 or phase == 3 if is_first_chunk then -- Reset flags. - tdots, tick, got_line1 = now, 0, false + tdots, tick, got_line1, undo_started = now, 0, false, false + end + if #lines == 0 then + lines = {''} + end + if #lines == 1 and lines[1] == '' and not is_last_chunk then + -- An empty chunk can cause some edge cases in streamed pasting, + -- so don't do anything unless it is the last chunk. + return true end -- Note: mode doesn't always start with "c" in cmdline mode, so use getcmdtype() instead. if vim.fn.getcmdtype() ~= '' then -- cmdline-mode: paste only 1 line. @@ -173,7 +182,7 @@ do return true end local mode = vim.api.nvim_get_mode().mode - if not is_first_chunk then + if undo_started then vim.api.nvim_command('undojoin') end if mode:find('^i') or mode:find('^n?t') then -- Insert mode or Terminal buffer @@ -190,7 +199,6 @@ do local firstline = lines[1] firstline = bufline:sub(1, col)..firstline lines[1] = firstline - -- FIXME: #lines can be 0 lines[#lines] = lines[#lines]..bufline:sub(col + nchars + 1, bufline:len()) vim.api.nvim_buf_set_lines(0, row-1, row, false, lines) elseif mode:find('^[nvV\22sS\19]') then -- Normal or Visual or Select mode @@ -216,6 +224,7 @@ do else -- Don't know what to do in other modes return false end + undo_started = true if phase ~= -1 and (now - tdots >= 100) then local dots = ('.'):rep(tick % 4) tdots = now @@ -224,7 +233,7 @@ do -- message when there are zero dots. vim.api.nvim_command(('echo "%s"'):format(dots)) end - if phase == -1 or phase == 3 then + if is_last_chunk then vim.api.nvim_command('redraw'..(tick > 1 and '|echo ""' or '')) end return true -- Paste will not continue if not returning `true`. diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index eaee9211f4..761cbb4036 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -630,6 +630,10 @@ describe('API', function() end) describe('nvim_paste', function() + before_each(function() + -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. + feed('ifoou') + end) it('validates args', function() eq('Invalid phase: -2', pcall_err(request, 'nvim_paste', 'foo', true, -2)) @@ -702,7 +706,7 @@ describe('API', function() feed('u') expect('||') end) - it('stream: Visual mode either end not at the end of a line', function() + it('stream: Visual mode neither end at the end of a line', function() feed('i|xxxxxx|hvhk') nvim('paste', 'aaaaaa', false, 1) nvim('paste', 'bbbbbb', false, 2) @@ -714,6 +718,30 @@ describe('API', function() |xxx xxx|]]) end) + it('stream: Visual mode neither end at the end of a line with empty first chunk', function() + feed('i|xxxxxx|hvhk') + nvim('paste', '', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('|bbbbbbccccccdddddd|') + feed('u') + expect([[ + |xxx + xxx|]]) + end) + it('stream: Visual mode neither end at the end of a line with all chunks empty', function() + feed('i|xxxxxx|hvhk') + nvim('paste', '', false, 1) + nvim('paste', '', false, 2) + nvim('paste', '', false, 2) + nvim('paste', '', false, 3) + expect('||') + feed('u') + expect([[ + |xxx + xxx|]]) + end) it('stream: Visual mode cursor at the end of a line', function() feed('i||xxxxxxvko') nvim('paste', 'aaaaaa', false, 1) @@ -726,6 +754,18 @@ describe('API', function() ||xxx xxx]]) end) + it('stream: Visual mode cursor at the end of a line with empty first chunk', function() + feed('i||xxxxxxvko') + nvim('paste', '', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('||bbbbbbccccccdddddd') + feed('u') + expect([[ + ||xxx + xxx]]) + end) it('stream: Visual mode other end at the end of a line', function() feed('i||xxxxxxvk') nvim('paste', 'aaaaaa', false, 1) @@ -738,6 +778,18 @@ describe('API', function() ||xxx xxx]]) end) + it('stream: Visual mode other end at the end of a line with empty first chunk', function() + feed('i||xxxxxxvk') + nvim('paste', '', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('||bbbbbbccccccdddddd') + feed('u') + expect([[ + ||xxx + xxx]]) + end) it('non-streaming', function() -- With final "\n". nvim('paste', 'line 1\nline 2\nline 3\n', true, -1) @@ -817,6 +869,19 @@ describe('API', function() eq('aabbccdd', funcs.getcmdline()) expect('') end) + it('pasting with empty last chunk in Cmdline mode', function() + local screen = Screen.new(20, 4) + screen:attach() + feed(':') + nvim('paste', 'Foo', true, 1) + nvim('paste', '', true, 3) + screen:expect([[ + | + ~ | + ~ | + :Foo^ | + ]]) + end) it('crlf=false does not break lines at CR, CRLF', function() nvim('paste', 'line 1\r\n\r\rline 2\nline 3\rline 4\r', false, -1) expect('line 1\r\n\r\rline 2\nline 3\rline 4\r') From a6eafc77ceaf2d7036aed89361b6556f46131b17 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 6 Mar 2022 06:56:24 +0800 Subject: [PATCH 6/9] fix(paste): deal with trailing new line in chunk --- runtime/lua/vim/_editor.lua | 10 +++++--- test/functional/api/vim_spec.lua | 41 ++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 42adda6e7f..26b9693189 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -128,7 +128,7 @@ local function inspect(object, options) -- luacheck: no unused end do - local tdots, tick, got_line1, undo_started = 0, 0, false, false + local tdots, tick, got_line1, undo_started, trailing_nl = 0, 0, false, false, false --- Paste handler, invoked by |nvim_paste()| when a conforming UI --- (such as the |TUI|) pastes text into the editor. @@ -160,7 +160,7 @@ do local is_first_chunk = phase < 2 local is_last_chunk = phase == -1 or phase == 3 if is_first_chunk then -- Reset flags. - tdots, tick, got_line1, undo_started = now, 0, false, false + tdots, tick, got_line1, undo_started, trailing_nl = now, 0, false, false, false end if #lines == 0 then lines = {''} @@ -203,7 +203,10 @@ do vim.api.nvim_buf_set_lines(0, row-1, row, false, lines) elseif mode:find('^[nvV\22sS\19]') then -- Normal or Visual or Select mode if mode:find('^n') then -- Normal mode - vim.api.nvim_put(lines, 'c', true, false) + -- When there was a trailing new line in the previous chunk, + -- the cursor is on the first character of the next line, + -- so paste before the cursor instead of after it. + vim.api.nvim_put(lines, 'c', not trailing_nl, false) else -- Visual or Select mode vim.api.nvim_command([[exe "silent normal! \"]]) local del_start = vim.fn.getpos("'[") @@ -221,6 +224,7 @@ do end -- put cursor at the end of the text instead of one character after it vim.fn.setpos('.', vim.fn.getpos("']")) + trailing_nl = lines[#lines] == '' else -- Don't know what to do in other modes return false end diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 761cbb4036..9818746251 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -686,6 +686,19 @@ describe('API', function() feed('u') expect('') end) + it('stream: Normal mode on empty line pasting multiple lines', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd', false, 3) + expect([[ + aaaaaa + bbbbbb + cccccc + dddddd]]) + feed('u') + expect('') + end) it('stream: Normal mode not at the end of a line', function() feed('i||0') nvim('paste', 'aaaaaa', false, 1) @@ -696,6 +709,20 @@ describe('API', function() feed('u') expect('||') end) + it('stream: Normal mode not at the end of a line pasting multiple lines', function() + feed('i||0') + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd', false, 3) + expect([[ + |aaaaaa + bbbbbb + cccccc + dddddd|]]) + feed('u') + expect('||') + end) it('stream: Normal mode at the end of a line', function() feed('i||') nvim('paste', 'aaaaaa', false, 1) @@ -706,6 +733,20 @@ describe('API', function() feed('u') expect('||') end) + it('stream: Normal mode at the end of a line pasting multiple lines', function() + feed('i||') + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd', false, 3) + expect([[ + ||aaaaaa + bbbbbb + cccccc + dddddd]]) + feed('u') + expect('||') + end) it('stream: Visual mode neither end at the end of a line', function() feed('i|xxxxxx|hvhk') nvim('paste', 'aaaaaa', false, 1) From e4ec8d7d5001ff15a7a844a6f74d4a9c090ac5c6 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 6 Mar 2022 06:56:24 +0800 Subject: [PATCH 7/9] test(paste): reorganize tests and add tests for linewise Visual mode --- test/functional/api/vim_spec.lua | 471 +++++++++++++++++++++---------- 1 file changed, 317 insertions(+), 154 deletions(-) diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 9818746251..263add599c 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -630,10 +630,6 @@ describe('API', function() end) describe('nvim_paste', function() - before_each(function() - -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. - feed('ifoou') - end) it('validates args', function() eq('Invalid phase: -2', pcall_err(request, 'nvim_paste', 'foo', true, -2)) @@ -670,166 +666,333 @@ describe('API', function() expect(expected1) end) it('stream: Insert mode', function() + -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. + feed('afoou') feed('i') nvim('paste', 'aaaaaa', false, 1) nvim('paste', 'bbbbbb', false, 2) nvim('paste', 'cccccc', false, 2) nvim('paste', 'dddddd', false, 3) expect('aaaaaabbbbbbccccccdddddd') - end) - it('stream: Normal mode on empty line', function() - nvim('paste', 'aaaaaa', false, 1) - nvim('paste', 'bbbbbb', false, 2) - nvim('paste', 'cccccc', false, 2) - nvim('paste', 'dddddd', false, 3) - expect('aaaaaabbbbbbccccccdddddd') - feed('u') + feed('u') expect('') end) - it('stream: Normal mode on empty line pasting multiple lines', function() - nvim('paste', 'aaaaaa\n', false, 1) - nvim('paste', 'bbbbbb\n', false, 2) - nvim('paste', 'cccccc\n', false, 2) - nvim('paste', 'dddddd', false, 3) - expect([[ - aaaaaa - bbbbbb - cccccc - dddddd]]) - feed('u') - expect('') + describe('stream: Normal mode', function() + describe('on empty line', function() + before_each(function() + -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. + feed('afoou') + end) + after_each(function() + feed('u') + expect('') + end) + it('pasting one line', function() + nvim('paste', 'aaaaaa', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('aaaaaabbbbbbccccccdddddd') + end) + it('pasting multiple lines', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd', false, 3) + expect([[ + aaaaaa + bbbbbb + cccccc + dddddd]]) + end) + end) + describe('not at the end of a line', function() + before_each(function() + feed('i||') + -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. + feed('afoou') + feed('0') + end) + after_each(function() + feed('u') + expect('||') + end) + it('pasting one line', function() + nvim('paste', 'aaaaaa', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('|aaaaaabbbbbbccccccdddddd|') + end) + it('pasting multiple lines', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd', false, 3) + expect([[ + |aaaaaa + bbbbbb + cccccc + dddddd|]]) + end) + end) + describe('at the end of a line', function() + before_each(function() + feed('i||') + -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. + feed('afoou') + feed('$') + end) + after_each(function() + feed('u') + expect('||') + end) + it('pasting one line', function() + nvim('paste', 'aaaaaa', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('||aaaaaabbbbbbccccccdddddd') + end) + it('pasting multiple lines', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd', false, 3) + expect([[ + ||aaaaaa + bbbbbb + cccccc + dddddd]]) + end) + end) end) - it('stream: Normal mode not at the end of a line', function() - feed('i||0') - nvim('paste', 'aaaaaa', false, 1) - nvim('paste', 'bbbbbb', false, 2) - nvim('paste', 'cccccc', false, 2) - nvim('paste', 'dddddd', false, 3) - expect('|aaaaaabbbbbbccccccdddddd|') - feed('u') - expect('||') + describe('stream: Visual mode', function() + describe('neither end at the end of a line', function() + before_each(function() + feed('i|xxxxxx|') + -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. + feed('afoou') + feed('hvhk') + end) + after_each(function() + feed('u') + expect([[ + |xxx + xxx|]]) + end) + it('with non-empty chunks', function() + nvim('paste', 'aaaaaa', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('|aaaaaabbbbbbccccccdddddd|') + end) + it('with empty first chunk', function() + nvim('paste', '', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('|bbbbbbccccccdddddd|') + end) + it('with all chunks empty', function() + nvim('paste', '', false, 1) + nvim('paste', '', false, 2) + nvim('paste', '', false, 2) + nvim('paste', '', false, 3) + expect('||') + end) + end) + describe('cursor at the end of a line', function() + before_each(function() + feed('i||xxxxxx') + -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. + feed('afoou') + feed('vko') + end) + after_each(function() + feed('u') + expect([[ + ||xxx + xxx]]) + end) + it('with non-empty chunks', function() + nvim('paste', 'aaaaaa', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('||aaaaaabbbbbbccccccdddddd') + end) + it('with empty first chunk', function() + nvim('paste', '', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('||bbbbbbccccccdddddd') + end) + end) + describe('other end at the end of a line', function() + before_each(function() + feed('i||xxxxxx') + -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. + feed('afoou') + feed('vk') + end) + after_each(function() + feed('u') + expect([[ + ||xxx + xxx]]) + end) + it('with non-empty chunks', function() + nvim('paste', 'aaaaaa', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('||aaaaaabbbbbbccccccdddddd') + end) + it('with empty first chunk', function() + nvim('paste', '', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('||bbbbbbccccccdddddd') + end) + end) end) - it('stream: Normal mode not at the end of a line pasting multiple lines', function() - feed('i||0') - nvim('paste', 'aaaaaa\n', false, 1) - nvim('paste', 'bbbbbb\n', false, 2) - nvim('paste', 'cccccc\n', false, 2) - nvim('paste', 'dddddd', false, 3) - expect([[ - |aaaaaa - bbbbbb - cccccc - dddddd|]]) - feed('u') - expect('||') - end) - it('stream: Normal mode at the end of a line', function() - feed('i||') - nvim('paste', 'aaaaaa', false, 1) - nvim('paste', 'bbbbbb', false, 2) - nvim('paste', 'cccccc', false, 2) - nvim('paste', 'dddddd', false, 3) - expect('||aaaaaabbbbbbccccccdddddd') - feed('u') - expect('||') - end) - it('stream: Normal mode at the end of a line pasting multiple lines', function() - feed('i||') - nvim('paste', 'aaaaaa\n', false, 1) - nvim('paste', 'bbbbbb\n', false, 2) - nvim('paste', 'cccccc\n', false, 2) - nvim('paste', 'dddddd', false, 3) - expect([[ - ||aaaaaa - bbbbbb - cccccc - dddddd]]) - feed('u') - expect('||') - end) - it('stream: Visual mode neither end at the end of a line', function() - feed('i|xxxxxx|hvhk') - nvim('paste', 'aaaaaa', false, 1) - nvim('paste', 'bbbbbb', false, 2) - nvim('paste', 'cccccc', false, 2) - nvim('paste', 'dddddd', false, 3) - expect('|aaaaaabbbbbbccccccdddddd|') - feed('u') - expect([[ - |xxx - xxx|]]) - end) - it('stream: Visual mode neither end at the end of a line with empty first chunk', function() - feed('i|xxxxxx|hvhk') - nvim('paste', '', false, 1) - nvim('paste', 'bbbbbb', false, 2) - nvim('paste', 'cccccc', false, 2) - nvim('paste', 'dddddd', false, 3) - expect('|bbbbbbccccccdddddd|') - feed('u') - expect([[ - |xxx - xxx|]]) - end) - it('stream: Visual mode neither end at the end of a line with all chunks empty', function() - feed('i|xxxxxx|hvhk') - nvim('paste', '', false, 1) - nvim('paste', '', false, 2) - nvim('paste', '', false, 2) - nvim('paste', '', false, 3) - expect('||') - feed('u') - expect([[ - |xxx - xxx|]]) - end) - it('stream: Visual mode cursor at the end of a line', function() - feed('i||xxxxxxvko') - nvim('paste', 'aaaaaa', false, 1) - nvim('paste', 'bbbbbb', false, 2) - nvim('paste', 'cccccc', false, 2) - nvim('paste', 'dddddd', false, 3) - expect('||aaaaaabbbbbbccccccdddddd') - feed('u') - expect([[ - ||xxx - xxx]]) - end) - it('stream: Visual mode cursor at the end of a line with empty first chunk', function() - feed('i||xxxxxxvko') - nvim('paste', '', false, 1) - nvim('paste', 'bbbbbb', false, 2) - nvim('paste', 'cccccc', false, 2) - nvim('paste', 'dddddd', false, 3) - expect('||bbbbbbccccccdddddd') - feed('u') - expect([[ - ||xxx - xxx]]) - end) - it('stream: Visual mode other end at the end of a line', function() - feed('i||xxxxxxvk') - nvim('paste', 'aaaaaa', false, 1) - nvim('paste', 'bbbbbb', false, 2) - nvim('paste', 'cccccc', false, 2) - nvim('paste', 'dddddd', false, 3) - expect('||aaaaaabbbbbbccccccdddddd') - feed('u') - expect([[ - ||xxx - xxx]]) - end) - it('stream: Visual mode other end at the end of a line with empty first chunk', function() - feed('i||xxxxxxvk') - nvim('paste', '', false, 1) - nvim('paste', 'bbbbbb', false, 2) - nvim('paste', 'cccccc', false, 2) - nvim('paste', 'dddddd', false, 3) - expect('||bbbbbbccccccdddddd') - feed('u') - expect([[ - ||xxx - xxx]]) + describe('stream: linewise Visual mode', function() + before_each(function() + feed('i123456789987654321123456789') + -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. + feed('afoou') + end) + after_each(function() + feed('u') + expect([[ + 123456789 + 987654321 + 123456789]]) + end) + describe('selecting the start of a file', function() + before_each(function() + feed('ggV') + end) + it('pasting text without final new line', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd', false, 3) + expect([[ + aaaaaa + bbbbbb + cccccc + dddddd987654321 + 123456789]]) + end) + it('pasting text with final new line', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd\n', false, 3) + expect([[ + aaaaaa + bbbbbb + cccccc + dddddd + 987654321 + 123456789]]) + end) + end) + describe('selecting the middle of a file', function() + before_each(function() + feed('2ggV') + end) + it('pasting text without final new line', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd', false, 3) + expect([[ + 123456789 + aaaaaa + bbbbbb + cccccc + dddddd123456789]]) + end) + it('pasting text with final new line', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd\n', false, 3) + expect([[ + 123456789 + aaaaaa + bbbbbb + cccccc + dddddd + 123456789]]) + end) + end) + describe('selecting the end of a file', function() + before_each(function() + feed('3ggV') + end) + it('pasting text without final new line', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd', false, 3) + expect([[ + 123456789 + 987654321 + aaaaaa + bbbbbb + cccccc + dddddd]]) + end) + it('pasting text with final new line', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd\n', false, 3) + expect([[ + 123456789 + 987654321 + aaaaaa + bbbbbb + cccccc + dddddd + ]]) + end) + end) + describe('selecting the whole file', function() + before_each(function() + feed('ggVG') + end) + it('pasting text without final new line', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd', false, 3) + expect([[ + aaaaaa + bbbbbb + cccccc + dddddd]]) + end) + it('pasting text with final new line', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd\n', false, 3) + expect([[ + aaaaaa + bbbbbb + cccccc + dddddd + ]]) + end) + end) end) it('non-streaming', function() -- With final "\n". From 3470a9c3de076ec3170525c3de66544f9836c775 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 6 Mar 2022 06:56:24 +0800 Subject: [PATCH 8/9] test(paste): add tests with virtualedit=onemore --- test/functional/api/vim_spec.lua | 695 ++++++++++++++++--------------- 1 file changed, 353 insertions(+), 342 deletions(-) diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 263add599c..f4a7d38d31 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -636,363 +636,374 @@ describe('API', function() eq('Invalid phase: 4', pcall_err(request, 'nvim_paste', 'foo', true, 4)) end) - it('stream: multiple chunks form one undo-block', function() - nvim('paste', '1/chunk 1 (start)\n', true, 1) - nvim('paste', '1/chunk 2 (end)\n', true, 3) - local expected1 = [[ - 1/chunk 1 (start) - 1/chunk 2 (end) - ]] - expect(expected1) - nvim('paste', '2/chunk 1 (start)\n', true, 1) - nvim('paste', '2/chunk 2\n', true, 2) - expect([[ - 1/chunk 1 (start) - 1/chunk 2 (end) - 2/chunk 1 (start) - 2/chunk 2 - ]]) - nvim('paste', '2/chunk 3\n', true, 2) - nvim('paste', '2/chunk 4 (end)\n', true, 3) - expect([[ - 1/chunk 1 (start) - 1/chunk 2 (end) - 2/chunk 1 (start) - 2/chunk 2 - 2/chunk 3 - 2/chunk 4 (end) - ]]) - feed('u') -- Undo. - expect(expected1) - end) - it('stream: Insert mode', function() - -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. - feed('afoou') - feed('i') - nvim('paste', 'aaaaaa', false, 1) - nvim('paste', 'bbbbbb', false, 2) - nvim('paste', 'cccccc', false, 2) - nvim('paste', 'dddddd', false, 3) - expect('aaaaaabbbbbbccccccdddddd') - feed('u') - expect('') - end) - describe('stream: Normal mode', function() - describe('on empty line', function() - before_each(function() - -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. - feed('afoou') - end) - after_each(function() - feed('u') - expect('') - end) - it('pasting one line', function() - nvim('paste', 'aaaaaa', false, 1) - nvim('paste', 'bbbbbb', false, 2) - nvim('paste', 'cccccc', false, 2) - nvim('paste', 'dddddd', false, 3) - expect('aaaaaabbbbbbccccccdddddd') - end) - it('pasting multiple lines', function() - nvim('paste', 'aaaaaa\n', false, 1) - nvim('paste', 'bbbbbb\n', false, 2) - nvim('paste', 'cccccc\n', false, 2) - nvim('paste', 'dddddd', false, 3) - expect([[ - aaaaaa - bbbbbb - cccccc - dddddd]]) - end) + local function run_streamed_paste_tests() + it('stream: multiple chunks form one undo-block', function() + nvim('paste', '1/chunk 1 (start)\n', true, 1) + nvim('paste', '1/chunk 2 (end)\n', true, 3) + local expected1 = [[ + 1/chunk 1 (start) + 1/chunk 2 (end) + ]] + expect(expected1) + nvim('paste', '2/chunk 1 (start)\n', true, 1) + nvim('paste', '2/chunk 2\n', true, 2) + expect([[ + 1/chunk 1 (start) + 1/chunk 2 (end) + 2/chunk 1 (start) + 2/chunk 2 + ]]) + nvim('paste', '2/chunk 3\n', true, 2) + nvim('paste', '2/chunk 4 (end)\n', true, 3) + expect([[ + 1/chunk 1 (start) + 1/chunk 2 (end) + 2/chunk 1 (start) + 2/chunk 2 + 2/chunk 3 + 2/chunk 4 (end) + ]]) + feed('u') -- Undo. + expect(expected1) end) - describe('not at the end of a line', function() - before_each(function() - feed('i||') - -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. - feed('afoou') - feed('0') - end) - after_each(function() - feed('u') - expect('||') - end) - it('pasting one line', function() - nvim('paste', 'aaaaaa', false, 1) - nvim('paste', 'bbbbbb', false, 2) - nvim('paste', 'cccccc', false, 2) - nvim('paste', 'dddddd', false, 3) - expect('|aaaaaabbbbbbccccccdddddd|') - end) - it('pasting multiple lines', function() - nvim('paste', 'aaaaaa\n', false, 1) - nvim('paste', 'bbbbbb\n', false, 2) - nvim('paste', 'cccccc\n', false, 2) - nvim('paste', 'dddddd', false, 3) - expect([[ - |aaaaaa - bbbbbb - cccccc - dddddd|]]) - end) - end) - describe('at the end of a line', function() - before_each(function() - feed('i||') - -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. - feed('afoou') - feed('$') - end) - after_each(function() - feed('u') - expect('||') - end) - it('pasting one line', function() - nvim('paste', 'aaaaaa', false, 1) - nvim('paste', 'bbbbbb', false, 2) - nvim('paste', 'cccccc', false, 2) - nvim('paste', 'dddddd', false, 3) - expect('||aaaaaabbbbbbccccccdddddd') - end) - it('pasting multiple lines', function() - nvim('paste', 'aaaaaa\n', false, 1) - nvim('paste', 'bbbbbb\n', false, 2) - nvim('paste', 'cccccc\n', false, 2) - nvim('paste', 'dddddd', false, 3) - expect([[ - ||aaaaaa - bbbbbb - cccccc - dddddd]]) - end) - end) - end) - describe('stream: Visual mode', function() - describe('neither end at the end of a line', function() - before_each(function() - feed('i|xxxxxx|') - -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. - feed('afoou') - feed('hvhk') - end) - after_each(function() - feed('u') - expect([[ - |xxx - xxx|]]) - end) - it('with non-empty chunks', function() - nvim('paste', 'aaaaaa', false, 1) - nvim('paste', 'bbbbbb', false, 2) - nvim('paste', 'cccccc', false, 2) - nvim('paste', 'dddddd', false, 3) - expect('|aaaaaabbbbbbccccccdddddd|') - end) - it('with empty first chunk', function() - nvim('paste', '', false, 1) - nvim('paste', 'bbbbbb', false, 2) - nvim('paste', 'cccccc', false, 2) - nvim('paste', 'dddddd', false, 3) - expect('|bbbbbbccccccdddddd|') - end) - it('with all chunks empty', function() - nvim('paste', '', false, 1) - nvim('paste', '', false, 2) - nvim('paste', '', false, 2) - nvim('paste', '', false, 3) - expect('||') - end) - end) - describe('cursor at the end of a line', function() - before_each(function() - feed('i||xxxxxx') - -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. - feed('afoou') - feed('vko') - end) - after_each(function() - feed('u') - expect([[ - ||xxx - xxx]]) - end) - it('with non-empty chunks', function() - nvim('paste', 'aaaaaa', false, 1) - nvim('paste', 'bbbbbb', false, 2) - nvim('paste', 'cccccc', false, 2) - nvim('paste', 'dddddd', false, 3) - expect('||aaaaaabbbbbbccccccdddddd') - end) - it('with empty first chunk', function() - nvim('paste', '', false, 1) - nvim('paste', 'bbbbbb', false, 2) - nvim('paste', 'cccccc', false, 2) - nvim('paste', 'dddddd', false, 3) - expect('||bbbbbbccccccdddddd') - end) - end) - describe('other end at the end of a line', function() - before_each(function() - feed('i||xxxxxx') - -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. - feed('afoou') - feed('vk') - end) - after_each(function() - feed('u') - expect([[ - ||xxx - xxx]]) - end) - it('with non-empty chunks', function() - nvim('paste', 'aaaaaa', false, 1) - nvim('paste', 'bbbbbb', false, 2) - nvim('paste', 'cccccc', false, 2) - nvim('paste', 'dddddd', false, 3) - expect('||aaaaaabbbbbbccccccdddddd') - end) - it('with empty first chunk', function() - nvim('paste', '', false, 1) - nvim('paste', 'bbbbbb', false, 2) - nvim('paste', 'cccccc', false, 2) - nvim('paste', 'dddddd', false, 3) - expect('||bbbbbbccccccdddddd') - end) - end) - end) - describe('stream: linewise Visual mode', function() - before_each(function() - feed('i123456789987654321123456789') + it('stream: Insert mode', function() -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. feed('afoou') + feed('i') + nvim('paste', 'aaaaaa', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('aaaaaabbbbbbccccccdddddd') + feed('u') + expect('') end) - after_each(function() - feed('u') - expect([[ - 123456789 - 987654321 - 123456789]]) - end) - describe('selecting the start of a file', function() - before_each(function() - feed('ggV') - end) - it('pasting text without final new line', function() - nvim('paste', 'aaaaaa\n', false, 1) - nvim('paste', 'bbbbbb\n', false, 2) - nvim('paste', 'cccccc\n', false, 2) - nvim('paste', 'dddddd', false, 3) - expect([[ - aaaaaa - bbbbbb - cccccc - dddddd987654321 - 123456789]]) - end) - it('pasting text with final new line', function() - nvim('paste', 'aaaaaa\n', false, 1) - nvim('paste', 'bbbbbb\n', false, 2) - nvim('paste', 'cccccc\n', false, 2) - nvim('paste', 'dddddd\n', false, 3) - expect([[ - aaaaaa - bbbbbb - cccccc - dddddd - 987654321 - 123456789]]) - end) - end) - describe('selecting the middle of a file', function() - before_each(function() - feed('2ggV') - end) - it('pasting text without final new line', function() - nvim('paste', 'aaaaaa\n', false, 1) - nvim('paste', 'bbbbbb\n', false, 2) - nvim('paste', 'cccccc\n', false, 2) - nvim('paste', 'dddddd', false, 3) - expect([[ - 123456789 - aaaaaa - bbbbbb - cccccc - dddddd123456789]]) - end) - it('pasting text with final new line', function() - nvim('paste', 'aaaaaa\n', false, 1) - nvim('paste', 'bbbbbb\n', false, 2) - nvim('paste', 'cccccc\n', false, 2) - nvim('paste', 'dddddd\n', false, 3) - expect([[ - 123456789 - aaaaaa - bbbbbb - cccccc - dddddd - 123456789]]) - end) - end) - describe('selecting the end of a file', function() - before_each(function() - feed('3ggV') - end) - it('pasting text without final new line', function() - nvim('paste', 'aaaaaa\n', false, 1) - nvim('paste', 'bbbbbb\n', false, 2) - nvim('paste', 'cccccc\n', false, 2) - nvim('paste', 'dddddd', false, 3) - expect([[ - 123456789 - 987654321 + describe('stream: Normal mode', function() + describe('on empty line', function() + before_each(function() + -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. + feed('afoou') + end) + after_each(function() + feed('u') + expect('') + end) + it('pasting one line', function() + nvim('paste', 'aaaaaa', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('aaaaaabbbbbbccccccdddddd') + end) + it('pasting multiple lines', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd', false, 3) + expect([[ aaaaaa bbbbbb cccccc dddddd]]) + end) end) - it('pasting text with final new line', function() - nvim('paste', 'aaaaaa\n', false, 1) - nvim('paste', 'bbbbbb\n', false, 2) - nvim('paste', 'cccccc\n', false, 2) - nvim('paste', 'dddddd\n', false, 3) + describe('not at the end of a line', function() + before_each(function() + feed('i||') + -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. + feed('afoou') + feed('0') + end) + after_each(function() + feed('u') + expect('||') + end) + it('pasting one line', function() + nvim('paste', 'aaaaaa', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('|aaaaaabbbbbbccccccdddddd|') + end) + it('pasting multiple lines', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd', false, 3) + expect([[ + |aaaaaa + bbbbbb + cccccc + dddddd|]]) + end) + end) + describe('at the end of a line', function() + before_each(function() + feed('i||') + -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. + feed('afoou') + feed('2|') + end) + after_each(function() + feed('u') + expect('||') + end) + it('pasting one line', function() + nvim('paste', 'aaaaaa', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('||aaaaaabbbbbbccccccdddddd') + end) + it('pasting multiple lines', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd', false, 3) + expect([[ + ||aaaaaa + bbbbbb + cccccc + dddddd]]) + end) + end) + end) + describe('stream: Visual mode', function() + describe('neither end at the end of a line', function() + before_each(function() + feed('i|xxxxxx|') + -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. + feed('afoou') + feed('3|vhk') + end) + after_each(function() + feed('u') + expect([[ + |xxx + xxx|]]) + end) + it('with non-empty chunks', function() + nvim('paste', 'aaaaaa', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('|aaaaaabbbbbbccccccdddddd|') + end) + it('with empty first chunk', function() + nvim('paste', '', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('|bbbbbbccccccdddddd|') + end) + it('with all chunks empty', function() + nvim('paste', '', false, 1) + nvim('paste', '', false, 2) + nvim('paste', '', false, 2) + nvim('paste', '', false, 3) + expect('||') + end) + end) + describe('cursor at the end of a line', function() + before_each(function() + feed('i||xxxxxx') + -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. + feed('afoou') + feed('3|vko') + end) + after_each(function() + feed('u') + expect([[ + ||xxx + xxx]]) + end) + it('with non-empty chunks', function() + nvim('paste', 'aaaaaa', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('||aaaaaabbbbbbccccccdddddd') + end) + it('with empty first chunk', function() + nvim('paste', '', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('||bbbbbbccccccdddddd') + end) + end) + describe('other end at the end of a line', function() + before_each(function() + feed('i||xxxxxx') + -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. + feed('afoou') + feed('3|vk') + end) + after_each(function() + feed('u') + expect([[ + ||xxx + xxx]]) + end) + it('with non-empty chunks', function() + nvim('paste', 'aaaaaa', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('||aaaaaabbbbbbccccccdddddd') + end) + it('with empty first chunk', function() + nvim('paste', '', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('||bbbbbbccccccdddddd') + end) + end) + end) + describe('stream: linewise Visual mode', function() + before_each(function() + feed('i123456789987654321123456789') + -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. + feed('afoou') + end) + after_each(function() + feed('u') expect([[ 123456789 987654321 - aaaaaa - bbbbbb - cccccc - dddddd - ]]) + 123456789]]) + end) + describe('selecting the start of a file', function() + before_each(function() + feed('ggV') + end) + it('pasting text without final new line', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd', false, 3) + expect([[ + aaaaaa + bbbbbb + cccccc + dddddd987654321 + 123456789]]) + end) + it('pasting text with final new line', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd\n', false, 3) + expect([[ + aaaaaa + bbbbbb + cccccc + dddddd + 987654321 + 123456789]]) + end) + end) + describe('selecting the middle of a file', function() + before_each(function() + feed('2ggV') + end) + it('pasting text without final new line', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd', false, 3) + expect([[ + 123456789 + aaaaaa + bbbbbb + cccccc + dddddd123456789]]) + end) + it('pasting text with final new line', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd\n', false, 3) + expect([[ + 123456789 + aaaaaa + bbbbbb + cccccc + dddddd + 123456789]]) + end) + end) + describe('selecting the end of a file', function() + before_each(function() + feed('3ggV') + end) + it('pasting text without final new line', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd', false, 3) + expect([[ + 123456789 + 987654321 + aaaaaa + bbbbbb + cccccc + dddddd]]) + end) + it('pasting text with final new line', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd\n', false, 3) + expect([[ + 123456789 + 987654321 + aaaaaa + bbbbbb + cccccc + dddddd + ]]) + end) + end) + describe('selecting the whole file', function() + before_each(function() + feed('ggVG') + end) + it('pasting text without final new line', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd', false, 3) + expect([[ + aaaaaa + bbbbbb + cccccc + dddddd]]) + end) + it('pasting text with final new line', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd\n', false, 3) + expect([[ + aaaaaa + bbbbbb + cccccc + dddddd + ]]) + end) end) end) - describe('selecting the whole file', function() - before_each(function() - feed('ggVG') - end) - it('pasting text without final new line', function() - nvim('paste', 'aaaaaa\n', false, 1) - nvim('paste', 'bbbbbb\n', false, 2) - nvim('paste', 'cccccc\n', false, 2) - nvim('paste', 'dddddd', false, 3) - expect([[ - aaaaaa - bbbbbb - cccccc - dddddd]]) - end) - it('pasting text with final new line', function() - nvim('paste', 'aaaaaa\n', false, 1) - nvim('paste', 'bbbbbb\n', false, 2) - nvim('paste', 'cccccc\n', false, 2) - nvim('paste', 'dddddd\n', false, 3) - expect([[ - aaaaaa - bbbbbb - cccccc - dddddd - ]]) - end) + end + describe('without virtualedit,', function() + run_streamed_paste_tests() + end) + describe('with virtualedit=onemore,', function() + before_each(function() + command('set virtualedit=onemore') end) + run_streamed_paste_tests() end) it('non-streaming', function() -- With final "\n". From e263afc0e972d11d3b9b663c3ac0b5575c4deb88 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 15 Mar 2022 06:04:50 +0800 Subject: [PATCH 9/9] fix(paste): escape control characters in Cmdline mode --- runtime/lua/vim/_editor.lua | 3 ++- test/functional/api/vim_spec.lua | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 26b9693189..d4db4850bd 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -175,7 +175,8 @@ do if not got_line1 then got_line1 = (#lines > 1) vim.api.nvim_set_option('paste', true) -- For nvim_input(). - local line1 = lines[1]:gsub('<', ''):gsub('[\r\n\012\027]', ' ') -- Scrub. + -- Escape "<" and control characters + local line1 = lines[1]:gsub('<', ''):gsub('(%c)', '\022%1') vim.api.nvim_input(line1) vim.api.nvim_set_option('paste', false) end diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index f4a7d38d31..af6872760a 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -1097,6 +1097,18 @@ describe('API', function() :Foo^ | ]]) end) + it('pasting text with control characters in Cmdline mode', function() + local screen = Screen.new(20, 4) + screen:attach() + feed(':') + nvim('paste', 'normal! \023\022\006\027', true, -1) + screen:expect([[ + | + ~ | + ~ | + :normal! ^W^V^F^[^ | + ]]) + end) it('crlf=false does not break lines at CR, CRLF', function() nvim('paste', 'line 1\r\n\r\rline 2\nline 3\rline 4\r', false, -1) expect('line 1\r\n\r\rline 2\nline 3\rline 4\r')