aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorThiago de Arruda <tpadilha84@gmail.com>2014-10-31 10:19:07 -0300
committerThiago de Arruda <tpadilha84@gmail.com>2014-10-31 22:52:10 -0300
commit01761cdd32b895cb9068db7fa90c5f4ef1c7c298 (patch)
tree4481836442845b8310f59ca24ff863bd92d60c3a /src
parent88c47475562aa1432d53aa221be2e8f1ed055889 (diff)
downloadrneovim-01761cdd32b895cb9068db7fa90c5f4ef1c7c298.tar.gz
rneovim-01761cdd32b895cb9068db7fa90c5f4ef1c7c298.tar.bz2
rneovim-01761cdd32b895cb9068db7fa90c5f4ef1c7c298.zip
job/shell: Refactor os_call_shell/os_system to share code
Diffstat (limited to 'src')
-rw-r--r--src/nvim/os/shell.c370
1 files changed, 112 insertions, 258 deletions
diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c
index 66a90dea43..7449ac637c 100644
--- a/src/nvim/os/shell.c
+++ b/src/nvim/os/shell.c
@@ -24,19 +24,9 @@
#include "nvim/option_defs.h"
#include "nvim/charset.h"
#include "nvim/strings.h"
+#include "nvim/ui.h"
#define DYNAMIC_BUFFER_INIT {NULL, 0, 0}
-#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;
typedef struct {
char *data;
@@ -109,140 +99,70 @@ void shell_free_argv(char **argv)
/// @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)
+/// @param extra_arg Extra argument to be passed to the shell
+int os_call_shell(char_u *cmd, ShellOpts opts, char_u *extra_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,
- };
-
+ DynamicBuffer input = DYNAMIC_BUFFER_INIT;
+ char *output = NULL, **output_ptr = NULL;
+ int current_state = State, old_mode = cur_tmode;
+ bool forward_output = true;
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;
+ forward_output = false;
} 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;
+ read_input(&input);
}
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');
+ output_ptr = &output;
+ forward_output = false;
}
-
- 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++;
+ size_t nread;
+ int status = shell((const char *)cmd,
+ (const char *)extra_arg,
+ input.data,
+ input.len,
+ output_ptr,
+ &nread,
+ emsg_silent,
+ forward_output);
+
+ if (input.data) {
+ free(input.data);
}
- // Keep running the loop until all three handles are completely closed
- while (pdata.exited < expected_exits) {
- event_poll(0);
-
- 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 (output) {
+ write_output(output, nread);
+ free(output);
}
- if (opts & kShellOptRead) {
- if (!GA_EMPTY(&pdata.ga)) {
- // 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 (!emsg_silent && status != 0 && !(opts & kShellOptSilent)) {
+ MSG_PUTS(_("\nshell returned "));
+ msg_outnum(status);
+ msg_putchar('\n');
}
- if (opts & kShellOptWrite) {
- free(pdata.wbuffer);
+ if (old_mode == TMODE_RAW) {
+ // restore mode
+ settmode(TMODE_RAW);
}
+ State = current_state;
+ signal_accept_deadly();
- return proc_cleanup_exit(&pdata, &proc_opts, opts);
+ return status;
}
/// os_system - synchronously execute a command in the shell
@@ -270,24 +190,48 @@ int os_system(const char *cmd,
char **output,
size_t *nread) FUNC_ATTR_NONNULL_ARG(1)
{
+ return shell(cmd, NULL, input, len, output, nread, true, false);
+}
+
+static int shell(const char *cmd,
+ const char *extra_arg,
+ const char *input,
+ size_t len,
+ char **output,
+ size_t *nread,
+ bool silent,
+ bool forward_output) FUNC_ATTR_NONNULL_ARG(1)
+{
// the output buffer
DynamicBuffer buf = DYNAMIC_BUFFER_INIT;
+ rstream_cb data_cb = system_data_cb;
- char **argv = shell_build_argv((char_u *) cmd, NULL);
+ if (forward_output) {
+ data_cb = out_data_cb;
+ } else if (!output) {
+ data_cb = NULL;
+ }
- int i;
+ char **argv = shell_build_argv((char_u *) cmd, (char_u *)extra_arg);
+
+ int status;
Job *job = job_start(argv,
&buf,
input != NULL,
- output ? system_data_cb : NULL,
- output ? system_data_cb : NULL,
+ data_cb,
+ data_cb,
NULL,
0,
- &i);
+ &status);
- if (i <= 0) {
- // couldn't even start the job
- ELOG("Couldn't start job, error code: '%d'", i);
+ if (status <= 0) {
+ // Failed, probably due to `sh` not being executable
+ ELOG("Couldn't start job, command: '%s', error code: '%d'", cmd, status);
+ if (!silent) {
+ MSG_PUTS(_("\nCannot execute shell "));
+ msg_outtrans(p_sh);
+ msg_putchar('\n');
+ }
return -1;
}
@@ -305,7 +249,7 @@ int os_system(const char *cmd,
job_close_in(job);
}
- int status = job_wait(job, -1);
+ status = job_wait(job, -1);
// prepare the out parameters if requested
if (output) {
@@ -354,6 +298,14 @@ static void system_data_cb(RStream *rstream, void *data, bool eof)
buf->len += nread;
}
+static void out_data_cb(RStream *rstream, void *data, bool eof)
+{
+ RBuffer *rbuffer = rstream_buffer(rstream);
+ size_t len = rbuffer_pending(rbuffer);
+ ui_write((char_u *)rbuffer_read_ptr(rbuffer), (int)len);
+ rbuffer_consumed(rbuffer, len);
+}
+
/// Parses a command string into a sequence of words, taking quotes into
/// consideration.
///
@@ -414,24 +366,11 @@ static size_t word_length(const char_u *str)
/// 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.
-/// 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)
+static void read_input(DynamicBuffer *buf)
{
- 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)
- size_t buflen = BUFFER_LENGTH;
- pdata->wbuffer = (char *)xmalloc(buflen);
- uv_buf_t uvbuf;
+ size_t written = 0, l = 0, len = 0;
linenr_T lnum = curbuf->b_op_start.lnum;
- size_t off = 0;
- size_t written = 0;
- char_u *lp = ml_get(lnum);
- size_t l;
- size_t len;
+ char_u *lp = ml_get(lnum);
for (;;) {
l = strlen((char *)lp + written);
@@ -440,26 +379,17 @@ static void write_selection(uv_write_t *req)
} 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;
+ dynamic_buffer_ensure(buf, buf->len + len);
+ buf->data[buf->len++] = NUL;
} else {
char_u *s = vim_strchr(lp + written, NL);
len = s == NULL ? l : (size_t)(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;
+ dynamic_buffer_ensure(buf, buf->len + len);
+ memcpy(buf->data + buf->len, lp + written, len);
+ buf->len += len;
}
if (len == l) {
- // Finished a line, add a NL, unless this line
- // should not have one.
+ // 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
@@ -467,12 +397,8 @@ static void write_selection(uv_write_t *req)
&& (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;
+ dynamic_buffer_ensure(buf, buf->len + 1);
+ buf->data[buf->len++] = NL;
}
++lnum;
if (lnum > curbuf->b_op_end.lnum) {
@@ -484,112 +410,40 @@ static void write_selection(uv_write_t *req)
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)
+static void write_output(char *output, size_t remaining)
{
- ProcessData *pdata = (ProcessData *)handle->data;
-
- if (pdata->reading) {
- buf->len = 0;
+ if (!output) {
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 unnecessary 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) {
+ size_t off = 0;
+ while (off < remaining) {
+ if (output[off] == 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]);
+ output[off] = NUL;
+ ml_append(curwin->w_cursor.lnum++, (char_u *)output, 0, false);
+ size_t skip = off + 1;
+ output += skip;
+ remaining -= skip;
+ off = 0;
+ continue;
}
- }
-
- 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++;
-}
-
-/// 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 *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');
+ if (output[off] == NUL) {
+ // Translate NUL to NL
+ output[off] = NL;
}
+ off++;
}
- State = proc_data->old_state;
-
- if (proc_data->old_mode == TMODE_RAW) {
- // restore mode
- settmode(TMODE_RAW);
+ if (remaining) {
+ // append unfinished line
+ ml_append(curwin->w_cursor.lnum++, (char_u *)output, 0, false);
+ // remember that the NL was missing
+ curbuf->b_no_eol_lnum = curwin->w_cursor.lnum;
+ } else {
+ curbuf->b_no_eol_lnum = 0;
}
-
- 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++;
- assert(status <= INT_MAX);
- data->exit_status = (int)status;
- uv_close((uv_handle_t *)proc, NULL);
}