aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/api/ui.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/api/ui.c')
-rw-r--r--src/nvim/api/ui.c617
1 files changed, 446 insertions, 171 deletions
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index 1703d49296..7ba5251c60 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -1,3 +1,6 @@
+// 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 <assert.h>
#include <stddef.h>
#include <stdint.h>
@@ -8,120 +11,264 @@
#include "nvim/memory.h"
#include "nvim/map.h"
#include "nvim/msgpack_rpc/channel.h"
+#include "nvim/api/ui.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/popupmnu.h"
+#include "nvim/cursor_shape.h"
+#include "nvim/highlight.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/ui.c.generated.h"
+# include "ui_events_remote.generated.h"
#endif
typedef struct {
uint64_t channel_id;
Array buffer;
+
+ int hl_id; // current higlight for legacy put event
+ Integer cursor_row, cursor_col; // Intended visibule cursor position
+
+ // Position of legacy cursor, used both for drawing and visible user cursor.
+ Integer client_row, client_col;
} UIData;
static PMap(uint64_t) *connected_uis = NULL;
void remote_ui_init(void)
- FUNC_API_NOEXPORT
+ FUNC_API_NOEXPORT
{
connected_uis = pmap_new(uint64_t)();
}
void remote_ui_disconnect(uint64_t channel_id)
- FUNC_API_NOEXPORT
+ FUNC_API_NOEXPORT
{
UI *ui = pmap_get(uint64_t)(connected_uis, channel_id);
if (!ui) {
return;
}
UIData *data = ui->data;
- // destroy pending screen updates
- api_free_array(data->buffer);
+ api_free_array(data->buffer); // Destroy pending screen updates.
pmap_del(uint64_t)(connected_uis, channel_id);
xfree(ui->data);
+ ui->data = NULL; // Flag UI as "stopped".
ui_detach_impl(ui);
xfree(ui);
}
-void ui_attach(uint64_t channel_id, Integer width, Integer height,
- Boolean enable_rgb, Error *err)
+/// Wait until ui has connected on stdio channel.
+void remote_ui_wait_for_attach(void)
+ FUNC_API_NOEXPORT
+{
+ Channel *channel = find_channel(CHAN_STDIO);
+ if (!channel) {
+ // this function should only be called in --embed mode, stdio channel
+ // can be assumed.
+ abort();
+ }
+
+ LOOP_PROCESS_EVENTS_UNTIL(&main_loop, channel->events, -1,
+ pmap_has(uint64_t)(connected_uis, CHAN_STDIO));
+}
+
+void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height,
+ Dictionary options, Error *err)
+ FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
{
if (pmap_has(uint64_t)(connected_uis, channel_id)) {
- api_set_error(err, Exception, _("UI already attached for channel"));
+ api_set_error(err, kErrorTypeException,
+ "UI already attached to channel: %" PRId64, channel_id);
return;
}
if (width <= 0 || height <= 0) {
- api_set_error(err, Validation,
- _("Expected width > 0 and height > 0"));
+ api_set_error(err, kErrorTypeValidation,
+ "Expected width > 0 and height > 0");
return;
}
- UIData *data = xmalloc(sizeof(UIData));
- data->channel_id = channel_id;
- data->buffer = (Array)ARRAY_DICT_INIT;
UI *ui = xcalloc(1, sizeof(UI));
ui->width = (int)width;
ui->height = (int)height;
- ui->rgb = enable_rgb;
- ui->data = data;
- ui->resize = remote_ui_resize;
- ui->clear = remote_ui_clear;
- ui->eol_clear = remote_ui_eol_clear;
- ui->cursor_goto = remote_ui_cursor_goto;
+ ui->rgb = true;
+ 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->set_scroll_region = remote_ui_set_scroll_region;
- ui->scroll = remote_ui_scroll;
- ui->highlight_set = remote_ui_highlight_set;
- ui->put = remote_ui_put;
+ ui->grid_scroll = remote_ui_grid_scroll;
+ ui->hl_attr_define = remote_ui_hl_attr_define;
+ ui->raw_line = remote_ui_raw_line;
ui->bell = remote_ui_bell;
ui->visual_bell = remote_ui_visual_bell;
- ui->update_fg = remote_ui_update_fg;
- ui->update_bg = remote_ui_update_bg;
- ui->update_sp = remote_ui_update_sp;
+ 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->event = remote_ui_event;
+ ui->inspect = remote_ui_inspect;
+
+ memset(ui->ui_ext, 0, sizeof(ui->ui_ext));
+
+ for (size_t i = 0; i < options.size; i++) {
+ ui_set_option(ui, true, options.items[i].key, options.items[i].value, err);
+ if (ERROR_SET(err)) {
+ xfree(ui);
+ return;
+ }
+ }
+
+ if (ui->ui_ext[kUIHlState] || ui->ui_ext[kUIMultigrid]) {
+ ui->ui_ext[kUILinegrid] = true;
+ }
+
+ UIData *data = xmalloc(sizeof(UIData));
+ data->channel_id = channel_id;
+ data->buffer = (Array)ARRAY_DICT_INIT;
+ data->hl_id = 0;
+ data->client_col = -1;
+ ui->data = data;
+
pmap_put(uint64_t)(connected_uis, channel_id, ui);
ui_attach_impl(ui);
- return;
}
-void ui_detach(uint64_t channel_id, Error *err)
+/// @deprecated
+void ui_attach(uint64_t channel_id, Integer width, Integer height,
+ Boolean enable_rgb, Error *err)
+{
+ Dictionary opts = ARRAY_DICT_INIT;
+ PUT(opts, "rgb", BOOLEAN_OBJ(enable_rgb));
+ nvim_ui_attach(channel_id, width, height, opts, err);
+ api_free_dictionary(opts);
+}
+
+void nvim_ui_detach(uint64_t channel_id, Error *err)
+ FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
{
if (!pmap_has(uint64_t)(connected_uis, channel_id)) {
- api_set_error(err, Exception, _("UI is not attached for channel"));
+ api_set_error(err, kErrorTypeException,
+ "UI not attached to channel: %" PRId64, channel_id);
+ return;
}
remote_ui_disconnect(channel_id);
}
-Object ui_try_resize(uint64_t channel_id, Integer width,
- Integer height, Error *err)
+
+void nvim_ui_try_resize(uint64_t channel_id, Integer width,
+ Integer height, Error *err)
+ FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
{
if (!pmap_has(uint64_t)(connected_uis, channel_id)) {
- api_set_error(err, Exception, _("UI is not attached for channel"));
+ api_set_error(err, kErrorTypeException,
+ "UI not attached to channel: %" PRId64, channel_id);
+ return;
}
if (width <= 0 || height <= 0) {
- api_set_error(err, Validation,
- _("Expected width > 0 and height > 0"));
- return NIL;
+ api_set_error(err, kErrorTypeValidation,
+ "Expected width > 0 and height > 0");
+ return;
}
UI *ui = pmap_get(uint64_t)(connected_uis, channel_id);
ui->width = (int)width;
ui->height = (int)height;
ui_refresh();
- return NIL;
}
-static void push_call(UI *ui, char *name, Array args)
+void nvim_ui_set_option(uint64_t channel_id, String name,
+ Object value, Error *error)
+ FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
+{
+ if (!pmap_has(uint64_t)(connected_uis, channel_id)) {
+ api_set_error(error, kErrorTypeException,
+ "UI not attached to channel: %" PRId64, channel_id);
+ return;
+ }
+ UI *ui = pmap_get(uint64_t)(connected_uis, channel_id);
+
+ ui_set_option(ui, false, name, value, error);
+}
+
+static void ui_set_option(UI *ui, bool init, String name, Object value,
+ Error *error)
+{
+ if (strequal(name.data, "rgb")) {
+ if (value.type != kObjectTypeBoolean) {
+ api_set_error(error, kErrorTypeValidation, "rgb must be a Boolean");
+ return;
+ }
+ ui->rgb = value.data.boolean;
+ // A little drastic, but only legacy uis need to use this option
+ if (!init) {
+ ui_refresh();
+ }
+ return;
+ }
+
+ // LEGACY: Deprecated option, use `ext_cmdline` instead.
+ bool is_popupmenu = strequal(name.data, "popupmenu_external");
+
+ for (UIExtension i = 0; i < kUIExtCount; i++) {
+ if (strequal(name.data, ui_ext_names[i])
+ || (i == kUIPopupmenu && is_popupmenu)) {
+ if (value.type != kObjectTypeBoolean) {
+ api_set_error(error, kErrorTypeValidation, "%s must be a Boolean",
+ name.data);
+ return;
+ }
+ bool boolval = value.data.boolean;
+ if (!init && i == kUILinegrid && boolval != ui->ui_ext[i]) {
+ // There shouldn't be a reason for an UI to do this ever
+ // so explicitly don't support this.
+ api_set_error(error, kErrorTypeValidation,
+ "ext_linegrid option cannot be changed");
+ }
+ ui->ui_ext[i] = boolval;
+ if (!init) {
+ ui_set_ext_option(ui, i, boolval);
+ }
+ return;
+ }
+ }
+
+ api_set_error(error, kErrorTypeValidation, "No such UI option: %s",
+ name.data);
+}
+
+/// Tell nvim to resize a grid. Nvim sends grid_resize event with the
+/// requested grid size is within size limits and with maximum allowed size
+/// otherwise.
+///
+/// On invalid grid handle, fails with error.
+///
+/// @param grid The handle of the grid to be changed.
+/// @param width The new requested width.
+/// @param height The new requested height.
+void nvim_ui_try_resize_grid(uint64_t channel_id, Integer grid, Integer width,
+ Integer height, Error *error)
+ FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY
+{
+ if (!pmap_has(uint64_t)(connected_uis, channel_id)) {
+ api_set_error(error, kErrorTypeException,
+ "UI not attached to channel: %" PRId64, channel_id);
+ return;
+ }
+
+ ui_grid_resize((handle_T)grid, (int)width, (int)height, error);
+}
+
+/// Pushes data into UI.UIData, to be consumed later by remote_ui_flush().
+static void push_call(UI *ui, const char *name, Array args)
{
Array call = ARRAY_DICT_INIT;
UIData *data = ui->data;
@@ -143,201 +290,329 @@ static void push_call(UI *ui, char *name, Array args)
kv_A(data->buffer, kv_size(data->buffer) - 1).data.array = call;
}
-static void remote_ui_resize(UI *ui, int width, int height)
+static void remote_ui_grid_clear(UI *ui, Integer grid)
{
Array args = ARRAY_DICT_INIT;
- ADD(args, INTEGER_OBJ(width));
- ADD(args, INTEGER_OBJ(height));
- push_call(ui, "resize", args);
-}
-
-static void remote_ui_clear(UI *ui)
-{
- Array args = ARRAY_DICT_INIT;
- push_call(ui, "clear", args);
-}
-
-static void remote_ui_eol_clear(UI *ui)
-{
- Array args = ARRAY_DICT_INIT;
- push_call(ui, "eol_clear", args);
-}
-
-static void remote_ui_cursor_goto(UI *ui, int row, int col)
-{
- Array args = ARRAY_DICT_INIT;
- ADD(args, INTEGER_OBJ(row));
- ADD(args, INTEGER_OBJ(col));
- push_call(ui, "cursor_goto", args);
+ if (ui->ui_ext[kUILinegrid]) {
+ ADD(args, INTEGER_OBJ(grid));
+ }
+ const char *name = ui->ui_ext[kUILinegrid] ? "grid_clear" : "clear";
+ push_call(ui, name, args);
}
-static void remote_ui_update_menu(UI *ui)
+static void remote_ui_grid_resize(UI *ui, Integer grid,
+ Integer width, Integer height)
{
Array args = ARRAY_DICT_INIT;
- push_call(ui, "update_menu", args);
+ if (ui->ui_ext[kUILinegrid]) {
+ ADD(args, INTEGER_OBJ(grid));
+ }
+ ADD(args, INTEGER_OBJ(width));
+ ADD(args, INTEGER_OBJ(height));
+ const char *name = ui->ui_ext[kUILinegrid] ? "grid_resize" : "resize";
+ push_call(ui, name, args);
}
-static void remote_ui_busy_start(UI *ui)
+static void remote_ui_grid_scroll(UI *ui, Integer grid, Integer top,
+ Integer bot, Integer left, Integer right,
+ Integer rows, Integer cols)
{
- Array args = ARRAY_DICT_INIT;
- push_call(ui, "busy_start", args);
+ if (ui->ui_ext[kUILinegrid]) {
+ Array args = ARRAY_DICT_INIT;
+ ADD(args, INTEGER_OBJ(grid));
+ ADD(args, INTEGER_OBJ(top));
+ ADD(args, INTEGER_OBJ(bot));
+ ADD(args, INTEGER_OBJ(left));
+ ADD(args, INTEGER_OBJ(right));
+ ADD(args, INTEGER_OBJ(rows));
+ ADD(args, INTEGER_OBJ(cols));
+ push_call(ui, "grid_scroll", args);
+ } else {
+ Array args = ARRAY_DICT_INIT;
+ ADD(args, INTEGER_OBJ(top));
+ ADD(args, INTEGER_OBJ(bot-1));
+ ADD(args, INTEGER_OBJ(left));
+ ADD(args, INTEGER_OBJ(right-1));
+ push_call(ui, "set_scroll_region", args);
+
+ args = (Array)ARRAY_DICT_INIT;
+ ADD(args, INTEGER_OBJ(rows));
+ push_call(ui, "scroll", args);
+
+ // some clients have "clear" being affected by scroll region,
+ // so reset it.
+ args = (Array)ARRAY_DICT_INIT;
+ ADD(args, INTEGER_OBJ(0));
+ ADD(args, INTEGER_OBJ(ui->height-1));
+ ADD(args, INTEGER_OBJ(0));
+ ADD(args, INTEGER_OBJ(ui->width-1));
+ push_call(ui, "set_scroll_region", args);
+ }
}
-static void remote_ui_busy_stop(UI *ui)
+static void remote_ui_default_colors_set(UI *ui, Integer rgb_fg,
+ Integer rgb_bg, Integer rgb_sp,
+ Integer cterm_fg, Integer cterm_bg)
{
Array args = ARRAY_DICT_INIT;
- push_call(ui, "busy_stop", args);
+ ADD(args, INTEGER_OBJ(rgb_fg));
+ ADD(args, INTEGER_OBJ(rgb_bg));
+ ADD(args, INTEGER_OBJ(rgb_sp));
+ ADD(args, INTEGER_OBJ(cterm_fg));
+ ADD(args, INTEGER_OBJ(cterm_bg));
+ push_call(ui, "default_colors_set", args);
+
+ // Deprecated
+ if (!ui->ui_ext[kUILinegrid]) {
+ args = (Array)ARRAY_DICT_INIT;
+ ADD(args, INTEGER_OBJ(ui->rgb ? rgb_fg : cterm_fg - 1));
+ push_call(ui, "update_fg", args);
+
+ args = (Array)ARRAY_DICT_INIT;
+ ADD(args, INTEGER_OBJ(ui->rgb ? rgb_bg : cterm_bg - 1));
+ push_call(ui, "update_bg", args);
+
+ args = (Array)ARRAY_DICT_INIT;
+ ADD(args, INTEGER_OBJ(ui->rgb ? rgb_sp : -1));
+ push_call(ui, "update_sp", args);
+ }
}
-static void remote_ui_mouse_on(UI *ui)
+static void remote_ui_hl_attr_define(UI *ui, Integer id, HlAttrs rgb_attrs,
+ HlAttrs cterm_attrs, Array info)
{
+ if (!ui->ui_ext[kUILinegrid]) {
+ return;
+ }
Array args = ARRAY_DICT_INIT;
- push_call(ui, "mouse_on", args);
-}
-static void remote_ui_mouse_off(UI *ui)
-{
- Array args = ARRAY_DICT_INIT;
- push_call(ui, "mouse_off", args);
-}
+ ADD(args, INTEGER_OBJ(id));
+ ADD(args, DICTIONARY_OBJ(hlattrs2dict(rgb_attrs, true)));
+ ADD(args, DICTIONARY_OBJ(hlattrs2dict(cterm_attrs, false)));
-static void remote_ui_mode_change(UI *ui, int mode)
-{
- Array args = ARRAY_DICT_INIT;
- if (mode == INSERT) {
- ADD(args, STRING_OBJ(cstr_to_string("insert")));
- } else if (mode == REPLACE) {
- ADD(args, STRING_OBJ(cstr_to_string("replace")));
+ if (ui->ui_ext[kUIHlState]) {
+ ADD(args, ARRAY_OBJ(copy_array(info)));
} else {
- assert(mode == NORMAL);
- ADD(args, STRING_OBJ(cstr_to_string("normal")));
+ ADD(args, ARRAY_OBJ((Array)ARRAY_DICT_INIT));
}
- push_call(ui, "mode_change", args);
-}
-static void remote_ui_set_scroll_region(UI *ui, int top, int bot, int left,
- int right)
-{
- Array args = ARRAY_DICT_INIT;
- ADD(args, INTEGER_OBJ(top));
- ADD(args, INTEGER_OBJ(bot));
- ADD(args, INTEGER_OBJ(left));
- ADD(args, INTEGER_OBJ(right));
- push_call(ui, "set_scroll_region", args);
+ push_call(ui, "hl_attr_define", args);
}
-static void remote_ui_scroll(UI *ui, int count)
+static void remote_ui_highlight_set(UI *ui, int id)
{
Array args = ARRAY_DICT_INIT;
- ADD(args, INTEGER_OBJ(count));
- push_call(ui, "scroll", args);
-}
-
-static void remote_ui_highlight_set(UI *ui, HlAttrs attrs)
-{
- Array args = ARRAY_DICT_INIT;
- Dictionary hl = ARRAY_DICT_INIT;
-
- if (attrs.bold) {
- PUT(hl, "bold", BOOLEAN_OBJ(true));
- }
-
- if (attrs.underline) {
- PUT(hl, "underline", BOOLEAN_OBJ(true));
- }
-
- if (attrs.undercurl) {
- PUT(hl, "undercurl", BOOLEAN_OBJ(true));
- }
-
- if (attrs.italic) {
- PUT(hl, "italic", BOOLEAN_OBJ(true));
- }
-
- if (attrs.reverse) {
- PUT(hl, "reverse", BOOLEAN_OBJ(true));
- }
-
- if (attrs.foreground != -1) {
- PUT(hl, "foreground", INTEGER_OBJ(attrs.foreground));
- }
+ UIData *data = ui->data;
- if (attrs.background != -1) {
- PUT(hl, "background", INTEGER_OBJ(attrs.background));
- }
- if (attrs.special != -1) {
- PUT(hl, "special", INTEGER_OBJ(attrs.special));
+ if (data->hl_id == id) {
+ return;
}
+ data->hl_id = id;
+ Dictionary hl = hlattrs2dict(syn_attr2entry(id), ui->rgb);
ADD(args, DICTIONARY_OBJ(hl));
push_call(ui, "highlight_set", args);
}
-static void remote_ui_put(UI *ui, uint8_t *data, size_t size)
+/// "true" cursor used only for input focus
+static void remote_ui_grid_cursor_goto(UI *ui, Integer grid, Integer row,
+ Integer col)
{
- Array args = ARRAY_DICT_INIT;
- String str = { .data = xmemdupz(data, size), .size = size };
- ADD(args, STRING_OBJ(str));
- push_call(ui, "put", args);
+ if (ui->ui_ext[kUILinegrid]) {
+ Array args = ARRAY_DICT_INIT;
+ ADD(args, INTEGER_OBJ(grid));
+ ADD(args, INTEGER_OBJ(row));
+ ADD(args, INTEGER_OBJ(col));
+ push_call(ui, "grid_cursor_goto", args);
+ } else {
+ UIData *data = ui->data;
+ data->cursor_row = row;
+ data->cursor_col = col;
+ remote_ui_cursor_goto(ui, row, col);
+ }
}
-static void remote_ui_bell(UI *ui)
+/// emulated cursor used both for drawing and for input focus
+static void remote_ui_cursor_goto(UI *ui, Integer row, Integer col)
{
+ UIData *data = ui->data;
+ if (data->client_row == row && data->client_col == col) {
+ return;
+ }
+ data->client_row = row;
+ data->client_col = col;
Array args = ARRAY_DICT_INIT;
- push_call(ui, "bell", args);
+ ADD(args, INTEGER_OBJ(row));
+ ADD(args, INTEGER_OBJ(col));
+ push_call(ui, "cursor_goto", args);
}
-static void remote_ui_visual_bell(UI *ui)
+static void remote_ui_put(UI *ui, const char *cell)
{
+ UIData *data = ui->data;
+ data->client_col++;
Array args = ARRAY_DICT_INIT;
- push_call(ui, "visual_bell", args);
+ ADD(args, STRING_OBJ(cstr_to_string(cell)));
+ push_call(ui, "put", args);
}
-static void remote_ui_update_fg(UI *ui, int fg)
+static void remote_ui_raw_line(UI *ui, Integer grid, Integer row,
+ Integer startcol, Integer endcol,
+ Integer clearcol, Integer clearattr,
+ Boolean wrap, const schar_T *chunk,
+ const sattr_T *attrs)
{
- Array args = ARRAY_DICT_INIT;
- ADD(args, INTEGER_OBJ(fg));
- push_call(ui, "update_fg", args);
+ UIData *data = ui->data;
+ if (ui->ui_ext[kUILinegrid]) {
+ Array args = ARRAY_DICT_INIT;
+ ADD(args, INTEGER_OBJ(grid));
+ ADD(args, INTEGER_OBJ(row));
+ ADD(args, INTEGER_OBJ(startcol));
+ Array cells = ARRAY_DICT_INIT;
+ int repeat = 0;
+ size_t ncells = (size_t)(endcol-startcol);
+ int last_hl = -1;
+ for (size_t i = 0; i < ncells; i++) {
+ repeat++;
+ if (i == ncells-1 || attrs[i] != attrs[i+1]
+ || STRCMP(chunk[i], chunk[i+1])) {
+ Array cell = ARRAY_DICT_INIT;
+ ADD(cell, STRING_OBJ(cstr_to_string((const char *)chunk[i])));
+ if (attrs[i] != last_hl || repeat > 1) {
+ ADD(cell, INTEGER_OBJ(attrs[i]));
+ last_hl = attrs[i];
+ }
+ if (repeat > 1) {
+ ADD(cell, INTEGER_OBJ(repeat));
+ }
+ ADD(cells, ARRAY_OBJ(cell));
+ repeat = 0;
+ }
+ }
+ if (endcol < clearcol) {
+ Array cell = ARRAY_DICT_INIT;
+ ADD(cell, STRING_OBJ(cstr_to_string(" ")));
+ ADD(cell, INTEGER_OBJ(clearattr));
+ ADD(cell, INTEGER_OBJ(clearcol-endcol));
+ ADD(cells, ARRAY_OBJ(cell));
+ }
+ ADD(args, ARRAY_OBJ(cells));
+
+ push_call(ui, "grid_line", args);
+ } else {
+ for (int i = 0; i < endcol-startcol; i++) {
+ remote_ui_cursor_goto(ui, row, startcol+i);
+ remote_ui_highlight_set(ui, attrs[i]);
+ remote_ui_put(ui, (const char *)chunk[i]);
+ if (utf_ambiguous_width(utf_ptr2char(chunk[i]))) {
+ data->client_col = -1; // force cursor update
+ }
+ }
+ if (endcol < clearcol) {
+ remote_ui_cursor_goto(ui, row, endcol);
+ remote_ui_highlight_set(ui, (int)clearattr);
+ // legacy eol_clear was only ever used with cleared attributes
+ // so be on the safe side
+ if (clearattr == 0 && clearcol == Columns) {
+ Array args = ARRAY_DICT_INIT;
+ push_call(ui, "eol_clear", args);
+ } else {
+ for (Integer c = endcol; c < clearcol; c++) {
+ remote_ui_put(ui, " ");
+ }
+ }
+ }
+ }
}
-static void remote_ui_update_bg(UI *ui, int bg)
+static void remote_ui_flush(UI *ui)
{
- Array args = ARRAY_DICT_INIT;
- ADD(args, INTEGER_OBJ(bg));
- push_call(ui, "update_bg", args);
+ UIData *data = ui->data;
+ if (data->buffer.size > 0) {
+ if (!ui->ui_ext[kUILinegrid]) {
+ remote_ui_cursor_goto(ui, data->cursor_row, data->cursor_col);
+ }
+ push_call(ui, "flush", (Array)ARRAY_DICT_INIT);
+ rpc_send_event(data->channel_id, "redraw", data->buffer);
+ data->buffer = (Array)ARRAY_DICT_INIT;
+ }
}
-static void remote_ui_update_sp(UI *ui, int sp)
+static Array translate_contents(UI *ui, Array contents)
{
- Array args = ARRAY_DICT_INIT;
- ADD(args, INTEGER_OBJ(sp));
- push_call(ui, "update_sp", args);
+ Array new_contents = ARRAY_DICT_INIT;
+ for (size_t i = 0; i < contents.size; i++) {
+ Array item = contents.items[i].data.array;
+ Array new_item = ARRAY_DICT_INIT;
+ int attr = (int)item.items[0].data.integer;
+ if (attr) {
+ Dictionary rgb_attrs = hlattrs2dict(syn_attr2entry(attr), ui->rgb);
+ ADD(new_item, DICTIONARY_OBJ(rgb_attrs));
+ } else {
+ ADD(new_item, DICTIONARY_OBJ((Dictionary)ARRAY_DICT_INIT));
+ }
+ ADD(new_item, copy_object(item.items[1]));
+ ADD(new_contents, ARRAY_OBJ(new_item));
+ }
+ return new_contents;
}
-static void remote_ui_flush(UI *ui)
+static Array translate_firstarg(UI *ui, Array args)
{
- UIData *data = ui->data;
- channel_send_event(data->channel_id, "redraw", data->buffer);
- data->buffer = (Array)ARRAY_DICT_INIT;
-}
+ Array new_args = ARRAY_DICT_INIT;
+ Array contents = args.items[0].data.array;
-static void remote_ui_suspend(UI *ui)
-{
- Array args = ARRAY_DICT_INIT;
- push_call(ui, "suspend", args);
+ ADD(new_args, ARRAY_OBJ(translate_contents(ui, contents)));
+ for (size_t i = 1; i < args.size; i++) {
+ ADD(new_args, copy_object(args.items[i]));
+ }
+ return new_args;
}
-static void remote_ui_set_title(UI *ui, char *title)
+static void remote_ui_event(UI *ui, char *name, Array args, bool *args_consumed)
{
- Array args = ARRAY_DICT_INIT;
- ADD(args, STRING_OBJ(cstr_to_string(title)));
- push_call(ui, "set_title", args);
+ if (!ui->ui_ext[kUILinegrid]) {
+ // the representation of highlights in cmdline changed, translate back
+ // never consumes args
+ if (strequal(name, "cmdline_show")) {
+ Array new_args = translate_firstarg(ui, args);
+ push_call(ui, name, new_args);
+ return;
+ } else if (strequal(name, "cmdline_block_show")) {
+ Array new_args = ARRAY_DICT_INIT;
+ Array block = args.items[0].data.array;
+ Array new_block = ARRAY_DICT_INIT;
+ for (size_t i = 0; i < block.size; i++) {
+ ADD(new_block,
+ ARRAY_OBJ(translate_contents(ui, block.items[i].data.array)));
+ }
+ ADD(new_args, ARRAY_OBJ(new_block));
+ push_call(ui, name, new_args);
+ return;
+ } else if (strequal(name, "cmdline_block_append")) {
+ Array new_args = translate_firstarg(ui, args);
+ push_call(ui, name, new_args);
+ return;
+ }
+ }
+
+ Array my_args = ARRAY_DICT_INIT;
+ // Objects are currently single-reference
+ // make a copy, but only if necessary
+ if (*args_consumed) {
+ for (size_t i = 0; i < args.size; i++) {
+ ADD(my_args, copy_object(args.items[i]));
+ }
+ } else {
+ my_args = args;
+ *args_consumed = true;
+ }
+ push_call(ui, name, my_args);
}
-static void remote_ui_set_icon(UI *ui, char *icon)
+static void remote_ui_inspect(UI *ui, Dictionary *info)
{
- Array args = ARRAY_DICT_INIT;
- ADD(args, STRING_OBJ(cstr_to_string(icon)));
- push_call(ui, "set_icon", args);
+ UIData *data = ui->data;
+ PUT(*info, "chan", INTEGER_OBJ((Integer)data->channel_id));
}