Merge pull request #40606 from superatomic/backport-seamless-restart

backport: `v:startreason` and ":restart" (no bang) saves/restores session
This commit is contained in:
Justin M. Keyes
2026-07-06 05:18:10 -04:00
committed by GitHub
23 changed files with 343 additions and 88 deletions

View File

@@ -1209,8 +1209,8 @@ ZZ Write current file, if modified, and close the current
ZQ Quit without checking for changes (same as ":q!").
*ZR*
[count]ZR Performs |:restart|. If a [count] is given, restarts
without checking for changes (":restart +qall!").
[count]ZR Performs |:restart!|. If a [count] is given, restarts
without checking for changes (":restart! +qall!").
MULTIPLE WINDOWS AND BUFFERS *window-exit*

View File

@@ -75,20 +75,23 @@ Stop or detach the current UI
------------------------------------------------------------------------------
Restart Nvim
*:restart*
:restart [+cmd] [command]
*:restart* *:restart!*
:restart[!] [+cmd] [command]
Restarts Nvim. Sets |v:exitreason|. See also |ZR|.
When [!] is included, the session is not restored.
1. Stops Nvim using `:qall` (or |+cmd|, if given).
2. Starts a new Nvim server using the same |v:argv| (except
`-- [file…]` files).
3. Attaches all UIs to the new Nvim server and runs `[command]`
1. Saves the current session (unless [!] was given).
2. Stops Nvim using `:qall` (or |+cmd|, if given).
3. Starts a new Nvim server using the same |v:argv| (except
`-- [file…]` files and `-S [file]`), and sets |v:startreason|.
4. Restores the saved session (unless [!] was given).
5. Attaches all UIs to the new Nvim server and runs `[command]`
on it.
Example: discard changes and stop with `:qall!`, then restart: >
:restart +qall!
< Example: restart and restore the current session: >
:mksession! Session.vim | restart source Session.vim
:mksession! Session.vim | restart! source Session.vim
< Example: restart and update plugins: >
:restart lua vim.pack.update()
<
@@ -96,8 +99,9 @@ Restart Nvim
• Only works if the UI and server are on the same system.
• Windows limitation: when +cmd is executed, |v:servername|
refers to a temporary address.
• If no UI handles the "restart" event, this command will lead
to a dangling server process.
• If no UI handles the "restart" event, this leaves a dangling
process.
• To adjust what `:restart` restores, set 'sessionoptions'.
------------------------------------------------------------------------------
Connect UI to a different server

View File

@@ -191,7 +191,8 @@ EDITOR
codeblocks via treesitter and executes them as Lua instead of Vimscript.
(Or use `:{range}lua` to skip the detection.)
• |:wall| with |++p| auto-creates missing parent directories.
• |ZR| restarts Nvim (|:restart|).
• |:restart| now restores the current session while |:restart!| does not.
• |ZR| restarts Nvim (|:restart!|).
EVENTS
@@ -453,6 +454,7 @@ VIMSCRIPT
• |v:vim_did_init| is set after sourcing |init.vim| but before |load-plugins|.
• |prompt_appendbuf()| appends text to prompt-buffer.
• |v:exitreason| is set before |QuitPre|.
• |v:startreason| differentiates between restart and normal start.
• |v:starttime| is the process start time (nanoseconds since UNIX epoch).
==============================================================================

View File

@@ -434,7 +434,7 @@ Initialization *initialization* *startup*
At startup, Nvim checks environment variables and files and sets values
accordingly, proceeding as follows:
1. Set |v:starttime|.
1. Set |v:starttime| and |v:startreason|.
2. Set the 'shell' option. *SHELL* *COMSPEC*
The environment variable SHELL, if it exists, is used to set the 'shell'

View File

@@ -633,6 +633,15 @@ v:stacktrace
stack trace. See also |v:exception|, |v:throwpoint|, and
|throw-variables|.
*v:startreason* *startreason-variable*
v:startreason
The reason Nvim started. Possible values:
- "normal" normal startup.
- "restart" started by |:restart|.
- "restart!" started by |:restart!| or |ZR|.
Read-only.
*v:starttime* *starttime-variable*
v:starttime
Timestamp (nanoseconds since UNIX epoch) when the Nvim process

View File

@@ -1,3 +1,5 @@
local fs = vim.fs
local uv = vim.uv
-- For "--listen" and related functionality.
local M = {}
@@ -97,4 +99,59 @@ function M.rebind_after_restart(canonical_addr, expected_uis)
end)
end
-- Called by ex_restart(). Saves the current session and calls back to
-- ex_restart() with the necessary arguments to restore the session.
-- TODO: https://github.com/neovim/neovim/issues/34204
function M.ex_session_restart(after_cmd, quit_cmd)
-- Commands to run after restart
assert(not after_cmd:find(']==]'))
-- Use custom quit command if provided
if quit_cmd == '' then
quit_cmd = 'qall'
end
-- Preserve the value of v:this_session
local this_session = vim.v.this_session
assert(not this_session:find(']==]'))
-- Get temp file to write session to
local temp_dir = fs.abspath(fs.dirname(fs.dirname(vim.fn.tempname())))
assert(not temp_dir:find(']==]'))
local fd, session = uv.fs_mkstemp(fs.joinpath(temp_dir, 'restart_session_XXXXXX'))
if not fd then
error('Failed to get temporary filename for restart session')
end
uv.fs_close(fd)
-- Write session
local session_arg = vim.fn.fnameescape(session)
vim.cmd('%argdelete')
vim.cmd.mksession { session_arg, bang = true }
-- Lua commands to restore the session and remove the session file
local after_list = {}
table.insert(after_list, ('vim.cmd("source %s")'):format(session_arg))
table.insert(after_list, ('pcall(vim.fs.rm, [==[%s]==])'):format(session))
table.insert(after_list, ('vim.v.this_session = [==[%s]==]'):format(this_session))
-- User provided command
if after_cmd ~= '' then
table.insert(after_list, ('vim.cmd([==[%s]==])'):format(after_cmd))
end
-- Concatenate everything together
local after = 'lua ' .. table.concat(after_list, ';')
-- Restart Neovim and run our Lua commands
local success, msg = pcall(function()
-- "+:::" special argument tells the C handler that this is actually a non-bang restart
-- That way, v:startreason can be set correctly
vim.cmd.restart { '+:::', quit_cmd, after, bang = true }
end)
if not success then
fs.rm(session, { force = true })
error(msg)
end
end
return M

View File

@@ -667,6 +667,15 @@ vim.v.shell_error = ...
--- @type table[]
vim.v.stacktrace = ...
--- The reason Nvim started. Possible values:
--- - "normal" normal startup.
--- - "restart" started by `:restart`.
--- - "restart!" started by `:restart!` or `ZR`.
---
--- Read-only.
--- @type string
vim.v.startreason = ...
--- Timestamp (nanoseconds since UNIX epoch) when the Nvim process
--- started.
---

View File

@@ -562,9 +562,9 @@ uint64_t channel_from_stdio(bool rpc, CallbackReader on_output, const char **err
os_set_cloexec(stdout_dup_fd);
// :restart spawns a replacement server that must not borrow the parent
// Nvim process console, because that parent process will soon exit.
const bool restart_alloc_console = os_env_exists("__NVIM_RESTART_ALLOC_CONSOLE", true);
const bool restart_alloc_console = os_env_exists(ENV_RESTART_ALLOC_CONSOLE, true);
if (restart_alloc_console) {
os_unsetenv("__NVIM_RESTART_ALLOC_CONSOLE");
os_unsetenv(ENV_RESTART_ALLOC_CONSOLE);
}
if (!GetConsoleWindow()) {
// Borrow the parent's console so CONOUT$ resolves to the real terminal,

View File

@@ -3385,8 +3385,11 @@ static const char *required_env_vars[] = {
NULL
};
/// Builds an environment dict for a child process (job).
///
/// @param set_nvim_addr Set the $NVIM env var.
dict_T *create_environment(const dictitem_T *job_env, const bool clear_env, const bool pty,
const char * const pty_term_name)
const bool set_nvim_addr, const char * const pty_term_name)
{
dict_T *env = tv_dict_alloc();
@@ -3427,13 +3430,15 @@ dict_T *create_environment(const dictitem_T *job_env, const bool clear_env, cons
}
// Set $NVIM (in the child process) to v:servername. #3118
char *nvim_addr = get_vim_var_str(VV_SEND_SERVER);
if (nvim_addr[0] != NUL) {
dictitem_T *dv = tv_dict_find(env, S_LEN("NVIM"));
if (dv) {
tv_dict_item_remove(env, dv);
if (set_nvim_addr) {
char *nvim_addr = get_vim_var_str(VV_SEND_SERVER);
if (nvim_addr[0] != NUL) {
dictitem_T *dv = tv_dict_find(env, S_LEN("NVIM"));
if (dv) {
tv_dict_item_remove(env, dv);
}
tv_dict_add_str(env, S_LEN("NVIM"), nvim_addr);
}
tv_dict_add_str(env, S_LEN("NVIM"), nvim_addr);
}
if (job_env) {
@@ -3619,7 +3624,7 @@ void f_jobstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
term_name = term_name ? term_name : "ansi";
}
dict_T *env = create_environment(job_env, clear_env, pty, term_name);
dict_T *env = create_environment(job_env, clear_env, pty, true, term_name);
Channel *chan = channel_job_start(argv, NULL, on_stdout, on_stderr, on_exit, pty,
rpc, overlapped, detach, stdin_mode, cwd,
width, height, env, &rettv->vval.v_number);

View File

@@ -217,6 +217,7 @@ static struct vimvar {
VV(VV_VIRTNUM, "virtnum", VAR_NUMBER, VV_RO),
VV(VV_STARTTIME, "starttime", VAR_NUMBER, VV_RO),
VV(VV_EXITREASON, "exitreason", VAR_STRING, VV_RO),
VV(VV_STARTREASON, "startreason", VAR_STRING, VV_RO),
};
#undef VV
@@ -316,6 +317,7 @@ void evalvars_init(void)
set_vim_var_nr(VV_SEARCHFORWARD, 1);
set_vim_var_nr(VV_HLSEARCH, 1);
set_vim_var_nr(VV_COUNT1, 1);
set_vim_var_string(VV_STARTREASON, S_LEN("normal"));
set_vim_var_special(VV_EXITING, kSpecialVarNull);
set_vim_var_nr(VV_TYPE_NUMBER, VAR_TYPE_NUMBER);
@@ -345,6 +347,15 @@ void evalvars_init(void)
set_vim_var_partial(VV_LUA, vvlua_partial);
set_reg_var(0); // default for v:register is not 0 but '"'
// Set v:startreason via environment variable
const char *startreason = os_getenv_noalloc(ENV_STARTREASON);
if (strequal(startreason, "restart!") || strequal(startreason, "restart")) {
set_vim_var_string(VV_STARTREASON, startreason, -1);
}
if (os_env_exists(ENV_STARTREASON, false)) {
os_unsetenv(ENV_STARTREASON);
}
}
#if defined(EXITFREE)

View File

@@ -137,4 +137,5 @@ typedef enum {
VV_VIRTNUM,
VV_STARTTIME,
VV_EXITREASON,
VV_STARTREASON,
} VimVarIndex;

View File

@@ -2260,7 +2260,7 @@ M.cmds = {
},
{
command = 'restart',
flags = bit.bor(CMDARG, EXTRA, NOTRLCOM),
flags = bit.bor(BANG, CMDARG, EXTRA, NOTRLCOM),
addr_type = 'ADDR_NONE',
func = 'ex_restart',
},

View File

@@ -38,6 +38,7 @@
#include "nvim/edit.h"
#include "nvim/errors.h"
#include "nvim/eval/fs.h"
#include "nvim/eval/funcs.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/eval/userfunc.h"
@@ -4975,6 +4976,41 @@ static void ex_quitall(exarg_T *eap)
/// ":restart +cmd <command>": restart the Nvim server using ":cmd" and runs <command> in the new server.
static void ex_restart(exarg_T *eap)
{
if (!eap->forceit) {
Error err = ERROR_INIT;
MAXSIZE_TEMP_ARRAY(args, 2);
ADD_C(args, CSTR_AS_OBJ(eap->arg));
ADD_C(args, CSTR_AS_OBJ(eap->do_ecmd_cmd ? eap->do_ecmd_cmd : ""));
NLUA_EXEC_STATIC("require'vim._core.server'.ex_session_restart(...)", args, kRetNilBool, NULL,
&err);
if (ERROR_SET(&err)) {
emsg(err.msg);
}
api_clear_error(&err);
return;
}
const char *startreason = "restart!";
char *quit_cmd = (eap->do_ecmd_cmd) ? eap->do_ecmd_cmd : "qall";
char *after_cmd = eap->arg;
// "+:::" is how ex_session_restart() signals that it (recursively) called into :restart.
if (strequal(quit_cmd, ":::")) {
startreason = "restart";
// Set quit_cmd and after_cmd from args
if (eap->argc > 1) {
eap->args[1][eap->arglens[1]] = NUL;
quit_cmd = eap->args[1];
after_cmd = eap->argc > 2 ? eap->args[2] : "";
} else {
emsg("restart failed: +cmd did not quit the server");
return;
}
}
Error err = ERROR_INIT;
const bool no_ui = !ui_active();
const char *exepath = get_vim_var_str(VV_PROGPATH);
@@ -4992,6 +5028,16 @@ static void ex_restart(exarg_T *eap)
if (i > 0 && strequal(arg, "--")) {
break;
}
// Drop "-S [file]". It conflicts with :restart and usually isn't wanted for :restart!
if (i > 0 && strequal(arg, "-S")) {
if (li->li_next != NULL) {
const char *next_arg = tv_get_string(TV_LIST_ITEM_TV(li->li_next));
if (next_arg[0] != '-') {
li = li->li_next;
}
}
continue;
}
// Drop "-s <scriptfile>": skip the scriptfile arg too.
if (i > 0 && strequal(arg, "-s")) {
li = li->li_next;
@@ -5051,11 +5097,10 @@ static void ex_restart(exarg_T *eap)
bool server_stopped = listen_arg ? server_stop(listen_arg, true) : false;
#endif
dict_T *env = create_environment(NULL, false, false, false, NULL);
tv_dict_add_str(env, S_LEN(ENV_STARTREASON), startreason);
#ifdef MSWIN
bool restart_alloc_console_env = false;
if (os_setenv("__NVIM_RESTART_ALLOC_CONSOLE", "1", 1) == 0) {
restart_alloc_console_env = true;
}
tv_dict_add_str(env, S_LEN(ENV_RESTART_ALLOC_CONSOLE), "1");
#endif
CallbackReader on_err = CALLBACK_READER_INIT;
@@ -5072,12 +5117,7 @@ static void ex_restart(exarg_T *eap)
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);
#ifdef MSWIN
if (restart_alloc_console_env) {
os_unsetenv("__NVIM_RESTART_ALLOC_CONSOLE");
}
#endif
NULL, 0, 0, env, &exit_status);
if (!channel) {
emsg("cannot create a channel job");
goto fail_1;
@@ -5094,12 +5134,12 @@ static void ex_restart(exarg_T *eap)
arena_mem_free(result_mem);
result_mem = NULL;
if (*eap->arg != NUL) {
if (*after_cmd != NUL) {
// Execute [command] on new server on UIEnter.
MAXSIZE_TEMP_DICT(autocmd_opts, 3);
PUT_C(autocmd_opts, "once", BOOLEAN_OBJ(true));
PUT_C(autocmd_opts, "nested", BOOLEAN_OBJ(true));
PUT_C(autocmd_opts, "command", CSTR_AS_OBJ(eap->arg));
PUT_C(autocmd_opts, "command", CSTR_AS_OBJ(after_cmd));
MAXSIZE_TEMP_ARRAY(autocmd_args, 2);
ADD_C(autocmd_args, CSTR_AS_OBJ("UIEnter"));
ADD_C(autocmd_args, DICT_OBJ(autocmd_opts));
@@ -5154,7 +5194,6 @@ static void ex_restart(exarg_T *eap)
set_vim_var_string(VV_EXITREASON, S_LEN("restart"));
char *quit_cmd = (eap->do_ecmd_cmd) ? eap->do_ecmd_cmd : "qall";
char *quit_cmd_copy = NULL;
// Prepend "confirm " to cmd if :confirm is used

View File

@@ -73,7 +73,7 @@ static void log_path_init(void)
|| !log_try_create(log_file_path)) {
if (user_set) { // User-provided $NVIM_LOG_FILE.
// Used by _core/log.lua:check_log_file to validate logfile on startup.
os_setenv("__NVIM_LOG_FILE_WANT", log_file_path, true);
os_setenv(ENV_LOGFILE_WANT, log_file_path, true);
}
// Make $XDG_STATE_HOME if it does not exist.
char *loghome = get_xdg_home(kXDGStateHome);
@@ -91,7 +91,7 @@ static void log_path_init(void)
if (len >= size || !log_try_create(log_file_path)) {
if (!user_set) { // Default fallback path.
// Used by _core/log.lua:check_log_file to validate logfile on startup.
os_setenv("__NVIM_LOG_FILE_WANT", log_file_path, true);
os_setenv(ENV_LOGFILE_WANT, log_file_path, true);
}
len = xstrlcpy(log_file_path, "nvim.log", size);
}

View File

@@ -54,7 +54,7 @@ bool server_init(const char *listen_addr)
int rv = server_start(listen_addr);
// TODO(justinmk): this is for log_spec. Can remove this after nvim_log #7062 is merged.
if (os_env_exists("__NVIM_TEST_LOG", false)) {
if (os_env_exists(ENV_TEST_LOG, false)) {
ELOG("test log message");
}

View File

@@ -3302,9 +3302,9 @@ static void nv_Zet(cmdarg_T *cap)
// "ZR": restart. With count, restart without checking for changes.
case 'R':
if (cap->count0 >= 1) {
do_cmdline_cmd("restart +qall!");
do_cmdline_cmd("restart! +qall!");
} else {
do_cmdline_cmd("restart");
do_cmdline_cmd("restart!");
}
break;

View File

@@ -25,4 +25,8 @@ extern char *default_lib_dir;
// IWYU pragma: end_exports
#define ENV_LOGFILE "NVIM_LOG_FILE"
#define ENV_LOGFILE_WANT "__NVIM_LOG_FILE_WANT"
#define ENV_NVIM "NVIM"
#define ENV_RESTART_ALLOC_CONSOLE "__NVIM_RESTART_ALLOC_CONSOLE"
#define ENV_STARTREASON "__NVIM_STARTREASON"
#define ENV_TEST_LOG "__NVIM_TEST_LOG"

View File

@@ -159,7 +159,7 @@ void ui_client_run(void)
ui_client_attach(tui_width, tui_height, tui_term, tui_rgb);
// TODO(justinmk): this is for log_spec. Can remove this after nvim_log #7062 is merged.
if (os_env_exists("__NVIM_TEST_LOG", true)) {
if (os_env_exists(ENV_TEST_LOG, true)) {
ELOG("test log message");
}

View File

@@ -765,6 +765,17 @@ M.vars = {
Read-only.
]=],
},
startreason = {
type = 'string',
desc = [=[
The reason Nvim started. Possible values:
- "normal" normal startup.
- "restart" started by |:restart|.
- "restart!" started by |:restart!| or |ZR|.
Read-only.
]=],
},
statusmsg = {
type = 'string',
desc = [=[

View File

@@ -217,7 +217,7 @@ describe('vim._core', function()
-- All `vim._core.*` modules are builtin.
t.eq(
{ 'rebind_after_restart', 'serverlist' },
{ 'ex_session_restart', 'rebind_after_restart', 'serverlist' },
n.exec_lua([[local k = vim.tbl_keys(require('vim._core.server')); table.sort(k); return k]])
)
local expected = {

View File

@@ -352,8 +352,8 @@ describe('startup --listen', function()
end)
end)
it(':restart works in headless server (no UI)', function()
t.skip(is_os('win'), 'FIXME: --listen not preserved by :restart on Windows')
it(':restart! works in headless server (no UI)', function()
t.skip(is_os('win'), 'FIXME: --listen not preserved by :restart! on Windows')
local nvim0 = clear()
local server_pipe = n.new_pipename()
@@ -364,18 +364,28 @@ it(':restart works in headless server (no UI)', function()
n.set_session(nil)
end)
fn.jobstart({ n.nvim_prog, '--clean', '--headless', '--listen', server_pipe })
fn.jobstart({
n.nvim_prog,
'--clean',
'--headless',
'--listen',
server_pipe,
'--cmd',
'let g:early_startreason = v:startreason',
})
t.retry(nil, nil, function()
neq(nil, vim.uv.fs_stat(server_pipe))
end)
n.set_session(n.connect(server_pipe))
n.expect_exit(n.command, 'restart')
n.expect_exit(n.command, 'restart!')
n.set_session(n.connect(server_pipe))
eq(1, api.nvim_get_vvar('vim_did_enter'))
eq('restart!', api.nvim_get_vvar('startreason'))
eq('restart!', n.eval('g:early_startreason'))
-- TODO: [command] is currently not executed without UI
-- n.expect_exit(n.command, 'restart lua _G.new_server = 1')
-- n.expect_exit(n.command, 'restart! lua _G.new_server = 1')
-- n.set_session(n.connect(server_pipe))
-- eq(1, n.exec_lua('return _G.new_server'))
end)

View File

@@ -261,11 +261,86 @@ describe('TUI :restart', function()
end
it('validation', function()
eq('Vim(restart):E481: No range allowed: :1restart', t.pcall_err(n.command, ':1restart'))
eq('Vim(restart):E481: No range allowed: :1restart!', t.pcall_err(n.command, ':1restart!'))
end)
it(':restart (no bang) restores session, window layout', function()
local file = 'Xtest-restart-file'
write_file(file, 'foobar')
finally(function()
os.remove(file)
end)
local server_pipe = new_pipename()
local server_session
finally(function()
if server_session then
server_session:close()
end
end)
local screen = tt.setup_child_nvim({
'--clean',
'--listen',
server_pipe,
'--cmd',
'set notermguicolors laststatus=0 noruler noshowcmd',
}, {
env = vim.tbl_extend('force', env_notermguicolors, {
-- Ignore logs, because assert_restarted may log "connection refused" while it retries.
NVIM_LOG_FILE = testlog,
}),
})
finally(function()
os.remove(testlog)
end)
feed_data(':edit ' .. file .. '\r')
feed_data(':wincmd v\r')
screen:expect([[
^foobar │foobar |
~ │~ |
~ │~ |
~ │~ |
~ │~ |
:wincmd v |
{5:-- TERMINAL --} |
]])
server_session = n.connect(server_pipe)
local _, starttime = server_session:request('nvim_eval', 'v:starttime')
eq({ true, '' }, { server_session:request('nvim_get_vvar', 'this_session') })
-- :restart
feed_data(':restart\r')
screen:expect([[
^foobar │foobar |
~ │~ |
~ │~ |
~ │~ |
~ │~ |
|
{5:-- TERMINAL --} |
]])
starttime, server_session = assert_restarted(starttime, server_session, server_pipe)
eq({ true, 'restart' }, { server_session:request('nvim_get_vvar', 'startreason') })
eq({ true, '' }, { server_session:request('nvim_get_vvar', 'this_session') })
-- :restart!
feed_data(':restart!\r')
screen:expect([[
^ |
~ |*4
|
{5:-- TERMINAL --} |
]])
starttime, server_session = assert_restarted(starttime, server_session, server_pipe)
eq({ true, 'restart!' }, { server_session:request('nvim_get_vvar', 'startreason') })
feed_data(':qall!\r')
screen:expect({ any = vim.pesc('[Process exited 0]') })
end)
it('ZR', function()
-- Just exercise ZR, don't need to test all :restart functionality here.
-- Just exercise ZR, don't need to test all :restart! functionality here.
local server_pipe = new_pipename()
local server_session
finally(function()
@@ -345,8 +420,8 @@ describe('TUI :restart', function()
}, { env = { COLORTERM = 'truecolor' } })
screen:set_option('rgb', true)
-- 'termguicolors' support should be detected properly after :restart.
-- The value of has("gui_running") should be 0 before and after :restart.
-- 'termguicolors' support should be detected properly after :restart!
-- The value of has("gui_running") should be 0 before and after :restart!
local function assert_termguicolors_and_no_gui_running()
tt.feed_data(':echo "&termguicolors: " .. &termguicolors\013')
screen:expect({ any = '&termguicolors: 1' })
@@ -392,14 +467,14 @@ describe('TUI :restart', function()
tt.feed_data(':set nomodified\013')
-- Command is run on new server.
tt.feed_data(":restart put ='Hello1'\013")
tt.feed_data(":restart! put ='Hello1'\013")
screen:expect(s1)
assert_new_pid()
assert_exitreason()
assert_termguicolors_and_no_gui_running()
-- Complex command following +cmd.
tt.feed_data(":restart +qall! put ='Hello2' | put ='World2'\013")
tt.feed_data(":restart! +qall! put ='Hello2' | put ='World2'\013")
screen:expect([[
|
Hello2 |
@@ -413,24 +488,24 @@ describe('TUI :restart', function()
assert_exitreason()
assert_termguicolors_and_no_gui_running()
-- Check ":restart" on an unmodified buffer.
-- Check ":restart!" on an unmodified buffer.
tt.feed_data(':set nomodified\013')
tt.feed_data(':restart\013')
tt.feed_data(':restart!\013')
screen:expect(s0)
assert_new_pid()
assert_exitreason()
assert_termguicolors_and_no_gui_running()
-- Check ":restart +qall!" on an unmodified buffer.
tt.feed_data(':restart +qall!\013')
-- Check ":restart! +qall!" on an unmodified buffer.
tt.feed_data(':restart! +qall!\013')
screen:expect(s0)
assert_new_pid()
assert_exitreason()
assert_termguicolors_and_no_gui_running()
-- Check ":restart +echo" cannot restart server.
-- Check ":restart! +echo" cannot restart server.
-- Check the full screen state to ensure this doesn't pollute the current UI.
tt.feed_data(':restart +echo\013')
tt.feed_data(':restart! +echo\013')
screen:expect([[
^ |
{1:~}{18: }|*3
@@ -442,8 +517,8 @@ describe('TUI :restart', function()
tt.feed_data('ithis will be removed\027')
screen:expect({ any = vim.pesc('this will be remove^d') })
-- Check ":confirm restart" on a modified buffer.
tt.feed_data(':confirm restart\013')
-- 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).
@@ -452,9 +527,9 @@ describe('TUI :restart', function()
-- Failed/cancelled restarts still fire QuitPre/ExitPre (but not VimLeave[Pre]).
assert_exitreason('QuitPre:restart\nExitPre:restart\n')
-- Check :restart respects 'confirm' option.
-- Check :restart! respects 'confirm' option.
tt.feed_data(':set confirm\013')
tt.feed_data(':restart\013')
tt.feed_data(':restart!\013')
screen:expect({ any = vim.pesc('Save changes to "Untitled"?') })
tt.feed_data('C\013')
screen:expect({ any = vim.pesc('[No Name]') })
@@ -462,8 +537,8 @@ describe('TUI :restart', function()
-- Failed/cancelled restarts still fire QuitPre/ExitPre (but not VimLeave[Pre]).
assert_exitreason('QuitPre:restart\nExitPre:restart\n')
-- Check ":confirm restart <cmd>" on a modified buffer.
tt.feed_data(":confirm restart put ='Hello3'\013")
-- Check ":confirm restart! <cmd>" on a modified buffer.
tt.feed_data(":confirm restart! put ='Hello3'\013")
screen:expect({ any = vim.pesc('Save changes to "Untitled"?') })
tt.feed_data('N\013')
screen:expect({ any = '%^Hello3' })
@@ -471,19 +546,19 @@ describe('TUI :restart', function()
assert_exitreason()
assert_termguicolors_and_no_gui_running()
-- Check ":confirm restart +echo" correctly ignores ":confirm"
tt.feed_data(':confirm restart +echo\013')
-- Check ":confirm restart! +echo" correctly ignores ":confirm"
tt.feed_data(':confirm restart! +echo\013')
screen:expect({ any = vim.pesc('+cmd did not quit the server') })
-- Check ":restart" on a modified buffer.
-- Check ":restart!" on a modified buffer.
tt.feed_data('ithis will be removed\027')
tt.feed_data(':restart\013')
tt.feed_data(':restart!\013')
screen:expect({ any = vim.pesc('Vim(qall):E37: No write since last change') })
assert_exitreason('QuitPre:restart\nExitPre:restart\n')
-- Check ":restart +qall!" on a modified buffer.
-- Check ":restart! +qall!" on a modified buffer.
tt.feed_data('ithis will be removed\027')
tt.feed_data(':restart +qall!\013')
tt.feed_data(':restart! +qall!\013')
screen:expect(s0)
assert_new_pid()
assert_exitreason()
@@ -491,7 +566,7 @@ describe('TUI :restart', function()
if not is_os('win') then
-- No --listen conflict when server exit is delayed.
feed_data(':lua vim.schedule(function() vim.wait(100) end); vim.cmd.restart()\n')
feed_data(':lua vim.schedule(function() vim.wait(100) end); vim.cmd("restart!")\n')
screen:expect(s0)
assert_new_pid()
assert_exitreason()
@@ -507,8 +582,8 @@ describe('TUI :restart', function()
{5:-- TERMINAL --} |
]])
--- Check that ":restart" uses the updated size after terminal resize.
tt.feed_data(':restart echo "restarted"\013')
--- Check that ":restart!" uses the updated size after terminal resize.
tt.feed_data(':restart! echo "restarted"\013')
screen:expect([[
^ |
{1:~}{18: }|*2
@@ -527,6 +602,11 @@ describe('TUI :restart', function()
it('drops "-" and "-- [files…]" from v:argv #34417', function()
t.skip(is_os('win'), 'stdin behavior differs on Windows')
local file = 'file.lua'
write_file(file, "print('-S works')\n")
finally(function()
os.remove(file)
end)
local server_session
finally(function()
if server_session then
@@ -543,6 +623,8 @@ describe('TUI :restart', function()
server_pipe,
'--cmd',
'set notermguicolors',
'-S',
file,
'-s',
'-',
'-',
@@ -554,16 +636,18 @@ describe('TUI :restart', function()
^ |
~ |*3
{2:Xtest-file1 0,0-1 All}|
|
-S works |
{5:-- TERMINAL --} |
]])
server_session = n.connect(server_pipe)
local expr = 'index(v:argv, "-") >= 0 || index(v:argv, "--") >= 0 ? v:true : v:false'
local has_s = 'index(v:argv, "-s") >= 0 ? v:true : v:false'
local has_S = 'index(v:argv, "-S") >= 0 ? v:true : v:false'
eq({ true, true }, { server_session:request('nvim_eval', expr) })
eq({ true, true }, { server_session:request('nvim_eval', has_s) })
eq({ true, true }, { server_session:request('nvim_eval', has_S) })
tt.feed_data(":restart put='foo'\013")
tt.feed_data(":restart! put='foo'\013")
screen:expect([[
|
^foo |
@@ -577,6 +661,7 @@ describe('TUI :restart', function()
eq({ true, false }, { server_session:request('nvim_eval', expr) })
eq({ true, false }, { server_session:request('nvim_eval', has_s) })
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)
@@ -603,7 +688,7 @@ describe('TUI :restart', function()
]])
-- 'laststatus' should be 0 in the new Nvim and FileType event should be triggered.
feed_data(':restart set nowrap | edit test/functional/fixtures/bigfile.txt\r')
feed_data(':restart! set nowrap | edit test/functional/fixtures/bigfile.txt\r')
screen:expect([[
^0000;<control>;Cc;0;BN;;;;;N;NULL;;;; |
0001;<control>;Cc;0;BN;;;;;N;START OF HEADING;;;; |
@@ -666,7 +751,7 @@ describe('TUI :restart', function()
{5:-- TERMINAL --} |
]])
feed_data(':restart echo "restarted"\r')
feed_data(':restart! echo "restarted"\r')
screen:expect([[
^ │0000;<control>;Cc;0;BN;;;;;N|
~ │0001;<control>;Cc;0;BN;;;;;N|
@@ -677,7 +762,7 @@ describe('TUI :restart', function()
{5:-- TERMINAL --} |
]])
feed_data(':set sessionoptions-=winsize | restart\r')
feed_data(':set sessionoptions-=winsize | restart!\r')
screen:expect([[
^ │0000;<control>;Cc;0;BN;;|
~ │0001;<control>;Cc;0;BN;;|
@@ -3034,8 +3119,8 @@ describe('TUI', function()
{ 'tty', 'tty' },
child_exec_lua('return { vim.uv.guess_handle(0), vim.uv.guess_handle(1) }')
)
-- Also works after :restart #38745
feed_data(':restart lua ={ vim.uv.guess_handle(0), vim.uv.guess_handle(1) }\r')
-- Also works after :restart! #38745
feed_data(':restart! lua ={ vim.uv.guess_handle(0), vim.uv.guess_handle(1) }\r')
screen:expect([[
^ |
{100:~ }|*3
@@ -4441,11 +4526,11 @@ describe('TUI client', function()
screen_server:expect(s1)
end)
it(':restart works when connecting to remote instance (with its own TUI)', function()
it(':restart! works when connecting to remote instance (with its own TUI)', function()
local _, screen_server, screen_client = start_tui_and_remote_client()
-- Both clients should attach to the new server.
feed_data(':restart +qall!\n')
feed_data(':restart! +qall!\n')
local screen_restarted = [[
^ |
{100:~ }|*3
@@ -4460,7 +4545,7 @@ describe('TUI client', function()
screen_client:expect({ any = 'GUI Running: 0' })
-- The :vsplit command should only be executed once.
feed_data(':restart vsplit\r')
feed_data(':restart! vsplit\r')
screen_restarted = [[
^ │ |
{100:~ }│{100:~ }|*3
@@ -4552,11 +4637,11 @@ describe('TUI client', function()
screen_client:expect({ any = 'GUI Running: 0' })
end)
it(':restart works when connecting to remote instance (--headless)', function()
it(':restart! works when connecting to remote instance (--headless)', function()
local _, server_pipe, screen_client = start_headless_server_and_client(false)
-- The client should attach to the new server and the original server should exit.
feed_data(':restart +qall!\n')
feed_data(':restart! +qall!\n')
screen_client:expect([[
^ |
{100:~ }|*4

View File

@@ -48,3 +48,11 @@ describe('v:argf', function()
eq({ abs1, abs2, abs3 }, n.eval('v:argf'))
end)
end)
describe('v:startreason', function()
it('is read-only and starts as "normal"', function()
n.clear { args = { '--cmd', 'let g:early_startreason = v:startreason' } }
eq('normal', eval('g:early_startreason'))
t.matches('E46', t.pcall_err(command, "let v:startreason = 'restart'"))
end)
end)