diff options
-rw-r--r-- | src/os/event.c | 74 | ||||
-rw-r--r-- | src/os/event.h | 16 | ||||
-rw-r--r-- | src/os/input.c | 272 | ||||
-rw-r--r-- | src/os/input.h | 20 | ||||
-rw-r--r-- | src/os_unix.c | 129 | ||||
-rw-r--r-- | src/os_unix.h | 3 | ||||
-rw-r--r-- | src/ui.c | 34 |
7 files changed, 393 insertions, 155 deletions
diff --git a/src/os/event.c b/src/os/event.c new file mode 100644 index 0000000000..1abde8d075 --- /dev/null +++ b/src/os/event.c @@ -0,0 +1,74 @@ +#include <stdint.h> +#include <stdbool.h> + +#include <uv.h> + +#include "os/event.h" +#include "os/input.h" + +static uv_timer_t timer_req; +static void timer_cb(uv_timer_t *handle, int); + +void event_init() +{ + /* Initialize input events */ + input_init(); + /* Timer to wake the event loop if a timeout argument is passed to + * `event_poll` */ + uv_timer_init(uv_default_loop(), &timer_req); +} + +/* Wait for some event */ +EventType event_poll(int32_t ms) +{ + bool timed_out; + EventType event; + uv_run_mode run_mode = UV_RUN_ONCE; + + if ((event = input_check()) != kEventNone) { + /* If there's a pending input event to be consumed, do it now */ + return event; + } + + input_start(); + timed_out = false; + + if (ms > 0) { + /* Timeout passed as argument, start the libuv timer to wake us up and + * set our local flag */ + timer_req.data = &timed_out; + uv_timer_start(&timer_req, timer_cb, ms, 0); + } else if (ms == 0) { + /* + * For ms == 0, we need to do a non-blocking event poll by + * setting the run mode to UV_RUN_NOWAIT. + */ + run_mode = UV_RUN_NOWAIT; + } + + do { + /* Wait for some event */ + uv_run(uv_default_loop(), run_mode); + } while ( + /* Continue running if ... */ + (event = input_check()) == kEventNone && /* ... we have no input */ + run_mode != UV_RUN_NOWAIT && /* ... ms != 0 */ + !timed_out /* ... we didn't get a timeout */ + ); + + input_stop(); + + if (!timed_out && ms > 0) { + /* Timer event did not trigger, stop the watcher since we no longer + * care about it */ + uv_timer_stop(&timer_req); + } + + return event; +} + +/* Set a flag in the `event_poll` loop for signaling of a timeout */ +static void timer_cb(uv_timer_t *handle, int status) +{ + *((bool *)handle->data) = true; +} diff --git a/src/os/event.h b/src/os/event.h new file mode 100644 index 0000000000..230bb6fab9 --- /dev/null +++ b/src/os/event.h @@ -0,0 +1,16 @@ +#ifndef NEOVIM_OS_EVENT_H +#define NEOVIM_OS_EVENT_H + +#include <stdint.h> + +typedef enum { + kEventNone, + kEventInput, + kEventEof +} EventType; + +void event_init(void); +EventType event_poll(int32_t ms); + +#endif + diff --git a/src/os/input.c b/src/os/input.c new file mode 100644 index 0000000000..0d344be11e --- /dev/null +++ b/src/os/input.c @@ -0,0 +1,272 @@ +#include <stdint.h> +#include <stdbool.h> + +#include <uv.h> + +#include "os/input.h" +#include "os/event.h" +#include "vim.h" +#include "globals.h" +#include "ui.h" +#include "types.h" +#include "fileio.h" +#include "getchar.h" +#include "term.h" +#include "misc2.h" + +#define READ_BUFFER_LENGTH 4096 + +typedef struct { + uv_buf_t uvbuf; + uint32_t rpos, wpos, fpos; + char_u data[READ_BUFFER_LENGTH]; + bool reading; +} ReadBuffer; + +static ReadBuffer rbuffer; +static uv_pipe_t read_stream; +/* Use an idle handle to make reading from the fs look like a normal libuv + * event */ +static uv_idle_t fread_idle; +static uv_handle_type read_channel_type; +static bool eof = false; + +static EventType inbuf_poll(int32_t ms); +static void stderr_switch(void); +static void alloc_cb(uv_handle_t *, size_t, uv_buf_t *); +static void read_cb(uv_stream_t *, ssize_t, const uv_buf_t *); +static void fread_idle_cb(uv_idle_t *, int); + +void input_init() +{ + rbuffer.wpos = rbuffer.rpos = rbuffer.fpos = 0; +#ifdef DEBUG + memset(&rbuffer.data, 0, READ_BUFFER_LENGTH); +#endif + + if ((read_channel_type = uv_guess_handle(read_cmd_fd)) == UV_FILE) { + uv_idle_init(uv_default_loop(), &fread_idle); + } else { + uv_pipe_init(uv_default_loop(), &read_stream, 0); + uv_pipe_open(&read_stream, read_cmd_fd); + } +} + +/* Check if there's a pending input event */ +EventType input_check() +{ + if (rbuffer.rpos < rbuffer.wpos) { + return kEventInput; + } + + if (eof) { + return kEventEof; + } + + return kEventNone; +} + +/* Listen for input */ +void input_start() +{ + /* Pin the buffer used by libuv */ + rbuffer.uvbuf.len = READ_BUFFER_LENGTH - rbuffer.wpos; + rbuffer.uvbuf.base = (char *)(rbuffer.data + rbuffer.wpos); + + if (read_channel_type == UV_FILE) { + /* Just invoke the `fread_idle_cb` as soon as there are no pending events */ + uv_idle_start(&fread_idle, fread_idle_cb); + } else { + /* Start reading */ + rbuffer.reading = false; + uv_read_start((uv_stream_t *)&read_stream, alloc_cb, read_cb); + } +} + +/* Stop listening for input */ +void input_stop() +{ + if (read_channel_type == UV_FILE) { + uv_idle_stop(&fread_idle); + } else { + uv_read_stop((uv_stream_t *)&read_stream); + } +} + +/* Copies (at most `count`) of was read from `read_cmd_fd` into `buf` */ +uint32_t input_read(char *buf, uint32_t count) +{ + uint32_t read_count = rbuffer.wpos - rbuffer.rpos; + + if (count < read_count) { + read_count = count; + } + + if (read_count > 0) { + memcpy(buf, rbuffer.data + rbuffer.rpos, read_count); + rbuffer.rpos += read_count; + } + + if (rbuffer.wpos == READ_BUFFER_LENGTH) { + /* `wpos` is at the end of the buffer, so free some space by moving unread + * data... */ + memmove( + rbuffer.data,/* ...To the beginning of the buffer(rpos 0) */ + rbuffer.data + rbuffer.rpos,/* ...From the first unread position */ + rbuffer.wpos - rbuffer.rpos/* ...By the number of unread bytes */ + ); + rbuffer.wpos -= rbuffer.rpos; + rbuffer.rpos = 0; + } + + return read_count; +} + + +/* Low level input function. */ +int mch_inchar(char_u *buf, int maxlen, long ms, int tb_change_cnt) +{ + EventType result; + + if (ms >= 0) { + if ((result = inbuf_poll(ms)) != kEventInput) { + return 0; + } + } else { + if ((result = inbuf_poll(p_ut)) != kEventInput) { + if (trigger_cursorhold() && maxlen >= 3 && + !typebuf_changed(tb_change_cnt)) { + buf[0] = K_SPECIAL; + buf[1] = KS_EXTRA; + buf[2] = KE_CURSORHOLD; + return 3; + } + + before_blocking(); + result = inbuf_poll(-1); + } + } + + /* If input was put directly in typeahead buffer bail out here. */ + if (typebuf_changed(tb_change_cnt)) + return 0; + + if (result == kEventEof) { + read_error_exit(); + return 0; + } + + return read_from_input_buf(buf, (long)maxlen); +} + +/* Check if a character is available for reading */ +bool mch_char_avail() +{ + return inbuf_poll(0) == kEventInput; +} + +/* + * Check for CTRL-C typed by reading all available characters. + * In cooked mode we should get SIGINT, no need to check. + */ +void mch_breakcheck() +{ + if (curr_tmode == TMODE_RAW && event_poll(0) == kEventInput) + fill_input_buf(FALSE); +} + +/* This is a replacement for the old `WaitForChar` function in os_unix.c */ +static EventType inbuf_poll(int32_t ms) +{ + if (input_available()) + return kEventInput; + + return event_poll(ms); +} + +static void stderr_switch() +{ + int mode = cur_tmode; + /* We probably set the wrong file descriptor to raw mode. Switch back to + * cooked mode */ + settmode(TMODE_COOK); + /* Stop the idle handle */ + uv_idle_stop(&fread_idle); + /* Use stderr for stdin, also works for shell commands. */ + read_cmd_fd = 2; + /* Initialize and start the input stream */ + uv_pipe_init(uv_default_loop(), &read_stream, 0); + uv_pipe_open(&read_stream, read_cmd_fd); + uv_read_start((uv_stream_t *)&read_stream, alloc_cb, read_cb); + rbuffer.reading = false; + /* Set the mode back to what it was */ + settmode(mode); +} + +/* Called by libuv to allocate memory for reading. */ +static void alloc_cb(uv_handle_t *handle, size_t suggested, uv_buf_t *buf) +{ + if (rbuffer.reading) { + buf->len = 0; + return; + } + + buf->base = rbuffer.uvbuf.base; + buf->len = rbuffer.uvbuf.len; + /* Avoid `alloc_cb`, `alloc_cb` sequences on windows */ + rbuffer.reading = true; +} + +/* + * Callback invoked by libuv after it copies the data into the buffer provided + * by `alloc_cb`. This is also called on EOF or when `alloc_cb` returns a + * 0-length buffer. + */ +static void read_cb(uv_stream_t *stream, ssize_t cnt, const uv_buf_t *buf) +{ + if (cnt <= 0) { + if (cnt != UV_ENOBUFS) { + /* Read error or EOF, either way vim must exit */ + eof = true; + } + return; + } + + /* Data was already written, so all we need is to update 'wpos' to reflect + * the space actually used in the buffer. */ + rbuffer.wpos += cnt; +} + +/* Called by the by the 'idle' handle to emulate a reading event */ +static void fread_idle_cb(uv_idle_t *handle, int status) +{ + uv_fs_t req; + + /* Synchronous read */ + uv_fs_read( + uv_default_loop(), + &req, + read_cmd_fd, + &rbuffer.uvbuf, + 1, + rbuffer.fpos, + NULL + ); + + uv_fs_req_cleanup(&req); + + if (req.result <= 0) { + if (rbuffer.fpos == 0 && uv_guess_handle(2) == UV_TTY) { + /* Read error. Since stderr is a tty we switch to reading from it. This + * is for handling for cases like "foo | xargs vim" because xargs + * redirects stdin from /dev/null. Previously, this was done in ui.c */ + stderr_switch(); + } else { + eof = true; + } + return; + } + + rbuffer.wpos += req.result; + rbuffer.fpos += req.result; +} diff --git a/src/os/input.h b/src/os/input.h new file mode 100644 index 0000000000..dd6a402cf9 --- /dev/null +++ b/src/os/input.h @@ -0,0 +1,20 @@ +#ifndef NEOVIM_OS_INPUT_H +#define NEOVIM_OS_INPUT_H + +#include <stdint.h> +#include <stdbool.h> + +#include "os/event.h" +#include "types.h" + +void input_init(void); +EventType input_check(void); +void input_start(void); +void input_stop(void); +uint32_t input_read(char *buf, uint32_t count); +int mch_inchar(char_u *, int, long, int); +bool mch_char_avail(void); +void mch_breakcheck(void); + +#endif + diff --git a/src/os_unix.c b/src/os_unix.c index 4b38cea807..f24e7ccff4 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -31,7 +31,6 @@ #include "vim.h" #include "os_unix.h" -#include "os/time.h" #include "buffer.h" #include "charset.h" #include "eval.h" @@ -51,6 +50,8 @@ #include "ui.h" #include "os/os.h" #include "os/time.h" +#include "os/event.h" +#include "os/input.h" #include "os_unixx.h" /* unix includes for os_unix.c only */ @@ -108,7 +109,6 @@ typedef int waitstatus; #endif static pid_t wait4pid(pid_t, waitstatus *); -static int WaitForChar(long); static int RealWaitForChar(int, long, int *); @@ -248,98 +248,12 @@ void mch_write(char_u *s, int len) RealWaitForChar(read_cmd_fd, p_wd, NULL); } -/* - * mch_inchar(): low level input function. - * Get a characters from the keyboard. - * Return the number of characters that are available. - * If wtime == 0 do not wait for characters. - * If wtime == n wait a short time for characters. - * If wtime == -1 wait forever for characters. - */ -int mch_inchar( - char_u *buf, - int maxlen, - long wtime, /* don't use "time", MIPS cannot handle it */ - int tb_change_cnt - ) -{ - int len; - - - /* Check if window changed size while we were busy, perhaps the ":set - * columns=99" command was used. */ - while (do_resize) - handle_resize(); - - if (wtime >= 0) { - while (WaitForChar(wtime) == 0) { /* no character available */ - if (!do_resize) /* return if not interrupted by resize */ - return 0; - handle_resize(); - } - } else { /* wtime == -1 */ - /* - * If there is no character available within 'updatetime' seconds - * flush all the swap files to disk. - * Also done when interrupted by SIGWINCH. - */ - if (WaitForChar(p_ut) == 0) { - if (trigger_cursorhold() && maxlen >= 3 - && !typebuf_changed(tb_change_cnt)) { - buf[0] = K_SPECIAL; - buf[1] = KS_EXTRA; - buf[2] = (int)KE_CURSORHOLD; - return 3; - } - before_blocking(); - } - } - - for (;; ) { /* repeat until we got a character */ - while (do_resize) /* window changed size */ - handle_resize(); - - /* - * We want to be interrupted by the winch signal - * or by an event on the monitored file descriptors. - */ - if (WaitForChar(-1L) == 0) { - if (do_resize) /* interrupted by SIGWINCH signal */ - handle_resize(); - return 0; - } - - /* If input was put directly in typeahead buffer bail out here. */ - if (typebuf_changed(tb_change_cnt)) - return 0; - - /* - * For some terminals we only get one character at a time. - * We want the get all available characters, so we could keep on - * trying until none is available - * For some other terminals this is quite slow, that's why we don't do - * it. - */ - len = read_from_input_buf(buf, (long)maxlen); - if (len > 0) { - return len; - } - } -} - static void handle_resize() { do_resize = FALSE; shell_resized(); } -/* - * return non-zero if a character is available - */ -int mch_char_avail() -{ - return WaitForChar(0L); -} #if defined(HAVE_SIGALTSTACK) || defined(HAVE_SIGSTACK) /* @@ -682,6 +596,8 @@ void mch_init() #ifdef MACOS_CONVERT mac_conv_init(); #endif + + event_init(); } static void set_signals() @@ -2461,43 +2377,6 @@ error: } /* - * Check for CTRL-C typed by reading all available characters. - * In cooked mode we should get SIGINT, no need to check. - */ -void mch_breakcheck() -{ - if (curr_tmode == TMODE_RAW && RealWaitForChar(read_cmd_fd, 0L, NULL)) - fill_input_buf(FALSE); -} - -/* - * Wait "msec" msec until a character is available from the keyboard or from - * inbuf[]. msec == -1 will block forever. - * When a GUI is being used, this will never get called -- webb - */ -static int WaitForChar(msec) -long msec; -{ - int avail; - - if (input_available()) /* something in inbuf[] */ - return 1; - - /* May need to query the mouse position. */ - if (WantQueryMouse) { - WantQueryMouse = FALSE; - mch_write((char_u *)IF_EB("\033[1'|", ESC_STR "[1'|"), 5); - } - - /* - * For FEAT_MOUSE_GPM and FEAT_XCLIPBOARD we loop here to process mouse - * events. This is a bit complicated, because they might both be defined. - */ - avail = RealWaitForChar(read_cmd_fd, msec, NULL); - return avail; -} - -/* * Wait "msec" msec until a character is available from file descriptor "fd". * "msec" == 0 will check for characters once. * "msec" == -1 will block until a character is available. diff --git a/src/os_unix.h b/src/os_unix.h index 456276fee3..ad7fa85ccb 100644 --- a/src/os_unix.h +++ b/src/os_unix.h @@ -2,8 +2,6 @@ #define NEOVIM_OS_UNIX_H /* os_unix.c */ void mch_write(char_u *s, int len); -int mch_inchar(char_u *buf, int maxlen, long wtime, int tb_change_cnt); -int mch_char_avail(void); void mch_startjmp(void); void mch_endjmp(void); void mch_didjmp(void); @@ -45,7 +43,6 @@ int mch_get_shellsize(void); void mch_set_shellsize(void); void mch_new_shellsize(void); int mch_call_shell(char_u *cmd, int options); -void mch_breakcheck(void); int mch_expandpath(garray_T *gap, char_u *path, int flags); int mch_expand_wildcards(int num_pat, char_u **pat, int *num_file, char_u ***file, @@ -33,6 +33,7 @@ #include "option.h" #include "os_unix.h" #include "os/time.h" +#include "os/input.h" #include "screen.h" #include "term.h" #include "window.h" @@ -475,7 +476,6 @@ void fill_input_buf(int exit_on_error) #if defined(UNIX) || defined(OS2) || defined(VMS) || defined(MACOS_X_UNIX) int len; int try; - static int did_read_something = FALSE; static char_u *rest = NULL; /* unconverted rest of previous read */ static int restlen = 0; int unconverted; @@ -513,40 +513,20 @@ void fill_input_buf(int exit_on_error) len = 0; /* to avoid gcc warning */ for (try = 0; try < 100; ++try) { - len = read(read_cmd_fd, - (char *)inbuf + inbufcount, (size_t)((INBUFLEN - inbufcount) - / input_conv.vc_factor - )); + len = input_read( + (char *)inbuf + inbufcount, + (size_t)((INBUFLEN - inbufcount) / input_conv.vc_factor)); if (len > 0 || got_int) break; - /* - * If reading stdin results in an error, continue reading stderr. - * This helps when using "foo | xargs vim". - */ - if (!did_read_something && !isatty(read_cmd_fd) && read_cmd_fd == 0) { - int m = cur_tmode; - - /* We probably set the wrong file descriptor to raw mode. Switch - * back to cooked mode, use another descriptor and set the mode to - * what it was. */ - settmode(TMODE_COOK); -#ifdef HAVE_DUP - /* Use stderr for stdin, also works for shell commands. */ - close(0); - ignored = dup(2); -#else - read_cmd_fd = 2; /* read from stderr instead of stdin */ -#endif - settmode(m); - } + if (!exit_on_error) return; } + if (len <= 0 && !got_int) read_error_exit(); - if (len > 0) - did_read_something = TRUE; + if (got_int) { /* Interrupted, pretend a CTRL-C was typed. */ inbuf[0] = 3; |