mirror of
https://github.com/neovim/neovim.git
synced 2025-09-06 03:18:16 +00:00

Problem: When a TUI client is suspended it still receives UI events from the server, and has to process these accumulated events when it is resumed. With mulitple TUI clients this is a bigger problem, considering the following steps: 1. A TUI client is attached. 2. CTRL-Z is pressed and the first client is suspended. 3. Another TUI client is attached. 4. CTRL-Z is pressed and a "suspend" event is sent to both clients. The second client is suspended, while the first client isn't able to process the event because it has already been suspended. 5. The first client is resumed. It processes the accumulated "suspend" event and suspends immediately. Solution: Make a TUI client detach on suspend and re-attach on resume.
212 lines
6.1 KiB
C
212 lines
6.1 KiB
C
// This is an open source non-commercial project. Dear PVS-Studio, please check
|
|
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
|
|
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "nvim/api/private/helpers.h"
|
|
#include "nvim/channel.h"
|
|
#include "nvim/eval.h"
|
|
#include "nvim/eval/typval_defs.h"
|
|
#include "nvim/event/loop.h"
|
|
#include "nvim/globals.h"
|
|
#include "nvim/highlight.h"
|
|
#include "nvim/log.h"
|
|
#include "nvim/main.h"
|
|
#include "nvim/memory.h"
|
|
#include "nvim/msgpack_rpc/channel.h"
|
|
#include "nvim/msgpack_rpc/channel_defs.h"
|
|
#include "nvim/os/os_defs.h"
|
|
#include "nvim/tui/tui.h"
|
|
#include "nvim/ui.h"
|
|
#include "nvim/ui_client.h"
|
|
|
|
static TUIData *tui = NULL;
|
|
static bool ui_client_is_remote = false;
|
|
|
|
// uncrustify:off
|
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
|
# include "ui_client.c.generated.h"
|
|
# include "ui_events_client.generated.h"
|
|
#endif
|
|
// uncrustify:on
|
|
//
|
|
|
|
uint64_t ui_client_start_server(int argc, char **argv)
|
|
{
|
|
varnumber_T exit_status;
|
|
char **args = xmalloc(((size_t)(2 + argc)) * sizeof(char *));
|
|
int args_idx = 0;
|
|
args[args_idx++] = xstrdup((const char *)get_vim_var_str(VV_PROGPATH));
|
|
args[args_idx++] = xstrdup("--embed");
|
|
for (int i = 1; i < argc; i++) {
|
|
args[args_idx++] = xstrdup(argv[i]);
|
|
}
|
|
args[args_idx++] = NULL;
|
|
|
|
CallbackReader on_err = CALLBACK_READER_INIT;
|
|
on_err.fwd_err = true;
|
|
|
|
Channel *channel = channel_job_start(args, CALLBACK_READER_INIT,
|
|
on_err, CALLBACK_NONE,
|
|
false, true, true, false, kChannelStdinPipe,
|
|
NULL, 0, 0, NULL, &exit_status);
|
|
|
|
// If stdin is not a pty, it is forwarded to the client.
|
|
// Replace stdin in the TUI process with the tty fd.
|
|
if (ui_client_forward_stdin) {
|
|
close(0);
|
|
#ifdef MSWIN
|
|
os_open_conin_fd();
|
|
#else
|
|
dup(stderr_isatty ? STDERR_FILENO : STDOUT_FILENO);
|
|
#endif
|
|
}
|
|
|
|
return channel->id;
|
|
}
|
|
|
|
void ui_client_attach(int width, int height, char *term)
|
|
{
|
|
MAXSIZE_TEMP_ARRAY(args, 3);
|
|
ADD_C(args, INTEGER_OBJ(width));
|
|
ADD_C(args, INTEGER_OBJ(height));
|
|
|
|
MAXSIZE_TEMP_DICT(opts, 9);
|
|
PUT_C(opts, "rgb", BOOLEAN_OBJ(true));
|
|
PUT_C(opts, "ext_linegrid", BOOLEAN_OBJ(true));
|
|
PUT_C(opts, "ext_termcolors", BOOLEAN_OBJ(true));
|
|
if (term) {
|
|
PUT_C(opts, "term_name", STRING_OBJ(cstr_as_string(term)));
|
|
}
|
|
if (ui_client_bg_response != kNone) {
|
|
bool is_dark = (ui_client_bg_response == kTrue);
|
|
PUT_C(opts, "term_background", STRING_OBJ(cstr_as_string(is_dark ? "dark" : "light")));
|
|
}
|
|
PUT_C(opts, "term_colors", INTEGER_OBJ(t_colors));
|
|
if (!ui_client_is_remote) {
|
|
PUT_C(opts, "stdin_tty", BOOLEAN_OBJ(stdin_isatty));
|
|
PUT_C(opts, "stdout_tty", BOOLEAN_OBJ(stdout_isatty));
|
|
if (ui_client_forward_stdin) {
|
|
PUT_C(opts, "stdin_fd", INTEGER_OBJ(UI_CLIENT_STDIN_FD));
|
|
}
|
|
}
|
|
ADD_C(args, DICTIONARY_OBJ(opts));
|
|
|
|
rpc_send_event(ui_client_channel_id, "nvim_ui_attach", args);
|
|
ui_client_attached = true;
|
|
}
|
|
|
|
void ui_client_detach(void)
|
|
{
|
|
rpc_send_event(ui_client_channel_id, "nvim_ui_detach", (Array)ARRAY_DICT_INIT);
|
|
ui_client_attached = false;
|
|
}
|
|
|
|
void ui_client_run(bool remote_ui)
|
|
FUNC_ATTR_NORETURN
|
|
{
|
|
ui_client_is_remote = remote_ui;
|
|
int width, height;
|
|
char *term;
|
|
tui = tui_start(&width, &height, &term);
|
|
|
|
ui_client_attach(width, height, term);
|
|
|
|
// os_exit() will be invoked when the client channel detaches
|
|
while (true) {
|
|
LOOP_PROCESS_EVENTS(&main_loop, resize_events, -1);
|
|
}
|
|
}
|
|
|
|
void ui_client_stop(void)
|
|
{
|
|
tui_stop(tui);
|
|
}
|
|
|
|
void ui_client_set_size(int width, int height)
|
|
{
|
|
// The currently known size will be sent when attaching
|
|
if (ui_client_attached) {
|
|
MAXSIZE_TEMP_ARRAY(args, 2);
|
|
ADD_C(args, INTEGER_OBJ((int)width));
|
|
ADD_C(args, INTEGER_OBJ((int)height));
|
|
rpc_send_event(ui_client_channel_id, "nvim_ui_try_resize", args);
|
|
}
|
|
}
|
|
|
|
UIClientHandler ui_client_get_redraw_handler(const char *name, size_t name_len, Error *error)
|
|
{
|
|
int hash = ui_client_handler_hash(name, name_len);
|
|
if (hash < 0) {
|
|
return (UIClientHandler){ NULL, NULL };
|
|
}
|
|
return event_handlers[hash];
|
|
}
|
|
|
|
/// Placeholder for _sync_ requests with 'redraw' method name
|
|
///
|
|
/// async 'redraw' events, which are expected when nvim acts as an ui client.
|
|
/// get handled in msgpack_rpc/unpacker.c and directly dispatched to handlers
|
|
/// of specific ui events, like ui_client_event_grid_resize and so on.
|
|
Object handle_ui_client_redraw(uint64_t channel_id, Array args, Arena *arena, Error *error)
|
|
{
|
|
api_set_error(error, kErrorTypeValidation, "'redraw' cannot be sent as a request");
|
|
return NIL;
|
|
}
|
|
|
|
static HlAttrs ui_client_dict2hlattrs(Dictionary d, bool rgb)
|
|
{
|
|
Error err = ERROR_INIT;
|
|
Dict(highlight) dict = { 0 };
|
|
if (!api_dict_to_keydict(&dict, KeyDict_highlight_get_field, d, &err)) {
|
|
// TODO(bfredl): log "err"
|
|
return HLATTRS_INIT;
|
|
}
|
|
return dict2hlattrs(&dict, rgb, NULL, &err);
|
|
}
|
|
|
|
void ui_client_event_grid_resize(Array args)
|
|
{
|
|
if (args.size < 3
|
|
|| args.items[0].type != kObjectTypeInteger
|
|
|| args.items[1].type != kObjectTypeInteger
|
|
|| args.items[2].type != kObjectTypeInteger) {
|
|
ELOG("Error handling ui event 'grid_resize'");
|
|
return;
|
|
}
|
|
|
|
Integer grid = args.items[0].data.integer;
|
|
Integer width = args.items[1].data.integer;
|
|
Integer height = args.items[2].data.integer;
|
|
tui_grid_resize(tui, grid, width, height);
|
|
|
|
if (grid_line_buf_size < (size_t)width) {
|
|
xfree(grid_line_buf_char);
|
|
xfree(grid_line_buf_attr);
|
|
grid_line_buf_size = (size_t)width;
|
|
grid_line_buf_char = xmalloc(grid_line_buf_size * sizeof(schar_T));
|
|
grid_line_buf_attr = xmalloc(grid_line_buf_size * sizeof(sattr_T));
|
|
}
|
|
}
|
|
|
|
void ui_client_event_grid_line(Array args)
|
|
FUNC_ATTR_NORETURN
|
|
{
|
|
abort(); // unreachable
|
|
}
|
|
|
|
void ui_client_event_raw_line(GridLineEvent *g)
|
|
{
|
|
int grid = g->args[0], row = g->args[1], startcol = g->args[2];
|
|
Integer endcol = startcol + g->coloff;
|
|
Integer clearcol = endcol + g->clear_width;
|
|
|
|
// TODO(hlpr98): Accommodate other LineFlags when included in grid_line
|
|
LineFlags lineflags = 0;
|
|
|
|
tui_raw_line(tui, grid, row, startcol, endcol, clearcol, g->cur_attr, lineflags,
|
|
(const schar_T *)grid_line_buf_char, grid_line_buf_attr);
|
|
}
|