aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/os/event.c74
-rw-r--r--src/os/event.h16
-rw-r--r--src/os/input.c272
-rw-r--r--src/os/input.h20
-rw-r--r--src/os_unix.c129
-rw-r--r--src/os_unix.h3
-rw-r--r--src/ui.c34
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,
diff --git a/src/ui.c b/src/ui.c
index 21f4dbb1ea..38beba82d9 100644
--- a/src/ui.c
+++ b/src/ui.c
@@ -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;