mirror of
https://github.com/neovim/neovim.git
synced 2026-11-06 05:16:52 +00:00
feat(startup): --embed --listen waits for UI if stdin is not a pipe
Problem:
`nvim --embed --listen addr` always treats stdin as an RPC channel. If
stdin is /dev/null (e.g. backgrounded over ssh), the channel closes
before a UI can attach, so Nvim exits (detach=false behavior). If stdin
is a tty, keystrokes are read as RPC.
Solution:
With `--embed --listen`, open the stdio RPC channel only if stdin is
a pipe or socket. Else wait for a UI at the `--listen` address.
$ nvim --embed --listen ./nvim.sock
Nvim server (pid: 71984) waiting for UI, listening on: "./nvim.sock"
Note:
- No change when stdin is a pipe. So the test harness, TUI, and :restart
all behave exactly as before.
- The #29634 use-case of configuring a UI that attaches *after* startup
(a second UI, or reattach) still requires #25377 (reliable UIEnter).
Not affected by this change.
This commit is contained in:
1 parent
c60f1c635d
commit
d212676f90
5 files changed
+51
-12
No files matched your search
@@ -489,7 +489,8 @@ PLUGINS
|
||||
|
||||
STARTUP
|
||||
|
||||
• todo
|
||||
• |--embed| with |--listen| waits for a UI on the listen address if stdin is
|
||||
not a pipe (e.g. tty or /dev/null), instead of exiting.
|
||||
|
||||
TERMINAL
|
||||
|
||||
|
||||
@@ -383,10 +383,10 @@ argument.
|
||||
channel is still open.
|
||||
|
||||
Waits for a client to call |nvim_ui_attach()| before sourcing
|
||||
startup files and reading buffers, so that UIs can handle
|
||||
(display) early messages, dialogs, etc. Clients can do other
|
||||
requests before `nvim_ui_attach` (e.g. `nvim_get_api_info` for
|
||||
feature-detection). During this pre-startup phase the user
|
||||
startup files and reading buffers, so UIs can display early
|
||||
messages/dialogs and make preliminary requests (e.g.
|
||||
`nvim_chan_set` to set channel config, or `nvim_get_api_info`
|
||||
for feature-detection). During this pre-startup phase the user
|
||||
config is of course not available (similar to `--cmd`).
|
||||
|
||||
Non-UI embedders must pass |--headless|, then startup will
|
||||
@@ -398,8 +398,17 @@ argument.
|
||||
UI embedders that want the UI protocol on a socket (instead of
|
||||
stdio) must pass |--listen| as well as |--embed|: >
|
||||
nvim --embed --listen addr
|
||||
< Then stdin is an RPC channel only if it is a pipe/socket.
|
||||
Otherwise (tty, /dev/null) Nvim waits for a UI to attach at
|
||||
`addr`, e.g. to start a server for a GUI on a remote host: >
|
||||
ssh host 'nvim --embed --listen 0.0.0.0:8888 &'
|
||||
< The UI can call |nvim_chan_set()| with `detach=false` to
|
||||
enable exit-on-channel-close behavior.
|
||||
|
||||
< See also: |ui-startup| |channel-stdio|
|
||||
To "daemonize" such a server, use |jobstart()| with "detach": >
|
||||
nvim -es +"lua vim.fn.jobstart({'nvim', '--embed', '--listen', 'addr'}, {detach=true, stdin='null'})" +q
|
||||
<
|
||||
See also: |ui-startup| |channel-stdio|
|
||||
|
||||
*--headless*
|
||||
--headless Start without UI, and do not wait for `nvim_ui_attach`. The
|
||||
|
||||
+10
-3
@@ -309,7 +309,11 @@ int main(int argc, char **argv)
|
||||
}
|
||||
#endif
|
||||
|
||||
if (embedded_mode) {
|
||||
// "--embed --listen …": use stdio as RPC chan only if stdin is a pipe/socket.
|
||||
// Otherwise (tty, /dev/null, EOF) wait for a UI.
|
||||
uv_handle_type stdin_type = uv_guess_handle(STDIN_FILENO);
|
||||
bool stdio_rpc = !params.listen_addr || stdin_type == UV_NAMED_PIPE || stdin_type == UV_TCP;
|
||||
if (embedded_mode && stdio_rpc) {
|
||||
const char *err;
|
||||
if (!channel_from_stdio(true, CALLBACK_READER_INIT, &err)) {
|
||||
abort();
|
||||
@@ -418,10 +422,13 @@ int main(int argc, char **argv)
|
||||
// Set the break level after the terminal is initialized.
|
||||
debug_break_level = params.use_debug_break_level;
|
||||
|
||||
// Wait for UIs to set up Nvim or show early messages
|
||||
// and prompts (--cmd, swapfile dialog, …).
|
||||
// Wait for UIs to set up Nvim or show early messages and prompts (--cmd, swapfile dialog, …).
|
||||
bool use_remote_ui = (embedded_mode && !headless_mode);
|
||||
if (use_remote_ui) {
|
||||
if (!stdio_rpc) {
|
||||
fprintf(stderr, "Nvim server (pid: %" PRId64 ") waiting for UI, listening on: \"%s\"\n",
|
||||
os_get_pid(), get_vim_var_str(VV_SEND_SERVER));
|
||||
}
|
||||
TIME_MSG("waiting for UI");
|
||||
remote_ui_wait_for_attach();
|
||||
TIME_MSG("done waiting for UI");
|
||||
|
||||
@@ -551,7 +551,7 @@ describe('TUI :restart', function()
|
||||
|
||||
-- [9]ZR discards unsaved changes.
|
||||
tt.feed_data('9ZR')
|
||||
screen:expect({ any = vim.pesc('[No Name]') })
|
||||
screen:expect({ any = vim.pesc('[No Name]'), none = vim.pesc('[+]') })
|
||||
starttime, server_session = assert_restarted(starttime, server_session, server_pipe)
|
||||
|
||||
-- Restarted server self-exits when the TUI disconnects / host-terminal closed. #42250
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
local t = require('test.testutil')
|
||||
local n = require('test.functional.testnvim')()
|
||||
local Screen = require('test.functional.ui.screen')
|
||||
local tt = require('test.functional.testterm')
|
||||
local describe, it, after_each, pending, finally =
|
||||
t.describe, t.it, t.after_each, t.pending, t.finally
|
||||
local uv = vim.uv
|
||||
@@ -299,8 +300,8 @@ describe('--embed UI', function()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('--embed --listen UI', function()
|
||||
it('waits for connection on listening address', function()
|
||||
describe('--embed --listen', function()
|
||||
it('waits for UI on listening address', function()
|
||||
clear()
|
||||
local child_server = assert(n.new_pipename())
|
||||
fn.jobstart({
|
||||
@@ -357,4 +358,25 @@ describe('--embed --listen UI', function()
|
||||
ok(var_ok)
|
||||
eq({ 'VimEnter', ('UIEnter:%d'):format(api_info[1]) }, var)
|
||||
end)
|
||||
|
||||
it('waits for UI if stdin is not a pipe/socket', function()
|
||||
clear()
|
||||
local child_server = assert(n.new_pipename())
|
||||
-- In :terminal, stdin is a tty.
|
||||
local screen = tt.setup_child_nvim(
|
||||
{ '--embed', '--listen', child_server, '--clean' },
|
||||
{ cols = 256 } -- Wide screen so the message doesn't wrap.
|
||||
)
|
||||
screen:expect({ any = ('waiting for UI, listening on: "%s"'):format(vim.pesc(child_server)) })
|
||||
|
||||
local child_session = n.connect(child_server)
|
||||
eq({ true, 0 }, { child_session:request('nvim_get_vvar', 'vim_did_enter') })
|
||||
local child_screen = Screen.new(40, 6, nil, child_session)
|
||||
child_screen:expect({ any = vim.pesc('[No Name]') })
|
||||
|
||||
-- The UI can claim the server: Nvim self-exits when the UI disconnects.
|
||||
child_session:request('nvim_chan_set', 0, { detach = false })
|
||||
child_session:close()
|
||||
screen:expect({ any = 'Process exited' })
|
||||
end)
|
||||
end)
|
||||
Reference in new issue
Block a user