feat(:restart): v:startreason #40186

Problem:
It's clumsy for scripts to handle a "restart", without custom mappings or
global vars.

Solution:
Introduce `v:startreason`

(cherry picked from commit ae426ee465)
This commit is contained in:
Nathan Zeng
2026-06-18 12:49:12 -07:00
committed by Olivia Kinnear
parent a4985ac902
commit 611b4f8237
12 changed files with 69 additions and 3 deletions

View File

@@ -81,7 +81,8 @@ 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).
`-- [file…]` files). Sets |v:startreason| to "restart" on the
new server.
3. Attaches all UIs to the new Nvim server and runs `[command]`
on it.

View File

@@ -453,6 +453,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,14 @@ 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| or |ZR|.
Read-only.
*v:starttime* *starttime-variable*
v:starttime
Timestamp (nanoseconds since UNIX epoch) when the Nvim process

View File

@@ -667,6 +667,14 @@ vim.v.shell_error = ...
--- @type table[]
vim.v.stacktrace = ...
--- The reason Nvim started. Possible values:
--- - "normal" normal startup.
--- - "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

@@ -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, "normal") || 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

@@ -5057,6 +5057,10 @@ static void ex_restart(exarg_T *eap)
restart_alloc_console_env = true;
}
#endif
bool startreason_env = false;
if (os_setenv(ENV_STARTREASON, "restart", 1) == 0) {
startreason_env = true;
}
CallbackReader on_err = CALLBACK_READER_INIT;
#ifdef MSWIN
@@ -5073,6 +5077,9 @@ static void ex_restart(exarg_T *eap)
CALLBACK_READER_INIT, on_err, CALLBACK_NONE,
false, true, true, detach, kChannelStdinPipe,
NULL, 0, 0, NULL, &exit_status);
if (startreason_env) {
os_unsetenv(ENV_STARTREASON);
}
#ifdef MSWIN
if (restart_alloc_console_env) {
os_unsetenv("__NVIM_RESTART_ALLOC_CONSOLE");

View File

@@ -26,3 +26,4 @@ extern char *default_lib_dir;
#define ENV_LOGFILE "NVIM_LOG_FILE"
#define ENV_NVIM "NVIM"
#define ENV_STARTREASON "__NVIM_STARTREASON"

View File

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

View File

@@ -364,7 +364,15 @@ 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)
@@ -373,6 +381,8 @@ it(':restart works in headless server (no UI)', function()
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')

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)