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:
Sathya Pramodh
2026-03-29 00:55:09 +05:30
committed by GitHub
parent 64d55b74d8
commit d5516daf12
17 changed files with 260 additions and 171 deletions

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

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

View File

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

View File

@@ -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.

View File

@@ -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) {

View File

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

View File

@@ -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

View File

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

View File

@@ -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?

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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([[
^ |