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.
This commit is contained in:
Eliseo Martínez
2014-05-12 02:25:17 +02:00
parent ffe61e5ba1
commit da51dc9cf2
463 changed files with 0 additions and 0 deletions

465
src/nvim/os/shell.c Normal file
View File

@@ -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);
}