diff options
-rw-r--r-- | cmake/GenerateHelptags.cmake | 12 | ||||
-rw-r--r-- | runtime/doc/nvim_python.txt | 2 | ||||
-rw-r--r-- | runtime/python_setup.vim (renamed from runtime/plugin/python_setup.vim) | 0 | ||||
-rw-r--r-- | src/nvim/api/window.c | 5 | ||||
-rw-r--r-- | src/nvim/eval.c | 3 | ||||
-rw-r--r-- | src/nvim/move.c | 12 | ||||
-rw-r--r-- | src/nvim/msgpack_rpc/channel.c | 27 | ||||
-rw-r--r-- | src/nvim/msgpack_rpc/server.c | 43 | ||||
-rw-r--r-- | src/nvim/os/event.c | 55 | ||||
-rw-r--r-- | src/nvim/os/input.c | 9 | ||||
-rw-r--r-- | src/nvim/os/job.c | 236 | ||||
-rw-r--r-- | src/nvim/os/rstream.c | 5 | ||||
-rw-r--r-- | src/nvim/os/shell.c | 423 | ||||
-rw-r--r-- | src/nvim/os/signal.c | 36 | ||||
-rw-r--r-- | src/nvim/os/wstream.c | 38 | ||||
-rw-r--r-- | src/nvim/os_unix.c | 4 | ||||
-rw-r--r-- | test/functional/api/window_spec.lua | 69 |
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() |