mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	fix(paste): avoid edges cases caused by empty chunk
This commit is contained in:
		| @@ -128,7 +128,7 @@ local function inspect(object, options)  -- luacheck: no unused | |||||||
| end | end | ||||||
|  |  | ||||||
| do | 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 |   --- Paste handler, invoked by |nvim_paste()| when a conforming UI | ||||||
|   --- (such as the |TUI|) pastes text into the editor. |   --- (such as the |TUI|) pastes text into the editor. | ||||||
| @@ -158,8 +158,17 @@ do | |||||||
|   function vim.paste(lines, phase) |   function vim.paste(lines, phase) | ||||||
|     local now = vim.loop.now() |     local now = vim.loop.now() | ||||||
|     local is_first_chunk = phase < 2 |     local is_first_chunk = phase < 2 | ||||||
|  |     local is_last_chunk = phase == -1 or phase == 3 | ||||||
|     if is_first_chunk then  -- Reset flags. |     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 |     end | ||||||
|     -- Note: mode doesn't always start with "c" in cmdline mode, so use getcmdtype() instead. |     -- 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 vim.fn.getcmdtype() ~= '' then  -- cmdline-mode: paste only 1 line. | ||||||
| @@ -173,7 +182,7 @@ do | |||||||
|       return true |       return true | ||||||
|     end |     end | ||||||
|     local mode = vim.api.nvim_get_mode().mode |     local mode = vim.api.nvim_get_mode().mode | ||||||
|     if not is_first_chunk then |     if undo_started then | ||||||
|       vim.api.nvim_command('undojoin') |       vim.api.nvim_command('undojoin') | ||||||
|     end |     end | ||||||
|     if mode:find('^i') or mode:find('^n?t') then  -- Insert mode or Terminal buffer |     if mode:find('^i') or mode:find('^n?t') then  -- Insert mode or Terminal buffer | ||||||
| @@ -190,7 +199,6 @@ do | |||||||
|       local firstline = lines[1] |       local firstline = lines[1] | ||||||
|       firstline = bufline:sub(1, col)..firstline |       firstline = bufline:sub(1, col)..firstline | ||||||
|       lines[1] = firstline |       lines[1] = firstline | ||||||
|       -- FIXME: #lines can be 0 |  | ||||||
|       lines[#lines] = lines[#lines]..bufline:sub(col + nchars + 1, bufline:len()) |       lines[#lines] = lines[#lines]..bufline:sub(col + nchars + 1, bufline:len()) | ||||||
|       vim.api.nvim_buf_set_lines(0, row-1, row, false, lines) |       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 |     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 |     else  -- Don't know what to do in other modes | ||||||
|       return false |       return false | ||||||
|     end |     end | ||||||
|  |     undo_started = true | ||||||
|     if phase ~= -1 and (now - tdots >= 100) then |     if phase ~= -1 and (now - tdots >= 100) then | ||||||
|       local dots = ('.'):rep(tick % 4) |       local dots = ('.'):rep(tick % 4) | ||||||
|       tdots = now |       tdots = now | ||||||
| @@ -224,7 +233,7 @@ do | |||||||
|       -- message when there are zero dots. |       -- message when there are zero dots. | ||||||
|       vim.api.nvim_command(('echo "%s"'):format(dots)) |       vim.api.nvim_command(('echo "%s"'):format(dots)) | ||||||
|     end |     end | ||||||
|     if phase == -1 or phase == 3 then |     if is_last_chunk then | ||||||
|       vim.api.nvim_command('redraw'..(tick > 1 and '|echo ""' or '')) |       vim.api.nvim_command('redraw'..(tick > 1 and '|echo ""' or '')) | ||||||
|     end |     end | ||||||
|     return true  -- Paste will not continue if not returning `true`. |     return true  -- Paste will not continue if not returning `true`. | ||||||
|   | |||||||
| @@ -630,6 +630,10 @@ describe('API', function() | |||||||
|   end) |   end) | ||||||
|  |  | ||||||
|   describe('nvim_paste', function() |   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() |     it('validates args', function() | ||||||
|       eq('Invalid phase: -2', |       eq('Invalid phase: -2', | ||||||
|         pcall_err(request, 'nvim_paste', 'foo', true, -2)) |         pcall_err(request, 'nvim_paste', 'foo', true, -2)) | ||||||
| @@ -702,7 +706,7 @@ describe('API', function() | |||||||
|       feed('u') |       feed('u') | ||||||
|       expect('||') |       expect('||') | ||||||
|     end) |     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') |       feed('i|xxx<CR>xxx|<Esc>hvhk') | ||||||
|       nvim('paste', 'aaaaaa', false, 1) |       nvim('paste', 'aaaaaa', false, 1) | ||||||
|       nvim('paste', 'bbbbbb', false, 2) |       nvim('paste', 'bbbbbb', false, 2) | ||||||
| @@ -714,6 +718,30 @@ describe('API', function() | |||||||
|         |xxx |         |xxx | ||||||
|         xxx|]]) |         xxx|]]) | ||||||
|     end) |     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() |     it('stream: Visual mode cursor at the end of a line', function() | ||||||
|       feed('i||xxx<CR>xxx<Esc>vko') |       feed('i||xxx<CR>xxx<Esc>vko') | ||||||
|       nvim('paste', 'aaaaaa', false, 1) |       nvim('paste', 'aaaaaa', false, 1) | ||||||
| @@ -726,6 +754,18 @@ describe('API', function() | |||||||
|         ||xxx |         ||xxx | ||||||
|         xxx]]) |         xxx]]) | ||||||
|     end) |     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() |     it('stream: Visual mode other end at the end of a line', function() | ||||||
|       feed('i||xxx<CR>xxx<Esc>vk') |       feed('i||xxx<CR>xxx<Esc>vk') | ||||||
|       nvim('paste', 'aaaaaa', false, 1) |       nvim('paste', 'aaaaaa', false, 1) | ||||||
| @@ -738,6 +778,18 @@ describe('API', function() | |||||||
|         ||xxx |         ||xxx | ||||||
|         xxx]]) |         xxx]]) | ||||||
|     end) |     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() |     it('non-streaming', function() | ||||||
|       -- With final "\n". |       -- With final "\n". | ||||||
|       nvim('paste', 'line 1\nline 2\nline 3\n', true, -1) |       nvim('paste', 'line 1\nline 2\nline 3\n', true, -1) | ||||||
| @@ -817,6 +869,19 @@ describe('API', function() | |||||||
|       eq('aabbccdd', funcs.getcmdline()) |       eq('aabbccdd', funcs.getcmdline()) | ||||||
|       expect('') |       expect('') | ||||||
|     end) |     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() |     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) |       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') |       expect('line 1\r\n\r\rline 2\nline 3\rline 4\r') | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 zeertzjq
					zeertzjq