aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/tui/input.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/tui/input.c')
-rw-r--r--src/nvim/tui/input.c94
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);
}