diff options
author | Justin M. Keyes <justinkz@gmail.com> | 2024-09-10 01:14:18 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-09-10 01:14:18 -0700 |
commit | 5d7853f22903a4f42d52f565f6a662c3ef178a8c (patch) | |
tree | 9329edaa4dc694186644a5a9e3f405eb365e8ae8 | |
parent | f279d1ae33ee5650bccf7b52f9f29fd192ccea1d (diff) | |
download | rneovim-5d7853f22903a4f42d52f565f6a662c3ef178a8c.tar.gz rneovim-5d7853f22903a4f42d52f565f6a662c3ef178a8c.tar.bz2 rneovim-5d7853f22903a4f42d52f565f6a662c3ef178a8c.zip |
refactor(os/input.c): rename os_inchar => input_get #30327
Problem:
The name `os_inchar` (from Vim's old `mch_inchar`) is ambiguous:
"inchar" sounds like it could be reading or enqueuing (setting) input.
Its docstring is also ambiguous.
Solution:
- Rename `os_inchar` to `input_get`.
- Write some mf'ing docstrings.
- Add assert() in TRY_READ().
-rw-r--r-- | src/nvim/event/loop.h | 17 | ||||
-rw-r--r-- | src/nvim/getchar.c | 6 | ||||
-rw-r--r-- | src/nvim/input.c | 4 | ||||
-rw-r--r-- | src/nvim/msgpack_rpc/channel.h | 2 | ||||
-rw-r--r-- | src/nvim/normal.c | 6 | ||||
-rw-r--r-- | src/nvim/os/input.c | 108 | ||||
-rw-r--r-- | src/nvim/profile.c | 12 | ||||
-rw-r--r-- | src/nvim/state.c | 7 |
8 files changed, 86 insertions, 76 deletions
diff --git a/src/nvim/event/loop.h b/src/nvim/event/loop.h index 6ecc7cb781..da4852b836 100644 --- a/src/nvim/event/loop.h +++ b/src/nvim/event/loop.h @@ -16,15 +16,14 @@ struct loop { uv_loop_t uv; MultiQueue *events; MultiQueue *thread_events; - // Immediate events: - // "Processed after exiting uv_run() (to avoid recursion), but before - // returning from loop_poll_events()." 502aee690c98 - // Practical consequence (for main_loop): these events are processed by - // state_enter()..os_inchar() - // whereas "regular" events (main_loop.events) are processed by - // state_enter()..VimState.execute() - // But state_enter()..os_inchar() can be "too early" if you want the event - // to trigger UI updates and other user-activity-related side-effects. + // Immediate events. + // - "Processed after exiting `uv_run()` (to avoid recursion), but before returning from + // `loop_poll_events()`." 502aee690c98 + // - Practical consequence (for `main_loop`): + // - these are processed by `state_enter()..input_get()` whereas "regular" events + // (`main_loop.events`) are processed by `state_enter()..VimState.execute()` + // - `state_enter()..input_get()` can be "too early" if you want the event to trigger UI + // updates and other user-activity-related side-effects. MultiQueue *fast_events; // used by process/job-control subsystem diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 7b388e6061..ba0b629896 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -1858,7 +1858,7 @@ static void getchar_common(typval_T *argvars, typval_T *rettv) if (!char_avail()) { // Flush screen updates before blocking. ui_flush(); - os_inchar(NULL, 0, -1, typebuf.tb_change_cnt, main_loop.events); + input_get(NULL, 0, -1, typebuf.tb_change_cnt, main_loop.events); if (!multiqueue_empty(main_loop.events)) { state_handle_k_event(); continue; @@ -2981,7 +2981,7 @@ int inchar(uint8_t *buf, int maxlen, long wait_time) uint8_t dum[DUM_LEN + 1]; while (true) { - len = os_inchar(dum, DUM_LEN, 0, 0, NULL); + len = input_get(dum, DUM_LEN, 0, 0, NULL); if (len == 0 || (len == 1 && dum[0] == Ctrl_C)) { break; } @@ -2997,7 +2997,7 @@ int inchar(uint8_t *buf, int maxlen, long wait_time) // Fill up to a third of the buffer, because each character may be // tripled below. - len = os_inchar(buf, maxlen / 3, (int)wait_time, tb_change_cnt, NULL); + len = input_get(buf, maxlen / 3, (int)wait_time, tb_change_cnt, NULL); } // If the typebuf was changed further down, it is like nothing was added by diff --git a/src/nvim/input.c b/src/nvim/input.c index e14bfe7539..ef400710fe 100644 --- a/src/nvim/input.c +++ b/src/nvim/input.c @@ -37,7 +37,7 @@ /// @param[in] str Prompt: question to ask user. Is always followed by /// " (y/n)?". /// @param[in] direct Determines what function to use to get user input. If -/// true then os_inchar() will be used, otherwise vgetc(). +/// true then input_get() will be used, otherwise vgetc(). /// I.e. when direct is true then characters are obtained /// directly from the user without buffers involved. /// @@ -111,7 +111,7 @@ int get_keystroke(MultiQueue *events) // First time: blocking wait. Second time: wait up to 100ms for a // terminal code to complete. - n = os_inchar(buf + len, maxlen, len == 0 ? -1 : 100, 0, events); + n = input_get(buf + len, maxlen, len == 0 ? -1 : 100, 0, events); if (n > 0) { // Replace zero and K_SPECIAL by a special key code. n = fix_input_buffer(buf + len, n); diff --git a/src/nvim/msgpack_rpc/channel.h b/src/nvim/msgpack_rpc/channel.h index ff73a2fc53..abf1ce7bf6 100644 --- a/src/nvim/msgpack_rpc/channel.h +++ b/src/nvim/msgpack_rpc/channel.h @@ -12,7 +12,7 @@ /// HACK: os/input.c drains this queue immediately before blocking for input. /// Events on this queue are async-safe, but they need the resolved state -/// of os_inchar(), so they are processed "just-in-time". +/// of input_get(), so they are processed "just-in-time". EXTERN MultiQueue *ch_before_blocking_events INIT( = NULL); #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/normal.c b/src/nvim/normal.c index f3bdea9a85..b9ce891b49 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -6597,11 +6597,11 @@ static void nv_open(cmdarg_T *cap) static void nv_event(cmdarg_T *cap) { // Garbage collection should have been executed before blocking for events in - // the `os_inchar` in `state_enter`, but we also disable it here in case the - // `os_inchar` branch was not executed (!multiqueue_empty(loop.events), which + // the `input_get` in `state_enter`, but we also disable it here in case the + // `input_get` branch was not executed (!multiqueue_empty(loop.events), which // could have `may_garbage_collect` set to true in `normal_check`). // - // That is because here we may run code that calls `os_inchar` + // That is because here we may run code that calls `input_get` // later(`f_confirm` or `get_keystroke` for example), but in these cases it is // not safe to perform garbage collection because there could be unreferenced // lists or dicts being used. diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index 8affc58591..a240c0e5b6 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -33,12 +33,6 @@ #define READ_BUFFER_SIZE 0xfff #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; @@ -84,57 +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`). +/// +/// - Consumes available input received from the OS. +/// - Consumes pending events. +/// - Manages CursorHold events. +/// - Handles EOF conditions. /// -/// 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) +/// Originally based on the Vim `mch_inchar` function. +/// +/// @param buf Buffer to store read 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 && input_available()) { - restart_cursorhold_wait(tb_change_cnt); - size_t to_read = MIN((size_t)maxlen, input_available()); - memcpy(buf, input_read_pos, to_read); - input_read_pos += to_read; - return (int)to_read; - } +#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 ((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 { @@ -153,32 +166,26 @@ int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *e return 0; } - if (maxlen && input_available()) { - restart_cursorhold_wait(tb_change_cnt); - // Safe to convert `to_read` to int, it will never overflow since - // INPUT_BUFFER_SIZE fits in an int - size_t to_read = MIN((size_t)maxlen, input_available()); - memcpy(buf, input_read_pos, to_read); - input_read_pos += to_read; - return (int)to_read; - } + 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. @@ -463,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) { @@ -479,20 +493,18 @@ 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=%d pending=%d", !!events, pending_events(events)); + 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; + return input_eof ? kNone : kFalse; } static size_t input_read_cb(RStream *stream, const char *buf, size_t c, void *data, bool at_eof) @@ -533,8 +545,8 @@ static void process_ctrl_c(void) } } -// 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 }; diff --git a/src/nvim/profile.c b/src/nvim/profile.c index 7ea7061b46..b397de353b 100644 --- a/src/nvim/profile.c +++ b/src/nvim/profile.c @@ -383,19 +383,19 @@ void set_context_in_profile_cmd(expand_T *xp, const char *arg) xp->xp_context = EXPAND_NOTHING; } -static proftime_T inchar_time; +static proftime_T wait_time; /// Called when starting to wait for the user to type a character. -void prof_inchar_enter(void) +void prof_input_start(void) { - inchar_time = profile_start(); + wait_time = profile_start(); } /// Called when finished waiting for the user to type a character. -void prof_inchar_exit(void) +void prof_input_end(void) { - inchar_time = profile_end(inchar_time); - profile_set_wait(profile_add(profile_get_wait(), inchar_time)); + wait_time = profile_end(wait_time); + profile_set_wait(profile_add(profile_get_wait(), wait_time)); } /// @return true when a function defined in the current script should be diff --git a/src/nvim/state.c b/src/nvim/state.c index eab976927c..1b08a08fba 100644 --- a/src/nvim/state.c +++ b/src/nvim/state.c @@ -74,10 +74,9 @@ getkey: } // Flush screen updates before blocking. ui_flush(); - // Call `os_inchar` directly to block for events or user input without - // consuming anything from `input_buffer`(os/input.c) or calling the - // mapping engine. - os_inchar(NULL, 0, -1, typebuf.tb_change_cnt, main_loop.events); + // Call `input_get` directly to block for events or user input without consuming anything from + // `os/input.c:input_buffer` or calling the mapping engine. + input_get(NULL, 0, -1, typebuf.tb_change_cnt, main_loop.events); // If an event was put into the queue, we send K_EVENT directly. if (!input_available() && !multiqueue_empty(main_loop.events)) { key = K_EVENT; |