mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
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).
This commit is contained in:
parent
4344ac1111
commit
ed60015266
@ -1206,7 +1206,7 @@ Dictionary nvim_get_namespaces(void)
|
|||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Paste
|
/// Pastes at cursor, in any mode.
|
||||||
///
|
///
|
||||||
/// Invokes the `vim.paste` handler, which handles each mode appropriately.
|
/// Invokes the `vim.paste` handler, which handles each mode appropriately.
|
||||||
/// Sets redo/undo. Faster than |nvim_input()|.
|
/// Sets redo/undo. Faster than |nvim_input()|.
|
||||||
@ -1219,29 +1219,44 @@ Dictionary nvim_get_namespaces(void)
|
|||||||
/// - 2: continues the paste (zero or more times)
|
/// - 2: continues the paste (zero or more times)
|
||||||
/// - 3: ends the paste (exactly once)
|
/// - 3: ends the paste (exactly once)
|
||||||
/// @param[out] err Error details, if any
|
/// @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)
|
Boolean nvim_paste(String data, Integer phase, Error *err)
|
||||||
FUNC_API_SINCE(6)
|
FUNC_API_SINCE(6)
|
||||||
{
|
{
|
||||||
|
static bool draining = false;
|
||||||
|
bool cancel = false;
|
||||||
|
|
||||||
if (phase < -1 || phase > 3) {
|
if (phase < -1 || phase > 3) {
|
||||||
api_set_error(err, kErrorTypeValidation, "Invalid phase: %"PRId64, phase);
|
api_set_error(err, kErrorTypeValidation, "Invalid phase: %"PRId64, phase);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
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);
|
||||||
|
ADD(args, ARRAY_OBJ(lines));
|
||||||
|
ADD(args, INTEGER_OBJ(phase));
|
||||||
|
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)) {
|
if (!(State & CMDLINE) && !(State & INSERT) && (phase == -1 || phase == 1)) {
|
||||||
ResetRedobuff();
|
ResetRedobuff();
|
||||||
AppendCharToRedobuff('a'); // Dot-repeat.
|
AppendCharToRedobuff('a'); // Dot-repeat.
|
||||||
}
|
}
|
||||||
Array lines = string_to_array(data);
|
// vim.paste() decides if client should cancel. Errors do NOT cancel: we
|
||||||
Array args = ARRAY_DICT_INIT;
|
// want to drain remaining chunks (rather than divert them to main input).
|
||||||
ADD(args, ARRAY_OBJ(lines));
|
cancel = (rv.type != kObjectTypeBoolean || !rv.data.boolean);
|
||||||
ADD(args, INTEGER_OBJ(phase));
|
if (!cancel && !(State & CMDLINE)) { // Dot-repeat.
|
||||||
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.
|
|
||||||
for (size_t i = 0; i < lines.size; i++) {
|
for (size_t i = 0; i < lines.size; i++) {
|
||||||
String s = lines.items[i].data.string;
|
String s = lines.items[i].data.string;
|
||||||
assert(data.size <= INT_MAX);
|
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)) {
|
if (!(State & CMDLINE) && !(State & INSERT) && (phase == -1 || phase == 3)) {
|
||||||
AppendCharToRedobuff(ESC); // Dot-repeat.
|
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.
|
// XXX: Tickle main loop to ensure cursor is updated.
|
||||||
loop_schedule_deferred(&main_loop, event_create(loop_dummy_event, 0));
|
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.
|
/// Compare |:put| and |p| which are always linewise.
|
||||||
///
|
///
|
||||||
|
@ -107,13 +107,12 @@ static void tinput_wait_enqueue(void **argv)
|
|||||||
const String keys = { .data = buf, .size = len };
|
const String keys = { .data = buf, .size = len };
|
||||||
if (input->paste) {
|
if (input->paste) {
|
||||||
Error err = ERROR_INIT;
|
Error err = ERROR_INIT;
|
||||||
Boolean rv = nvim_paste(keys, input->paste, &err);
|
// Paste phase: "continue" (unless handler canceled).
|
||||||
// Paste phase: "continue" (unless handler failed).
|
input->paste = !nvim_paste(keys, input->paste, &err)
|
||||||
input->paste = rv && !ERROR_SET(&err) ? 2 : 0;
|
? 0 : (1 == input->paste ? 2 : input->paste);
|
||||||
rbuffer_consumed(input->key_buffer, len);
|
rbuffer_consumed(input->key_buffer, len);
|
||||||
rbuffer_reset(input->key_buffer);
|
rbuffer_reset(input->key_buffer);
|
||||||
if (ERROR_SET(&err)) {
|
if (ERROR_SET(&err)) {
|
||||||
msg_putchar('\n');
|
|
||||||
// TODO(justinmk): emsgf() does not display, why?
|
// TODO(justinmk): emsgf() does not display, why?
|
||||||
msg_printf_attr(HL_ATTR(HLF_E)|MSG_HIST, "paste: %s", err.msg);
|
msg_printf_attr(HL_ATTR(HLF_E)|MSG_HIST, "paste: %s", err.msg);
|
||||||
api_clear_error(&err);
|
api_clear_error(&err);
|
||||||
@ -373,7 +372,7 @@ static bool handle_bracketed_paste(TermInput *input)
|
|||||||
tinput_flush(input, true);
|
tinput_flush(input, true);
|
||||||
// Paste phase: "first-chunk".
|
// Paste phase: "first-chunk".
|
||||||
input->paste = 1;
|
input->paste = 1;
|
||||||
} else if (input->paste != 0) {
|
} else if (input->paste) {
|
||||||
// Paste phase: "last-chunk".
|
// Paste phase: "last-chunk".
|
||||||
input->paste = input->paste == 2 ? 3 : -1;
|
input->paste = input->paste == 2 ? 3 : -1;
|
||||||
tinput_flush(input, true);
|
tinput_flush(input, true);
|
||||||
|
@ -395,6 +395,11 @@ describe('API', function()
|
|||||||
eq({0,3,6,0}, funcs.getpos('.'))
|
eq({0,3,6,0}, funcs.getpos('.'))
|
||||||
eq(false, nvim('get_option', 'paste'))
|
eq(false, nvim('get_option', 'paste'))
|
||||||
end)
|
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)
|
end)
|
||||||
|
|
||||||
describe('nvim_put', function()
|
describe('nvim_put', function()
|
||||||
@ -455,6 +460,25 @@ describe('API', function()
|
|||||||
eq({0,1,2,0}, funcs.getpos('.'))
|
eq({0,1,2,0}, funcs.getpos('.'))
|
||||||
eq('', nvim('eval', 'v:errmsg'))
|
eq('', nvim('eval', 'v:errmsg'))
|
||||||
end)
|
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)
|
end)
|
||||||
|
|
||||||
describe('nvim_strwidth', function()
|
describe('nvim_strwidth', function()
|
||||||
|
@ -324,6 +324,57 @@ describe('TUI', function()
|
|||||||
expect_cmdline('"stuff 1 more typed"')
|
expect_cmdline('"stuff 1 more typed"')
|
||||||
end)
|
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
|
-- TODO
|
||||||
it('paste: other modes', function()
|
it('paste: other modes', function()
|
||||||
-- Other modes act like CTRL-C + paste.
|
-- Other modes act like CTRL-C + paste.
|
||||||
|
Loading…
Reference in New Issue
Block a user