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