diff options
| author | Eliseo Martínez <eliseomarmol@gmail.com> | 2014-05-12 02:25:17 +0200 |
|---|---|---|
| committer | Eliseo Martínez <eliseomarmol@gmail.com> | 2014-05-15 20:46:01 +0200 |
| commit | da51dc9cf202772f60bd2da975dbef257bd9237c (patch) | |
| tree | 5c16b93238a153f55634e9323077f30c8133970c /src/nvim/os | |
| parent | ffe61e5ba1721340ca51d56bae3ddaca415fb5bc (diff) | |
| download | rneovim-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')
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() +{ |