refactor(ui): devirtualize the ui layer

- The defined interface for the UI is only the RPC protocol. The original
  UI interface as an array of function pointers fill no function.
- On the server, all the UI:s are all RPC channels.
  - ui.c is only used on the server.
  - The compositor is a preprocessing step for single-grid UI:s
- on the client, ui_client and tui talk directly to each other
  - we still do module separation, as ui_client.c could form the basis
    of a libnvim client module later.

Items for later PR:s
- vim.ui_attach is still an unhappy child, reconsider based on plugin experience.
- the flags in ui_events.in.h are still a mess. Can be simplified now.
- UX for remote attachment needs more work.
- startup for client can be simplified further (think of the millisecs we can save)
This commit is contained in:
bfredl 2022-12-30 22:17:01 +01:00
parent ae64772a88
commit 47ba78f89a
16 changed files with 941 additions and 1106 deletions

View File

@ -36,7 +36,6 @@ set(HEADER_GENERATOR ${GENERATOR_DIR}/gen_declarations.lua)
set(GENERATED_INCLUDES_DIR ${PROJECT_BINARY_DIR}/include)
set(GENERATED_API_DISPATCH ${GENERATED_DIR}/api/private/dispatch_wrappers.generated.h)
set(GENERATED_FUNCS_METADATA ${GENERATED_DIR}/api/private/funcs_metadata.generated.h)
set(GENERATED_UI_EVENTS ${GENERATED_DIR}/ui_events.generated.h)
set(GENERATED_UI_EVENTS_CALL ${GENERATED_DIR}/ui_events_call.generated.h)
set(GENERATED_UI_EVENTS_REMOTE ${GENERATED_DIR}/ui_events_remote.generated.h)
set(GENERATED_UI_EVENTS_CLIENT ${GENERATED_DIR}/ui_events_client.generated.h)
@ -310,14 +309,12 @@ list(APPEND NVIM_GENERATED_SOURCES
)
add_custom_command(
OUTPUT ${GENERATED_UI_EVENTS}
${GENERATED_UI_EVENTS_CALL}
OUTPUT ${GENERATED_UI_EVENTS_CALL}
${GENERATED_UI_EVENTS_REMOTE}
${GENERATED_UI_EVENTS_METADATA}
${GENERATED_UI_EVENTS_CLIENT}
COMMAND ${LUA_PRG} ${API_UI_EVENTS_GENERATOR} ${CMAKE_CURRENT_LIST_DIR}
${CMAKE_CURRENT_LIST_DIR}/api/ui_events.in.h
${GENERATED_UI_EVENTS}
${GENERATED_UI_EVENTS_CALL}
${GENERATED_UI_EVENTS_REMOTE}
${GENERATED_UI_EVENTS_METADATA}

View File

@ -29,41 +29,6 @@
#include "nvim/vim.h"
#include "nvim/window.h"
typedef struct {
uint64_t channel_id;
#define UI_BUF_SIZE 4096 ///< total buffer size for pending msgpack data.
/// guaranteed size available for each new event (so packing of simple events
/// and the header of grid_line will never fail)
#define EVENT_BUF_SIZE 256
char buf[UI_BUF_SIZE]; ///< buffer of packed but not yet sent msgpack data
char *buf_wptr; ///< write head of buffer
const char *cur_event; ///< name of current event (might get multiple arglists)
Array call_buf; ///< buffer for constructing a single arg list (max 16 elements!)
// state for write_cb, while packing a single arglist to msgpack. This
// might fail due to buffer overflow.
size_t pack_totlen;
bool buf_overflow;
char *temp_buf;
// We start packing the two outermost msgpack arrays before knowing the total
// number of elements. Thus track the location where array size will need
// to be written in the msgpack buffer, once the specific array is finished.
char *nevents_pos;
char *ncalls_pos;
uint32_t nevents; ///< number of distinct events (top-level args to "redraw"
uint32_t ncalls; ///< number of calls made to the current event (plus one for the name!)
bool flushed_events; ///< events where sent to client without "flush" event
int hl_id; // Current highlight for legacy put event.
Integer cursor_row, cursor_col; // Intended visible cursor position.
// Position of legacy cursor, used both for drawing and visible user cursor.
Integer client_row, client_col;
bool wildmenu_active;
} UIData;
#define BUF_POS(data) ((size_t)((data)->buf_wptr - (data)->buf))
#ifdef INCLUDE_GENERATED_DECLARATIONS
@ -143,8 +108,6 @@ void remote_ui_disconnect(uint64_t channel_id)
UIData *data = ui->data;
kv_destroy(data->call_buf);
pmap_del(uint64_t)(&connected_uis, channel_id);
xfree(data);
ui->data = NULL; // Flag UI as "stopped".
ui_detach_impl(ui, channel_id);
xfree(ui);
}
@ -204,32 +167,6 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictiona
ui->pum_col = -1.0;
ui->rgb = true;
ui->override = false;
ui->grid_resize = remote_ui_grid_resize;
ui->grid_clear = remote_ui_grid_clear;
ui->grid_cursor_goto = remote_ui_grid_cursor_goto;
ui->mode_info_set = remote_ui_mode_info_set;
ui->update_menu = remote_ui_update_menu;
ui->busy_start = remote_ui_busy_start;
ui->busy_stop = remote_ui_busy_stop;
ui->mouse_on = remote_ui_mouse_on;
ui->mouse_off = remote_ui_mouse_off;
ui->mode_change = remote_ui_mode_change;
ui->grid_scroll = remote_ui_grid_scroll;
ui->hl_attr_define = remote_ui_hl_attr_define;
ui->hl_group_set = remote_ui_hl_group_set;
ui->raw_line = remote_ui_raw_line;
ui->bell = remote_ui_bell;
ui->visual_bell = remote_ui_visual_bell;
ui->default_colors_set = remote_ui_default_colors_set;
ui->flush = remote_ui_flush;
ui->suspend = remote_ui_suspend;
ui->set_title = remote_ui_set_title;
ui->set_icon = remote_ui_set_icon;
ui->option_set = remote_ui_option_set;
ui->msg_set_pos = remote_ui_msg_set_pos;
ui->event = remote_ui_event;
ui->inspect = remote_ui_inspect;
ui->win_viewport = remote_ui_win_viewport;
CLEAR_FIELD(ui->ui_ext);
@ -252,7 +189,7 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictiona
ui->ui_ext[kUICmdline] = true;
}
UIData *data = xmalloc(sizeof(UIData));
UIData *data = ui->data;
data->channel_id = channel_id;
data->cur_event = NULL;
data->hl_id = 0;
@ -267,7 +204,6 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictiona
data->wildmenu_active = false;
data->call_buf = (Array)ARRAY_DICT_INIT;
kv_ensure_space(data->call_buf, 16);
ui->data = data;
pmap_put(uint64_t)(&connected_uis, channel_id, ui);
ui_attach_impl(ui, channel_id);
@ -313,6 +249,10 @@ void nvim_ui_detach(uint64_t channel_id, Error *err)
remote_ui_disconnect(channel_id);
}
// TODO(bfredl): use me to detach a specifc ui from the server
void remote_ui_stop(UI *ui)
{}
void nvim_ui_try_resize(uint64_t channel_id, Integer width, Integer height, Error *err)
FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
{
@ -684,7 +624,7 @@ static void push_call(UI *ui, const char *name, Array args)
data->ncalls++;
}
static void remote_ui_grid_clear(UI *ui, Integer grid)
void remote_ui_grid_clear(UI *ui, Integer grid)
{
UIData *data = ui->data;
Array args = data->call_buf;
@ -695,7 +635,7 @@ static void remote_ui_grid_clear(UI *ui, Integer grid)
push_call(ui, name, args);
}
static void remote_ui_grid_resize(UI *ui, Integer grid, Integer width, Integer height)
void remote_ui_grid_resize(UI *ui, Integer grid, Integer width, Integer height)
{
UIData *data = ui->data;
Array args = data->call_buf;
@ -708,8 +648,8 @@ static void remote_ui_grid_resize(UI *ui, Integer grid, Integer width, Integer h
push_call(ui, name, args);
}
static void remote_ui_grid_scroll(UI *ui, Integer grid, Integer top, Integer bot, Integer left,
Integer right, Integer rows, Integer cols)
void remote_ui_grid_scroll(UI *ui, Integer grid, Integer top, Integer bot, Integer left,
Integer right, Integer rows, Integer cols)
{
UIData *data = ui->data;
if (ui->ui_ext[kUILinegrid]) {
@ -745,8 +685,8 @@ static void remote_ui_grid_scroll(UI *ui, Integer grid, Integer top, Integer bot
}
}
static void remote_ui_default_colors_set(UI *ui, Integer rgb_fg, Integer rgb_bg, Integer rgb_sp,
Integer cterm_fg, Integer cterm_bg)
void remote_ui_default_colors_set(UI *ui, Integer rgb_fg, Integer rgb_bg, Integer rgb_sp,
Integer cterm_fg, Integer cterm_bg)
{
if (!ui->ui_ext[kUITermColors]) {
HL_SET_DEFAULT_COLORS(rgb_fg, rgb_bg, rgb_sp);
@ -776,8 +716,8 @@ static void remote_ui_default_colors_set(UI *ui, Integer rgb_fg, Integer rgb_bg,
}
}
static void remote_ui_hl_attr_define(UI *ui, Integer id, HlAttrs rgb_attrs, HlAttrs cterm_attrs,
Array info)
void remote_ui_hl_attr_define(UI *ui, Integer id, HlAttrs rgb_attrs, HlAttrs cterm_attrs,
Array info)
{
if (!ui->ui_ext[kUILinegrid]) {
return;
@ -802,7 +742,7 @@ static void remote_ui_hl_attr_define(UI *ui, Integer id, HlAttrs rgb_attrs, HlAt
push_call(ui, "hl_attr_define", args);
}
static void remote_ui_highlight_set(UI *ui, int id)
void remote_ui_highlight_set(UI *ui, int id)
{
UIData *data = ui->data;
Array args = data->call_buf;
@ -818,7 +758,7 @@ static void remote_ui_highlight_set(UI *ui, int id)
}
/// "true" cursor used only for input focus
static void remote_ui_grid_cursor_goto(UI *ui, Integer grid, Integer row, Integer col)
void remote_ui_grid_cursor_goto(UI *ui, Integer grid, Integer row, Integer col)
{
if (ui->ui_ext[kUILinegrid]) {
UIData *data = ui->data;
@ -836,7 +776,7 @@ static void remote_ui_grid_cursor_goto(UI *ui, Integer grid, Integer row, Intege
}
/// emulated cursor used both for drawing and for input focus
static void remote_ui_cursor_goto(UI *ui, Integer row, Integer col)
void remote_ui_cursor_goto(UI *ui, Integer row, Integer col)
{
UIData *data = ui->data;
if (data->client_row == row && data->client_col == col) {
@ -850,7 +790,7 @@ static void remote_ui_cursor_goto(UI *ui, Integer row, Integer col)
push_call(ui, "cursor_goto", args);
}
static void remote_ui_put(UI *ui, const char *cell)
void remote_ui_put(UI *ui, const char *cell)
{
UIData *data = ui->data;
data->client_col++;
@ -859,9 +799,9 @@ static void remote_ui_put(UI *ui, const char *cell)
push_call(ui, "put", args);
}
static void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Integer endcol,
Integer clearcol, Integer clearattr, LineFlags flags,
const schar_T *chunk, const sattr_T *attrs)
void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Integer endcol,
Integer clearcol, Integer clearattr, LineFlags flags, const schar_T *chunk,
const sattr_T *attrs)
{
UIData *data = ui->data;
if (ui->ui_ext[kUILinegrid]) {
@ -953,7 +893,7 @@ static void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startc
///
/// This might happen multiple times before the actual ui_flush, if the
/// total redraw size is large!
static void remote_ui_flush_buf(UI *ui)
void remote_ui_flush_buf(UI *ui)
{
UIData *data = ui->data;
if (!data->nevents_pos) {
@ -980,7 +920,7 @@ static void remote_ui_flush_buf(UI *ui)
///
/// Clients can know this happened by a final "flush" event at the end of the
/// "redraw" batch.
static void remote_ui_flush(UI *ui)
void remote_ui_flush(UI *ui)
{
UIData *data = ui->data;
if (data->nevents > 0 || data->flushed_events) {
@ -1025,7 +965,7 @@ static Array translate_firstarg(UI *ui, Array args, Arena *arena)
return new_args;
}
static void remote_ui_event(UI *ui, char *name, Array args)
void remote_ui_event(UI *ui, char *name, Array args)
{
Arena arena = ARENA_EMPTY;
UIData *data = ui->data;
@ -1092,7 +1032,7 @@ free_ret:
arena_mem_free(arena_finish(&arena));
}
static void remote_ui_inspect(UI *ui, Dictionary *info)
void remote_ui_inspect(UI *ui, Dictionary *info)
{
UIData *data = ui->data;
PUT(*info, "chan", INTEGER_OBJ((Integer)data->channel_id));

View File

@ -5,8 +5,10 @@
#include "nvim/api/private/defs.h"
#include "nvim/map.h"
#include "nvim/ui.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/ui.h.generated.h"
# include "ui_events_remote.h.generated.h"
#endif
#endif // NVIM_API_UI_H

View File

@ -37,7 +37,7 @@ void set_title(String title)
void set_icon(String icon)
FUNC_API_SINCE(3);
void screenshot(String path)
FUNC_API_SINCE(7) FUNC_API_REMOTE_IMPL;
FUNC_API_SINCE(7);
void option_set(String name, Object value)
FUNC_API_SINCE(4);
// Stop event is not exported as such, represented by EOF in the msgpack stream.
@ -75,7 +75,7 @@ void default_colors_set(Integer rgb_fg, Integer rgb_bg, Integer rgb_sp, Integer
void hl_attr_define(Integer id, HlAttrs rgb_attrs, HlAttrs cterm_attrs, Array info)
FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL;
void hl_group_set(String name, Integer id)
FUNC_API_SINCE(6);
FUNC_API_SINCE(6) FUNC_API_CLIENT_IGNORE;
void grid_resize(Integer grid, Integer width, Integer height)
FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL FUNC_API_CLIENT_IMPL;
void grid_clear(Integer grid)
@ -97,9 +97,6 @@ void raw_line(Integer grid, Integer row, Integer startcol, Integer endcol, Integ
Integer clearattr, LineFlags flags, const schar_T *chunk, const sattr_T *attrs)
FUNC_API_NOEXPORT FUNC_API_COMPOSITOR_IMPL;
void event(char *name, Array args)
FUNC_API_NOEXPORT FUNC_API_COMPOSITOR_IMPL;
void win_pos(Integer grid, Window win, Integer startrow, Integer startcol, Integer width,
Integer height)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;
@ -114,11 +111,11 @@ void win_close(Integer grid)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;
void msg_set_pos(Integer grid, Integer row, Boolean scrolled, String sep_char)
FUNC_API_SINCE(6) FUNC_API_COMPOSITOR_IMPL;
FUNC_API_SINCE(6) FUNC_API_COMPOSITOR_IMPL FUNC_API_CLIENT_IGNORE;
void win_viewport(Integer grid, Window win, Integer topline, Integer botline, Integer curline,
Integer curcol, Integer line_count)
FUNC_API_SINCE(7) FUNC_API_BRIDGE_IMPL;
FUNC_API_SINCE(7) FUNC_API_CLIENT_IGNORE;
void win_extmark(Integer grid, Window win, Integer ns_id, Integer mark_id, Integer row, Integer col)
FUNC_API_SINCE(10) FUNC_API_REMOTE_ONLY;
@ -150,11 +147,11 @@ void cmdline_block_hide(void)
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
void wildmenu_show(Array items)
FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL;
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
void wildmenu_select(Integer selected)
FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL;
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
void wildmenu_hide(void)
FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL;
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
void msg_show(String kind, Array content, Boolean replace_last)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;

View File

@ -49,9 +49,9 @@ local c_proto = Ct(
(fill * Cg((P('FUNC_API_LUA_ONLY') * Cc(true)), 'lua_only') ^ -1) *
(fill * Cg((P('FUNC_API_CHECK_TEXTLOCK') * Cc(true)), 'check_textlock') ^ -1) *
(fill * Cg((P('FUNC_API_REMOTE_IMPL') * Cc(true)), 'remote_impl') ^ -1) *
(fill * Cg((P('FUNC_API_BRIDGE_IMPL') * Cc(true)), 'bridge_impl') ^ -1) *
(fill * Cg((P('FUNC_API_COMPOSITOR_IMPL') * Cc(true)), 'compositor_impl') ^ -1) *
(fill * Cg((P('FUNC_API_CLIENT_IMPL') * Cc(true)), 'client_impl') ^ -1) *
(fill * Cg((P('FUNC_API_CLIENT_IGNORE') * Cc(true)), 'client_ignore') ^ -1) *
fill * P(';')
)

View File

@ -3,13 +3,12 @@ local mpack = require('mpack')
local nvimdir = arg[1]
package.path = nvimdir .. '/?.lua;' .. package.path
assert(#arg == 7)
assert(#arg == 6)
local input = io.open(arg[2], 'rb')
local proto_output = io.open(arg[3], 'wb')
local call_output = io.open(arg[4], 'wb')
local remote_output = io.open(arg[5], 'wb')
local metadata_output = io.open(arg[6], 'wb')
local client_output = io.open(arg[7], 'wb')
local call_output = io.open(arg[3], 'wb')
local remote_output = io.open(arg[4], 'wb')
local metadata_output = io.open(arg[5], 'wb')
local client_output = io.open(arg[6], 'wb')
local c_grammar = require('generators.c_grammar')
local events = c_grammar.grammar:match(input:read('*all'))
@ -81,12 +80,9 @@ local function call_ui_event_method(output, ev)
end
end
output:write(' ui_call_'..ev.name..'(')
output:write(' tui_'..ev.name..'(tui')
for j = 1, #ev.parameters do
output:write('arg_'..j)
if j ~= #ev.parameters then
output:write(', ')
end
output:write(', arg_'..j)
end
output:write(');\n')
@ -104,12 +100,9 @@ for i = 1, #events do
ev.since = tonumber(ev.since)
if not ev.remote_only then
proto_output:write(' void (*'..ev.name..')')
write_signature(proto_output, ev, 'UI *ui')
proto_output:write(';\n')
if not ev.remote_impl and not ev.noexport then
remote_output:write('static void remote_ui_'..ev.name)
remote_output:write('void remote_ui_'..ev.name)
write_signature(remote_output, ev, 'UI *ui')
remote_output:write('\n{\n')
remote_output:write(' UIData *data = ui->data;\n')
@ -130,6 +123,9 @@ for i = 1, #events do
call_output:write(' UI_LOG('..ev.name..');\n')
call_output:write(' ui_call_event("'..ev.name..'", args);\n')
elseif ev.compositor_impl then
call_output:write(' ui_comp_'..ev.name)
write_signature(call_output, ev, '', true)
call_output:write(";\n")
call_output:write(' UI_CALL')
write_signature(call_output, ev, '!ui->composed, '..ev.name..', ui', true)
call_output:write(";\n")
@ -151,14 +147,14 @@ for i = 1, #events do
call_output:write("}\n\n")
end
if (not ev.remote_only) and (not ev.noexport) and (not ev.client_impl) then
if (not ev.remote_only) and (not ev.noexport) and (not ev.client_impl) and (not ev.client_ignore) then
call_ui_event_method(client_output, ev)
end
end
local client_events = {}
for _,ev in ipairs(events) do
if (not ev.noexport) and ((not ev.remote_only) or ev.client_impl) then
if (not ev.noexport) and ((not ev.remote_only) or ev.client_impl) and (not ev.client_ignore) then
client_events[ev.name] = ev
end
end
@ -176,7 +172,6 @@ end
client_output:write('\n};\n\n')
client_output:write(hashfun)
proto_output:close()
call_output:close()
remote_output:close()
client_output:close()

View File

@ -12,6 +12,7 @@
#include "lauxlib.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
#include "nvim/api/ui.h"
#include "nvim/decoration_provider.h"
#include "nvim/drawscreen.h"
#include "nvim/gettext.h"
@ -128,19 +129,15 @@ static int get_attr_entry(HlEntry entry)
/// When a UI connects, we need to send it the table of highlights used so far.
void ui_send_all_hls(UI *ui)
{
if (ui->hl_attr_define) {
for (size_t i = 1; i < kv_size(attr_entries); i++) {
Array inspect = hl_inspect((int)i);
ui->hl_attr_define(ui, (Integer)i, kv_A(attr_entries, i).attr,
kv_A(attr_entries, i).attr, inspect);
api_free_array(inspect);
}
for (size_t i = 1; i < kv_size(attr_entries); i++) {
Array inspect = hl_inspect((int)i);
remote_ui_hl_attr_define(ui, (Integer)i, kv_A(attr_entries, i).attr,
kv_A(attr_entries, i).attr, inspect);
api_free_array(inspect);
}
if (ui->hl_group_set) {
for (size_t hlf = 0; hlf < HLF_COUNT; hlf++) {
ui->hl_group_set(ui, cstr_as_string((char *)hlf_names[hlf]),
highlight_attr[hlf]);
}
for (size_t hlf = 0; hlf < HLF_COUNT; hlf++) {
remote_ui_hl_group_set(ui, cstr_as_string((char *)hlf_names[hlf]),
highlight_attr[hlf]);
}
}

View File

@ -653,7 +653,7 @@ ok:
}
LuaRef ui_event_cb = nlua_ref_global(lstate, 3);
ui_comp_add_cb(ns_id, ui_event_cb, ext_widgets);
ui_add_cb(ns_id, ui_event_cb, ext_widgets);
return 0;
}
@ -667,7 +667,7 @@ static int nlua_ui_detach(lua_State *lstate)
return luaL_error(lstate, "invalid ns_id");
}
ui_comp_remove_cb(ns_id);
ui_remove_cb(ns_id);
return 0;
}

View File

@ -627,11 +627,13 @@ void os_exit(int r)
{
exiting = true;
if (!ui_client_channel_id) {
if (ui_client_channel_id) {
ui_client_stop();
} else {
ui_flush();
ui_call_stop();
ml_close_all(true); // remove all memfiles
}
ui_call_stop();
ml_close_all(true); // remove all memfiles
if (!event_teardown() && r == 0) {
r = 1; // Exit with error if main_loop did not teardown gracefully.

View File

@ -834,7 +834,6 @@ void free_all_mem(void)
decor_free_all_mem();
ui_free_all_mem();
ui_comp_free_all_mem();
nlua_free_all_mem();
// should be last, in case earlier free functions deallocates arenas

File diff suppressed because it is too large Load Diff

View File

@ -11,6 +11,7 @@
#include "auto/config.h"
#include "klib/kvec.h"
#include "nvim/api/private/helpers.h"
#include "nvim/api/ui.h"
#include "nvim/ascii.h"
#include "nvim/autocmd.h"
#include "nvim/buffer_defs.h"
@ -25,6 +26,7 @@
#include "nvim/highlight.h"
#include "nvim/highlight_defs.h"
#include "nvim/log.h"
#include "nvim/lua/executor.h"
#include "nvim/main.h"
#include "nvim/memory.h"
#include "nvim/message.h"
@ -55,6 +57,9 @@ static bool pending_mode_info_update = false;
static bool pending_mode_update = false;
static handle_T cursor_grid_handle = DEFAULT_GRID_HANDLE;
static PMap(uint32_t) ui_event_cbs = MAP_INIT;
bool ui_cb_ext[kUIExtCount]; ///< Internalized UI capabilities.
static bool has_mouse = false;
static int pending_has_mouse = -1;
@ -96,8 +101,8 @@ static char uilog_last_event[1024] = { 0 };
bool any_call = false; \
for (size_t i = 0; i < ui_count; i++) { \
UI *ui = uis[i]; \
if (ui->funname && (cond)) { \
ui->funname(__VA_ARGS__); \
if ((cond)) { \
remote_ui_##funname(__VA_ARGS__); \
any_call = true; \
} \
} \
@ -122,36 +127,25 @@ void ui_init(void)
kv_ensure_space(call_buf, 16);
}
static UI *builtin_ui = NULL;
#ifdef EXITFREE
void ui_free_all_mem(void)
{
kv_destroy(call_buf);
if (builtin_ui) {
tui_free_all_mem(builtin_ui);
builtin_ui = NULL;
}
UIEventCallback *event_cb;
map_foreach_value(&ui_event_cbs, event_cb, {
free_ui_event_callback(event_cb);
})
pmap_destroy(uint32_t)(&ui_event_cbs);
}
#endif
void ui_builtin_start(void)
{
builtin_ui = tui_start();
}
UI *ui_get_by_index(int idx)
{
assert(idx < 16);
return uis[idx];
}
bool ui_rgb_attached(void)
{
if (!headless_mode && p_tgc) {
return true;
}
for (size_t i = 1; i < ui_count; i++) {
for (size_t i = 0; i < ui_count; i++) {
if (uis[i]->rgb) {
return true;
}
@ -162,7 +156,7 @@ bool ui_rgb_attached(void)
/// Returns true if any UI requested `override=true`.
bool ui_override(void)
{
for (size_t i = 1; i < ui_count; i++) {
for (size_t i = 0; i < ui_count; i++) {
if (uis[i]->override) {
return true;
}
@ -172,11 +166,15 @@ bool ui_override(void)
bool ui_active(void)
{
return ui_count > 1;
return ui_count > 0;
}
void ui_refresh(void)
{
if (ui_client_channel_id) {
abort();
}
if (!ui_active()) {
return;
}
@ -192,16 +190,13 @@ void ui_refresh(void)
ext_widgets[i] = true;
}
UI *compositor = uis[0];
bool inclusive = ui_override();
for (size_t i = 1; i < ui_count; i++) {
for (size_t i = 0; i < ui_count; i++) {
UI *ui = uis[i];
width = MIN(ui->width, width);
height = MIN(ui->height, height);
for (UIExtension j = 0; (int)j < kUIExtCount; j++) {
bool in_compositor = (ui->composed || j < kUIGlobalCount) && compositor->ui_ext[j];
ext_widgets[j] &= (ui->ui_ext[j] || in_compositor || inclusive);
ext_widgets[j] &= (ui->ui_ext[j] || inclusive);
}
}
@ -209,6 +204,9 @@ void ui_refresh(void)
pending_cursor_update = true;
for (UIExtension i = 0; (int)i < kUIExtCount; i++) {
if (i < kUIGlobalCount) {
ext_widgets[i] |= ui_cb_ext[i];
}
ui_ext[i] = ext_widgets[i];
if (i < kUIGlobalCount) {
ui_call_option_set(cstr_as_string((char *)ui_ext_names[i]),
@ -218,27 +216,10 @@ void ui_refresh(void)
ui_default_colors_set();
if (!ui_client_channel_id) {
int save_p_lz = p_lz;
p_lz = false; // convince redrawing() to return true ...
screen_resize(width, height);
p_lz = save_p_lz;
} else {
if (ui_client_attached) {
// TODO(bfredl): ui_refresh() should only be used on the server
// we are in the client process. forward the resize
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);
} else {
/// TODO(bfredl): Messy! The screen does not yet exist, but we need to
/// communicate its size from the TUI to the client. Clean this up
/// in The UI Devirtualization Project.
Rows = height;
Columns = width;
}
}
int save_p_lz = p_lz;
p_lz = false; // convince redrawing() to return true ...
screen_resize(width, height);
p_lz = save_p_lz;
if (ext_widgets[kUIMessages]) {
set_option_value("cmdheight", 0L, NULL, 0);
@ -253,7 +234,7 @@ void ui_refresh(void)
int ui_pum_get_height(void)
{
int pum_height = 0;
for (size_t i = 1; i < ui_count; i++) {
for (size_t i = 0; i < ui_count; i++) {
int ui_pum_height = uis[i]->pum_nlines;
if (ui_pum_height) {
pum_height =
@ -265,7 +246,7 @@ int ui_pum_get_height(void)
bool ui_pum_get_pos(double *pwidth, double *pheight, double *prow, double *pcol)
{
for (size_t i = 1; i < ui_count; i++) {
for (size_t i = 0; i < ui_count; i++) {
if (!uis[i]->pum_pos) {
continue;
}
@ -372,10 +353,7 @@ void ui_attach_impl(UI *ui, uint64_t chanid)
}
ui_refresh();
bool is_compositor = (ui == uis[0]);
if (!is_compositor) {
do_autocmd_uienter(chanid, true);
}
do_autocmd_uienter(chanid, true);
}
void ui_detach_impl(UI *ui, uint64_t chanid)
@ -420,9 +398,9 @@ void ui_set_ext_option(UI *ui, UIExtension ext, bool active)
ui_refresh();
return;
}
if (ui->option_set && (ui_ext_names[ext][0] != '_' || active)) {
ui->option_set(ui, cstr_as_string((char *)ui_ext_names[ext]),
BOOLEAN_OBJ(active));
if (ui_ext_names[ext][0] != '_' || active) {
remote_ui_option_set(ui, cstr_as_string((char *)ui_ext_names[ext]),
BOOLEAN_OBJ(active));
}
if (ext == kUITermColors) {
ui_default_colors_set();
@ -618,7 +596,7 @@ bool ui_has(UIExtension ext)
Array ui_array(void)
{
Array all_uis = ARRAY_DICT_INIT;
for (size_t i = 1; i < ui_count; i++) {
for (size_t i = 0; i < ui_count; i++) {
UI *ui = uis[i];
Dictionary info = ARRAY_DICT_INIT;
PUT(info, "width", INTEGER_OBJ(ui->width));
@ -630,7 +608,7 @@ Array ui_array(void)
PUT(info, ui_ext_names[j], BOOLEAN_OBJ(ui->ui_ext[j]));
}
}
ui->inspect(ui, &info);
remote_ui_inspect(ui, &info);
ADD(all_uis, DICTIONARY_OBJ(info));
}
return all_uis;
@ -663,3 +641,75 @@ void ui_grid_resize(handle_T grid_handle, int width, int height, Error *error)
win_set_inner_size(wp, true);
}
}
void ui_call_event(char *name, Array args)
{
UIEventCallback *event_cb;
bool handled = false;
map_foreach_value(&ui_event_cbs, event_cb, {
Error err = ERROR_INIT;
Object res = nlua_call_ref(event_cb->cb, name, args, false, &err);
if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
handled = true;
}
if (ERROR_SET(&err)) {
ELOG("Error while executing ui_comp_event callback: %s", err.msg);
}
api_clear_error(&err);
})
if (!handled) {
UI_CALL(true, event, ui, name, args);
}
}
void ui_cb_update_ext(void)
{
memset(ui_cb_ext, 0, ARRAY_SIZE(ui_cb_ext));
for (size_t i = 0; i < kUIGlobalCount; i++) {
UIEventCallback *event_cb;
map_foreach_value(&ui_event_cbs, event_cb, {
if (event_cb->ext_widgets[i]) {
ui_cb_ext[i] = true;
break;
}
})
}
}
void free_ui_event_callback(UIEventCallback *event_cb)
{
api_free_luaref(event_cb->cb);
xfree(event_cb);
}
void ui_add_cb(uint32_t ns_id, LuaRef cb, bool *ext_widgets)
{
UIEventCallback *event_cb = xcalloc(1, sizeof(UIEventCallback));
event_cb->cb = cb;
memcpy(event_cb->ext_widgets, ext_widgets, ARRAY_SIZE(event_cb->ext_widgets));
if (event_cb->ext_widgets[kUIMessages]) {
event_cb->ext_widgets[kUICmdline] = true;
}
UIEventCallback **item = (UIEventCallback **)pmap_ref(uint32_t)(&ui_event_cbs, ns_id, true);
if (*item) {
free_ui_event_callback(*item);
}
*item = event_cb;
ui_cb_update_ext();
ui_refresh();
}
void ui_remove_cb(uint32_t ns_id)
{
if (pmap_has(uint32_t)(&ui_event_cbs, ns_id)) {
free_ui_event_callback(pmap_get(uint32_t)(&ui_event_cbs, ns_id));
pmap_del(uint32_t)(&ui_event_cbs, ns_id);
}
ui_cb_update_ext();
ui_refresh();
}

View File

@ -52,6 +52,41 @@ enum {
typedef int LineFlags;
typedef struct {
uint64_t channel_id;
#define UI_BUF_SIZE 4096 ///< total buffer size for pending msgpack data.
/// guaranteed size available for each new event (so packing of simple events
/// and the header of grid_line will never fail)
#define EVENT_BUF_SIZE 256
char buf[UI_BUF_SIZE]; ///< buffer of packed but not yet sent msgpack data
char *buf_wptr; ///< write head of buffer
const char *cur_event; ///< name of current event (might get multiple arglists)
Array call_buf; ///< buffer for constructing a single arg list (max 16 elements!)
// state for write_cb, while packing a single arglist to msgpack. This
// might fail due to buffer overflow.
size_t pack_totlen;
bool buf_overflow;
char *temp_buf;
// We start packing the two outermost msgpack arrays before knowing the total
// number of elements. Thus track the location where array size will need
// to be written in the msgpack buffer, once the specific array is finished.
char *nevents_pos;
char *ncalls_pos;
uint32_t nevents; ///< number of distinct events (top-level args to "redraw"
uint32_t ncalls; ///< number of calls made to the current event (plus one for the name!)
bool flushed_events; ///< events where sent to client without "flush" event
int hl_id; // Current highlight for legacy put event.
Integer cursor_row, cursor_col; // Intended visible cursor position.
// Position of legacy cursor, used both for drawing and visible user cursor.
Integer client_row, client_col;
bool wildmenu_active;
} UIData;
struct ui_t {
bool rgb;
bool override; ///< Force highest-requested UI capabilities.
@ -65,13 +100,9 @@ struct ui_t {
double pum_col;
double pum_height;
double pum_width;
void *data;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ui_events.generated.h"
#endif
void (*inspect)(UI *ui, Dictionary *info);
// TODO(bfredl): integrate into struct!
UIData data[1];
};
typedef struct ui_event_callback {

View File

@ -15,15 +15,19 @@
#include "nvim/main.h"
#include "nvim/memory.h"
#include "nvim/msgpack_rpc/channel.h"
#include "nvim/tui/tui.h"
#include "nvim/ui.h"
#include "nvim/ui_client.h"
static TUIData *tui = NULL;
// 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)
{
@ -52,20 +56,20 @@ uint64_t ui_client_start_server(int argc, char **argv)
void ui_client_run(bool remote_ui)
FUNC_ATTR_NORETURN
{
ui_builtin_start();
loop_poll_events(&main_loop, 1);
int width, height;
char *term;
tui = tui_start(&width, &height, &term);
MAXSIZE_TEMP_ARRAY(args, 3);
ADD_C(args, INTEGER_OBJ(Columns));
ADD_C(args, INTEGER_OBJ(Rows));
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 (ui_client_termname) {
PUT_C(opts, "term_name", STRING_OBJ(cstr_as_string(ui_client_termname)));
if (term) {
PUT(opts, "term_name", STRING_OBJ(cstr_to_string(term)));
}
if (ui_client_bg_respose != kNone) {
bool is_dark = (ui_client_bg_respose == kTrue);
@ -80,6 +84,7 @@ void ui_client_run(bool remote_ui)
}
}
ADD_C(args, DICTIONARY_OBJ(opts));
rpc_send_event(ui_client_channel_id, "nvim_ui_attach", args);
ui_client_attached = true;
@ -89,6 +94,22 @@ void ui_client_run(bool remote_ui)
}
}
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);
@ -133,7 +154,7 @@ void ui_client_event_grid_resize(Array args)
Integer grid = args.items[0].data.integer;
Integer width = args.items[1].data.integer;
Integer height = args.items[2].data.integer;
ui_call_grid_resize(grid, width, height);
tui_grid_resize(tui, grid, width, height);
if (grid_line_buf_size < (size_t)width) {
xfree(grid_line_buf_char);
@ -159,6 +180,6 @@ void ui_client_event_raw_line(GridLineEvent *g)
// TODO(hlpr98): Accommodate other LineFlags when included in grid_line
LineFlags lineflags = 0;
ui_call_raw_line(grid, row, startcol, endcol, clearcol, g->cur_attr, lineflags,
(const schar_T *)grid_line_buf_char, grid_line_buf_attr);
tui_raw_line(tui, grid, row, startcol, endcol, clearcol, g->cur_attr, lineflags,
(const schar_T *)grid_line_buf_char, grid_line_buf_attr);
}

View File

@ -34,8 +34,6 @@ EXTERN TriState ui_client_bg_respose INIT(= kNone);
/// by convention, this uses fd=3 (next free number after stdio)
EXTERN bool ui_client_forward_stdin INIT(= false);
EXTERN char *ui_client_termname INIT(= NULL);
#define UI_CLIENT_STDIN_FD 3
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ui_client.h.generated.h"

View File

@ -23,7 +23,6 @@
#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
#include "nvim/log.h"
#include "nvim/lua/executor.h"
#include "nvim/macros.h"
#include "nvim/map.h"
#include "nvim/memory.h"
@ -39,7 +38,6 @@
# include "ui_compositor.c.generated.h"
#endif
static UI *compositor = NULL;
static int composed_uis = 0;
kvec_t(ScreenGrid *) layers = KV_INITIAL_VALUE;
@ -60,52 +58,12 @@ static bool msg_was_scrolled = false;
static int msg_sep_row = -1;
static schar_T msg_sep_char = { ' ', NUL };
static PMap(uint32_t) ui_event_cbs = MAP_INIT;
static int dbghl_normal, dbghl_clear, dbghl_composed, dbghl_recompose;
void ui_comp_init(void)
{
if (compositor != NULL) {
return;
}
compositor = xcalloc(1, sizeof(UI));
compositor->rgb = true;
compositor->grid_resize = ui_comp_grid_resize;
compositor->grid_scroll = ui_comp_grid_scroll;
compositor->grid_cursor_goto = ui_comp_grid_cursor_goto;
compositor->raw_line = ui_comp_raw_line;
compositor->msg_set_pos = ui_comp_msg_set_pos;
compositor->event = ui_comp_event;
// Be unopinionated: will be attached together with a "real" ui anyway
compositor->width = INT_MAX;
compositor->height = INT_MAX;
for (UIExtension i = kUIGlobalCount; (int)i < kUIExtCount; i++) {
compositor->ui_ext[i] = true;
}
// TODO(bfredl): one day. in the future.
compositor->ui_ext[kUIMultigrid] = false;
// TODO(bfredl): this will be more complicated if we implement
// hlstate per UI (i e reduce hl ids for non-hlstate UIs)
compositor->ui_ext[kUIHlState] = false;
kv_push(layers, &default_grid);
curgrid = &default_grid;
ui_attach_impl(compositor, 0);
}
void ui_comp_free_all_mem(void)
{
UIEventCallback *event_cb;
map_foreach_value(&ui_event_cbs, event_cb, {
free_ui_event_callback(event_cb);
})
pmap_destroy(uint32_t)(&ui_event_cbs);
}
void ui_comp_syn_init(void)
@ -258,7 +216,7 @@ bool ui_comp_set_grid(handle_T handle)
return false;
}
static void ui_comp_raise_grid(ScreenGrid *grid, size_t new_index)
void ui_comp_raise_grid(ScreenGrid *grid, size_t new_index)
{
size_t old_index = grid->comp_index;
for (size_t i = old_index; i < new_index; i++) {
@ -278,7 +236,7 @@ static void ui_comp_raise_grid(ScreenGrid *grid, size_t new_index)
}
}
static void ui_comp_grid_cursor_goto(UI *ui, Integer grid_handle, Integer r, Integer c)
void ui_comp_grid_cursor_goto(Integer grid_handle, Integer r, Integer c)
{
if (!ui_comp_should_draw() || !ui_comp_set_grid((int)grid_handle)) {
return;
@ -538,9 +496,9 @@ void ui_comp_compose_grid(ScreenGrid *grid)
}
}
static void ui_comp_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Integer endcol,
Integer clearcol, Integer clearattr, LineFlags flags,
const schar_T *chunk, const sattr_T *attrs)
void ui_comp_raw_line(Integer grid, Integer row, Integer startcol, Integer endcol, Integer clearcol,
Integer clearattr, LineFlags flags, const schar_T *chunk,
const sattr_T *attrs)
{
if (!ui_comp_should_draw() || !ui_comp_set_grid((int)grid)) {
return;
@ -605,8 +563,7 @@ bool ui_comp_set_screen_valid(bool valid)
return old_val;
}
static void ui_comp_msg_set_pos(UI *ui, Integer grid, Integer row, Boolean scrolled,
String sep_char)
void ui_comp_msg_set_pos(Integer grid, Integer row, Boolean scrolled, String sep_char)
{
msg_grid.comp_row = (int)row;
if (scrolled && row > 0) {
@ -650,8 +607,8 @@ static bool curgrid_covered_above(int row)
return kv_size(layers) - (above_msg?1:0) > curgrid->comp_index + 1;
}
static void ui_comp_grid_scroll(UI *ui, Integer grid, Integer top, Integer bot, Integer left,
Integer right, Integer rows, Integer cols)
void ui_comp_grid_scroll(Integer grid, Integer top, Integer bot, Integer left, Integer right,
Integer rows, Integer cols)
{
if (!ui_comp_should_draw() || !ui_comp_set_grid((int)grid)) {
return;
@ -685,7 +642,7 @@ static void ui_comp_grid_scroll(UI *ui, Integer grid, Integer top, Integer bot,
}
}
static void ui_comp_grid_resize(UI *ui, Integer grid, Integer width, Integer height)
void ui_comp_grid_resize(Integer grid, Integer width, Integer height)
{
if (grid == 1) {
ui_composed_call_grid_resize(1, width, height);
@ -703,75 +660,3 @@ static void ui_comp_grid_resize(UI *ui, Integer grid, Integer width, Integer hei
}
}
}
static void ui_comp_event(UI *ui, char *name, Array args)
{
UIEventCallback *event_cb;
bool handled = false;
map_foreach_value(&ui_event_cbs, event_cb, {
Error err = ERROR_INIT;
Object res = nlua_call_ref(event_cb->cb, name, args, false, &err);
if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
handled = true;
}
if (ERROR_SET(&err)) {
ELOG("Error while executing ui_comp_event callback: %s", err.msg);
}
api_clear_error(&err);
})
if (!handled) {
ui_composed_call_event(name, args);
}
}
static void ui_comp_update_ext(void)
{
memset(compositor->ui_ext, 0, ARRAY_SIZE(compositor->ui_ext));
for (size_t i = 0; i < kUIGlobalCount; i++) {
UIEventCallback *event_cb;
map_foreach_value(&ui_event_cbs, event_cb, {
if (event_cb->ext_widgets[i]) {
compositor->ui_ext[i] = true;
break;
}
})
}
}
void free_ui_event_callback(UIEventCallback *event_cb)
{
api_free_luaref(event_cb->cb);
xfree(event_cb);
}
void ui_comp_add_cb(uint32_t ns_id, LuaRef cb, bool *ext_widgets)
{
UIEventCallback *event_cb = xcalloc(1, sizeof(UIEventCallback));
event_cb->cb = cb;
memcpy(event_cb->ext_widgets, ext_widgets, ARRAY_SIZE(event_cb->ext_widgets));
if (event_cb->ext_widgets[kUIMessages]) {
event_cb->ext_widgets[kUICmdline] = true;
}
UIEventCallback **item = (UIEventCallback **)pmap_ref(uint32_t)(&ui_event_cbs, ns_id, true);
if (*item) {
free_ui_event_callback(*item);
}
*item = event_cb;
ui_comp_update_ext();
ui_refresh();
}
void ui_comp_remove_cb(uint32_t ns_id)
{
if (pmap_has(uint32_t)(&ui_event_cbs, ns_id)) {
free_ui_event_callback(pmap_get(uint32_t)(&ui_event_cbs, ns_id));
pmap_del(uint32_t)(&ui_event_cbs, ns_id);
}
ui_comp_update_ext();
ui_refresh();
}