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()
+{
+ if (curr_tmode == TMODE_RAW && event_poll(0))
+ fill_input_buf(false);
+}
+
+bool os_isatty(int fd)
+{
+ return uv_guess_handle(fd) == UV_TTY;
+}
+
+// This is a replacement for the old `WaitForChar` function in os_unix.c
+static InbufPollResult inbuf_poll(int32_t ms)
+{
+ if (input_available()) {
+ return kInputAvail;
+ }
+
+ if (event_poll(ms)) {
+ return eof && rstream_available(read_stream) == 0 ?
+ kInputEof :
+ kInputAvail;
+ }
+
+ return kInputNone;
+}
+
+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
+ rstream_stop(read_stream);
+ // Use stderr for stdin, also works for shell commands.
+ read_cmd_fd = 2;
+ // Initialize and start the input stream
+ rstream_set_file(read_stream, read_cmd_fd);
+ rstream_start(read_stream);
+ // Set the mode back to what it was
+ settmode(mode);
+}
+
+static void read_cb(RStream *rstream, void *data, bool at_eof)
+{
+ if (at_eof) {
+ if (!started_reading
+ && rstream_is_regular_file(rstream)
+ && os_isatty(STDERR_FILENO)) {
+ // 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;
+ }
+ }
+
+ started_reading = true;
+}
+
+static int push_event_key(uint8_t *buf, int maxlen)
+{
+ static const uint8_t key[3] = { K_SPECIAL, KS_EXTRA, KE_EVENT };
+ static int key_idx = 0;
+ int buf_idx = 0;
+
+ do {
+ buf[buf_idx++] = key[key_idx++];
+ key_idx %= 3;
+ } while (key_idx > 0 && buf_idx < maxlen);
+
+ return buf_idx;
+}
diff --git a/src/nvim/os/input.h b/src/nvim/os/input.h
new file mode 100644
index 0000000000..9ffd50fd3f
--- /dev/null
+++ b/src/nvim/os/input.h
@@ -0,0 +1,23 @@
+#ifndef NEOVIM_OS_INPUT_H
+#define NEOVIM_OS_INPUT_H
+
+#include <stdint.h>
+#include <stdbool.h>
+
+void input_init(void);
+bool input_ready(void);
+void input_start(void);
+void input_stop(void);
+uint32_t input_read(char *buf, uint32_t count);
+int os_inchar(uint8_t *, int, int32_t, int);
+bool os_char_avail(void);
+void os_breakcheck(void);
+
+/// Test whether a file descriptor refers to a terminal.
+///
+/// @param fd File descriptor.
+/// @return `true` if file descriptor refers to a terminal.
+bool os_isatty(int fd);
+
+#endif // NEOVIM_OS_INPUT_H
+
diff --git a/src/nvim/os/job.c b/src/nvim/os/job.c
new file mode 100644
index 0000000000..7940338b70
--- /dev/null
+++ b/src/nvim/os/job.c
@@ -0,0 +1,370 @@
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <uv.h>
+
+#include "os/uv_helpers.h"
+#include "os/job.h"
+#include "os/job_defs.h"
+#include "os/rstream.h"
+#include "os/rstream_defs.h"
+#include "os/wstream.h"
+#include "os/wstream_defs.h"
+#include "os/event.h"
+#include "os/event_defs.h"
+#include "os/time.h"
+#include "os/shell.h"
+#include "vim.h"
+#include "memory.h"
+#include "term.h"
+
+#define EXIT_TIMEOUT 25
+#define MAX_RUNNING_JOBS 100
+#define JOB_BUFFER_SIZE 1024
+#define JOB_WRITE_MAXMEM 1024 * 1024
+
+struct job {
+ // Job id the index in the job table plus one.
+ int id;
+ // Number of polls after a SIGTERM that will trigger a SIGKILL
+ int exit_timeout;
+ // exit_cb may be called while there's still pending data from stdout/stderr.
+ // We use this reference count to ensure the JobExit event is only emitted
+ // when stdout/stderr are drained
+ int pending_refs;
+ // Same as above, but for freeing the job memory which contains
+ // libuv handles. Only after all are closed the job can be safely freed.
+ int pending_closes;
+ // If the job was already stopped
+ bool stopped;
+ // Data associated with the job
+ void *data;
+ // Callbacks
+ job_exit_cb exit_cb;
+ rstream_cb stdout_cb, stderr_cb;
+ // Readable streams(std{out,err})
+ RStream *out, *err;
+ // Writable stream(stdin)
+ WStream *in;
+ // Structures for process spawning/management used by libuv
+ uv_process_t proc;
+ uv_process_options_t proc_opts;
+ uv_stdio_container_t stdio[3];
+ uv_pipe_t proc_stdin, proc_stdout, proc_stderr;
+};
+
+static Job *table[MAX_RUNNING_JOBS] = {NULL};
+static uint32_t job_count = 0;
+static uv_prepare_t job_prepare;
+
+// Some helpers shared in this module
+static bool is_alive(Job *job);
+static Job * find_job(int id);
+static void free_job(Job *job);
+
+// Callbacks for libuv
+static void job_prepare_cb(uv_prepare_t *handle);
+static void read_cb(RStream *rstream, void *data, bool eof);
+static void exit_cb(uv_process_t *proc, int64_t status, int term_signal);
+static void close_cb(uv_handle_t *handle);
+static void emit_exit_event(Job *job);
+
+void job_init()
+{
+ uv_disable_stdio_inheritance();
+ uv_prepare_init(uv_default_loop(), &job_prepare);
+}
+
+void job_teardown()
+{
+ // 20 tries will give processes about 1 sec to exit cleanly
+ uint32_t remaining_tries = 20;
+ bool all_dead = true;
+ int i;
+ Job *job;
+
+ // Politely ask each job to terminate
+ for (i = 0; i < MAX_RUNNING_JOBS; i++) {
+ if ((job = table[i]) != NULL) {
+ all_dead = false;
+ uv_process_kill(&job->proc, SIGTERM);
+ }
+ }
+
+ if (all_dead) {
+ return;
+ }
+
+ os_delay(10, 0);
+ // Right now any exited process are zombies waiting for us to acknowledge
+ // their status with `wait` or handling SIGCHLD. libuv does that
+ // automatically (and then calls `exit_cb`) but we have to give it a chance
+ // by running the loop one more time
+ uv_run(uv_default_loop(), UV_RUN_NOWAIT);
+
+ // Prepare to start shooting
+ for (i = 0; i < MAX_RUNNING_JOBS; i++) {
+ if ((job = table[i]) == NULL) {
+ continue;
+ }
+
+ // Still alive
+ while (is_alive(job) && remaining_tries--) {
+ os_delay(50, 0);
+ // Acknowledge child exits
+ uv_run(uv_default_loop(), UV_RUN_NOWAIT);
+ }
+
+ if (is_alive(job)) {
+ uv_process_kill(&job->proc, SIGKILL);
+ }
+ }
+}
+
+int job_start(char **argv,
+ void *data,
+ rstream_cb stdout_cb,
+ rstream_cb stderr_cb,
+ job_exit_cb job_exit_cb)
+{
+ int i;
+ Job *job;
+
+ // Search for a free slot in the table
+ for (i = 0; i < MAX_RUNNING_JOBS; i++) {
+ if (table[i] == NULL) {
+ break;
+ }
+ }
+
+ if (i == MAX_RUNNING_JOBS) {
+ // No free slots
+ return 0;
+ }
+
+ job = xmalloc(sizeof(Job));
+ // Initialize
+ job->id = i + 1;
+ job->pending_refs = 3;
+ job->pending_closes = 4;
+ job->data = data;
+ job->stdout_cb = stdout_cb;
+ job->stderr_cb = stderr_cb;
+ job->exit_cb = job_exit_cb;
+ job->stopped = false;
+ job->exit_timeout = EXIT_TIMEOUT;
+ job->proc_opts.file = argv[0];
+ job->proc_opts.args = argv;
+ job->proc_opts.stdio = job->stdio;
+ job->proc_opts.stdio_count = 3;
+ job->proc_opts.flags = UV_PROCESS_WINDOWS_HIDE;
+ job->proc_opts.exit_cb = exit_cb;
+ job->proc_opts.cwd = NULL;
+ job->proc_opts.env = NULL;
+ job->proc.data = NULL;
+ job->proc_stdin.data = NULL;
+ job->proc_stdout.data = NULL;
+ job->proc_stderr.data = NULL;
+
+ // Initialize the job std{in,out,err}
+ uv_pipe_init(uv_default_loop(), &job->proc_stdin, 0);
+ job->stdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE;
+ job->stdio[0].data.stream = (uv_stream_t *)&job->proc_stdin;
+
+ uv_pipe_init(uv_default_loop(), &job->proc_stdout, 0);
+ job->stdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
+ job->stdio[1].data.stream = (uv_stream_t *)&job->proc_stdout;
+
+ uv_pipe_init(uv_default_loop(), &job->proc_stderr, 0);
+ job->stdio[2].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
+ job->stdio[2].data.stream = (uv_stream_t *)&job->proc_stderr;
+
+ // Spawn the job
+ if (uv_spawn(uv_default_loop(), &job->proc, &job->proc_opts) != 0) {
+ free_job(job);
+ return -1;
+ }
+
+ // Give all handles a reference to the job
+ handle_set_job((uv_handle_t *)&job->proc, job);
+ handle_set_job((uv_handle_t *)&job->proc_stdin, job);
+ handle_set_job((uv_handle_t *)&job->proc_stdout, job);
+ handle_set_job((uv_handle_t *)&job->proc_stderr, job);
+
+ job->in = wstream_new(JOB_WRITE_MAXMEM);
+ wstream_set_stream(job->in, (uv_stream_t *)&job->proc_stdin);
+ // Start the readable streams
+ job->out = rstream_new(read_cb, JOB_BUFFER_SIZE, job, true);
+ job->err = rstream_new(read_cb, JOB_BUFFER_SIZE, job, true);
+ rstream_set_stream(job->out, (uv_stream_t *)&job->proc_stdout);
+ rstream_set_stream(job->err, (uv_stream_t *)&job->proc_stderr);
+ rstream_start(job->out);
+ rstream_start(job->err);
+ // Save the job to the table
+ table[i] = job;
+
+ // Start polling job status if this is the first
+ if (job_count == 0) {
+ uv_prepare_start(&job_prepare, job_prepare_cb);
+ }
+ job_count++;
+
+ return job->id;
+}
+
+bool job_stop(int id)
+{
+ Job *job = find_job(id);
+
+ if (job == NULL || job->stopped) {
+ return false;
+ }
+
+ job->stopped = true;
+
+ return true;
+}
+
+bool job_write(int id, char *data, uint32_t len)
+{
+ Job *job = find_job(id);
+
+ if (job == NULL || job->stopped) {
+ free(data);
+ return false;
+ }
+
+ if (!wstream_write(job->in, data, len, true)) {
+ job_stop(job->id);
+ return false;
+ }
+
+ return true;
+}
+
+void job_exit_event(Event event)
+{
+ Job *job = event.data.job;
+
+ // Free the slot now, 'exit_cb' may want to start another job to replace
+ // this one
+ table[job->id - 1] = NULL;
+
+ if (job->exit_cb) {
+ // Invoke the exit callback
+ job->exit_cb(job, job->data);
+ }
+
+ // Free the job resources
+ free_job(job);
+
+ // Stop polling job status if this was the last
+ job_count--;
+ if (job_count == 0) {
+ uv_prepare_stop(&job_prepare);
+ }
+}
+
+int job_id(Job *job)
+{
+ return job->id;
+}
+
+void *job_data(Job *job)
+{
+ return job->data;
+}
+
+static bool is_alive(Job *job)
+{
+ return uv_process_kill(&job->proc, 0) == 0;
+}
+
+static Job * find_job(int id)
+{
+ if (id <= 0 || id > MAX_RUNNING_JOBS) {
+ return NULL;
+ }
+
+ return table[id - 1];
+}
+
+static void free_job(Job *job)
+{
+ uv_close((uv_handle_t *)&job->proc_stdout, close_cb);
+ uv_close((uv_handle_t *)&job->proc_stdin, close_cb);
+ uv_close((uv_handle_t *)&job->proc_stderr, close_cb);
+ uv_close((uv_handle_t *)&job->proc, close_cb);
+}
+
+/// Iterates the table, sending SIGTERM to stopped jobs and SIGKILL to those
+/// that didn't die from SIGTERM after a while(exit_timeout is 0).
+static void job_prepare_cb(uv_prepare_t *handle)
+{
+ Job *job;
+ int i;
+
+ for (i = 0; i < MAX_RUNNING_JOBS; i++) {
+ if ((job = table[i]) == NULL || !job->stopped) {
+ continue;
+ }
+
+ if ((job->exit_timeout--) == EXIT_TIMEOUT) {
+ // Job was just stopped, close all stdio handles and send SIGTERM
+ uv_process_kill(&job->proc, SIGTERM);
+ } else if (job->exit_timeout == 0) {
+ // We've waited long enough, send SIGKILL
+ uv_process_kill(&job->proc, SIGKILL);
+ }
+ }
+}
+
+// Wraps the call to std{out,err}_cb and emits a JobExit event if necessary.
+static void read_cb(RStream *rstream, void *data, bool eof)
+{
+ Job *job = data;
+
+ if (rstream == job->out) {
+ job->stdout_cb(rstream, data, eof);
+ } else {
+ job->stderr_cb(rstream, data, eof);
+ }
+
+ if (eof && --job->pending_refs == 0) {
+ emit_exit_event(job);
+ }
+}
+
+// Emits a JobExit event if both rstreams are closed
+static void exit_cb(uv_process_t *proc, int64_t status, int term_signal)
+{
+ Job *job = handle_get_job((uv_handle_t *)proc);
+
+ if (--job->pending_refs == 0) {
+ emit_exit_event(job);
+ }
+}
+
+static void emit_exit_event(Job *job)
+{
+ Event event;
+ event.type = kEventJobExit;
+ event.data.job = job;
+ event_push(event);
+}
+
+static void close_cb(uv_handle_t *handle)
+{
+ Job *job = handle_get_job(handle);
+
+ if (--job->pending_closes == 0) {
+ // Only free the job memory after all the associated handles are properly
+ // closed by libuv
+ rstream_free(job->out);
+ rstream_free(job->err);
+ wstream_free(job->in);
+ shell_free_argv(job->proc_opts.args);
+ free(job->data);
+ free(job);
+ }
+}
diff --git a/src/nvim/os/job.h b/src/nvim/os/job.h
new file mode 100644
index 0000000000..db147f2c24
--- /dev/null
+++ b/src/nvim/os/job.h
@@ -0,0 +1,78 @@
+// Job is a short name we use to refer to child processes that run in parallel
+// with the editor, probably executing long-running tasks and sending updates
+// asynchronously. Communication happens through anonymous pipes connected to
+// the job's std{in,out,err}. They are more like bash/zsh co-processes than the
+// usual shell background job. The name 'Job' was chosen because it applies to
+// the concept while being significantly shorter.
+#ifndef NEOVIM_OS_JOB_H
+#define NEOVIM_OS_JOB_H
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include "os/event_defs.h"
+#include "os/rstream_defs.h"
+
+/// Initializes job control resources
+void job_init(void);
+
+/// Releases job control resources and terminates running jobs
+void job_teardown(void);
+
+/// Tries to start a new job.
+///
+/// @param argv Argument vector for the process. The first item is the
+/// executable to run.
+/// @param data Caller data that will be associated with the job
+/// @param stdout_cb Callback that will be invoked when data is available
+/// on stdout
+/// @param stderr_cb Callback that will be invoked when data is available
+/// on stderr
+/// @param exit_cb Callback that will be invoked when the job exits. This is
+/// optional.
+/// @return The job id if the job started successfully. If the the first item /
+/// of `argv`(the program) could not be executed, -1 will be returned.
+// 0 will be returned if the job table is full.
+int job_start(char **argv,
+ void *data,
+ rstream_cb stdout_cb,
+ rstream_cb stderr_cb,
+ job_exit_cb exit_cb);
+
+/// Terminates a job. This is a non-blocking operation, but if the job exists
+/// it's guaranteed to succeed(SIGKILL will eventually be sent)
+///
+/// @param id The job id
+/// @return true if the stop request was successfully sent, false if the job
+/// id is invalid(probably because it has already stopped)
+bool job_stop(int id);
+
+/// Writes data to the job's stdin. This is a non-blocking operation, it
+/// returns when the write request was sent.
+///
+/// @param id The job id
+/// @param data Buffer containing the data to be written
+/// @param len Size of the data
+/// @return true if the write request was successfully sent, false if the job
+/// id is invalid(probably because it has already stopped)
+bool job_write(int id, char *data, uint32_t len);
+
+/// Runs the read callback associated with the job exit event
+///
+/// @param event Object containing data necessary to invoke the callback
+void job_exit_event(Event event);
+
+/// Get the job id
+///
+/// @param job A pointer to the job
+/// @return The job id
+int job_id(Job *job);
+
+/// Get data associated with a job
+///
+/// @param job A pointer to the job
+/// @return The job data
+void *job_data(Job *job);
+
+#endif // NEOVIM_OS_JOB_H
+
diff --git a/src/nvim/os/job_defs.h b/src/nvim/os/job_defs.h
new file mode 100644
index 0000000000..e98b639154
--- /dev/null
+++ b/src/nvim/os/job_defs.h
@@ -0,0 +1,15 @@
+#ifndef NEOVIM_OS_JOB_DEFS_H
+#define NEOVIM_OS_JOB_DEFS_H
+
+#include "os/rstream_defs.h"
+
+typedef struct job Job;
+
+/// Function called when the job reads data
+///
+/// @param id The job id
+/// @param data Some data associated with the job by the caller
+typedef void (*job_exit_cb)(Job *job, void *data);
+
+#endif // NEOVIM_OS_JOB_DEFS_H
+
diff --git a/src/nvim/os/mem.c b/src/nvim/os/mem.c
new file mode 100644
index 0000000000..e5549340ff
--- /dev/null
+++ b/src/nvim/os/mem.c
@@ -0,0 +1,10 @@
+/// Functions for accessing system memory information.
+
+#include <uv.h>
+
+#include "os/os.h"
+
+uint64_t os_get_total_mem_kib(void) {
+ // Convert bytes to KiB.
+ return uv_get_total_memory() >> 10;
+}
diff --git a/src/nvim/os/msgpack_rpc.c b/src/nvim/os/msgpack_rpc.c
new file mode 100644
index 0000000000..e429bf0519
--- /dev/null
+++ b/src/nvim/os/msgpack_rpc.c
@@ -0,0 +1,408 @@
+#include <msgpack.h>
+
+#include "msgpack_rpc.h"
+#include "vim.h"
+#include "memory.h"
+
+static bool msgpack_rpc_to_uint16_t(msgpack_object *obj, uint16_t *arg);
+
+void msgpack_rpc_call(msgpack_object *req, msgpack_packer *res)
+{
+ // The initial response structure is the same no matter what happens,
+ // we set it up here
+ // Array of size 4
+ msgpack_pack_array(res, 4);
+ // Response type is 1
+ msgpack_pack_int(res, 1);
+
+ // Validate the basic structure of the msgpack-rpc payload
+ if (req->type != MSGPACK_OBJECT_ARRAY) {
+ msgpack_pack_int(res, 0); // no message id yet
+ msgpack_rpc_error("Request is not an array", res);
+ return;
+ }
+
+ if (req->via.array.size != 4) {
+ msgpack_pack_int(res, 0); // no message id yet
+ char error_msg[256];
+ snprintf(error_msg,
+ sizeof(error_msg),
+ "Request array size is %u, it should be 4",
+ req->via.array.size);
+ msgpack_rpc_error(error_msg, res);
+ }
+
+ if (req->via.array.ptr[1].type != MSGPACK_OBJECT_POSITIVE_INTEGER) {
+ msgpack_pack_int(res, 0); // no message id yet
+ msgpack_rpc_error("Id must be a positive integer", res);
+ }
+
+ // Set the response id, which is the same as the request
+ msgpack_pack_int(res, req->via.array.ptr[1].via.u64);
+
+ if (req->via.array.ptr[0].type != MSGPACK_OBJECT_POSITIVE_INTEGER) {
+ msgpack_rpc_error("Message type must be an integer", res);
+ return;
+ }
+
+ if (req->via.array.ptr[0].via.u64 != 0) {
+ msgpack_rpc_error("Message type must be 0", res);
+ return;
+ }
+
+ if (req->via.array.ptr[2].type != MSGPACK_OBJECT_POSITIVE_INTEGER) {
+ msgpack_rpc_error("Method id must be a positive integer", res);
+ return;
+ }
+
+ if (req->via.array.ptr[3].type != MSGPACK_OBJECT_ARRAY) {
+ msgpack_rpc_error("Paremeters must be an array", res);
+ return;
+ }
+
+ // dispatch the message
+ msgpack_rpc_dispatch(req, res);
+}
+
+void msgpack_rpc_error(char *msg, msgpack_packer *res)
+{
+ size_t len = strlen(msg);
+
+ // error message
+ msgpack_pack_raw(res, len);
+ msgpack_pack_raw_body(res, msg, len);
+ // Nil result
+ msgpack_pack_nil(res);
+}
+
+bool msgpack_rpc_to_bool(msgpack_object *obj, bool *arg)
+{
+ *arg = obj->via.boolean;
+ return obj->type == MSGPACK_OBJECT_BOOLEAN;
+}
+
+bool msgpack_rpc_to_int64_t(msgpack_object *obj, int64_t *arg)
+{
+ *arg = obj->via.i64;
+ return obj->type == MSGPACK_OBJECT_POSITIVE_INTEGER
+ || obj->type == MSGPACK_OBJECT_NEGATIVE_INTEGER;
+}
+
+bool msgpack_rpc_to_double(msgpack_object *obj, double *arg)
+{
+ *arg = obj->via.dec;
+ return obj->type == MSGPACK_OBJECT_DOUBLE;
+}
+
+bool msgpack_rpc_to_string(msgpack_object *obj, String *arg)
+{
+ arg->data = (char *)obj->via.raw.ptr;
+ arg->size = obj->via.raw.size;
+ return obj->type == MSGPACK_OBJECT_RAW;
+}
+
+bool msgpack_rpc_to_buffer(msgpack_object *obj, Buffer *arg)
+{
+ return msgpack_rpc_to_uint16_t(obj, arg);
+}
+
+bool msgpack_rpc_to_window(msgpack_object *obj, Window *arg)
+{
+ return msgpack_rpc_to_uint16_t(obj, arg);
+}
+
+bool msgpack_rpc_to_tabpage(msgpack_object *obj, Tabpage *arg)
+{
+ return msgpack_rpc_to_uint16_t(obj, arg);
+}
+
+bool msgpack_rpc_to_object(msgpack_object *obj, Object *arg)
+{
+ switch (obj->type) {
+ case MSGPACK_OBJECT_NIL:
+ arg->type = kObjectTypeNil;
+ return true;
+
+ case MSGPACK_OBJECT_BOOLEAN:
+ arg->type = kObjectTypeBool;
+ return msgpack_rpc_to_bool(obj, &arg->data.boolean);
+
+ case MSGPACK_OBJECT_POSITIVE_INTEGER:
+ case MSGPACK_OBJECT_NEGATIVE_INTEGER:
+ arg->type = kObjectTypeInt;
+ return msgpack_rpc_to_int64_t(obj, &arg->data.integer);
+
+ case MSGPACK_OBJECT_DOUBLE:
+ arg->type = kObjectTypeFloat;
+ return msgpack_rpc_to_double(obj, &arg->data.floating_point);
+
+ case MSGPACK_OBJECT_RAW:
+ arg->type = kObjectTypeString;
+ return msgpack_rpc_to_string(obj, &arg->data.string);
+
+ case MSGPACK_OBJECT_ARRAY:
+ arg->type = kObjectTypeArray;
+ return msgpack_rpc_to_array(obj, &arg->data.array);
+
+ case MSGPACK_OBJECT_MAP:
+ arg->type = kObjectTypeDictionary;
+ return msgpack_rpc_to_dictionary(obj, &arg->data.dictionary);
+
+ default:
+ return false;
+ }
+}
+
+bool msgpack_rpc_to_stringarray(msgpack_object *obj, StringArray *arg)
+{
+ if (obj->type != MSGPACK_OBJECT_ARRAY) {
+ return false;
+ }
+
+ arg->size = obj->via.array.size;
+ arg->items = xcalloc(obj->via.array.size, sizeof(String));
+
+ for (uint32_t i = 0; i < obj->via.array.size; i++) {
+ if (!msgpack_rpc_to_string(obj->via.array.ptr + i, &arg->items[i])) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool msgpack_rpc_to_position(msgpack_object *obj, Position *arg)
+{
+ // positions are represented by integer arrays of size 2
+ if (obj->type != MSGPACK_OBJECT_ARRAY
+ || obj->via.array.size != 2
+ || obj->via.array.ptr[0].type != MSGPACK_OBJECT_POSITIVE_INTEGER
+ || obj->via.array.ptr[1].type != MSGPACK_OBJECT_POSITIVE_INTEGER) {
+ return false;
+ }
+
+ arg->row = obj->via.array.ptr[0].via.u64;
+ arg->col = obj->via.array.ptr[1].via.u64;
+
+ return true;
+}
+
+
+bool msgpack_rpc_to_array(msgpack_object *obj, Array *arg)
+{
+ if (obj->type != MSGPACK_OBJECT_ARRAY) {
+ return false;
+ }
+
+ arg->size = obj->via.array.size;
+ arg->items = xcalloc(obj->via.array.size, sizeof(Object));
+
+ for (uint32_t i = 0; i < obj->via.array.size; i++) {
+ if (!msgpack_rpc_to_object(obj->via.array.ptr + i, &arg->items[i])) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool msgpack_rpc_to_dictionary(msgpack_object *obj, Dictionary *arg)
+{
+ if (obj->type != MSGPACK_OBJECT_MAP) {
+ return false;
+ }
+
+ arg->size = obj->via.array.size;
+ arg->items = xcalloc(obj->via.map.size, sizeof(KeyValuePair));
+
+
+ for (uint32_t i = 0; i < obj->via.map.size; i++) {
+ if (!msgpack_rpc_to_string(&obj->via.map.ptr[i].key,
+ &arg->items[i].key)) {
+ return false;
+ }
+
+ if (!msgpack_rpc_to_object(&obj->via.map.ptr[i].val,
+ &arg->items[i].value)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void msgpack_rpc_from_bool(bool result, msgpack_packer *res)
+{
+ if (result) {
+ msgpack_pack_true(res);
+ } else {
+ msgpack_pack_false(res);
+ }
+}
+
+void msgpack_rpc_from_int64_t(int64_t result, msgpack_packer *res)
+{
+ msgpack_pack_int64(res, result);
+}
+
+void msgpack_rpc_from_uint64_t(uint64_t result, msgpack_packer *res)
+{
+ msgpack_pack_uint64(res, result);
+}
+
+void msgpack_rpc_from_double(double result, msgpack_packer *res)
+{
+ msgpack_pack_double(res, result);
+}
+
+void msgpack_rpc_from_string(String result, msgpack_packer *res)
+{
+ msgpack_pack_raw(res, result.size);
+ msgpack_pack_raw_body(res, result.data, result.size);
+}
+
+void msgpack_rpc_from_buffer(Buffer result, msgpack_packer *res)
+{
+ msgpack_rpc_from_uint64_t(result, res);
+}
+
+void msgpack_rpc_from_window(Window result, msgpack_packer *res)
+{
+ msgpack_rpc_from_uint64_t(result, res);
+}
+
+void msgpack_rpc_from_tabpage(Tabpage result, msgpack_packer *res)
+{
+ msgpack_rpc_from_uint64_t(result, res);
+}
+
+void msgpack_rpc_from_object(Object result, msgpack_packer *res)
+{
+ switch (result.type) {
+ case kObjectTypeNil:
+ msgpack_pack_nil(res);
+ break;
+
+ case kObjectTypeBool:
+ msgpack_rpc_from_bool(result.data.boolean, res);
+ break;
+
+ case kObjectTypeInt:
+ msgpack_rpc_from_int64_t(result.data.integer, res);
+ break;
+
+ case kObjectTypeFloat:
+ msgpack_rpc_from_double(result.data.floating_point, res);
+ break;
+
+ case kObjectTypeString:
+ msgpack_rpc_from_string(result.data.string, res);
+ break;
+
+ case kObjectTypeArray:
+ msgpack_rpc_from_array(result.data.array, res);
+ break;
+
+ case kObjectTypeDictionary:
+ msgpack_rpc_from_dictionary(result.data.dictionary, res);
+ break;
+
+ default:
+ abort();
+ }
+}
+
+void msgpack_rpc_from_stringarray(StringArray result, msgpack_packer *res)
+{
+ msgpack_pack_array(res, result.size);
+
+ for (size_t i = 0; i < result.size; i++) {
+ msgpack_rpc_from_string(result.items[i], res);
+ }
+}
+
+void msgpack_rpc_from_position(Position result, msgpack_packer *res)
+{
+ msgpack_pack_array(res, 2);;
+ msgpack_pack_uint16(res, result.row);
+ msgpack_pack_uint16(res, result.col);
+}
+
+void msgpack_rpc_from_array(Array result, msgpack_packer *res)
+{
+ msgpack_pack_array(res, result.size);
+
+ for (size_t i = 0; i < result.size; i++) {
+ msgpack_rpc_from_object(result.items[i], res);
+ }
+}
+
+void msgpack_rpc_from_dictionary(Dictionary result, msgpack_packer *res)
+{
+ msgpack_pack_map(res, result.size);
+
+ for (size_t i = 0; i < result.size; i++) {
+ msgpack_rpc_from_string(result.items[i].key, res);
+ msgpack_rpc_from_object(result.items[i].value, res);
+ }
+}
+
+void msgpack_rpc_free_object(Object value)
+{
+ switch (value.type) {
+ case kObjectTypeNil:
+ case kObjectTypeBool:
+ case kObjectTypeInt:
+ case kObjectTypeFloat:
+ break;
+
+ case kObjectTypeString:
+ msgpack_rpc_free_string(value.data.string);
+ break;
+
+ case kObjectTypeArray:
+ msgpack_rpc_free_array(value.data.array);
+ break;
+
+ case kObjectTypeDictionary:
+ msgpack_rpc_free_dictionary(value.data.dictionary);
+ break;
+
+ default:
+ abort();
+ }
+}
+
+void msgpack_rpc_free_stringarray(StringArray value) {
+ for (uint32_t i = 0; i < value.size; i++) {
+ msgpack_rpc_free_string(value.items[i]);
+ }
+
+ free(value.items);
+}
+
+void msgpack_rpc_free_array(Array value)
+{
+ for (uint32_t i = 0; i < value.size; i++) {
+ msgpack_rpc_free_object(value.items[i]);
+ }
+
+ free(value.items);
+}
+
+void msgpack_rpc_free_dictionary(Dictionary value)
+{
+ for (uint32_t i = 0; i < value.size; i++) {
+ msgpack_rpc_free_string(value.items[i].key);
+ msgpack_rpc_free_object(value.items[i].value);
+ }
+
+ free(value.items);
+}
+
+static bool msgpack_rpc_to_uint16_t(msgpack_object *obj, uint16_t *arg)
+{
+ *arg = obj->via.u64;
+ return obj->type == MSGPACK_OBJECT_POSITIVE_INTEGER;
+}
+
diff --git a/src/nvim/os/msgpack_rpc.h b/src/nvim/os/msgpack_rpc.h
new file mode 100644
index 0000000000..7f754bfca1
--- /dev/null
+++ b/src/nvim/os/msgpack_rpc.h
@@ -0,0 +1,107 @@
+#ifndef NEOVIM_OS_MSGPACK_RPC_H
+#define NEOVIM_OS_MSGPACK_RPC_H
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <msgpack.h>
+
+#include "api/defs.h"
+
+/// Validates the basic structure of the msgpack-rpc call and fills `res`
+/// with the basic response structure.
+///
+/// @param req The parsed request object
+/// @param res A packer that contains the response
+void msgpack_rpc_call(msgpack_object *req, msgpack_packer *res);
+
+/// Dispatches to the actual API function after basic payload validation by
+/// `msgpack_rpc_call`. It is responsible for validating/converting arguments
+/// to C types, and converting the return value back to msgpack types.
+/// The implementation is generated at compile time with metadata extracted
+/// from the api/*.h headers,
+///
+/// @param req The parsed request object
+/// @param res A packer that contains the response
+void msgpack_rpc_dispatch(msgpack_object *req, msgpack_packer *res);
+
+/// Finishes the msgpack-rpc call with an error message.
+///
+/// @param msg The error message
+/// @param res A packer that contains the response
+void msgpack_rpc_error(char *msg, msgpack_packer *res);
+
+/// Functions for validating and converting from msgpack types to C types.
+/// These are used by `msgpack_rpc_dispatch` to validate and convert each
+/// argument.
+///
+/// @param obj The object to convert
+/// @param[out] arg A pointer to the avalue
+/// @return true if the convertion succeeded, false otherwise
+bool msgpack_rpc_to_bool(msgpack_object *obj, bool *arg);
+bool msgpack_rpc_to_int64_t(msgpack_object *obj, int64_t *arg);
+bool msgpack_rpc_to_double(msgpack_object *obj, double *arg);
+bool msgpack_rpc_to_position(msgpack_object *obj, Position *arg);
+bool msgpack_rpc_to_string(msgpack_object *obj, String *arg);
+bool msgpack_rpc_to_buffer(msgpack_object *obj, Buffer *arg);
+bool msgpack_rpc_to_window(msgpack_object *obj, Window *arg);
+bool msgpack_rpc_to_tabpage(msgpack_object *obj, Tabpage *arg);
+bool msgpack_rpc_to_object(msgpack_object *obj, Object *arg);
+bool msgpack_rpc_to_stringarray(msgpack_object *obj, StringArray *arg);
+bool msgpack_rpc_to_array(msgpack_object *obj, Array *arg);
+bool msgpack_rpc_to_dictionary(msgpack_object *obj, Dictionary *arg);
+
+/// Functions for converting from C types to msgpack types.
+/// These are used by `msgpack_rpc_dispatch` to convert return values
+/// from the API
+///
+/// @param result A pointer to the result
+/// @param res A packer that contains the response
+void msgpack_rpc_from_bool(bool result, msgpack_packer *res);
+void msgpack_rpc_from_int64_t(int64_t result, msgpack_packer *res);
+void msgpack_rpc_from_double(double result, msgpack_packer *res);
+void msgpack_rpc_from_position(Position result, msgpack_packer *res);
+void msgpack_rpc_from_string(String result, msgpack_packer *res);
+void msgpack_rpc_from_buffer(Buffer result, msgpack_packer *res);
+void msgpack_rpc_from_window(Window result, msgpack_packer *res);
+void msgpack_rpc_from_tabpage(Tabpage result, msgpack_packer *res);
+void msgpack_rpc_from_object(Object result, msgpack_packer *res);
+void msgpack_rpc_from_stringarray(StringArray result, msgpack_packer *res);
+void msgpack_rpc_from_array(Array result, msgpack_packer *res);
+void msgpack_rpc_from_dictionary(Dictionary result, msgpack_packer *res);
+
+/// Helpers for initializing types that may be freed later
+#define msgpack_rpc_init_bool
+#define msgpack_rpc_init_int64_t
+#define msgpack_rpc_init_double
+#define msgpack_rpc_init_position
+#define msgpack_rpc_init_string
+#define msgpack_rpc_init_buffer
+#define msgpack_rpc_init_window
+#define msgpack_rpc_init_tabpage
+#define msgpack_rpc_init_object = {.type = kObjectTypeNil}
+#define msgpack_rpc_init_stringarray = {.items = NULL, .size = 0}
+#define msgpack_rpc_init_array = {.items = NULL, .size = 0}
+#define msgpack_rpc_init_dictionary = {.items = NULL, .size = 0}
+
+/// Helpers for freeing arguments/return value
+///
+/// @param value The value to be freed
+#define msgpack_rpc_free_bool(value)
+#define msgpack_rpc_free_int64_t(value)
+#define msgpack_rpc_free_double(value)
+#define msgpack_rpc_free_position(value)
+// Strings are not copied from msgpack and so don't need to be freed(they
+// probably "live" in the msgpack streaming buffer)
+#define msgpack_rpc_free_string(value)
+#define msgpack_rpc_free_buffer(value)
+#define msgpack_rpc_free_window(value)
+#define msgpack_rpc_free_tabpage(value)
+void msgpack_rpc_free_object(Object value);
+void msgpack_rpc_free_stringarray(StringArray value);
+void msgpack_rpc_free_array(Array value);
+void msgpack_rpc_free_dictionary(Dictionary value);
+
+
+#endif // NEOVIM_OS_MSGPACK_RPC_H
+
diff --git a/src/nvim/os/os.h b/src/nvim/os/os.h
new file mode 100644
index 0000000000..b30872f06d
--- /dev/null
+++ b/src/nvim/os/os.h
@@ -0,0 +1,146 @@
+#ifndef NEOVIM_OS_OS_H
+#define NEOVIM_OS_OS_H
+#include <uv.h>
+
+#include "vim.h"
+
+/// Change to the given directory.
+///
+/// @return `0` on success, a libuv error code on failure.
+int os_chdir(const char *path);
+
+/// Get the name of current directory.
+///
+/// @param buf Buffer to store the directory name.
+/// @param len Length of `buf`.
+/// @return `OK` for success, `FAIL` for failure.
+int os_dirname(char_u *buf, size_t len);
+
+/// Check if the given path is a directory or not.
+///
+/// @return `true` if `fname` is a directory.
+bool os_isdir(const char_u *name);
+
+/// Check if the given path represents an executable file.
+///
+/// @return `true` if `name` is executable and
+/// - can be found in $PATH,
+/// - is relative to current dir or
+/// - is absolute.
+///
+/// @return `false` otherwise.
+bool os_can_exe(const char_u *name);
+
+/// Get the file permissions for a given file.
+///
+/// @return `-1` when `name` doesn't exist.
+int32_t os_getperm(const char_u *name);
+
+/// Set the permission of a file.
+///
+/// @return `OK` for success, `FAIL` for failure.
+int os_setperm(const char_u *name, int perm);
+
+/// Check if a file exists.
+///
+/// @return `true` if `name` exists.
+bool os_file_exists(const char_u *name);
+
+/// Check if a file is readonly.
+///
+/// @return `true` if `name` is readonly.
+bool os_file_is_readonly(const char *name);
+
+/// Check if a file is writable.
+///
+/// @return `0` if `name` is not writable,
+/// @return `1` if `name` is writable,
+/// @return `2` for a directory which we have rights to write into.
+int os_file_is_writable(const char *name);
+
+/// Get the size of a file in bytes.
+///
+/// @param[out] size pointer to an off_t to put the size into.
+/// @return `true` for success, `false` for failure.
+bool os_get_file_size(const char *name, off_t *size);
+
+/// Rename a file or directory.
+///
+/// @return `OK` for success, `FAIL` for failure.
+int os_rename(const char_u *path, const char_u *new_path);
+
+/// Make a directory.
+///
+/// @return `0` for success, non-zero for failure.
+int os_mkdir(const char *path, int32_t mode);
+
+/// Remove a directory.
+///
+/// @return `0` for success, non-zero for failure.
+int os_rmdir(const char *path);
+
+/// Remove a file.
+///
+/// @return `0` for success, non-zero for failure.
+int os_remove(const char *path);
+
+/// Get the total system physical memory in KiB.
+uint64_t os_get_total_mem_kib(void);
+const char *os_getenv(const char *name);
+int os_setenv(const char *name, const char *value, int overwrite);
+char *os_getenvname_at_index(size_t index);
+
+/// Get the process ID of the Neovim process.
+///
+/// @return the process ID.
+int64_t os_get_pid(void);
+
+/// Get the hostname of the machine runing Neovim.
+///
+/// @param hostname Buffer to store the hostname.
+/// @param len Length of `hostname`.
+void os_get_hostname(char *hostname, size_t len);
+
+int os_get_usernames(garray_T *usernames);
+int os_get_user_name(char *s, size_t len);
+int os_get_uname(uid_t uid, char *s, size_t len);
+char *os_get_user_directory(const char *name);
+
+/// Get stat information for a file.
+///
+/// @return OK on success, FAIL if an failure occured.
+int os_stat(const char_u *name, uv_stat_t *statbuf);
+
+/// Struct which encapsulates stat information.
+typedef struct {
+ // TODO(stefan991): make stat private
+ uv_stat_t stat;
+} FileInfo;
+
+/// Get the file information for a given path
+///
+/// @param file_descriptor File descriptor of the file.
+/// @param[out] file_info Pointer to a FileInfo to put the information in.
+/// @return `true` on sucess, `false` for failure.
+bool os_get_file_info(const char *path, FileInfo *file_info);
+
+/// Get the file information for a given path without following links
+///
+/// @param path Path to the file.
+/// @param[out] file_info Pointer to a FileInfo to put the information in.
+/// @return `true` on sucess, `false` for failure.
+bool os_get_file_info_link(const char *path, FileInfo *file_info);
+
+/// Get the file information for a given file descriptor
+///
+/// @param file_descriptor File descriptor of the file.
+/// @param[out] file_info Pointer to a FileInfo to put the information in.
+/// @return `true` on sucess, `false` for failure.
+bool os_get_file_info_fd(int file_descriptor, FileInfo *file_info);
+
+/// Compare the inodes of two FileInfos
+///
+/// @return `true` if the two FileInfos represent the same file.
+bool os_file_info_id_equal(FileInfo *file_info_1, FileInfo *file_info_2);
+
+#endif // NEOVIM_OS_OS_H
diff --git a/src/nvim/os/rstream.c b/src/nvim/os/rstream.c
new file mode 100644
index 0000000000..e14075c8db
--- /dev/null
+++ b/src/nvim/os/rstream.c
@@ -0,0 +1,297 @@
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include <uv.h>
+
+#include "os/uv_helpers.h"
+#include "os/rstream_defs.h"
+#include "os/rstream.h"
+#include "os/event_defs.h"
+#include "os/event.h"
+#include "vim.h"
+#include "memory.h"
+
+struct rstream {
+ uv_buf_t uvbuf;
+ void *data;
+ char *buffer;
+ uv_stream_t *stream;
+ uv_idle_t *fread_idle;
+ uv_handle_type file_type;
+ uv_file fd;
+ rstream_cb cb;
+ size_t buffer_size, rpos, wpos, fpos;
+ bool reading, free_handle, async;
+};
+
+// Callbacks used by libuv
+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 *);
+static void close_cb(uv_handle_t *handle);
+static void emit_read_event(RStream *rstream, bool eof);
+
+RStream * rstream_new(rstream_cb cb,
+ uint32_t buffer_size,
+ void *data,
+ bool async)
+{
+ RStream *rv = xmalloc(sizeof(RStream));
+ rv->buffer = xmalloc(buffer_size);
+ rv->buffer_size = buffer_size;
+ rv->data = data;
+ rv->async = async;
+ rv->cb = cb;
+ rv->rpos = rv->wpos = rv->fpos = 0;
+ rv->stream = NULL;
+ rv->fread_idle = NULL;
+ rv->free_handle = false;
+ rv->file_type = UV_UNKNOWN_HANDLE;
+
+ return rv;
+}
+
+void rstream_free(RStream *rstream)
+{
+ if (rstream->free_handle) {
+ if (rstream->fread_idle != NULL) {
+ uv_close((uv_handle_t *)rstream->fread_idle, close_cb);
+ } else {
+ uv_close((uv_handle_t *)rstream->stream, close_cb);
+ }
+ }
+
+ free(rstream->buffer);
+ free(rstream);
+}
+
+void rstream_set_stream(RStream *rstream, uv_stream_t *stream)
+{
+ handle_set_rstream((uv_handle_t *)stream, rstream);
+ rstream->stream = stream;
+}
+
+void rstream_set_file(RStream *rstream, uv_file file)
+{
+ rstream->file_type = uv_guess_handle(file);
+
+ if (rstream->free_handle) {
+ // If this is the second time we're calling this function, free the
+ // previously allocated memory
+ if (rstream->fread_idle != NULL) {
+ uv_close((uv_handle_t *)rstream->fread_idle, close_cb);
+ } else {
+ uv_close((uv_handle_t *)rstream->stream, close_cb);
+ }
+ }
+
+ if (rstream->file_type == UV_FILE) {
+ // Non-blocking file reads are simulated with a idle handle that reads
+ // in chunks of rstream->buffer_size, giving time for other events to
+ // be processed between reads.
+ rstream->fread_idle = xmalloc(sizeof(uv_idle_t));
+ uv_idle_init(uv_default_loop(), rstream->fread_idle);
+ rstream->fread_idle->data = NULL;
+ handle_set_rstream((uv_handle_t *)rstream->fread_idle, rstream);
+ } else {
+ // Only pipes are supported for now
+ assert(rstream->file_type == UV_NAMED_PIPE
+ || rstream->file_type == UV_TTY);
+ rstream->stream = xmalloc(sizeof(uv_pipe_t));
+ uv_pipe_init(uv_default_loop(), (uv_pipe_t *)rstream->stream, 0);
+ uv_pipe_open((uv_pipe_t *)rstream->stream, file);
+ rstream->stream->data = NULL;
+ handle_set_rstream((uv_handle_t *)rstream->stream, rstream);
+ }
+
+ rstream->fd = file;
+ rstream->free_handle = true;
+}
+
+bool rstream_is_regular_file(RStream *rstream)
+{
+ return rstream->file_type == UV_FILE;
+}
+
+void rstream_start(RStream *rstream)
+{
+ if (rstream->file_type == UV_FILE) {
+ uv_idle_start(rstream->fread_idle, fread_idle_cb);
+ } else {
+ rstream->reading = false;
+ uv_read_start(rstream->stream, alloc_cb, read_cb);
+ }
+}
+
+void rstream_stop(RStream *rstream)
+{
+ if (rstream->file_type == UV_FILE) {
+ uv_idle_stop(rstream->fread_idle);
+ } else {
+ uv_read_stop(rstream->stream);
+ }
+}
+
+size_t rstream_read(RStream *rstream, char *buf, uint32_t count)
+{
+ size_t read_count = rstream->wpos - rstream->rpos;
+
+ if (count < read_count) {
+ read_count = count;
+ }
+
+ if (read_count > 0) {
+ memcpy(buf, rstream->buffer + rstream->rpos, read_count);
+ rstream->rpos += read_count;
+ }
+
+ if (rstream->wpos == rstream->buffer_size) {
+ // `wpos` is at the end of the buffer, so free some space by moving unread
+ // data...
+ memmove(
+ rstream->buffer, // ...To the beginning of the buffer(rpos 0)
+ rstream->buffer + rstream->rpos, // ...From the first unread position
+ rstream->wpos - rstream->rpos); // ...By the number of unread bytes
+ rstream->wpos -= rstream->rpos;
+ rstream->rpos = 0;
+
+ if (rstream->wpos < rstream->buffer_size) {
+ // Restart reading since we have freed some space
+ rstream_start(rstream);
+ }
+ }
+
+ return read_count;
+}
+
+size_t rstream_available(RStream *rstream)
+{
+ return rstream->wpos - rstream->rpos;
+}
+
+void rstream_read_event(Event event)
+{
+ RStream *rstream = event.data.rstream.ptr;
+
+ rstream->cb(rstream, rstream->data, event.data.rstream.eof);
+}
+
+// Called by libuv to allocate memory for reading.
+static void alloc_cb(uv_handle_t *handle, size_t suggested, uv_buf_t *buf)
+{
+ RStream *rstream = handle_get_rstream(handle);
+
+ if (rstream->reading) {
+ buf->len = 0;
+ return;
+ }
+
+ buf->len = rstream->buffer_size - rstream->wpos;
+ buf->base = rstream->buffer + rstream->wpos;
+
+ // Avoid `alloc_cb`, `alloc_cb` sequences on windows
+ rstream->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)
+{
+ RStream *rstream = handle_get_rstream((uv_handle_t *)stream);
+
+ if (cnt <= 0) {
+ if (cnt != UV_ENOBUFS) {
+ // Read error or EOF, either way stop the stream and invoke the callback
+ // with eof == true
+ uv_read_stop(stream);
+ emit_read_event(rstream, true);
+ }
+ return;
+ }
+
+ // at this point we're sure that cnt is positive, no error occurred
+ size_t nread = (size_t) cnt;
+
+ // Data was already written, so all we need is to update 'wpos' to reflect
+ // the space actually used in the buffer.
+ rstream->wpos += nread;
+
+ if (rstream->wpos == rstream->buffer_size) {
+ // The last read filled the buffer, stop reading for now
+ rstream_stop(rstream);
+ }
+
+ rstream->reading = false;
+ emit_read_event(rstream, false);
+}
+
+// Called by the by the 'idle' handle to emulate a reading event
+static void fread_idle_cb(uv_idle_t *handle)
+{
+ uv_fs_t req;
+ RStream *rstream = handle_get_rstream((uv_handle_t *)handle);
+
+ rstream->uvbuf.base = rstream->buffer + rstream->wpos;
+ rstream->uvbuf.len = rstream->buffer_size - rstream->wpos;
+
+ // the offset argument to uv_fs_read is int64_t, could someone really try
+ // to read more than 9 quintillion (9e18) bytes?
+ // upcast is meant to avoid tautological condition warning on 32 bits
+ uintmax_t fpos_intmax = rstream->fpos;
+ assert(fpos_intmax <= INT64_MAX);
+
+ // Synchronous read
+ uv_fs_read(
+ uv_default_loop(),
+ &req,
+ rstream->fd,
+ &rstream->uvbuf,
+ 1,
+ (int64_t) rstream->fpos,
+ NULL);
+
+ uv_fs_req_cleanup(&req);
+
+ if (req.result <= 0) {
+ uv_idle_stop(rstream->fread_idle);
+ emit_read_event(rstream, true);
+ return;
+ }
+
+ // no errors (req.result (ssize_t) is positive), it's safe to cast.
+ size_t nread = (size_t) req.result;
+
+ rstream->wpos += nread;
+ rstream->fpos += nread;
+
+ if (rstream->wpos == rstream->buffer_size) {
+ // The last read filled the buffer, stop reading for now
+ rstream_stop(rstream);
+ }
+
+ emit_read_event(rstream, false);
+}
+
+static void close_cb(uv_handle_t *handle)
+{
+ free(handle->data);
+ free(handle);
+}
+
+static void emit_read_event(RStream *rstream, bool eof)
+{
+ if (rstream->async) {
+ Event event;
+
+ event.type = kEventRStreamData;
+ event.data.rstream.ptr = rstream;
+ event.data.rstream.eof = eof;
+ event_push(event);
+ } else {
+ // Invoke the callback passing in the number of bytes available and data
+ // associated with the stream
+ rstream->cb(rstream, rstream->data, eof);
+ }
+}
diff --git a/src/nvim/os/rstream.h b/src/nvim/os/rstream.h
new file mode 100644
index 0000000000..5eb3e97f55
--- /dev/null
+++ b/src/nvim/os/rstream.h
@@ -0,0 +1,82 @@
+#ifndef NEOVIM_OS_RSTREAM_H
+#define NEOVIM_OS_RSTREAM_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <uv.h>
+
+#include "os/event_defs.h"
+#include "os/rstream_defs.h"
+
+/// Creates a new RStream instance. A RStream encapsulates all the boilerplate
+/// necessary for reading from a libuv stream.
+///
+/// @param cb A function that will be called whenever some data is available
+/// for reading with `rstream_read`
+/// @param buffer_size Size in bytes of the internal buffer.
+/// @param data Some state to associate with the `RStream` instance
+/// @param async Flag that specifies if the callback should only be called
+/// outside libuv event loop(When processing async events with
+/// KE_EVENT). Only the RStream instance reading user input should set
+/// this to false
+/// @return The newly-allocated `RStream` instance
+RStream * rstream_new(rstream_cb cb,
+ uint32_t buffer_size,
+ void *data,
+ bool async);
+
+/// Frees all memory allocated for a RStream instance
+///
+/// @param rstream The `RStream` instance
+void rstream_free(RStream *rstream);
+
+/// Sets the underlying `uv_stream_t` instance
+///
+/// @param rstream The `RStream` instance
+/// @param stream The new `uv_stream_t` instance
+void rstream_set_stream(RStream *rstream, uv_stream_t *stream);
+
+/// Sets the underlying file descriptor that will be read from. Only pipes
+/// and regular files are supported for now.
+///
+/// @param rstream The `RStream` instance
+/// @param file The file descriptor
+void rstream_set_file(RStream *rstream, uv_file file);
+
+/// Tests if the stream is backed by a regular file
+///
+/// @param rstream The `RStream` instance
+/// @return True if the underlying file descriptor represents a regular file
+bool rstream_is_regular_file(RStream *rstream);
+
+/// Starts watching for events from a `RStream` instance.
+///
+/// @param rstream The `RStream` instance
+void rstream_start(RStream *rstream);
+
+/// Stops watching for events from a `RStream` instance.
+///
+/// @param rstream The `RStream` instance
+void rstream_stop(RStream *rstream);
+
+/// Reads data from a `RStream` instance into a buffer.
+///
+/// @param rstream The `RStream` instance
+/// @param buffer The buffer which will receive the data
+/// @param count Number of bytes that `buffer` can accept
+/// @return The number of bytes copied into `buffer`
+size_t rstream_read(RStream *rstream, char *buffer, uint32_t count);
+
+/// Returns the number of bytes available for reading from `rstream`
+///
+/// @param rstream The `RStream` instance
+/// @return The number of bytes available
+size_t rstream_available(RStream *rstream);
+
+/// Runs the read callback associated with the rstream
+///
+/// @param event Object containing data necessary to invoke the callback
+void rstream_read_event(Event event);
+
+#endif // NEOVIM_OS_RSTREAM_H
+
diff --git a/src/nvim/os/rstream_defs.h b/src/nvim/os/rstream_defs.h
new file mode 100644
index 0000000000..3d1dbec34f
--- /dev/null
+++ b/src/nvim/os/rstream_defs.h
@@ -0,0 +1,16 @@
+#ifndef NEOVIM_OS_RSTREAM_DEFS_H
+#define NEOVIM_OS_RSTREAM_DEFS_H
+
+#include <stdbool.h>
+
+typedef struct rstream RStream;
+
+/// Type of function called when the RStream receives data
+///
+/// @param rstream The RStream instance
+/// @param data State associated with the RStream instance
+/// @param eof If the stream reached EOF.
+typedef void (*rstream_cb)(RStream *rstream, void *data, bool eof);
+
+#endif // NEOVIM_OS_RSTREAM_DEFS_H
+
diff --git a/src/nvim/os/server.c b/src/nvim/os/server.c
new file mode 100644
index 0000000000..bda9602936
--- /dev/null
+++ b/src/nvim/os/server.c
@@ -0,0 +1,243 @@
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <uv.h>
+
+#include "os/channel_defs.h"
+#include "os/channel.h"
+#include "os/server.h"
+#include "os/os.h"
+#include "vim.h"
+#include "memory.h"
+#include "message.h"
+#include "fileio.h"
+#include "map.h"
+
+#define MAX_CONNECTIONS 32
+#define ADDRESS_MAX_SIZE 256
+#define NEOVIM_DEFAULT_TCP_PORT 7450
+
+typedef enum {
+ kServerTypeTcp,
+ kServerTypePipe
+} ServerType;
+
+typedef struct {
+ // Protocol for channels established through this server
+ ChannelProtocol protocol;
+ // Type of the union below
+ ServerType type;
+
+ // This is either a tcp server or unix socket(named pipe on windows)
+ union {
+ struct {
+ uv_tcp_t handle;
+ struct sockaddr_in addr;
+ } tcp;
+ struct {
+ uv_pipe_t handle;
+ char addr[ADDRESS_MAX_SIZE];
+ } pipe;
+ } socket;
+} Server;
+
+static Map *servers = NULL;
+
+static void close_server(Map *map, const char *endpoint, void *server);
+static void connection_cb(uv_stream_t *server, int status);
+static void free_client(uv_handle_t *handle);
+static void free_server(uv_handle_t *handle);
+
+void server_init()
+{
+ servers = map_new();
+
+ if (!os_getenv("NEOVIM_LISTEN_ADDRESS")) {
+ char *listen_address = (char *)vim_tempname('s');
+ os_setenv("NEOVIM_LISTEN_ADDRESS", listen_address, 1);
+ free(listen_address);
+ }
+
+ server_start((char *)os_getenv("NEOVIM_LISTEN_ADDRESS"),
+ kChannelProtocolMsgpack);
+}
+
+void server_teardown()
+{
+ if (!servers) {
+ return;
+ }
+
+ map_foreach(servers, close_server);
+}
+
+void server_start(char *endpoint, ChannelProtocol prot)
+{
+ char addr[ADDRESS_MAX_SIZE];
+
+ // Trim to `ADDRESS_MAX_SIZE`
+ strncpy(addr, endpoint, sizeof(addr));
+
+ // Check if the server already exists
+ if (map_has(servers, addr)) {
+ EMSG2("Already listening on %s", addr);
+ return;
+ }
+
+ ServerType server_type = kServerTypeTcp;
+ Server *server = xmalloc(sizeof(Server));
+ char ip[16], *ip_end = strrchr(addr, ':');
+
+ server->protocol = prot;
+
+ if (!ip_end) {
+ ip_end = strchr(addr, NUL);
+ }
+
+ uint32_t addr_len = ip_end - addr;
+
+ if (addr_len > sizeof(ip) - 1) {
+ // Maximum length of a ip address buffer is 15(eg: 255.255.255.255)
+ addr_len = sizeof(ip);
+ }
+
+ // Extract the address part
+ strncpy(ip, addr, addr_len);
+
+ int port = NEOVIM_DEFAULT_TCP_PORT;
+
+ if (*ip_end == ':') {
+ char *port_end;
+ // Extract the port
+ port = strtol(ip_end + 1, &port_end, 10);
+
+ errno = 0;
+ if (errno != 0 || port == 0 || port > 0xffff) {
+ // Invalid port, treat as named pipe or unix socket
+ server_type = kServerTypePipe;
+ }
+ }
+
+ if (server_type == kServerTypeTcp) {
+ // Try to parse ip address
+ if (uv_ip4_addr(ip, port, &server->socket.tcp.addr)) {
+ // Invalid address, treat as named pipe or unix socket
+ server_type = kServerTypePipe;
+ }
+ }
+
+ int result;
+
+ if (server_type == kServerTypeTcp) {
+ // Listen on tcp address/port
+ uv_tcp_init(uv_default_loop(), &server->socket.tcp.handle);
+ server->socket.tcp.handle.data = server;
+ uv_tcp_bind(&server->socket.tcp.handle,
+ (const struct sockaddr *)&server->socket.tcp.addr,
+ 0);
+ result = uv_listen((uv_stream_t *)&server->socket.tcp.handle,
+ MAX_CONNECTIONS,
+ connection_cb);
+ if (result) {
+ uv_close((uv_handle_t *)&server->socket.tcp.handle, free_server);
+ }
+ } else {
+ // Listen on named pipe or unix socket
+ strcpy(server->socket.pipe.addr, addr);
+ uv_pipe_init(uv_default_loop(), &server->socket.pipe.handle, 0);
+ server->socket.pipe.handle.data = server;
+ uv_pipe_bind(&server->socket.pipe.handle, server->socket.pipe.addr);
+ result = uv_listen((uv_stream_t *)&server->socket.pipe.handle,
+ MAX_CONNECTIONS,
+ connection_cb);
+
+ if (result) {
+ uv_close((uv_handle_t *)&server->socket.pipe.handle, free_server);
+ }
+ }
+
+ if (result) {
+ EMSG2("Failed to start server: %s", uv_strerror(result));
+ return;
+ }
+
+
+ server->type = server_type;
+
+ // Add the server to the hash table
+ map_put(servers, addr, server);
+}
+
+void server_stop(char *endpoint)
+{
+ Server *server;
+ char addr[ADDRESS_MAX_SIZE];
+
+ // Trim to `ADDRESS_MAX_SIZE`
+ strncpy(addr, endpoint, sizeof(addr));
+
+ if ((server = map_get(servers, addr)) == NULL) {
+ EMSG2("Not listening on %s", addr);
+ return;
+ }
+
+ if (server->type == kServerTypeTcp) {
+ uv_close((uv_handle_t *)&server->socket.tcp.handle, free_server);
+ } else {
+ uv_close((uv_handle_t *)&server->socket.pipe.handle, free_server);
+ }
+
+ map_del(servers, addr);
+}
+
+static void connection_cb(uv_stream_t *server, int status)
+{
+ int result;
+ uv_stream_t *client;
+ Server *srv = server->data;
+
+ if (status < 0) {
+ abort();
+ }
+
+ if (srv->type == kServerTypeTcp) {
+ client = xmalloc(sizeof(uv_tcp_t));
+ uv_tcp_init(uv_default_loop(), (uv_tcp_t *)client);
+ } else {
+ client = xmalloc(sizeof(uv_pipe_t));
+ uv_pipe_init(uv_default_loop(), (uv_pipe_t *)client, 0);
+ }
+
+ result = uv_accept(server, client);
+
+ if (result) {
+ EMSG2("Failed to accept connection: %s", uv_strerror(result));
+ uv_close((uv_handle_t *)client, free_client);
+ return;
+ }
+
+ channel_from_stream(client, srv->protocol);
+}
+
+static void close_server(Map *map, const char *endpoint, void *srv)
+{
+ Server *server = srv;
+
+ if (server->type == kServerTypeTcp) {
+ uv_close((uv_handle_t *)&server->socket.tcp.handle, free_server);
+ } else {
+ uv_close((uv_handle_t *)&server->socket.pipe.handle, free_server);
+ }
+}
+
+static void free_client(uv_handle_t *handle)
+{
+ free(handle);
+}
+
+static void free_server(uv_handle_t *handle)
+{
+ free(handle->data);
+}
diff --git a/src/nvim/os/server.h b/src/nvim/os/server.h
new file mode 100644
index 0000000000..b9459a81af
--- /dev/null
+++ b/src/nvim/os/server.h
@@ -0,0 +1,30 @@
+#ifndef NEOVIM_OS_SERVER_H
+#define NEOVIM_OS_SERVER_H
+
+#include "os/channel_defs.h"
+
+/// Initializes the module
+void server_init();
+
+/// Teardown the server module
+void server_teardown();
+
+/// Starts listening on arbitrary tcp/unix addresses specified by
+/// `endpoint` for API calls. The type of socket used(tcp or unix/pipe) will
+/// be determined by parsing `endpoint`: If it's a valid tcp address in the
+/// 'ip:port' format, then it will be tcp socket, else it will be a unix
+/// socket or named pipe.
+///
+/// @param endpoint Address of the server. Either a 'ip:port' string or an
+/// arbitrary identifier(trimmed to 256 bytes) for the unix socket or
+/// named pipe.
+/// @param prot The rpc protocol to be used
+void server_start(char *endpoint, ChannelProtocol prot);
+
+/// Stops listening on the address specified by `endpoint`.
+///
+/// @param endpoint Address of the server.
+void server_stop(char *endpoint);
+
+#endif // NEOVIM_OS_SERVER_H
+
diff --git a/src/nvim/os/server_defs.h b/src/nvim/os/server_defs.h
new file mode 100644
index 0000000000..cbaefe3949
--- /dev/null
+++ b/src/nvim/os/server_defs.h
@@ -0,0 +1,7 @@
+#ifndef NEOVIM_OS_SERVER_DEFS_H
+#define NEOVIM_OS_SERVER_DEFS_H
+
+typedef struct server Server;
+
+#endif // NEOVIM_OS_SERVER_DEFS_H
+
diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c
new file mode 100644
index 0000000000..d14e355d19
--- /dev/null
+++ b/src/nvim/os/shell.c
@@ -0,0 +1,465 @@
+#include <string.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include <uv.h>
+
+#include "os/shell.h"
+#include "os/signal.h"
+#include "types.h"
+#include "vim.h"
+#include "message.h"
+#include "ascii.h"
+#include "memory.h"
+#include "term.h"
+#include "misc2.h"
+#include "screen.h"
+#include "memline.h"
+#include "option_defs.h"
+#include "charset.h"
+
+#define BUFFER_LENGTH 1024
+
+typedef struct {
+ bool reading;
+ int old_state, old_mode, exit_status, exited;
+ char *wbuffer;
+ char rbuffer[BUFFER_LENGTH];
+ uv_buf_t bufs[2];
+ uv_stream_t *shell_stdin;
+ garray_T ga;
+} ProcessData;
+
+/// Parses a command string into a sequence of words, taking quotes into
+/// consideration.
+///
+/// @param str The command string to be parsed
+/// @param argv The vector that will be filled with copies of the parsed
+/// words. It can be NULL if the caller only needs to count words.
+/// @return The number of words parsed.
+static int tokenize(char_u *str, char **argv);
+
+/// Calculates the length of a shell word.
+///
+/// @param str A pointer to the first character of the word
+/// @return The offset from `str` at which the word ends.
+static int word_length(char_u *command);
+
+/// Queues selected range for writing to the child process stdin.
+///
+/// @param req The structure containing information to peform the write
+static void write_selection(uv_write_t *req);
+
+/// Cleanup memory and restore state modified by `os_call_shell`.
+///
+/// @param data State shared by all functions collaborating with
+/// `os_call_shell`.
+/// @param opts Process spawning options, containing some allocated memory
+/// @param shellopts Options passed to `os_call_shell`. Used for deciding
+/// if/which messages are displayed.
+static int proc_cleanup_exit(ProcessData *data,
+ uv_process_options_t *opts,
+ int shellopts);
+// Callbacks for libuv
+static void alloc_cb(uv_handle_t *handle, size_t suggested, uv_buf_t *buf);
+static void read_cb(uv_stream_t *stream, ssize_t cnt, const uv_buf_t *buf);
+static void write_cb(uv_write_t *req, int status);
+static void exit_cb(uv_process_t *proc, int64_t status, int term_signal);
+
+char ** shell_build_argv(char_u *cmd, char_u *extra_shell_opt)
+{
+ int i;
+ char **rv;
+ int argc = tokenize(p_sh, NULL) + tokenize(p_shcf, NULL);
+
+ rv = (char **)xmalloc((unsigned)((argc + 4) * sizeof(char *)));
+
+ // Split 'shell'
+ i = tokenize(p_sh, rv);
+
+ if (extra_shell_opt != NULL) {
+ // Push a copy of `extra_shell_opt`
+ rv[i++] = xstrdup((char *)extra_shell_opt);
+ }
+
+ if (cmd != NULL) {
+ // Split 'shellcmdflag'
+ i += tokenize(p_shcf, rv + i);
+ rv[i++] = xstrdup((char *)cmd);
+ }
+
+ rv[i] = NULL;
+
+ return rv;
+}
+
+void shell_free_argv(char **argv)
+{
+ char **p = argv;
+
+ if (p == NULL) {
+ // Nothing was allocated, return
+ return;
+ }
+
+ while (*p != NULL) {
+ // Free each argument
+ free(*p);
+ p++;
+ }
+
+ free(argv);
+}
+
+int os_call_shell(char_u *cmd, ShellOpts opts, char_u *extra_shell_arg)
+{
+ uv_stdio_container_t proc_stdio[3];
+ uv_process_options_t proc_opts;
+ uv_process_t proc;
+ uv_pipe_t proc_stdin, proc_stdout;
+ uv_write_t write_req;
+ int expected_exits = 1;
+ ProcessData pdata = {
+ .reading = false,
+ .exited = 0,
+ .old_mode = cur_tmode,
+ .old_state = State,
+ .shell_stdin = (uv_stream_t *)&proc_stdin,
+ .wbuffer = NULL,
+ };
+
+ out_flush();
+ if (opts & kShellOptCooked) {
+ // set to normal mode
+ settmode(TMODE_COOK);
+ }
+
+ // While the child is running, ignore terminating signals
+ signal_reject_deadly();
+
+ // Create argv for `uv_spawn`
+ // TODO(tarruda): we can use a static buffer for small argument vectors. 1024
+ // bytes should be enough for most of the commands and if more is necessary
+ // we can allocate a another buffer
+ proc_opts.args = shell_build_argv(cmd, extra_shell_arg);
+ proc_opts.file = proc_opts.args[0];
+ proc_opts.exit_cb = exit_cb;
+ // Initialize libuv structures
+ proc_opts.stdio = proc_stdio;
+ proc_opts.stdio_count = 3;
+ // Hide window on Windows :)
+ proc_opts.flags = UV_PROCESS_WINDOWS_HIDE;
+ proc_opts.cwd = NULL;
+ proc_opts.env = NULL;
+
+ // The default is to inherit all standard file descriptors(this will change
+ // when the UI is moved to an external process)
+ proc_stdio[0].flags = UV_INHERIT_FD;
+ proc_stdio[0].data.fd = 0;
+ proc_stdio[1].flags = UV_INHERIT_FD;
+ proc_stdio[1].data.fd = 1;
+ proc_stdio[2].flags = UV_INHERIT_FD;
+ proc_stdio[2].data.fd = 2;
+
+ if (opts & (kShellOptHideMess | kShellOptExpand)) {
+ // Ignore the shell stdio(redirects to /dev/null on unixes)
+ proc_stdio[0].flags = UV_IGNORE;
+ proc_stdio[1].flags = UV_IGNORE;
+ proc_stdio[2].flags = UV_IGNORE;
+ } else {
+ State = EXTERNCMD;
+
+ if (opts & kShellOptWrite) {
+ // Write from the current buffer into the process stdin
+ uv_pipe_init(uv_default_loop(), &proc_stdin, 0);
+ write_req.data = &pdata;
+ proc_stdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE;
+ proc_stdio[0].data.stream = (uv_stream_t *)&proc_stdin;
+ }
+
+ if (opts & kShellOptRead) {
+ // Read from the process stdout into the current buffer
+ uv_pipe_init(uv_default_loop(), &proc_stdout, 0);
+ proc_stdout.data = &pdata;
+ proc_stdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
+ proc_stdio[1].data.stream = (uv_stream_t *)&proc_stdout;
+ ga_init(&pdata.ga, 1, BUFFER_LENGTH);
+ }
+ }
+
+ if (uv_spawn(uv_default_loop(), &proc, &proc_opts)) {
+ // Failed, probably due to `sh` not being executable
+ if (!emsg_silent) {
+ MSG_PUTS(_("\nCannot execute shell "));
+ msg_outtrans(p_sh);
+ msg_putchar('\n');
+ }
+
+ return proc_cleanup_exit(&pdata, &proc_opts, opts);
+ }
+
+ // Assign the flag address after `proc` is initialized by `uv_spawn`
+ proc.data = &pdata;
+
+ if (opts & kShellOptWrite) {
+ // Queue everything for writing to the shell stdin
+ write_selection(&write_req);
+ expected_exits++;
+ }
+
+ if (opts & kShellOptRead) {
+ // Start the read stream for the shell stdout
+ uv_read_start((uv_stream_t *)&proc_stdout, alloc_cb, read_cb);
+ expected_exits++;
+ }
+
+ // Keep running the loop until all three handles are completely closed
+ while (pdata.exited < expected_exits) {
+ uv_run(uv_default_loop(), UV_RUN_ONCE);
+
+ if (got_int) {
+ // Forward SIGINT to the shell
+ // TODO(tarruda): for now this is only needed if the terminal is in raw
+ // mode, but when the UI is externalized we'll also need it, so leave it
+ // here
+ uv_process_kill(&proc, SIGINT);
+ got_int = false;
+ }
+ }
+
+ if (opts & kShellOptRead) {
+ if (pdata.ga.ga_len > 0) {
+ // If there's an unfinished line in the growable array, append it now.
+ append_ga_line(&pdata.ga);
+ // remember that the NL was missing
+ curbuf->b_no_eol_lnum = curwin->w_cursor.lnum;
+ } else {
+ curbuf->b_no_eol_lnum = 0;
+ }
+ ga_clear(&pdata.ga);
+ }
+
+ if (opts & kShellOptWrite) {
+ free(pdata.wbuffer);
+ }
+
+ return proc_cleanup_exit(&pdata, &proc_opts, opts);
+}
+
+static int tokenize(char_u *str, char **argv)
+{
+ int argc = 0, len;
+ char_u *p = str;
+
+ while (*p != NUL) {
+ len = word_length(p);
+
+ if (argv != NULL) {
+ // Fill the slot
+ argv[argc] = xmalloc(len + 1);
+ memcpy(argv[argc], p, len);
+ argv[argc][len] = NUL;
+ }
+
+ argc++;
+ p += len;
+ p = skipwhite(p);
+ }
+
+ return argc;
+}
+
+static int word_length(char_u *str)
+{
+ char_u *p = str;
+ bool inquote = false;
+ int length = 0;
+
+ // Move `p` to the end of shell word by advancing the pointer while it's
+ // inside a quote or it's a non-whitespace character
+ while (*p && (inquote || (*p != ' ' && *p != TAB))) {
+ if (*p == '"') {
+ // Found a quote character, switch the `inquote` flag
+ inquote = !inquote;
+ }
+
+ p++;
+ length++;
+ }
+
+ return length;
+}
+
+/// To remain compatible with the old implementation(which forked a process
+/// for writing) the entire text is copied to a temporary buffer before the
+/// event loop starts. If we don't(by writing in chunks returned by `ml_get`)
+/// the buffer being modified might get modified by reading from the process
+/// before we finish writing.
+static void write_selection(uv_write_t *req)
+{
+ ProcessData *pdata = (ProcessData *)req->data;
+ // TODO(tarruda): use a static buffer for up to a limit(BUFFER_LENGTH) and
+ // only after filled we should start allocating memory(skip unnecessary
+ // allocations for small writes)
+ int buflen = BUFFER_LENGTH;
+ pdata->wbuffer = (char *)xmalloc(buflen);
+ uv_buf_t uvbuf;
+ linenr_T lnum = curbuf->b_op_start.lnum;
+ int off = 0;
+ int written = 0;
+ char_u *lp = ml_get(lnum);
+ int l;
+ int len;
+
+ for (;;) {
+ l = strlen((char *)lp + written);
+ if (l == 0) {
+ len = 0;
+ } else if (lp[written] == NL) {
+ // NL -> NUL translation
+ len = 1;
+ if (off + len >= buflen) {
+ // Resize the buffer
+ buflen *= 2;
+ pdata->wbuffer = xrealloc(pdata->wbuffer, buflen);
+ }
+ pdata->wbuffer[off++] = NUL;
+ } else {
+ char_u *s = vim_strchr(lp + written, NL);
+ len = s == NULL ? l : s - (lp + written);
+ while (off + len >= buflen) {
+ // Resize the buffer
+ buflen *= 2;
+ pdata->wbuffer = xrealloc(pdata->wbuffer, buflen);
+ }
+ memcpy(pdata->wbuffer + off, lp + written, len);
+ off += len;
+ }
+ if (len == l) {
+ // Finished a line, add a NL, unless this line
+ // should not have one.
+ // FIXME need to make this more readable
+ if (lnum != curbuf->b_op_end.lnum
+ || !curbuf->b_p_bin
+ || (lnum != curbuf->b_no_eol_lnum
+ && (lnum !=
+ curbuf->b_ml.ml_line_count
+ || curbuf->b_p_eol))) {
+ if (off + 1 >= buflen) {
+ // Resize the buffer
+ buflen *= 2;
+ pdata->wbuffer = xrealloc(pdata->wbuffer, buflen);
+ }
+ pdata->wbuffer[off++] = NL;
+ }
+ ++lnum;
+ if (lnum > curbuf->b_op_end.lnum) {
+ break;
+ }
+ lp = ml_get(lnum);
+ written = 0;
+ } else if (len > 0) {
+ written += len;
+ }
+ }
+
+ uvbuf.base = pdata->wbuffer;
+ uvbuf.len = off;
+
+ uv_write(req, pdata->shell_stdin, &uvbuf, 1, write_cb);
+}
+
+// "Allocates" a buffer for reading from the shell stdout.
+static void alloc_cb(uv_handle_t *handle, size_t suggested, uv_buf_t *buf)
+{
+ ProcessData *pdata = (ProcessData *)handle->data;
+
+ if (pdata->reading) {
+ buf->len = 0;
+ return;
+ }
+
+ buf->base = pdata->rbuffer;
+ buf->len = BUFFER_LENGTH;
+ // Avoid `alloc_cb`, `alloc_cb` sequences on windows
+ pdata->reading = true;
+}
+
+static void read_cb(uv_stream_t *stream, ssize_t cnt, const uv_buf_t *buf)
+{
+ // TODO(tarruda): avoid using a growable array for this, refactor the
+ // algorithm to call `ml_append` directly(skip unecessary copies/resizes)
+ int i;
+ ProcessData *pdata = (ProcessData *)stream->data;
+
+ if (cnt <= 0) {
+ if (cnt != UV_ENOBUFS) {
+ uv_read_stop(stream);
+ uv_close((uv_handle_t *)stream, NULL);
+ pdata->exited++;
+ }
+ return;
+ }
+
+ for (i = 0; i < cnt; ++i) {
+ if (pdata->rbuffer[i] == NL) {
+ // Insert the line
+ append_ga_line(&pdata->ga);
+ } else if (pdata->rbuffer[i] == NUL) {
+ // Translate NUL to NL
+ ga_append(&pdata->ga, NL);
+ } else {
+ // buffer data into the grow array
+ ga_append(&pdata->ga, pdata->rbuffer[i]);
+ }
+ }
+
+ windgoto(msg_row, msg_col);
+ cursor_on();
+ out_flush();
+
+ pdata->reading = false;
+}
+
+static void write_cb(uv_write_t *req, int status)
+{
+ ProcessData *pdata = (ProcessData *)req->data;
+ uv_close((uv_handle_t *)pdata->shell_stdin, NULL);
+ pdata->exited++;
+}
+
+static int proc_cleanup_exit(ProcessData *proc_data,
+ uv_process_options_t *proc_opts,
+ int shellopts)
+{
+ if (proc_data->exited) {
+ if (!emsg_silent && proc_data->exit_status != 0 &&
+ !(shellopts & kShellOptSilent)) {
+ MSG_PUTS(_("\nshell returned "));
+ msg_outnum((int64_t)proc_data->exit_status);
+ msg_putchar('\n');
+ }
+ }
+
+ State = proc_data->old_state;
+
+ if (proc_data->old_mode == TMODE_RAW) {
+ // restore mode
+ settmode(TMODE_RAW);
+ }
+
+ signal_accept_deadly();
+
+ // Release argv memory
+ shell_free_argv(proc_opts->args);
+
+ return proc_data->exit_status;
+}
+
+static void exit_cb(uv_process_t *proc, int64_t status, int term_signal)
+{
+ ProcessData *data = (ProcessData *)proc->data;
+ data->exited++;
+ data->exit_status = status;
+ uv_close((uv_handle_t *)proc, NULL);
+}
diff --git a/src/nvim/os/shell.h b/src/nvim/os/shell.h
new file mode 100644
index 0000000000..776c36d384
--- /dev/null
+++ b/src/nvim/os/shell.h
@@ -0,0 +1,45 @@
+#ifndef NEOVIM_OS_SHELL_H
+#define NEOVIM_OS_SHELL_H
+
+#include <stdbool.h>
+
+#include "types.h"
+
+// Flags for mch_call_shell() second argument
+typedef enum {
+ kShellOptFilter = 1, ///< filtering text
+ kShellOptExpand = 2, ///< expanding wildcards
+ kShellOptCooked = 4, ///< set term to cooked mode
+ kShellOptDoOut = 8, ///< redirecting output
+ kShellOptSilent = 16, ///< don't print error returned by command
+ kShellOptRead = 32, ///< read lines and insert into buffer
+ kShellOptWrite = 64, ///< write lines from buffer
+ kShellOptHideMess = 128, ///< previously a global variable from os_unix.c
+} ShellOpts;
+
+/// Builds the argument vector for running the shell configured in `sh`
+/// ('shell' option), optionally with a command that will be passed with `shcf`
+/// ('shellcmdflag').
+///
+/// @param cmd Command string. If NULL it will run an interactive shell.
+/// @param extra_shell_opt Extra argument to the shell. If NULL it is ignored
+/// @return A newly allocated argument vector. It must be freed with
+/// `shell_free_argv` when no longer needed.
+char ** shell_build_argv(char_u *cmd, char_u *extra_shell_arg);
+
+/// Releases the memory allocated by `shell_build_argv`.
+///
+/// @param argv The argument vector.
+void shell_free_argv(char **argv);
+
+/// Calls the user shell for running a command, interactive session or
+/// wildcard expansion. It uses the shell set in the `sh` option.
+///
+/// @param cmd The command to be executed. If NULL it will run an interactive
+/// shell
+/// @param opts Various options that control how the shell will work
+/// @param extra_shell_arg Extra argument to be passed to the shell
+int os_call_shell(char_u *cmd, ShellOpts opts, char_u *extra_shell_arg);
+
+#endif // NEOVIM_OS_SHELL_H
+
diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c
new file mode 100644
index 0000000000..d3bedad09c
--- /dev/null
+++ b/src/nvim/os/signal.c
@@ -0,0 +1,163 @@
+#include <stdbool.h>
+
+#include <uv.h>
+
+#include "types.h"
+#include "vim.h"
+#include "globals.h"
+#include "memline.h"
+#include "eval.h"
+#include "term.h"
+#include "memory.h"
+#include "misc1.h"
+#include "misc2.h"
+#include "os/event_defs.h"
+#include "os/event.h"
+#include "os/signal.h"
+
+static uv_signal_t sint, spipe, shup, squit, sterm, swinch;
+#ifdef SIGPWR
+static uv_signal_t spwr;
+#endif
+
+static bool rejecting_deadly;
+static char * signal_name(int signum);
+static void deadly_signal(int signum);
+static void signal_cb(uv_signal_t *, int signum);
+
+void signal_init()
+{
+ uv_signal_init(uv_default_loop(), &sint);
+ uv_signal_init(uv_default_loop(), &spipe);
+ uv_signal_init(uv_default_loop(), &shup);
+ uv_signal_init(uv_default_loop(), &squit);
+ uv_signal_init(uv_default_loop(), &sterm);
+ uv_signal_init(uv_default_loop(), &swinch);
+ uv_signal_start(&sint, signal_cb, SIGINT);
+ uv_signal_start(&spipe, signal_cb, SIGPIPE);
+ uv_signal_start(&shup, signal_cb, SIGHUP);
+ uv_signal_start(&squit, signal_cb, SIGQUIT);
+ uv_signal_start(&sterm, signal_cb, SIGTERM);
+ uv_signal_start(&swinch, signal_cb, SIGWINCH);
+#ifdef SIGPWR
+ uv_signal_init(uv_default_loop(), &spwr);
+ uv_signal_start(&spwr, signal_cb, SIGPWR);
+#endif
+}
+
+void signal_stop()
+{
+ uv_signal_stop(&sint);
+ uv_signal_stop(&spipe);
+ uv_signal_stop(&shup);
+ uv_signal_stop(&squit);
+ uv_signal_stop(&sterm);
+ uv_signal_stop(&swinch);
+#ifdef SIGPWR
+ uv_signal_stop(&spwr);
+#endif
+}
+
+void signal_reject_deadly()
+{
+ rejecting_deadly = true;
+}
+
+void signal_accept_deadly()
+{
+ rejecting_deadly = false;
+}
+
+void signal_handle(Event event)
+{
+ int signum = event.data.signum;
+
+ switch (signum) {
+ case SIGINT:
+ got_int = true;
+ break;
+#ifdef SIGPWR
+ case SIGPWR:
+ // Signal of a power failure(eg batteries low), flush the swap files to
+ // be safe
+ ml_sync_all(false, false);
+ break;
+#endif
+ case SIGPIPE:
+ // Ignore
+ break;
+ case SIGWINCH:
+ shell_resized();
+ break;
+ case SIGTERM:
+ case SIGQUIT:
+ case SIGHUP:
+ if (!rejecting_deadly) {
+ deadly_signal(signum);
+ }
+ break;
+ default:
+ fprintf(stderr, "Invalid signal %d", signum);
+ break;
+ }
+}
+
+static char * signal_name(int signum)
+{
+ switch (signum) {
+ case SIGINT:
+ return "SIGINT";
+#ifdef SIGPWR
+ case SIGPWR:
+ return "SIGPWR";
+#endif
+ case SIGPIPE:
+ return "SIGPIPE";
+ case SIGWINCH:
+ return "SIGWINCH";
+ case SIGTERM:
+ return "SIGTERM";
+ case SIGQUIT:
+ return "SIGQUIT";
+ case SIGHUP:
+ return "SIGHUP";
+ default:
+ return "Unknown";
+ }
+}
+
+// This function handles deadly signals.
+// It tries to preserve any swap files and exit properly.
+// (partly from Elvis).
+// NOTE: Avoid unsafe functions, such as allocating memory, they can result in
+// a deadlock.
+static void deadly_signal(int signum)
+{
+ // Set the v:dying variable.
+ set_vim_var_nr(VV_DYING, 1);
+
+ snprintf((char *)IObuff, sizeof(IObuff), "Vim: Caught deadly signal '%s'\n",
+ signal_name(signum));
+
+ // Preserve files and exit.
+ preserve_exit();
+}
+
+static void signal_cb(uv_signal_t *handle, int signum)
+{
+ if (rejecting_deadly) {
+ if (signum == SIGINT) {
+ got_int = true;
+ }
+
+ return;
+ }
+
+ Event event = {
+ .type = kEventSignal,
+ .data = {
+ .signum = signum
+ }
+ };
+ event_push(event);
+}
diff --git a/src/nvim/os/signal.h b/src/nvim/os/signal.h
new file mode 100644
index 0000000000..79fdc8d79c
--- /dev/null
+++ b/src/nvim/os/signal.h
@@ -0,0 +1,13 @@
+#ifndef NEOVIM_OS_SIGNAL_H
+#define NEOVIM_OS_SIGNAL_H
+
+#include "os/event_defs.h"
+
+void signal_init(void);
+void signal_stop(void);
+void signal_accept_deadly(void);
+void signal_reject_deadly(void);
+void signal_handle(Event event);
+
+#endif // NEOVIM_OS_SIGNAL_H
+
diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c
new file mode 100644
index 0000000000..1dc7ca68d4
--- /dev/null
+++ b/src/nvim/os/time.c
@@ -0,0 +1,86 @@
+#include <stdint.h>
+#include <stdbool.h>
+#include <sys/time.h>
+
+#include <uv.h>
+
+#include "os/time.h"
+#include "vim.h"
+#include "term.h"
+
+static uv_mutex_t delay_mutex;
+static uv_cond_t delay_cond;
+
+static void microdelay(uint64_t ms);
+
+void time_init()
+{
+ uv_mutex_init(&delay_mutex);
+ uv_cond_init(&delay_cond);
+}
+
+void os_delay(uint64_t milliseconds, bool ignoreinput)
+{
+ os_microdelay(milliseconds * 1000, ignoreinput);
+}
+
+void os_microdelay(uint64_t microseconds, bool ignoreinput)
+{
+ int old_tmode;
+
+ if (ignoreinput) {
+ // Go to cooked mode without echo, to allow SIGINT interrupting us
+ // here
+ old_tmode = curr_tmode;
+
+ if (curr_tmode == TMODE_RAW)
+ settmode(TMODE_SLEEP);
+
+ microdelay(microseconds);
+
+ settmode(old_tmode);
+ } else {
+ microdelay(microseconds);
+ }
+}
+
+static void microdelay(uint64_t microseconds)
+{
+ uint64_t hrtime;
+ int64_t ns = microseconds * 1000; // convert to nanoseconds
+
+ uv_mutex_lock(&delay_mutex);
+
+ while (ns > 0) {
+ hrtime = uv_hrtime();
+ if (uv_cond_timedwait(&delay_cond, &delay_mutex, ns) == UV_ETIMEDOUT)
+ break;
+ ns -= uv_hrtime() - hrtime;
+ }
+
+ uv_mutex_unlock(&delay_mutex);
+}
+
+struct tm *os_localtime_r(const time_t *clock, struct tm *result)
+{
+#ifdef UNIX
+ // POSIX provides localtime_r() as a thread-safe version of localtime().
+ return localtime_r(clock, result);
+#else
+ // Windows version of localtime() is thread-safe.
+ // See http://msdn.microsoft.com/en-us/library/bf12f0hc%28VS.80%29.aspx
+ struct tm *local_time = localtime(clock); // NOLINT
+ *result = *local_time;
+return result;
+#endif
+}
+
+struct tm *os_get_localtime(struct tm *result)
+{
+ struct timeval tv;
+ if (gettimeofday(&tv, NULL) < 0) {
+ return NULL;
+ }
+
+ return os_localtime_r(&tv.tv_sec, result);
+}
diff --git a/src/nvim/os/time.h b/src/nvim/os/time.h
new file mode 100644
index 0000000000..ef795d03be
--- /dev/null
+++ b/src/nvim/os/time.h
@@ -0,0 +1,35 @@
+#ifndef NEOVIM_OS_TIME_H
+#define NEOVIM_OS_TIME_H
+
+#include <stdint.h>
+#include <stdbool.h>
+
+/// Initializes the time module
+void time_init(void);
+
+/// Sleeps for a certain amount of milliseconds
+///
+/// @param milliseconds Number of milliseconds to sleep
+/// @param ignoreinput If true, allow a SIGINT to interrupt us
+void os_delay(uint64_t milliseconds, bool ignoreinput);
+
+/// Sleeps for a certain amount of microseconds
+///
+/// @param microseconds Number of microseconds to sleep
+/// @param ignoreinput If true, allow a SIGINT to interrupt us
+void os_microdelay(uint64_t microseconds, bool ignoreinput);
+
+/// Portable version of POSIX localtime_r()
+///
+/// @return NULL in case of error
+struct tm *os_localtime_r(const time_t *clock, struct tm *result);
+
+/// Obtains the current UNIX timestamp and adjusts it to local time
+///
+/// @param result Pointer to a 'struct tm' where the result should be placed
+/// @return A pointer to a 'struct tm' in the current time zone (the 'result'
+/// argument) or NULL in case of error
+struct tm *os_get_localtime(struct tm *result);
+
+#endif // NEOVIM_OS_TIME_H
+
diff --git a/src/nvim/os/users.c b/src/nvim/os/users.c
new file mode 100644
index 0000000000..e7b362637b
--- /dev/null
+++ b/src/nvim/os/users.c
@@ -0,0 +1,87 @@
+// users.c -- operating system user information
+
+#include <uv.h>
+
+#include "os/os.h"
+#include "garray.h"
+#include "misc2.h"
+#ifdef HAVE_PWD_H
+# include <pwd.h>
+#endif
+
+// Initialize users garray and fill it with os usernames.
+// Return Ok for success, FAIL for failure.
+int os_get_usernames(garray_T *users)
+{
+ if (users == NULL) {
+ return FAIL;
+ }
+ ga_init(users, sizeof(char *), 20);
+
+# if defined(HAVE_GETPWENT) && defined(HAVE_PWD_H)
+ char *user;
+ struct passwd *pw;
+
+ setpwent();
+ while ((pw = getpwent()) != NULL) {
+ // pw->pw_name shouldn't be NULL but just in case...
+ if (pw->pw_name != NULL) {
+ ga_grow(users, 1);
+ user = (char *)vim_strsave((char_u*)pw->pw_name);
+ if (user == NULL) {
+ return FAIL;
+ }
+ ((char **)(users->ga_data))[users->ga_len++] = user;
+ }
+ }
+ endpwent();
+# endif
+
+ return OK;
+}
+
+// Insert user name in s[len].
+// Return OK if a name found.
+int os_get_user_name(char *s, size_t len)
+{
+ return os_get_uname(getuid(), s, len);
+}
+
+// Insert user name for "uid" in s[len].
+// Return OK if a name found.
+// If the name is not found, write the uid into s[len] and return FAIL.
+int os_get_uname(uid_t uid, char *s, size_t len)
+{
+#if defined(HAVE_PWD_H) && defined(HAVE_GETPWUID)
+ struct passwd *pw;
+
+ if ((pw = getpwuid(uid)) != NULL
+ && pw->pw_name != NULL && *(pw->pw_name) != NUL) {
+ vim_strncpy((char_u *)s, (char_u *)pw->pw_name, len - 1);
+ return OK;
+ }
+#endif
+ snprintf(s, len, "%d", (int)uid);
+ return FAIL; // a number is not a name
+}
+
+// Returns the user directory for the given username.
+// The caller has to free() the returned string.
+// If the username is not found, NULL is returned.
+char *os_get_user_directory(const char *name)
+{
+#if defined(HAVE_GETPWNAM) && defined(HAVE_PWD_H)
+ struct passwd *pw;
+ if (name == NULL) {
+ return NULL;
+ }
+ pw = getpwnam(name);
+ if (pw != NULL) {
+ // save the string from the static passwd entry into malloced memory
+ char *user_directory = (char *)vim_strsave((char_u *)pw->pw_dir);
+ return user_directory;
+ }
+#endif
+ return NULL;
+}
+
diff --git a/src/nvim/os/uv_helpers.c b/src/nvim/os/uv_helpers.c
new file mode 100644
index 0000000000..62b021de5e
--- /dev/null
+++ b/src/nvim/os/uv_helpers.c
@@ -0,0 +1,70 @@
+#include <uv.h>
+
+#include "os/uv_helpers.h"
+#include "vim.h"
+#include "memory.h"
+
+/// Common structure that will always be assigned to the `data` field of
+/// libuv handles. It has fields for many types of pointers, and allow a single
+/// handle to contain data from many sources
+typedef struct {
+ WStream *wstream;
+ RStream *rstream;
+ Job *job;
+} HandleData;
+
+static HandleData *init(uv_handle_t *handle);
+
+RStream *handle_get_rstream(uv_handle_t *handle)
+{
+ RStream *rv = init(handle)->rstream;
+ assert(rv != NULL);
+ return rv;
+}
+
+void handle_set_rstream(uv_handle_t *handle, RStream *rstream)
+{
+ init(handle)->rstream = rstream;
+}
+
+WStream *handle_get_wstream(uv_handle_t *handle)
+{
+ WStream *rv = init(handle)->wstream;
+ assert(rv != NULL);
+ return rv;
+}
+
+void handle_set_wstream(uv_handle_t *handle, WStream *wstream)
+{
+ HandleData *data = init(handle);
+ data->wstream = wstream;
+}
+
+Job *handle_get_job(uv_handle_t *handle)
+{
+ Job *rv = init(handle)->job;
+ assert(rv != NULL);
+ return rv;
+}
+
+void handle_set_job(uv_handle_t *handle, Job *job)
+{
+ init(handle)->job = job;
+}
+
+static HandleData *init(uv_handle_t *handle)
+{
+ HandleData *rv;
+
+ if (handle->data == NULL) {
+ rv = xmalloc(sizeof(HandleData));
+ rv->rstream = NULL;
+ rv->wstream = NULL;
+ rv->job = NULL;
+ handle->data = rv;
+ } else {
+ rv = handle->data;
+ }
+
+ return rv;
+}
diff --git a/src/nvim/os/uv_helpers.h b/src/nvim/os/uv_helpers.h
new file mode 100644
index 0000000000..9d4cea30b2
--- /dev/null
+++ b/src/nvim/os/uv_helpers.h
@@ -0,0 +1,47 @@
+#ifndef NEOVIM_OS_UV_HELPERS_H
+#define NEOVIM_OS_UV_HELPERS_H
+
+#include <uv.h>
+
+#include "os/wstream_defs.h"
+#include "os/rstream_defs.h"
+#include "os/job_defs.h"
+
+/// Gets the RStream instance associated with a libuv handle
+///
+/// @param handle libuv handle
+/// @return the RStream pointer
+RStream *handle_get_rstream(uv_handle_t *handle);
+
+/// Associates a RStream instance with a libuv handle
+///
+/// @param handle libuv handle
+/// @param rstream the RStream pointer
+void handle_set_rstream(uv_handle_t *handle, RStream *rstream);
+
+/// Gets the WStream instance associated with a libuv handle
+///
+/// @param handle libuv handle
+/// @return the WStream pointer
+WStream *handle_get_wstream(uv_handle_t *handle);
+
+/// Associates a WStream instance with a libuv handle
+///
+/// @param handle libuv handle
+/// @param wstream the WStream pointer
+void handle_set_wstream(uv_handle_t *handle, WStream *wstream);
+
+/// Gets the Job instance associated with a libuv handle
+///
+/// @param handle libuv handle
+/// @return the Job pointer
+Job *handle_get_job(uv_handle_t *handle);
+
+/// Associates a Job instance with a libuv handle
+///
+/// @param handle libuv handle
+/// @param job the Job pointer
+void handle_set_job(uv_handle_t *handle, Job *job);
+
+#endif // NEOVIM_OS_UV_HELPERS_H
+
diff --git a/src/nvim/os/wstream.c b/src/nvim/os/wstream.c
new file mode 100644
index 0000000000..0b289e80f5
--- /dev/null
+++ b/src/nvim/os/wstream.c
@@ -0,0 +1,117 @@
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <uv.h>
+
+#include "os/uv_helpers.h"
+#include "os/wstream.h"
+#include "os/wstream_defs.h"
+#include "vim.h"
+#include "memory.h"
+
+struct wstream {
+ uv_stream_t *stream;
+ // Memory currently used by pending buffers
+ uint32_t curmem;
+ // Maximum memory used by this instance
+ uint32_t maxmem;
+ // Number of pending requests
+ uint32_t pending_reqs;
+ bool freed;
+};
+
+typedef struct {
+ WStream *wstream;
+ // Buffer containing data to be written
+ char *buffer;
+ // Size of the buffer
+ uint32_t length;
+ // If it's our responsibility to free the buffer
+ bool free;
+} WriteData;
+
+static void write_cb(uv_write_t *req, int status);
+
+WStream * wstream_new(uint32_t maxmem)
+{
+ WStream *rv = xmalloc(sizeof(WStream));
+ rv->maxmem = maxmem;
+ rv->stream = NULL;
+ rv->curmem = 0;
+ rv->pending_reqs = 0;
+ rv->freed = false;
+
+ return rv;
+}
+
+void wstream_free(WStream *wstream)
+{
+ if (!wstream->pending_reqs) {
+ free(wstream);
+ } else {
+ wstream->freed = true;
+ }
+}
+
+void wstream_set_stream(WStream *wstream, uv_stream_t *stream)
+{
+ handle_set_wstream((uv_handle_t *)stream, wstream);
+ wstream->stream = stream;
+}
+
+bool wstream_write(WStream *wstream, char *buffer, uint32_t length, bool free)
+{
+ WriteData *data;
+ uv_buf_t uvbuf;
+ uv_write_t *req;
+
+ if (wstream->freed) {
+ // Don't accept write requests after the WStream instance was freed
+ return false;
+ }
+
+ if (wstream->curmem + length > wstream->maxmem) {
+ return false;
+ }
+
+ if (free) {
+ // We should only account for buffers that are ours to free
+ wstream->curmem += length;
+ }
+
+ data = xmalloc(sizeof(WriteData));
+ data->wstream = wstream;
+ data->buffer = buffer;
+ data->length = length;
+ data->free = free;
+ req = xmalloc(sizeof(uv_write_t));
+ req->data = data;
+ uvbuf.base = buffer;
+ uvbuf.len = length;
+ wstream->pending_reqs++;
+ uv_write(req, wstream->stream, &uvbuf, 1, write_cb);
+
+ return true;
+}
+
+static void write_cb(uv_write_t *req, int status)
+{
+ WriteData *data = req->data;
+
+ free(req);
+
+ if (data->free) {
+ // Free the data written to the stream
+ free(data->buffer);
+ data->wstream->curmem -= data->length;
+ }
+
+ data->wstream->pending_reqs--;
+ if (data->wstream->freed && data->wstream->pending_reqs == 0) {
+ // Last pending write, free the wstream;
+ free(data->wstream);
+ }
+
+ free(data);
+}
+
diff --git a/src/nvim/os/wstream.h b/src/nvim/os/wstream.h
new file mode 100644
index 0000000000..4a557ffd9f
--- /dev/null
+++ b/src/nvim/os/wstream.h
@@ -0,0 +1,40 @@
+#ifndef NEOVIM_OS_WSTREAM_H
+#define NEOVIM_OS_WSTREAM_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <uv.h>
+
+#include "os/wstream_defs.h"
+
+/// Creates a new WStream instance. A WStream encapsulates all the boilerplate
+/// necessary for writing to a libuv stream.
+///
+/// @param maxmem Maximum amount memory used by this `WStream` instance.
+/// @return The newly-allocated `WStream` instance
+WStream * wstream_new(uint32_t maxmem);
+
+/// Frees all memory allocated for a WStream instance
+///
+/// @param wstream The `WStream` instance
+void wstream_free(WStream *wstream);
+
+/// Sets the underlying `uv_stream_t` instance
+///
+/// @param wstream The `WStream` instance
+/// @param stream The new `uv_stream_t` instance
+void wstream_set_stream(WStream *wstream, uv_stream_t *stream);
+
+/// Queues data for writing to the backing file descriptor of a `WStream`
+/// instance. This will fail if the write would cause the WStream use more
+/// memory than specified by `maxmem`.
+///
+/// @param wstream The `WStream` instance
+/// @param buffer The buffer which contains data to be written
+/// @param length Number of bytes that should be written from `buffer`
+/// @param free If true, `buffer` will be freed after the write is complete
+/// @return true if the data was successfully queued, false otherwise.
+bool wstream_write(WStream *wstream, char *buffer, uint32_t length, bool free);
+
+#endif // NEOVIM_OS_WSTREAM_H
+
diff --git a/src/nvim/os/wstream_defs.h b/src/nvim/os/wstream_defs.h
new file mode 100644
index 0000000000..59d57365fa
--- /dev/null
+++ b/src/nvim/os/wstream_defs.h
@@ -0,0 +1,7 @@
+#ifndef NEOVIM_OS_WSTREAM_DEFS_H
+#define NEOVIM_OS_WSTREAM_DEFS_H
+
+typedef struct wstream WStream;
+
+#endif // NEOVIM_OS_WSTREAM_DEFS_H
+