diff options
| author | Justin M. Keyes <justinkz@gmail.com> | 2026-04-22 13:40:41 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-04-22 13:40:41 -0400 |
| commit | 28ba06837281e14a7dc5ade143642d11475d2823 (patch) | |
| tree | e60f0b3aa160e820d2fade9e3973281d7ddc581c /src | |
| parent | fb6aeaba2d3a38c7febd0a39cabd89685de11b9d (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.c | 2 | ||||
| -rw-r--r-- | src/nvim/eval/vars.c | 2 | ||||
| -rw-r--r-- | src/nvim/eval_defs.h | 2 | ||||
| -rw-r--r-- | src/nvim/ex_docmd.c | 48 | ||||
| -rw-r--r-- | src/nvim/main.c | 8 | ||||
| -rw-r--r-- | src/nvim/vvars.lua | 41 |
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 = [=[ |
