mirror of
https://github.com/neovim/neovim.git
synced 2025-09-30 15:08:35 +00:00
feat(ui): UI :detach command
Problem: Cannot detach the current UI. Solution: - Introduce `:detach`. - Introduce `Channel.detach`. Co-authored-by: bfredl <bjorn.linse@gmail.com>
This commit is contained in:
@@ -189,6 +189,7 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dict opt
|
||||
ui->wildmenu_active = false;
|
||||
|
||||
pmap_put(uint64_t)(&connected_uis, channel_id, ui);
|
||||
current_ui = channel_id;
|
||||
ui_attach_impl(ui, channel_id);
|
||||
|
||||
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) {
|
||||
current_ui = channel_id;
|
||||
may_trigger_vim_suspend_resume(false);
|
||||
}
|
||||
|
||||
|
@@ -359,11 +359,11 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_ks)
|
||||
/// @param keys to be typed
|
||||
/// @return Number of bytes actually written (can be fewer than
|
||||
/// 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
|
||||
{
|
||||
may_trigger_vim_suspend_resume(false);
|
||||
return (Integer)input_enqueue(keys);
|
||||
return (Integer)input_enqueue(channel_id, keys);
|
||||
}
|
||||
|
||||
/// Send mouse event from GUI.
|
||||
@@ -1485,7 +1485,8 @@ Array nvim_get_api_info(uint64_t channel_id, Arena *arena)
|
||||
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
|
||||
/// orchestration. (Note: Something is better than nothing! Fields are optional, but at least set
|
||||
|
@@ -219,6 +219,7 @@ Channel *channel_alloc(ChannelStreamType type)
|
||||
chan->refcount = 1;
|
||||
chan->exit_status = -1;
|
||||
chan->streamtype = type;
|
||||
chan->detach = false;
|
||||
assert(chan->id <= VARNUMBER_MAX);
|
||||
pmap_put(uint64_t)(&channels, chan->id, chan);
|
||||
return chan;
|
||||
|
@@ -31,6 +31,9 @@ struct Channel {
|
||||
} stream;
|
||||
|
||||
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;
|
||||
Terminal *term;
|
||||
|
||||
|
@@ -315,10 +315,8 @@ static void decref(Proc *proc)
|
||||
static void proc_close(Proc *proc)
|
||||
FUNC_ATTR_NONNULL_ARG(1)
|
||||
{
|
||||
if (proc_is_tearing_down && (proc->detach || proc->type == kProcTypePty)
|
||||
&& proc->closed) {
|
||||
// If a detached/pty process dies while tearing down it might get closed
|
||||
// twice.
|
||||
if (proc_is_tearing_down && proc->closed && (proc->detach || proc->type == kProcTypePty)) {
|
||||
// If a detached/pty process dies while tearing down it might get closed twice.
|
||||
return;
|
||||
}
|
||||
assert(!proc->closed);
|
||||
@@ -427,20 +425,22 @@ 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);
|
||||
}
|
||||
|
||||
static void on_proc_exit(Proc *proc)
|
||||
{
|
||||
Loop *loop = proc->loop;
|
||||
ILOG("exited: pid=%d status=%d stoptime=%" PRIu64, proc->pid, proc->status,
|
||||
proc->stopped_time);
|
||||
ILOG("child exited: pid=%d status=%d" PRIu64, proc->pid, proc->status);
|
||||
|
||||
if (ui_client_channel_id) {
|
||||
exit_from_channel(proc->status);
|
||||
}
|
||||
// XXX: This assumes the TUI never spawns any other processes...?
|
||||
// if (ui_client_channel_id) {
|
||||
// exit_on_closed_chan(proc->status);
|
||||
// }
|
||||
|
||||
// Process has terminated, but there could still be data to be read from the
|
||||
// OS. We are still in the libuv loop, so we cannot call code that polls for
|
||||
|
@@ -104,9 +104,10 @@ void stream_may_close(Stream *stream, bool rstream)
|
||||
if (stream->closed) {
|
||||
return;
|
||||
}
|
||||
assert(!stream->closed);
|
||||
DLOG("closing Stream: %p", (void *)stream);
|
||||
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_data = NULL;
|
||||
|
||||
|
@@ -732,6 +732,12 @@ M.cmds = {
|
||||
addr_type = 'ADDR_NONE',
|
||||
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',
|
||||
flags = bit.bor(EXTRA, NOTRLCOM, TRLBAR, SBOXOK, CMDWIN, LOCK_OK),
|
||||
|
@@ -14,6 +14,7 @@
|
||||
#include "auto/config.h"
|
||||
#include "nvim/api/private/defs.h"
|
||||
#include "nvim/api/private/helpers.h"
|
||||
#include "nvim/api/ui.h"
|
||||
#include "nvim/arglist.h"
|
||||
#include "nvim/ascii_defs.h"
|
||||
#include "nvim/autocmd.h"
|
||||
@@ -21,6 +22,7 @@
|
||||
#include "nvim/buffer.h"
|
||||
#include "nvim/buffer_defs.h"
|
||||
#include "nvim/change.h"
|
||||
#include "nvim/channel.h"
|
||||
#include "nvim/charset.h"
|
||||
#include "nvim/cmdexpand.h"
|
||||
#include "nvim/cmdexpand_defs.h"
|
||||
@@ -67,6 +69,7 @@
|
||||
#include "nvim/message.h"
|
||||
#include "nvim/mouse.h"
|
||||
#include "nvim/move.h"
|
||||
#include "nvim/msgpack_rpc/server.h"
|
||||
#include "nvim/normal.h"
|
||||
#include "nvim/normal_defs.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":
|
||||
/// If no argument given, get the screen size and redraw.
|
||||
static void ex_mode(exarg_T *eap)
|
||||
|
@@ -301,6 +301,8 @@ EXTERN bool garbage_collect_at_exit INIT( = false);
|
||||
EXTERN sctx_T current_sctx INIT( = { 0, 0, 0 });
|
||||
// ID of the current channel making a client API call
|
||||
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);
|
||||
|
||||
|
@@ -224,8 +224,7 @@ static size_t receive_msgpack(RStream *stream, const char *rbuf, size_t c, void
|
||||
if (eof) {
|
||||
channel_close(channel->id, kChannelPartRpc, NULL);
|
||||
char buf[256];
|
||||
snprintf(buf, sizeof(buf), "ch %" PRIu64 " was closed by the client",
|
||||
channel->id);
|
||||
snprintf(buf, sizeof(buf), "ch %" PRIu64 " was closed by the peer", channel->id);
|
||||
chan_close_with_error(channel, buf, LOGLVL_INF);
|
||||
}
|
||||
|
||||
@@ -293,7 +292,7 @@ static void parse_msgpack(Channel *channel)
|
||||
|
||||
Object res = p->result;
|
||||
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;
|
||||
}
|
||||
Array arg = res.data.array;
|
||||
@@ -487,13 +486,16 @@ void rpc_close(Channel *channel)
|
||||
channel->rpc.closed = true;
|
||||
channel_decref(channel);
|
||||
|
||||
if (channel->streamtype == kChannelStreamStdio
|
||||
|| (channel->id == ui_client_channel_id && channel->streamtype != kChannelStreamProc)) {
|
||||
if (channel->streamtype == kChannelStreamStdio) {
|
||||
// Avoid hanging when there are no other UIs and a prompt is triggered on exit.
|
||||
remote_ui_disconnect(channel->id);
|
||||
if (ui_client_channel_id && channel->id == ui_client_channel_id) {
|
||||
assert(!channel->detach); // `Channel.detach` is not currently used by the UI client.
|
||||
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.
|
||||
remote_ui_disconnect(channel->id);
|
||||
|
||||
if (!channel->detach) {
|
||||
exit_on_closed_chan(0);
|
||||
}
|
||||
exit_from_channel(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -274,8 +274,10 @@ void input_enqueue_raw(const char *data, size_t size)
|
||||
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 *end = ptr + keys.size;
|
||||
|
||||
|
@@ -61,9 +61,14 @@ uint64_t ui_client_start_server(int argc, char **argv)
|
||||
CallbackReader on_err = CALLBACK_READER_INIT;
|
||||
on_err.fwd_err = true;
|
||||
|
||||
#ifdef MSWIN
|
||||
bool detach = false; // TODO(justinmk): detach=true breaks `tt.setup_child_nvim` tests on Windows.
|
||||
#else
|
||||
bool detach = true;
|
||||
#endif
|
||||
Channel *channel = channel_job_start(args, get_vim_var_str(VV_PROGPATH),
|
||||
CALLBACK_READER_INIT, on_err, CALLBACK_NONE,
|
||||
false, true, true, false, kChannelStdinPipe,
|
||||
false, true, true, detach, kChannelStdinPipe,
|
||||
NULL, 0, 0, NULL, &exit_status);
|
||||
if (!channel) {
|
||||
return 0;
|
||||
|
Reference in New Issue
Block a user