fix(:restart): only pass --headless when there is no UI (#38580)

Change --embed so that the first UI can be on non-stdio channel even if
neither --headless nor --listen is passed.

(cherry picked from commit a3a48392c5)
This commit is contained in:
zeertzjq
2026-03-31 07:00:34 +08:00
committed by github-actions[bot]
parent d83141c0f2
commit 8b3f3113c4
5 changed files with 114 additions and 22 deletions

View File

@@ -120,23 +120,9 @@ void remote_ui_free_all_mem(void)
#endif
/// Wait until UI has connected.
///
/// @param only_stdio UI is expected to connect on stdio.
void remote_ui_wait_for_attach(bool only_stdio)
void remote_ui_wait_for_attach(void)
{
if (only_stdio) {
Channel *channel = find_channel(CHAN_STDIO);
if (!channel) {
// `only_stdio` implies --embed mode, thus stdio channel can be assumed.
abort();
}
LOOP_PROCESS_EVENTS_UNTIL(&main_loop, channel->events, -1,
map_has(uint64_t, &connected_uis, CHAN_STDIO));
} else {
LOOP_PROCESS_EVENTS_UNTIL(&main_loop, main_loop.events, -1,
ui_active());
}
LOOP_PROCESS_EVENTS_UNTIL(&main_loop, main_loop.events, -1, ui_active());
}
/// Activates UI events on the channel.

View File

@@ -4963,6 +4963,7 @@ static void ex_quitall(exarg_T *eap)
static void ex_restart(exarg_T *eap)
{
Error err = ERROR_INIT;
const bool no_ui = !ui_active();
const char *exepath = get_vim_var_str(VV_PROGPATH);
const list_T *l = get_vim_var_list(VV_ARGV);
int argc = tv_list_len(l);
@@ -4996,14 +4997,18 @@ static void ex_restart(exarg_T *eap)
}
}
}
// Replace `--embed` OR `--headless` with `--embed --headless` once.
// Replace `--embed` OR `--headless` with `--embed` or `--embed --headless` once.
// Drop stdin ("-") argument.
if (i == 0
|| (!strequal(arg, "--embed") && !strequal(arg, "--headless") && !strequal(arg, "-"))) {
argv[i++] = xstrdup(arg);
if (i == 1) {
argv[i++] = xstrdup("--embed");
argv[i++] = xstrdup("--headless");
// Without --headless, embed waits for UI to attach.
// Only add --headless when there is no UI.
if (no_ui) {
argv[i++] = xstrdup("--headless");
}
}
}
});
@@ -5075,7 +5080,7 @@ static void ex_restart(exarg_T *eap)
result_mem = NULL;
// Send restart event with new listen address to current UI.
if (!remote_ui_restart(current_ui, listen_addr, &err)) {
if (!no_ui && !remote_ui_restart(current_ui, listen_addr, &err)) {
if (ERROR_SET(&err)) {
ELOG("%s", err.msg); // UI disappeared already?
api_clear_error(&err);

View File

@@ -436,10 +436,9 @@ int main(int argc, char **argv)
// Wait for UIs to set up Nvim or show early messages
// and prompts (--cmd, swapfile dialog, …).
bool use_remote_ui = (embedded_mode && !headless_mode);
bool listen_and_embed = params.listen_addr != NULL;
if (use_remote_ui) {
TIME_MSG("waiting for UI");
remote_ui_wait_for_attach(!listen_and_embed);
remote_ui_wait_for_attach();
TIME_MSG("done waiting for UI");
firstwin->w_prev_height = firstwin->w_height; // may have changed
}

View File

@@ -351,3 +351,31 @@ describe('startup --listen', function()
matches([[[/\\]test%-name[^/\\]*]], api.nvim_get_vvar('servername'))
end)
end)
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()
finally(function()
n.expect_exit(n.command, 'qall!')
nvim0:close()
n.set_session(nil)
end)
fn.jobstart({ n.nvim_prog, '--clean', '--headless', '--listen', server_pipe })
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.set_session(n.connect(server_pipe))
eq(1, api.nvim_get_vvar('vim_did_enter'))
-- TODO: [command] is currently not executed without UI
-- 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)

View File

@@ -466,7 +466,7 @@ describe('TUI :restart', function()
screen:expect({ any = vim.pesc('[Process exited 0]') })
end)
it('autocommands are triggered by [command] properly #38549', function()
it('[command] triggers autocommands properly #38549', function()
local screen = tt.setup_child_nvim({
'--clean',
'--cmd',
@@ -497,6 +497,80 @@ describe('TUI :restart', function()
feed_data(':qall!\r')
screen:expect({ any = vim.pesc('[Process exited 0]') })
end)
it('new server loads user config after old server exits #38569', function()
local config_file = 'Xrestart_session_config.lua'
local session_file = 'Xrestart_session.vim'
write_file(
config_file,
([[
vim.api.nvim_create_autocmd("VimLeavePre", {
callback = function()
vim.cmd('mksession! %s')
end,
})
if vim.v.vim_did_enter and vim.uv.fs_stat('%s') then
vim.cmd('source %s')
end
]]):format(session_file, session_file, session_file)
)
finally(function()
os.remove(config_file)
os.remove(session_file)
end)
local screen = tt.setup_child_nvim({
'--clean',
'-u',
config_file,
'--cmd',
'set notermguicolors noswapfile laststatus=0 nowrap noruler',
}, { env = env_notermguicolors })
screen:expect([[
^ |
~ |*4
|
{5:-- TERMINAL --} |
]])
feed_data(':rightbelow 28vsplit test/functional/fixtures/bigfile.txt\r')
screen:expect([[
│^0000;<control>;Cc;0;BN;;;;;N|
~ │0001;<control>;Cc;0;BN;;;;;N|
~ │0002;<control>;Cc;0;BN;;;;;N|
~ │0003;<control>;Cc;0;BN;;;;;N|
~ │0004;<control>;Cc;0;BN;;;;;N|
|
{5:-- TERMINAL --} |
]])
feed_data(':restart echo "restarted"\r')
screen:expect([[
^ │0000;<control>;Cc;0;BN;;;;;N|
~ │0001;<control>;Cc;0;BN;;;;;N|
~ │0002;<control>;Cc;0;BN;;;;;N|
~ │0003;<control>;Cc;0;BN;;;;;N|
~ │0004;<control>;Cc;0;BN;;;;;N|
restarted |
{5:-- TERMINAL --} |
]])
feed_data(':set sessionoptions-=winsize | restart\r')
screen:expect([[
^ │0000;<control>;Cc;0;BN;;|
~ │0001;<control>;Cc;0;BN;;|
~ │0002;<control>;Cc;0;BN;;|
~ │0003;<control>;Cc;0;BN;;|
~ │0004;<control>;Cc;0;BN;;|
|
{5:-- TERMINAL --} |
]])
-- The server is now detached and needs to be quit explicitly.
feed_data(':qall!\r')
screen:expect({ any = vim.pesc('[Process exited 0]') })
end)
end)
describe('TUI :connect', function()