summaryrefslogtreecommitdiffstatshomepage
path: root/src
diff options
context:
space:
mode:
authorJustin M. Keyes <justinkz@gmail.com>2026-04-22 13:40:41 -0400
committerGitHub <noreply@github.com>2026-04-22 13:40:41 -0400
commit28ba06837281e14a7dc5ade143642d11475d2823 (patch)
treee60f0b3aa160e820d2fade9e3973281d7ddc581c /src
parentfb6aeaba2d3a38c7febd0a39cabd89685de11b9d (diff)
feat(:restart): v:starttime, v:exitreason #39282
Problem: - The `ZR` feature makes it more obvious that we need some sort of flag so that an `ExitPre` / `QuitPre` / `VimLeave` handler can handle restarts differently than a normal exit. For example, it's common that users want `:mksession` on restart, but perhaps not on a normal exit. - Nvim has no way to report its "uptime". Solution: - Introduce `v:starttime` - Introduce `v:exitreason`
Diffstat (limited to 'src')
-rw-r--r--src/nvim/eval/userfunc.c2
-rw-r--r--src/nvim/eval/vars.c2
-rw-r--r--src/nvim/eval_defs.h2
-rw-r--r--src/nvim/ex_docmd.c48
-rw-r--r--src/nvim/main.c8
-rw-r--r--src/nvim/vvars.lua41
6 files changed, 76 insertions, 27 deletions
diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c
index a16ff39e6f..b22e91864e 100644
--- a/src/nvim/eval/userfunc.c
+++ b/src/nvim/eval/userfunc.c
@@ -3520,7 +3520,7 @@ static void handle_defer_one(funccall_T *funccal)
ga_clear(&funccal->fc_defer);
}
-/// Called when exiting: call all defer functions.
+/// When exiting: call all ":defer" functions.
void invoke_all_defer(void)
{
for (funccall_T *fc = current_funccal; fc != NULL; fc = fc->fc_caller) {
diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c
index 4722524872..b2f300e5d1 100644
--- a/src/nvim/eval/vars.c
+++ b/src/nvim/eval/vars.c
@@ -215,6 +215,8 @@ static struct vimvar {
VV(VV_LUA, "lua", VAR_PARTIAL, VV_RO),
VV(VV_RELNUM, "relnum", VAR_NUMBER, VV_RO),
VV(VV_VIRTNUM, "virtnum", VAR_NUMBER, VV_RO),
+ VV(VV_STARTTIME, "starttime", VAR_NUMBER, VV_RO),
+ VV(VV_EXITREASON, "exitreason", VAR_STRING, VV_RO),
};
#undef VV
diff --git a/src/nvim/eval_defs.h b/src/nvim/eval_defs.h
index 365bddb00b..7cec3d6167 100644
--- a/src/nvim/eval_defs.h
+++ b/src/nvim/eval_defs.h
@@ -135,4 +135,6 @@ typedef enum {
VV_LUA,
VV_RELNUM,
VV_VIRTNUM,
+ VV_STARTTIME,
+ VV_EXITREASON,
} VimVarIndex;
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 1a29a61d22..0737e31642 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -4818,10 +4818,15 @@ static void ex_highlight(exarg_T *eap)
void not_exiting(bool save_exiting)
{
exiting = save_exiting;
+ set_vim_var_string(VV_EXITREASON, NULL, -1);
}
bool before_quit_autocmds(win_T *wp, bool quit_all, bool forceit)
{
+ // Set v:exitreason if not already set (e.g. by :restart).
+ if (*get_vim_var_str(VV_EXITREASON) == NUL) {
+ set_vim_var_string(VV_EXITREASON, S_LEN("quit"));
+ }
apply_autocmds(EVENT_QUITPRE, NULL, NULL, false, wp->w_buffer);
// Bail out when autocommands closed the window.
@@ -4830,6 +4835,7 @@ bool before_quit_autocmds(win_T *wp, bool quit_all, bool forceit)
if (!win_valid(wp)
|| curbuf_locked()
|| (wp->w_buffer->b_nwindows == 1 && wp->w_buffer->b_locked > 0)) {
+ set_vim_var_string(VV_EXITREASON, NULL, -1);
return true;
}
@@ -4842,6 +4848,7 @@ bool before_quit_autocmds(win_T *wp, bool quit_all, bool forceit)
if (!win_valid(wp)
|| curbuf_locked()
|| (curbuf->b_nwindows == 1 && curbuf->b_locked > 0)) {
+ set_vim_var_string(VV_EXITREASON, NULL, -1);
return true;
}
}
@@ -4976,15 +4983,10 @@ static void ex_restart(exarg_T *eap)
char **argv = xcalloc((size_t)argc + 3, sizeof(char *));
size_t i = 0;
- const char *listen_arg = NULL;
-#ifdef MSWIN
- // On Windows, don't pass --listen to new server (named pipe can't be reused immediately).
- // Instead pass the address via RPC, and new server will rebind to it after startup.
-# define HANDLE_LISTEN_ADDR listen_arg = addr; li = next_li; continue
-#else
-# define HANDLE_LISTEN_ADDR listen_arg = addr
-#endif
- TV_LIST_ITER_CONST(l, li, {
+ const char *listen_arg = NULL; // --listen arg given by user, if any.
+
+ // Build args to start the new Nvim, based on the current v:argv.
+ for (const listitem_T *li = l->lv_first; li != NULL; li = li->li_next) {
const char *arg = tv_get_string(TV_LIST_ITEM_TV(li));
// Drop "-- [files…]". Usually isn't wanted. User can :mksession instead.
if (i > 0 && strequal(arg, "--")) {
@@ -4992,16 +4994,22 @@ static void ex_restart(exarg_T *eap)
}
// Drop "-s <scriptfile>": skip the scriptfile arg too.
if (i > 0 && strequal(arg, "-s")) {
- li = TV_LIST_ITEM_NEXT(l, li);
+ li = li->li_next;
continue;
}
// The address after --listen may be in use by the current server.
if (i > 0 && strequal(arg, "--listen")) {
- listitem_T *next_li = TV_LIST_ITEM_NEXT(l, li);
+ const listitem_T *next_li = li->li_next;
if (next_li != NULL) {
const char *addr = tv_get_string(TV_LIST_ITEM_TV(next_li));
if (strstr(addr, ":") || strstr(addr, "/") || strstr(addr, "\\")) {
- HANDLE_LISTEN_ADDR;
+ listen_arg = addr;
+#ifdef MSWIN
+ // On Windows, don't pass --listen to new server (named pipe can't be reused immediately).
+ // Instead pass the address via RPC; new server rebinds after startup.
+ li = next_li;
+ continue;
+#endif
}
}
}
@@ -5019,12 +5027,11 @@ static void ex_restart(exarg_T *eap)
}
}
}
- });
-#undef HANDLE_LISTEN_ADDR
+ }
#ifdef MSWIN
// On Windows, --listen is omitted from child argv because the named pipe can't be reused immediately.
- // Recover the canonical address from the Lua module state (set by the previous rebind_old_addr_after_restart() call),
+ // Recover the canonical address from the Lua module state (set by the previous rebind_after_restart() call),
// and keep the current listener alive (new server reclaims it).
char *listen_arg_alloc = NULL;
if (listen_arg == NULL) {
@@ -5104,8 +5111,7 @@ static void ex_restart(exarg_T *eap)
result_mem = NULL;
}
- // Get the new server's initial listen address. On Windows this is the
- // temporary bootstrap address that UIs should reconnect to first.
+ // Get the new server's initial address. On Windows this is the temporary self-generated address.
MAXSIZE_TEMP_ARRAY(servername_args, 1);
ADD_C(servername_args, CSTR_AS_OBJ("servername"));
Object result = rpc_send_call(channel->id, "nvim_get_vvar", servername_args, &result_mem, &err);
@@ -5116,6 +5122,7 @@ static void ex_restart(exarg_T *eap)
emsg("restart failed: could not get listen address from new server");
goto fail_2;
}
+ // New server's self-generated address.
char *listen_addr = xmemdupz(result.data.string.data, result.data.string.size);
arena_mem_free(result_mem);
result_mem = NULL;
@@ -5126,10 +5133,9 @@ static void ex_restart(exarg_T *eap)
// then retire the bootstrap address after all UIs have reattached (or timeout).
MAXSIZE_TEMP_ARRAY(lua_args, 2);
ADD_C(lua_args,
- CSTR_AS_OBJ("return require('vim._core.server').rebind_old_addr_after_restart(...)"));
+ CSTR_AS_OBJ("return require('vim._core.server').rebind_after_restart(...)"));
MAXSIZE_TEMP_ARRAY(handoff_params, 3);
ADD_C(handoff_params, CSTR_AS_OBJ(listen_arg));
- ADD_C(handoff_params, CSTR_AS_OBJ(listen_addr));
ADD_C(handoff_params, INTEGER_OBJ((Integer)ui_active()));
ADD_C(lua_args, ARRAY_OBJ(handoff_params));
rpc_send_call(channel->id, "nvim_exec_lua", lua_args, &result_mem, &err);
@@ -5146,6 +5152,8 @@ static void ex_restart(exarg_T *eap)
ui_flush();
xfree(listen_addr);
+ 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;
@@ -5154,6 +5162,7 @@ static void ex_restart(exarg_T *eap)
quit_cmd_copy = concat_str("confirm ", quit_cmd);
quit_cmd = quit_cmd_copy;
}
+ // Try to quit.
nvim_command(cstr_as_string(quit_cmd), &err);
xfree(quit_cmd_copy);
@@ -5165,6 +5174,7 @@ static void ex_restart(exarg_T *eap)
}
fail_2:
+ set_vim_var_string(VV_EXITREASON, NULL, -1);
if (ERROR_SET(&err)) {
emsg(err.msg);
api_clear_error(&err);
diff --git a/src/nvim/main.c b/src/nvim/main.c
index 62500066e9..08e741b3a7 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -197,6 +197,7 @@ void early_init(mparm_T *paramp)
estack_init();
cmdline_init();
eval_init(); // init global variables
+ set_vim_var_nr(VV_STARTTIME, (varnumber_T)os_hrtime());
init_path(argv0 ? argv0 : "nvim");
init_normal_cmds(); // Init the table of Normal mode commands.
runtime_init();
@@ -763,7 +764,12 @@ void getout(int exitval)
set_vim_var_type(VV_EXITING, VAR_NUMBER);
set_vim_var_nr(VV_EXITING, exitval);
- // Invoked all deferred functions in the function stack.
+ // Set v:exitreason if not already set (e.g. by :restart).
+ if (*get_vim_var_str(VV_EXITREASON) == NUL) {
+ set_vim_var_string(VV_EXITREASON, S_LEN("quit"));
+ }
+
+ // Invoked all ":defer" functions in the function stack.
invoke_all_defer();
// Optionally print hashtable efficiency.
diff --git a/src/nvim/vvars.lua b/src/nvim/vvars.lua
index 26a62830d1..648e5f1310 100644
--- a/src/nvim/vvars.lua
+++ b/src/nvim/vvars.lua
@@ -268,10 +268,27 @@ M.vars = {
Exit code, or |v:null| before invoking the |VimLeavePre|
and |VimLeave| autocmds. See |:q|, |:x| and |:cquit|.
Example: >vim
- :au VimLeave * echo "Exit value is " .. v:exiting
+ :au VimLeave * echo "Exit code is " .. v:exiting
<
]=],
},
+ exitreason = {
+ type = 'string',
+ desc = [=[
+ Reason for the current exit. Set before |QuitPre|. Reset if
+ exit was canceled.
+
+ Possible values:
+ - "" Not exiting, or exit was canceled.
+ - "quit" |:quit|, |:qall|, |:wq|, |ZZ|, |ZQ|, etc.
+ - "restart" |:restart|, |ZR|.
+
+ Example: >vim
+ autocmd ExitPre * if v:exitreason ==# 'restart' | echomsg 'restarting' | endif
+ <
+ Read-only.
+ ]=],
+ },
fcs_choice = {
type = 'string',
desc = [=[
@@ -301,12 +318,12 @@ M.vars = {
The reason why the |FileChangedShell| event was triggered.
Can be used in an autocommand to decide what to do and/or what
to set v:fcs_choice to. Possible values:
- deleted file no longer exists
- conflict file contents, mode or timestamp was
+ - deleted file no longer exists
+ - conflict file contents, mode or timestamp was
changed and buffer is modified
- changed file contents has changed
- mode mode of file changed
- time only file timestamp changed
+ - changed file contents has changed
+ - mode mode of file changed
+ - time only file timestamp changed
]=],
},
fname = {
@@ -736,6 +753,18 @@ M.vars = {
|throw-variables|.
]=],
},
+ starttime = {
+ type = 'integer',
+ desc = [=[
+ Timestamp (monotonic nanoseconds) when the Nvim process
+ started.
+
+ To see the current "uptime": >lua
+ vim.print(('uptime: %d seconds'):format((vim.uv.hrtime() - vim.v.starttime) / 1e9))
+ <
+ Read-only.
+ ]=],
+ },
statusmsg = {
type = 'string',
desc = [=[