diff --git a/runtime/doc/api-ui-events.txt b/runtime/doc/api-ui-events.txt index 67ef56c828..ec3177d71d 100644 --- a/runtime/doc/api-ui-events.txt +++ b/runtime/doc/api-ui-events.txt @@ -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 diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 01771be7c0..55dc040b84 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -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. diff --git a/runtime/doc/gui.txt b/runtime/doc/gui.txt index 0d2cd3ef04..87cff97140 100644 --- a/runtime/doc/gui.txt +++ b/runtime/doc/gui.txt @@ -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 diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index 136ba6c94e..373c1361eb 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -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; } diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index c58f577c56..424d474366 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -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); diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 280ecc0f05..d947b2ed0d 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -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. diff --git a/src/nvim/channel.c b/src/nvim/channel.c index e292830b50..9478ab6d68 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -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) { diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index c2738d45ab..07361d820c 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -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); } } diff --git a/src/nvim/event/proc.c b/src/nvim/event/proc.c index eee728679d..9f4e0537b5 100644 --- a/src/nvim/event/proc.c +++ b/src/nvim/event/proc.c @@ -5,7 +5,6 @@ #include #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 diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 26b73580df..82e4316149 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -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 ": restart the Nvim server using ":cmd" and add -c to the new server. +/// ":restart +cmd ": restart the Nvim server using ":cmd" and runs 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 ": 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 ": 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", ""` to end of v:argv. - // The "+:::" item is a no-op placeholder to mark the :restart "". - 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); } } diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 2a82d518eb..a5a9938c76 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -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? diff --git a/src/nvim/main.c b/src/nvim/main.c index 7304b77ae6..0e26883aad 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -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); } diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index 745029a733..913d6968e8 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -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; } } diff --git a/src/nvim/msgpack_rpc/server.c b/src/nvim/msgpack_rpc/server.c index 6c205b6954..48f3e8ce04 100644 --- a/src/nvim/msgpack_rpc/server.c +++ b/src/nvim/msgpack_rpc/server.c @@ -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); } diff --git a/src/nvim/ui_client.c b/src/nvim/ui_client.c index bb2bc39ebd..13774a0156 100644 --- a/src/nvim/ui_client.c +++ b/src/nvim/ui_client.c @@ -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; } diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua index e317a5d7a5..06e2912698 100644 --- a/test/functional/core/startup_spec.lua +++ b/test/functional/core/startup_spec.lua @@ -1498,7 +1498,17 @@ describe('user config init', function() -- a total of 2 exrc files are executed feed(':echo g:exrc_count') - 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!') + screen:expect({ any = vim.pesc('[Process exited 0]') }) end) end) diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 0fd0e17dbe..58e154e7b1 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -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 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([[ ^ |