From 611b4f8237e6d49e6515b7318e8da2e07d451ef2 Mon Sep 17 00:00:00 2001 From: Nathan Zeng Date: Thu, 18 Jun 2026 12:49:12 -0700 Subject: [PATCH 1/5] feat(:restart): v:startreason #40186 Problem: It's clumsy for scripts to handle a "restart", without custom mappings or global vars. Solution: Introduce `v:startreason` (cherry picked from commit ae426ee4656687472daf6886db66cd9905d1a1c5) --- runtime/doc/gui.txt | 3 ++- runtime/doc/news.txt | 1 + runtime/doc/starting.txt | 2 +- runtime/doc/vvars.txt | 8 ++++++++ runtime/lua/vim/_meta/vvars.gen.lua | 8 ++++++++ src/nvim/eval/vars.c | 11 +++++++++++ src/nvim/eval_defs.h | 1 + src/nvim/ex_docmd.c | 7 +++++++ src/nvim/os/os.h | 1 + src/nvim/vvars.lua | 10 ++++++++++ test/functional/core/server_spec.lua | 12 +++++++++++- test/functional/vimscript/vvars_spec.lua | 8 ++++++++ 12 files changed, 69 insertions(+), 3 deletions(-) diff --git a/runtime/doc/gui.txt b/runtime/doc/gui.txt index b21f072e33..c24c21d133 100644 --- a/runtime/doc/gui.txt +++ b/runtime/doc/gui.txt @@ -81,7 +81,8 @@ Restart Nvim 1. Stops Nvim using `:qall` (or |+cmd|, if given). 2. Starts a new Nvim server using the same |v:argv| (except - `-- [file…]` files). + `-- [file…]` files). Sets |v:startreason| to "restart" on the + new server. 3. Attaches all UIs to the new Nvim server and runs `[command]` on it. diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 1ab7346ff0..573691de4f 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -453,6 +453,7 @@ VIMSCRIPT • |v:vim_did_init| is set after sourcing |init.vim| but before |load-plugins|. • |prompt_appendbuf()| appends text to prompt-buffer. • |v:exitreason| is set before |QuitPre|. +• |v:startreason| differentiates between restart and normal start. • |v:starttime| is the process start time (nanoseconds since UNIX epoch). ============================================================================== diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index ce79b9416d..85a4de3eae 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -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' diff --git a/runtime/doc/vvars.txt b/runtime/doc/vvars.txt index e61bc78624..6e829f8cb7 100644 --- a/runtime/doc/vvars.txt +++ b/runtime/doc/vvars.txt @@ -633,6 +633,14 @@ v:stacktrace stack trace. See also |v:exception|, |v:throwpoint|, and |throw-variables|. + *v:startreason* *startreason-variable* +v:startreason + The reason Nvim started. Possible values: + - "normal" normal startup. + - "restart" started by |:restart| or |ZR|. + + Read-only. + *v:starttime* *starttime-variable* v:starttime Timestamp (nanoseconds since UNIX epoch) when the Nvim process diff --git a/runtime/lua/vim/_meta/vvars.gen.lua b/runtime/lua/vim/_meta/vvars.gen.lua index 543d4169c1..2b97a1f8f8 100644 --- a/runtime/lua/vim/_meta/vvars.gen.lua +++ b/runtime/lua/vim/_meta/vvars.gen.lua @@ -667,6 +667,14 @@ vim.v.shell_error = ... --- @type table[] vim.v.stacktrace = ... +--- The reason Nvim started. Possible values: +--- - "normal" normal startup. +--- - "restart" started by `:restart` or `ZR`. +--- +--- Read-only. +--- @type string +vim.v.startreason = ... + --- Timestamp (nanoseconds since UNIX epoch) when the Nvim process --- started. --- diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c index 803b121133..4e467b9576 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -217,6 +217,7 @@ static struct vimvar { VV(VV_VIRTNUM, "virtnum", VAR_NUMBER, VV_RO), VV(VV_STARTTIME, "starttime", VAR_NUMBER, VV_RO), VV(VV_EXITREASON, "exitreason", VAR_STRING, VV_RO), + VV(VV_STARTREASON, "startreason", VAR_STRING, VV_RO), }; #undef VV @@ -316,6 +317,7 @@ void evalvars_init(void) set_vim_var_nr(VV_SEARCHFORWARD, 1); set_vim_var_nr(VV_HLSEARCH, 1); set_vim_var_nr(VV_COUNT1, 1); + set_vim_var_string(VV_STARTREASON, S_LEN("normal")); set_vim_var_special(VV_EXITING, kSpecialVarNull); set_vim_var_nr(VV_TYPE_NUMBER, VAR_TYPE_NUMBER); @@ -345,6 +347,15 @@ void evalvars_init(void) set_vim_var_partial(VV_LUA, vvlua_partial); set_reg_var(0); // default for v:register is not 0 but '"' + + // Set v:startreason via environment variable + const char *startreason = os_getenv_noalloc(ENV_STARTREASON); + if (strequal(startreason, "normal") || strequal(startreason, "restart")) { + set_vim_var_string(VV_STARTREASON, startreason, -1); + } + if (os_env_exists(ENV_STARTREASON, false)) { + os_unsetenv(ENV_STARTREASON); + } } #if defined(EXITFREE) diff --git a/src/nvim/eval_defs.h b/src/nvim/eval_defs.h index 7cec3d6167..f45cb35213 100644 --- a/src/nvim/eval_defs.h +++ b/src/nvim/eval_defs.h @@ -137,4 +137,5 @@ typedef enum { VV_VIRTNUM, VV_STARTTIME, VV_EXITREASON, + VV_STARTREASON, } VimVarIndex; diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 9531fc34c7..0aae15cb68 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -5057,6 +5057,10 @@ static void ex_restart(exarg_T *eap) restart_alloc_console_env = true; } #endif + bool startreason_env = false; + if (os_setenv(ENV_STARTREASON, "restart", 1) == 0) { + startreason_env = true; + } CallbackReader on_err = CALLBACK_READER_INIT; #ifdef MSWIN @@ -5073,6 +5077,9 @@ static void ex_restart(exarg_T *eap) CALLBACK_READER_INIT, on_err, CALLBACK_NONE, false, true, true, detach, kChannelStdinPipe, NULL, 0, 0, NULL, &exit_status); + if (startreason_env) { + os_unsetenv(ENV_STARTREASON); + } #ifdef MSWIN if (restart_alloc_console_env) { os_unsetenv("__NVIM_RESTART_ALLOC_CONSOLE"); diff --git a/src/nvim/os/os.h b/src/nvim/os/os.h index cd7db3bf80..9ac0ab7e24 100644 --- a/src/nvim/os/os.h +++ b/src/nvim/os/os.h @@ -26,3 +26,4 @@ extern char *default_lib_dir; #define ENV_LOGFILE "NVIM_LOG_FILE" #define ENV_NVIM "NVIM" +#define ENV_STARTREASON "__NVIM_STARTREASON" diff --git a/src/nvim/vvars.lua b/src/nvim/vvars.lua index db87bcda28..2ec77e3dc0 100644 --- a/src/nvim/vvars.lua +++ b/src/nvim/vvars.lua @@ -765,6 +765,16 @@ M.vars = { Read-only. ]=], }, + startreason = { + type = 'string', + desc = [=[ + The reason Nvim started. Possible values: + - "normal" normal startup. + - "restart" started by |:restart| or |ZR|. + + Read-only. + ]=], + }, statusmsg = { type = 'string', desc = [=[ diff --git a/test/functional/core/server_spec.lua b/test/functional/core/server_spec.lua index cf79fe817f..8b94539e16 100644 --- a/test/functional/core/server_spec.lua +++ b/test/functional/core/server_spec.lua @@ -364,7 +364,15 @@ it(':restart works in headless server (no UI)', function() n.set_session(nil) end) - fn.jobstart({ n.nvim_prog, '--clean', '--headless', '--listen', server_pipe }) + fn.jobstart({ + n.nvim_prog, + '--clean', + '--headless', + '--listen', + server_pipe, + '--cmd', + 'let g:early_startreason = v:startreason', + }) t.retry(nil, nil, function() neq(nil, vim.uv.fs_stat(server_pipe)) end) @@ -373,6 +381,8 @@ it(':restart works in headless server (no UI)', function() n.expect_exit(n.command, 'restart') n.set_session(n.connect(server_pipe)) eq(1, api.nvim_get_vvar('vim_did_enter')) + eq('restart', api.nvim_get_vvar('startreason')) + eq('restart', n.eval('g:early_startreason')) -- TODO: [command] is currently not executed without UI -- n.expect_exit(n.command, 'restart lua _G.new_server = 1') diff --git a/test/functional/vimscript/vvars_spec.lua b/test/functional/vimscript/vvars_spec.lua index 377d6296fa..8afe719299 100644 --- a/test/functional/vimscript/vvars_spec.lua +++ b/test/functional/vimscript/vvars_spec.lua @@ -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) From 2b5693961965e65dffdc8b7c5893e9e9ac4976bf Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Thu, 18 Jun 2026 17:44:29 -0400 Subject: [PATCH 2/5] fix(:restart): set job env instead of mutating parent env #40308 Problem: - Transient mutation of the parent env is visible to any concurrent code. Or at least just kinda sloppy. - Latent bug:`channel_job_start` queues the spawn and returns before `uv_spawn` runs, so the prior `os_unsetenv` immediately after the call could in principle race with the deferred spawn. Solution: Pass `env` to the channel. (cherry picked from commit a1da5d1f141f58158ffc33aa2c84e790633b57c9) --- src/nvim/channel.c | 4 ++-- src/nvim/eval/funcs.c | 21 +++++++++++++-------- src/nvim/ex_docmd.c | 22 +++++----------------- src/nvim/log.c | 4 ++-- src/nvim/msgpack_rpc/server.c | 2 +- src/nvim/os/os.h | 3 +++ src/nvim/ui_client.c | 2 +- 7 files changed, 27 insertions(+), 31 deletions(-) diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 31f6c80bd6..08cd76c398 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -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, diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index d2c339f008..afe356a72c 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -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); diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 0aae15cb68..ee7699e580 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -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" @@ -5051,16 +5052,11 @@ 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), "restart"); #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 - bool startreason_env = false; - if (os_setenv(ENV_STARTREASON, "restart", 1) == 0) { - startreason_env = true; - } CallbackReader on_err = CALLBACK_READER_INIT; #ifdef MSWIN @@ -5076,15 +5072,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); - if (startreason_env) { - os_unsetenv(ENV_STARTREASON); - } -#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; diff --git a/src/nvim/log.c b/src/nvim/log.c index 58eda798e5..c7c46ff11e 100644 --- a/src/nvim/log.c +++ b/src/nvim/log.c @@ -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); } diff --git a/src/nvim/msgpack_rpc/server.c b/src/nvim/msgpack_rpc/server.c index 48f3e8ce04..2cd7207ff7 100644 --- a/src/nvim/msgpack_rpc/server.c +++ b/src/nvim/msgpack_rpc/server.c @@ -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"); } diff --git a/src/nvim/os/os.h b/src/nvim/os/os.h index 9ac0ab7e24..d9677cb58b 100644 --- a/src/nvim/os/os.h +++ b/src/nvim/os/os.h @@ -25,5 +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" diff --git a/src/nvim/ui_client.c b/src/nvim/ui_client.c index e5ad4dcb7d..0a769cfcae 100644 --- a/src/nvim/ui_client.c +++ b/src/nvim/ui_client.c @@ -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"); } From 9905c7b425f3806275c96997b73572ffb1cc6e10 Mon Sep 17 00:00:00 2001 From: Nathan Zeng Date: Mon, 29 Jun 2026 07:55:23 -0700 Subject: [PATCH 3/5] feat(:restart)!: ":restart" (no bang) saves/restores session #40321 Problem: `:restart` does not preserve window layout, etc. Solution: - Change `:restart` to save/restore a session automatically. - Introduce "bang" variant `:restart!` to restart *without* session save/restore. - Introduce `v:startreason`. - `ZR` maps to `:restart!`. (cherry picked from commit 845b66dd4a50d7b54050990e27919549c331e1bb) --- runtime/doc/editing.txt | 4 +- runtime/doc/gui.txt | 7 +- runtime/doc/news.txt | 3 +- runtime/doc/vvars.txt | 3 +- runtime/lua/vim/_core/server.lua | 57 ++++++++++ runtime/lua/vim/_meta/vvars.gen.lua | 3 +- src/nvim/eval/vars.c | 2 +- src/nvim/ex_cmds.lua | 2 +- src/nvim/ex_docmd.c | 42 ++++++- src/nvim/normal.c | 4 +- src/nvim/vvars.lua | 3 +- test/functional/core/main_spec.lua | 2 +- test/functional/core/server_spec.lua | 12 +- test/functional/terminal/tui_spec.lua | 151 +++++++++++++++++++------- 14 files changed, 233 insertions(+), 62 deletions(-) diff --git a/runtime/doc/editing.txt b/runtime/doc/editing.txt index dbff1fc2d2..b3a1e759ad 100644 --- a/runtime/doc/editing.txt +++ b/runtime/doc/editing.txt @@ -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* diff --git a/runtime/doc/gui.txt b/runtime/doc/gui.txt index c24c21d133..57d756fd84 100644 --- a/runtime/doc/gui.txt +++ b/runtime/doc/gui.txt @@ -75,9 +75,10 @@ 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 @@ -89,7 +90,7 @@ Restart Nvim 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() < diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 573691de4f..456b2798cc 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -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 diff --git a/runtime/doc/vvars.txt b/runtime/doc/vvars.txt index 6e829f8cb7..16a5cd55f0 100644 --- a/runtime/doc/vvars.txt +++ b/runtime/doc/vvars.txt @@ -637,7 +637,8 @@ v:stacktrace v:startreason The reason Nvim started. Possible values: - "normal" normal startup. - - "restart" started by |:restart| or |ZR|. + - "restart" started by |:restart|. + - "restart!" started by |:restart!| or |ZR|. Read-only. diff --git a/runtime/lua/vim/_core/server.lua b/runtime/lua/vim/_core/server.lua index 3b05142a33..4d9640ebd6 100644 --- a/runtime/lua/vim/_core/server.lua +++ b/runtime/lua/vim/_core/server.lua @@ -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 diff --git a/runtime/lua/vim/_meta/vvars.gen.lua b/runtime/lua/vim/_meta/vvars.gen.lua index 2b97a1f8f8..f31865f19c 100644 --- a/runtime/lua/vim/_meta/vvars.gen.lua +++ b/runtime/lua/vim/_meta/vvars.gen.lua @@ -669,7 +669,8 @@ vim.v.stacktrace = ... --- The reason Nvim started. Possible values: --- - "normal" normal startup. ---- - "restart" started by `:restart` or `ZR`. +--- - "restart" started by `:restart`. +--- - "restart!" started by `:restart!` or `ZR`. --- --- Read-only. --- @type string diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c index 4e467b9576..958af29d7e 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -350,7 +350,7 @@ void evalvars_init(void) // Set v:startreason via environment variable const char *startreason = os_getenv_noalloc(ENV_STARTREASON); - if (strequal(startreason, "normal") || strequal(startreason, "restart")) { + if (strequal(startreason, "restart!") || strequal(startreason, "restart")) { set_vim_var_string(VV_STARTREASON, startreason, -1); } if (os_env_exists(ENV_STARTREASON, false)) { diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index 5ecb2b2be2..ceece89702 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -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', }, diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index ee7699e580..8b09c7f76a 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -4976,6 +4976,41 @@ static void ex_quitall(exarg_T *eap) /// ":restart +cmd ": restart the Nvim server using ":cmd" and runs 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); @@ -5053,7 +5088,7 @@ static void ex_restart(exarg_T *eap) #endif dict_T *env = create_environment(NULL, false, false, false, NULL); - tv_dict_add_str(env, S_LEN(ENV_STARTREASON), "restart"); + tv_dict_add_str(env, S_LEN(ENV_STARTREASON), startreason); #ifdef MSWIN tv_dict_add_str(env, S_LEN(ENV_RESTART_ALLOC_CONSOLE), "1"); #endif @@ -5089,12 +5124,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)); @@ -5149,7 +5184,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 diff --git a/src/nvim/normal.c b/src/nvim/normal.c index ddb0966b7d..b2b64b1c5d 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -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; diff --git a/src/nvim/vvars.lua b/src/nvim/vvars.lua index 2ec77e3dc0..e90695b40c 100644 --- a/src/nvim/vvars.lua +++ b/src/nvim/vvars.lua @@ -770,7 +770,8 @@ M.vars = { desc = [=[ The reason Nvim started. Possible values: - "normal" normal startup. - - "restart" started by |:restart| or |ZR|. + - "restart" started by |:restart|. + - "restart!" started by |:restart!| or |ZR|. Read-only. ]=], diff --git a/test/functional/core/main_spec.lua b/test/functional/core/main_spec.lua index 904beb1444..2d8eecce99 100644 --- a/test/functional/core/main_spec.lua +++ b/test/functional/core/main_spec.lua @@ -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 = { diff --git a/test/functional/core/server_spec.lua b/test/functional/core/server_spec.lua index 8b94539e16..05d1b546e3 100644 --- a/test/functional/core/server_spec.lua +++ b/test/functional/core/server_spec.lua @@ -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() @@ -378,14 +378,14 @@ it(':restart works in headless server (no UI)', function() 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')) + 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) diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 27d6374626..3650242461 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -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 " on a modified buffer. - tt.feed_data(":confirm restart put ='Hello3'\013") + -- Check ":confirm restart! " 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 @@ -563,7 +638,7 @@ describe('TUI :restart', function() eq({ true, true }, { server_session:request('nvim_eval', expr) }) 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 | @@ -603,7 +678,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;;Cc;0;BN;;;;;N;NULL;;;; | 0001;;Cc;0;BN;;;;;N;START OF HEADING;;;; | @@ -666,7 +741,7 @@ describe('TUI :restart', function() {5:-- TERMINAL --} | ]]) - feed_data(':restart echo "restarted"\r') + feed_data(':restart! echo "restarted"\r') screen:expect([[ ^ │0000;;Cc;0;BN;;;;;N| ~ │0001;;Cc;0;BN;;;;;N| @@ -677,7 +752,7 @@ describe('TUI :restart', function() {5:-- TERMINAL --} | ]]) - feed_data(':set sessionoptions-=winsize | restart\r') + feed_data(':set sessionoptions-=winsize | restart!\r') screen:expect([[ ^ │0000;;Cc;0;BN;;| ~ │0001;;Cc;0;BN;;| @@ -3034,8 +3109,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 +4516,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 +4535,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 +4627,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 From 97290e86581a3ba49dabee732e009c1d8eb15767 Mon Sep 17 00:00:00 2001 From: Nathan Zeng Date: Sun, 5 Jul 2026 14:53:45 -0700 Subject: [PATCH 4/5] fix(:restart): remove `-S [file]` from v:argv #40521 Problem: Session files specified at startup `-S [file]`, logically conflict with `:restart`. Solution: Remove `-S [file]` from `v:argv` when doing :restart. Also for the "bang" variant `:restart!`, just because it's simpler (if anyone reports a use-case later, we can revisit). (cherry picked from commit 47958bb4db519ccacdc5ed34da19228539f392e7) --- runtime/doc/gui.txt | 11 ++++++----- src/nvim/ex_docmd.c | 10 ++++++++++ test/functional/terminal/tui_spec.lua | 12 +++++++++++- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/runtime/doc/gui.txt b/runtime/doc/gui.txt index 57d756fd84..6fbcb1a532 100644 --- a/runtime/doc/gui.txt +++ b/runtime/doc/gui.txt @@ -80,11 +80,12 @@ Restart Nvim 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). Sets |v:startreason| to "restart" on the - new server. - 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: > diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 8b09c7f76a..194d8af615 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -5028,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 ": skip the scriptfile arg too. if (i > 0 && strequal(arg, "-s")) { li = li->li_next; diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 3650242461..01e5a99b53 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -602,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 @@ -618,6 +623,8 @@ describe('TUI :restart', function() server_pipe, '--cmd', 'set notermguicolors', + '-S', + file, '-s', '-', '-', @@ -629,14 +636,16 @@ 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") screen:expect([[ @@ -652,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) From de42a039debf9329cde8333ea8fbb33f6a9c5ff0 Mon Sep 17 00:00:00 2001 From: Nathan Zeng Date: Sun, 5 Jul 2026 02:52:49 -0700 Subject: [PATCH 5/5] docs(restart): use 'sessionoptions' to adjust :restart behavior #40583 (cherry picked from commit 656b4d9c34676c70eefc602c6f82cb1cc955c4b4) --- runtime/doc/gui.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/runtime/doc/gui.txt b/runtime/doc/gui.txt index 6fbcb1a532..0af0a07495 100644 --- a/runtime/doc/gui.txt +++ b/runtime/doc/gui.txt @@ -99,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