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