feat: make :restart work for remote UI (#34354)

This commit is contained in:
zeertzjq
2025-06-08 06:10:34 +08:00
committed by GitHub
parent faae1e72a2
commit 52c61d9690
11 changed files with 202 additions and 113 deletions

View File

@@ -78,13 +78,13 @@ Restart Nvim
This fails when changes have been made and Vim refuses to This fails when changes have been made and Vim refuses to
|abandon| the current buffer. |abandon| the current buffer.
Note: This only works if the UI and server are on the same Note: If the current UI hasn't implemented the "restart" UI
system. event, this command is equivalent to `:qall`.
Note: Only works if the UI and server are on the same system.
Note: Not supported on Windows yet. Note: Not supported on Windows yet.
:restart! :restart!
Force restarts the embedded server irrespective of unsaved Force restarts the Nvim server, abandoning unsaved buffers.
buffers.
------------------------------------------------------------------------------ ------------------------------------------------------------------------------
GUI commands GUI commands

View File

@@ -248,10 +248,13 @@ the editor.
Indicates to the UI that it must stop rendering the cursor. This event 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. is misnamed and does not actually have anything to do with busyness.
["restart"] ~ ["restart", progpath, argv] ~
|:restart| command was used. The UI should stop-and-restart the Nvim |:restart| command has been used and the Nvim server is about to exit.
server using the same startup arguments |v:argv|, and reattach to the The UI should wait for the server to exit, and then start a new server
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.
["suspend"] ~ ["suspend"] ~
|:suspend| command or |CTRL-Z| mapping is used. A terminal client (or |:suspend| command or |CTRL-Z| mapping is used. A terminal client (or

View File

@@ -7,6 +7,7 @@
#include <string.h> #include <string.h>
#include "klib/kvec.h" #include "klib/kvec.h"
#include "nvim/api/private/converter.h"
#include "nvim/api/private/defs.h" #include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h" #include "nvim/api/private/helpers.h"
#include "nvim/api/private/validate.h" #include "nvim/api/private/validate.h"
@@ -17,6 +18,7 @@
#include "nvim/channel.h" #include "nvim/channel.h"
#include "nvim/channel_defs.h" #include "nvim/channel_defs.h"
#include "nvim/eval.h" #include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/event/defs.h" #include "nvim/event/defs.h"
#include "nvim/event/loop.h" #include "nvim/event/loop.h"
#include "nvim/event/multiqueue.h" #include "nvim/event/multiqueue.h"
@@ -239,6 +241,47 @@ void nvim_ui_detach(uint64_t channel_id, Error *err)
remote_ui_disconnect(channel_id); remote_ui_disconnect(channel_id);
} }
/// 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)
{
RemoteUI *ui = pmap_get(uint64_t)(&connected_uis, channel_id);
if (!ui) {
api_set_error(err, kErrorTypeException,
"UI not attached to channel: %" PRId64, channel_id);
return false;
}
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);
bool had_minmin = false;
TV_LIST_ITER_CONST(l, li, {
const char *arg = tv_get_string(TV_LIST_ITEM_TV(li));
if (argv.size > 0 && !had_minmin && strequal(arg, "--")) {
had_minmin = true;
}
// Exclude --embed/--headless from `argv`, as the client may start the server in a
// different way than how the server was originally started.
if (argv.size == 0 || had_minmin
|| (!strequal(arg, "--embed") && !strequal(arg, "--headless"))) {
ADD_C(argv, CSTR_AS_OBJ(arg));
}
});
ADD_C(args, ARRAY_OBJ(argv));
push_call(ui, "restart", args);
arena_mem_free(arena_finish(&arena));
return true;
}
// TODO(bfredl): use me to detach a specific ui from the server // TODO(bfredl): use me to detach a specific ui from the server
void remote_ui_stop(RemoteUI *ui) void remote_ui_stop(RemoteUI *ui)
{ {

View File

@@ -29,8 +29,8 @@ void visual_bell(void)
FUNC_API_SINCE(3); FUNC_API_SINCE(3);
void flush(void) void flush(void)
FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL; FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL;
void restart(void) void restart(String progpath, Array argv)
FUNC_API_SINCE(14) FUNC_API_REMOTE_ONLY FUNC_API_CLIENT_IMPL; FUNC_API_SINCE(14) FUNC_API_REMOTE_ONLY FUNC_API_REMOTE_IMPL FUNC_API_CLIENT_IMPL;
void suspend(void) void suspend(void)
FUNC_API_SINCE(3); FUNC_API_SINCE(3);
void set_title(String title) void set_title(String title)

View File

@@ -2184,13 +2184,13 @@ M.cmds = {
command = 'quitall', command = 'quitall',
flags = bit.bor(BANG, TRLBAR), flags = bit.bor(BANG, TRLBAR),
addr_type = 'ADDR_NONE', addr_type = 'ADDR_NONE',
func = 'ex_quit_all', func = 'ex_quitall_or_restart',
}, },
{ {
command = 'qall', command = 'qall',
flags = bit.bor(BANG, TRLBAR, CMDWIN, LOCK_OK), flags = bit.bor(BANG, TRLBAR, CMDWIN, LOCK_OK),
addr_type = 'ADDR_NONE', addr_type = 'ADDR_NONE',
func = 'ex_quit_all', func = 'ex_quitall_or_restart',
}, },
{ {
command = 'read', command = 'read',
@@ -2250,7 +2250,7 @@ M.cmds = {
command = 'restart', command = 'restart',
flags = bit.bor(BANG, TRLBAR), flags = bit.bor(BANG, TRLBAR),
addr_type = 'ADDR_NONE', addr_type = 'ADDR_NONE',
func = 'ex_restart', func = 'ex_quitall_or_restart',
}, },
{ {
command = 'retab', command = 'retab',

View File

@@ -4828,16 +4828,23 @@ int before_quit_all(exarg_T *eap)
} }
/// ":qall": try to quit all windows /// ":qall": try to quit all windows
static void ex_quit_all(exarg_T *eap) /// ":restart": restart the Nvim server
static void ex_quitall_or_restart(exarg_T *eap)
{ {
if (before_quit_all(eap) == FAIL) { if (before_quit_all(eap) == FAIL) {
return; return;
} }
exiting = true; exiting = true;
if (eap->forceit || !check_changed_any(false, false)) { Error err = ERROR_INIT;
if ((eap->forceit || !check_changed_any(false, false))
&& (eap->cmdidx != CMD_restart || remote_ui_restart(current_ui, &err))) {
getout(0); getout(0);
} }
not_exiting(); not_exiting();
if (ERROR_SET(&err)) {
emsg(err.msg); // UI disappeared already?
api_clear_error(&err);
}
} }
/// ":close": close current window, unless it is the last one /// ":close": close current window, unless it is the last one
@@ -5592,31 +5599,6 @@ static void ex_detach(exarg_T *eap)
} }
} }
/// ":restart" command
/// Restarts the server by delegating the work to the UI.
static void ex_restart(exarg_T *eap)
{
bool forceit = eap && eap->forceit;
win_T *wp = curwin;
// If any buffer is changed and not saved, we cannot restart.
// But if called using bang (!), we will force restart.
if ((!buf_hide(wp->w_buffer)
&& check_changed(wp->w_buffer, (p_awa ? CCGD_AW : 0)
| (forceit ? CCGD_FORCEIT : 0)
| CCGD_EXCMD))
|| check_more(true, forceit) == FAIL
|| check_changed_any(forceit, true)) {
if (!forceit) {
return;
}
}
// Send an ui restart event.
ui_call_restart();
}
/// ":mode": /// ":mode":
/// If no argument given, get the screen size and redraw. /// If no argument given, get the screen size and redraw.
static void ex_mode(exarg_T *eap) static void ex_mode(exarg_T *eap)

View File

@@ -334,7 +334,8 @@ int main(int argc, char **argv)
if (use_builtin_ui && !remote_ui) { if (use_builtin_ui && !remote_ui) {
ui_client_forward_stdin = !stdin_isatty; ui_client_forward_stdin = !stdin_isatty;
uint64_t rv = ui_client_start_server(params.argc, params.argv); uint64_t rv = ui_client_start_server(get_vim_var_str(VV_PROGPATH),
(size_t)params.argc, params.argv);
if (!rv) { if (!rv) {
fprintf(stderr, "Failed to start Nvim server!\n"); fprintf(stderr, "Failed to start Nvim server!\n");
os_exit(1); os_exit(1);

View File

@@ -499,8 +499,13 @@ static void rpc_close_event(void **argv)
if (!is_ui_client) { if (!is_ui_client) {
// Avoid hanging when there are no other UIs and a prompt is triggered on exit. // Avoid hanging when there are no other UIs and a prompt is triggered on exit.
remote_ui_disconnect(channel->id); remote_ui_disconnect(channel->id);
} else {
ui_client_may_restart_server();
if (ui_client_channel_id != channel->id) {
// A new server has been started. Don't exit.
return;
}
} }
if (!channel->detach) { if (!channel->detach) {
exit_on_closed_chan(channel->exit_status == -1 ? 0 : channel->exit_status); exit_on_closed_chan(channel->exit_status == -1 ? 0 : channel->exit_status);
} }

View File

@@ -7,12 +7,9 @@
#include "nvim/api/keysets_defs.h" #include "nvim/api/keysets_defs.h"
#include "nvim/api/private/defs.h" #include "nvim/api/private/defs.h"
#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h" #include "nvim/api/private/helpers.h"
#include "nvim/channel.h" #include "nvim/channel.h"
#include "nvim/channel_defs.h" #include "nvim/channel_defs.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h" #include "nvim/eval/typval_defs.h"
#include "nvim/event/multiqueue.h" #include "nvim/event/multiqueue.h"
#include "nvim/globals.h" #include "nvim/globals.h"
@@ -25,7 +22,6 @@
#include "nvim/msgpack_rpc/channel.h" #include "nvim/msgpack_rpc/channel.h"
#include "nvim/msgpack_rpc/channel_defs.h" #include "nvim/msgpack_rpc/channel_defs.h"
#include "nvim/os/os.h" #include "nvim/os/os.h"
#include "nvim/os/os_defs.h"
#include "nvim/profile.h" #include "nvim/profile.h"
#include "nvim/tui/tui.h" #include "nvim/tui/tui.h"
#include "nvim/tui/tui_defs.h" #include "nvim/tui/tui_defs.h"
@@ -51,14 +47,13 @@ static bool ui_client_is_remote = false;
#endif #endif
// uncrustify:on // uncrustify:on
uint64_t ui_client_start_server(int argc, char **argv) uint64_t ui_client_start_server(const char *exepath, size_t argc, char **argv)
{ {
varnumber_T exit_status; char **args = xmalloc((2 + argc) * sizeof(char *));
char **args = xmalloc(((size_t)(2 + argc)) * sizeof(char *));
int args_idx = 0; int args_idx = 0;
args[args_idx++] = xstrdup(argv[0]); args[args_idx++] = xstrdup(argv[0]);
args[args_idx++] = xstrdup("--embed"); args[args_idx++] = xstrdup("--embed");
for (int i = 1; i < argc; i++) { for (size_t i = 1; i < argc; i++) {
args[args_idx++] = xstrdup(argv[i]); args[args_idx++] = xstrdup(argv[i]);
} }
args[args_idx++] = NULL; args[args_idx++] = NULL;
@@ -72,7 +67,8 @@ uint64_t ui_client_start_server(int argc, char **argv)
#else #else
bool detach = true; bool detach = true;
#endif #endif
Channel *channel = channel_job_start(args, get_vim_var_str(VV_PROGPATH), varnumber_T exit_status;
Channel *channel = channel_job_start(args, exepath,
CALLBACK_READER_INIT, on_err, CALLBACK_NONE, CALLBACK_READER_INIT, on_err, CALLBACK_NONE,
false, true, true, detach, kChannelStdinPipe, false, true, true, detach, kChannelStdinPipe,
NULL, 0, 0, NULL, &exit_status); NULL, 0, 0, NULL, &exit_status);
@@ -287,57 +283,69 @@ void ui_client_event_raw_line(GridLineEvent *g)
(const schar_T *)grid_line_buf_char, grid_line_buf_attr); (const schar_T *)grid_line_buf_char, grid_line_buf_attr);
} }
/// Restarts the embedded server without killing the UI. /// When a "restart" UI event is received, its arguments are saved here when
/// waiting for the server to exit.
static Array restart_args = ARRAY_DICT_INIT;
static bool restart_pending = false;
void ui_client_event_restart(Array args) void ui_client_event_restart(Array args)
{ {
// 1. Client-side server detach. // NB: don't send nvim_ui_detach to server, as it may have already exited.
ui_client_detach(); // ui_client_detach();
// 2. Close ui client channel (auto kills the `nvim --embed` server due to self-exit). // Save the arguments for ui_client_may_restart_server() later.
const char *error; api_free_array(restart_args);
bool success = channel_close(ui_client_channel_id, kChannelPartAll, &error); restart_args = copy_array(args, NULL);
if (!success) { restart_pending = true;
ELOG("%s", error);
return;
} }
// 3. Get v:argv. /// Called when the current server has exited.
typval_T *tv = get_vim_var_tv(VV_ARGV); void ui_client_may_restart_server(void)
if (tv->v_type != VAR_LIST || tv->vval.v_list == NULL) { {
ELOG("failed to get vim var typval"); if (!restart_pending) {
return; return;
} }
list_T *l = tv->vval.v_list; restart_pending = false;
int argc = tv_list_len(l);
// Assert to be positive for safe conversion to size_t. size_t argc;
assert(argc > 0); 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) {
ELOG("Error handling ui event 'restart'");
goto cleanup;
}
char **argv = xmalloc(sizeof(char *) * ((size_t)argc + 1)); // 1. Get executable path and command-line arguments.
listitem_T *li = tv_list_first(l); const char *exepath = restart_args.items[0].data.string.data;
for (int i = 0; i < argc && li != NULL; i++, li = TV_LIST_ITEM_NEXT(l, li)) { argv = xcalloc(argc + 1, sizeof(char *));
if (TV_LIST_ITEM_TV(li)->v_type == VAR_STRING && TV_LIST_ITEM_TV(li)->vval.v_string != NULL) { for (size_t i = 0; i < argc; i++) {
argv[i] = TV_LIST_ITEM_TV(li)->vval.v_string; if (restart_args.items[1].data.array.items[i].type == kObjectTypeString) {
} else { argv[i] = restart_args.items[1].data.array.items[i].data.string.data;
}
if (argv[i] == NULL) {
argv[i] = ""; argv[i] = "";
} }
} }
argv[argc] = NULL;
// 4. Start a new `nvim --embed` server. // 2. Start a new `nvim --embed` server.
uint64_t rv = ui_client_start_server(argc, argv); uint64_t rv = ui_client_start_server(exepath, argc, argv);
if (!rv) { if (!rv) {
ELOG("failed to start nvim server"); ELOG("failed to start nvim server");
goto cleanup; goto cleanup;
} }
// 5. Client-side server re-attach. // 3. Client-side server re-attach.
ui_client_channel_id = rv; ui_client_channel_id = rv;
ui_client_is_remote = false;
ui_client_attach(tui_width, tui_height, tui_term, tui_rgb); ui_client_attach(tui_width, tui_height, tui_term, tui_rgb);
ILOG("restarted server id=%" PRId64, rv); ILOG("restarted server id=%" PRId64, rv);
cleanup: cleanup:
xfree(argv); xfree(argv);
api_free_array(restart_args);
restart_args = (Array)ARRAY_DICT_INIT;
} }
#ifdef EXITFREE #ifdef EXITFREE

View File

@@ -20,9 +20,6 @@ EXTERN uint64_t ui_client_channel_id INIT( = 0);
// exit status from embedded nvim process // exit status from embedded nvim process
EXTERN int ui_client_exit_status INIT( = 0); EXTERN int ui_client_exit_status INIT( = 0);
// TODO(bfredl): the current structure for how tui and ui_client.c communicate is a bit awkward.
// This will be restructured as part of The UI Devirtualization Project.
/// Whether ui client has sent nvim_ui_attach yet /// Whether ui client has sent nvim_ui_attach yet
EXTERN bool ui_client_attached INIT( = false); EXTERN bool ui_client_attached INIT( = false);

View File

@@ -168,11 +168,14 @@ describe('TUI :restart', function()
n.check_close() n.check_close()
end) end)
local server_pipe = new_pipename()
local screen = tt.setup_child_nvim({ local screen = tt.setup_child_nvim({
'-u', '-u',
'NONE', 'NONE',
'-i', '-i',
'NONE', 'NONE',
'--listen',
server_pipe,
'--cmd', '--cmd',
'colorscheme vim', 'colorscheme vim',
'--cmd', '--cmd',
@@ -189,6 +192,16 @@ describe('TUI :restart', function()
{3:-- TERMINAL --} | {3:-- TERMINAL --} |
]] ]]
screen:expect(s0) screen:expect(s0)
local server_session = n.connect(server_pipe)
local _, server_pid = server_session:request('nvim_call_function', 'getpid', {})
local function restart_pid_check()
server_session:close()
server_session = n.connect(server_pipe)
local _, new_pid = server_session:request('nvim_call_function', 'getpid', {})
t.neq(server_pid, new_pid)
server_pid = new_pid
end
tt.feed_data(':1restart\013') tt.feed_data(':1restart\013')
screen:expect({ any = vim.pesc('{8:E481: No range allowed}') }) screen:expect({ any = vim.pesc('{8:E481: No range allowed}') })
@@ -199,6 +212,7 @@ describe('TUI :restart', function()
-- Check ":restart" on an unmodified buffer. -- Check ":restart" on an unmodified buffer.
tt.feed_data(':restart\013') tt.feed_data(':restart\013')
screen:expect(s0) screen:expect(s0)
restart_pid_check()
tt.feed_data('ithis will be removed\027') tt.feed_data('ithis will be removed\027')
screen:expect([[ screen:expect([[
@@ -224,6 +238,21 @@ describe('TUI :restart', function()
-- Check ":restart!". -- Check ":restart!".
tt.feed_data(':restart!\013') tt.feed_data(':restart!\013')
screen:expect(s0) screen:expect(s0)
restart_pid_check()
tt.feed_data(':echo\n')
screen:expect([[
^ |
{4:~ }|*3
{5:[No Name] }|
|
{3:-- TERMINAL --} |
]])
-- No --listen conflict when server exit is delayed.
feed_data(':lua vim.schedule(function() vim.wait(100) end); vim.cmd.restart()\n')
screen:expect(s0)
restart_pid_check()
screen:try_resize(60, 6) screen:try_resize(60, 6)
screen:expect([[ screen:expect([[
@@ -234,7 +263,7 @@ describe('TUI :restart', function()
{3:-- TERMINAL --} | {3:-- TERMINAL --} |
]]) ]])
--- Check that ":restart" uses the updated size after terminal resize --- Check that ":restart" uses the updated size after terminal resize.
tt.feed_data(':restart\013') tt.feed_data(':restart\013')
screen:expect([[ screen:expect([[
^ | ^ |
@@ -243,6 +272,7 @@ describe('TUI :restart', function()
{MATCH:%d+ +}| {MATCH:%d+ +}|
{3:-- TERMINAL --} | {3:-- TERMINAL --} |
]]) ]])
restart_pid_check()
end) end)
end) end)
@@ -3641,25 +3671,22 @@ describe('TUI client', function()
}) })
feed_data('iHello, World') feed_data('iHello, World')
screen_server:expect { screen_server:expect([[
grid = [[
Hello, World^ | Hello, World^ |
{4:~ }|*3 {4:~ }|*3
{5:[No Name] [+] }| {5:[No Name] [+] }|
{3:-- INSERT --} | {3:-- INSERT --} |
{3:-- TERMINAL --} | {3:-- TERMINAL --} |
]], ]])
}
feed_data('\027') feed_data('\027')
screen_server:expect { local s0 = [[
grid = [[
Hello, Worl^d | Hello, Worl^d |
{4:~ }|*3 {4:~ }|*3
{5:[No Name] [+] }| {5:[No Name] [+] }|
| |
{3:-- TERMINAL --} | {3:-- TERMINAL --} |
]], ]]
} screen_server:expect(s0)
set_session(client_super) set_session(client_super)
local screen_client = tt.setup_child_nvim({ local screen_client = tt.setup_child_nvim({
@@ -3667,28 +3694,31 @@ describe('TUI client', function()
'--server', '--server',
server_pipe, server_pipe,
}) })
screen_client:expect(s0)
screen_client:expect {
grid = [[
Hello, Worl^d |
{4:~ }|*3
{5:[No Name] [+] }|
|
{3:-- TERMINAL --} |
]],
}
-- grid smaller than containing terminal window is cleared properly -- grid smaller than containing terminal window is cleared properly
feed_data(":call setline(1,['a'->repeat(&columns)]->repeat(&lines))\n") feed_data(":call setline(1,['a'->repeat(&columns)]->repeat(&lines))\n")
feed_data('0:set lines=3\n') feed_data('0:set lines=3\n')
screen_server:expect { local s1 = [[
grid = [[
^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| ^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
{5:[No Name] [+] }| {5:[No Name] [+] }|
|*4 |*4
{3:-- TERMINAL --} | {3:-- TERMINAL --} |
]], ]]
} screen_client:expect(s1)
screen_server:expect(s1)
-- Run :restart! on the remote client.
-- The remote client should start a new server while the original one should exit.
feed_data(':restart!\n')
screen_client:expect([[
^ |
{4:~ }|*3
{5:[No Name] }|
|
{3:-- TERMINAL --} |
]])
screen_server:expect({ any = vim.pesc('[Process exited 0]') })
feed_data(':q!\n') feed_data(':q!\n')
@@ -3712,33 +3742,53 @@ describe('TUI client', function()
server_pipe, server_pipe,
}) })
screen_client:expect { screen_client:expect([[
grid = [[
Halloj^! | Halloj^! |
{4:~ }|*4 {4:~ }|*4
| |
{3:-- TERMINAL --} | {3:-- TERMINAL --} |
]], ]])
}
-- No heap-use-after-free when receiving UI events after deadly signal #22184 -- No heap-use-after-free when receiving UI events after deadly signal #22184
server:request('nvim_input', ('a'):rep(1000)) server:request('nvim_input', ('a'):rep(1000))
exec_lua([[vim.uv.kill(vim.fn.jobpid(vim.bo.channel), 'sigterm')]]) exec_lua([[vim.uv.kill(vim.fn.jobpid(vim.bo.channel), 'sigterm')]])
screen_client:expect { screen_client:expect([[
grid = [[
Nvim: Caught deadly signal 'SIGTERM' | Nvim: Caught deadly signal 'SIGTERM' |
| |
[Process exited 1]^ | [Process exited 1]^ |
|*3 |*3
{3:-- TERMINAL --} | {3:-- TERMINAL --} |
]], ]])
}
eq(0, api.nvim_get_vvar('shell_error')) eq(0, api.nvim_get_vvar('shell_error'))
-- exits on input eof #22244 -- exits on input eof #22244
fn.system({ nvim_prog, '--remote-ui', '--server', server_pipe }) fn.system({ nvim_prog, '--remote-ui', '--server', server_pipe })
eq(1, api.nvim_get_vvar('shell_error')) eq(1, api.nvim_get_vvar('shell_error'))
command('bwipe!')
fn.jobstart({ nvim_prog, '--remote-ui', '--server', server_pipe }, { term = true })
command('startinsert')
screen_client:expect([[
{4:<<<}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|*3
aaaaaa^ |
{3:-- INSERT --} |
{3:-- TERMINAL --} |
]])
-- Run :restart! on the client.
-- The client should start a new server while the original server should exit.
feed_data('\027:restart!\n')
screen_client:expect([[
^ |
{4:~ }|*4
|
{3:-- TERMINAL --} |
]])
retry(nil, nil, function()
eq(nil, vim.uv.fs_stat(server_pipe))
end)
client_super:close() client_super:close()
server:close() server:close()
if is_os('mac') then if is_os('mac') then