mirror of
https://github.com/neovim/neovim.git
synced 2026-06-16 00:31:16 +00:00
feat(:restart): v:starttime, v:exitreason #39319
This commit is contained in:
@@ -59,17 +59,16 @@ Stop or detach the current UI
|
||||
>vim
|
||||
:echo v:servername
|
||||
<
|
||||
Note: The server closes the UI RPC channel, so :detach
|
||||
inherently "works" for all UIs. But if a UI isn't expecting
|
||||
the channel to be closed, it may be (incorrectly) reported as
|
||||
an error.
|
||||
Note: The server closes the UI RPC channel, so :detach works
|
||||
for any UI client. But if the client isn't expecting this, it
|
||||
may (incorrectly) report an error.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
Restart Nvim
|
||||
|
||||
*:restart*
|
||||
:restart [+cmd] [command]
|
||||
Restarts Nvim. See also |ZR|.
|
||||
Restarts Nvim. Sets |v:exitreason|. See also |ZR|.
|
||||
|
||||
1. Stops Nvim using `:qall` (or |+cmd|, if given).
|
||||
2. Starts a new Nvim server using the same |v:argv| (except
|
||||
@@ -84,9 +83,12 @@ Restart Nvim
|
||||
< Example: restart and update plugins: >
|
||||
:restart lua vim.pack.update()
|
||||
<
|
||||
Note: Only works if the UI and server are on the same system.
|
||||
Note: If no attached UI implements the "restart" UI event,
|
||||
this command will lead to a dangling server process.
|
||||
Note:
|
||||
• 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.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
Connect UI to a different server
|
||||
|
||||
@@ -440,6 +440,8 @@ VIMSCRIPT
|
||||
• |wildtrigger()| triggers command-line expansion.
|
||||
• |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:starttime| is the process start time (monotonic nanoseconds).
|
||||
|
||||
==============================================================================
|
||||
CHANGED FEATURES *news-changed*
|
||||
|
||||
@@ -434,30 +434,32 @@ Initialization *initialization* *startup*
|
||||
At startup, Nvim checks environment variables and files and sets values
|
||||
accordingly, proceeding as follows:
|
||||
|
||||
1. Set the 'shell' option. *SHELL* *COMSPEC*
|
||||
1. Set |v:starttime|.
|
||||
|
||||
2. Set the 'shell' option. *SHELL* *COMSPEC*
|
||||
The environment variable SHELL, if it exists, is used to set the 'shell'
|
||||
option. On Win32, the COMSPEC variable is used if SHELL is not set.
|
||||
|
||||
2. Process the arguments.
|
||||
3. Process the arguments.
|
||||
The options and file names from the command that start Vim are inspected.
|
||||
The |-V| argument can be used to display or log what happens next, useful
|
||||
for debugging the initializations. The |--cmd| arguments are executed.
|
||||
Buffers are created for all files (but not loaded yet).
|
||||
|
||||
3. Start a server (unless |--listen| was given) and set |v:servername|.
|
||||
4. Start a server (unless |--listen| was given) and set |v:servername|.
|
||||
|
||||
4. Wait for UI to connect.
|
||||
5. Wait for UI to connect.
|
||||
Nvim started with |--embed| waits for the UI to connect before proceeding
|
||||
to load user configuration.
|
||||
|
||||
5. Setup |default-mappings| and |default-autocmds|. Create |popup-menu|.
|
||||
6. Setup |default-mappings| and |default-autocmds|. Create |popup-menu|.
|
||||
|
||||
6. Enable filetype and indent plugins.
|
||||
7. Enable filetype and indent plugins.
|
||||
Skipped if the "-u NONE" command line argument was given.
|
||||
This does the same as: >
|
||||
:runtime! ftplugin.vim indent.vim
|
||||
|
||||
7. Load user config (execute Ex commands from files, environment, …).
|
||||
8. Load user config (execute Ex commands from files, environment, …).
|
||||
$VIMINIT environment variable is read as one Ex command line (separate
|
||||
multiple commands with '|' or <NL>).
|
||||
*config* *init.vim* *init.lua* *vimrc* *exrc*
|
||||
@@ -499,19 +501,19 @@ accordingly, proceeding as follows:
|
||||
- ".nvimrc"
|
||||
- ".exrc"
|
||||
|
||||
8. Enable filetype detection.
|
||||
9. Enable filetype detection.
|
||||
Skipped if ":filetype off" was called or if the "-u NONE" command line
|
||||
argument was given. This does the same as: >
|
||||
:runtime! filetype.lua
|
||||
|
||||
9. Enable syntax highlighting.
|
||||
10. Enable syntax highlighting.
|
||||
Skipped if ":syntax off" was called or if the "-u NONE" command line
|
||||
argument was given. This does the same as: >
|
||||
:runtime! syntax/syntax.vim
|
||||
|
||||
10. Set the |v:vim_did_init| variable to 1.
|
||||
11. Set the |v:vim_did_init| variable to 1.
|
||||
|
||||
11. Load the plugin scripts. *load-plugins*
|
||||
12. Load the plugin scripts. *load-plugins*
|
||||
This does the same as:
|
||||
`:runtime! plugin/**/*.{vim,lua}`
|
||||
- The result is that all directories in 'runtimepath' will be searched for
|
||||
@@ -538,26 +540,26 @@ accordingly, proceeding as follows:
|
||||
if packages have been found, but that should not add a directory ending
|
||||
in "after".
|
||||
|
||||
12. Set 'shellpipe' and 'shellredir', according to the value of the 'shell'
|
||||
13. Set 'shellpipe' and 'shellredir', according to the value of the 'shell'
|
||||
option, unless they were already set. Nvim will figure out the values of
|
||||
'shellpipe' and 'shellredir' for you, unless you have set them yourself.
|
||||
|
||||
13. Set 'updatecount' to zero, if |-n| was given.
|
||||
14. Set 'updatecount' to zero, if |-n| was given.
|
||||
|
||||
14. Set binary options if |-b| was given.
|
||||
15. Set binary options if |-b| was given.
|
||||
|
||||
15. Read the |shada-file|.
|
||||
16. Read the |shada-file|.
|
||||
|
||||
16. Read the quickfix file if |-q| was given, or exit on failure.
|
||||
17. Read the quickfix file if |-q| was given, or exit on failure.
|
||||
|
||||
17. Open all windows.
|
||||
18. Open all windows.
|
||||
- When |-o| was given, windows will be opened (but not displayed yet).
|
||||
- When |-p| was given, tab pages will be created (but not displayed yet).
|
||||
- When switching screens, it happens now. Redrawing starts.
|
||||
- If |-q| was given, the first error is jumped to.
|
||||
- Buffers for all windows will be loaded, without triggering |BufAdd|.
|
||||
|
||||
18. Execute startup commands.
|
||||
19. Execute startup commands.
|
||||
- If |-t| was given, the tag is jumped to.
|
||||
- Commands given with |-c| and |+cmd| are executed.
|
||||
- The |vim_starting| flag is reset, `has("vim_starting")` will now return zero.
|
||||
|
||||
@@ -230,9 +230,24 @@ v:exiting
|
||||
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
|
||||
<
|
||||
|
||||
*v:exitreason* *exitreason-variable*
|
||||
v:exitreason
|
||||
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.
|
||||
|
||||
*v:false* *false-variable*
|
||||
v:false
|
||||
Special value used to put "false" in JSON and msgpack. See
|
||||
@@ -267,12 +282,12 @@ v:fcs_reason
|
||||
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
|
||||
|
||||
*v:fname* *fname-variable*
|
||||
v:fname
|
||||
@@ -618,6 +633,16 @@ v:stacktrace
|
||||
stack trace. See also |v:exception|, |v:throwpoint|, and
|
||||
|throw-variables|.
|
||||
|
||||
*v:starttime* *starttime-variable*
|
||||
v:starttime
|
||||
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.
|
||||
|
||||
*v:statusmsg* *statusmsg-variable*
|
||||
v:statusmsg
|
||||
Last given status message.
|
||||
|
||||
@@ -42,10 +42,10 @@ M.restart_canonical_addr = nil ---@type string?
|
||||
--- Windows named pipes can't be rebound immediately, so the new server starts on a
|
||||
--- temporary bootstrap address and polls until the canonical address is reclaimable.
|
||||
--- @param canonical_addr string The original --listen address to reclaim.
|
||||
--- @param bootstrap_addr string Temporary address the new server started on.
|
||||
--- @param expected_uis integer Number of UIs expected to reattach (0 = don't wait).
|
||||
function M.rebind_old_addr_after_restart(canonical_addr, bootstrap_addr, expected_uis)
|
||||
function M.rebind_after_restart(canonical_addr, expected_uis)
|
||||
M.restart_canonical_addr = canonical_addr
|
||||
local bootstrap_addr = vim.v.servername -- Temporary autogenerated address.
|
||||
local poll_ms = 50
|
||||
local max_wait_ms = 30000
|
||||
local timer = assert(vim.uv.new_timer())
|
||||
|
||||
43
runtime/lua/vim/_meta/vvars.gen.lua
generated
43
runtime/lua/vim/_meta/vvars.gen.lua
generated
@@ -244,11 +244,29 @@ vim.v.exception = ...
|
||||
--- Example:
|
||||
---
|
||||
--- ```vim
|
||||
--- :au VimLeave * echo "Exit value is " .. v:exiting
|
||||
--- :au VimLeave * echo "Exit code is " .. v:exiting
|
||||
--- ```
|
||||
--- @type integer?
|
||||
vim.v.exiting = ...
|
||||
|
||||
--- 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.
|
||||
--- @type string
|
||||
vim.v.exitreason = ...
|
||||
|
||||
--- Special value used to put "false" in JSON and msgpack. See
|
||||
--- `json_encode()`. This value is converted to "v:false" when used
|
||||
--- as a String (e.g. in `expr5` with string concatenation
|
||||
@@ -281,12 +299,12 @@ vim.v.fcs_choice = ...
|
||||
--- 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
|
||||
--- @type string
|
||||
vim.v.fcs_reason = ...
|
||||
|
||||
@@ -649,6 +667,19 @@ vim.v.shell_error = ...
|
||||
--- @type table[]
|
||||
vim.v.stacktrace = ...
|
||||
|
||||
--- 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.
|
||||
--- @type integer
|
||||
vim.v.starttime = ...
|
||||
|
||||
--- Last given status message.
|
||||
--- Modifiable (can be set).
|
||||
--- @type string
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -135,4 +135,6 @@ typedef enum {
|
||||
VV_LUA,
|
||||
VV_RELNUM,
|
||||
VV_VIRTNUM,
|
||||
VV_STARTTIME,
|
||||
VV_EXITREASON,
|
||||
} VimVarIndex;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 = [=[
|
||||
|
||||
@@ -97,7 +97,7 @@ function SocketStream:write(data)
|
||||
end
|
||||
uv.write(self._socket, data, function(err)
|
||||
if err then
|
||||
error(self._stream_error or err)
|
||||
self._stream_error = self._stream_error or err
|
||||
end
|
||||
end)
|
||||
end
|
||||
@@ -108,7 +108,9 @@ function SocketStream:read_start(cb)
|
||||
end
|
||||
uv.read_start(self._socket, function(err, chunk)
|
||||
if err then
|
||||
error(err)
|
||||
self._stream_error = self._stream_error or err
|
||||
cb(nil) -- Signal EOF so the session layer stops.
|
||||
return
|
||||
end
|
||||
cb(chunk)
|
||||
end)
|
||||
|
||||
@@ -12,7 +12,7 @@ local pcall_err = t.pcall_err
|
||||
local exec_capture = n.exec_capture
|
||||
local poke_eventloop = n.poke_eventloop
|
||||
|
||||
describe('v:exiting', function()
|
||||
describe('exit:', function()
|
||||
local cid
|
||||
|
||||
before_each(function()
|
||||
@@ -20,39 +20,51 @@ describe('v:exiting', function()
|
||||
cid = n.api.nvim_get_chan_info(0).id
|
||||
end)
|
||||
|
||||
it('defaults to v:null', function()
|
||||
it('v:exiting defaults to v:null', function()
|
||||
eq(1, eval('v:exiting is v:null'))
|
||||
eq('', eval('v:exitreason'))
|
||||
end)
|
||||
|
||||
local function test_exiting(setup_fn)
|
||||
local function on_setup()
|
||||
command('autocmd VimLeavePre * call rpcrequest(' .. cid .. ', "exit", "VimLeavePre")')
|
||||
command('autocmd VimLeave * call rpcrequest(' .. cid .. ', "exit", "VimLeave")')
|
||||
command(('autocmd QuitPre * call rpcrequest(%d, "exit", "QuitPre")'):format(cid))
|
||||
command(('autocmd ExitPre * call rpcrequest(%d, "exit", "ExitPre")'):format(cid))
|
||||
command(('autocmd VimLeavePre * call rpcrequest(%d, "exit", "VimLeavePre")'):format(cid))
|
||||
command(('autocmd VimLeave * call rpcrequest(%d, "exit", "VimLeave")'):format(cid))
|
||||
setup_fn()
|
||||
end
|
||||
local requests_args = {}
|
||||
local received = {}
|
||||
local function on_request(name, args)
|
||||
eq('exit', name)
|
||||
table.insert(requests_args, args)
|
||||
eq(0, eval('v:exiting'))
|
||||
table.insert(received, args)
|
||||
eq('quit', eval('v:exitreason'))
|
||||
if args[1] == 'VimLeavePre' or args[1] == 'VimLeave' then
|
||||
eq(0, eval('v:exiting'))
|
||||
end
|
||||
return ''
|
||||
end
|
||||
run(on_request, nil, on_setup)
|
||||
eq({ { 'VimLeavePre' }, { 'VimLeave' } }, requests_args)
|
||||
eq({ { 'QuitPre' }, { 'ExitPre' }, { 'VimLeavePre' }, { 'VimLeave' } }, received)
|
||||
end
|
||||
|
||||
it('is 0 on normal exit', function()
|
||||
it('v:exiting=0, v:exitreason=quit on normal exit', function()
|
||||
test_exiting(function()
|
||||
command('quit')
|
||||
end)
|
||||
end)
|
||||
|
||||
it('is 0 on exit from Ex mode involving try-catch vim-patch:8.0.0184', function()
|
||||
it('v:exiting=0, v:exitreason=quit on exit from Ex mode try-catch vim-patch:8.0.0184', function()
|
||||
test_exiting(function()
|
||||
feed('gQ')
|
||||
feed_command('try', 'call NoFunction()', 'catch', 'echo "bye"', 'endtry', 'quit')
|
||||
end)
|
||||
end)
|
||||
|
||||
it('resets v:exitreason if quit is cancelled', function()
|
||||
n.api.nvim_buf_set_lines(0, 0, -1, true, { 'modified' })
|
||||
pcall_err(command, 'quit')
|
||||
eq('', eval('v:exitreason'))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe(':cquit', function()
|
||||
|
||||
@@ -217,7 +217,7 @@ describe('vim._core', function()
|
||||
|
||||
-- All `vim._core.*` modules are builtin.
|
||||
t.eq(
|
||||
{ 'rebind_old_addr_after_restart', 'serverlist' },
|
||||
{ 'rebind_after_restart', 'serverlist' },
|
||||
n.exec_lua([[local k = vim.tbl_keys(require('vim._core.server')); table.sort(k); return k]])
|
||||
)
|
||||
local expected = {
|
||||
|
||||
@@ -207,22 +207,24 @@ describe('TUI :restart', function()
|
||||
before_each(n.clear)
|
||||
after_each(n.check_close)
|
||||
|
||||
---@param exp boolean Restart expected
|
||||
--- Asserts that the server at `addr` has (or has not) restarted since `starttime`.
|
||||
---@param starttime integer v:starttime of the server before restart
|
||||
---@param sess table Session
|
||||
---@param addr string
|
||||
local function assert_restarted(exp, sess, addr)
|
||||
local s = sess
|
||||
---@return integer starttime
|
||||
---@return table session
|
||||
local function assert_restarted(starttime, sess, addr)
|
||||
sess:close()
|
||||
local new_starttime
|
||||
-- Retry: old server may still be alive (connect succeeds but yields stale starttime),
|
||||
-- or on Windows the --listen address is restored async.
|
||||
retry(nil, 5000, function()
|
||||
if exp then
|
||||
s:close()
|
||||
s = n.connect(addr)
|
||||
end
|
||||
|
||||
-- Cheesy but reliable: :restart drops "-- [files…]", so empty v:argf means restart happened.
|
||||
-- TODO(justinmk): add `v:startreason`, `v:starttime`
|
||||
local _, argf = s:request('nvim_eval', 'v:argf')
|
||||
ok(vim.tbl_count(argf) == (exp and 0 or 1), exp and 'empty v:argf' or 'nonempty v:argf', argf)
|
||||
sess = n.connect(addr)
|
||||
local _, t = sess:request('nvim_eval', 'v:starttime')
|
||||
ok(t > starttime, ('v:starttime (%d) > old starttime (%d)'):format(t, starttime), t)
|
||||
new_starttime = t
|
||||
end)
|
||||
return new_starttime, sess
|
||||
end
|
||||
|
||||
it('validation', function()
|
||||
@@ -231,25 +233,19 @@ describe('TUI :restart', function()
|
||||
|
||||
it('ZR', function()
|
||||
-- Just exercise ZR, don't need to test all :restart functionality here.
|
||||
t.skip(is_os('win'), 'FIXME: --listen not preserved by :restart on Windows #38539')
|
||||
local server_pipe = new_pipename()
|
||||
local server_session
|
||||
finally(function()
|
||||
if server_session and not server_session.closed then
|
||||
if server_session then
|
||||
server_session:close()
|
||||
end
|
||||
end)
|
||||
local screen = tt.setup_child_nvim({
|
||||
'-u',
|
||||
'NONE',
|
||||
'-i',
|
||||
'NONE',
|
||||
'--clean',
|
||||
'--listen',
|
||||
server_pipe,
|
||||
'--cmd',
|
||||
'set notermguicolors',
|
||||
'--',
|
||||
'file1.txt', -- XXX: see comment in assert_restarted()
|
||||
}, {
|
||||
env = vim.tbl_extend('force', env_notermguicolors, {
|
||||
-- Ignore logs, because assert_restarted may log "connection refused" while it retries.
|
||||
@@ -259,10 +255,10 @@ describe('TUI :restart', function()
|
||||
finally(function()
|
||||
os.remove(testlog)
|
||||
end)
|
||||
screen:expect({ any = 'file1%.txt' })
|
||||
screen:expect({ any = '%[No Name%]' })
|
||||
|
||||
server_session = n.connect(server_pipe)
|
||||
assert_restarted(false, server_session, server_pipe)
|
||||
local _, starttime = server_session:request('nvim_eval', 'v:starttime')
|
||||
|
||||
-- ZR on modified buffer fails with E37.
|
||||
tt.feed_data('ifoo\027')
|
||||
@@ -272,26 +268,47 @@ describe('TUI :restart', function()
|
||||
-- [count]ZR discards unsaved changes.
|
||||
tt.feed_data('1ZR')
|
||||
screen:expect({ any = vim.pesc('[No Name]') })
|
||||
assert_restarted(true, server_session, server_pipe)
|
||||
starttime, server_session = assert_restarted(starttime, server_session, server_pipe)
|
||||
|
||||
-- The server is now detached and needs to be quit explicitly.
|
||||
tt.feed_data(':qall!\r')
|
||||
end)
|
||||
|
||||
it('works', function()
|
||||
-- Log exit events + v:exitreason.
|
||||
local eventlog = t.tmpname()
|
||||
local initfile = t.tmpname() .. '.lua'
|
||||
local init = [[
|
||||
local f = %q
|
||||
vim.api.nvim_create_autocmd({ 'QuitPre', 'ExitPre', 'VimLeavePre', 'VimLeave' }, {
|
||||
callback = function(ev)
|
||||
vim.fn.writefile({ ('%%s:%%s'):format(ev.event, vim.v.exitreason) }, f, 'a')
|
||||
end,
|
||||
})
|
||||
]]
|
||||
write_file(initfile, init:format(eventlog))
|
||||
finally(function()
|
||||
os.remove(eventlog)
|
||||
os.remove(initfile)
|
||||
end)
|
||||
|
||||
local function assert_exitreason(expected)
|
||||
local default = 'QuitPre:restart\nExitPre:restart\nVimLeavePre:restart\nVimLeave:restart\n'
|
||||
eq(expected or default, t.read_file(eventlog))
|
||||
os.remove(eventlog)
|
||||
end
|
||||
|
||||
local server_pipe = new_pipename()
|
||||
local screen = tt.setup_child_nvim({
|
||||
'--clean',
|
||||
'-u',
|
||||
'NONE',
|
||||
'-i',
|
||||
'NONE',
|
||||
initfile,
|
||||
'--listen',
|
||||
server_pipe,
|
||||
'--cmd',
|
||||
'colorscheme vim',
|
||||
'--cmd',
|
||||
'set laststatus=2 background=dark noruler noshowcmd',
|
||||
-- XXX: New server starts before the UI connects to it.
|
||||
-- So checking screen state for this pid is not possible.
|
||||
-- '--cmd',
|
||||
-- 'echo getpid()',
|
||||
}, { env = { COLORTERM = 'truecolor' } })
|
||||
screen:set_option('rgb', true)
|
||||
|
||||
@@ -331,13 +348,6 @@ describe('TUI :restart', function()
|
||||
server_pid = new_pid
|
||||
end
|
||||
|
||||
--- XXX: No longer using -c <command> during new server startup.
|
||||
--- Gets the last `argn` items in v:argv as a joined string.
|
||||
-- local function get_argv(argn)
|
||||
-- local argv = ({ server_session:request('nvim_eval', 'v:argv') })[2] --[[@type table]]
|
||||
-- return table.concat(argv, ' ', #argv - argn, #argv)
|
||||
-- end
|
||||
|
||||
local s1 = [[
|
||||
|
|
||||
^Hello1 |
|
||||
@@ -352,6 +362,7 @@ describe('TUI :restart', function()
|
||||
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.
|
||||
@@ -366,6 +377,7 @@ describe('TUI :restart', function()
|
||||
{5:-- TERMINAL --} |
|
||||
]])
|
||||
assert_new_pid()
|
||||
assert_exitreason()
|
||||
assert_termguicolors_and_no_gui_running()
|
||||
|
||||
-- Check ":restart" on an unmodified buffer.
|
||||
@@ -373,12 +385,14 @@ describe('TUI :restart', function()
|
||||
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')
|
||||
screen:expect(s0)
|
||||
assert_new_pid()
|
||||
assert_exitreason()
|
||||
assert_termguicolors_and_no_gui_running()
|
||||
|
||||
-- Check ":restart +echo" cannot restart server.
|
||||
@@ -402,6 +416,8 @@ describe('TUI :restart', function()
|
||||
-- Cancel the operation (abandons restart).
|
||||
tt.feed_data('C\013')
|
||||
screen:expect({ any = vim.pesc('[No Name]') })
|
||||
-- Failed/cancelled restarts still fire QuitPre/ExitPre (but not VimLeave[Pre]).
|
||||
assert_exitreason('QuitPre:restart\nExitPre:restart\n')
|
||||
|
||||
-- Check :restart respects 'confirm' option.
|
||||
tt.feed_data(':set confirm\013')
|
||||
@@ -410,6 +426,8 @@ describe('TUI :restart', function()
|
||||
tt.feed_data('C\013')
|
||||
screen:expect({ any = vim.pesc('[No Name]') })
|
||||
tt.feed_data(':set noconfirm\013')
|
||||
-- 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")
|
||||
@@ -417,6 +435,7 @@ describe('TUI :restart', function()
|
||||
tt.feed_data('N\013')
|
||||
screen:expect({ any = '%^Hello3' })
|
||||
assert_new_pid()
|
||||
assert_exitreason()
|
||||
assert_termguicolors_and_no_gui_running()
|
||||
|
||||
-- Check ":confirm restart +echo" correctly ignores ":confirm"
|
||||
@@ -427,12 +446,14 @@ describe('TUI :restart', function()
|
||||
tt.feed_data('ithis will be removed\027')
|
||||
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.
|
||||
tt.feed_data('ithis will be removed\027')
|
||||
tt.feed_data(':restart +qall!\013')
|
||||
screen:expect(s0)
|
||||
assert_new_pid()
|
||||
assert_exitreason()
|
||||
assert_termguicolors_and_no_gui_running()
|
||||
|
||||
if not is_os('win') then
|
||||
@@ -440,6 +461,7 @@ describe('TUI :restart', function()
|
||||
feed_data(':lua vim.schedule(function() vim.wait(100) end); vim.cmd.restart()\n')
|
||||
screen:expect(s0)
|
||||
assert_new_pid()
|
||||
assert_exitreason()
|
||||
assert_termguicolors_and_no_gui_running()
|
||||
end
|
||||
|
||||
@@ -462,6 +484,7 @@ describe('TUI :restart', function()
|
||||
{5:-- TERMINAL --} |
|
||||
]])
|
||||
assert_new_pid()
|
||||
assert_exitreason()
|
||||
assert_termguicolors_and_no_gui_running()
|
||||
|
||||
-- The server is now detached and needs to be quit explicitly.
|
||||
|
||||
Reference in New Issue
Block a user