diff --git a/runtime/doc/gui.txt b/runtime/doc/gui.txt index bf4a52f742..45f4231ce0 100644 --- a/runtime/doc/gui.txt +++ b/runtime/doc/gui.txt @@ -74,8 +74,8 @@ Restart Nvim Restarts Nvim. 1. Stops Nvim using `:qall` (or |+cmd|, if given). - 2. Starts a new Nvim server using the same |v:argv|, - optionally running [command] at startup. |-c| + 2. Starts a new Nvim server using the same |v:argv| (except + `-- [file…]` files), appended with `-c [command]`. 3. Attaches the current UI to the new Nvim server. Other UIs (if any) will not reattach on restart (this may change in the future). diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index 66c295a2f5..10abe24a68 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -286,35 +286,9 @@ bool remote_ui_restart(uint64_t channel_id, Error *err) int argc = tv_list_len(l); assert(argc > 0); Array argv = arena_array(&arena, (size_t)argc + 1); - bool had_minmin = false; - bool skipping_minc = false; // Skip -c from argv. - bool first_minc = true; // Avoid skipping the first -c from argv. TV_LIST_ITER_CONST(l, li, { const char *arg = tv_get_string(TV_LIST_ITEM_TV(li)); - if (argv.size > 0 && !had_minmin && strequal(arg, "--")) { - had_minmin = true; - skipping_minc = false; - } - bool startswith_min = strlen(arg) > 0 && arg[0] == '-'; - bool startswith_minmin = strlen(arg) > 1 && arg[0] == '-' && arg[1] == '-'; - if (skipping_minc && (startswith_min || startswith_minmin)) { - skipping_minc = false; - } - if (!had_minmin && !skipping_minc && strequal(arg, "-c")) { - if (!first_minc) { - skipping_minc = true; - continue; - } - first_minc = false; - } - // Exclude --embed/--headless/-c from `argv`, as the client may start the server in a - // different way than how the server was originally started. - // Eg: 'nvim -c foo -c bar --embed --headless -- example.txt' would be parsed as { 'nvim', '-c', 'foo', '--', 'example.txt' }. - if (argv.size == 0 || had_minmin - || (!strequal(arg, "--embed") && !strequal(arg, "--headless") && !skipping_minc)) { - ADD_C(argv, CSTR_AS_OBJ(arg)); - } - skipping_minc = false; + ADD_C(argv, CSTR_AS_OBJ(arg)); }); ADD_C(args, ARRAY_OBJ(argv)); diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index d50a5e4bd5..2ccc3e6649 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1014,8 +1014,8 @@ static int list_join_inner(garray_T *const gap, list_T *const l, const char *con /// Join list into a string using given separator /// -/// @param[out] gap Garray where result will be saved. -/// @param[in] l Joined list. +/// @param[out] gap Garray where the joined list will be saved. +/// @param[in] l List. /// @param[in] sep Separator. /// /// @return OK in case of success, FAIL otherwise. diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index ea48a18e8b..82520d8496 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -4943,27 +4943,42 @@ static void ex_restart(exarg_T *eap) const list_T *l = get_vim_var_list(VV_ARGV); int argc = tv_list_len(l); list_T *argv_cpy = tv_list_alloc(eap->arg ? argc + 2 : argc); - bool added_startup_arg = false; - TV_LIST_ITER_CONST(l, li, { + // Copy v:argv, skipping unwanted items. + for (listitem_T *li = l != NULL ? l->lv_first : NULL; li != NULL; li = li->li_next) { const char *arg = tv_get_string(TV_LIST_ITEM_TV(li)); size_t arg_size = strlen(arg); assert(arg_size <= (size_t)SSIZE_MAX); - // Skip "-" argument (stdin input marker). - if (strequal(arg, "-")) { - continue; + + if (strequal(arg, "--embed") || strequal(arg, "--headless")) { + continue; // Drop --embed/--headless: the client decides how to start+attach the server. + } else if (strequal(arg, "-")) { + continue; // Drop stdin ("-") argument. + } else if (strequal(arg, "+:::")) { + // The special placeholder "+:::" marks a previous :restart command. + // Drop the `"+:::", "-c", "…"` triplet, to avoid "stacking" commands from previous :restart(s). + listitem_T *next1 = li->li_next; + if (next1 != NULL && strequal(tv_get_string(TV_LIST_ITEM_TV(next1)), "-c")) { + listitem_T *next2 = next1->li_next; + if (next2 != NULL) { + li = next2; + continue; + } + } + continue; // If the triplet is incomplete, just skip "+:::" + } else if (strequal(arg, "--")) { + break; // Drop "-- [files…]". Usually isn't wanted. User can :mksession instead. } tv_list_append_string(argv_cpy, arg, (ssize_t)arg_size); - - // Patch v:argv to include "-c " when it restarts. - if (eap->arg && !added_startup_arg) { - tv_list_append_string(argv_cpy, "-c", 2); - tv_list_append_string(argv_cpy, eap->arg, (ssize_t)strlen(eap->arg)); - added_startup_arg = true; - } - }); - + } + // Append `"+:::", "-c", ""` to end of v:argv. + // The "+:::" item is a no-op placeholder to mark the :restart "". + if (eap->arg && eap->arg[0] != '\0') { + tv_list_append_string(argv_cpy, S_LEN("+:::")); + tv_list_append_string(argv_cpy, S_LEN("-c")); + tv_list_append_string(argv_cpy, eap->arg, (ssize_t)strlen(eap->arg)); + } set_vim_var_list(VV_ARGV, argv_cpy); char *quit_cmd = (eap->do_ecmd_cmd) ? eap->do_ecmd_cmd : "qall"; diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index c1c94de2b3..af294a7859 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -237,7 +237,12 @@ describe('TUI :detach', function() end) describe('TUI :restart', function() - it('resets buffer to blank', function() + it('validation', function() + clear() + eq('Vim(restart):E481: No range allowed: :1restart', t.pcall_err(n.command, ':1restart')) + end) + + it('works', function() clear() finally(function() n.check_close() @@ -272,7 +277,7 @@ describe('TUI :restart', function() end -- The value of has("gui_running") should be 0 before and after :restart. - local function gui_running_check() + local function assert_no_gui_running() tt.feed_data(':echo "GUI Running: " .. has("gui_running")\013') screen:expect({ any = 'GUI Running: 0' }) end @@ -285,12 +290,12 @@ describe('TUI :restart', function() {5:-- TERMINAL --} | ]] screen_expect(s0) - gui_running_check() + assert_no_gui_running() local server_session = n.connect(server_pipe) local _, server_pid = server_session:request('nvim_call_function', 'getpid', {}) - local function restart_pid_check() + local function assert_new_pid() server_session:close() server_session = n.connect(server_pipe) local _, new_pid = server_session:request('nvim_call_function', 'getpid', {}) @@ -298,52 +303,58 @@ describe('TUI :restart', function() server_pid = new_pid end - tt.feed_data(':1restart\013') - screen:expect({ any = vim.pesc('{101:E481: No range allowed}') }) + --- 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 = [[ | - | - {2: }| + ^Hello1 | + {100:~ }|*2 + {3:[No Name] [+] }| {MATCH:%d+ +}| - Hello | - {102:Press ENTER or type command to continue}^ | {5:-- TERMINAL --} | ]] - -- Check trailing characters are considered in -c - tt.feed_data(':restart echo "Hello"\013') + tt.feed_data(':set nomodified\013') + -- Command is added as "-c" arg. + tt.feed_data(":restart put ='Hello1'\013") screen_expect(s1) tt.feed_data('\013') - restart_pid_check() - gui_running_check() + assert_new_pid() + assert_no_gui_running() + eq("--cmd echo getpid() +::: -c put ='Hello1'", get_argv(4)) - -- Check trailing characters after +cmd are considered in -c - tt.feed_data(':restart +qall echo "Hello" | echo "World"\013') + -- Complex command following +cmd. + tt.feed_data(":restart +qall! put ='Hello2' | put ='World2'\013") screen_expect([[ | - {2: }| + Hello2 | + ^World2 | + {100:~ }| + {3:[No Name] [+] }| {MATCH:%d+ +}| - Hello | - World | - {102:Press ENTER or type command to continue}^ | {5:-- TERMINAL --} | ]]) - tt.feed_data('\013') - restart_pid_check() - gui_running_check() + assert_new_pid() + assert_no_gui_running() + eq("--cmd echo getpid() +::: -c put ='Hello2' | put ='World2'", get_argv(4)) -- Check ":restart" on an unmodified buffer. + tt.feed_data(':set nomodified\013') tt.feed_data(':restart\013') screen_expect(s0) - restart_pid_check() - gui_running_check() + assert_new_pid() + assert_no_gui_running() -- Check ":restart +qall!" on an unmodified buffer. tt.feed_data(':restart +qall!\013') screen_expect(s0) - restart_pid_check() - gui_running_check() + assert_new_pid() + assert_no_gui_running() + eq('--cmd echo getpid()', get_argv(1)) -- Check ":restart +echo" cannot restart server. tt.feed_data(':restart +echo\013') @@ -375,15 +386,13 @@ describe('TUI :restart', function() tt.feed_data(':set noconfirm\013') -- Check ":confirm restart " on a modified buffer. - tt.feed_data(':confirm restart echo "Hello"\013') + tt.feed_data(":confirm restart put ='Hello3'\013") screen:expect({ any = vim.pesc('Save changes to "Untitled"?') }) tt.feed_data('N\013') - - -- Check if the -c runs after restart. - screen_expect(s1) - tt.feed_data('\013') - restart_pid_check() - gui_running_check() + screen:expect({ any = '%^Hello3' }) + assert_new_pid() + assert_no_gui_running() + eq("--cmd echo getpid() +::: -c put ='Hello3'", get_argv(4)) -- Check ":confirm restart +echo" correctly ignores ":confirm" tt.feed_data(':confirm restart +echo\013') @@ -398,14 +407,14 @@ describe('TUI :restart', function() tt.feed_data('ithis will be removed\027') tt.feed_data(':restart +qall!\013') screen_expect(s0) - restart_pid_check() - gui_running_check() + assert_new_pid() + assert_no_gui_running() -- No --listen conflict when server exit is delayed. feed_data(':lua vim.schedule(function() vim.wait(100) end); vim.cmd.restart()\n') screen_expect(s0) - restart_pid_check() - gui_running_check() + assert_new_pid() + assert_no_gui_running() screen:try_resize(60, 6) screen_expect([[ @@ -425,11 +434,11 @@ describe('TUI :restart', function() {MATCH:%d+ +}| {5:-- TERMINAL --} | ]]) - restart_pid_check() - gui_running_check() + assert_new_pid() + assert_no_gui_running() end) - it('filters stdin marker from v:argv on restart #34417', function() + it('drops "-" and "-- [files…]" from v:argv #34417', function() t.skip(is_os('win'), 'stdin behavior differs on Windows') clear() local server_session @@ -450,31 +459,37 @@ describe('TUI :restart', function() '--cmd', 'set notermguicolors', '-', + '--', + 'Xtest-file1', + 'Xtest-file2', }) screen:expect([[ ^ | ~ |*3 - {2:[No Name] [RO] 1,0-1 All}| + {2:Xtest-file1 0,0-1 All}| | {5:-- TERMINAL --} | ]]) server_session = n.connect(server_pipe) - local expr = 'index(v:argv, "-") >= 0 ? v:true : v:false' - local _, has_stdin = server_session:request('nvim_eval', expr) - eq(true, has_stdin) + local expr = 'index(v:argv, "-") >= 0 || index(v:argv, "--") >= 0 ? v:true : v:false' + eq({ true, true }, { server_session:request('nvim_eval', expr) }) - tt.feed_data(':restart\013') + tt.feed_data(":restart put='foo'\013") screen:expect([[ - ^ | - ~ |*3 - {2:[No Name] 0,0-1 All}| + | + ^foo | + ~ |*2 + {2:[No Name] [+] 2,1 All}| | {5:-- TERMINAL --} | ]]) server_session:close() server_session = n.connect(server_pipe) - local _, has_stdin_after = server_session:request('nvim_eval', expr) - eq(false, has_stdin_after) + + eq({ true, false }, { server_session:request('nvim_eval', expr) }) + local argv = ({ server_session:request('nvim_eval', 'v:argv') })[2] --[[@type table]] + eq(13, #argv) + eq("-c put='foo'", table.concat(argv, ' ', #argv - 1, #argv)) end) end)