diff options
Diffstat (limited to 'src/nvim/tui/input.c')
-rw-r--r-- | src/nvim/tui/input.c | 94 |
1 files changed, 71 insertions, 23 deletions
diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index fe8ffee8e0..c74ef58ba1 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -16,7 +16,6 @@ #include "nvim/os/input.h" #include "nvim/event/rstream.h" -#define PASTETOGGLE_KEY "<Paste>" #define KEY_BUFFER_SIZE 0xfff #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -26,7 +25,7 @@ void tinput_init(TermInput *input, Loop *loop) { input->loop = loop; - input->paste_enabled = false; + input->paste = 0; input->in_fd = 0; input->key_buffer = rbuffer_new(KEY_BUFFER_SIZE); uv_mutex_init(&input->key_buffer_mutex); @@ -105,13 +104,28 @@ static void tinput_wait_enqueue(void **argv) { TermInput *input = argv[0]; RBUFFER_UNTIL_EMPTY(input->key_buffer, buf, len) { - size_t consumed = input_enqueue((String){.data = buf, .size = len}); - if (consumed) { - rbuffer_consumed(input->key_buffer, consumed); - } - rbuffer_reset(input->key_buffer); - if (consumed < len) { - break; + const String keys = { .data = buf, .size = len }; + if (input->paste) { + Error err = ERROR_INIT; + // 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)) { + // TODO(justinmk): emsgf() does not display, why? + msg_printf_attr(HL_ATTR(HLF_E)|MSG_HIST, "paste: %s", err.msg); + api_clear_error(&err); + } + } else { + const size_t consumed = input_enqueue(keys); + if (consumed) { + rbuffer_consumed(input->key_buffer, consumed); + } + rbuffer_reset(input->key_buffer); + if (consumed < len) { + break; + } } } uv_mutex_lock(&input->key_buffer_mutex); @@ -292,9 +306,12 @@ static void tk_getkeys(TermInput *input, bool force) } } - if (result != TERMKEY_RES_AGAIN || input->paste_enabled) { + if (result != TERMKEY_RES_AGAIN) { return; } + // else: Partial keypress event was found in the buffer, but it does not + // yet contain all the bytes required. `key` structure indicates what + // termkey_getkey_force() would return. int ms = get_key_code_timeout(); @@ -326,8 +343,8 @@ static bool handle_focus_event(TermInput *input) if (rbuffer_size(input->read_stream.buffer) > 2 && (!rbuffer_cmp(input->read_stream.buffer, "\x1b[I", 3) || !rbuffer_cmp(input->read_stream.buffer, "\x1b[O", 3))) { - // Advance past the sequence bool focus_gained = *rbuffer_get(input->read_stream.buffer, 2) == 'I'; + // Advance past the sequence rbuffer_consumed(input->read_stream.buffer, 3); aucmd_schedule_focusgained(focus_gained); return true; @@ -341,18 +358,33 @@ static bool handle_bracketed_paste(TermInput *input) && (!rbuffer_cmp(input->read_stream.buffer, "\x1b[200~", 6) || !rbuffer_cmp(input->read_stream.buffer, "\x1b[201~", 6))) { bool enable = *rbuffer_get(input->read_stream.buffer, 4) == '0'; + if (input->paste && enable) { + return false; // Pasting "start paste" code literally. + } // Advance past the sequence rbuffer_consumed(input->read_stream.buffer, 6); - if (input->paste_enabled == enable) { - return true; + if (!!input->paste == enable) { + return true; // Spurious "disable paste" code. + } + + if (enable) { + // Flush before starting paste. + tinput_flush(input, true); + // Paste phase: "first-chunk". + input->paste = 1; + } else if (input->paste) { + // Paste phase: "last-chunk". + input->paste = input->paste == 2 ? 3 : -1; + tinput_flush(input, true); + // Paste phase: "disabled". + input->paste = 0; } - tinput_enqueue(input, PASTETOGGLE_KEY, sizeof(PASTETOGGLE_KEY) - 1); - input->paste_enabled = enable; return true; } return false; } +// ESC NUL => <Esc> static bool handle_forced_escape(TermInput *input) { if (rbuffer_size(input->read_stream.buffer) > 1 @@ -477,9 +509,11 @@ static void tinput_read_cb(Stream *stream, RBuffer *buf, size_t count_, continue; } - // Find the next 'esc' and push everything up to it(excluding). This is done - // so the `handle_bracketed_paste`/`handle_forced_escape` calls above work - // as expected. + // + // Find the next ESC and push everything up to it (excluding), so it will + // be the first thing encountered on the next iteration. The `handle_*` + // calls (above) depend on this. + // size_t count = 0; RBUFFER_EACH(input->read_stream.buffer, c, i) { count = i + 1; @@ -488,15 +522,28 @@ static void tinput_read_cb(Stream *stream, RBuffer *buf, size_t count_, break; } } - + // Push bytes directly (paste). + if (input->paste) { + RBUFFER_UNTIL_EMPTY(input->read_stream.buffer, ptr, len) { + size_t consumed = MIN(count, len); + assert(consumed <= input->read_stream.buffer->size); + tinput_enqueue(input, ptr, consumed); + rbuffer_consumed(input->read_stream.buffer, consumed); + if (!(count -= consumed)) { + break; + } + } + continue; + } + // Push through libtermkey (translates to "<keycode>" strings, etc.). RBUFFER_UNTIL_EMPTY(input->read_stream.buffer, ptr, len) { size_t consumed = termkey_push_bytes(input->tk, ptr, MIN(count, len)); // termkey_push_bytes can return (size_t)-1, so it is possible that // `consumed > input->read_stream.buffer->size`, but since tk_getkeys is - // called soon, it shouldn't happen + // called soon, it shouldn't happen. assert(consumed <= input->read_stream.buffer->size); rbuffer_consumed(input->read_stream.buffer, consumed); - // Need to process the keys now since there's no guarantee "count" will + // Process the keys now: there is no guarantee `count` will // fit into libtermkey's input buffer. tk_getkeys(input, false); if (!(count -= consumed)) { @@ -505,7 +552,8 @@ static void tinput_read_cb(Stream *stream, RBuffer *buf, size_t count_, } } while (rbuffer_size(input->read_stream.buffer)); tinput_flush(input, true); - // Make sure the next input escape sequence fits into the ring buffer - // without wrap around, otherwise it could be misinterpreted. + // Make sure the next input escape sequence fits into the ring buffer without + // wraparound, else it could be misinterpreted (because rbuffer_read_ptr() + // exposes the underlying buffer to callers unaware of the wraparound). rbuffer_reset(input->read_stream.buffer); } |