mirror of
https://github.com/neovim/neovim.git
synced 2026-02-19 01:48:30 +00:00
fix(restart): append -c <cmd> at end, drop -- [files…] #37846
Problem:
- `:restart <cmd>` prepends `-c <cmd>` before the original `-c` args (if
any). So the original `-c` args may "override" it, which is
surprising.
- Confusing logic: `v:argv` is partially prepared in `ex_docmd.c`, and
then later `ui.c` skips other parts of it.
Current behavior is nonsense, for example this sequence:
:restart echo "Hello"
:restart +qall echo "Hello" | echo "World"
results in this v:argv:
[
'nvim'
'-c'
'echo "Hello" | echo "World"'
'--embed'
'-c'
'echo "Hello"'
...
]
Whereas after this commit, v:argv is:
[
'nvim'
'--embed'
...
'-c'
'echo "Hello" | echo "World"'
]
Solution:
- Append `-c <cmd>` at the _end_ of `v:argv`, not the start.
- Use a dummy placeholder `+:::` to mark where the "restart command"
appears in `v:argv`.
- Do all `v:argv` preparation in `ex_docmd.c`. This simplifies `ui.c`.
- Drop `-- [files…]` from `v:argv` since it is probably more annoying
than useful. (Users can use sessions to restore files on restart.)
This commit is contained in:
@@ -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).
|
||||
|
||||
@@ -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 <cmd> from argv.
|
||||
bool first_minc = true; // Avoid skipping the first -c <cmd> 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 <cmd> 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));
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 <arg>" 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", "<command>"` to end of v:argv.
|
||||
// The "+:::" item is a no-op placeholder to mark the :restart "<command>".
|
||||
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";
|
||||
|
||||
@@ -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 <cmd>" 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 <cmd> 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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user