Merge #32385 UI :detach command

This commit is contained in:
Justin M. Keyes
2025-02-10 10:21:47 -08:00
committed by GitHub
20 changed files with 300 additions and 134 deletions

View File

@@ -129,7 +129,7 @@ Each pull request must pass the automated builds on [Cirrus CI] and [GitHub Acti
passes various linter checks. passes various linter checks.
- CI for FreeBSD runs on [Cirrus CI]. - CI for FreeBSD runs on [Cirrus CI].
- To see CI results faster in your PR, you can temporarily set `TEST_FILE` in - To see CI results faster in your PR, you can temporarily set `TEST_FILE` in
[test.yml](https://github.com/neovim/neovim/blob/e35b9020b16985eee26e942f9a3f6b045bc3809b/.github/workflows/test.yml#L29). [test.yml](https://github.com/neovim/neovim/blob/ad8e0cfc1dfd937c2577dc032e524c799a772693/.github/workflows/test.yml#L26).
### Coverity ### Coverity

View File

@@ -1208,8 +1208,8 @@ nvim_select_popupmenu_item({item}, {insert}, {finish}, {opts})
*nvim_set_client_info()* *nvim_set_client_info()*
nvim_set_client_info({name}, {version}, {type}, {methods}, {attributes}) nvim_set_client_info({name}, {version}, {type}, {methods}, {attributes})
Self-identifies the client. Sets the `client` object returned by Self-identifies the client, and sets optional flags on the channel.
|nvim_get_chan_info()|. Defines the `client` object returned by |nvim_get_chan_info()|.
Clients should call this just after connecting, to provide hints for Clients should call this just after connecting, to provide hints for
debugging and orchestration. (Note: Something is better than nothing! debugging and orchestration. (Note: Something is better than nothing!

View File

@@ -47,6 +47,25 @@ can connect to any Nvim instance).
Example: this sets "g:gui" to the value of the UI's "rgb" field: > Example: this sets "g:gui" to the value of the UI's "rgb" field: >
:autocmd UIEnter * let g:gui = filter(nvim_list_uis(),{k,v-> v.chan==v:event.chan})[0].rgb :autocmd UIEnter * let g:gui = filter(nvim_list_uis(),{k,v-> v.chan==v:event.chan})[0].rgb
< <
------------------------------------------------------------------------------
Stop or detach the current UI
*:detach*
:detach
Detaches the current UI. Other UIs (if any) remain attached.
The server (typically `nvim --embed`) continues running as
a background process, and you can reattach to it later.
Before detaching, you may want to note the server address:
>vim
:echo v:servername
<
Note: The server closes the UI RPC channel, so :detach
inherently "works" for all UIs. But if a UI isn't expecting
the channel to be closed, it may be (incorrectly) reported as
an error.
------------------------------------------------------------------------------
GUI commands
*:winp* *:winpos* *E188* *:winp* *:winpos* *E188*
:winp[os] :winp[os]

View File

@@ -400,6 +400,8 @@ TUI
UI UI
• |:detach| the current UI, let the Nvim server continue running as a background
process. Works with the builtin TUI, and all GUIs.
• |vim.ui.open()| (by default bound to |gx|) accepts an `opt.cmd` parameter • |vim.ui.open()| (by default bound to |gx|) accepts an `opt.cmd` parameter
which controls the tool used to open the given path or URL. If you want to which controls the tool used to open the given path or URL. If you want to
globally set this, you can override vim.ui.open using the same approach globally set this, you can override vim.ui.open using the same approach

View File

@@ -189,6 +189,7 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dict opt
ui->wildmenu_active = false; ui->wildmenu_active = false;
pmap_put(uint64_t)(&connected_uis, channel_id, ui); pmap_put(uint64_t)(&connected_uis, channel_id, ui);
current_ui = channel_id;
ui_attach_impl(ui, channel_id); ui_attach_impl(ui, channel_id);
may_trigger_vim_suspend_resume(false); may_trigger_vim_suspend_resume(false);
@@ -214,6 +215,7 @@ void nvim_ui_set_focus(uint64_t channel_id, Boolean gained, Error *error)
} }
if (gained) { if (gained) {
current_ui = channel_id;
may_trigger_vim_suspend_resume(false); may_trigger_vim_suspend_resume(false);
} }

View File

@@ -359,11 +359,11 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_ks)
/// @param keys to be typed /// @param keys to be typed
/// @return Number of bytes actually written (can be fewer than /// @return Number of bytes actually written (can be fewer than
/// requested if the buffer becomes full). /// requested if the buffer becomes full).
Integer nvim_input(String keys) Integer nvim_input(uint64_t channel_id, String keys)
FUNC_API_SINCE(1) FUNC_API_FAST FUNC_API_SINCE(1) FUNC_API_FAST
{ {
may_trigger_vim_suspend_resume(false); may_trigger_vim_suspend_resume(false);
return (Integer)input_enqueue(keys); return (Integer)input_enqueue(channel_id, keys);
} }
/// Send mouse event from GUI. /// Send mouse event from GUI.
@@ -1485,7 +1485,8 @@ Array nvim_get_api_info(uint64_t channel_id, Arena *arena)
return rv; return rv;
} }
/// Self-identifies the client. Sets the `client` object returned by |nvim_get_chan_info()|. /// Self-identifies the client, and sets optional flags on the channel. Defines the `client` object
/// returned by |nvim_get_chan_info()|.
/// ///
/// Clients should call this just after connecting, to provide hints for debugging and /// Clients should call this just after connecting, to provide hints for debugging and
/// orchestration. (Note: Something is better than nothing! Fields are optional, but at least set /// orchestration. (Note: Something is better than nothing! Fields are optional, but at least set

View File

@@ -219,6 +219,7 @@ Channel *channel_alloc(ChannelStreamType type)
chan->refcount = 1; chan->refcount = 1;
chan->exit_status = -1; chan->exit_status = -1;
chan->streamtype = type; chan->streamtype = type;
chan->detach = false;
assert(chan->id <= VARNUMBER_MAX); assert(chan->id <= VARNUMBER_MAX);
pmap_put(uint64_t)(&channels, chan->id, chan); pmap_put(uint64_t)(&channels, chan->id, chan);
return chan; return chan;

View File

@@ -31,6 +31,9 @@ struct Channel {
} stream; } stream;
bool is_rpc; bool is_rpc;
bool detach; ///< Prevents self-exit on channel-close. Normally, Nvim self-exits if its primary
///< RPC channel is closed, unless detach=true. Note: currently, detach=false does
///< not FORCE self-exit.
RpcState rpc; RpcState rpc;
Terminal *term; Terminal *term;

View File

@@ -315,10 +315,8 @@ static void decref(Proc *proc)
static void proc_close(Proc *proc) static void proc_close(Proc *proc)
FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_NONNULL_ARG(1)
{ {
if (proc_is_tearing_down && (proc->detach || proc->type == kProcTypePty) if (proc_is_tearing_down && proc->closed && (proc->detach || proc->type == kProcTypePty)) {
&& proc->closed) { // If a detached/pty process dies while tearing down it might get closed twice.
// If a detached/pty process dies while tearing down it might get closed
// twice.
return; return;
} }
assert(!proc->closed); assert(!proc->closed);
@@ -427,19 +425,21 @@ static void exit_event(void **argv)
} }
} }
void exit_from_channel(int status) /// Performs self-exit because the primary RPC channel was closed.
void exit_on_closed_chan(int status)
{ {
DLOG("self-exit triggered by closed RPC channel...");
multiqueue_put(main_loop.fast_events, exit_event, (void *)(intptr_t)status); multiqueue_put(main_loop.fast_events, exit_event, (void *)(intptr_t)status);
} }
static void on_proc_exit(Proc *proc) static void on_proc_exit(Proc *proc)
{ {
Loop *loop = proc->loop; Loop *loop = proc->loop;
ILOG("exited: pid=%d status=%d stoptime=%" PRIu64, proc->pid, proc->status, ILOG("child exited: pid=%d status=%d" PRIu64, proc->pid, proc->status);
proc->stopped_time);
// XXX: This assumes the TUI never spawns any other processes...?
if (ui_client_channel_id) { if (ui_client_channel_id) {
exit_from_channel(proc->status); exit_on_closed_chan(proc->status);
} }
// Process has terminated, but there could still be data to be read from the // Process has terminated, but there could still be data to be read from the

View File

@@ -104,9 +104,10 @@ void stream_may_close(Stream *stream, bool rstream)
if (stream->closed) { if (stream->closed) {
return; return;
} }
assert(!stream->closed);
DLOG("closing Stream: %p", (void *)stream); DLOG("closing Stream: %p", (void *)stream);
stream->closed = true; stream->closed = true;
// TODO(justinmk): stream->close_cb is never actually invoked. Either remove it, or see if it can
// be used somewhere...
stream->close_cb = NULL; stream->close_cb = NULL;
stream->close_cb_data = NULL; stream->close_cb_data = NULL;

View File

@@ -732,6 +732,12 @@ M.cmds = {
addr_type = 'ADDR_NONE', addr_type = 'ADDR_NONE',
func = 'ex_delfunction', func = 'ex_delfunction',
}, },
{
command = 'detach',
flags = bit.bor(BANG, FILES, CMDARG, ARGOPT, TRLBAR, CMDWIN, LOCK_OK),
addr_type = 'ADDR_NONE',
func = 'ex_detach',
},
{ {
command = 'display', command = 'display',
flags = bit.bor(EXTRA, NOTRLCOM, TRLBAR, SBOXOK, CMDWIN, LOCK_OK), flags = bit.bor(EXTRA, NOTRLCOM, TRLBAR, SBOXOK, CMDWIN, LOCK_OK),

View File

@@ -14,6 +14,7 @@
#include "auto/config.h" #include "auto/config.h"
#include "nvim/api/private/defs.h" #include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h" #include "nvim/api/private/helpers.h"
#include "nvim/api/ui.h"
#include "nvim/arglist.h" #include "nvim/arglist.h"
#include "nvim/ascii_defs.h" #include "nvim/ascii_defs.h"
#include "nvim/autocmd.h" #include "nvim/autocmd.h"
@@ -21,6 +22,7 @@
#include "nvim/buffer.h" #include "nvim/buffer.h"
#include "nvim/buffer_defs.h" #include "nvim/buffer_defs.h"
#include "nvim/change.h" #include "nvim/change.h"
#include "nvim/channel.h"
#include "nvim/charset.h" #include "nvim/charset.h"
#include "nvim/cmdexpand.h" #include "nvim/cmdexpand.h"
#include "nvim/cmdexpand_defs.h" #include "nvim/cmdexpand_defs.h"
@@ -67,6 +69,7 @@
#include "nvim/message.h" #include "nvim/message.h"
#include "nvim/mouse.h" #include "nvim/mouse.h"
#include "nvim/move.h" #include "nvim/move.h"
#include "nvim/msgpack_rpc/server.h"
#include "nvim/normal.h" #include "nvim/normal.h"
#include "nvim/normal_defs.h" #include "nvim/normal_defs.h"
#include "nvim/ops.h" #include "nvim/ops.h"
@@ -5530,6 +5533,56 @@ static void ex_tabs(exarg_T *eap)
} }
} }
/// ":detach"
///
/// Detaches the current UI.
///
/// ":detach!" with bang (!) detaches all UIs _except_ the current UI.
static void ex_detach(exarg_T *eap)
{
// come on pooky let's burn this mf down
if (eap && eap->forceit) {
emsg("bang (!) not supported yet");
} else {
// 1. (TODO) Send "detach" UI-event (notification only).
// 2. Perform server-side `nvim_ui_detach`.
// 3. Close server-side channel without self-exit.
if (!current_ui) {
emsg("UI not attached");
return;
}
Channel *chan = find_channel(current_ui);
if (!chan) {
emsg(e_invchan);
return;
}
chan->detach = true; // Prevent self-exit on channel-close.
// Server-side UI detach. Doesn't close the channel.
Error err2 = ERROR_INIT;
nvim_ui_detach(chan->id, &err2);
if (ERROR_SET(&err2)) {
emsg(err2.msg); // UI disappeared already?
api_clear_error(&err2);
return;
}
// Server-side channel close.
const char *err = NULL;
bool rv = channel_close(chan->id, kChannelPartAll, &err);
if (!rv && err) {
emsg(err); // UI disappeared already?
return;
}
// XXX: Can't do this, channel_decref() is async...
// assert(!find_channel(chan->id));
ILOG("detach current_ui=%" PRId64, chan->id);
}
}
/// ":mode": /// ":mode":
/// If no argument given, get the screen size and redraw. /// If no argument given, get the screen size and redraw.
static void ex_mode(exarg_T *eap) static void ex_mode(exarg_T *eap)

View File

@@ -301,6 +301,8 @@ EXTERN bool garbage_collect_at_exit INIT( = false);
EXTERN sctx_T current_sctx INIT( = { 0, 0, 0 }); EXTERN sctx_T current_sctx INIT( = { 0, 0, 0 });
// ID of the current channel making a client API call // ID of the current channel making a client API call
EXTERN uint64_t current_channel_id INIT( = 0); EXTERN uint64_t current_channel_id INIT( = 0);
/// Last channel that invoked 'nvim_input` or got FocusGained.
EXTERN uint64_t current_ui INIT( = 0);
EXTERN bool did_source_packages INIT( = false); EXTERN bool did_source_packages INIT( = false);

View File

@@ -224,8 +224,7 @@ static size_t receive_msgpack(RStream *stream, const char *rbuf, size_t c, void
if (eof) { if (eof) {
channel_close(channel->id, kChannelPartRpc, NULL); channel_close(channel->id, kChannelPartRpc, NULL);
char buf[256]; char buf[256];
snprintf(buf, sizeof(buf), "ch %" PRIu64 " was closed by the client", snprintf(buf, sizeof(buf), "ch %" PRIu64 " was closed by the peer", channel->id);
channel->id);
chan_close_with_error(channel, buf, LOGLVL_INF); chan_close_with_error(channel, buf, LOGLVL_INF);
} }
@@ -293,7 +292,7 @@ static void parse_msgpack(Channel *channel)
Object res = p->result; Object res = p->result;
if (p->result.type != kObjectTypeArray) { if (p->result.type != kObjectTypeArray) {
chan_close_with_error(channel, "msgpack-rpc request args has to be an array", LOGLVL_ERR); chan_close_with_error(channel, "msgpack-rpc request args must be an array", LOGLVL_ERR);
return; return;
} }
Array arg = res.data.array; Array arg = res.data.array;
@@ -487,13 +486,16 @@ void rpc_close(Channel *channel)
channel->rpc.closed = true; channel->rpc.closed = true;
channel_decref(channel); channel_decref(channel);
if (channel->streamtype == kChannelStreamStdio if (ui_client_channel_id && channel->id == ui_client_channel_id) {
|| (channel->id == ui_client_channel_id && channel->streamtype != kChannelStreamProc)) { assert(!channel->detach); // `Channel.detach` is not currently used by the UI client.
if (channel->streamtype == kChannelStreamStdio) { exit_on_closed_chan(0);
} else if (channel->streamtype == kChannelStreamStdio) {
// Avoid hanging when there are no other UIs and a prompt is triggered on exit. // Avoid hanging when there are no other UIs and a prompt is triggered on exit.
remote_ui_disconnect(channel->id); remote_ui_disconnect(channel->id);
if (!channel->detach) {
exit_on_closed_chan(0);
} }
exit_from_channel(0);
} }
} }

View File

@@ -274,8 +274,10 @@ void input_enqueue_raw(const char *data, size_t size)
input_write_pos += to_write; input_write_pos += to_write;
} }
size_t input_enqueue(String keys) size_t input_enqueue(uint64_t chan_id, String keys)
{ {
current_ui = chan_id;
const char *ptr = keys.data; const char *ptr = keys.data;
const char *end = ptr + keys.size; const char *end = ptr + keys.size;

View File

@@ -61,9 +61,15 @@ uint64_t ui_client_start_server(int argc, char **argv)
CallbackReader on_err = CALLBACK_READER_INIT; CallbackReader on_err = CALLBACK_READER_INIT;
on_err.fwd_err = true; on_err.fwd_err = true;
#ifdef MSWIN
// TODO(justinmk): detach breaks `tt.setup_child_nvim` tests on Windows?
bool detach = os_env_exists("__NVIM_DETACH");
#else
bool detach = true;
#endif
Channel *channel = channel_job_start(args, get_vim_var_str(VV_PROGPATH), Channel *channel = channel_job_start(args, get_vim_var_str(VV_PROGPATH),
CALLBACK_READER_INIT, on_err, CALLBACK_NONE, CALLBACK_READER_INIT, on_err, CALLBACK_NONE,
false, true, true, false, kChannelStdinPipe, false, true, true, detach, kChannelStdinPipe,
NULL, 0, 0, NULL, &exit_status); NULL, 0, 0, NULL, &exit_status);
if (!channel) { if (!channel) {
return 0; return 0;

View File

@@ -1265,12 +1265,16 @@ describe('jobs', function()
]]) ]])
feed(':q<CR>') feed(':q<CR>')
if is_os('freebsd') then
screen:expect { any = vim.pesc('[Process exited 0]') }
else
screen:expect([[ screen:expect([[
| |
[Process exited 0]^ | [Process exited 0]^ |
|*4 |*4
{3:-- TERMINAL --} | {3:-- TERMINAL --} |
]]) ]])
end
end) end)
end) end)

View File

@@ -931,6 +931,8 @@ describe('put command', function()
end) end)
it('should ring the bell when deleting if not appropriate', function() it('should ring the bell when deleting if not appropriate', function()
t.skip(t.is_os('bsd'), 'crashes on freebsd')
command('goto 2') command('goto 2')
feed('i<bs><esc>') feed('i<bs><esc>')
expect([[ expect([[

View File

@@ -33,6 +33,133 @@ local assert_log = t.assert_log
local testlog = 'Xtest-tui-log' local testlog = 'Xtest-tui-log'
describe('TUI :detach', function()
before_each(function()
os.remove(testlog)
end)
teardown(function()
os.remove(testlog)
end)
it('does not stop server', function()
local job_opts = {
env = {
NVIM_LOG_FILE = testlog,
},
}
if is_os('win') then
-- TODO(justinmk): on Windows,
-- - tt.setup_child_nvim() is broken.
-- - session.lua is broken after the pipe closes.
-- So this test currently just exercises __NVIM_DETACH + :detach, without asserting anything.
-- TODO(justinmk): temporary hack for Windows.
job_opts.env['__NVIM_DETACH'] = '1'
n.clear(job_opts)
local screen = Screen.new(50, 10)
n.feed('iHello, World')
screen:expect([[
Hello, World^ |
{1:~ }|*8
{5:-- INSERT --} |
]])
-- local addr = api.nvim_get_vvar('servername')
eq(1, #n.api.nvim_list_uis())
-- TODO(justinmk): test util should not freak out when the pipe closes.
n.expect_exit(n.command, 'detach')
-- n.get_session():close() -- XXX: hangs
-- n.set_session(n.connect(addr)) -- XXX: hangs
-- eq(0, #n.api.nvim_list_uis()) -- XXX: hangs
-- Avoid a dangling process.
n.get_session():close('kill')
-- n.expect_exit(n.command, 'qall!')
return
end
local server_super = n.clear()
local client_super = n.new_session(true)
finally(function()
server_super:close()
client_super:close()
end)
local child_server = new_pipename()
local screen = tt.setup_child_nvim({
'--listen',
child_server,
'-u',
'NONE',
'-i',
'NONE',
'--cmd',
'colorscheme vim',
'--cmd',
nvim_set .. ' notermguicolors laststatus=2 background=dark',
}, job_opts)
tt.feed_data('iHello, World')
screen:expect {
grid = [[
Hello, World^ |
{4:~ }|*3
{MATCH:No Name}
{3:-- INSERT --} |
{3:-- TERMINAL --} |
]],
}
local child_session = n.connect(child_server)
finally(function()
child_session:request('nvim_command', 'qall!')
end)
local status, child_uis = child_session:request('nvim_list_uis')
assert(status)
eq(1, #child_uis)
tt.feed_data('\027\027:detach\013')
-- Note: "Process exited" message is misleading; tt.setup_child_nvim() sees the foreground
-- process (client) exited, and doesn't know the server is still running?
screen:expect {
any = [[Process exited 0]],
}
child_uis --[[@type any[] ]] = ({ child_session:request('nvim_list_uis') })[2]
eq(0, #child_uis)
-- NOTE: The tt.setup_child_nvim() screen just wraps :terminal, it's not connected to the child.
-- To use it again, we need to detach the old one.
screen:detach()
-- Edit some text on the headless server.
status = (child_session:request('nvim_input', 'ddiWe did it, pooky.<Esc><Esc>'))
assert(status)
-- Test reattach by connecting a new TUI.
local screen_reattached = tt.setup_child_nvim({
'--remote-ui',
'--server',
child_server,
}, job_opts)
screen_reattached:expect {
grid = [[
We did it, pooky^. |
{4:~ }|*3
{5:[No Name] [+] }|
|
{3:-- TERMINAL --} |
]],
}
end)
end)
if t.skip(is_os('win')) then if t.skip(is_os('win')) then
return return
end end
@@ -48,10 +175,7 @@ describe('TUI', function()
screen = tt.setup_child_nvim({ screen = tt.setup_child_nvim({
'--listen', '--listen',
child_server, child_server,
'-u', '--clean',
'NONE',
'-i',
'NONE',
'--cmd', '--cmd',
nvim_set .. ' notermguicolors laststatus=2 background=dark', nvim_set .. ' notermguicolors laststatus=2 background=dark',
'--cmd', '--cmd',
@@ -2222,7 +2346,7 @@ describe('TUI', function()
end) end)
local screen = tt.setup_screen( local screen = tt.setup_screen(
0, 0,
('"%s" -u NONE -i NONE --cmd "set noswapfile noshowcmd noruler" --cmd "normal iabc" > /dev/null 2>&1 && cat testF && rm testF'):format( ('"%s" --clean --cmd "set noswapfile noshowcmd noruler" --cmd "normal iabc" > /dev/null 2>&1 && cat testF && rm testF'):format(
nvim_prog nvim_prog
), ),
nil, nil,
@@ -2242,10 +2366,7 @@ describe('TUI', function()
it('<C-h> #10134', function() it('<C-h> #10134', function()
local screen = tt.setup_child_nvim({ local screen = tt.setup_child_nvim({
'-u', '--clean',
'NONE',
'-i',
'NONE',
'--cmd', '--cmd',
'colorscheme vim', 'colorscheme vim',
'--cmd', '--cmd',
@@ -2275,10 +2396,7 @@ describe('TUI', function()
it('draws line with many trailing spaces correctly #24955', function() it('draws line with many trailing spaces correctly #24955', function()
local screen = tt.setup_child_nvim({ local screen = tt.setup_child_nvim({
'-u', '--clean',
'NONE',
'-i',
'NONE',
'--cmd', '--cmd',
'set notermguicolors', 'set notermguicolors',
'--cmd', '--cmd',
@@ -2312,10 +2430,7 @@ describe('TUI', function()
it('draws screen lines with leading spaces correctly #29711', function() it('draws screen lines with leading spaces correctly #29711', function()
local screen = tt.setup_child_nvim({ local screen = tt.setup_child_nvim({
'-u', '--clean',
'NONE',
'-i',
'NONE',
'--cmd', '--cmd',
'set foldcolumn=6 | call setline(1, ["", repeat("aabb", 1000)]) | echo 42', 'set foldcolumn=6 | call setline(1, ["", repeat("aabb", 1000)]) | echo 42',
}, { extra_rows = 10, cols = 66 }) }, { extra_rows = 10, cols = 66 })
@@ -2355,10 +2470,7 @@ describe('TUI', function()
-- Set a different bg colour and change $TERM to something dumber so the `print_spaces()` -- Set a different bg colour and change $TERM to something dumber so the `print_spaces()`
-- codepath in `clear_region()` is hit. -- codepath in `clear_region()` is hit.
local screen = tt.setup_child_nvim({ local screen = tt.setup_child_nvim({
'-u', '--clean',
'NONE',
'-i',
'NONE',
'--cmd', '--cmd',
'set notermguicolors | highlight Normal ctermbg=red', 'set notermguicolors | highlight Normal ctermbg=red',
'--cmd', '--cmd',
@@ -2399,10 +2511,7 @@ describe('TUI UIEnter/UILeave', function()
it('fires exactly once, after VimEnter', function() it('fires exactly once, after VimEnter', function()
clear() clear()
local screen = tt.setup_child_nvim({ local screen = tt.setup_child_nvim({
'-u', '--clean',
'NONE',
'-i',
'NONE',
'--cmd', '--cmd',
'colorscheme vim', 'colorscheme vim',
'--cmd', '--cmd',
@@ -2665,10 +2774,7 @@ describe("TUI 't_Co' (terminal colors)", function()
local function assert_term_colors(term, colorterm, maxcolors) local function assert_term_colors(term, colorterm, maxcolors)
clear({ env = { TERM = term }, args = {} }) clear({ env = { TERM = term }, args = {} })
screen = tt.setup_child_nvim({ screen = tt.setup_child_nvim({
'-u', '--clean',
'NONE',
'-i',
'NONE',
'--cmd', '--cmd',
'colorscheme vim', 'colorscheme vim',
'--cmd', '--cmd',
@@ -2948,10 +3054,7 @@ describe("TUI 'term' option", function()
local function assert_term(term_envvar, term_expected) local function assert_term(term_envvar, term_expected)
clear() clear()
screen = tt.setup_child_nvim({ screen = tt.setup_child_nvim({
'-u', '--clean',
'NONE',
'-i',
'NONE',
'--cmd', '--cmd',
nvim_set .. ' notermguicolors', nvim_set .. ' notermguicolors',
}, { }, {
@@ -3008,10 +3111,7 @@ describe('TUI', function()
local function nvim_tui(extra_args) local function nvim_tui(extra_args)
clear() clear()
screen = tt.setup_child_nvim({ screen = tt.setup_child_nvim({
'-u', '--clean',
'NONE',
'-i',
'NONE',
'--cmd', '--cmd',
'colorscheme vim', 'colorscheme vim',
'--cmd', '--cmd',
@@ -3089,12 +3189,9 @@ describe('TUI', function()
local child_server = new_pipename() local child_server = new_pipename()
screen = tt.setup_child_nvim({ screen = tt.setup_child_nvim({
'--clean',
'--listen', '--listen',
child_server, child_server,
'-u',
'NONE',
'-i',
'NONE',
}, { }, {
env = { env = {
VIMRUNTIME = os.getenv('VIMRUNTIME'), VIMRUNTIME = os.getenv('VIMRUNTIME'),
@@ -3146,12 +3243,9 @@ describe('TUI', function()
local child_server = new_pipename() local child_server = new_pipename()
screen = tt.setup_child_nvim({ screen = tt.setup_child_nvim({
'--clean',
'--listen', '--listen',
child_server, child_server,
'-u',
'NONE',
'-i',
'NONE',
}, { }, {
env = { env = {
VIMRUNTIME = os.getenv('VIMRUNTIME'), VIMRUNTIME = os.getenv('VIMRUNTIME'),
@@ -3220,12 +3314,9 @@ describe('TUI bg color', function()
command('set background=dark') -- set outer Nvim background command('set background=dark') -- set outer Nvim background
local child_server = new_pipename() local child_server = new_pipename()
local screen = tt.setup_child_nvim({ local screen = tt.setup_child_nvim({
'--clean',
'--listen', '--listen',
child_server, child_server,
'-u',
'NONE',
'-i',
'NONE',
'--cmd', '--cmd',
'colorscheme vim', 'colorscheme vim',
'--cmd', '--cmd',
@@ -3243,12 +3334,9 @@ describe('TUI bg color', function()
command('set background=light') -- set outer Nvim background command('set background=light') -- set outer Nvim background
local child_server = new_pipename() local child_server = new_pipename()
local screen = tt.setup_child_nvim({ local screen = tt.setup_child_nvim({
'--clean',
'--listen', '--listen',
child_server, child_server,
'-u',
'NONE',
'-i',
'NONE',
'--cmd', '--cmd',
'colorscheme vim', 'colorscheme vim',
'--cmd', '--cmd',
@@ -3274,10 +3362,7 @@ describe('TUI bg color', function()
}) })
]]) ]])
tt.setup_child_nvim({ tt.setup_child_nvim({
'-u', '--clean',
'NONE',
'-i',
'NONE',
'--cmd', '--cmd',
'colorscheme vim', 'colorscheme vim',
'--cmd', '--cmd',
@@ -3290,10 +3375,7 @@ describe('TUI bg color', function()
it('triggers OptionSet from automatic background processing', function() it('triggers OptionSet from automatic background processing', function()
local screen = tt.setup_child_nvim({ local screen = tt.setup_child_nvim({
'-u', '--clean',
'NONE',
'-i',
'NONE',
'--cmd', '--cmd',
'colorscheme vim', 'colorscheme vim',
'--cmd', '--cmd',
@@ -3314,12 +3396,9 @@ describe('TUI bg color', function()
command('set background=dark') -- set outer Nvim background command('set background=dark') -- set outer Nvim background
local child_server = new_pipename() local child_server = new_pipename()
local screen = tt.setup_child_nvim({ local screen = tt.setup_child_nvim({
'--clean',
'--listen', '--listen',
child_server, child_server,
'-u',
'NONE',
'-i',
'NONE',
'--cmd', '--cmd',
'colorscheme vim', 'colorscheme vim',
'--cmd', '--cmd',
@@ -3349,12 +3428,9 @@ describe('TUI client', function()
set_session(server_super) set_session(server_super)
local server_pipe = new_pipename() local server_pipe = new_pipename()
local screen_server = tt.setup_child_nvim({ local screen_server = tt.setup_child_nvim({
'--clean',
'--listen', '--listen',
server_pipe, server_pipe,
'-u',
'NONE',
'-i',
'NONE',
'--cmd', '--cmd',
'colorscheme vim', 'colorscheme vim',
'--cmd', '--cmd',
@@ -3384,9 +3460,9 @@ describe('TUI client', function()
set_session(client_super) set_session(client_super)
local screen_client = tt.setup_child_nvim({ local screen_client = tt.setup_child_nvim({
'--remote-ui',
'--server', '--server',
server_pipe, server_pipe,
'--remote-ui',
}) })
screen_client:expect { screen_client:expect {
@@ -3428,9 +3504,9 @@ describe('TUI client', function()
set_session(client_super) set_session(client_super)
local screen_client = tt.setup_child_nvim({ local screen_client = tt.setup_child_nvim({
'--remote-ui',
'--server', '--server',
server_pipe, server_pipe,
'--remote-ui',
}) })
screen_client:expect { screen_client:expect {
@@ -3457,7 +3533,7 @@ describe('TUI client', function()
eq(0, api.nvim_get_vvar('shell_error')) eq(0, api.nvim_get_vvar('shell_error'))
-- exits on input eof #22244 -- exits on input eof #22244
fn.system({ nvim_prog, '--server', server_pipe, '--remote-ui' }) fn.system({ nvim_prog, '--remote-ui', '--server', server_pipe })
eq(1, api.nvim_get_vvar('shell_error')) eq(1, api.nvim_get_vvar('shell_error'))
client_super:close() client_super:close()
@@ -3470,9 +3546,9 @@ describe('TUI client', function()
it('throws error when no server exists', function() it('throws error when no server exists', function()
clear() clear()
local screen = tt.setup_child_nvim({ local screen = tt.setup_child_nvim({
'--remote-ui',
'--server', '--server',
'127.0.0.1:2436546', '127.0.0.1:2436546',
'--remote-ui',
}, { cols = 60 }) }, { cols = 60 })
screen:expect([[ screen:expect([[
@@ -3485,18 +3561,18 @@ describe('TUI client', function()
end) end)
local function test_remote_tui_quit(status) local function test_remote_tui_quit(status)
local server_super = n.new_session(false) local server_super = n.clear()
local client_super = n.new_session(true) local client_super = n.new_session(true)
finally(function()
server_super:close()
client_super:close()
end)
set_session(server_super)
local server_pipe = new_pipename() local server_pipe = new_pipename()
local screen_server = tt.setup_child_nvim({ local screen_server = tt.setup_child_nvim({
'--clean',
'--listen', '--listen',
server_pipe, server_pipe,
'-u',
'NONE',
'-i',
'NONE',
'--cmd', '--cmd',
'colorscheme vim', 'colorscheme vim',
'--cmd', '--cmd',
@@ -3535,9 +3611,9 @@ describe('TUI client', function()
set_session(client_super) set_session(client_super)
local screen_client = tt.setup_child_nvim({ local screen_client = tt.setup_child_nvim({
'--remote-ui',
'--server', '--server',
server_pipe, server_pipe,
'--remote-ui',
}) })
screen_client:expect { screen_client:expect {
@@ -3554,26 +3630,8 @@ describe('TUI client', function()
set_session(server_super) set_session(server_super)
feed_data(status and ':' .. status .. 'cquit!\n' or ':quit!\n') feed_data(status and ':' .. status .. 'cquit!\n' or ':quit!\n')
status = status and status or 0 status = status and status or 0
screen_server:expect { screen_server:expect({ any = 'Process exited ' .. status })
grid = [[ screen_client:expect({ any = 'Process exited ' .. status })
|
[Process exited ]] .. status .. [[]^ {MATCH:%s+}|
|*4
{3:-- TERMINAL --} |
]],
}
-- assert that client has exited
screen_client:expect {
grid = [[
|
[Process exited ]] .. status .. [[]^ {MATCH:%s+}|
|*4
{3:-- TERMINAL --} |
]],
}
server_super:close()
client_super:close()
end end
describe('exits when server quits', function() describe('exits when server quits', function()

View File

@@ -908,8 +908,10 @@ function M.is_asan()
return version:match('-fsanitize=[a-z,]*address') return version:match('-fsanitize=[a-z,]*address')
end end
-- Returns a valid, platform-independent Nvim listen address. --- Returns a valid, platform-independent Nvim listen address.
-- Useful for communicating with child instances. --- Useful for communicating with child instances.
---
--- @return string
function M.new_pipename() function M.new_pipename()
-- HACK: Start a server temporarily, get the name, then stop it. -- HACK: Start a server temporarily, get the name, then stop it.
local pipename = M.eval('serverstart()') local pipename = M.eval('serverstart()')