diff options
-rw-r--r-- | runtime/doc/api.txt | 6 | ||||
-rw-r--r-- | runtime/lua/vim/_meta/api.lua | 4 | ||||
-rw-r--r-- | src/nvim/api/vim.c | 39 | ||||
-rw-r--r-- | test/functional/api/vim_spec.lua | 69 | ||||
-rw-r--r-- | test/functional/terminal/tui_spec.lua | 35 |
5 files changed, 125 insertions, 28 deletions
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index a7c71d9a32..e7a8e788b9 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -1155,8 +1155,8 @@ nvim_paste({data}, {crlf}, {phase}) *nvim_paste()* Errors ('nomodifiable', `vim.paste()` failure, …) are reflected in `err` but do not affect the return value (which is strictly decided by - `vim.paste()`). On error, subsequent calls are ignored ("drained") until - the next paste is initiated (phase 1 or -1). + `vim.paste()`). On error or cancel, subsequent calls are ignored + ("drained") until the next paste is initiated (phase 1 or -1). Attributes: ~ not allowed when |textlock| is active @@ -1173,7 +1173,7 @@ nvim_paste({data}, {crlf}, {phase}) *nvim_paste()* Return: ~ • true: Client may continue pasting. - • false: Client must cancel the paste. + • false: Client should cancel the paste. nvim_put({lines}, {type}, {after}, {follow}) *nvim_put()* Puts text at cursor, in any mode. diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index f14606fc99..b53a9ad321 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -1816,8 +1816,8 @@ function vim.api.nvim_parse_expression(expr, flags, highlight) end --- --- Errors ('nomodifiable', `vim.paste()` failure, …) are reflected in `err` --- but do not affect the return value (which is strictly decided by ---- `vim.paste()`). On error, subsequent calls are ignored ("drained") until ---- the next paste is initiated (phase 1 or -1). +--- `vim.paste()`). On error or cancel, subsequent calls are ignored +--- ("drained") until the next paste is initiated (phase 1 or -1). --- --- @param data string Multiline input. May be binary (containing NUL bytes). --- @param crlf boolean Also break lines at CR and CRLF. diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index c1add75af9..b31accd3b0 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1219,8 +1219,8 @@ void nvim_set_current_tabpage(Tabpage tabpage, Error *err) /// /// Errors ('nomodifiable', `vim.paste()` failure, …) are reflected in `err` /// but do not affect the return value (which is strictly decided by -/// `vim.paste()`). On error, subsequent calls are ignored ("drained") until -/// the next paste is initiated (phase 1 or -1). +/// `vim.paste()`). On error or cancel, subsequent calls are ignored +/// ("drained") until the next paste is initiated (phase 1 or -1). /// /// @param data Multiline input. May be binary (containing NUL bytes). /// @param crlf Also break lines at CR and CRLF. @@ -1233,20 +1233,19 @@ void nvim_set_current_tabpage(Tabpage tabpage, Error *err) /// @param[out] err Error details, if any /// @return /// - true: Client may continue pasting. -/// - false: Client must cancel the paste. +/// - false: Client should cancel the paste. Boolean nvim_paste(String data, Boolean crlf, Integer phase, Arena *arena, Error *err) FUNC_API_SINCE(6) FUNC_API_TEXTLOCK_ALLOW_CMDWIN { - static bool draining = false; - bool cancel = false; + static bool cancelled = false; VALIDATE_INT((phase >= -1 && phase <= 3), "phase", phase, { return false; }); if (phase == -1 || phase == 1) { // Start of paste-stream. - draining = false; - } else if (draining) { + cancelled = false; + } else if (cancelled) { // Skip remaining chunks. Report error only once per "stream". goto theend; } @@ -1255,26 +1254,26 @@ Boolean nvim_paste(String data, Boolean crlf, Integer phase, Arena *arena, Error ADD_C(args, ARRAY_OBJ(lines)); ADD_C(args, INTEGER_OBJ(phase)); Object rv = NLUA_EXEC_STATIC("return vim.paste(...)", args, kRetNilBool, arena, err); - if (ERROR_SET(err)) { - draining = true; - goto theend; + // vim.paste() decides if client should cancel. + if (ERROR_SET(err) || (rv.type == kObjectTypeBoolean && !rv.data.boolean)) { + cancelled = true; } - if (phase == -1 || phase == 1) { + if (!cancelled && (phase == -1 || phase == 1)) { paste_store(kFalse, NULL_STRING, crlf); } - // vim.paste() decides if client should cancel. Errors do NOT cancel: we - // want to drain remaining chunks (rather than divert them to main input). - cancel = (rv.type == kObjectTypeBoolean && !rv.data.boolean); - if (!cancel) { + if (!cancelled) { paste_store(kNone, data, crlf); } -theend: - if (cancel || phase == -1 || phase == 3) { // End of paste-stream. - draining = false; + if (phase == 3 || phase == (cancelled ? 2 : -1)) { paste_store(kTrue, NULL_STRING, crlf); } - - return !cancel; +theend: + ; + bool retval = !cancelled; + if (phase == -1 || phase == 3) { // End of paste-stream. + cancelled = false; + } + return retval; } /// Puts text at cursor, in any mode. diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index b35ccb0c40..1b34945f13 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -1358,9 +1358,72 @@ describe('API', function() test_paste_repeat_visual_select(true) end) end) - it('vim.paste() failure', function() - api.nvim_exec_lua('vim.paste = (function(lines, phase) error("fake fail") end)', {}) - eq('fake fail', pcall_err(request, 'nvim_paste', 'line 1\nline 2\nline 3', false, 1)) + local function test_paste_cancel_error(is_error) + before_each(function() + exec_lua(([[ + vim.paste = (function(overridden) + return function(lines, phase) + for i, line in ipairs(lines) do + if line == 'CANCEL' then + %s + end + end + return overridden(lines, phase) + end + end)(vim.paste) + ]]):format(is_error and 'error("fake fail")' or 'return false')) + end) + local function check_paste_cancel_error(data, crlf, phase) + if is_error then + eq('fake fail', pcall_err(api.nvim_paste, data, crlf, phase)) + else + eq(false, api.nvim_paste(data, crlf, phase)) + end + end + it('in phase -1', function() + feed('A') + check_paste_cancel_error('CANCEL', true, -1) + feed('<Esc>') + expect('') + feed('.') + expect('') + end) + it('in phase 1', function() + feed('A') + check_paste_cancel_error('CANCEL', true, 1) + feed('<Esc>') + expect('') + feed('.') + expect('') + end) + it('in phase 2', function() + feed('A') + eq(true, api.nvim_paste('aaa', true, 1)) + expect('aaa') + check_paste_cancel_error('CANCEL', true, 2) + feed('<Esc>') + expect('aaa') + feed('.') + expect('aaaaaa') + end) + it('in phase 3', function() + feed('A') + eq(true, api.nvim_paste('aaa', true, 1)) + expect('aaa') + eq(true, api.nvim_paste('bbb', true, 2)) + expect('aaabbb') + check_paste_cancel_error('CANCEL', true, 3) + feed('<Esc>') + expect('aaabbb') + feed('.') + expect('aaabbbaaabbb') + end) + end + describe('vim.paste() cancel', function() + test_paste_cancel_error(false) + end) + describe('vim.paste() error', function() + test_paste_cancel_error(true) end) end) diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index b85ddec0f0..a7d87bb231 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -1298,6 +1298,41 @@ describe('TUI', function() expect_child_buf_lines({ 'foo', '' }) end) + it('paste: vim.paste() cancel (retval=false) with streaming #30462', function() + child_session:request( + 'nvim_exec_lua', + [[ + vim.paste = (function(overridden) + return function(lines, phase) + for i, line in ipairs(lines) do + if line:find('!') then + return false + end + end + return overridden(lines, phase) + end + end)(vim.paste) + ]], + {} + ) + feed_data('A') + wait_for_mode('i') + feed_data('\027[200~aaa') + expect_child_buf_lines({ 'aaa' }) + feed_data('bbb') + expect_child_buf_lines({ 'aaabbb' }) + feed_data('ccc!') -- This chunk is cancelled. + expect_child_buf_lines({ 'aaabbb' }) + feed_data('ddd\027[201~') -- This chunk is ignored. + expect_child_buf_lines({ 'aaabbb' }) + feed_data('\027[27u') + wait_for_mode('n') + feed_data('.') -- Dot-repeat only includes chunks actually pasted. + expect_child_buf_lines({ 'aaabbbaaabbb' }) + feed_data('$\027[200~eee\027[201~') -- A following paste works normally. + expect_child_buf_lines({ 'aaabbbaaabbbeee' }) + end) + it("paste: 'nomodifiable' buffer", function() child_session:request('nvim_command', 'set nomodifiable') child_session:request( |