commit 54b8c99e51bb124d03e21a5d268d60125ef9cc01
parent 0dcdd65dcc08483d9a5c106f62b862a9de30983e
Author: Sathya Pramodh <94102031+sathya-pramodh@users.noreply.github.com>
Date: Thu, 24 Jul 2025 08:15:31 +0530
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 <justinkz@gmail.com>
Co-authored-by: zeertzjq <zeertzjq@outlook.com>
Diffstat:
6 files changed, 76 insertions(+), 34 deletions(-)
diff --git 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
@@ -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
@@ -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;
+ if (!eap->forceit && check_changed_any(false, false)) {
+ not_exiting();
+ return;
+ }
+ 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 ((eap->forceit || !check_changed_any(false, false))
- && (eap->cmdidx != CMD_restart || remote_ui_restart(current_ui, &err))) {
- getout(0);
+ if ((cmdmod.cmod_flags & CMOD_CONFIRM) && check_changed_any(false, false)) {
+ return;
}
- not_exiting();
+ 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
@@ -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
@@ -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
@@ -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"?') })
+
+ -- Cancel the operation (abandons restart).
+ tt.feed_data('C\013')
- -- Check ":restart!".
- tt.feed_data(':restart!\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