aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmake/GenerateHelptags.cmake12
-rw-r--r--runtime/doc/nvim_python.txt2
-rw-r--r--runtime/python_setup.vim (renamed from runtime/plugin/python_setup.vim)0
-rw-r--r--src/nvim/api/window.c5
-rw-r--r--src/nvim/eval.c3
-rw-r--r--src/nvim/move.c12
-rw-r--r--src/nvim/msgpack_rpc/channel.c27
-rw-r--r--src/nvim/msgpack_rpc/server.c43
-rw-r--r--src/nvim/os/event.c55
-rw-r--r--src/nvim/os/input.c9
-rw-r--r--src/nvim/os/job.c236
-rw-r--r--src/nvim/os/rstream.c5
-rw-r--r--src/nvim/os/shell.c423
-rw-r--r--src/nvim/os/signal.c36
-rw-r--r--src/nvim/os/wstream.c38
-rw-r--r--src/nvim/os_unix.c4
-rw-r--r--test/functional/api/window_spec.lua69
17 files changed, 503 insertions, 476 deletions
diff --git a/cmake/GenerateHelptags.cmake b/cmake/GenerateHelptags.cmake
index 4101ff6cbf..2e923ab45f 100644
--- a/cmake/GenerateHelptags.cmake
+++ b/cmake/GenerateHelptags.cmake
@@ -1,6 +1,12 @@
-file(TO_CMAKE_PATH
- "$ENV{DESTDIR}/${CMAKE_INSTALL_PREFIX}/share/nvim/runtime/doc"
- HELPTAGS_WORKING_DIRECTORY)
+if(ENV{DESTDIR})
+ file(TO_CMAKE_PATH
+ "$ENV{DESTDIR}/${CMAKE_INSTALL_PREFIX}/share/nvim/runtime/doc"
+ HELPTAGS_WORKING_DIRECTORY)
+else()
+ file(TO_CMAKE_PATH
+ "${CMAKE_INSTALL_PREFIX}/share/nvim/runtime/doc"
+ HELPTAGS_WORKING_DIRECTORY)
+endif()
message(STATUS "Generating helptags in ${HELPTAGS_WORKING_DIRECTORY}.")
diff --git a/runtime/doc/nvim_python.txt b/runtime/doc/nvim_python.txt
index b618562900..9e6f408657 100644
--- a/runtime/doc/nvim_python.txt
+++ b/runtime/doc/nvim_python.txt
@@ -31,7 +31,7 @@ simple step-by-step:
loaded:
>
if has('nvim')
- runtime! plugin/python_setup.vim
+ runtime! python_setup.vim
endif
<
Most python plugins created for Vim 7.3 should work after these steps.
diff --git a/runtime/plugin/python_setup.vim b/runtime/python_setup.vim
index 0070885958..0070885958 100644
--- a/runtime/plugin/python_setup.vim
+++ b/runtime/python_setup.vim
diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c
index fde1ebfa4c..597c857ad5 100644
--- a/src/nvim/api/window.c
+++ b/src/nvim/api/window.c
@@ -9,6 +9,7 @@
#include "nvim/cursor.h"
#include "nvim/window.h"
#include "nvim/screen.h"
+#include "nvim/move.h"
#include "nvim/misc2.h"
@@ -86,6 +87,10 @@ void window_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err)
win->w_cursor.coladd = 0;
// When column is out of range silently correct it.
check_cursor_col_win(win);
+
+ // make sure cursor is in visible range even if win != curwin
+ update_topline_win(win);
+
update_screen(VALID);
}
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index d0af4b8249..caa936ba13 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -10675,6 +10675,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv)
job_start(argv,
xstrdup((char *)argvars[0].vval.v_string),
+ true,
on_job_stdout,
on_job_stderr,
on_job_exit,
@@ -19540,7 +19541,7 @@ char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub, char_u *flags)
event_push((Event) { \
.handler = on_job_event, \
.data = event_data \
- }); \
+ }, true); \
} while(0)
static void on_job_stdout(RStream *rstream, void *data, bool eof)
diff --git a/src/nvim/move.c b/src/nvim/move.c
index 2118f58f7e..c43fa1d09d 100644
--- a/src/nvim/move.c
+++ b/src/nvim/move.c
@@ -34,6 +34,7 @@
#include "nvim/popupmnu.h"
#include "nvim/screen.h"
#include "nvim/strings.h"
+#include "nvim/window.h"
typedef struct {
linenr_T lnum; /* line number */
@@ -315,6 +316,17 @@ void update_topline(void)
}
/*
+ * Update win->w_topline to move the cursor onto the screen.
+ */
+void update_topline_win(win_T* win)
+{
+ win_T *save_curwin;
+ switch_win(&save_curwin, NULL, win, NULL, true);
+ update_topline();
+ restore_win(save_curwin, NULL, true);
+}
+
+/*
* Return the scrolljump value to use for the current window.
* When 'scrolljump' is positive use it as-is.
* When 'scrolljump' is negative use it as a percentage of the window height.
diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c
index 43bed54b2c..10d180b3b7 100644
--- a/src/nvim/msgpack_rpc/channel.c
+++ b/src/nvim/msgpack_rpc/channel.c
@@ -119,6 +119,7 @@ uint64_t channel_from_job(char **argv)
int status;
channel->data.job = job_start(argv,
channel,
+ true,
job_out,
job_err,
job_exit,
@@ -432,15 +433,9 @@ static void handle_request(Channel *channel, msgpack_object *request)
handler.defer = false;
}
- Array args;
+ Array args = ARRAY_DICT_INIT;
msgpack_rpc_to_array(request->via.array.ptr + 3, &args);
-
- if (kv_size(channel->call_stack) || !handler.defer) {
- call_request_handler(channel, handler, args, request_id);
- return;
- }
-
- // Defer calling the request handler.
+ bool defer = (!kv_size(channel->call_stack) && handler.defer);
RequestEvent *event_data = kmp_alloc(RequestEventPool, request_event_pool);
event_data->channel = channel;
event_data->handler = handler;
@@ -449,21 +444,16 @@ static void handle_request(Channel *channel, msgpack_object *request)
event_push((Event) {
.handler = on_request_event,
.data = event_data
- });
+ }, defer);
}
static void on_request_event(Event event)
{
RequestEvent *e = event.data;
- call_request_handler(e->channel, e->handler, e->args, e->request_id);
- kmp_free(RequestEventPool, request_event_pool, e);
-}
-
-static void call_request_handler(Channel *channel,
- MsgpackRpcRequestHandler handler,
- Array args,
- uint64_t request_id)
-{
+ Channel *channel = e->channel;
+ MsgpackRpcRequestHandler handler = e->handler;
+ Array args = e->args;
+ uint64_t request_id = e->request_id;
Error error = ERROR_INIT;
Object result = handler.fn(channel->id, request_id, args, &error);
// send the response
@@ -476,6 +466,7 @@ static void call_request_handler(Channel *channel,
&out_buffer));
// All arguments were freed already, but we still need to free the array
free(args.items);
+ kmp_free(RequestEventPool, request_event_pool, e);
}
static bool channel_write(Channel *channel, WBuffer *buffer)
diff --git a/src/nvim/msgpack_rpc/server.c b/src/nvim/msgpack_rpc/server.c
index 087ba24111..91aca0c37a 100644
--- a/src/nvim/msgpack_rpc/server.c
+++ b/src/nvim/msgpack_rpc/server.c
@@ -11,7 +11,7 @@
#include "nvim/ascii.h"
#include "nvim/vim.h"
#include "nvim/memory.h"
-#include "nvim/message.h"
+#include "nvim/log.h"
#include "nvim/tempfile.h"
#include "nvim/map.h"
#include "nvim/path.h"
@@ -102,12 +102,12 @@ int server_start(const char *endpoint)
if (xstrlcpy(addr, endpoint, sizeof(addr)) >= sizeof(addr)) {
// TODO(aktau): since this is not what the user wanted, perhaps we
// should return an error here
- EMSG2("Address was too long, truncated to %s", addr);
+ WLOG("Address was too long, truncated to %s", addr);
}
// Check if the server already exists
if (pmap_has(cstr_t)(servers, addr)) {
- EMSG2("Already listening on %s", addr);
+ ELOG("Already listening on %s", addr);
return 1;
}
@@ -152,38 +152,30 @@ int server_start(const char *endpoint)
}
int result;
+ uv_stream_t *stream = NULL;
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;
result = uv_tcp_bind(&server->socket.tcp.handle,
(const struct sockaddr *)&server->socket.tcp.addr,
0);
- if (result == 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);
- }
- }
+ stream = (uv_stream_t *)&server->socket.tcp.handle;
} else {
// Listen on named pipe or unix socket
xstrlcpy(server->socket.pipe.addr, addr, sizeof(server->socket.pipe.addr));
uv_pipe_init(uv_default_loop(), &server->socket.pipe.handle, 0);
- server->socket.pipe.handle.data = server;
result = uv_pipe_bind(&server->socket.pipe.handle,
server->socket.pipe.addr);
- if (result == 0) {
- result = uv_listen((uv_stream_t *)&server->socket.pipe.handle,
- MAX_CONNECTIONS,
- connection_cb);
+ stream = (uv_stream_t *)&server->socket.pipe.handle;
+ }
- if (result) {
- uv_close((uv_handle_t *)&server->socket.pipe.handle, free_server);
- }
- }
+ stream->data = server;
+
+ if (result == 0) {
+ result = uv_listen((uv_stream_t *)&server->socket.tcp.handle,
+ MAX_CONNECTIONS,
+ connection_cb);
}
assert(result <= 0); // libuv should have returned -errno or zero.
@@ -196,13 +188,12 @@ int server_start(const char *endpoint)
result = -ENOENT;
}
}
- EMSG2("Failed to start server: %s", uv_strerror(result));
- free(server);
+ uv_close((uv_handle_t *)stream, free_server);
+ ELOG("Failed to start server: %s", uv_strerror(result));
return result;
}
server->type = server_type;
-
// Add the server to the hash table
pmap_put(cstr_t)(servers, addr, server);
@@ -221,7 +212,7 @@ void server_stop(char *endpoint)
xstrlcpy(addr, endpoint, sizeof(addr));
if ((server = pmap_get(cstr_t)(servers, addr)) == NULL) {
- EMSG2("Not listening on %s", addr);
+ ELOG("Not listening on %s", addr);
return;
}
@@ -255,7 +246,7 @@ static void connection_cb(uv_stream_t *server, int status)
result = uv_accept(server, client);
if (result) {
- EMSG2("Failed to accept connection: %s", uv_strerror(result));
+ ELOG("Failed to accept connection: %s", uv_strerror(result));
uv_close((uv_handle_t *)client, free_client);
return;
}
diff --git a/src/nvim/os/event.c b/src/nvim/os/event.c
index 2dee529452..ecaec0b9ce 100644
--- a/src/nvim/os/event.c
+++ b/src/nvim/os/event.c
@@ -14,6 +14,7 @@
#include "nvim/os/provider.h"
#include "nvim/os/signal.h"
#include "nvim/os/rstream.h"
+#include "nvim/os/wstream.h"
#include "nvim/os/job.h"
#include "nvim/vim.h"
#include "nvim/memory.h"
@@ -34,17 +35,24 @@ typedef struct {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/event.c.generated.h"
#endif
-static klist_t(Event) *pending_events;
+// deferred_events: Events that should be processed as the K_EVENT special key
+// immediate_events: Events that should be processed after exiting libuv event
+// loop(to avoid recursion), but before returning from
+// `event_poll`
+static klist_t(Event) *deferred_events, *immediate_events;
void event_init(void)
{
+ // Initialize the event queues
+ deferred_events = kl_init(Event);
+ immediate_events = kl_init(Event);
// early msgpack-rpc initialization
msgpack_rpc_init_method_table();
msgpack_rpc_helpers_init();
- // Initialize the event queues
- pending_events = kl_init(Event);
+ wstream_init();
// Initialize input events
input_init();
+ input_start();
// Timer to wake the event loop if a timeout argument is passed to
// `event_poll`
// Signals
@@ -63,20 +71,27 @@ void event_teardown(void)
channel_teardown();
job_teardown();
server_teardown();
+ signal_teardown();
+ input_stop();
+ input_teardown();
+ do {
+ // This will loop forever if we leave any unclosed handles. Currently it is
+ // the most reliable way to use travis for verifying the no libuv-related
+ // bugs(which can be hard to track later) were introduced on a PR.
+ uv_run(uv_default_loop(), UV_RUN_DEFAULT);
+ } while (uv_loop_close(uv_default_loop()));
}
// Wait for some event
void event_poll(int ms)
{
- uv_run_mode run_mode = UV_RUN_ONCE;
-
static int recursive = 0;
- if (!(recursive++)) {
- // Only needs to start the libuv handle the first time we enter here
- input_start();
+ if (recursive++) {
+ abort(); // Should not re-enter uv_run
}
+ uv_run_mode run_mode = UV_RUN_ONCE;
uv_timer_t timer;
uv_prepare_t timer_prepare;
TimerData timer_data = {.ms = ms, .timed_out = false, .timer = &timer};
@@ -99,37 +114,41 @@ void event_poll(int ms)
loop(run_mode);
- if (!(--recursive)) {
- // Again, only stop when we leave the top-level invocation
- input_stop();
- }
-
if (ms > 0) {
// Ensure the timer-related handles are closed and run the event loop
// once more to let libuv perform it's cleanup
+ uv_timer_stop(&timer);
+ uv_prepare_stop(&timer_prepare);
uv_close((uv_handle_t *)&timer, NULL);
uv_close((uv_handle_t *)&timer_prepare, NULL);
loop(UV_RUN_NOWAIT);
}
+
+ recursive--; // Can re-enter uv_run now
+ process_events_from(immediate_events);
}
bool event_has_deferred(void)
{
- return !kl_empty(pending_events);
+ return !kl_empty(deferred_events);
}
// Queue an event
-void event_push(Event event)
+void event_push(Event event, bool deferred)
{
- *kl_pushp(Event, pending_events) = event;
+ *kl_pushp(Event, deferred ? deferred_events : immediate_events) = event;
}
-
void event_process(void)
{
+ process_events_from(deferred_events);
+}
+
+static void process_events_from(klist_t(Event) *queue)
+{
Event event;
- while (kl_shift(Event, pending_events, &event) == 0) {
+ while (kl_shift(Event, queue, &event) == 0) {
event.handler(event);
}
}
diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c
index d948a48b64..3ebfb3f12b 100644
--- a/src/nvim/os/input.c
+++ b/src/nvim/os/input.c
@@ -52,6 +52,15 @@ void input_init(void)
rstream_set_file(read_stream, read_cmd_fd);
}
+void input_teardown(void)
+{
+ if (embedded_mode) {
+ return;
+ }
+
+ rstream_free(read_stream);
+}
+
// Listen for input
void input_start(void)
{
diff --git a/src/nvim/os/job.c b/src/nvim/os/job.c
index f8ad6874c9..2f610cb51f 100644
--- a/src/nvim/os/job.c
+++ b/src/nvim/os/job.c
@@ -13,10 +13,14 @@
#include "nvim/os/event.h"
#include "nvim/os/event_defs.h"
#include "nvim/os/shell.h"
+#include "nvim/os/time.h"
#include "nvim/vim.h"
#include "nvim/memory.h"
-#define EXIT_TIMEOUT 25
+// {SIGNAL}_TIMEOUT is the time (in nanoseconds) that a job has to cleanly exit
+// before we send SIGNAL to it
+#define TERM_TIMEOUT 1000000000
+#define KILL_TIMEOUT (TERM_TIMEOUT * 2)
#define MAX_RUNNING_JOBS 100
#define JOB_BUFFER_SIZE 0xFFFF
@@ -39,14 +43,14 @@ struct job {
// Job id the index in the job table plus one.
int id;
// Exit status code of the job process
- int64_t status;
- // Number of polls after a SIGTERM that will trigger a SIGKILL
- int exit_timeout;
+ int status;
// Number of references to the job. The job resources will only be freed by
// close_cb when this is 0
int refcount;
- // If the job was already stopped
- bool stopped;
+ // Time when job_stop was called for the job.
+ uint64_t stopped_time;
+ // If SIGTERM was already sent to the job(only send one before SIGKILL)
+ bool term_sent;
// Data associated with the job
void *data;
// Callbacks
@@ -64,8 +68,8 @@ struct job {
};
static Job *table[MAX_RUNNING_JOBS] = {NULL};
-static uint32_t job_count = 0;
-static uv_prepare_t job_prepare;
+size_t stop_requests = 0;
+static uv_timer_t job_stop_timer;
// Some helpers shared in this module
@@ -78,57 +82,24 @@ static uv_prepare_t job_prepare;
void job_init(void)
{
uv_disable_stdio_inheritance();
- uv_prepare_init(uv_default_loop(), &job_prepare);
+ uv_timer_init(uv_default_loop(), &job_stop_timer);
}
/// Releases job control resources and terminates running jobs
void job_teardown(void)
{
- // 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++) {
+ // Stop all jobs
+ for (int i = 0; i < MAX_RUNNING_JOBS; i++) {
+ Job *job;
if ((job = table[i]) != NULL) {
- all_dead = false;
- uv_process_kill(&job->proc, SIGTERM);
+ job_stop(job);
}
}
- 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
- event_poll(0);
-
- // Prepare to start shooting
- for (i = 0; i < MAX_RUNNING_JOBS; i++) {
- job = table[i];
-
- // Still alive
- while (job && is_alive(job) && remaining_tries--) {
- os_delay(50, 0);
- // Acknowledge child exits
- event_poll(0);
- // It's possible that the event_poll call removed the job from the table,
- // reset 'job' so the next iteration won't run in that case.
- job = table[i];
- }
-
- if (job && is_alive(job)) {
- uv_process_kill(&job->proc, SIGKILL);
- }
- }
- // Last run to ensure all children were removed
- event_poll(0);
+ // Wait until all jobs are closed
+ event_poll_until(-1, !stop_requests);
+ // Close the timer
+ uv_close((uv_handle_t *)&job_stop_timer, NULL);
}
/// Tries to start a new job.
@@ -136,10 +107,12 @@ void job_teardown(void)
/// @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 writable If true the job stdin will be available for writing with
+/// job_write, otherwise it will be redirected to /dev/null
/// @param stdout_cb Callback that will be invoked when data is available
-/// on stdout
+/// on stdout. If NULL stdout will be redirected to /dev/null.
/// @param stderr_cb Callback that will be invoked when data is available
-/// on stderr
+/// on stderr. If NULL stderr will be redirected to /dev/null.
/// @param job_exit_cb Callback that will be invoked when the job exits
/// @param maxmem Maximum amount of memory used by the job WStream
/// @param[out] status The job id if the job started successfully, 0 if the job
@@ -147,6 +120,7 @@ void job_teardown(void)
/// @return The job pointer if the job started successfully, NULL otherwise
Job *job_start(char **argv,
void *data,
+ bool writable,
rstream_cb stdout_cb,
rstream_cb stderr_cb,
job_exit_cb job_exit_cb,
@@ -174,13 +148,13 @@ Job *job_start(char **argv,
job->id = i + 1;
*status = job->id;
job->status = -1;
- job->refcount = 4;
+ job->refcount = 1;
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->stopped_time = 0;
+ job->term_sent = false;
job->proc_opts.file = argv[0];
job->proc_opts.args = argv;
job->proc_opts.stdio = job->stdio;
@@ -193,49 +167,78 @@ Job *job_start(char **argv,
job->proc_stdin.data = NULL;
job->proc_stdout.data = NULL;
job->proc_stderr.data = NULL;
+ job->in = NULL;
+ job->out = NULL;
+ job->err = 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;
+ job->stdio[0].flags = UV_IGNORE;
+ job->stdio[1].flags = UV_IGNORE;
+ job->stdio[2].flags = UV_IGNORE;
+
+ if (writable) {
+ 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;
+ handle_set_job((uv_handle_t *)&job->proc_stdin, job);
+ job->refcount++;
+ }
- 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;
+ if (stdout_cb) {
+ 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;
+ handle_set_job((uv_handle_t *)&job->proc_stdout, job);
+ job->refcount++;
+ }
- 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;
+ if (stderr_cb) {
+ 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;
+ handle_set_job((uv_handle_t *)&job->proc_stderr, job);
+ job->refcount++;
+ }
- // 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);
// Spawn the job
if (uv_spawn(uv_default_loop(), &job->proc, &job->proc_opts) != 0) {
+ if (writable) {
+ uv_close((uv_handle_t *)&job->proc_stdin, close_cb);
+ }
+ if (stdout_cb) {
+ uv_close((uv_handle_t *)&job->proc_stdout, close_cb);
+ }
+ if (stderr_cb) {
+ uv_close((uv_handle_t *)&job->proc_stderr, close_cb);
+ }
+ uv_close((uv_handle_t *)&job->proc, close_cb);
+ event_poll(0);
+ // Manually invoke the close_cb to free the job resources
*status = -1;
return NULL;
}
- job->in = wstream_new(maxmem);
- wstream_set_stream(job->in, (uv_stream_t *)&job->proc_stdin);
+ if (writable) {
+ job->in = wstream_new(maxmem);
+ wstream_set_stream(job->in, (uv_stream_t *)&job->proc_stdin);
+ }
+
// Start the readable streams
- job->out = rstream_new(read_cb, rbuffer_new(JOB_BUFFER_SIZE), job);
- job->err = rstream_new(read_cb, rbuffer_new(JOB_BUFFER_SIZE), job);
- 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;
+ if (stdout_cb) {
+ job->out = rstream_new(read_cb, rbuffer_new(JOB_BUFFER_SIZE), job);
+ rstream_set_stream(job->out, (uv_stream_t *)&job->proc_stdout);
+ rstream_start(job->out);
+ }
- // Start polling job status if this is the first
- if (job_count == 0) {
- uv_prepare_start(&job_prepare, job_prepare_cb);
+ if (stderr_cb) {
+ job->err = rstream_new(read_cb, rbuffer_new(JOB_BUFFER_SIZE), job);
+ rstream_set_stream(job->err, (uv_stream_t *)&job->proc_stderr);
+ rstream_start(job->err);
}
- job_count++;
+ // Save the job to the table
+ table[i] = job;
return job;
}
@@ -249,7 +252,7 @@ Job *job_find(int id)
Job *job;
if (id <= 0 || id > MAX_RUNNING_JOBS || !(job = table[id - 1])
- || job->stopped) {
+ || job->stopped_time) {
return NULL;
}
@@ -262,7 +265,22 @@ Job *job_find(int id)
/// @param job The Job instance
void job_stop(Job *job)
{
- job->stopped = true;
+ if (job->stopped_time) {
+ return;
+ }
+
+ job->stopped_time = os_hrtime();
+ // Close the standard streams of the job
+ close_job_in(job);
+ close_job_out(job);
+ close_job_err(job);
+
+ if (!stop_requests++) {
+ // When there's at least one stop request pending, start a timer that
+ // will periodically check if a signal should be send to a to the job
+ DLOG("Starting job kill timer");
+ uv_timer_start(&job_stop_timer, job_stop_timer_cb, 100, 100);
+ }
}
/// job_wait - synchronously wait for a job to finish
@@ -276,6 +294,9 @@ void job_stop(Job *job)
/// is possible on some OS.
int job_wait(Job *job, int ms) FUNC_ATTR_NONNULL_ALL
{
+ // The default status is -1, which represents a timeout
+ int status = -1;
+
// Increase refcount to stop the job from being freed before we have a
// chance to get the status.
job->refcount++;
@@ -291,15 +312,16 @@ int job_wait(Job *job, int ms) FUNC_ATTR_NONNULL_ALL
event_poll(0);
}
- if (!--job->refcount) {
- int status = (int) job->status;
- // Manually invoke close_cb to free the job resources
+ if (job->refcount == 1) {
+ // Job exited, collect status and manually invoke close_cb to free the job
+ // resources
+ status = job->status;
close_cb((uv_handle_t *)&job->proc);
- return status;
+ } else {
+ job->refcount--;
}
- // return -1 for a timeout
- return -1;
+ return status;
}
/// Close the pipe used to write to the job.
@@ -372,35 +394,33 @@ static void job_exit_callback(Job *job)
job->exit_cb(job, job->data);
}
- // Stop polling job status if this was the last
- job_count--;
- if (job_count == 0) {
- uv_prepare_stop(&job_prepare);
+ if (stop_requests && !--stop_requests) {
+ // Stop the timer if no more stop requests are pending
+ DLOG("Stopping job kill timer");
+ uv_timer_stop(&job_stop_timer);
}
}
-static bool is_alive(Job *job)
-{
- return uv_process_kill(&job->proc, 0) == 0;
-}
-
/// 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)
+static void job_stop_timer_cb(uv_timer_t *handle)
{
Job *job;
- int i;
+ uint64_t now = os_hrtime();
- for (i = 0; i < MAX_RUNNING_JOBS; i++) {
- if ((job = table[i]) == NULL || !job->stopped) {
+ for (size_t i = 0; i < MAX_RUNNING_JOBS; i++) {
+ if ((job = table[i]) == NULL || !job->stopped_time) {
continue;
}
- if ((job->exit_timeout--) == EXIT_TIMEOUT) {
- // Job was just stopped, close all stdio handles and send SIGTERM
+ uint64_t elapsed = now - job->stopped_time;
+
+ if (!job->term_sent && elapsed >= TERM_TIMEOUT) {
+ ILOG("Sending SIGTERM to job(id: %d)", job->id);
uv_process_kill(&job->proc, SIGTERM);
- } else if (job->exit_timeout == 0) {
- // We've waited long enough, send SIGKILL
+ job->term_sent = true;
+ } else if (elapsed >= KILL_TIMEOUT) {
+ ILOG("Sending SIGKILL to job(id: %d)", job->id);
uv_process_kill(&job->proc, SIGKILL);
}
}
@@ -429,7 +449,7 @@ static void exit_cb(uv_process_t *proc, int64_t status, int term_signal)
{
Job *job = handle_get_job((uv_handle_t *)proc);
- job->status = status;
+ job->status = (int)status;
uv_close((uv_handle_t *)&job->proc, close_cb);
}
diff --git a/src/nvim/os/rstream.c b/src/nvim/os/rstream.c
index beff404fd0..f16226cdd1 100644
--- a/src/nvim/os/rstream.c
+++ b/src/nvim/os/rstream.c
@@ -309,6 +309,11 @@ size_t rstream_read(RStream *rstream, char *buffer, size_t count)
return rbuffer_read(rstream->buffer, buffer, count);
}
+RBuffer *rstream_buffer(RStream *rstream)
+{
+ return rstream->buffer;
+}
+
// Callbacks used by libuv
// Called by libuv to allocate memory for reading.
diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c
index d5464f7975..7449ac637c 100644
--- a/src/nvim/os/shell.c
+++ b/src/nvim/os/shell.c
@@ -24,24 +24,14 @@
#include "nvim/option_defs.h"
#include "nvim/charset.h"
#include "nvim/strings.h"
+#include "nvim/ui.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;
+#define DYNAMIC_BUFFER_INIT {NULL, 0, 0}
typedef struct {
char *data;
- size_t cap;
- size_t len;
-} dyn_buffer_t;
+ size_t cap, len;
+} DynamicBuffer;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/shell.c.generated.h"
@@ -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);
+ output_ptr = &output;
+ forward_output = false;
}
}
- 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);
+ 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);
}
- // 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) {
- 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
@@ -258,8 +178,8 @@ int os_call_shell(char_u *cmd, ShellOpts opts, char_u *extra_shell_arg)
/// @param len The length of the input buffer (not used if `input` == NULL)
/// @param[out] output A pointer to to a location where the output will be
/// allocated and stored. Will point to NULL if the shell
-/// command did not output anything. NOTE: it's not
-/// allowed to pass NULL yet
+/// command did not output anything. If NULL is passed,
+/// the shell output will be ignored.
/// @param[out] nread the number of bytes in the returned buffer (if the
/// returned buffer is not NULL)
/// @return the return code of the process, -1 if the process couldn't be
@@ -268,26 +188,50 @@ int os_system(const char *cmd,
const char *input,
size_t len,
char **output,
- size_t *nread) FUNC_ATTR_NONNULL_ARG(1, 4)
+ 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
- dyn_buffer_t buf;
- memset(&buf, 0, sizeof(buf));
+ 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;
+ }
+
+ char **argv = shell_build_argv((char_u *) cmd, (char_u *)extra_arg);
- int i;
+ int status;
Job *job = job_start(argv,
&buf,
- system_data_cb,
- system_data_cb,
+ input != 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;
}
@@ -300,34 +244,37 @@ int os_system(const char *cmd,
job_stop(job);
return -1;
}
+ // close the input stream, let the process know that no more input is
+ // coming
+ job_close_in(job);
}
- // close the input stream, let the process know that no more input is coming
- job_close_in(job);
- int status = job_wait(job, -1);
+ status = job_wait(job, -1);
// prepare the out parameters if requested
- if (buf.len == 0) {
- // no data received from the process, return NULL
- *output = NULL;
- free(buf.data);
- } else {
- // NUL-terminate to make the output directly usable as a C string
- buf.data[buf.len] = NUL;
- *output = buf.data;
- }
+ if (output) {
+ if (buf.len == 0) {
+ // no data received from the process, return NULL
+ *output = NULL;
+ free(buf.data);
+ } else {
+ // NUL-terminate to make the output directly usable as a C string
+ buf.data[buf.len] = NUL;
+ *output = buf.data;
+ }
- if (nread) {
- *nread = buf.len;
+ if (nread) {
+ *nread = buf.len;
+ }
}
return status;
}
-/// dyn_buf_ensure - ensures at least `desired` bytes in buffer
+/// - ensures at least `desired` bytes in buffer
///
/// TODO(aktau): fold with kvec/garray
-static void dyn_buf_ensure(dyn_buffer_t *buf, size_t desired)
+static void dynamic_buffer_ensure(DynamicBuffer *buf, size_t desired)
{
if (buf->cap >= desired) {
return;
@@ -341,16 +288,24 @@ static void dyn_buf_ensure(dyn_buffer_t *buf, size_t desired)
static void system_data_cb(RStream *rstream, void *data, bool eof)
{
Job *job = data;
- dyn_buffer_t *buf = job_data(job);
+ DynamicBuffer *buf = job_data(job);
size_t nread = rstream_pending(rstream);
- dyn_buf_ensure(buf, buf->len + nread + 1);
+ dynamic_buffer_ensure(buf, buf->len + nread + 1);
rstream_read(rstream, buf->data + buf->len, nread);
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.
///
@@ -411,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);
@@ -437,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
@@ -464,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) {
@@ -481,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)
-{
- 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)
+static void write_output(char *output, size_t remaining)
{
- // 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++;
- }
+ if (!output) {
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);
}
diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c
index 36f7b37c48..cf8ba85ed5 100644
--- a/src/nvim/os/signal.c
+++ b/src/nvim/os/signal.c
@@ -2,6 +2,8 @@
#include <uv.h>
+#include "nvim/lib/klist.h"
+
#include "nvim/types.h"
#include "nvim/ascii.h"
#include "nvim/vim.h"
@@ -13,6 +15,11 @@
#include "nvim/misc1.h"
#include "nvim/misc2.h"
#include "nvim/os/signal.h"
+#include "nvim/os/event.h"
+
+#define SignalEventFreer(x)
+KMEMPOOL_INIT(SignalEventPool, int, SignalEventFreer)
+kmempool_t(SignalEventPool) *signal_event_pool = NULL;
static uv_signal_t sint, spipe, shup, squit, sterm, swinch;
#ifdef SIGPWR
@@ -26,6 +33,7 @@ static bool rejecting_deadly;
#endif
void signal_init(void)
{
+ signal_event_pool = kmp_init(SignalEventPool);
uv_signal_init(uv_default_loop(), &sint);
uv_signal_init(uv_default_loop(), &spipe);
uv_signal_init(uv_default_loop(), &shup);
@@ -47,6 +55,20 @@ void signal_init(void)
#endif
}
+void signal_teardown(void)
+{
+ signal_stop();
+ uv_close((uv_handle_t *)&sint, NULL);
+ uv_close((uv_handle_t *)&spipe, NULL);
+ uv_close((uv_handle_t *)&shup, NULL);
+ uv_close((uv_handle_t *)&squit, NULL);
+ uv_close((uv_handle_t *)&sterm, NULL);
+ uv_close((uv_handle_t *)&swinch, NULL);
+#ifdef SIGPWR
+ uv_close((uv_handle_t *)&spwr, NULL);
+#endif
+}
+
void signal_stop(void)
{
uv_signal_stop(&sint);
@@ -113,6 +135,19 @@ static void deadly_signal(int signum)
static void signal_cb(uv_signal_t *handle, int signum)
{
+ int *n = kmp_alloc(SignalEventPool, signal_event_pool);
+ *n = signum;
+ event_push((Event) {
+ .handler = on_signal_event,
+ .data = n
+ }, false);
+}
+
+static void on_signal_event(Event event)
+{
+ int signum = *((int *)event.data);
+ kmp_free(SignalEventPool, signal_event_pool, event.data);
+
switch (signum) {
case SIGINT:
got_int = true;
@@ -142,3 +177,4 @@ static void signal_cb(uv_signal_t *handle, int signum)
break;
}
}
+
diff --git a/src/nvim/os/wstream.c b/src/nvim/os/wstream.c
index eb7de02a2f..a1f3612418 100644
--- a/src/nvim/os/wstream.c
+++ b/src/nvim/os/wstream.c
@@ -5,6 +5,8 @@
#include <uv.h>
+#include "nvim/lib/klist.h"
+
#include "nvim/os/uv_helpers.h"
#include "nvim/os/wstream.h"
#include "nvim/os/wstream_defs.h"
@@ -36,13 +38,27 @@ struct wbuffer {
typedef struct {
WStream *wstream;
WBuffer *buffer;
-} WriteData;
+ uv_write_t uv_req;
+} WRequest;
+#define WRequestFreer(x)
+KMEMPOOL_INIT(WRequestPool, WRequest, WRequestFreer)
+kmempool_t(WRequestPool) *wrequest_pool = NULL;
+#define WBufferFreer(x)
+KMEMPOOL_INIT(WBufferPool, WBuffer, WBufferFreer)
+kmempool_t(WBufferPool) *wbuffer_pool = NULL;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/wstream.c.generated.h"
#endif
+/// Initialize pools for reusing commonly created objects
+void wstream_init(void)
+{
+ wrequest_pool = kmp_init(WRequestPool);
+ wbuffer_pool = kmp_init(WBufferPool);
+}
+
/// Creates a new WStream instance. A WStream encapsulates all the boilerplate
/// necessary for writing to a libuv stream.
///
@@ -148,20 +164,17 @@ bool wstream_write(WStream *wstream, WBuffer *buffer)
wstream->curmem += buffer->size;
- WriteData *data = xmalloc(sizeof(WriteData));
+ WRequest *data = kmp_alloc(WRequestPool, wrequest_pool);
data->wstream = wstream;
data->buffer = buffer;
-
- uv_write_t *req = xmalloc(sizeof(uv_write_t));
- req->data = data;
+ data->uv_req.data = data;
uv_buf_t uvbuf;
uvbuf.base = buffer->data;
uvbuf.len = buffer->size;
- if (uv_write(req, wstream->stream, &uvbuf, 1, write_cb)) {
- free(data);
- free(req);
+ if (uv_write(&data->uv_req, wstream->stream, &uvbuf, 1, write_cb)) {
+ kmp_free(WRequestPool, wrequest_pool, data);
goto err;
}
@@ -190,7 +203,7 @@ WBuffer *wstream_new_buffer(char *data,
size_t refcount,
wbuffer_data_finalizer cb)
{
- WBuffer *rv = xmalloc(sizeof(WBuffer));
+ WBuffer *rv = kmp_alloc(WBufferPool, wbuffer_pool);
rv->size = size;
rv->refcount = refcount;
rv->cb = cb;
@@ -201,9 +214,8 @@ WBuffer *wstream_new_buffer(char *data,
static void write_cb(uv_write_t *req, int status)
{
- WriteData *data = req->data;
+ WRequest *data = req->data;
- free(req);
data->wstream->curmem -= data->buffer->size;
release_wbuffer(data->buffer);
@@ -221,7 +233,7 @@ static void write_cb(uv_write_t *req, int status)
free(data->wstream);
}
- free(data);
+ kmp_free(WRequestPool, wrequest_pool, data);
}
static void release_wbuffer(WBuffer *buffer)
@@ -231,7 +243,7 @@ static void release_wbuffer(WBuffer *buffer)
buffer->cb(buffer->data);
}
- free(buffer);
+ kmp_free(WBufferPool, wbuffer_pool, buffer);
}
}
diff --git a/src/nvim/os_unix.c b/src/nvim/os_unix.c
index 0ad15bc433..8ab61045dc 100644
--- a/src/nvim/os_unix.c
+++ b/src/nvim/os_unix.c
@@ -522,8 +522,6 @@ void mch_exit(int r)
{
exiting = TRUE;
- event_teardown();
-
{
settmode(TMODE_COOK);
mch_restore_title(3); /* restore xterm title and icon name */
@@ -559,7 +557,7 @@ void mch_exit(int r)
mac_conv_cleanup();
#endif
-
+ event_teardown();
#ifdef EXITFREE
free_all_mem();
diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua
index a3814cce0f..4c867d2f5d 100644
--- a/test/functional/api/window_spec.lua
+++ b/test/functional/api/window_spec.lua
@@ -1,9 +1,28 @@
-- Sanity checks for window_* API calls via msgpack-rpc
local helpers = require('test.functional.helpers')
local clear, nvim, buffer, curbuf, curbuf_contents, window, curwin, eq, neq,
- ok = helpers.clear, helpers.nvim, helpers.buffer, helpers.curbuf,
+ ok, feed, rawfeed, insert, eval = helpers.clear, helpers.nvim, helpers.buffer, helpers.curbuf,
helpers.curbuf_contents, helpers.window, helpers.curwin, helpers.eq,
- helpers.neq, helpers.ok
+ helpers.neq, helpers.ok, helpers.feed, helpers.rawfeed, helpers.insert, helpers.eval
+
+-- check if str is visible at the beginning of some line
+local function is_visible(str)
+ local slen = string.len(str)
+ local nlines = eval("&lines")
+ for i = 1,nlines do
+ local iseq = true
+ for j = 1,slen do
+ if string.byte(str,j) ~= eval("screenchar("..i..","..j..")") then
+ iseq = false
+ break
+ end
+ end
+ if iseq then
+ return true
+ end
+ end
+ return false
+end
describe('window_* functions', function()
before_each(clear)
@@ -29,6 +48,52 @@ describe('window_* functions', function()
nvim('command', 'normal i dumb')
eq('typing\n some dumb text', curbuf_contents())
end)
+
+ it('updates the screen, and also when the window is unfocused', function()
+ insert("prologue")
+ feed('100o<esc>')
+ insert("epilogue")
+ win = curwin()
+ feed('gg')
+
+ -- cursor position is at beginning
+ eq({1, 0}, window('get_cursor', win))
+ eq(true, is_visible("prologue"))
+ eq(false, is_visible("epilogue"))
+
+ -- move cursor to end
+ window('set_cursor', win, {101, 0})
+ eq(false, is_visible("prologue"))
+ eq(true, is_visible("epilogue"))
+
+ -- move cursor to the beginning again
+ window('set_cursor', win, {1, 0})
+ eq(true, is_visible("prologue"))
+ eq(false, is_visible("epilogue"))
+
+ -- move focus to new window
+ nvim('command',"new")
+ neq(win, curwin())
+
+ -- sanity check, cursor position is kept
+ eq({1, 0}, window('get_cursor', win))
+ eq(true, is_visible("prologue"))
+ eq(false, is_visible("epilogue"))
+
+ -- move cursor to end
+ window('set_cursor', win, {101, 0})
+ eq(false, is_visible("prologue"))
+ eq(true, is_visible("epilogue"))
+
+ -- move cursor to the beginning again
+ window('set_cursor', win, {1, 0})
+ eq(true, is_visible("prologue"))
+ eq(false, is_visible("epilogue"))
+
+ -- curwin didn't change back
+ neq(win, curwin())
+ end)
+
end)
describe('{get,set}_height', function()