mirror of
https://github.com/neovim/neovim.git
synced 2025-10-15 14:26:07 +00:00
feat(ui): :connect command #34586
Add the `:connect <address>` command which connects the currently running TUI to the server at the given address.
This commit is contained in:
@@ -87,6 +87,22 @@ Restart Nvim
|
|||||||
event, this command is equivalent to `:qall`.
|
event, this command is equivalent to `:qall`.
|
||||||
Note: Only works if the UI and server are on the same system.
|
Note: Only works if the UI and server are on the same system.
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
Connect UI to a different server
|
||||||
|
|
||||||
|
*:connect*
|
||||||
|
|
||||||
|
:connect {address}
|
||||||
|
Detaches the UI from the server it is currently attached to
|
||||||
|
and attaches it to the server at {address} instead.
|
||||||
|
|
||||||
|
Note: If the current UI hasn't implemented the "connect" UI
|
||||||
|
event, this command is equivalent to |:detach|.
|
||||||
|
|
||||||
|
:connect! {address}
|
||||||
|
Same as |:connect| but it also stops the detached server if
|
||||||
|
no other UI is currently attached to it.
|
||||||
|
|
||||||
------------------------------------------------------------------------------
|
------------------------------------------------------------------------------
|
||||||
GUI commands
|
GUI commands
|
||||||
|
|
||||||
|
@@ -297,6 +297,8 @@ TUI
|
|||||||
UI
|
UI
|
||||||
|
|
||||||
• |:restart| restarts Nvim and reattaches the current UI.
|
• |:restart| restarts Nvim and reattaches the current UI.
|
||||||
|
• |:connect| dynamically connects the current UI to the server at the given
|
||||||
|
address.
|
||||||
• |:checkhealth| shows a summary in the header for every healthcheck.
|
• |:checkhealth| shows a summary in the header for every healthcheck.
|
||||||
• |ui-multigrid| provides composition information and absolute coordinates.
|
• |ui-multigrid| provides composition information and absolute coordinates.
|
||||||
• `vim._extui` provides an experimental commandline and message UI intended to
|
• `vim._extui` provides an experimental commandline and message UI intended to
|
||||||
|
@@ -319,6 +319,20 @@ bool remote_ui_restart(uint64_t channel_id, Error *err)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send a connect UI event to the UI on the given channel
|
||||||
|
void remote_ui_connect(uint64_t channel_id, char *server_addr, Error *err)
|
||||||
|
{
|
||||||
|
RemoteUI *ui = get_ui_or_err(channel_id, err);
|
||||||
|
if (!ui) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MAXSIZE_TEMP_ARRAY(args, 1);
|
||||||
|
ADD_C(args, CSTR_AS_OBJ(server_addr));
|
||||||
|
|
||||||
|
push_call(ui, "connect", args);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO(bfredl): use me to detach a specific ui from the server
|
// TODO(bfredl): use me to detach a specific ui from the server
|
||||||
void remote_ui_stop(RemoteUI *ui)
|
void remote_ui_stop(RemoteUI *ui)
|
||||||
{
|
{
|
||||||
|
@@ -27,6 +27,8 @@ void visual_bell(void)
|
|||||||
FUNC_API_SINCE(3);
|
FUNC_API_SINCE(3);
|
||||||
void flush(void)
|
void flush(void)
|
||||||
FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL;
|
FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL;
|
||||||
|
void connect(Array args)
|
||||||
|
FUNC_API_SINCE(14) FUNC_API_REMOTE_ONLY FUNC_API_REMOTE_IMPL FUNC_API_CLIENT_IMPL;
|
||||||
void restart(String progpath, Array argv)
|
void restart(String progpath, Array argv)
|
||||||
FUNC_API_SINCE(14) FUNC_API_REMOTE_ONLY FUNC_API_REMOTE_IMPL FUNC_API_CLIENT_IMPL;
|
FUNC_API_SINCE(14) FUNC_API_REMOTE_ONLY FUNC_API_REMOTE_IMPL FUNC_API_CLIENT_IMPL;
|
||||||
void suspend(void)
|
void suspend(void)
|
||||||
|
@@ -146,7 +146,7 @@ void stream_close_handle(Stream *stream, bool rstream)
|
|||||||
static void rstream_close_cb(uv_handle_t *handle)
|
static void rstream_close_cb(uv_handle_t *handle)
|
||||||
{
|
{
|
||||||
RStream *stream = handle->data;
|
RStream *stream = handle->data;
|
||||||
if (stream->buffer) {
|
if (stream && stream->buffer) {
|
||||||
free_block(stream->buffer);
|
free_block(stream->buffer);
|
||||||
}
|
}
|
||||||
close_cb(handle);
|
close_cb(handle);
|
||||||
@@ -155,10 +155,10 @@ static void rstream_close_cb(uv_handle_t *handle)
|
|||||||
static void close_cb(uv_handle_t *handle)
|
static void close_cb(uv_handle_t *handle)
|
||||||
{
|
{
|
||||||
Stream *stream = handle->data;
|
Stream *stream = handle->data;
|
||||||
if (stream->close_cb) {
|
if (stream && stream->close_cb) {
|
||||||
stream->close_cb(stream, stream->close_cb_data);
|
stream->close_cb(stream, stream->close_cb_data);
|
||||||
}
|
}
|
||||||
if (stream->internal_close_cb) {
|
if (stream && stream->internal_close_cb) {
|
||||||
stream->internal_close_cb(stream, stream->internal_data);
|
stream->internal_close_cb(stream, stream->internal_data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -582,6 +582,12 @@ M.cmds = {
|
|||||||
addr_type = 'ADDR_OTHER',
|
addr_type = 'ADDR_OTHER',
|
||||||
func = 'ex_menu',
|
func = 'ex_menu',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
command = 'connect',
|
||||||
|
flags = bit.bor(BANG, WORD1, NOTRLCOM, NEEDARG),
|
||||||
|
addr_type = 'ADDR_NONE',
|
||||||
|
func = 'ex_connect',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
command = 'copy',
|
command = 'copy',
|
||||||
flags = bit.bor(RANGE, WHOLEFOLD, EXTRA, TRLBAR, CMDWIN, LOCK_OK, MODIFY),
|
flags = bit.bor(RANGE, WHOLEFOLD, EXTRA, TRLBAR, CMDWIN, LOCK_OK, MODIFY),
|
||||||
|
@@ -5649,6 +5649,32 @@ static void ex_detach(exarg_T *eap)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ":connect"
|
||||||
|
///
|
||||||
|
/// Connects the current UI to a different server
|
||||||
|
///
|
||||||
|
/// ":connect <address>" detaches the current UI and connects to the given server.
|
||||||
|
/// ":connect! <address>" stops the current server if no other UIs are attached, then connects to the given server.
|
||||||
|
static void ex_connect(exarg_T *eap)
|
||||||
|
{
|
||||||
|
bool stop_server = eap->forceit ? (ui_active() == 1) : false;
|
||||||
|
|
||||||
|
Error err = ERROR_INIT;
|
||||||
|
remote_ui_connect(current_ui, eap->arg, &err);
|
||||||
|
|
||||||
|
if (ERROR_SET(&err)) {
|
||||||
|
emsg(err.msg);
|
||||||
|
api_clear_error(&err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ex_detach(NULL);
|
||||||
|
if (stop_server) {
|
||||||
|
exiting = true;
|
||||||
|
getout(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// ":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)
|
||||||
|
@@ -281,6 +281,38 @@ void ui_client_event_raw_line(GridLineEvent *g)
|
|||||||
(const schar_T *)grid_line_buf_char, grid_line_buf_attr);
|
(const schar_T *)grid_line_buf_char, grid_line_buf_attr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ui_client_event_connect(Array args)
|
||||||
|
{
|
||||||
|
if (args.size < 1 || args.items[0].type != kObjectTypeString) {
|
||||||
|
ELOG("Error handling UI event 'connect'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *server_addr = args.items[0].data.string.data;
|
||||||
|
multiqueue_put(main_loop.fast_events, channel_connect_event, server_addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void channel_connect_event(void **argv)
|
||||||
|
{
|
||||||
|
char *server_addr = argv[0];
|
||||||
|
|
||||||
|
const char *err = "";
|
||||||
|
bool is_tcp = !!strrchr(server_addr, ':');
|
||||||
|
CallbackReader on_data = CALLBACK_READER_INIT;
|
||||||
|
uint64_t chan = channel_connect(is_tcp, server_addr, true, on_data, 50, &err);
|
||||||
|
|
||||||
|
if (!strequal(err, "")) {
|
||||||
|
ELOG("Error handling UI event 'connect': %s", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ui_client_channel_id = chan;
|
||||||
|
ui_client_is_remote = true;
|
||||||
|
ui_client_attach(tui_width, tui_height, tui_term, tui_rgb);
|
||||||
|
|
||||||
|
ELOG("Connected to channel: %" PRId64, chan);
|
||||||
|
}
|
||||||
|
|
||||||
/// When a "restart" UI event is received, its arguments are saved here when
|
/// When a "restart" UI event is received, its arguments are saved here when
|
||||||
/// waiting for the server to exit.
|
/// waiting for the server to exit.
|
||||||
static Array restart_args = ARRAY_DICT_INIT;
|
static Array restart_args = ARRAY_DICT_INIT;
|
||||||
|
@@ -365,6 +365,123 @@ describe('TUI :restart', function()
|
|||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
describe('TUI :connect', function()
|
||||||
|
if t.skip(is_os('win'), "relies on :detach which currently doesn't work on windows") then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
it('leaves the current server running', function()
|
||||||
|
n.clear()
|
||||||
|
finally(function()
|
||||||
|
n.check_close()
|
||||||
|
end)
|
||||||
|
|
||||||
|
local server1 = new_pipename()
|
||||||
|
local screen = tt.setup_child_nvim({
|
||||||
|
'--listen',
|
||||||
|
server1,
|
||||||
|
'-u',
|
||||||
|
'NONE',
|
||||||
|
})
|
||||||
|
|
||||||
|
tt.feed_data(':connect\013')
|
||||||
|
screen:expect([[
|
||||||
|
^ |
|
||||||
|
~ |*3
|
||||||
|
[No Name] 0,0-1 All|
|
||||||
|
E471: Argument required |
|
||||||
|
{5:-- TERMINAL --} |
|
||||||
|
]])
|
||||||
|
|
||||||
|
screen:detach()
|
||||||
|
|
||||||
|
local server2 = new_pipename()
|
||||||
|
local screen2 = tt.setup_child_nvim({
|
||||||
|
'--listen',
|
||||||
|
server2,
|
||||||
|
'-u',
|
||||||
|
'NONE',
|
||||||
|
})
|
||||||
|
tt.feed_data('iThis is server 2.\027')
|
||||||
|
tt.feed_data(':connect ' .. server1 .. '\013')
|
||||||
|
|
||||||
|
screen2:expect({
|
||||||
|
any = [[Process exited]],
|
||||||
|
})
|
||||||
|
|
||||||
|
local server1_session = n.connect(server1)
|
||||||
|
server1_session:request('nvim_command', 'qall!')
|
||||||
|
|
||||||
|
screen2:detach()
|
||||||
|
|
||||||
|
local server2_session = n.connect(server2)
|
||||||
|
|
||||||
|
local screen3 = tt.setup_child_nvim({
|
||||||
|
'--remote-ui',
|
||||||
|
'--server',
|
||||||
|
server2,
|
||||||
|
})
|
||||||
|
screen3:expect([[
|
||||||
|
This is server 2^. |
|
||||||
|
~ |*3
|
||||||
|
{2:[No Name] [+] 1,17 All}|
|
||||||
|
|
|
||||||
|
{5:-- TERMINAL --} |
|
||||||
|
]])
|
||||||
|
|
||||||
|
screen3:detach()
|
||||||
|
server2_session:request('nvim_command', 'qall!')
|
||||||
|
end)
|
||||||
|
it('! stops the current server', function()
|
||||||
|
n.clear()
|
||||||
|
finally(function()
|
||||||
|
n.check_close()
|
||||||
|
end)
|
||||||
|
|
||||||
|
local server1 = new_pipename()
|
||||||
|
local screen1 = tt.setup_child_nvim({
|
||||||
|
'--listen',
|
||||||
|
server1,
|
||||||
|
})
|
||||||
|
tt.feed_data('iThis is server 1')
|
||||||
|
|
||||||
|
screen1:detach()
|
||||||
|
|
||||||
|
local server2 = new_pipename()
|
||||||
|
local screen2 = tt.setup_child_nvim({
|
||||||
|
'--listen',
|
||||||
|
server2,
|
||||||
|
})
|
||||||
|
tt.feed_data('\027:connect! ' .. server1 .. '\013')
|
||||||
|
screen2:expect([[
|
||||||
|
This is server 1^ |
|
||||||
|
~ |*3
|
||||||
|
[No Name] [+] 1,17 All|
|
||||||
|
-- INSERT -- |
|
||||||
|
{5:-- TERMINAL --} |
|
||||||
|
]])
|
||||||
|
|
||||||
|
local server1_session = n.connect(server1)
|
||||||
|
server1_session:request('nvim_command', 'qall!')
|
||||||
|
|
||||||
|
screen2:detach()
|
||||||
|
|
||||||
|
local screen3 = tt.setup_child_nvim({
|
||||||
|
'--remote-ui',
|
||||||
|
'--server',
|
||||||
|
server2,
|
||||||
|
})
|
||||||
|
screen3:expect([[
|
||||||
|
Remote ui failed to start: connection refused |
|
||||||
|
|
|
||||||
|
[Process exited 1]^ |
|
||||||
|
|*3
|
||||||
|
{5:-- TERMINAL --} |
|
||||||
|
]])
|
||||||
|
screen3:detach()
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
if t.skip(is_os('win')) then
|
if t.skip(is_os('win')) then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
Reference in New Issue
Block a user