aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/os
diff options
context:
space:
mode:
authorEliseo Martínez <eliseomarmol@gmail.com>2014-05-12 02:25:17 +0200
committerEliseo Martínez <eliseomarmol@gmail.com>2014-05-15 20:46:01 +0200
commitda51dc9cf202772f60bd2da975dbef257bd9237c (patch)
tree5c16b93238a153f55634e9323077f30c8133970c /src/nvim/os
parentffe61e5ba1721340ca51d56bae3ddaca415fb5bc (diff)
downloadrneovim-da51dc9cf202772f60bd2da975dbef257bd9237c.tar.gz
rneovim-da51dc9cf202772f60bd2da975dbef257bd9237c.tar.bz2
rneovim-da51dc9cf202772f60bd2da975dbef257bd9237c.zip
Introduce nvim namespace: Move files.
Move files from src/ to src/nvim/. - src/nvim/ becomes the new root dir for nvim executable sources. - src/libnvim/ is planned to become root dir of the neovim library.
Diffstat (limited to 'src/nvim/os')
-rw-r--r--src/nvim/os/channel.c175
-rw-r--r--src/nvim/os/channel.h29
-rw-r--r--src/nvim/os/channel_defs.h8
-rw-r--r--src/nvim/os/env.c80
-rw-r--r--src/nvim/os/event.c148
-rw-r--r--src/nvim/os/event.h18
-rw-r--r--src/nvim/os/event_defs.h25
-rw-r--r--src/nvim/os/fs.c277
-rw-r--r--src/nvim/os/input.c195
-rw-r--r--src/nvim/os/input.h23
-rw-r--r--src/nvim/os/job.c370
-rw-r--r--src/nvim/os/job.h78
-rw-r--r--src/nvim/os/job_defs.h15
-rw-r--r--src/nvim/os/mem.c10
-rw-r--r--src/nvim/os/msgpack_rpc.c408
-rw-r--r--src/nvim/os/msgpack_rpc.h107
-rw-r--r--src/nvim/os/os.h146
-rw-r--r--src/nvim/os/rstream.c297
-rw-r--r--src/nvim/os/rstream.h82
-rw-r--r--src/nvim/os/rstream_defs.h16
-rw-r--r--src/nvim/os/server.c243
-rw-r--r--src/nvim/os/server.h30
-rw-r--r--src/nvim/os/server_defs.h7
-rw-r--r--src/nvim/os/shell.c465
-rw-r--r--src/nvim/os/shell.h45
-rw-r--r--src/nvim/os/signal.c163
-rw-r--r--src/nvim/os/signal.h13
-rw-r--r--src/nvim/os/time.c86
-rw-r--r--src/nvim/os/time.h35
-rw-r--r--src/nvim/os/users.c87
-rw-r--r--src/nvim/os/uv_helpers.c70
-rw-r--r--src/nvim/os/uv_helpers.h47
-rw-r--r--src/nvim/os/wstream.c117
-rw-r--r--src/nvim/os/wstream.h40
-rw-r--r--src/nvim/os/wstream_defs.h7
35 files changed, 3962 insertions, 0 deletions
diff --git a/src/nvim/os/channel.c b/src/nvim/os/channel.c
new file mode 100644
index 0000000000..2427db2a14
--- /dev/null
+++ b/src/nvim/os/channel.c
@@ -0,0 +1,175 @@
+#include <string.h>
+
+#include <uv.h>
+#include <msgpack.h>
+
+#include "lib/klist.h"
+#include "os/channel.h"
+#include "os/channel_defs.h"
+#include "os/rstream.h"
+#include "os/rstream_defs.h"
+#include "os/wstream.h"
+#include "os/wstream_defs.h"
+#include "os/job.h"
+#include "os/job_defs.h"
+#include "os/msgpack_rpc.h"
+#include "vim.h"
+#include "memory.h"
+
+typedef struct {
+ ChannelProtocol protocol;
+ bool is_job;
+ union {
+ struct {
+ msgpack_unpacker *unpacker;
+ msgpack_sbuffer *sbuffer;
+ } msgpack;
+ } proto;
+ union {
+ int job_id;
+ struct {
+ RStream *read;
+ WStream *write;
+ } streams;
+ } data;
+} Channel;
+
+#define _destroy_channel(x)
+
+KLIST_INIT(Channel, Channel *, _destroy_channel)
+
+static klist_t(Channel) *channels = NULL;
+static void on_job_stdout(RStream *rstream, void *data, bool eof);
+static void on_job_stderr(RStream *rstream, void *data, bool eof);
+static void parse_msgpack(RStream *rstream, void *data, bool eof);
+
+void channel_init()
+{
+ channels = kl_init(Channel);
+}
+
+void channel_teardown()
+{
+ if (!channels) {
+ return;
+ }
+
+ Channel *channel;
+
+ while (kl_shift(Channel, channels, &channel) == 0) {
+
+ switch (channel->protocol) {
+ case kChannelProtocolMsgpack:
+ msgpack_sbuffer_free(channel->proto.msgpack.sbuffer);
+ msgpack_unpacker_free(channel->proto.msgpack.unpacker);
+ break;
+ default:
+ abort();
+ }
+
+ if (channel->is_job) {
+ job_stop(channel->data.job_id);
+ } else {
+ rstream_free(channel->data.streams.read);
+ wstream_free(channel->data.streams.write);
+ }
+ }
+}
+
+void channel_from_job(char **argv, ChannelProtocol prot)
+{
+ Channel *channel = xmalloc(sizeof(Channel));
+ rstream_cb rcb = NULL;
+
+ switch (prot) {
+ case kChannelProtocolMsgpack:
+ rcb = on_job_stdout;
+ channel->proto.msgpack.unpacker =
+ msgpack_unpacker_new(MSGPACK_UNPACKER_INIT_BUFFER_SIZE);
+ channel->proto.msgpack.sbuffer = msgpack_sbuffer_new();
+ break;
+ default:
+ abort();
+ }
+
+ channel->protocol = prot;
+ channel->is_job = true;
+ channel->data.job_id = job_start(argv, channel, rcb, on_job_stderr, NULL);
+ *kl_pushp(Channel, channels) = channel;
+}
+
+void channel_from_stream(uv_stream_t *stream, ChannelProtocol prot)
+{
+ Channel *channel = xmalloc(sizeof(Channel));
+ rstream_cb rcb = NULL;
+
+ switch (prot) {
+ case kChannelProtocolMsgpack:
+ rcb = parse_msgpack;
+ channel->proto.msgpack.unpacker =
+ msgpack_unpacker_new(MSGPACK_UNPACKER_INIT_BUFFER_SIZE);
+ channel->proto.msgpack.sbuffer = msgpack_sbuffer_new();
+ break;
+ default:
+ abort();
+ }
+
+ stream->data = NULL;
+ channel->protocol = prot;
+ channel->is_job = false;
+ // read stream
+ channel->data.streams.read = rstream_new(rcb, 1024, channel, true);
+ rstream_set_stream(channel->data.streams.read, stream);
+ rstream_start(channel->data.streams.read);
+ // write stream
+ channel->data.streams.write = wstream_new(1024 * 1024);
+ wstream_set_stream(channel->data.streams.write, stream);
+ // push to channel list
+ *kl_pushp(Channel, channels) = channel;
+}
+
+static void on_job_stdout(RStream *rstream, void *data, bool eof)
+{
+ Job *job = data;
+ parse_msgpack(rstream, job_data(job), eof);
+}
+
+static void on_job_stderr(RStream *rstream, void *data, bool eof)
+{
+ // TODO(tarruda): plugin error messages should be sent to the error buffer
+}
+
+static void parse_msgpack(RStream *rstream, void *data, bool eof)
+{
+ msgpack_unpacked unpacked;
+ Channel *channel = data;
+ uint32_t count = rstream_available(rstream);
+
+ // Feed the unpacker with data
+ msgpack_unpacker_reserve_buffer(channel->proto.msgpack.unpacker, count);
+ rstream_read(rstream,
+ msgpack_unpacker_buffer(channel->proto.msgpack.unpacker),
+ count);
+ msgpack_unpacker_buffer_consumed(channel->proto.msgpack.unpacker, count);
+
+ msgpack_unpacked_init(&unpacked);
+
+ // Deserialize everything we can.
+ while (msgpack_unpacker_next(channel->proto.msgpack.unpacker, &unpacked)) {
+ // Each object is a new msgpack-rpc request and requires an empty response
+ msgpack_packer response;
+ msgpack_packer_init(&response,
+ channel->proto.msgpack.sbuffer,
+ msgpack_sbuffer_write);
+ // Perform the call
+ msgpack_rpc_call(&unpacked.data, &response);
+ wstream_write(channel->data.streams.write,
+ xmemdup(channel->proto.msgpack.sbuffer->data,
+ channel->proto.msgpack.sbuffer->size),
+ channel->proto.msgpack.sbuffer->size,
+ true);
+
+ // Clear the buffer for future calls
+ msgpack_sbuffer_clear(channel->proto.msgpack.sbuffer);
+ }
+}
diff --git a/src/nvim/os/channel.h b/src/nvim/os/channel.h
new file mode 100644
index 0000000000..c58e91a0e5
--- /dev/null
+++ b/src/nvim/os/channel.h
@@ -0,0 +1,29 @@
+#ifndef NEOVIM_OS_CHANNEL_H
+#define NEOVIM_OS_CHANNEL_H
+
+#include <uv.h>
+
+#include "os/channel_defs.h"
+
+/// Initializes the module
+void channel_init(void);
+
+/// Teardown the module
+void channel_teardown(void);
+
+/// Creates an API channel from a libuv stream representing a tcp or
+/// pipe/socket client connection
+///
+/// @param stream The established connection
+/// @param prot The rpc protocol used
+void channel_from_stream(uv_stream_t *stream, ChannelProtocol prot);
+
+/// Creates an API channel by starting a job and connecting to its
+/// stdin/stdout. stderr is forwarded to the editor error stream.
+///
+/// @param argv The argument vector for the process
+/// @param prot The rpc protocol used
+void channel_from_job(char **argv, ChannelProtocol prot);
+
+#endif // NEOVIM_OS_CHANNEL_H
+
diff --git a/src/nvim/os/channel_defs.h b/src/nvim/os/channel_defs.h
new file mode 100644
index 0000000000..27ba103fbe
--- /dev/null
+++ b/src/nvim/os/channel_defs.h
@@ -0,0 +1,8 @@
+#ifndef NEOVIM_OS_CHANNEL_DEFS_H
+#define NEOVIM_OS_CHANNEL_DEFS_H
+
+typedef enum {
+ kChannelProtocolMsgpack
+} ChannelProtocol;
+
+#endif // NEOVIM_OS_CHANNEL_DEFS_H
diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c
new file mode 100644
index 0000000000..e6cdb92ea8
--- /dev/null
+++ b/src/nvim/os/env.c
@@ -0,0 +1,80 @@
+// env.c -- environment variable access
+
+#include <uv.h>
+
+#include "os/os.h"
+#include "misc2.h"
+
+#ifdef HAVE__NSGETENVIRON
+#include <crt_externs.h>
+#endif
+
+#ifdef HAVE_SYS_UTSNAME_H
+#include <sys/utsname.h>
+#endif
+
+const char *os_getenv(const char *name)
+{
+ return getenv(name);
+}
+
+int os_setenv(const char *name, const char *value, int overwrite)
+{
+ return setenv(name, value, overwrite);
+}
+
+char *os_getenvname_at_index(size_t index)
+{
+# if defined(HAVE__NSGETENVIRON)
+ char **environ = *_NSGetEnviron();
+# elif !defined(__WIN32__)
+ // Borland C++ 5.2 has this in a header file.
+ extern char **environ;
+# endif
+ // check if index is inside the environ array
+ for (size_t i = 0; i < index; i++) {
+ if (environ[i] == NULL) {
+ return NULL;
+ }
+ }
+ char *str = environ[index];
+ if (str == NULL) {
+ return NULL;
+ }
+ int namesize = 0;
+ while (str[namesize] != '=' && str[namesize] != NUL) {
+ namesize++;
+ }
+ char *name = (char *)vim_strnsave((char_u *)str, namesize);
+ return name;
+}
+
+
+int64_t os_get_pid()
+{
+#ifdef _WIN32
+ return (int64_t)GetCurrentProcessId();
+#else
+ return (int64_t)getpid();
+#endif
+}
+
+void os_get_hostname(char *hostname, size_t len)
+{
+#ifdef HAVE_SYS_UTSNAME_H
+ struct utsname vutsname;
+
+ if (uname(&vutsname) < 0) {
+ *hostname = '\0';
+ } else {
+ strncpy(hostname, vutsname.nodename, len - 1);
+ hostname[len - 1] = '\0';
+ }
+#else
+ // TODO(unknown): Implement this for windows.
+ // See the implementation used in vim:
+ // https://code.google.com/p/vim/source/browse/src/os_win32.c?r=6b69d8dde19e32909f4ee3a6337e6a2ecfbb6f72#2899
+ *hostname = '\0';
+#endif
+}
+
diff --git a/src/nvim/os/event.c b/src/nvim/os/event.c
new file mode 100644
index 0000000000..2c25852778
--- /dev/null
+++ b/src/nvim/os/event.c
@@ -0,0 +1,148 @@
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include <uv.h>
+
+#include "lib/klist.h"
+#include "os/event.h"
+#include "os/input.h"
+#include "os/channel.h"
+#include "os/server.h"
+#include "os/signal.h"
+#include "os/rstream.h"
+#include "os/job.h"
+#include "vim.h"
+#include "memory.h"
+#include "misc2.h"
+
+// event will be cleaned up after it gets processed
+#define _destroy_event(x) // do nothing
+KLIST_INIT(Event, Event, _destroy_event)
+
+static klist_t(Event) *event_queue;
+static uv_timer_t timer;
+static uv_prepare_t timer_prepare;
+static void timer_cb(uv_timer_t *handle);
+static void timer_prepare_cb(uv_prepare_t *);
+
+void event_init()
+{
+ // Initialize the event queue
+ event_queue = kl_init(Event);
+ // Initialize input events
+ input_init();
+ // Timer to wake the event loop if a timeout argument is passed to
+ // `event_poll`
+ // Signals
+ signal_init();
+ // Jobs
+ job_init();
+ // Channels
+ channel_init();
+ // Servers
+ server_init();
+ uv_timer_init(uv_default_loop(), &timer);
+ // This prepare handle that actually starts the timer
+ uv_prepare_init(uv_default_loop(), &timer_prepare);
+}
+
+void event_teardown()
+{
+ channel_teardown();
+ job_teardown();
+ server_teardown();
+}
+
+// Wait for some event
+bool event_poll(int32_t ms)
+{
+ bool timed_out;
+ uv_run_mode run_mode = UV_RUN_ONCE;
+
+ if (input_ready()) {
+ // If there's a pending input event to be consumed, do it now
+ return true;
+ }
+
+ input_start();
+ timed_out = false;
+
+ if (ms > 0) {
+ // Timeout passed as argument to the timer
+ timer.data = &timed_out;
+ // We only start the timer after the loop is running, for that we
+ // use an prepare handle(pass the interval as data to it)
+ timer_prepare.data = &ms;
+ uv_prepare_start(&timer_prepare, timer_prepare_cb);
+ } 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 {
+ // Run one event loop iteration, blocking for events if run_mode is
+ // UV_RUN_ONCE
+ uv_run(uv_default_loop(), run_mode);
+ } while (
+ // Continue running if ...
+ !input_ready() && // we have no input
+ kl_empty(event_queue) && // no events are waiting to be processed
+ run_mode != UV_RUN_NOWAIT && // ms != 0
+ !timed_out); // we didn't get a timeout
+
+ input_stop();
+
+ if (ms > 0) {
+ // Stop the timer
+ uv_timer_stop(&timer);
+ }
+
+ return input_ready() || event_is_pending();
+}
+
+bool event_is_pending()
+{
+ return !kl_empty(event_queue);
+}
+
+// Push an event to the queue
+void event_push(Event event)
+{
+ *kl_pushp(Event, event_queue) = event;
+}
+
+// Runs the appropriate action for each queued event
+void event_process()
+{
+ Event event;
+
+ while (kl_shift(Event, event_queue, &event) == 0) {
+ switch (event.type) {
+ case kEventSignal:
+ signal_handle(event);
+ break;
+ case kEventRStreamData:
+ rstream_read_event(event);
+ break;
+ case kEventJobExit:
+ job_exit_event(event);
+ break;
+ default:
+ abort();
+ }
+ }
+}
+
+// Set a flag in the `event_poll` loop for signaling of a timeout
+static void timer_cb(uv_timer_t *handle)
+{
+ *((bool *)handle->data) = true;
+}
+
+static void timer_prepare_cb(uv_prepare_t *handle)
+{
+ uv_timer_start(&timer, timer_cb, *(uint32_t *)timer_prepare.data, 0);
+ uv_prepare_stop(&timer_prepare);
+}
diff --git a/src/nvim/os/event.h b/src/nvim/os/event.h
new file mode 100644
index 0000000000..5ddca46875
--- /dev/null
+++ b/src/nvim/os/event.h
@@ -0,0 +1,18 @@
+#ifndef NEOVIM_OS_EVENT_H
+#define NEOVIM_OS_EVENT_H
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include "os/event_defs.h"
+#include "os/job_defs.h"
+
+void event_init(void);
+void event_teardown(void);
+bool event_poll(int32_t ms);
+bool event_is_pending(void);
+void event_push(Event event);
+void event_process(void);
+
+#endif // NEOVIM_OS_EVENT_H
+
diff --git a/src/nvim/os/event_defs.h b/src/nvim/os/event_defs.h
new file mode 100644
index 0000000000..428ae50f3e
--- /dev/null
+++ b/src/nvim/os/event_defs.h
@@ -0,0 +1,25 @@
+#ifndef NEOVIM_OS_EVENT_DEFS_H
+#define NEOVIM_OS_EVENT_DEFS_H
+
+#include "os/job_defs.h"
+#include "os/rstream_defs.h"
+
+typedef enum {
+ kEventSignal,
+ kEventRStreamData,
+ kEventJobExit
+} EventType;
+
+typedef struct {
+ EventType type;
+ union {
+ int signum;
+ struct {
+ RStream *ptr;
+ bool eof;
+ } rstream;
+ Job *job;
+ } data;
+} Event;
+
+#endif // NEOVIM_OS_EVENT_DEFS_H
diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c
new file mode 100644
index 0000000000..89eb4c8691
--- /dev/null
+++ b/src/nvim/os/fs.c
@@ -0,0 +1,277 @@
+// fs.c -- filesystem access
+
+#include "os/os.h"
+#include "memory.h"
+#include "message.h"
+#include "misc1.h"
+#include "misc2.h"
+#include "path.h"
+
+static bool is_executable(const char_u *name);
+static bool is_executable_in_path(const char_u *name);
+
+// Many fs functions from libuv return that value on success.
+static const int kLibuvSuccess = 0;
+
+int os_chdir(const char *path) {
+ if (p_verbose >= 5) {
+ verbose_enter();
+ smsg((char_u *)"chdir(%s)", path);
+ verbose_leave();
+ }
+ return uv_chdir(path);
+}
+
+int os_dirname(char_u *buf, size_t len)
+{
+ assert(buf && len);
+
+ int errno;
+ if ((errno = uv_cwd((char *)buf, &len)) != kLibuvSuccess) {
+ vim_strncpy(buf, (char_u *)uv_strerror(errno), len - 1);
+ return FAIL;
+ }
+ return OK;
+}
+
+bool os_isdir(const char_u *name)
+{
+ int32_t mode = os_getperm(name);
+ if (mode < 0) {
+ return false;
+ }
+
+ if (!S_ISDIR(mode)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool os_can_exe(const char_u *name)
+{
+ // If it's an absolute or relative path don't need to use $PATH.
+ if (path_is_absolute_path(name) ||
+ (name[0] == '.' && (name[1] == '/' ||
+ (name[1] == '.' && name[2] == '/')))) {
+ return is_executable(name);
+ }
+
+ return is_executable_in_path(name);
+}
+
+// Return true if "name" is an executable file, false if not or it doesn't
+// exist.
+static bool is_executable(const char_u *name)
+{
+ int32_t mode = os_getperm(name);
+
+ if (mode < 0) {
+ return false;
+ }
+
+ if (S_ISREG(mode) && (S_IEXEC & mode)) {
+ return true;
+ }
+
+ return false;
+}
+
+/// Check if a file is inside the $PATH and is executable.
+///
+/// @return `true` if `name` is an executable inside $PATH.
+static bool is_executable_in_path(const char_u *name)
+{
+ const char *path = getenv("PATH");
+ // PATH environment variable does not exist or is empty.
+ if (path == NULL || *path == NUL) {
+ return false;
+ }
+
+ int buf_len = STRLEN(name) + STRLEN(path) + 2;
+ char_u *buf = alloc((unsigned)(buf_len));
+
+ // Walk through all entries in $PATH to check if "name" exists there and
+ // is an executable file.
+ for (;; ) {
+ const char *e = strchr(path, ':');
+ if (e == NULL) {
+ e = path + STRLEN(path);
+ }
+
+ // Glue together the given directory from $PATH with name and save into
+ // buf.
+ vim_strncpy(buf, (char_u *) path, e - path);
+ append_path((char *) buf, (const char *) name, buf_len);
+
+ if (is_executable(buf)) {
+ // Found our executable. Free buf and return.
+ free(buf);
+ return true;
+ }
+
+ if (*e != ':') {
+ // End of $PATH without finding any executable called name.
+ free(buf);
+ return false;
+ }
+
+ path = e + 1;
+ }
+
+ // We should never get to this point.
+ assert(false);
+ return false;
+}
+
+int os_stat(const char_u *name, uv_stat_t *statbuf)
+{
+ uv_fs_t request;
+ int result = uv_fs_stat(uv_default_loop(), &request,
+ (const char *)name, NULL);
+ *statbuf = request.statbuf;
+ uv_fs_req_cleanup(&request);
+
+ if (result == kLibuvSuccess) {
+ return OK;
+ }
+
+ return FAIL;
+}
+
+int32_t os_getperm(const char_u *name)
+{
+ uv_stat_t statbuf;
+ if (os_stat(name, &statbuf) == FAIL) {
+ return -1;
+ } else {
+ return (int32_t)statbuf.st_mode;
+ }
+}
+
+int os_setperm(const char_u *name, int perm)
+{
+ uv_fs_t request;
+ int result = uv_fs_chmod(uv_default_loop(), &request,
+ (const char*)name, perm, NULL);
+ uv_fs_req_cleanup(&request);
+
+ if (result == kLibuvSuccess) {
+ return OK;
+ }
+
+ return FAIL;
+}
+
+bool os_file_exists(const char_u *name)
+{
+ uv_stat_t statbuf;
+ if (os_stat(name, &statbuf) == OK) {
+ return true;
+ }
+
+ return false;
+}
+
+bool os_file_is_readonly(const char *name)
+{
+ return access(name, W_OK) != 0;
+}
+
+int os_file_is_writable(const char *name)
+{
+ if (access(name, W_OK) == 0) {
+ if (os_isdir((char_u *)name)) {
+ return 2;
+ }
+ return 1;
+ }
+ return 0;
+}
+
+bool os_get_file_size(const char *name, off_t *size)
+{
+ uv_stat_t statbuf;
+ if (os_stat((char_u *)name, &statbuf) == OK) {
+ *size = statbuf.st_size;
+ return true;
+ }
+ return false;
+}
+
+int os_rename(const char_u *path, const char_u *new_path)
+{
+ uv_fs_t request;
+ int result = uv_fs_rename(uv_default_loop(), &request,
+ (const char *)path, (const char *)new_path, NULL);
+ uv_fs_req_cleanup(&request);
+
+ if (result == kLibuvSuccess) {
+ return OK;
+ }
+
+ return FAIL;
+}
+
+int os_mkdir(const char *path, int32_t mode)
+{
+ uv_fs_t request;
+ int result = uv_fs_mkdir(uv_default_loop(), &request, path, mode, NULL);
+ uv_fs_req_cleanup(&request);
+ return result;
+}
+
+int os_rmdir(const char *path)
+{
+ uv_fs_t request;
+ int result = uv_fs_rmdir(uv_default_loop(), &request, path, NULL);
+ uv_fs_req_cleanup(&request);
+ return result;
+}
+
+int os_remove(const char *path)
+{
+ uv_fs_t request;
+ int result = uv_fs_unlink(uv_default_loop(), &request, path, NULL);
+ uv_fs_req_cleanup(&request);
+ return result;
+}
+
+bool os_get_file_info(const char *path, FileInfo *file_info)
+{
+ if (os_stat((char_u *)path, &(file_info->stat)) == OK) {
+ return true;
+ }
+ return false;
+}
+
+bool os_get_file_info_link(const char *path, FileInfo *file_info)
+{
+ uv_fs_t request;
+ int result = uv_fs_lstat(uv_default_loop(), &request, path, NULL);
+ file_info->stat = request.statbuf;
+ uv_fs_req_cleanup(&request);
+ if (result == kLibuvSuccess) {
+ return true;
+ }
+ return false;
+}
+
+bool os_get_file_info_fd(int file_descriptor, FileInfo *file_info)
+{
+ uv_fs_t request;
+ int result = uv_fs_fstat(uv_default_loop(), &request, file_descriptor, NULL);
+ file_info->stat = request.statbuf;
+ uv_fs_req_cleanup(&request);
+ if (result == kLibuvSuccess) {
+ return true;
+ }
+ return false;
+}
+
+bool os_file_info_id_equal(FileInfo *file_info_1, FileInfo *file_info_2)
+{
+ return file_info_1->stat.st_ino == file_info_2->stat.st_ino
+ && file_info_1->stat.st_dev == file_info_2->stat.st_dev;
+}
+
diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c
new file mode 100644
index 0000000000..4310edd514
--- /dev/null
+++ b/src/nvim/os/input.c
@@ -0,0 +1,195 @@
+#include <string.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <uv.h>
+
+#include "os/input.h"
+#include "os/event.h"
+#include "os/rstream_defs.h"
+#include "os/rstream.h"
+#include "vim.h"
+#include "ui.h"
+#include "fileio.h"
+#include "getchar.h"
+#include "term.h"
+
+#define READ_BUFFER_SIZE 256
+
+typedef enum {
+ kInputNone,
+ kInputAvail,
+ kInputEof
+} InbufPollResult;
+
+static RStream *read_stream;
+static bool eof = false, started_reading = false;
+
+static InbufPollResult inbuf_poll(int32_t ms);
+static void stderr_switch(void);
+static void read_cb(RStream *rstream, void *data, bool eof);
+// Helper function used to push bytes from the 'event' key sequence partially
+// between calls to os_inchar when maxlen < 3
+static int push_event_key(uint8_t *buf, int maxlen);
+
+void input_init()
+{
+ read_stream = rstream_new(read_cb, READ_BUFFER_SIZE, NULL, false);
+ rstream_set_file(read_stream, read_cmd_fd);
+}
+
+// Check if there's pending input
+bool input_ready()
+{
+ return rstream_available(read_stream) > 0 || eof;
+}
+
+// Listen for input
+void input_start()
+{
+ rstream_start(read_stream);
+}
+
+// Stop listening for input
+void input_stop()
+{
+ rstream_stop(read_stream);
+}
+
+// Copies (at most `count`) of was read from `read_cmd_fd` into `buf`
+uint32_t input_read(char *buf, uint32_t count)
+{
+ return rstream_read(read_stream, buf, count);
+}
+
+
+// Low level input function.
+int os_inchar(uint8_t *buf, int maxlen, int32_t ms, int tb_change_cnt)
+{
+ InbufPollResult result;
+
+ if (event_is_pending()) {
+ // Return pending event bytes
+ return push_event_key(buf, maxlen);
+ }
+
+ if (ms >= 0) {
+ if ((result = inbuf_poll(ms)) == kInputNone) {
+ return 0;
+ }
+ } else {
+ if ((result = inbuf_poll(p_ut)) == kInputNone) {
+ 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 there are pending events, return the keys directly
+ if (event_is_pending()) {
+ return push_event_key(buf, maxlen);
+ }
+
+ // If input was put directly in typeahead buffer bail out here.
+ if (typebuf_changed(tb_change_cnt)) {
+ return 0;
+ }
+
+ if (result == kInputEof) {
+ read_error_exit();
+ return 0;
+ }
+
+ return read_from_input_buf(buf, (int64_t)maxlen);
+}
+
+// Check if a character is available for reading
+bool os_char_avail()
+{
+ return inbuf_poll(0) == kInputAvail;
+}
+
+// Check for CTRL-C typed by reading all available characters.
+// In cooked mode we should get SIGINT, no need to check.
+void os_breakcheck()
+{