diff options
Diffstat (limited to 'src/nvim/os/input.c')
-rw-r--r-- | src/nvim/os/input.c | 215 |
1 files changed, 116 insertions, 99 deletions
diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index 60b5b48745..7c5293a8b8 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -27,22 +27,17 @@ #include "nvim/os/os_defs.h" #include "nvim/os/time.h" #include "nvim/profile.h" -#include "nvim/rbuffer.h" -#include "nvim/rbuffer_defs.h" #include "nvim/state.h" #include "nvim/state_defs.h" #define READ_BUFFER_SIZE 0xfff -#define INPUT_BUFFER_SIZE (READ_BUFFER_SIZE * 4) +#define INPUT_BUFFER_SIZE ((READ_BUFFER_SIZE * 4) + MAX_KEY_CODE_LEN) -typedef enum { - kInputNone, - kInputAvail, - kInputEof, -} InbufPollResult; +static RStream read_stream = { .s.closed = true }; // Input before UI starts. +static char input_buffer[INPUT_BUFFER_SIZE]; +static char *input_read_pos = input_buffer; +static char *input_write_pos = input_buffer; -static Stream read_stream = { .closed = true }; // Input before UI starts. -static RBuffer *input_buffer = NULL; static bool input_eof = false; static bool blocking = false; static int cursorhold_time = 0; ///< time waiting for CursorHold event @@ -52,39 +47,27 @@ static int cursorhold_tb_change_cnt = 0; ///< tb_change_cnt when waiting starte # include "os/input.c.generated.h" #endif -void input_init(void) -{ - input_buffer = rbuffer_new(INPUT_BUFFER_SIZE + MAX_KEY_CODE_LEN); -} - void input_start(void) { - if (!read_stream.closed) { + if (!read_stream.s.closed) { return; } used_stdin = true; - rstream_init_fd(&main_loop, &read_stream, STDIN_FILENO, READ_BUFFER_SIZE); + rstream_init_fd(&main_loop, &read_stream, STDIN_FILENO); rstream_start(&read_stream, input_read_cb, NULL); } void input_stop(void) { - if (read_stream.closed) { + if (read_stream.s.closed) { return; } rstream_stop(&read_stream); - stream_close(&read_stream, NULL, NULL); + rstream_may_close(&read_stream); } -#ifdef EXITFREE -void input_free_all_mem(void) -{ - rbuffer_free(input_buffer); -} -#endif - static void cursorhold_event(void **argv) { event_T event = State & MODE_INSERT ? EVENT_CURSORHOLDI : EVENT_CURSORHOLD; @@ -95,54 +78,76 @@ static void cursorhold_event(void **argv) static void create_cursorhold_event(bool events_enabled) { // If events are enabled and the queue has any items, this function should not - // have been called (inbuf_poll would return kInputAvail). + // have been called (`inbuf_poll` would return `kTrue`). // TODO(tarruda): Cursorhold should be implemented as a timer set during the // `state_check` callback for the states where it can be triggered. assert(!events_enabled || multiqueue_empty(main_loop.events)); multiqueue_put(main_loop.events, cursorhold_event, NULL); } -static void restart_cursorhold_wait(int tb_change_cnt) +static void reset_cursorhold_wait(int tb_change_cnt) { cursorhold_time = 0; cursorhold_tb_change_cnt = tb_change_cnt; } -/// Low level input function +/// Reads OS input into `buf`, and consumes pending events while waiting (if `ms != 0`). /// -/// Wait until either the input buffer is non-empty or, if `events` is not NULL -/// until `events` is non-empty. -int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *events) +/// - Consumes available input received from the OS. +/// - Consumes pending events. +/// - Manages CursorHold events. +/// - Handles EOF conditions. +/// +/// Originally based on the Vim `mch_inchar` function. +/// +/// @param buf Buffer to store consumed input. +/// @param maxlen Maximum bytes to read into `buf`, or 0 to skip reading. +/// @param ms Timeout in milliseconds. -1 for indefinite wait, 0 for no wait. +/// @param tb_change_cnt Used to detect when typeahead changes. +/// @param events (optional) Events to process. +/// @return Bytes read into buf, or 0 if no input was read +int input_get(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *events) { // This check is needed so that feeding typeahead from RPC can prevent CursorHold. if (tb_change_cnt != cursorhold_tb_change_cnt) { - restart_cursorhold_wait(tb_change_cnt); + reset_cursorhold_wait(tb_change_cnt); } - if (maxlen && rbuffer_size(input_buffer)) { - restart_cursorhold_wait(tb_change_cnt); - return (int)rbuffer_read(input_buffer, (char *)buf, (size_t)maxlen); - } +#define TRY_READ() \ + do { \ + if (maxlen && input_available()) { \ + reset_cursorhold_wait(tb_change_cnt); \ + assert(maxlen >= 0); \ + size_t to_read = MIN((size_t)maxlen, input_available()); \ + memcpy(buf, input_read_pos, to_read); \ + input_read_pos += to_read; \ + /* This is safe because INPUT_BUFFER_SIZE fits in an int. */ \ + assert(to_read <= INT_MAX); \ + return (int)to_read; \ + } \ + } while (0) + + TRY_READ(); // No risk of a UI flood, so disable CTRL-C "interrupt" behavior if it's mapped. if ((mapped_ctrl_c | curbuf->b_mapped_ctrl_c) & get_real_state()) { ctrl_c_interrupts = false; } - InbufPollResult result; + TriState result; ///< inbuf_poll result. if (ms >= 0) { - if ((result = inbuf_poll(ms, events)) == kInputNone) { + if ((result = inbuf_poll(ms, events)) == kFalse) { return 0; } } else { uint64_t wait_start = os_hrtime(); cursorhold_time = MIN(cursorhold_time, (int)p_ut); - if ((result = inbuf_poll((int)p_ut - cursorhold_time, events)) == kInputNone) { - if (read_stream.closed && silent_mode) { + if ((result = inbuf_poll((int)p_ut - cursorhold_time, events)) == kFalse) { + if (read_stream.s.closed && silent_mode) { // Drained eventloop & initial input; exit silent/batch-mode (-es/-Es). read_error_exit(); } - restart_cursorhold_wait(tb_change_cnt); + reset_cursorhold_wait(tb_change_cnt); if (trigger_cursorhold() && !typebuf_changed(tb_change_cnt)) { create_cursorhold_event(events == main_loop.events); } else { @@ -161,29 +166,26 @@ int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *e return 0; } - if (maxlen && rbuffer_size(input_buffer)) { - restart_cursorhold_wait(tb_change_cnt); - // Safe to convert rbuffer_read to int, it will never overflow since we use - // relatively small buffers. - return (int)rbuffer_read(input_buffer, (char *)buf, (size_t)maxlen); - } + TRY_READ(); // If there are events, return the keys directly if (maxlen && pending_events(events)) { return push_event_key(buf, maxlen); } - if (result == kInputEof) { + if (result == kNone) { read_error_exit(); } return 0; + +#undef TRY_READ } // Check if a character is available for reading bool os_char_avail(void) { - return inbuf_poll(0, NULL) == kInputAvail; + return inbuf_poll(0, NULL) == kTrue; } /// Poll for fast events. `got_int` will be set to `true` if CTRL-C was typed. @@ -247,11 +249,28 @@ bool os_isatty(int fd) return uv_guess_handle(fd) == UV_TTY; } -void input_enqueue_raw(String keys) +size_t input_available(void) { - if (keys.size > 0) { - rbuffer_write(input_buffer, keys.data, keys.size); + return (size_t)(input_write_pos - input_read_pos); +} + +static size_t input_space(void) +{ + return (size_t)(input_buffer + INPUT_BUFFER_SIZE - input_write_pos); +} + +void input_enqueue_raw(const char *data, size_t size) +{ + if (input_read_pos > input_buffer) { + size_t available = input_available(); + memmove(input_buffer, input_read_pos, available); + input_read_pos = input_buffer; + input_write_pos = input_buffer + available; } + + size_t to_write = MIN(size, input_space()); + memcpy(input_write_pos, data, to_write); + input_write_pos += to_write; } size_t input_enqueue(String keys) @@ -259,7 +278,7 @@ size_t input_enqueue(String keys) const char *ptr = keys.data; const char *end = ptr + keys.size; - while (rbuffer_space(input_buffer) >= 19 && ptr < end) { + while (input_space() >= 19 && ptr < end) { // A "<x>" form occupies at least 1 characters, and produces up // to 19 characters (1 + 5 * 3 for the char and 3 for a modifier). // In the case of K_SPECIAL (0x80), 3 bytes are escaped and needed, @@ -272,7 +291,7 @@ size_t input_enqueue(String keys) if (new_size) { new_size = handle_mouse_event(&ptr, buf, new_size); - rbuffer_write(input_buffer, (char *)buf, new_size); + input_enqueue_raw((char *)buf, new_size); continue; } @@ -293,11 +312,11 @@ size_t input_enqueue(String keys) // copy the character, escaping K_SPECIAL if ((uint8_t)(*ptr) == K_SPECIAL) { - rbuffer_write(input_buffer, (char *)&(uint8_t){ K_SPECIAL }, 1); - rbuffer_write(input_buffer, (char *)&(uint8_t){ KS_SPECIAL }, 1); - rbuffer_write(input_buffer, (char *)&(uint8_t){ KE_FILLER }, 1); + input_enqueue_raw((char *)&(uint8_t){ K_SPECIAL }, 1); + input_enqueue_raw((char *)&(uint8_t){ KS_SPECIAL }, 1); + input_enqueue_raw((char *)&(uint8_t){ KE_FILLER }, 1); } else { - rbuffer_write(input_buffer, ptr, 1); + input_enqueue_raw(ptr, 1); } ptr++; } @@ -422,7 +441,7 @@ static unsigned handle_mouse_event(const char **ptr, uint8_t *buf, unsigned bufs return bufsize; } -size_t input_enqueue_mouse(int code, uint8_t modifier, int grid, int row, int col) +void input_enqueue_mouse(int code, uint8_t modifier, int grid, int row, int col) { modifier |= check_multiclick(code, grid, row, col); uint8_t buf[7]; @@ -442,8 +461,7 @@ size_t input_enqueue_mouse(int code, uint8_t modifier, int grid, int row, int co mouse_col = col; size_t written = 3 + (size_t)(p - buf); - rbuffer_write(input_buffer, (char *)buf, written); - return written; + input_enqueue_raw((char *)buf, written); } /// @return true if the main loop is blocked and waiting for input. @@ -452,15 +470,22 @@ bool input_blocking(void) return blocking; } -// This is a replacement for the old `WaitForChar` function in os_unix.c -static InbufPollResult inbuf_poll(int ms, MultiQueue *events) +/// Checks for (but does not read) available input, and consumes `main_loop.events` while waiting. +/// +/// @param ms Timeout in milliseconds. -1 for indefinite wait, 0 for no wait. +/// @param events (optional) Queue to check for pending events. +/// @return TriState: +/// - kTrue: Input/events available +/// - kFalse: No input/events +/// - kNone: EOF reached on the input stream +static TriState inbuf_poll(int ms, MultiQueue *events) { if (os_input_ready(events)) { - return kInputAvail; + return kTrue; } if (do_profiling == PROF_YES && ms) { - prof_inchar_enter(); + prof_input_start(); } if ((ms == -1 || ms > 0) && events != main_loop.events && !input_eof) { @@ -468,38 +493,29 @@ static InbufPollResult inbuf_poll(int ms, MultiQueue *events) blocking = true; multiqueue_process_events(ch_before_blocking_events); } - DLOG("blocking... events_enabled=%d events_pending=%d", events != NULL, - events && !multiqueue_empty(events)); - LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, ms, - os_input_ready(events) || input_eof); + DLOG("blocking... events=%s", !!events ? "true" : "false"); + LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, ms, os_input_ready(events) || input_eof); blocking = false; if (do_profiling == PROF_YES && ms) { - prof_inchar_exit(); + prof_input_end(); } if (os_input_ready(events)) { - return kInputAvail; + return kTrue; } - return input_eof ? kInputEof : kInputNone; -} - -bool input_available(void) -{ - return rbuffer_size(input_buffer) != 0; + return input_eof ? kNone : kFalse; } -static void input_read_cb(Stream *stream, RBuffer *buf, size_t c, void *data, bool at_eof) +static size_t input_read_cb(RStream *stream, const char *buf, size_t c, void *data, bool at_eof) { if (at_eof) { input_eof = true; } - assert(rbuffer_space(input_buffer) >= rbuffer_size(buf)); - RBUFFER_UNTIL_EMPTY(buf, ptr, len) { - (void)rbuffer_write(input_buffer, ptr, len); - rbuffer_consumed(buf, len); - } + assert(input_space() >= c); + input_enqueue_raw(buf, c); + return c; } static void process_ctrl_c(void) @@ -508,28 +524,29 @@ static void process_ctrl_c(void) return; } - size_t consume_count = 0; - RBUFFER_EACH_REVERSE(input_buffer, c, i) { - if ((uint8_t)c == Ctrl_C - || ((uint8_t)c == 'C' && i >= 3 - && (uint8_t)(*rbuffer_get(input_buffer, i - 3)) == K_SPECIAL - && (uint8_t)(*rbuffer_get(input_buffer, i - 2)) == KS_MODIFIER - && (uint8_t)(*rbuffer_get(input_buffer, i - 1)) == MOD_MASK_CTRL)) { - *rbuffer_get(input_buffer, i) = Ctrl_C; + size_t available = input_available(); + ssize_t i; + for (i = (ssize_t)available - 1; i >= 0; i--) { // Reverse-search input for Ctrl_C. + uint8_t c = (uint8_t)input_read_pos[i]; + if (c == Ctrl_C + || (c == 'C' && i >= 3 + && (uint8_t)input_read_pos[i - 3] == K_SPECIAL + && (uint8_t)input_read_pos[i - 2] == KS_MODIFIER + && (uint8_t)input_read_pos[i - 1] == MOD_MASK_CTRL)) { + input_read_pos[i] = Ctrl_C; got_int = true; - consume_count = i; break; } } - if (got_int && consume_count) { + if (got_int && i > 0) { // Remove all unprocessed input (typeahead) before the CTRL-C. - rbuffer_consumed(input_buffer, consume_count); + input_read_pos += i; } } -// Helper function used to push bytes from the 'event' key sequence partially -// between calls to os_inchar when maxlen < 3 +/// Pushes bytes from the "event" key sequence (KE_EVENT) partially between calls to input_get when +/// `maxlen < 3`. static int push_event_key(uint8_t *buf, int maxlen) { static const uint8_t key[3] = { K_SPECIAL, KS_EXTRA, KE_EVENT }; @@ -548,7 +565,7 @@ static int push_event_key(uint8_t *buf, int maxlen) bool os_input_ready(MultiQueue *events) { return (typebuf_was_filled // API call filled typeahead - || rbuffer_size(input_buffer) // Input buffer filled + || input_available() // Input buffer filled || pending_events(events)); // Events must be processed } @@ -559,7 +576,7 @@ static void read_error_exit(void) if (silent_mode) { // Normal way to exit for "nvim -es". getout(0); } - preserve_exit(_("Vim: Error reading input, exiting...\n")); + preserve_exit(_("Nvim: Error reading input, exiting...\n")); } static bool pending_events(MultiQueue *events) |