mirror of
https://github.com/neovim/neovim.git
synced 2026-08-28 10:01:49 +00:00
Merge pull request #40606 from superatomic/backport-seamless-restart
backport: `v:startreason` and ":restart" (no bang) saves/restores session
This commit is contained in:
@@ -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*
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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).
|
||||
|
||||
==============================================================================
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
9
runtime/lua/vim/_meta/vvars.gen.lua
generated
9
runtime/lua/vim/_meta/vvars.gen.lua
generated
@@ -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.
|
||||
---
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -137,4 +137,5 @@ typedef enum {
|
||||
VV_VIRTNUM,
|
||||
VV_STARTTIME,
|
||||
VV_EXITREASON,
|
||||
VV_STARTREASON,
|
||||
} VimVarIndex;
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = [=[
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user