aboutsummaryrefslogtreecommitdiff
path: root/src/os/input.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/os/input.c')
-rw-r--r--src/os/input.c272
1 files changed, 272 insertions, 0 deletions
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;
+}