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>
This commit is contained in:
Sathya Pramodh
2025-07-24 08:15:31 +05:30
committed by GitHub
parent 0dcdd65dcc
commit 54b8c99e51
6 changed files with 77 additions and 35 deletions

View File

@@ -70,21 +70,20 @@ Stop or detach the current UI
Restart Nvim Restart Nvim
*:restart* *:restart*
:restart :restart [+cmd]
Restarts the Nvim server with the same startup arguments Restarts the Nvim server with the same startup arguments
|v:argv| and reattaches the current UI to the new server. |v:argv| and reattaches the current UI to the new server.
All other UIs will detach. All other UIs will detach.
This fails when changes have been made and Vim refuses to Use with `:confirm` to prompt if changes have been made.
|abandon| the current buffer.
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 Note: If the current UI hasn't implemented the "restart" UI
event, this command is equivalent to `:qall`. event, this command is equivalent to `:qall`.
Note: Only works if the UI and server are on the same system. Note: Only works if the UI and server are on the same system.
:restart!
Force restarts the Nvim server, abandoning unsaved buffers.
------------------------------------------------------------------------------ ------------------------------------------------------------------------------
GUI commands GUI commands

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_quitall_or_restart', func = 'ex_quitall',
}, },
{ {
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_quitall_or_restart', func = 'ex_quitall',
}, },
{ {
command = 'read', command = 'read',
@@ -2248,9 +2248,9 @@ M.cmds = {
}, },
{ {
command = 'restart', command = 'restart',
flags = bit.bor(BANG, TRLBAR), flags = bit.bor(CMDARG, TRLBAR),
addr_type = 'ADDR_NONE', addr_type = 'ADDR_NONE',
func = 'ex_quitall_or_restart', func = 'ex_restart',
}, },
{ {
command = 'retab', command = 'retab',

View File

@@ -15,6 +15,7 @@
#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/ui.h" #include "nvim/api/ui.h"
#include "nvim/api/vimscript.h"
#include "nvim/arglist.h" #include "nvim/arglist.h"
#include "nvim/ascii_defs.h" #include "nvim/ascii_defs.h"
#include "nvim/autocmd.h" #include "nvim/autocmd.h"
@@ -4700,6 +4701,13 @@ void not_exiting(void)
exiting = false; 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) bool before_quit_autocmds(win_T *wp, bool quit_all, bool forceit)
{ {
apply_autocmds(EVENT_QUITPRE, NULL, NULL, false, wp->w_buffer); 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 /// ":qall": try to quit all windows
/// ":restart": restart the Nvim server static void ex_quitall(exarg_T *eap)
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;
Error err = ERROR_INIT; if (!eap->forceit && check_changed_any(false, false)) {
if ((eap->forceit || !check_changed_any(false, false)) not_exiting();
&& (eap->cmdidx != CMD_restart || remote_ui_restart(current_ui, &err))) { return;
getout(0);
} }
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)) { if (ERROR_SET(&err)) {
emsg(err.msg); // UI disappeared already? emsg(err.msg); // Could not exit
api_clear_error(&err); api_clear_error(&err);
not_restarting();
return;
}
if (!exiting) {
emsg("restart failed: +cmd did not quit the server");
not_restarting();
} }
} }

View File

@@ -423,6 +423,8 @@ EXTERN int sc_col; // column for shown command
EXTERN int starting INIT( = NO_SCREEN); EXTERN int starting INIT( = NO_SCREEN);
// true when planning to exit. Might keep running if there is a changed buffer. // true when planning to exit. Might keep running if there is a changed buffer.
EXTERN bool exiting INIT( = false); EXTERN bool exiting INIT( = false);
// true when planning to restart.
EXTERN bool restarting INIT( = false);
// internal value of v:dying // internal value of v:dying
EXTERN int v_dying INIT( = 0); EXTERN int v_dying INIT( = 0);
// is stdin a terminal? // is stdin a terminal?

View File

@@ -809,6 +809,17 @@ void getout(int exitval)
ui_call_set_title(cstr_as_string(p_titleold)); 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) { if (garbage_collect_at_exit) {
garbage_collect(false); garbage_collect(false);
} }

View File

@@ -264,6 +264,16 @@ describe('TUI :restart', function()
restart_pid_check() restart_pid_check()
gui_running_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') tt.feed_data('ithis will be removed\027')
screen_expect([[ screen_expect([[
this will be remove^d | this will be remove^d |
@@ -273,20 +283,15 @@ describe('TUI :restart', function()
{5:-- TERMINAL --} | {5:-- TERMINAL --} |
]]) ]])
-- Check ":restart" on a modified buffer. -- Check ":confirm restart" on a modified buffer.
tt.feed_data(':restart\013') tt.feed_data(':confirm restart\013')
screen_expect([[ screen:expect({ any = vim.pesc('Save changes to "Untitled"?') })
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 ":restart!". -- Cancel the operation (abandons restart).
tt.feed_data(':restart!\013') tt.feed_data('C\013')
-- Check ":restart" on the modified buffer.
tt.feed_data(':restart\013')
screen_expect(s0) screen_expect(s0)
restart_pid_check() restart_pid_check()
gui_running_check() gui_running_check()
@@ -3728,9 +3733,9 @@ describe('TUI client', function()
screen_client:expect(s1) screen_client:expect(s1)
screen_server: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. -- 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([[ screen_client:expect([[
^ | ^ |
{100:~ }|*3 {100:~ }|*3
@@ -3805,9 +3810,9 @@ describe('TUI client', function()
feed_data(':echo "GUI Running: " .. has("gui_running")\013') feed_data(':echo "GUI Running: " .. has("gui_running")\013')
screen_client:expect({ any = 'GUI Running: 1' }) 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. -- The client should start a new server while the original server should exit.
feed_data(':restart!\n') feed_data(':restart\n')
screen_client:expect([[ screen_client:expect([[
^ | ^ |
{100:~ }|*4 {100:~ }|*4