1// This is an open source non-commercial project. Dear PVS-Studio, please check
2// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
3
4#include <assert.h>
5#include <stddef.h>
6#include <stdint.h>
7#include <stdbool.h>
8
9#include "nvim/vim.h"
10#include "nvim/ui.h"
11#include "nvim/memory.h"
12#include "nvim/map.h"
13#include "nvim/msgpack_rpc/channel.h"
14#include "nvim/api/ui.h"
15#include "nvim/api/private/defs.h"
16#include "nvim/api/private/helpers.h"
17#include "nvim/popupmnu.h"
18#include "nvim/cursor_shape.h"
19#include "nvim/highlight.h"
20#include "nvim/screen.h"
21#include "nvim/window.h"
22
23#ifdef INCLUDE_GENERATED_DECLARATIONS
24# include "api/ui.c.generated.h"
25# include "ui_events_remote.generated.h"
26#endif
27
28typedef struct {
29 uint64_t channel_id;
30 Array buffer;
31
32 int hl_id; // Current highlight for legacy put event.
33 Integer cursor_row, cursor_col; // Intended visible cursor position.
34
35 // Position of legacy cursor, used both for drawing and visible user cursor.
36 Integer client_row, client_col;
37 bool wildmenu_active;
38} UIData;
39
40static PMap(uint64_t) *connected_uis = NULL;
41
42void remote_ui_init(void)
43 FUNC_API_NOEXPORT
44{
45 connected_uis = pmap_new(uint64_t)();
46}
47
48void remote_ui_disconnect(uint64_t channel_id)
49 FUNC_API_NOEXPORT
50{
51 UI *ui = pmap_get(uint64_t)(connected_uis, channel_id);
52 if (!ui) {
53 return;
54 }
55 UIData *data = ui->data;
56 api_free_array(data->buffer); // Destroy pending screen updates.
57 pmap_del(uint64_t)(connected_uis, channel_id);
58 xfree(ui->data);
59 ui->data = NULL; // Flag UI as "stopped".
60 ui_detach_impl(ui, channel_id);
61 xfree(ui);
62}
63
64/// Wait until ui has connected on stdio channel.
65void remote_ui_wait_for_attach(void)
66 FUNC_API_NOEXPORT
67{
68 Channel *channel = find_channel(CHAN_STDIO);
69 if (!channel) {
70 // this function should only be called in --embed mode, stdio channel
71 // can be assumed.
72 abort();
73 }
74
75 LOOP_PROCESS_EVENTS_UNTIL(&main_loop, channel->events, -1,
76 pmap_has(uint64_t)(connected_uis, CHAN_STDIO));
77}
78
79/// Activates UI events on the channel.
80///
81/// Entry point of all UI clients. Allows |\-\-embed| to continue startup.
82/// Implies that the client is ready to show the UI. Adds the client to the
83/// list of UIs. |nvim_list_uis()|
84///
85/// @note If multiple UI clients are attached, the global screen dimensions
86/// degrade to the smallest client. E.g. if client A requests 80x40 but
87/// client B requests 200x100, the global screen has size 80x40.
88///
89/// @param channel_id
90/// @param width Requested screen columns
91/// @param height Requested screen rows
92/// @param options |ui-option| map
93/// @param[out] err Error details, if any
94void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height,
95 Dictionary options, Error *err)
96 FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
97{
98 if (pmap_has(uint64_t)(connected_uis, channel_id)) {
99 api_set_error(err, kErrorTypeException,
100 "UI already attached to channel: %" PRId64, channel_id);
101 return;
102 }
103
104 if (width <= 0 || height <= 0) {
105 api_set_error(err, kErrorTypeValidation,
106 "Expected width > 0 and height > 0");
107 return;
108 }
109 UI *ui = xcalloc(1, sizeof(UI));
110 ui->width = (int)width;
111 ui->height = (int)height;
112 ui->pum_height = 0;
113 ui->rgb = true;
114 ui->override = false;
115 ui->grid_resize = remote_ui_grid_resize;
116 ui->grid_clear = remote_ui_grid_clear;
117 ui->grid_cursor_goto = remote_ui_grid_cursor_goto;
118 ui->mode_info_set = remote_ui_mode_info_set;
119 ui->update_menu = remote_ui_update_menu;
120 ui->busy_start = remote_ui_busy_start;
121 ui->busy_stop = remote_ui_busy_stop;
122 ui->mouse_on = remote_ui_mouse_on;
123 ui->mouse_off = remote_ui_mouse_off;
124 ui->mode_change = remote_ui_mode_change;
125 ui->grid_scroll = remote_ui_grid_scroll;
126 ui->hl_attr_define = remote_ui_hl_attr_define;
127 ui->hl_group_set = remote_ui_hl_group_set;
128 ui->raw_line = remote_ui_raw_line;
129 ui->bell = remote_ui_bell;
130 ui->visual_bell = remote_ui_visual_bell;
131 ui->default_colors_set = remote_ui_default_colors_set;
132 ui->flush = remote_ui_flush;
133 ui->suspend = remote_ui_suspend;
134 ui->set_title = remote_ui_set_title;
135 ui->set_icon = remote_ui_set_icon;
136 ui->option_set = remote_ui_option_set;
137 ui->msg_set_pos = remote_ui_msg_set_pos;
138 ui->event = remote_ui_event;
139 ui->inspect = remote_ui_inspect;
140
141 memset(ui->ui_ext, 0, sizeof(ui->ui_ext));
142
143 for (size_t i = 0; i < options.size; i++) {
144 ui_set_option(ui, true, options.items[i].key, options.items[i].value, err);
145 if (ERROR_SET(err)) {
146 xfree(ui);
147 return;
148 }
149 }
150
151 if (ui->ui_ext[kUIHlState] || ui->ui_ext[kUIMultigrid]) {
152 ui->ui_ext[kUILinegrid] = true;
153 }
154
155 if (ui->ui_ext[kUIMessages]) {
156 // This uses attribute indicies, so ext_linegrid is needed.
157 ui->ui_ext[kUILinegrid] = true;
158 // Cmdline uses the messages area, so it should be externalized too.
159 ui->ui_ext[kUICmdline] = true;
160 }
161
162 UIData *data = xmalloc(sizeof(UIData));
163 data->channel_id = channel_id;
164 data->buffer = (Array)ARRAY_DICT_INIT;
165 data->hl_id = 0;
166 data->client_col = -1;
167 data->wildmenu_active = false;
168 ui->data = data;
169
170 pmap_put(uint64_t)(connected_uis, channel_id, ui);
171 ui_attach_impl(ui, channel_id);
172}
173
174/// @deprecated
175void ui_attach(uint64_t channel_id, Integer width, Integer height,
176 Boolean enable_rgb, Error *err)
177{
178 Dictionary opts = ARRAY_DICT_INIT;
179 PUT(opts, "rgb", BOOLEAN_OBJ(enable_rgb));
180 nvim_ui_attach(channel_id, width, height, opts, err);
181 api_free_dictionary(opts);
182}
183
184/// Deactivates UI events on the channel.
185///
186/// Removes the client from the list of UIs. |nvim_list_uis()|
187///
188/// @param channel_id
189/// @param[out] err Error details, if any
190void nvim_ui_detach(uint64_t channel_id, Error *err)
191 FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
192{
193 if (!pmap_has(uint64_t)(connected_uis, channel_id)) {
194 api_set_error(err, kErrorTypeException,
195 "UI not attached to channel: %" PRId64, channel_id);
196 return;
197 }
198 remote_ui_disconnect(channel_id);
199}
200
201
202void nvim_ui_try_resize(uint64_t channel_id, Integer width,
203 Integer height, Error *err)
204 FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
205{
206 if (!pmap_has(uint64_t)(connected_uis, channel_id)) {
207 api_set_error(err, kErrorTypeException,
208 "UI not attached to channel: %" PRId64, channel_id);
209 return;
210 }
211
212 if (width <= 0 || height <= 0) {
213 api_set_error(err, kErrorTypeValidation,
214 "Expected width > 0 and height > 0");
215 return;
216 }
217
218 UI *ui = pmap_get(uint64_t)(connected_uis, channel_id);
219 ui->width = (int)width;
220 ui->height = (int)height;
221 ui_refresh();
222}
223
224void nvim_ui_set_option(uint64_t channel_id, String name,
225 Object value, Error *error)
226 FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
227{
228 if (!pmap_has(uint64_t)(connected_uis, channel_id)) {
229 api_set_error(error, kErrorTypeException,
230 "UI not attached to channel: %" PRId64, channel_id);
231 return;
232 }
233 UI *ui = pmap_get(uint64_t)(connected_uis, channel_id);
234
235 ui_set_option(ui, false, name, value, error);
236}
237
238static void ui_set_option(UI *ui, bool init, String name, Object value,
239 Error *error)
240{
241 if (strequal(name.data, "override")) {
242 if (value.type != kObjectTypeBoolean) {
243 api_set_error(error, kErrorTypeValidation, "override must be a Boolean");
244 return;
245 }
246 ui->override = value.data.boolean;
247 return;
248 }
249
250 if (strequal(name.data, "rgb")) {
251 if (value.type != kObjectTypeBoolean) {
252 api_set_error(error, kErrorTypeValidation, "rgb must be a Boolean");
253 return;
254 }
255 ui->rgb = value.data.boolean;
256 // A little drastic, but only takes effect for legacy uis. For linegrid UI
257 // only changes metadata for nvim_list_uis(), no refresh needed.
258 if (!init && !ui->ui_ext[kUILinegrid]) {
259 ui_refresh();
260 }
261 return;
262 }
263
264 // LEGACY: Deprecated option, use `ext_cmdline` instead.
265 bool is_popupmenu = strequal(name.data, "popupmenu_external");
266
267 for (UIExtension i = 0; i < kUIExtCount; i++) {
268 if (strequal(name.data, ui_ext_names[i])
269 || (i == kUIPopupmenu && is_popupmenu)) {
270 if (value.type != kObjectTypeBoolean) {
271 api_set_error(error, kErrorTypeValidation, "%s must be a Boolean",
272 name.data);
273 return;
274 }
275 bool boolval = value.data.boolean;
276 if (!init && i == kUILinegrid && boolval != ui->ui_ext[i]) {
277 // There shouldn't be a reason for an UI to do this ever
278 // so explicitly don't support this.
279 api_set_error(error, kErrorTypeValidation,
280 "ext_linegrid option cannot be changed");
281 }
282 ui->ui_ext[i] = boolval;
283 if (!init) {
284 ui_set_ext_option(ui, i, boolval);
285 }
286 return;
287 }
288 }
289
290 api_set_error(error, kErrorTypeValidation, "No such UI option: %s",
291 name.data);
292}
293
294/// Tell Nvim to resize a grid. Triggers a grid_resize event with the requested
295/// grid size or the maximum size if it exceeds size limits.
296///
297/// On invalid grid handle, fails with error.
298///
299/// @param channel_id
300/// @param grid The handle of the grid to be changed.
301/// @param width The new requested width.
302/// @param height The new requested height.
303/// @param[out] err Error details, if any
304void nvim_ui_try_resize_grid(uint64_t channel_id, Integer grid, Integer width,
305 Integer height, Error *err)
306 FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY
307{
308 if (!pmap_has(uint64_t)(connected_uis, channel_id)) {
309 api_set_error(err, kErrorTypeException,
310 "UI not attached to channel: %" PRId64, channel_id);
311 return;
312 }
313
314 ui_grid_resize((handle_T)grid, (int)width, (int)height, err);
315}
316
317/// Tells Nvim the number of elements displaying in the popumenu, to decide
318/// <PageUp> and <PageDown> movement.
319///
320/// @param channel_id
321/// @param height Popupmenu height, must be greater than zero.
322/// @param[out] err Error details, if any
323void nvim_ui_pum_set_height(uint64_t channel_id, Integer height, Error *err)
324 FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY
325{
326 if (!pmap_has(uint64_t)(connected_uis, channel_id)) {
327 api_set_error(err, kErrorTypeException,
328 "UI not attached to channel: %" PRId64, channel_id);
329 return;
330 }
331
332 if (height <= 0) {
333 api_set_error(err, kErrorTypeValidation, "Expected pum height > 0");
334 return;
335 }
336
337 UI *ui = pmap_get(uint64_t)(connected_uis, channel_id);
338 if (!ui->ui_ext[kUIPopupmenu]) {
339 api_set_error(err, kErrorTypeValidation,
340 "It must support the ext_popupmenu option");
341 return;
342 }
343 ui->pum_height = (int)height;
344}
345
346/// Pushes data into UI.UIData, to be consumed later by remote_ui_flush().
347static void push_call(UI *ui, const char *name, Array args)
348{
349 Array call = ARRAY_DICT_INIT;
350 UIData *data = ui->data;
351
352 // To optimize data transfer(especially for "put"), we bundle adjacent
353 // calls to same method together, so only add a new call entry if the last
354 // method call is different from "name"
355 if (kv_size(data->buffer)) {
356 call = kv_A(data->buffer, kv_size(data->buffer) - 1).data.array;
357 }
358
359 if (!kv_size(call) || strcmp(kv_A(call, 0).data.string.data, name)) {
360 call = (Array)ARRAY_DICT_INIT;
361 ADD(data->buffer, ARRAY_OBJ(call));
362 ADD(call, STRING_OBJ(cstr_to_string(name)));
363 }
364
365 ADD(call, ARRAY_OBJ(args));
366 kv_A(data->buffer, kv_size(data->buffer) - 1).data.array = call;
367}
368
369static void remote_ui_grid_clear(UI *ui, Integer grid)
370{
371 Array args = ARRAY_DICT_INIT;
372 if (ui->ui_ext[kUILinegrid]) {
373 ADD(args, INTEGER_OBJ(grid));
374 }
375 const char *name = ui->ui_ext[kUILinegrid] ? "grid_clear" : "clear";
376 push_call(ui, name, args);
377}
378
379static void remote_ui_grid_resize(UI *ui, Integer grid,
380 Integer width, Integer height)
381{
382 Array args = ARRAY_DICT_INIT;
383 if (ui->ui_ext[kUILinegrid]) {
384 ADD(args, INTEGER_OBJ(grid));
385 }
386 ADD(args, INTEGER_OBJ(width));
387 ADD(args, INTEGER_OBJ(height));
388 const char *name = ui->ui_ext[kUILinegrid] ? "grid_resize" : "resize";
389 push_call(ui, name, args);
390}
391
392static void remote_ui_grid_scroll(UI *ui, Integer grid, Integer top,
393 Integer bot, Integer left, Integer right,
394 Integer rows, Integer cols)
395{
396 if (ui->ui_ext[kUILinegrid]) {
397 Array args = ARRAY_DICT_INIT;
398 ADD(args, INTEGER_OBJ(grid));
399 ADD(args, INTEGER_OBJ(top));
400 ADD(args, INTEGER_OBJ(bot));
401 ADD(args, INTEGER_OBJ(left));
402 ADD(args, INTEGER_OBJ(right));
403 ADD(args, INTEGER_OBJ(rows));
404 ADD(args, INTEGER_OBJ(cols));
405 push_call(ui, "grid_scroll", args);
406 } else {
407 Array args = ARRAY_DICT_INIT;
408 ADD(args, INTEGER_OBJ(top));
409 ADD(args, INTEGER_OBJ(bot-1));
410 ADD(args, INTEGER_OBJ(left));
411 ADD(args, INTEGER_OBJ(right-1));
412 push_call(ui, "set_scroll_region", args);
413
414 args = (Array)ARRAY_DICT_INIT;
415 ADD(args, INTEGER_OBJ(rows));
416 push_call(ui, "scroll", args);
417
418 // some clients have "clear" being affected by scroll region,
419 // so reset it.
420 args = (Array)ARRAY_DICT_INIT;
421 ADD(args, INTEGER_OBJ(0));
422 ADD(args, INTEGER_OBJ(ui->height-1));
423 ADD(args, INTEGER_OBJ(0));
424 ADD(args, INTEGER_OBJ(ui->width-1));
425 push_call(ui, "set_scroll_region", args);
426 }
427}
428
429static void remote_ui_default_colors_set(UI *ui, Integer rgb_fg,
430 Integer rgb_bg, Integer rgb_sp,
431 Integer cterm_fg, Integer cterm_bg)
432{
433 if (!ui->ui_ext[kUITermColors]) {
434 HL_SET_DEFAULT_COLORS(rgb_fg, rgb_bg, rgb_sp);
435 }
436 Array args = ARRAY_DICT_INIT;
437 ADD(args, INTEGER_OBJ(rgb_fg));
438 ADD(args, INTEGER_OBJ(rgb_bg));
439 ADD(args, INTEGER_OBJ(rgb_sp));
440 ADD(args, INTEGER_OBJ(cterm_fg));
441 ADD(args, INTEGER_OBJ(cterm_bg));
442 push_call(ui, "default_colors_set", args);
443
444 // Deprecated
445 if (!ui->ui_ext[kUILinegrid]) {
446 args = (Array)ARRAY_DICT_INIT;
447 ADD(args, INTEGER_OBJ(ui->rgb ? rgb_fg : cterm_fg - 1));
448 push_call(ui, "update_fg", args);
449
450 args = (Array)ARRAY_DICT_INIT;
451 ADD(args, INTEGER_OBJ(ui->rgb ? rgb_bg : cterm_bg - 1));
452 push_call(ui, "update_bg", args);
453
454 args = (Array)ARRAY_DICT_INIT;
455 ADD(args, INTEGER_OBJ(ui->rgb ? rgb_sp : -1));
456 push_call(ui, "update_sp", args);
457 }
458}
459
460static void remote_ui_hl_attr_define(UI *ui, Integer id, HlAttrs rgb_attrs,
461 HlAttrs cterm_attrs, Array info)
462{
463 if (!ui->ui_ext[kUILinegrid]) {
464 return;
465 }
466 Array args = ARRAY_DICT_INIT;
467
468 ADD(args, INTEGER_OBJ(id));
469 ADD(args, DICTIONARY_OBJ(hlattrs2dict(rgb_attrs, true)));
470 ADD(args, DICTIONARY_OBJ(hlattrs2dict(cterm_attrs, false)));
471
472 if (ui->ui_ext[kUIHlState]) {
473 ADD(args, ARRAY_OBJ(copy_array(info)));
474 } else {
475 ADD(args, ARRAY_OBJ((Array)ARRAY_DICT_INIT));
476 }
477
478 push_call(ui, "hl_attr_define", args);
479}
480
481static void remote_ui_highlight_set(UI *ui, int id)
482{
483 Array args = ARRAY_DICT_INIT;
484 UIData *data = ui->data;
485
486
487 if (data->hl_id == id) {
488 return;
489 }
490 data->hl_id = id;
491 Dictionary hl = hlattrs2dict(syn_attr2entry(id), ui->rgb);
492
493 ADD(args, DICTIONARY_OBJ(hl));
494 push_call(ui, "highlight_set", args);
495}
496
497/// "true" cursor used only for input focus
498static void remote_ui_grid_cursor_goto(UI *ui, Integer grid, Integer row,
499 Integer col)
500{
501 if (ui->ui_ext[kUILinegrid]) {
502 Array args = ARRAY_DICT_INIT;
503 ADD(args, INTEGER_OBJ(grid));
504 ADD(args, INTEGER_OBJ(row));
505 ADD(args, INTEGER_OBJ(col));
506 push_call(ui, "grid_cursor_goto", args);
507 } else {
508 UIData *data = ui->data;
509 data->cursor_row = row;
510 data->cursor_col = col;
511 remote_ui_cursor_goto(ui, row, col);
512 }
513}
514
515/// emulated cursor used both for drawing and for input focus
516static void remote_ui_cursor_goto(UI *ui, Integer row, Integer col)
517{
518 UIData *data = ui->data;
519 if (data->client_row == row && data->client_col == col) {
520 return;
521 }
522 data->client_row = row;
523 data->client_col = col;
524 Array args = ARRAY_DICT_INIT;
525 ADD(args, INTEGER_OBJ(row));
526 ADD(args, INTEGER_OBJ(col));
527 push_call(ui, "cursor_goto", args);
528}
529
530static void remote_ui_put(UI *ui, const char *cell)
531{
532 UIData *data = ui->data;
533 data->client_col++;
534 Array args = ARRAY_DICT_INIT;
535 ADD(args, STRING_OBJ(cstr_to_string(cell)));
536 push_call(ui, "put", args);
537}
538
539static void remote_ui_raw_line(UI *ui, Integer grid, Integer row,
540 Integer startcol, Integer endcol,
541 Integer clearcol, Integer clearattr,
542 LineFlags flags, const schar_T *chunk,
543 const sattr_T *attrs)
544{
545 UIData *data = ui->data;
546 if (ui->ui_ext[kUILinegrid]) {
547 Array args = ARRAY_DICT_INIT;
548 ADD(args, INTEGER_OBJ(grid));
549 ADD(args, INTEGER_OBJ(row));
550 ADD(args, INTEGER_OBJ(startcol));
551 Array cells = ARRAY_DICT_INIT;
552 int repeat = 0;
553 size_t ncells = (size_t)(endcol-startcol);
554 int last_hl = -1;
555 for (size_t i = 0; i < ncells; i++) {
556 repeat++;
557 if (i == ncells-1 || attrs[i] != attrs[i+1]
558 || STRCMP(chunk[i], chunk[i+1])) {
559 Array cell = ARRAY_DICT_INIT;
560 ADD(cell, STRING_OBJ(cstr_to_string((const char *)chunk[i])));
561 if (attrs[i] != last_hl || repeat > 1) {
562 ADD(cell, INTEGER_OBJ(attrs[i]));
563 last_hl = attrs[i];
564 }
565 if (repeat > 1) {
566 ADD(cell, INTEGER_OBJ(repeat));
567 }
568 ADD(cells, ARRAY_OBJ(cell));
569 repeat = 0;
570 }
571 }
572 if (endcol < clearcol) {
573 Array cell = ARRAY_DICT_INIT;
574 ADD(cell, STRING_OBJ(cstr_to_string(" ")));
575 ADD(cell, INTEGER_OBJ(clearattr));
576 ADD(cell, INTEGER_OBJ(clearcol-endcol));
577 ADD(cells, ARRAY_OBJ(cell));
578 }
579 ADD(args, ARRAY_OBJ(cells));
580
581 push_call(ui, "grid_line", args);
582 } else {
583 for (int i = 0; i < endcol-startcol; i++) {
584 remote_ui_cursor_goto(ui, row, startcol+i);
585 remote_ui_highlight_set(ui, attrs[i]);
586 remote_ui_put(ui, (const char *)chunk[i]);
587 if (utf_ambiguous_width(utf_ptr2char(chunk[i]))) {
588 data->client_col = -1; // force cursor update
589 }
590 }
591 if (endcol < clearcol) {
592 remote_ui_cursor_goto(ui, row, endcol);
593 remote_ui_highlight_set(ui, (int)clearattr);
594 // legacy eol_clear was only ever used with cleared attributes
595 // so be on the safe side
596 if (clearattr == 0 && clearcol == Columns) {
597 Array args = ARRAY_DICT_INIT;
598 push_call(ui, "eol_clear", args);
599 } else {
600 for (Integer c = endcol; c < clearcol; c++) {
601 remote_ui_put(ui, " ");
602 }
603 }
604 }
605 }
606}
607
608static void remote_ui_flush(UI *ui)
609{
610 UIData *data = ui->data;
611 if (data->buffer.size > 0) {
612 if (!ui->ui_ext[kUILinegrid]) {
613 remote_ui_cursor_goto(ui, data->cursor_row, data->cursor_col);
614 }
615 push_call(ui, "flush", (Array)ARRAY_DICT_INIT);
616 rpc_send_event(data->channel_id, "redraw", data->buffer);
617 data->buffer = (Array)ARRAY_DICT_INIT;
618 }
619}
620
621static Array translate_contents(UI *ui, Array contents)
622{
623 Array new_contents = ARRAY_DICT_INIT;
624 for (size_t i = 0; i < contents.size; i++) {
625 Array item = contents.items[i].data.array;
626 Array new_item = ARRAY_DICT_INIT;
627 int attr = (int)item.items[0].data.integer;
628 if (attr) {
629 Dictionary rgb_attrs = hlattrs2dict(syn_attr2entry(attr), ui->rgb);
630 ADD(new_item, DICTIONARY_OBJ(rgb_attrs));
631 } else {
632 ADD(new_item, DICTIONARY_OBJ((Dictionary)ARRAY_DICT_INIT));
633 }
634 ADD(new_item, copy_object(item.items[1]));
635 ADD(new_contents, ARRAY_OBJ(new_item));
636 }
637 return new_contents;
638}
639
640static Array translate_firstarg(UI *ui, Array args)
641{
642 Array new_args = ARRAY_DICT_INIT;
643 Array contents = args.items[0].data.array;
644
645 ADD(new_args, ARRAY_OBJ(translate_contents(ui, contents)));
646 for (size_t i = 1; i < args.size; i++) {
647 ADD(new_args, copy_object(args.items[i]));
648 }
649 return new_args;
650}
651
652static void remote_ui_event(UI *ui, char *name, Array args, bool *args_consumed)
653{
654 UIData *data = ui->data;
655 if (!ui->ui_ext[kUILinegrid]) {
656 // the representation of highlights in cmdline changed, translate back
657 // never consumes args
658 if (strequal(name, "cmdline_show")) {
659 Array new_args = translate_firstarg(ui, args);
660 push_call(ui, name, new_args);
661 return;
662 } else if (strequal(name, "cmdline_block_show")) {
663 Array new_args = ARRAY_DICT_INIT;
664 Array block = args.items[0].data.array;
665 Array new_block = ARRAY_DICT_INIT;
666 for (size_t i = 0; i < block.size; i++) {
667 ADD(new_block,
668 ARRAY_OBJ(translate_contents(ui, block.items[i].data.array)));
669 }
670 ADD(new_args, ARRAY_OBJ(new_block));
671 push_call(ui, name, new_args);
672 return;
673 } else if (strequal(name, "cmdline_block_append")) {
674 Array new_args = translate_firstarg(ui, args);
675 push_call(ui, name, new_args);
676 return;
677 }
678 }
679
680 // Back-compat: translate popupmenu_xx to legacy wildmenu_xx.
681 if (ui->ui_ext[kUIWildmenu]) {
682 if (strequal(name, "popupmenu_show")) {
683 data->wildmenu_active = (args.items[4].data.integer == -1)
684 || !ui->ui_ext[kUIPopupmenu];
685 if (data->wildmenu_active) {
686 Array new_args = ARRAY_DICT_INIT;
687 Array items = args.items[0].data.array;
688 Array new_items = ARRAY_DICT_INIT;
689 for (size_t i = 0; i < items.size; i++) {
690 ADD(new_items, copy_object(items.items[i].data.array.items[0]));
691 }
692 ADD(new_args, ARRAY_OBJ(new_items));
693 push_call(ui, "wildmenu_show", new_args);
694 if (args.items[1].data.integer != -1) {
695 Array new_args2 = ARRAY_DICT_INIT;
696 ADD(new_args2, args.items[1]);
697 push_call(ui, "wildmenu_select", new_args);
698 }
699 return;
700 }
701 } else if (strequal(name, "popupmenu_select")) {
702 if (data->wildmenu_active) {
703 name = "wildmenu_select";
704 }
705 } else if (strequal(name, "popupmenu_hide")) {
706 if (data->wildmenu_active) {
707 name = "wildmenu_hide";
708 }
709 }
710 }
711
712
713 Array my_args = ARRAY_DICT_INIT;
714 // Objects are currently single-reference
715 // make a copy, but only if necessary
716 if (*args_consumed) {
717 for (size_t i = 0; i < args.size; i++) {
718 ADD(my_args, copy_object(args.items[i]));
719 }
720 } else {
721 my_args = args;
722 *args_consumed = true;
723 }
724 push_call(ui, name, my_args);
725}
726
727static void remote_ui_inspect(UI *ui, Dictionary *info)
728{
729 UIData *data = ui->data;
730 PUT(*info, "chan", INTEGER_OBJ((Integer)data->channel_id));
731}
732