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 <inttypes.h>
6 #include <limits.h>
7 #include <stdbool.h>
8 #include <string.h>
9 
10 #include "nvim/ascii.h"
11 #include "nvim/aucmd.h"
12 #include "nvim/charset.h"
13 #include "nvim/cursor.h"
14 #include "nvim/cursor_shape.h"
15 #include "nvim/diff.h"
16 #include "nvim/event/loop.h"
17 #include "nvim/ex_cmds2.h"
18 #include "nvim/ex_getln.h"
19 #include "nvim/fold.h"
20 #include "nvim/garray.h"
21 #include "nvim/highlight.h"
22 #include "nvim/log.h"
23 #include "nvim/main.h"
24 #include "nvim/mbyte.h"
25 #include "nvim/memory.h"
26 #include "nvim/misc1.h"
27 #include "nvim/move.h"
28 #include "nvim/normal.h"
29 #include "nvim/option.h"
30 #include "nvim/os/input.h"
31 #include "nvim/os/signal.h"
32 #include "nvim/os/time.h"
33 #include "nvim/os_unix.h"
34 #include "nvim/popupmnu.h"
35 #include "nvim/screen.h"
36 #include "nvim/ui.h"
37 #include "nvim/ui_compositor.h"
38 #include "nvim/vim.h"
39 #include "nvim/window.h"
40 #ifdef FEAT_TUI
41 # include "nvim/tui/tui.h"
42 #else
43 # include "nvim/msgpack_rpc/server.h"
44 #endif
45 #include "nvim/api/private/helpers.h"
46 
47 #ifdef INCLUDE_GENERATED_DECLARATIONS
48 # include "ui.c.generated.h"
49 #endif
50 
51 #define MAX_UI_COUNT 16
52 
53 static UI *uis[MAX_UI_COUNT];
54 static bool ui_ext[kUIExtCount] = { 0 };
55 static size_t ui_count = 0;
56 static int ui_mode_idx = SHAPE_IDX_N;
57 static int cursor_row = 0, cursor_col = 0;
58 static bool pending_cursor_update = false;
59 static int busy = 0;
60 static bool pending_mode_info_update = false;
61 static bool pending_mode_update = false;
62 static handle_T cursor_grid_handle = DEFAULT_GRID_HANDLE;
63 
64 static bool has_mouse = false;
65 static int pending_has_mouse = -1;
66 
67 #if MIN_LOG_LEVEL > DEBUG_LOG_LEVEL
68 # define UI_LOG(funname)
69 #else
70 static size_t uilog_seen = 0;
71 static char uilog_last_event[1024] = { 0 };
72 
73 # ifndef EXITFREE
74 #  define entered_free_all_mem false
75 # endif
76 
77 # define UI_LOG(funname) \
78   do { \
79     if (entered_free_all_mem) { \
80       /* do nothing, we cannot log now */ \
81     } else if (strequal(uilog_last_event, STR(funname))) { \
82       uilog_seen++; \
83     } else { \
84       if (uilog_seen > 0) { \
85         logmsg(DEBUG_LOG_LEVEL, "UI: ", NULL, -1, true, \
86                "%s (+%zu times...)", uilog_last_event, uilog_seen); \
87       } \
88       logmsg(DEBUG_LOG_LEVEL, "UI: ", NULL, -1, true, STR(funname)); \
89       uilog_seen = 0; \
90       xstrlcpy(uilog_last_event, STR(funname), sizeof(uilog_last_event)); \
91     } \
92   } while (0)
93 #endif
94 
95 // UI_CALL invokes a function on all registered UI instances.
96 // This is called by code generated by generators/gen_api_ui_events.lua
97 // C code should use ui_call_{funname} instead.
98 #define UI_CALL(cond, funname, ...) \
99   do { \
100     bool any_call = false; \
101     for (size_t i = 0; i < ui_count; i++) { \
102       UI *ui = uis[i]; \
103       if (ui->funname && (cond)) { \
104         ui->funname(__VA_ARGS__); \
105         any_call = true; \
106       } \
107     } \
108     if (any_call) { \
109       UI_LOG(funname); \
110     } \
111   } while (0)
112 
113 #ifdef INCLUDE_GENERATED_DECLARATIONS
114 # include "ui_events_call.generated.h"
115 #endif
116 
117 #ifndef EXITFREE
118 # undef entered_free_all_mem
119 #endif
120 
ui_init(void)121 void ui_init(void)
122 {
123   default_grid.handle = 1;
124   msg_grid_adj.target = &default_grid;
125   ui_comp_init();
126 }
127 
ui_builtin_start(void)128 void ui_builtin_start(void)
129 {
130 #ifdef FEAT_TUI
131   tui_start();
132 #else
133   fprintf(stderr, "Nvim headless-mode started.\n");
134   size_t len;
135   char **addrs = server_address_list(&len);
136   if (addrs != NULL) {
137     fprintf(stderr, "Listening on:\n");
138     for (size_t i = 0; i < len; i++) {
139       fprintf(stderr, "\t%s\n", addrs[i]);
140     }
141     xfree(addrs);
142   }
143   fprintf(stderr, "Press CTRL+C to exit.\n");
144 #endif
145 }
146 
ui_rgb_attached(void)147 bool ui_rgb_attached(void)
148 {
149   if (!headless_mode && p_tgc) {
150     return true;
151   }
152   for (size_t i = 1; i < ui_count; i++) {
153     if (uis[i]->rgb) {
154       return true;
155     }
156   }
157   return false;
158 }
159 
160 /// Returns true if any UI requested `override=true`.
ui_override(void)161 bool ui_override(void)
162 {
163   for (size_t i = 1; i < ui_count; i++) {
164     if (uis[i]->override) {
165       return true;
166     }
167   }
168   return false;
169 }
170 
ui_active(void)171 bool ui_active(void)
172 {
173   return ui_count > 1;
174 }
175 
ui_event(char * name,Array args)176 void ui_event(char *name, Array args)
177 {
178   bool args_consumed = false;
179   ui_call_event(name, args, &args_consumed);
180   if (!args_consumed) {
181     api_free_array(args);
182   }
183 }
184 
185 
ui_refresh(void)186 void ui_refresh(void)
187 {
188   if (!ui_active()) {
189     return;
190   }
191 
192   if (updating_screen) {
193     deferred_refresh_event(NULL);
194     return;
195   }
196 
197   int width = INT_MAX, height = INT_MAX;
198   bool ext_widgets[kUIExtCount];
199   for (UIExtension i = 0; (int)i < kUIExtCount; i++) {
200     ext_widgets[i] = true;
201   }
202 
203   bool inclusive = ui_override();
204   for (size_t i = 0; i < ui_count; i++) {
205     UI *ui = uis[i];
206     width = MIN(ui->width, width);
207     height = MIN(ui->height, height);
208     for (UIExtension j = 0; (int)j < kUIExtCount; j++) {
209       ext_widgets[j] &= (ui->ui_ext[j] || inclusive);
210     }
211   }
212 
213   cursor_row = cursor_col = 0;
214   pending_cursor_update = true;
215 
216   for (UIExtension i = 0; (int)i < kUIExtCount; i++) {
217     ui_ext[i] = ext_widgets[i];
218     if (i < kUIGlobalCount) {
219       ui_call_option_set(cstr_as_string((char *)ui_ext_names[i]),
220                          BOOLEAN_OBJ(ext_widgets[i]));
221     }
222   }
223 
224   ui_default_colors_set();
225 
226   int save_p_lz = p_lz;
227   p_lz = false;  // convince redrawing() to return true ...
228   screen_resize(width, height);
229   p_lz = save_p_lz;
230 
231   if (ext_widgets[kUIMessages]) {
232     p_ch = 0;
233     command_height();
234   }
235   ui_mode_info_set();
236   pending_mode_update = true;
237   ui_cursor_shape();
238   pending_has_mouse = -1;
239 }
240 
ui_pum_get_height(void)241 int ui_pum_get_height(void)
242 {
243   int pum_height = 0;
244   for (size_t i = 1; i < ui_count; i++) {
245     int ui_pum_height = uis[i]->pum_nlines;
246     if (ui_pum_height) {
247       pum_height =
248         pum_height != 0 ? MIN(pum_height, ui_pum_height) : ui_pum_height;
249     }
250   }
251   return pum_height;
252 }
253 
ui_pum_get_pos(double * pwidth,double * pheight,double * prow,double * pcol)254 bool ui_pum_get_pos(double *pwidth, double *pheight, double *prow, double *pcol)
255 {
256   for (size_t i = 1; i < ui_count; i++) {
257     if (!uis[i]->pum_pos) {
258       continue;
259     }
260     *pwidth = uis[i]->pum_width;
261     *pheight = uis[i]->pum_height;
262     *prow = uis[i]->pum_row;
263     *pcol = uis[i]->pum_col;
264     return true;
265   }
266   return false;
267 }
268 
ui_refresh_event(void ** argv)269 static void ui_refresh_event(void **argv)
270 {
271   ui_refresh();
272 }
273 
ui_schedule_refresh(void)274 void ui_schedule_refresh(void)
275 {
276   loop_schedule_fast(&main_loop, event_create(deferred_refresh_event, 0));
277 }
deferred_refresh_event(void ** argv)278 static void deferred_refresh_event(void **argv)
279 {
280   multiqueue_put(resize_events, ui_refresh_event, 0);
281 }
282 
ui_default_colors_set(void)283 void ui_default_colors_set(void)
284 {
285   ui_call_default_colors_set(normal_fg, normal_bg, normal_sp,
286                              cterm_normal_fg_color, cterm_normal_bg_color);
287 }
288 
ui_busy_start(void)289 void ui_busy_start(void)
290 {
291   if (!(busy++)) {
292     ui_call_busy_start();
293   }
294 }
295 
ui_busy_stop(void)296 void ui_busy_stop(void)
297 {
298   if (!(--busy)) {
299     ui_call_busy_stop();
300   }
301 }
302 
ui_attach_impl(UI * ui,uint64_t chanid)303 void ui_attach_impl(UI *ui, uint64_t chanid)
304 {
305   if (ui_count == MAX_UI_COUNT) {
306     abort();
307   }
308   if (!ui->ui_ext[kUIMultigrid] && !ui->ui_ext[kUIFloatDebug]) {
309     ui_comp_attach(ui);
310   }
311 
312   uis[ui_count++] = ui;
313   ui_refresh_options();
314 
315   for (UIExtension i = kUIGlobalCount; (int)i < kUIExtCount; i++) {
316     ui_set_ext_option(ui, i, ui->ui_ext[i]);
317   }
318 
319   bool sent = false;
320   if (ui->ui_ext[kUIHlState]) {
321     sent = highlight_use_hlstate();
322   }
323   if (!sent) {
324     ui_send_all_hls(ui);
325   }
326   ui_refresh();
327 
328   bool is_compositor = (ui == uis[0]);
329   if (!is_compositor) {
330     do_autocmd_uienter(chanid, true);
331   }
332 }
333 
ui_detach_impl(UI * ui,uint64_t chanid)334 void ui_detach_impl(UI *ui, uint64_t chanid)
335 {
336   size_t shift_index = MAX_UI_COUNT;
337 
338   // Find the index that will be removed
339   for (size_t i = 0; i < ui_count; i++) {
340     if (uis[i] == ui) {
341       shift_index = i;
342       break;
343     }
344   }
345 
346   if (shift_index == MAX_UI_COUNT) {
347     abort();
348   }
349 
350   // Shift UIs at "shift_index"
351   while (shift_index < ui_count - 1) {
352     uis[shift_index] = uis[shift_index + 1];
353     shift_index++;
354   }
355 
356   if (--ui_count
357       // During teardown/exit the loop was already destroyed, cannot schedule.
358       // https://github.com/neovim/neovim/pull/5119#issuecomment-258667046
359       && !exiting) {
360     ui_schedule_refresh();
361   }
362 
363   if (!ui->ui_ext[kUIMultigrid] && !ui->ui_ext[kUIFloatDebug]) {
364     ui_comp_detach(ui);
365   }
366 
367   do_autocmd_uienter(chanid, false);
368 }
369 
ui_set_ext_option(UI * ui,UIExtension ext,bool active)370 void ui_set_ext_option(UI *ui, UIExtension ext, bool active)
371 {
372   if (ext < kUIGlobalCount) {
373     ui_refresh();
374     return;
375   }
376   if (ui->option_set && (ui_ext_names[ext][0] != '_' || active)) {
377     ui->option_set(ui, cstr_as_string((char *)ui_ext_names[ext]),
378                    BOOLEAN_OBJ(active));
379   }
380   if (ext == kUITermColors) {
381     ui_default_colors_set();
382   }
383 }
384 
ui_line(ScreenGrid * grid,int row,int startcol,int endcol,int clearcol,int clearattr,bool wrap)385 void ui_line(ScreenGrid *grid, int row, int startcol, int endcol, int clearcol, int clearattr,
386              bool wrap)
387 {
388   assert(0 <= row && row < grid->Rows);
389   LineFlags flags = wrap ? kLineFlagWrap : 0;
390   if (startcol == -1) {
391     startcol = 0;
392     flags |= kLineFlagInvalid;
393   }
394 
395   size_t off = grid->line_offset[row] + (size_t)startcol;
396 
397   ui_call_raw_line(grid->handle, row, startcol, endcol, clearcol, clearattr,
398                    flags, (const schar_T *)grid->chars + off,
399                    (const sattr_T *)grid->attrs + off);
400 
401   // 'writedelay': flush & delay each time.
402   if (p_wd && !(rdb_flags & RDB_COMPOSITOR)) {
403     // If 'writedelay' is active, set the cursor to indicate what was drawn.
404     ui_call_grid_cursor_goto(grid->handle, row,
405                              MIN(clearcol, (int)grid->Columns-1));
406     ui_call_flush();
407     uint64_t wd = (uint64_t)labs(p_wd);
408     os_microdelay(wd * 1000u, true);
409     pending_cursor_update = true;  // restore the cursor later
410   }
411 }
412 
ui_cursor_goto(int new_row,int new_col)413 void ui_cursor_goto(int new_row, int new_col)
414 {
415   ui_grid_cursor_goto(DEFAULT_GRID_HANDLE, new_row, new_col);
416 }
417 
ui_grid_cursor_goto(handle_T grid_handle,int new_row,int new_col)418 void ui_grid_cursor_goto(handle_T grid_handle, int new_row, int new_col)
419 {
420   if (new_row == cursor_row
421       && new_col == cursor_col
422       && grid_handle == cursor_grid_handle) {
423     return;
424   }
425 
426   cursor_row = new_row;
427   cursor_col = new_col;
428   cursor_grid_handle = grid_handle;
429   pending_cursor_update = true;
430 }
431 
432 /// moving the cursor grid will implicitly move the cursor
ui_check_cursor_grid(handle_T grid_handle)433 void ui_check_cursor_grid(handle_T grid_handle)
434 {
435   if (cursor_grid_handle == grid_handle) {
436     pending_cursor_update = true;
437   }
438 }
439 
ui_mode_info_set(void)440 void ui_mode_info_set(void)
441 {
442   pending_mode_info_update = true;
443 }
444 
ui_current_row(void)445 int ui_current_row(void)
446 {
447   return cursor_row;
448 }
449 
ui_current_col(void)450 int ui_current_col(void)
451 {
452   return cursor_col;
453 }
454 
ui_flush(void)455 void ui_flush(void)
456 {
457   cmdline_ui_flush();
458   win_ui_flush();
459   msg_ext_ui_flush();
460   msg_scroll_flush();
461 
462   if (pending_cursor_update) {
463     ui_call_grid_cursor_goto(cursor_grid_handle, cursor_row, cursor_col);
464     pending_cursor_update = false;
465   }
466   if (pending_mode_info_update) {
467     Array style = mode_style_array();
468     bool enabled = (*p_guicursor != NUL);
469     ui_call_mode_info_set(enabled, style);
470     api_free_array(style);
471     pending_mode_info_update = false;
472   }
473   if (pending_mode_update) {
474     char *full_name = shape_table[ui_mode_idx].full_name;
475     ui_call_mode_change(cstr_as_string(full_name), ui_mode_idx);
476     pending_mode_update = false;
477   }
478   if (pending_has_mouse != has_mouse) {
479     (has_mouse ? ui_call_mouse_on : ui_call_mouse_off)();
480     pending_has_mouse = has_mouse;
481   }
482   ui_call_flush();
483 }
484 
485 
486 /// Check if 'mouse' is active for the current mode
487 ///
488 /// TODO(bfredl): precompute the State -> active mapping when 'mouse' changes,
489 /// then this can be checked directly in ui_flush()
ui_check_mouse(void)490 void ui_check_mouse(void)
491 {
492   has_mouse = false;
493   // Be quick when mouse is off.
494   if (*p_mouse == NUL) {
495     return;
496   }
497 
498   int checkfor = MOUSE_NORMAL;  // assume normal mode
499   if (VIsual_active) {
500     checkfor = MOUSE_VISUAL;
501   } else if (State == HITRETURN || State == ASKMORE || State == SETWSIZE) {
502     checkfor = MOUSE_RETURN;
503   } else if (State & INSERT) {
504     checkfor = MOUSE_INSERT;
505   } else if (State & CMDLINE) {
506     checkfor = MOUSE_COMMAND;
507   } else if (State == CONFIRM || State == EXTERNCMD) {
508     checkfor = ' ';  // don't use mouse for ":confirm" or ":!cmd"
509   }
510 
511   // mouse should be active if at least one of the following is true:
512   // - "c" is in 'mouse', or
513   // - 'a' is in 'mouse' and "c" is in MOUSE_A, or
514   // - the current buffer is a help file and 'h' is in 'mouse' and we are in a
515   //   normal editing mode (not at hit-return message).
516   for (char_u *p = p_mouse; *p; p++) {
517     switch (*p) {
518     case 'a':
519       if (vim_strchr((char_u *)MOUSE_A, checkfor) != NULL) {
520         has_mouse = true;
521         return;
522       }
523       break;
524     case MOUSE_HELP:
525       if (checkfor != MOUSE_RETURN && curbuf->b_help) {
526         has_mouse = true;
527         return;
528       }
529       break;
530     default:
531       if (checkfor == *p) {
532         has_mouse = true;
533         return;
534       }
535     }
536   }
537 }
538 
539 /// Check if current mode has changed.
540 ///
541 /// May update the shape of the cursor.
ui_cursor_shape(void)542 void ui_cursor_shape(void)
543 {
544   if (!full_screen) {
545     return;
546   }
547   int new_mode_idx = cursor_get_mode_idx();
548 
549   if (new_mode_idx != ui_mode_idx) {
550     ui_mode_idx = new_mode_idx;
551     pending_mode_update = true;
552   }
553   conceal_check_cursor_line();
554 }
555 
556 /// Returns true if the given UI extension is enabled.
ui_has(UIExtension ext)557 bool ui_has(UIExtension ext)
558 {
559   return ui_ext[ext];
560 }
561 
ui_array(void)562 Array ui_array(void)
563 {
564   Array all_uis = ARRAY_DICT_INIT;
565   for (size_t i = 1; i < ui_count; i++) {
566     UI *ui = uis[i];
567     Dictionary info = ARRAY_DICT_INIT;
568     PUT(info, "width", INTEGER_OBJ(ui->width));
569     PUT(info, "height", INTEGER_OBJ(ui->height));
570     PUT(info, "rgb", BOOLEAN_OBJ(ui->rgb));
571     PUT(info, "override", BOOLEAN_OBJ(ui->override));
572     for (UIExtension j = 0; j < kUIExtCount; j++) {
573       if (ui_ext_names[j][0] != '_' || ui->ui_ext[j]) {
574         PUT(info, ui_ext_names[j], BOOLEAN_OBJ(ui->ui_ext[j]));
575       }
576     }
577     ui->inspect(ui, &info);
578     ADD(all_uis, DICTIONARY_OBJ(info));
579   }
580   return all_uis;
581 }
582 
ui_grid_resize(handle_T grid_handle,int width,int height,Error * error)583 void ui_grid_resize(handle_T grid_handle, int width, int height, Error *error)
584 {
585   if (grid_handle == DEFAULT_GRID_HANDLE) {
586     screen_resize(width, height);
587     return;
588   }
589 
590   win_T *wp = get_win_by_grid_handle(grid_handle);
591   if (wp == NULL) {
592     api_set_error(error, kErrorTypeValidation,
593                   "No window with the given handle");
594     return;
595   }
596 
597   if (wp->w_floating) {
598     if (width != wp->w_width || height != wp->w_height) {
599       wp->w_float_config.width = width;
600       wp->w_float_config.height = height;
601       win_config_float(wp, wp->w_float_config);
602     }
603   } else {
604     // non-positive indicates no request
605     wp->w_height_request = (int)MAX(height, 0);
606     wp->w_width_request = (int)MAX(width, 0);
607     win_set_inner_size(wp);
608   }
609 }
610