From 54b8c99e51bb124d03e21a5d268d60125ef9cc01 Mon Sep 17 00:00:00 2001 From: Sathya Pramodh <94102031+sathya-pramodh@users.noreply.github.com> Date: Thu, 24 Jul 2025 08:15:31 +0530 Subject: [PATCH] feat: ":restart +cmd" #34788 Problem: ":restart" always executes ":qall" to exit the server. Solution: Support ":restart +cmd" so the user can control the command used to exit the server. Co-authored-by: Justin M. Keyes Co-authored-by: zeertzjq --- runtime/doc/gui.txt | 11 ++++--- src/nvim/ex_cmds.lua | 8 +++--- src/nvim/ex_docmd.c | 41 +++++++++++++++++++++------ src/nvim/globals.h | 2 ++ src/nvim/main.c | 11 +++++++ test/functional/terminal/tui_spec.lua | 39 ++++++++++++++----------- 6 files changed, 77 insertions(+), 35 deletions(-) diff --git a/runtime/doc/gui.txt b/runtime/doc/gui.txt index 56d1d54e74..493f8700c4 100644 --- a/runtime/doc/gui.txt +++ b/runtime/doc/gui.txt @@ -70,21 +70,20 @@ Stop or detach the current UI Restart Nvim *:restart* -:restart +:restart [+cmd] Restarts the Nvim server with the same startup arguments |v:argv| and reattaches the current UI to the new server. All other UIs will detach. - This fails when changes have been made and Vim refuses to - |abandon| the current buffer. + Use with `:confirm` to prompt if changes have been made. + Example: `:restart +qall!` stops the server using `:qall!`. + + Note: |+cmd| defaults to `qall!` if not specified. Note: If the current UI hasn't implemented the "restart" UI event, this command is equivalent to `:qall`. Note: Only works if the UI and server are on the same system. -:restart! - Force restarts the Nvim server, abandoning unsaved buffers. - ------------------------------------------------------------------------------ GUI commands diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index 7a50d3d72e..a55d1f9d3c 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -2184,13 +2184,13 @@ M.cmds = { command = 'quitall', flags = bit.bor(BANG, TRLBAR), addr_type = 'ADDR_NONE', - func = 'ex_quitall_or_restart', + func = 'ex_quitall', }, { command = 'qall', flags = bit.bor(BANG, TRLBAR, CMDWIN, LOCK_OK), addr_type = 'ADDR_NONE', - func = 'ex_quitall_or_restart', + func = 'ex_quitall', }, { command = 'read', @@ -2248,9 +2248,9 @@ M.cmds = { }, { command = 'restart', - flags = bit.bor(BANG, TRLBAR), + flags = bit.bor(CMDARG, TRLBAR), addr_type = 'ADDR_NONE', - func = 'ex_quitall_or_restart', + func = 'ex_restart', }, { command = 'retab', diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 4d2ed4230e..864baf5c06 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -15,6 +15,7 @@ #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/api/ui.h" +#include "nvim/api/vimscript.h" #include "nvim/arglist.h" #include "nvim/ascii_defs.h" #include "nvim/autocmd.h" @@ -4700,6 +4701,13 @@ void not_exiting(void) exiting = false; } +/// 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); @@ -4829,22 +4837,39 @@ int before_quit_all(exarg_T *eap) } /// ":qall": try to quit all windows -/// ":restart": restart the Nvim server -static void ex_quitall_or_restart(exarg_T *eap) +static void ex_quitall(exarg_T *eap) { if (before_quit_all(eap) == FAIL) { return; } exiting = true; - Error err = ERROR_INIT; - if ((eap->forceit || !check_changed_any(false, false)) - && (eap->cmdidx != CMD_restart || remote_ui_restart(current_ui, &err))) { - getout(0); + if (!eap->forceit && check_changed_any(false, false)) { + not_exiting(); + return; } - not_exiting(); + getout(0); +} + +/// ":restart": restart the Nvim server (using ":qall!"). +/// ":restart +cmd": restart the Nvim server using ":cmd". +static void ex_restart(exarg_T *eap) +{ + char *quit_cmd = (eap->do_ecmd_cmd) ? eap->do_ecmd_cmd : "qall!"; + Error err = ERROR_INIT; + if ((cmdmod.cmod_flags & CMOD_CONFIRM) && check_changed_any(false, false)) { + return; + } + restarting = true; + nvim_command(cstr_as_string(quit_cmd), &err); if (ERROR_SET(&err)) { - emsg(err.msg); // UI disappeared already? + emsg(err.msg); // Could not exit api_clear_error(&err); + not_restarting(); + return; + } + if (!exiting) { + emsg("restart failed: +cmd did not quit the server"); + not_restarting(); } } diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 1bfd6e4739..5def6ffc16 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -423,6 +423,8 @@ EXTERN int sc_col; // column for shown command EXTERN int starting INIT( = NO_SCREEN); // true when planning to exit. Might keep running if there is a changed buffer. EXTERN bool exiting INIT( = false); +// true when 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 a5748e7615..4331415929 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -809,6 +809,17 @@ 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/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index f41c3521ed..1c3e3ccb7f 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -264,6 +264,16 @@ describe('TUI :restart', function() restart_pid_check() gui_running_check() + -- Check ":restart +qall" on an unmodified buffer. + tt.feed_data(':restart +qall\013') + screen_expect(s0) + restart_pid_check() + gui_running_check() + + -- Check ":restart +echo" cannot restart server. + tt.feed_data(':restart +echo\013') + screen:expect({ any = vim.pesc('+cmd did not quit the server') }) + tt.feed_data('ithis will be removed\027') screen_expect([[ this will be remove^d | @@ -273,20 +283,15 @@ describe('TUI :restart', function() {5:-- TERMINAL --} | ]]) - -- Check ":restart" on a modified buffer. - tt.feed_data(':restart\013') - screen_expect([[ - this will be removed | - {3: }| - {101:E37: No write since last change} | - {101:E162: No write since last change for buffer "[No N}| - {101:ame]"} | - {102:Press ENTER or type command to continue}^ | - {5:-- TERMINAL --} | - ]]) + -- Check ":confirm restart" on a modified buffer. + tt.feed_data(':confirm restart\013') + screen:expect({ any = vim.pesc('Save changes to "Untitled"?') }) - -- Check ":restart!". - tt.feed_data(':restart!\013') + -- Cancel the operation (abandons restart). + tt.feed_data('C\013') + + -- Check ":restart" on the modified buffer. + tt.feed_data(':restart\013') screen_expect(s0) restart_pid_check() gui_running_check() @@ -3728,9 +3733,9 @@ describe('TUI client', function() screen_client:expect(s1) screen_server:expect(s1) - -- Run :restart! on the remote client. + -- Run :restart on the remote client. -- The remote client should start a new server while the original one should exit. - feed_data(':restart!\n') + feed_data(':restart\n') screen_client:expect([[ ^ | {100:~ }|*3 @@ -3805,9 +3810,9 @@ describe('TUI client', function() feed_data(':echo "GUI Running: " .. has("gui_running")\013') screen_client:expect({ any = 'GUI Running: 1' }) - -- Run :restart! on the client. + -- Run :restart on the client. -- The client should start a new server while the original server should exit. - feed_data(':restart!\n') + feed_data(':restart\n') screen_client:expect([[ ^ | {100:~ }|*4