diff options
-rw-r--r-- | runtime/lua/vim/_editor.lua | 19 | ||||
-rw-r--r-- | 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('ifoo<Esc>u') + 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|xxx<CR>xxx|<Esc>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|xxx<CR>xxx|<Esc>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|xxx<CR>xxx|<Esc>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||xxx<CR>xxx<Esc>vko') 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||xxx<CR>xxx<Esc>vko') + 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||xxx<CR>xxx<Esc>vk') 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||xxx<CR>xxx<Esc>vk') + 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') |