diff options
author | Justin M. Keyes <justinkz@gmail.com> | 2019-08-26 20:57:57 +0200 |
---|---|---|
committer | Justin M. Keyes <justinkz@gmail.com> | 2019-08-27 22:13:45 +0200 |
commit | ed60015266356b3c0c42aa34698d9287f22fcba1 (patch) | |
tree | c1ae9ec1da22f94bce1157cd42a74112c2849746 | |
parent | 4344ac11119abd20ba911d72cf540321277dd150 (diff) | |
download | rneovim-ed60015266356b3c0c42aa34698d9287f22fcba1.tar.gz rneovim-ed60015266356b3c0c42aa34698d9287f22fcba1.tar.bz2 rneovim-ed60015266356b3c0c42aa34698d9287f22fcba1.zip |
paste: handle vim.paste() failure
- Show error only once per "paste stream".
- Drain remaining chunks until phase=3.
- Lay groundwork for "cancel".
- Constrain semantics of "cancel" to mean "client must stop"; it is
unrelated to presence of error(s).
-rw-r--r-- | src/nvim/api/vim.c | 52 | ||||
-rw-r--r-- | src/nvim/tui/input.c | 9 | ||||
-rw-r--r-- | test/functional/api/vim_spec.lua | 24 | ||||
-rw-r--r-- | test/functional/terminal/tui_spec.lua | 51 |
4 files changed, 113 insertions, 23 deletions
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 3631fbff66..910b76d02d 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1206,7 +1206,7 @@ Dictionary nvim_get_namespaces(void) return retval; } -/// Paste +/// Pastes at cursor, in any mode. /// /// Invokes the `vim.paste` handler, which handles each mode appropriately. /// Sets redo/undo. Faster than |nvim_input()|. @@ -1219,29 +1219,44 @@ Dictionary nvim_get_namespaces(void) /// - 2: continues the paste (zero or more times) /// - 3: ends the paste (exactly once) /// @param[out] err Error details, if any -/// @return true if paste should continue, false if paste was canceled +/// @return +/// - true: Client may continue pasting. +/// - false: Client must cancel the paste. Boolean nvim_paste(String data, Integer phase, Error *err) FUNC_API_SINCE(6) { + static bool draining = false; + bool cancel = false; + if (phase < -1 || phase > 3) { api_set_error(err, kErrorTypeValidation, "Invalid phase: %"PRId64, phase); return false; } - if (!(State & CMDLINE) && !(State & INSERT) && (phase == -1 || phase == 1)) { - ResetRedobuff(); - AppendCharToRedobuff('a'); // Dot-repeat. + Array args = ARRAY_DICT_INIT; + Object rv = OBJECT_INIT; + if (phase == -1 || phase == 1) { // Start of paste-stream. + draining = false; + } else if (draining) { + // Skip remaining chunks. Report error only once per "stream". + goto theend; } Array lines = string_to_array(data); - Array args = ARRAY_DICT_INIT; ADD(args, ARRAY_OBJ(lines)); ADD(args, INTEGER_OBJ(phase)); - Object rv - = nvim_execute_lua(STATIC_CSTR_AS_STRING("return vim._paste(...)"), - args, err); - // Abort paste if handler does not return true. - bool ok = !ERROR_SET(err) - && (rv.type == kObjectTypeBoolean && rv.data.boolean); - if (ok && !(State & CMDLINE)) { // Dot-repeat. + rv = nvim_execute_lua(STATIC_CSTR_AS_STRING("return vim._paste(...)"), args, + err); + if (ERROR_SET(err)) { + draining = true; + goto theend; + } + if (!(State & CMDLINE) && !(State & INSERT) && (phase == -1 || phase == 1)) { + ResetRedobuff(); + AppendCharToRedobuff('a'); // Dot-repeat. + } + // 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 && !(State & CMDLINE)) { // Dot-repeat. for (size_t i = 0; i < lines.size; i++) { String s = lines.items[i].data.string; assert(data.size <= INT_MAX); @@ -1252,20 +1267,21 @@ Boolean nvim_paste(String data, Integer phase, Error *err) } } } - api_free_object(rv); - api_free_array(args); if (!(State & CMDLINE) && !(State & INSERT) && (phase == -1 || phase == 3)) { AppendCharToRedobuff(ESC); // Dot-repeat. } - if (phase == -1 || phase == 3) { +theend: + api_free_object(rv); + api_free_array(args); + if (cancel || phase == -1 || phase == 3) { // End of paste-stream. // XXX: Tickle main loop to ensure cursor is updated. loop_schedule_deferred(&main_loop, event_create(loop_dummy_event, 0)); } - return ok; + return !cancel; } -/// Puts text at cursor. +/// Puts text at cursor, in any mode. /// /// Compare |:put| and |p| which are always linewise. /// diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index f9f39c36ff..c74ef58ba1 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -107,13 +107,12 @@ static void tinput_wait_enqueue(void **argv) const String keys = { .data = buf, .size = len }; if (input->paste) { Error err = ERROR_INIT; - Boolean rv = nvim_paste(keys, input->paste, &err); - // Paste phase: "continue" (unless handler failed). - input->paste = rv && !ERROR_SET(&err) ? 2 : 0; + // Paste phase: "continue" (unless handler canceled). + input->paste = !nvim_paste(keys, input->paste, &err) + ? 0 : (1 == input->paste ? 2 : input->paste); rbuffer_consumed(input->key_buffer, len); rbuffer_reset(input->key_buffer); if (ERROR_SET(&err)) { - msg_putchar('\n'); // TODO(justinmk): emsgf() does not display, why? msg_printf_attr(HL_ATTR(HLF_E)|MSG_HIST, "paste: %s", err.msg); api_clear_error(&err); @@ -373,7 +372,7 @@ static bool handle_bracketed_paste(TermInput *input) tinput_flush(input, true); // Paste phase: "first-chunk". input->paste = 1; - } else if (input->paste != 0) { + } else if (input->paste) { // Paste phase: "last-chunk". input->paste = input->paste == 2 ? 3 : -1; tinput_flush(input, true); diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 212c4f4300..01e4a3a1a0 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -395,6 +395,11 @@ describe('API', function() eq({0,3,6,0}, funcs.getpos('.')) eq(false, nvim('get_option', 'paste')) end) + it('vim.paste() failure', function() + nvim('execute_lua', 'vim._paste = (function(lines, phase) error("fake fail") end)', {}) + expect_err([[Error executing lua: %[string "%<nvim>"]:1: fake fail]], + request, 'nvim_paste', 'line 1\nline 2\nline 3', 1) + end) end) describe('nvim_put', function() @@ -455,6 +460,25 @@ describe('API', function() eq({0,1,2,0}, funcs.getpos('.')) eq('', nvim('eval', 'v:errmsg')) end) + + it('detects charwise/linewise text (empty {type})', function() + -- linewise (final item is empty string) + nvim('put', {'line 1','line 2','line 3',''}, '', true, true) + expect([[ + + line 1 + line 2 + line 3]]) + eq({0,4,1,0}, funcs.getpos('.')) + command('%delete _') + -- charwise (final item is non-empty) + nvim('put', {'line 1','line 2','line 3'}, '', true, true) + expect([[ + line 1 + line 2 + line 3]]) + eq({0,3,6,0}, funcs.getpos('.')) + end) end) describe('nvim_strwidth', function() diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 08ff06e0a5..e7db39b5d0 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -324,6 +324,57 @@ describe('TUI', function() expect_cmdline('"stuff 1 more typed"') end) + it('paste: recovers from vim.paste() failure', function() + child_session:request('nvim_execute_lua', [[ + _G.save_paste_fn = vim._paste + vim._paste = function(lines, phase) error("fake fail") end + ]], {}) + -- Start pasting... + feed_data('\027[200~line 1\nline 2\n') + screen:expect{grid=[[ + | + {4:~ }| + {4:~ }| + {5: }| + {8:paste: Error executing lua: [string "<nvim>"]:2: f}| + {10:Press ENTER or type command to continue}{1: } | + {3:-- TERMINAL --} | + ]]} + -- Remaining chunks are discarded after vim.paste() failure. + feed_data('line 3\nline 4\n') + feed_data('line 5\nline 6\n') + feed_data('line 7\nline 8\n') + -- Stop paste. + feed_data('\027[201~') + feed_data('\n') -- <Enter> + -- Editor should still work after failed/drained paste. + feed_data('ityped input...\027\000') + screen:expect{grid=[[ + typed input..{1:.} | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]} + -- Paste works if vim.paste() succeeds. + child_session:request('nvim_execute_lua', [[ + vim._paste = _G.save_paste_fn + ]], {}) + feed_data('\027[200~line A\nline B\n\027[201~') + feed_data('\n') -- <Enter> + screen:expect{grid=[[ + typed input...line A | + line B | + {1: } | + {4:~ }| + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]} + end) + -- TODO it('paste: other modes', function() -- Other modes act like CTRL-C + paste. |