mirror of
https://github.com/neovim/neovim.git
synced 2026-09-08 23:18:59 +00:00
fix(:restart): formalize restart event #35223
Problem: The "restart" event has some problems: - all UI clients must implement a somewhat complex set of setups - UI must be on the same machine as the server - only works for the "current" UI - race/edge case: If the user config has errors / waiting for input, are all UIs able to attach while Nvim is waiting for input? Solution: - Perform the restart on the server, not the client. - Pass listen address (instead of CLI args) in the UI event. - Simplifies UI logic: they only need to attach to new address. - Opens the door for more enhancements in the future, such as allowing all UIs to reattach instead of only the "current" UI. Co-authored-by: zeertzjq <zeertzjq@outlook.com> Co-authored-by: Justin M. Keyes <justinkz@gmail.com>
This commit is contained in:
@@ -247,13 +247,11 @@ the editor.
|
||||
Indicates to the UI that it must stop rendering the cursor. This event
|
||||
is misnamed and does not actually have anything to do with busyness.
|
||||
|
||||
["restart", progpath, argv] ~
|
||||
["restart", listen_addr, command] ~
|
||||
|:restart| command has been used and the Nvim server is about to exit.
|
||||
The UI should wait for the server to exit, and then start a new server
|
||||
using `progpath` as the full path to the Nvim executable |v:progpath| and
|
||||
`argv` as its arguments |v:argv|, and reattach to the new server.
|
||||
Note: |--embed| and |--headless| are excluded from `argv`, and the client
|
||||
should decide itself whether to add either flag.
|
||||
After the current server's channel is closed, the UI should attach to
|
||||
the new server's listening address at `listen_addr`, and then execute
|
||||
`command` on the new server.
|
||||
|
||||
["suspend"] ~
|
||||
|:suspend| command or |CTRL-Z| mapping is used. A terminal client (or
|
||||
|
||||
@@ -1688,6 +1688,20 @@ nvim_strwidth({text}) *nvim_strwidth()*
|
||||
Return: ~
|
||||
(`integer`) Number of cells
|
||||
|
||||
nvim__chan_set_detach({detach}) *nvim__chan_set_detach()*
|
||||
WARNING: This feature is experimental/unstable.
|
||||
|
||||
Sets the detach flag for the channel.
|
||||
|
||||
Detached channels do not trigger self-exit when they are closed.
|
||||
|
||||
Attributes: ~
|
||||
|RPC| only
|
||||
Since: 0.12.0
|
||||
|
||||
Parameters: ~
|
||||
• {detach} (`boolean`) New detach value for the channel.
|
||||
|
||||
nvim__complete_set({index}, {opts}) *nvim__complete_set()*
|
||||
WARNING: This feature is experimental/unstable.
|
||||
|
||||
|
||||
@@ -73,10 +73,10 @@ Restart Nvim
|
||||
|
||||
1. Stops Nvim using `:qall` (or |+cmd|, if given).
|
||||
2. Starts a new Nvim server using the same |v:argv| (except
|
||||
`-- [file…]` files), appended with `-c [command]`.
|
||||
3. Attaches the current UI to the new Nvim server. Other UIs
|
||||
(if any) will not reattach on restart (this may change in
|
||||
the future).
|
||||
`-- [file…]` files).
|
||||
3. Attaches the current UI to the new Nvim server and runs
|
||||
`[command]` on it. Other UIs (if any) will not reattach
|
||||
on restart (this may change in the future).
|
||||
|
||||
Example: discard changes and stop with `:qall!`, then restart: >
|
||||
:restart +qall!
|
||||
@@ -87,7 +87,7 @@ Restart Nvim
|
||||
<
|
||||
Note: Only works if the UI and server are on the same system.
|
||||
Note: If the UI hasn't implemented the "restart" UI event,
|
||||
this command is equivalent to `:qall` (or |+cmd|, if given).
|
||||
this command will lead to a dangling server process.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
Connect UI to a different server
|
||||
|
||||
@@ -270,7 +270,7 @@ void nvim_ui_detach(uint64_t channel_id, Error *err)
|
||||
/// Sends a "restart" UI event to the UI on the given channel.
|
||||
///
|
||||
/// @return false if there is no UI on the channel, otherwise true
|
||||
bool remote_ui_restart(uint64_t channel_id, Error *err)
|
||||
bool remote_ui_restart(uint64_t channel_id, const char *listen_addr, String command, Error *err)
|
||||
{
|
||||
RemoteUI *ui = get_ui_or_err(channel_id, err);
|
||||
if (!ui) {
|
||||
@@ -278,22 +278,11 @@ bool remote_ui_restart(uint64_t channel_id, Error *err)
|
||||
}
|
||||
|
||||
MAXSIZE_TEMP_ARRAY(args, 2);
|
||||
|
||||
ADD_C(args, CSTR_AS_OBJ(get_vim_var_str(VV_PROGPATH)));
|
||||
|
||||
Arena arena = ARENA_EMPTY;
|
||||
const list_T *l = get_vim_var_list(VV_ARGV);
|
||||
int argc = tv_list_len(l);
|
||||
assert(argc > 0);
|
||||
Array argv = arena_array(&arena, (size_t)argc + 1);
|
||||
TV_LIST_ITER_CONST(l, li, {
|
||||
const char *arg = tv_get_string(TV_LIST_ITEM_TV(li));
|
||||
ADD_C(argv, CSTR_AS_OBJ(arg));
|
||||
});
|
||||
ADD_C(args, ARRAY_OBJ(argv));
|
||||
ADD_C(args, CSTR_AS_OBJ(listen_addr));
|
||||
ADD_C(args, STRING_OBJ(command));
|
||||
|
||||
push_call(ui, "restart", args);
|
||||
arena_mem_free(arena_finish(&arena));
|
||||
ui_flush_buf(ui, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ void flush(void)
|
||||
FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL;
|
||||
void connect(Array args)
|
||||
FUNC_API_SINCE(14) FUNC_API_REMOTE_ONLY FUNC_API_REMOTE_IMPL FUNC_API_CLIENT_IMPL;
|
||||
void restart(String progpath, Array argv)
|
||||
void restart(String listen_addr, String command)
|
||||
FUNC_API_SINCE(14) FUNC_API_REMOTE_ONLY FUNC_API_REMOTE_IMPL FUNC_API_CLIENT_IMPL;
|
||||
void suspend(void)
|
||||
FUNC_API_SINCE(3);
|
||||
|
||||
@@ -1723,6 +1723,24 @@ void nvim_set_client_info(uint64_t channel_id, String name, Dict version, String
|
||||
rpc_set_client_info(channel_id, copy_dict(info, NULL));
|
||||
}
|
||||
|
||||
/// Sets the detach flag for the channel.
|
||||
///
|
||||
/// Detached channels do not trigger self-exit when they are closed.
|
||||
///
|
||||
/// @param channel_id
|
||||
/// @param detach New detach value for the channel.
|
||||
/// @param[out] err Error details, if any.
|
||||
void nvim__chan_set_detach(uint64_t channel_id, Boolean detach, Error *err)
|
||||
FUNC_API_SINCE(14) FUNC_API_REMOTE_ONLY
|
||||
{
|
||||
Channel *chan = find_channel(channel_id);
|
||||
VALIDATE(chan != NULL, "%s", e_invchan, {
|
||||
return;
|
||||
});
|
||||
|
||||
chan->detach = (bool)detach;
|
||||
}
|
||||
|
||||
/// Gets information about a channel.
|
||||
///
|
||||
/// See |nvim_list_uis()| for an example of how to get channel info.
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
#include "nvim/os/shell.h"
|
||||
#include "nvim/terminal.h"
|
||||
#include "nvim/types_defs.h"
|
||||
#include "nvim/ui_client.h"
|
||||
|
||||
#ifdef MSWIN
|
||||
# include "nvim/os/fs.h"
|
||||
@@ -781,6 +782,21 @@ static void channel_proc_exit_cb(Proc *proc, int status, void *data)
|
||||
terminal_close(&chan->term, status);
|
||||
}
|
||||
|
||||
// TODO(justinmk): figure out why rpc_close sometimes(??) isn't called.
|
||||
// Theories:
|
||||
// - EOF not received in receive_msgpack, then doesn't call chan_close_on_err().
|
||||
// - proc_close_handles not tickled by ui_client.c's LOOP_PROCESS_EVENTS?
|
||||
if (!exiting && ui_client_channel_id == chan->id) {
|
||||
// Need to call ui_client_attach_to_restarted_server() here as well, as sometimes
|
||||
// rpc_close_event() hasn't been called yet (also see comments above).
|
||||
ui_client_attach_to_restarted_server();
|
||||
if (ui_client_channel_id == chan->id) {
|
||||
// If the current embedded server has exited and no new server is started,
|
||||
// the client should exit with the same status.
|
||||
exit_on_closed_chan(status);
|
||||
}
|
||||
}
|
||||
|
||||
// If process did not exit, we only closed the handle of a detached process.
|
||||
bool exited = (status >= 0);
|
||||
if (exited && chan->on_exit.type != kCallbackNone) {
|
||||
|
||||
@@ -6409,7 +6409,7 @@ static void f_serverstop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||||
rettv->v_type = VAR_NUMBER;
|
||||
rettv->vval.v_number = 0;
|
||||
if (argvars[0].vval.v_string) {
|
||||
bool rv = server_stop(argvars[0].vval.v_string);
|
||||
bool rv = server_stop(argvars[0].vval.v_string, false);
|
||||
rettv->vval.v_number = (rv ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include <uv.h>
|
||||
|
||||
#include "klib/kvec.h"
|
||||
#include "nvim/channel.h"
|
||||
#include "nvim/event/libuv_proc.h"
|
||||
#include "nvim/event/loop.h"
|
||||
#include "nvim/event/multiqueue.h"
|
||||
@@ -467,26 +466,6 @@ static void on_proc_exit(Proc *proc)
|
||||
Loop *loop = proc->loop;
|
||||
ILOG("child exited: pid=%d status=%d" PRIu64, proc->pid, proc->status);
|
||||
|
||||
// TODO(justinmk): figure out why rpc_close sometimes(??) isn't called.
|
||||
// Theories:
|
||||
// - EOF not received in receive_msgpack, then doesn't call chan_close_on_err().
|
||||
// - proc_close_handles not tickled by ui_client.c's LOOP_PROCESS_EVENTS?
|
||||
if (ui_client_channel_id) {
|
||||
uint64_t server_chan_id = ui_client_channel_id;
|
||||
Channel *server_chan = find_channel(server_chan_id);
|
||||
if (server_chan != NULL && server_chan->streamtype == kChannelStreamProc
|
||||
&& proc == &server_chan->stream.proc) {
|
||||
// Need to call ui_client_may_restart_server() here as well, as sometimes
|
||||
// rpc_close_event() hasn't been called yet (also see comments above).
|
||||
ui_client_may_restart_server();
|
||||
if (ui_client_channel_id == server_chan_id) {
|
||||
// If the current embedded server has exited and no new server is started,
|
||||
// the client should exit with the same status.
|
||||
exit_on_closed_chan(proc->status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process has terminated, but there could still be data to be read from the
|
||||
// OS. We are still in the libuv loop, so we cannot call code that polls for
|
||||
// more data directly. Instead delay the reading after the libuv loop by
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
#include <limits.h>
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
@@ -40,8 +41,11 @@
|
||||
#include "nvim/eval/typval_defs.h"
|
||||
#include "nvim/eval/userfunc.h"
|
||||
#include "nvim/eval/vars.h"
|
||||
#include "nvim/eval_defs.h"
|
||||
#include "nvim/event/loop.h"
|
||||
#include "nvim/event/multiqueue.h"
|
||||
#include "nvim/event/proc.h"
|
||||
#include "nvim/event/socket.h"
|
||||
#include "nvim/ex_cmds.h"
|
||||
#include "nvim/ex_cmds2.h"
|
||||
#include "nvim/ex_cmds_defs.h"
|
||||
@@ -73,6 +77,8 @@
|
||||
#include "nvim/message.h"
|
||||
#include "nvim/mouse.h"
|
||||
#include "nvim/move.h"
|
||||
#include "nvim/msgpack_rpc/channel.h"
|
||||
#include "nvim/msgpack_rpc/server.h"
|
||||
#include "nvim/normal.h"
|
||||
#include "nvim/normal_defs.h"
|
||||
#include "nvim/option.h"
|
||||
@@ -4808,13 +4814,6 @@ void not_exiting(bool save_exiting)
|
||||
exiting = save_exiting;
|
||||
}
|
||||
|
||||
/// Call this function if we thought we were going to restart, but we won't
|
||||
/// (because of an error).
|
||||
void not_restarting(void)
|
||||
{
|
||||
restarting = false;
|
||||
}
|
||||
|
||||
bool before_quit_autocmds(win_T *wp, bool quit_all, bool forceit)
|
||||
{
|
||||
apply_autocmds(EVENT_QUITPRE, NULL, NULL, false, wp->w_buffer);
|
||||
@@ -4960,55 +4959,120 @@ static void ex_quitall(exarg_T *eap)
|
||||
|
||||
/// ":restart": restart the Nvim server (using ":qall!").
|
||||
/// ":restart +cmd": restart the Nvim server using ":cmd".
|
||||
/// ":restart +cmd <command>": restart the Nvim server using ":cmd" and add -c <command> to the new server.
|
||||
/// ":restart +cmd <command>": restart the Nvim server using ":cmd" and runs <command> in the new server.
|
||||
static void ex_restart(exarg_T *eap)
|
||||
{
|
||||
Error err = ERROR_INIT;
|
||||
const char *exepath = get_vim_var_str(VV_PROGPATH);
|
||||
const list_T *l = get_vim_var_list(VV_ARGV);
|
||||
int argc = tv_list_len(l);
|
||||
list_T *argv_cpy = tv_list_alloc(eap->arg ? argc + 2 : argc);
|
||||
|
||||
// Copy v:argv, skipping unwanted items.
|
||||
for (listitem_T *li = l != NULL ? l->lv_first : NULL; li != NULL; li = li->li_next) {
|
||||
char **argv = xcalloc((size_t)argc + 3, sizeof(char *));
|
||||
size_t i = 0;
|
||||
const char *listen_arg = NULL;
|
||||
#ifdef MSWIN // FIXME: --listen doesn't work on Windows and needs to be dropped
|
||||
# define HANDLE_LISTEN_ADDR li = next_li; continue
|
||||
#else
|
||||
# define HANDLE_LISTEN_ADDR listen_arg = addr
|
||||
#endif
|
||||
TV_LIST_ITER_CONST(l, li, {
|
||||
const char *arg = tv_get_string(TV_LIST_ITEM_TV(li));
|
||||
size_t arg_size = strlen(arg);
|
||||
assert(arg_size <= (size_t)SSIZE_MAX);
|
||||
|
||||
if (strequal(arg, "--embed") || strequal(arg, "--headless")) {
|
||||
continue; // Drop --embed/--headless: the client decides how to start+attach the server.
|
||||
} else if (strequal(arg, "-")) {
|
||||
continue; // Drop stdin ("-") argument.
|
||||
} else if (strequal(arg, "-s")) {
|
||||
// Drop "-s <scriptfile>": skip the scriptfile arg too.
|
||||
if (li->li_next != NULL) {
|
||||
li = li->li_next;
|
||||
}
|
||||
// Drop "-- [files…]". Usually isn't wanted. User can :mksession instead.
|
||||
if (i > 0 && strequal(arg, "--")) {
|
||||
break;
|
||||
}
|
||||
// Drop "-s <scriptfile>": skip the scriptfile arg too.
|
||||
if (i > 0 && strequal(arg, "-s")) {
|
||||
li = TV_LIST_ITEM_NEXT(l, li);
|
||||
continue;
|
||||
} else if (strequal(arg, "+:::")) {
|
||||
// The special placeholder "+:::" marks a previous :restart command.
|
||||
// Drop the `"+:::", "-c", "…"` triplet, to avoid "stacking" commands from previous :restart(s).
|
||||
listitem_T *next1 = li->li_next;
|
||||
if (next1 != NULL && strequal(tv_get_string(TV_LIST_ITEM_TV(next1)), "-c")) {
|
||||
listitem_T *next2 = next1->li_next;
|
||||
if (next2 != NULL) {
|
||||
li = next2;
|
||||
continue;
|
||||
}
|
||||
// The address after --listen may be in use by the current server.
|
||||
if (i > 0 && strequal(arg, "--listen")) {
|
||||
listitem_T *next_li = TV_LIST_ITEM_NEXT(l, li);
|
||||
if (next_li != NULL) {
|
||||
const char *addr = tv_get_string(TV_LIST_ITEM_TV(next_li));
|
||||
if (strstr(addr, ":") || strstr(addr, "/") || strstr(addr, "\\")) {
|
||||
HANDLE_LISTEN_ADDR;
|
||||
}
|
||||
}
|
||||
continue; // If the triplet is incomplete, just skip "+:::"
|
||||
} else if (strequal(arg, "--")) {
|
||||
break; // Drop "-- [files…]". Usually isn't wanted. User can :mksession instead.
|
||||
}
|
||||
// Replace `--embed` OR `--headless` with `--embed --headless` once.
|
||||
// Drop stdin ("-") argument.
|
||||
if (i == 0
|
||||
|| (!strequal(arg, "--embed") && !strequal(arg, "--headless") && !strequal(arg, "-"))) {
|
||||
argv[i++] = xstrdup(arg);
|
||||
if (i == 1) {
|
||||
argv[i++] = xstrdup("--embed");
|
||||
argv[i++] = xstrdup("--headless");
|
||||
}
|
||||
}
|
||||
});
|
||||
#undef HANDLE_LISTEN_ADDR
|
||||
|
||||
tv_list_append_string(argv_cpy, arg, (ssize_t)arg_size);
|
||||
bool server_stopped = false;
|
||||
if (listen_arg != NULL) {
|
||||
// Stop listening on the --listen address so that the new server can listen.
|
||||
server_stopped = server_stop(listen_arg, true);
|
||||
}
|
||||
// Append `"+:::", "-c", "<command>"` to end of v:argv.
|
||||
// The "+:::" item is a no-op placeholder to mark the :restart "<command>".
|
||||
if (eap->arg && eap->arg[0] != '\0') {
|
||||
tv_list_append_string(argv_cpy, S_LEN("+:::"));
|
||||
tv_list_append_string(argv_cpy, S_LEN("-c"));
|
||||
tv_list_append_string(argv_cpy, eap->arg, (ssize_t)strlen(eap->arg));
|
||||
|
||||
CallbackReader on_err = CALLBACK_READER_INIT;
|
||||
// This temporary bootstrap channel is closed intentionally once we obtain
|
||||
// the new server address. Don't forward child stderr to the current UI.
|
||||
on_err.fwd_err = false;
|
||||
bool detach = true;
|
||||
varnumber_T exit_status;
|
||||
|
||||
Channel *channel = channel_job_start(argv, exepath,
|
||||
CALLBACK_READER_INIT, on_err, CALLBACK_NONE,
|
||||
false, true, true, detach, kChannelStdinPipe,
|
||||
NULL, 0, 0, NULL, &exit_status);
|
||||
if (!channel) {
|
||||
emsg("cannot create a channel job");
|
||||
goto fail_1;
|
||||
}
|
||||
set_vim_var_list(VV_ARGV, argv_cpy);
|
||||
|
||||
// Prevent new server from self-exiting when the channel closes.
|
||||
ArenaMem result_mem = NULL;
|
||||
MAXSIZE_TEMP_ARRAY(detach_args, 1);
|
||||
ADD_C(detach_args, BOOLEAN_OBJ(true));
|
||||
rpc_send_call(channel->id, "nvim__chan_set_detach", detach_args, &result_mem, &err);
|
||||
if (ERROR_SET(&err)) {
|
||||
emsg(err.msg);
|
||||
api_clear_error(&err);
|
||||
arena_mem_free(result_mem);
|
||||
goto fail_2;
|
||||
}
|
||||
arena_mem_free(result_mem);
|
||||
|
||||
// Get new server's listen address.
|
||||
MAXSIZE_TEMP_ARRAY(servername_args, 1);
|
||||
ADD_C(servername_args, CSTR_AS_OBJ("servername"));
|
||||
result_mem = NULL;
|
||||
Object result = rpc_send_call(channel->id, "nvim_get_vvar", servername_args, &result_mem, &err);
|
||||
if (ERROR_SET(&err)) {
|
||||
emsg(err.msg);
|
||||
api_clear_error(&err);
|
||||
arena_mem_free(result_mem);
|
||||
goto fail_2;
|
||||
}
|
||||
if (result.type != kObjectTypeString || result.data.string.size == 0) {
|
||||
arena_mem_free(result_mem);
|
||||
emsg("restart failed: could not get listen address from new server");
|
||||
goto fail_2;
|
||||
}
|
||||
char *listen_addr = xmemdupz(result.data.string.data, result.data.string.size);
|
||||
arena_mem_free(result_mem);
|
||||
|
||||
// Send restart event with new listen address to current UI.
|
||||
if (!remote_ui_restart(current_ui, listen_addr, cstr_as_string(eap->arg), &err)) {
|
||||
if (ERROR_SET(&err)) {
|
||||
ELOG("%s", err.msg); // UI disappeared already?
|
||||
api_clear_error(&err);
|
||||
}
|
||||
xfree(listen_addr);
|
||||
goto fail_2;
|
||||
}
|
||||
xfree(listen_addr);
|
||||
|
||||
char *quit_cmd = (eap->do_ecmd_cmd) ? eap->do_ecmd_cmd : "qall";
|
||||
char *quit_cmd_copy = NULL;
|
||||
@@ -5018,20 +5082,27 @@ static void ex_restart(exarg_T *eap)
|
||||
quit_cmd_copy = concat_str("confirm ", quit_cmd);
|
||||
quit_cmd = quit_cmd_copy;
|
||||
}
|
||||
|
||||
Error err = ERROR_INIT;
|
||||
restarting = true;
|
||||
nvim_command(cstr_as_string(quit_cmd), &err);
|
||||
xfree(quit_cmd_copy);
|
||||
|
||||
if (ERROR_SET(&err)) {
|
||||
emsg(err.msg); // Could not exit
|
||||
api_clear_error(&err);
|
||||
not_restarting();
|
||||
return;
|
||||
}
|
||||
if (!exiting) {
|
||||
} else if (!exiting) {
|
||||
emsg("restart failed: +cmd did not quit the server");
|
||||
not_restarting();
|
||||
}
|
||||
|
||||
fail_2:
|
||||
// Kill the new nvim server.
|
||||
proc_stop(&channel->stream.proc);
|
||||
if (proc_wait(&channel->stream.proc, -1, NULL) < 0) {
|
||||
emsg("killing new nvim server failed");
|
||||
}
|
||||
|
||||
fail_1:
|
||||
// Restart listening on the --listen address.
|
||||
if (server_stopped && server_start(listen_arg) != 0) {
|
||||
semsg("couldn't resume listening on %s", listen_arg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -419,8 +419,6 @@ EXTERN int sc_col; // column for shown command
|
||||
EXTERN int starting INIT( = NO_SCREEN);
|
||||
// Planning to exit. Might keep running if there is a changed buffer.
|
||||
EXTERN bool exiting INIT( = false);
|
||||
// Planning to restart.
|
||||
EXTERN bool restarting INIT( = false);
|
||||
// Internal value of v:dying
|
||||
EXTERN int v_dying INIT( = 0);
|
||||
// Is stdin a terminal?
|
||||
|
||||
@@ -861,17 +861,6 @@ void getout(int exitval)
|
||||
ui_call_set_title(cstr_as_string(p_titleold));
|
||||
}
|
||||
|
||||
if (restarting) {
|
||||
Error err = ERROR_INIT;
|
||||
if (!remote_ui_restart(current_ui, &err)) {
|
||||
if (ERROR_SET(&err)) {
|
||||
ELOG("%s", err.msg); // UI disappeared already?
|
||||
api_clear_error(&err);
|
||||
}
|
||||
}
|
||||
restarting = false;
|
||||
}
|
||||
|
||||
if (garbage_collect_at_exit) {
|
||||
garbage_collect(false);
|
||||
}
|
||||
|
||||
@@ -500,9 +500,9 @@ static void rpc_close_event(void **argv)
|
||||
// Avoid hanging when there are no other UIs and a prompt is triggered on exit.
|
||||
remote_ui_disconnect(channel->id, NULL, false);
|
||||
} else {
|
||||
ui_client_may_restart_server();
|
||||
ui_client_attach_to_restarted_server();
|
||||
if (ui_client_channel_id != channel->id) {
|
||||
// A new server has been started. Don't exit.
|
||||
// Attached to new server. Don't exit.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,7 +214,7 @@ int server_start(const char *addr)
|
||||
/// Stops listening on the address specified by `endpoint`.
|
||||
///
|
||||
/// @param endpoint Address of the server.
|
||||
bool server_stop(char *endpoint)
|
||||
bool server_stop(const char *endpoint, bool keep_vservername)
|
||||
{
|
||||
SocketWatcher *watcher;
|
||||
bool watcher_found = false;
|
||||
@@ -247,7 +247,7 @@ bool server_stop(char *endpoint)
|
||||
watchers.ga_len--;
|
||||
|
||||
// Bump v:servername to the next available server, if any.
|
||||
if (strequal(addr, get_vim_var_str(VV_SEND_SERVER))) {
|
||||
if (!keep_vservername && strequal(addr, get_vim_var_str(VV_SEND_SERVER))) {
|
||||
set_vservername(&watchers);
|
||||
}
|
||||
|
||||
|
||||
@@ -169,7 +169,9 @@ void ui_client_run(bool remote_ui)
|
||||
|
||||
// os_exit() will be invoked when the client channel detaches
|
||||
while (true) {
|
||||
LOOP_PROCESS_EVENTS(&main_loop, resize_events, -1);
|
||||
// Need to process main_loop.events,
|
||||
// otherwise channels closed due to server restart are never freed.
|
||||
LOOP_PROCESS_EVENTS(&main_loop, main_loop.events, -1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,57 +325,55 @@ void ui_client_event_restart(Array args)
|
||||
// NB: don't send nvim_ui_detach to server, as it may have already exited.
|
||||
// ui_client_detach();
|
||||
|
||||
// Save the arguments for ui_client_may_restart_server() later.
|
||||
// Save the arguments for ui_client_attach_to_restarted_server() later.
|
||||
api_free_array(restart_args);
|
||||
restart_args = copy_array(args, NULL);
|
||||
restart_pending = true;
|
||||
}
|
||||
|
||||
/// Called when the current server has exited.
|
||||
void ui_client_may_restart_server(void)
|
||||
void ui_client_attach_to_restarted_server(void)
|
||||
{
|
||||
if (!restart_pending) {
|
||||
return;
|
||||
}
|
||||
|
||||
restart_pending = false;
|
||||
|
||||
size_t argc;
|
||||
char **argv = NULL;
|
||||
if (restart_args.size < 2
|
||||
|| restart_args.items[0].type != kObjectTypeString
|
||||
|| restart_args.items[1].type != kObjectTypeArray
|
||||
|| (argc = restart_args.items[1].data.array.size) < 1) {
|
||||
|| restart_args.items[1].type != kObjectTypeString) {
|
||||
ELOG("Error handling ui event 'restart'");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
// 1. Get executable path and command-line arguments.
|
||||
const char *exepath = restart_args.items[0].data.string.data;
|
||||
argv = xcalloc(argc + 1, sizeof(char *));
|
||||
for (size_t i = 0; i < argc; i++) {
|
||||
if (restart_args.items[1].data.array.items[i].type == kObjectTypeString) {
|
||||
argv[i] = restart_args.items[1].data.array.items[i].data.string.data;
|
||||
}
|
||||
if (argv[i] == NULL) {
|
||||
argv[i] = "";
|
||||
}
|
||||
}
|
||||
char *listen_addr = restart_args.items[0].data.string.data;
|
||||
bool is_tcp = socket_address_tcp_host_end(listen_addr) != NULL;
|
||||
const char *err = "";
|
||||
uint64_t chan_id = channel_connect(is_tcp, listen_addr, true, CALLBACK_READER_INIT, 50, &err);
|
||||
|
||||
// 2. Start a new `nvim --embed` server.
|
||||
uint64_t rv = ui_client_start_server(exepath, argc, argv);
|
||||
if (!rv) {
|
||||
ELOG("failed to start nvim server");
|
||||
if (!strequal(err, "")) {
|
||||
ELOG("cannot connect to server %s: %s", listen_addr, err);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
// 3. Client-side server re-attach.
|
||||
ui_client_channel_id = rv;
|
||||
ui_client_is_remote = false;
|
||||
// Client-side server re-attach.
|
||||
ui_client_channel_id = chan_id;
|
||||
ui_client_is_remote = is_tcp;
|
||||
ui_client_attach(tui_width, tui_height, tui_term, tui_rgb);
|
||||
|
||||
ILOG("restarted server id=%" PRId64, rv);
|
||||
String command = restart_args.items[1].data.string;
|
||||
if (command.size > 0) {
|
||||
MAXSIZE_TEMP_ARRAY(cmd_args, 1);
|
||||
ADD_C(cmd_args, STRING_OBJ(command));
|
||||
// TODO(justinmk): if reattaching multiple UIs, only 1 UI should do this command.
|
||||
if (!rpc_send_event(ui_client_channel_id, "nvim_command", cmd_args)) {
|
||||
ELOG("cannot execute '%s'", command.data);
|
||||
}
|
||||
}
|
||||
|
||||
ILOG("restarted server address=%s id=%" PRId64, listen_addr, chan_id);
|
||||
cleanup:
|
||||
xfree(argv);
|
||||
api_free_array(restart_args);
|
||||
restart_args = (Array)ARRAY_DICT_INIT;
|
||||
}
|
||||
|
||||
@@ -1498,7 +1498,17 @@ describe('user config init', function()
|
||||
|
||||
-- a total of 2 exrc files are executed
|
||||
feed(':echo g:exrc_count<CR>')
|
||||
screen:expect({ any = '2' })
|
||||
screen:expect([[
|
||||
^{MATCH: +}|
|
||||
~{MATCH: +}|*4
|
||||
[No Name]{MATCH: +}0,0-1{MATCH: +}All|
|
||||
2{MATCH: +}|
|
||||
-- TERMINAL --{MATCH: +}|
|
||||
]])
|
||||
|
||||
-- The server is now detached and needs to be quit explicitly.
|
||||
feed(':qall!<CR>')
|
||||
screen:expect({ any = vim.pesc('[Process exited 0]') })
|
||||
end)
|
||||
end)
|
||||
|
||||
|
||||
@@ -233,8 +233,10 @@ describe('TUI :restart', function()
|
||||
'colorscheme vim',
|
||||
'--cmd',
|
||||
nvim_set .. ' notermguicolors laststatus=2 background=dark',
|
||||
'--cmd',
|
||||
'echo getpid()',
|
||||
-- XXX: New server starts before the UI connects to it.
|
||||
-- So checking screen state for this pid is not possible.
|
||||
-- '--cmd',
|
||||
-- 'echo getpid()',
|
||||
}, { env = env_notermguicolors })
|
||||
|
||||
local function screen_expect(s)
|
||||
@@ -251,16 +253,18 @@ describe('TUI :restart', function()
|
||||
^ |
|
||||
{100:~ }|*3
|
||||
{3:[No Name] }|
|
||||
{MATCH:%d+ +}|
|
||||
|
|
||||
{5:-- TERMINAL --} |
|
||||
]]
|
||||
screen_expect(s0)
|
||||
screen:expect(s0)
|
||||
assert_no_gui_running()
|
||||
|
||||
local server_session = n.connect(server_pipe)
|
||||
local _, server_pid = server_session:request('nvim_call_function', 'getpid', {})
|
||||
|
||||
local function assert_new_pid()
|
||||
if is_os('win') then
|
||||
return -- FIXME
|
||||
end
|
||||
server_session:close()
|
||||
server_session = n.connect(server_pipe)
|
||||
local _, new_pid = server_session:request('nvim_call_function', 'getpid', {})
|
||||
@@ -268,29 +272,28 @@ describe('TUI :restart', function()
|
||||
server_pid = new_pid
|
||||
end
|
||||
|
||||
--- XXX: No longer using -c <command> during new server startup.
|
||||
--- Gets the last `argn` items in v:argv as a joined string.
|
||||
local function get_argv(argn)
|
||||
local argv = ({ server_session:request('nvim_eval', 'v:argv') })[2] --[[@type table]]
|
||||
return table.concat(argv, ' ', #argv - argn, #argv)
|
||||
end
|
||||
-- local function get_argv(argn)
|
||||
-- local argv = ({ server_session:request('nvim_eval', 'v:argv') })[2] --[[@type table]]
|
||||
-- return table.concat(argv, ' ', #argv - argn, #argv)
|
||||
-- end
|
||||
|
||||
local s1 = [[
|
||||
|
|
||||
^Hello1 |
|
||||
{100:~ }|*2
|
||||
{3:[No Name] [+] }|
|
||||
{MATCH:%d+ +}|
|
||||
|
|
||||
{5:-- TERMINAL --} |
|
||||
]]
|
||||
|
||||
tt.feed_data(':set nomodified\013')
|
||||
-- Command is added as "-c" arg.
|
||||
-- Command is run on new server.
|
||||
tt.feed_data(":restart put ='Hello1'\013")
|
||||
screen_expect(s1)
|
||||
tt.feed_data('\013')
|
||||
assert_new_pid()
|
||||
assert_no_gui_running()
|
||||
eq("--cmd echo getpid() +::: -c put ='Hello1'", get_argv(4))
|
||||
|
||||
-- Complex command following +cmd.
|
||||
tt.feed_data(":restart +qall! put ='Hello2' | put ='World2'\013")
|
||||
@@ -300,12 +303,11 @@ describe('TUI :restart', function()
|
||||
^World2 |
|
||||
{100:~ }|
|
||||
{3:[No Name] [+] }|
|
||||
{MATCH:%d+ +}|
|
||||
|
|
||||
{5:-- TERMINAL --} |
|
||||
]])
|
||||
assert_new_pid()
|
||||
assert_no_gui_running()
|
||||
eq("--cmd echo getpid() +::: -c put ='Hello2' | put ='World2'", get_argv(4))
|
||||
|
||||
-- Check ":restart" on an unmodified buffer.
|
||||
tt.feed_data(':set nomodified\013')
|
||||
@@ -319,7 +321,6 @@ describe('TUI :restart', function()
|
||||
screen_expect(s0)
|
||||
assert_new_pid()
|
||||
assert_no_gui_running()
|
||||
eq('--cmd echo getpid()', get_argv(1))
|
||||
|
||||
-- Check ":restart +echo" cannot restart server.
|
||||
tt.feed_data(':restart +echo\013')
|
||||
@@ -357,7 +358,6 @@ describe('TUI :restart', function()
|
||||
screen:expect({ any = '%^Hello3' })
|
||||
assert_new_pid()
|
||||
assert_no_gui_running()
|
||||
eq("--cmd echo getpid() +::: -c put ='Hello3'", get_argv(4))
|
||||
|
||||
-- Check ":confirm restart +echo" correctly ignores ":confirm"
|
||||
tt.feed_data(':confirm restart +echo\013')
|
||||
@@ -391,16 +391,20 @@ describe('TUI :restart', function()
|
||||
]])
|
||||
|
||||
--- Check that ":restart" uses the updated size after terminal resize.
|
||||
tt.feed_data(':restart\013')
|
||||
tt.feed_data(':restart echo "restarted"\013')
|
||||
screen_expect([[
|
||||
^ |
|
||||
{100:~ }|*2
|
||||
{3:[No Name] }|
|
||||
{MATCH:%d+ +}|
|
||||
restarted |
|
||||
{5:-- TERMINAL --} |
|
||||
]])
|
||||
assert_new_pid()
|
||||
assert_no_gui_running()
|
||||
|
||||
-- The server is now detached and needs to be quit explicitly.
|
||||
feed_data(':qall!\r')
|
||||
screen:expect({ any = vim.pesc('[Process exited 0]') })
|
||||
end)
|
||||
|
||||
it('drops "-" and "-- [files…]" from v:argv #34417', function()
|
||||
@@ -457,9 +461,14 @@ describe('TUI :restart', function()
|
||||
|
||||
eq({ true, false }, { server_session:request('nvim_eval', expr) })
|
||||
eq({ true, false }, { server_session:request('nvim_eval', has_s) })
|
||||
local argv = ({ server_session:request('nvim_eval', 'v:argv') })[2] --[[@type table]]
|
||||
eq(13, #argv)
|
||||
eq("-c put='foo'", table.concat(argv, ' ', #argv - 1, #argv))
|
||||
|
||||
-- local argv = ({ server_session:request('nvim_eval', 'v:argv') })[2] --[[@type table]]
|
||||
-- eq(13, #argv)
|
||||
-- eq("-c put='foo'", table.concat(argv, ' ', #argv - 1, #argv))
|
||||
|
||||
-- The server is now detached and needs to be quit explicitly.
|
||||
feed_data(':qall!\r')
|
||||
screen:expect({ any = vim.pesc('[Process exited 0]') })
|
||||
end)
|
||||
end)
|
||||
|
||||
@@ -4169,8 +4178,7 @@ describe('TUI client', function()
|
||||
it(':restart works when connecting to remote instance (with its own TUI)', function()
|
||||
local _, screen_server, screen_client = start_tui_and_remote_client()
|
||||
|
||||
-- Run :restart on the remote client.
|
||||
-- The remote client should start a new server while the original one should exit.
|
||||
-- The remote client should attach to the new server.
|
||||
feed_data(':restart +qall!\n')
|
||||
screen_client:expect([[
|
||||
^ |
|
||||
@@ -4267,8 +4275,7 @@ describe('TUI client', function()
|
||||
it(':restart works when connecting to remote instance (--headless)', function()
|
||||
local _, server_pipe, screen_client = start_headless_server_and_client(false)
|
||||
|
||||
-- Run :restart on the client.
|
||||
-- The client should start a new server while the original server should exit.
|
||||
-- The client should attach to the new server and the original server should exit.
|
||||
feed_data(':restart +qall!\n')
|
||||
screen_client:expect([[
|
||||
^ |
|
||||
|
||||
Reference in New Issue
Block a user