ui.c (22433B)
1 #include <assert.h> 2 #include <limits.h> 3 #include <stdbool.h> 4 #include <stdint.h> 5 #include <stdlib.h> 6 #include <string.h> 7 #include <uv.h> 8 9 #include "nvim/api/extmark.h" 10 #include "nvim/api/private/helpers.h" 11 #include "nvim/api/private/validate.h" 12 #include "nvim/api/ui.h" 13 #include "nvim/ascii_defs.h" 14 #include "nvim/autocmd.h" 15 #include "nvim/buffer.h" 16 #include "nvim/buffer_defs.h" 17 #include "nvim/cursor_shape.h" 18 #include "nvim/drawscreen.h" 19 #include "nvim/event/multiqueue.h" 20 #include "nvim/ex_getln.h" 21 #include "nvim/gettext_defs.h" 22 #include "nvim/globals.h" 23 #include "nvim/grid.h" 24 #include "nvim/highlight.h" 25 #include "nvim/highlight_defs.h" 26 #include "nvim/log.h" 27 #include "nvim/lua/executor.h" 28 #include "nvim/map_defs.h" 29 #include "nvim/memory.h" 30 #include "nvim/memory_defs.h" 31 #include "nvim/message.h" 32 #include "nvim/option.h" 33 #include "nvim/option_defs.h" 34 #include "nvim/option_vars.h" 35 #include "nvim/os/os_defs.h" 36 #include "nvim/os/time.h" 37 #include "nvim/state_defs.h" 38 #include "nvim/strings.h" 39 #include "nvim/ui.h" 40 #include "nvim/ui_client.h" 41 #include "nvim/ui_compositor.h" 42 #include "nvim/window.h" 43 #include "nvim/winfloat.h" 44 45 typedef struct { 46 LuaRef cb; 47 uint8_t errors; 48 bool ext_widgets[kUIGlobalCount]; 49 } UIEventCallback; 50 51 #include "ui.c.generated.h" 52 53 #define MAX_UI_COUNT 16 54 55 static RemoteUI *uis[MAX_UI_COUNT]; 56 static bool ui_ext[kUIExtCount] = { 0 }; 57 static size_t ui_count = 0; 58 static int ui_mode_idx = SHAPE_IDX_N; 59 static int cursor_row = 0, cursor_col = 0; 60 static bool pending_cursor_update = false; 61 static int busy = 0; 62 static bool pending_mode_info_update = false; 63 static bool pending_mode_update = false; 64 static handle_T cursor_grid_handle = DEFAULT_GRID_HANDLE; 65 66 static PMap(uint32_t) ui_event_cbs = MAP_INIT; 67 bool ui_cb_ext[kUIExtCount]; ///< Internalized UI capabilities. 68 69 static bool has_mouse = false; 70 static int pending_has_mouse = -1; 71 static bool pending_default_colors = false; 72 73 #ifdef NVIM_LOG_DEBUG 74 static size_t uilog_seen = 0; 75 static const char *uilog_last_event = NULL; 76 77 static void ui_log(const char *funname) 78 { 79 # ifdef EXITFREE 80 if (entered_free_all_mem) { 81 return; // do nothing, we cannot log now 82 } 83 # endif 84 85 if (uilog_last_event == funname) { 86 uilog_seen++; 87 } else { 88 if (uilog_seen > 0) { 89 logmsg(LOGLVL_DBG, "UI: ", NULL, -1, true, 90 "%s (+%zu times...)", uilog_last_event, uilog_seen); 91 } 92 logmsg(LOGLVL_DBG, "UI: ", NULL, -1, true, "%s", funname); 93 uilog_seen = 0; 94 uilog_last_event = funname; 95 } 96 } 97 #else 98 # define ui_log(funname) 99 #endif 100 101 // UI_CALL invokes a function on all registered UI instances. 102 // This is called by code generated by generators/gen_api_ui_events.lua 103 // C code should use ui_call_{funname} instead. 104 #define UI_CALL(cond, funname, ...) \ 105 do { \ 106 bool any_call = false; \ 107 for (size_t i = 0; i < ui_count; i++) { \ 108 RemoteUI *ui = uis[i]; \ 109 if ((cond)) { \ 110 remote_ui_##funname(__VA_ARGS__); \ 111 any_call = true; \ 112 } \ 113 } \ 114 if (any_call) { \ 115 ui_log(STR(funname)); \ 116 } \ 117 } while (0) 118 119 #include "ui_events_call.generated.h" 120 121 void ui_init(void) 122 { 123 default_grid.handle = 1; 124 msg_grid_adj.target = &default_grid; 125 ui_comp_init(); 126 } 127 128 #ifdef EXITFREE 129 void ui_free_all_mem(void) 130 { 131 UIEventCallback *event_cb; 132 map_foreach_value(&ui_event_cbs, event_cb, { 133 free_ui_event_callback(event_cb); 134 }) 135 map_destroy(uint32_t, &ui_event_cbs); 136 137 multiqueue_free(resize_events); 138 } 139 #endif 140 141 /// Returns true if any `rgb=true` UI is attached. 142 bool ui_rgb_attached(void) 143 { 144 if (p_tgc) { 145 return true; 146 } 147 for (size_t i = 0; i < ui_count; i++) { 148 // We do not consider the TUI in this loop because we already checked for 'termguicolors' at the 149 // beginning of this function. In this loop, we are checking to see if any _other_ UIs which 150 // support RGB are attached. 151 bool tui = uis[i]->stdin_tty || uis[i]->stdout_tty; 152 if (!tui && uis[i]->rgb) { 153 return true; 154 } 155 } 156 return false; 157 } 158 159 /// Returns true if a GUI is attached. 160 bool ui_gui_attached(void) 161 { 162 for (size_t i = 0; i < ui_count; i++) { 163 bool tui = uis[i]->stdin_tty || uis[i]->stdout_tty; 164 if (!tui) { 165 return true; 166 } 167 } 168 return false; 169 } 170 171 /// Returns true if any UI requested `override=true`. 172 bool ui_override(void) 173 { 174 for (size_t i = 0; i < ui_count; i++) { 175 if (uis[i]->override) { 176 return true; 177 } 178 } 179 return false; 180 } 181 182 /// Gets the number of UIs connected to this server. 183 size_t ui_active(void) 184 { 185 return ui_count; 186 } 187 188 void ui_refresh(void) 189 { 190 if (ui_client_channel_id) { 191 abort(); 192 } 193 194 int width = INT_MAX; 195 int height = INT_MAX; 196 bool ext_widgets[kUIExtCount]; 197 bool inclusive = ui_override(); 198 memset(ext_widgets, !!ui_active(), ARRAY_SIZE(ext_widgets)); 199 200 for (size_t i = 0; i < ui_count; i++) { 201 RemoteUI *ui = uis[i]; 202 width = MIN(ui->width, width); 203 height = MIN(ui->height, height); 204 for (UIExtension j = 0; (int)j < kUIExtCount; j++) { 205 ext_widgets[j] &= (ui->ui_ext[j] || inclusive); 206 } 207 } 208 209 cursor_row = cursor_col = 0; 210 pending_cursor_update = true; 211 212 bool had_message = ui_ext[kUIMessages]; 213 for (UIExtension i = 0; (int)i < kUIExtCount; i++) { 214 ui_ext[i] = ext_widgets[i] | ui_cb_ext[i]; 215 if (i < kUIGlobalCount) { 216 ui_call_option_set(cstr_as_string(ui_ext_names[i]), BOOLEAN_OBJ(ui_ext[i])); 217 } 218 } 219 220 // Reset 'cmdheight' for all tabpages when ext_messages toggles. 221 if (had_message != ui_ext[kUIMessages]) { 222 if (ui_refresh_cmdheight) { 223 set_option_value(kOptCmdheight, NUMBER_OPTVAL(had_message), 0); 224 FOR_ALL_TABS(tp) { 225 tp->tp_ch_used = had_message; 226 } 227 } 228 msg_scroll_flush(); 229 } 230 msg_ui_refresh(); 231 232 if (!ui_active()) { 233 return; 234 } 235 236 if (updating_screen) { 237 ui_schedule_refresh(); 238 return; 239 } 240 241 ui_default_colors_set(); 242 243 int save_p_lz = p_lz; 244 p_lz = false; // convince redrawing() to return true ... 245 screen_resize(width, height); 246 p_lz = save_p_lz; 247 248 ui_mode_info_set(); 249 pending_mode_update = true; 250 ui_cursor_shape(); 251 pending_has_mouse = -1; 252 } 253 254 int ui_pum_get_height(void) 255 { 256 int pum_height = 0; 257 for (size_t i = 0; i < ui_count; i++) { 258 int ui_pum_height = uis[i]->pum_nlines; 259 if (ui_pum_height) { 260 pum_height = 261 pum_height != 0 ? MIN(pum_height, ui_pum_height) : ui_pum_height; 262 } 263 } 264 return pum_height; 265 } 266 267 bool ui_pum_get_pos(double *pwidth, double *pheight, double *prow, double *pcol) 268 { 269 for (size_t i = 0; i < ui_count; i++) { 270 if (!uis[i]->pum_pos) { 271 continue; 272 } 273 *pwidth = uis[i]->pum_width; 274 *pheight = uis[i]->pum_height; 275 *prow = uis[i]->pum_row; 276 *pcol = uis[i]->pum_col; 277 return true; 278 } 279 return false; 280 } 281 282 static void ui_refresh_event(void **argv) 283 { 284 ui_refresh(); 285 } 286 287 void ui_schedule_refresh(void) 288 { 289 multiqueue_put(resize_events, ui_refresh_event, NULL); 290 } 291 292 void ui_default_colors_set(void) 293 { 294 // Throttle setting of default colors at startup, so it only happens once 295 // if the user sets the colorscheme in startup. 296 pending_default_colors = true; 297 if (starting == 0) { 298 ui_may_set_default_colors(); 299 } 300 } 301 302 static void ui_may_set_default_colors(void) 303 { 304 if (pending_default_colors) { 305 pending_default_colors = false; 306 ui_call_default_colors_set(normal_fg, normal_bg, normal_sp, 307 cterm_normal_fg_color, cterm_normal_bg_color); 308 } 309 } 310 311 void ui_busy_start(void) 312 { 313 if (!(busy++)) { 314 ui_call_busy_start(); 315 } 316 } 317 318 void ui_busy_stop(void) 319 { 320 if (!(--busy)) { 321 ui_call_busy_stop(); 322 } 323 } 324 325 /// Emit a bell or visualbell as a warning 326 /// 327 /// val is one of the OptBoFlags values, e.g., kOptBoFlagOperator 328 void vim_beep(unsigned val) 329 { 330 called_vim_beep = true; 331 332 if (emsg_silent != 0 || in_assert_fails) { 333 return; 334 } 335 336 if (!((bo_flags & val) || (bo_flags & kOptBoFlagAll))) { 337 static int beeps = 0; 338 static uint64_t start_time = 0; 339 340 // Only beep up to three times per half a second, 341 // otherwise a sequence of beeps would freeze Vim. 342 if (start_time == 0 || os_hrtime() - start_time > 500000000U) { 343 beeps = 0; 344 start_time = os_hrtime(); 345 } 346 beeps++; 347 if (beeps <= 3) { 348 if (p_vb) { 349 ui_call_visual_bell(); 350 } else { 351 ui_call_bell(); 352 } 353 } 354 } 355 356 // When 'debug' contains "beep" produce a message. If we are sourcing 357 // a script or executing a function give the user a hint where the beep 358 // comes from. 359 if (vim_strchr(p_debug, 'e') != NULL) { 360 msg_source(HLF_W); 361 msg(_("Beep!"), HLF_W); 362 } 363 } 364 365 /// Trigger UIEnter for all attached UIs. 366 /// Used on startup after VimEnter. 367 void do_autocmd_uienter_all(void) 368 { 369 for (size_t i = 0; i < ui_count; i++) { 370 do_autocmd_uienter(uis[i]->channel_id, true); 371 } 372 } 373 374 bool ui_can_attach_more(void) 375 { 376 return ui_count < MAX_UI_COUNT; 377 } 378 379 void ui_attach_impl(RemoteUI *ui, uint64_t chanid) 380 { 381 if (ui_count >= MAX_UI_COUNT) { 382 abort(); 383 } 384 if (!ui->ui_ext[kUIMultigrid] && !ui->ui_ext[kUIFloatDebug] 385 && !ui_client_channel_id) { 386 ui_comp_attach(ui); 387 } 388 389 uis[ui_count++] = ui; 390 ui_refresh_options(); 391 resettitle(); 392 393 char cwd[MAXPATHL]; 394 size_t cwdlen = sizeof(cwd); 395 if (uv_cwd(cwd, &cwdlen) == 0) { 396 ui_call_chdir((String){ .data = cwd, .size = cwdlen }); 397 } 398 399 for (UIExtension i = kUIGlobalCount; (int)i < kUIExtCount; i++) { 400 ui_set_ext_option(ui, i, ui->ui_ext[i]); 401 } 402 403 bool sent = false; 404 if (ui->ui_ext[kUIHlState]) { 405 sent = highlight_use_hlstate(); 406 } 407 if (!sent) { 408 ui_send_all_hls(ui); 409 } 410 ui_refresh(); 411 412 do_autocmd_uienter(chanid, true); 413 } 414 415 void ui_detach_impl(RemoteUI *ui, uint64_t chanid) 416 { 417 if (ui_count > MAX_UI_COUNT) { 418 abort(); 419 } 420 size_t shift_index = MAX_UI_COUNT; 421 422 // Find the index that will be removed 423 for (size_t i = 0; i < ui_count; i++) { 424 if (uis[i] == ui) { 425 shift_index = i; 426 break; 427 } 428 } 429 430 if (shift_index >= MAX_UI_COUNT) { 431 abort(); 432 } 433 434 // Shift UIs at "shift_index" 435 while (shift_index < ui_count - 1) { 436 uis[shift_index] = uis[shift_index + 1]; 437 shift_index++; 438 } 439 440 if (--ui_count 441 // During teardown/exit the loop was already destroyed, cannot schedule. 442 // https://github.com/neovim/neovim/pull/5119#issuecomment-258667046 443 && !exiting) { 444 ui_schedule_refresh(); 445 } 446 447 if (!ui->ui_ext[kUIMultigrid] && !ui->ui_ext[kUIFloatDebug]) { 448 ui_comp_detach(ui); 449 } 450 451 do_autocmd_uienter(chanid, false); 452 } 453 454 void ui_set_ext_option(RemoteUI *ui, UIExtension ext, bool active) 455 { 456 if (ext < kUIGlobalCount) { 457 ui_refresh(); 458 return; 459 } 460 if (ui_ext_names[ext][0] != '_' || active) { 461 remote_ui_option_set(ui, cstr_as_string(ui_ext_names[ext]), BOOLEAN_OBJ(active)); 462 } 463 if (ext == kUITermColors) { 464 ui_default_colors_set(); 465 } 466 } 467 468 void ui_line(ScreenGrid *grid, int row, bool invalid_row, int startcol, int endcol, int clearcol, 469 int clearattr, bool wrap) 470 { 471 assert(0 <= row && row < grid->rows); 472 LineFlags flags = wrap ? kLineFlagWrap : 0; 473 if (startcol == 0 && invalid_row) { 474 flags |= kLineFlagInvalid; 475 } 476 477 // set default colors now so that that text won't have to be repainted later 478 ui_may_set_default_colors(); 479 480 size_t off = grid->line_offset[row] + (size_t)startcol; 481 482 ui_call_raw_line(grid->handle, row, startcol, endcol, clearcol, clearattr, 483 flags, (const schar_T *)grid->chars + off, 484 (const sattr_T *)grid->attrs + off); 485 486 // 'writedelay': flush & delay each time. 487 if (p_wd && (rdb_flags & kOptRdbFlagLine)) { 488 // If 'writedelay' is active, set the cursor to indicate what was drawn. 489 ui_call_grid_cursor_goto(grid->handle, row, 490 MIN(clearcol, (int)grid->cols - 1)); 491 ui_call_flush(); 492 uint64_t wd = (uint64_t)llabs(p_wd); 493 os_sleep(wd); 494 pending_cursor_update = true; // restore the cursor later 495 } 496 } 497 498 void ui_cursor_goto(int new_row, int new_col) 499 { 500 ui_grid_cursor_goto(DEFAULT_GRID_HANDLE, new_row, new_col); 501 } 502 503 void ui_grid_cursor_goto(handle_T grid_handle, int new_row, int new_col) 504 { 505 if (new_row == cursor_row 506 && new_col == cursor_col 507 && grid_handle == cursor_grid_handle) { 508 return; 509 } 510 511 cursor_row = new_row; 512 cursor_col = new_col; 513 cursor_grid_handle = grid_handle; 514 pending_cursor_update = true; 515 } 516 517 /// moving the cursor grid will implicitly move the cursor 518 void ui_check_cursor_grid(handle_T grid_handle) 519 { 520 if (cursor_grid_handle == grid_handle) { 521 pending_cursor_update = true; 522 } 523 } 524 525 void ui_mode_info_set(void) 526 { 527 pending_mode_info_update = true; 528 } 529 530 int ui_current_row(void) 531 { 532 return cursor_row; 533 } 534 535 int ui_current_col(void) 536 { 537 return cursor_col; 538 } 539 540 void ui_flush(void) 541 { 542 assert(!ui_client_channel_id); 543 if (!ui_active()) { 544 return; 545 } 546 547 static bool was_busy = false; 548 549 if (!(State & MODE_CMDLINE) && curwin->w_floating && curwin->w_config.hide) { 550 if (!was_busy) { 551 ui_call_busy_start(); 552 was_busy = true; 553 } 554 } else if (was_busy) { 555 ui_call_busy_stop(); 556 was_busy = false; 557 } 558 559 win_ui_flush(false); 560 // Avoid flushing callbacks expected to change text during textlock. 561 if (textlock == 0 && expr_map_lock == 0) { 562 cmdline_ui_flush(); 563 msg_ext_ui_flush(); 564 } 565 msg_scroll_flush(); 566 567 if (pending_cursor_update) { 568 ui_call_grid_cursor_goto(cursor_grid_handle, cursor_row, cursor_col); 569 pending_cursor_update = false; 570 // The cursor move might change the composition order, 571 // so flush again to update the windows that changed 572 // TODO(bfredl): refactor the flow of information so that win_ui_flush() 573 // only is called once. (as order state is exposed, it should be owned 574 // by nvim core, not the compositor) 575 win_ui_flush(false); 576 } 577 if (pending_mode_info_update) { 578 Arena arena = ARENA_EMPTY; 579 Array style = mode_style_array(&arena); 580 bool enabled = (*p_guicursor != NUL); 581 ui_call_mode_info_set(enabled, style); 582 arena_mem_free(arena_finish(&arena)); 583 pending_mode_info_update = false; 584 } 585 586 static bool cursor_was_obscured = false; 587 bool cursor_obscured = ui_cursor_is_behind_floatwin(); 588 if ((cursor_obscured != cursor_was_obscured || pending_mode_update) && !starting) { 589 // Show "empty box" (underline style) cursor instead if behind a floatwin. 590 int idx = cursor_obscured ? SHAPE_IDX_R : ui_mode_idx; 591 char *full_name = shape_table[idx].full_name; 592 ui_call_mode_change(cstr_as_string(full_name), idx); 593 pending_mode_update = false; 594 cursor_was_obscured = cursor_obscured; 595 } 596 597 if (pending_has_mouse != has_mouse) { 598 (has_mouse ? ui_call_mouse_on : ui_call_mouse_off)(); 599 pending_has_mouse = has_mouse; 600 } 601 ui_call_flush(); 602 603 if (p_wd && (rdb_flags & kOptRdbFlagFlush)) { 604 os_sleep((uint64_t)llabs(p_wd)); 605 } 606 } 607 608 /// Check if 'mouse' is active for the current mode 609 /// 610 /// TODO(bfredl): precompute the State -> active mapping when 'mouse' changes, 611 /// then this can be checked directly in ui_flush() 612 void ui_check_mouse(void) 613 { 614 has_mouse = false; 615 // Be quick when mouse is off. 616 if (*p_mouse == NUL) { 617 return; 618 } 619 620 int checkfor = MOUSE_NORMAL; // assume normal mode 621 if (VIsual_active) { 622 checkfor = MOUSE_VISUAL; 623 } else if (State == MODE_HITRETURN || State == MODE_ASKMORE || State == MODE_SETWSIZE) { 624 checkfor = MOUSE_RETURN; 625 } else if (State & MODE_INSERT) { 626 checkfor = MOUSE_INSERT; 627 } else if (State & MODE_CMDLINE) { 628 checkfor = MOUSE_COMMAND; 629 } else if (State == MODE_EXTERNCMD) { 630 checkfor = ' '; // don't use mouse for ":!cmd" 631 } 632 633 // mouse should be active if at least one of the following is true: 634 // - "c" is in 'mouse', or 635 // - 'a' is in 'mouse' and "c" is in MOUSE_A, or 636 // - the current buffer is a help file and 'h' is in 'mouse' and we are in a 637 // normal editing mode (not at hit-return message). 638 for (char *p = p_mouse; *p; p++) { 639 switch (*p) { 640 case 'a': 641 if (vim_strchr(MOUSE_A, checkfor) != NULL) { 642 has_mouse = true; 643 return; 644 } 645 break; 646 case MOUSE_HELP: 647 if (checkfor != MOUSE_RETURN && curbuf->b_help) { 648 has_mouse = true; 649 return; 650 } 651 break; 652 default: 653 if (checkfor == *p) { 654 has_mouse = true; 655 return; 656 } 657 } 658 } 659 } 660 661 /// Check if current mode has changed. 662 /// 663 /// May update the shape of the cursor. 664 void ui_cursor_shape_no_check_conceal(void) 665 { 666 if (!full_screen) { 667 return; 668 } 669 int new_mode_idx = cursor_get_mode_idx(); 670 671 if (new_mode_idx != ui_mode_idx) { 672 ui_mode_idx = new_mode_idx; 673 pending_mode_update = true; 674 } 675 } 676 677 /// Check if current mode has changed. 678 /// 679 /// May update the shape of the cursor. 680 /// With concealing on, may conceal or unconceal the cursor line. 681 void ui_cursor_shape(void) 682 { 683 ui_cursor_shape_no_check_conceal(); 684 conceal_check_cursor_line(); 685 } 686 687 /// Check if the cursor is behind a floating window (only in compositor mode). 688 /// @return true if cursor is obscured by a float with higher zindex 689 static bool ui_cursor_is_behind_floatwin(void) 690 { 691 if ((State & MODE_CMDLINE) || !ui_comp_should_draw()) { 692 return false; 693 } 694 695 int crow = curwin->w_winrow + curwin->w_winrow_off + curwin->w_wrow; 696 int ccol = curwin->w_wincol + curwin->w_wincol_off 697 + (curwin->w_p_rl ? curwin->w_view_width - curwin->w_wcol - 1 : curwin->w_wcol); 698 699 ScreenGrid *top_grid = ui_comp_get_grid_at_coord(crow, ccol); 700 return top_grid != &curwin->w_grid_alloc && top_grid != &default_grid; 701 } 702 703 /// Returns true if the given UI extension is enabled. 704 bool ui_has(UIExtension ext) 705 { 706 return ui_ext[ext]; 707 } 708 709 Array ui_array(Arena *arena) 710 { 711 Array all_uis = arena_array(arena, ui_count); 712 for (size_t i = 0; i < ui_count; i++) { 713 RemoteUI *ui = uis[i]; 714 Dict info = arena_dict(arena, 10 + kUIExtCount); 715 PUT_C(info, "width", INTEGER_OBJ(ui->width)); 716 PUT_C(info, "height", INTEGER_OBJ(ui->height)); 717 PUT_C(info, "rgb", BOOLEAN_OBJ(ui->rgb)); 718 PUT_C(info, "override", BOOLEAN_OBJ(ui->override)); 719 720 // TUI fields. (`stdin_fd` is intentionally omitted.) 721 PUT_C(info, "term_name", CSTR_AS_OBJ(ui->term_name)); 722 723 // term_background is deprecated. Populate with an empty string 724 PUT_C(info, "term_background", STATIC_CSTR_AS_OBJ("")); 725 726 PUT_C(info, "term_colors", INTEGER_OBJ(ui->term_colors)); 727 PUT_C(info, "stdin_tty", BOOLEAN_OBJ(ui->stdin_tty)); 728 PUT_C(info, "stdout_tty", BOOLEAN_OBJ(ui->stdout_tty)); 729 730 for (UIExtension j = 0; j < kUIExtCount; j++) { 731 if (ui_ext_names[j][0] != '_' || ui->ui_ext[j]) { 732 PUT_C(info, (char *)ui_ext_names[j], BOOLEAN_OBJ(ui->ui_ext[j])); 733 } 734 } 735 PUT_C(info, "chan", INTEGER_OBJ((Integer)ui->channel_id)); 736 737 ADD_C(all_uis, DICT_OBJ(info)); 738 } 739 return all_uis; 740 } 741 742 void ui_grid_resize(handle_T grid_handle, int width, int height, Error *err) 743 { 744 if (grid_handle == DEFAULT_GRID_HANDLE) { 745 screen_resize(width, height); 746 return; 747 } 748 749 win_T *wp = get_win_by_grid_handle(grid_handle); 750 VALIDATE_INT((wp != NULL), "window handle", (int64_t)grid_handle, { 751 return; 752 }); 753 754 if (wp->w_floating) { 755 if (width != wp->w_width || height != wp->w_height) { 756 wp->w_config.width = MAX(width, 1); 757 wp->w_config.height = MAX(height, 1); 758 win_config_float(wp, wp->w_config); 759 } 760 } else { 761 // non-positive indicates no request 762 wp->w_height_request = MAX(height, 0); 763 wp->w_width_request = MAX(width, 0); 764 win_set_inner_size(wp, true); 765 } 766 } 767 768 static void ui_attach_error(uint32_t ns_id, const char *name, const char *msg) 769 { 770 const char *ns = describe_ns((NS)ns_id, "(UNKNOWN PLUGIN)"); 771 ELOG("Error in \"%s\" UI event handler (ns=%s):\n%s", name, ns, msg); 772 msg_schedule_semsg_multiline("Error in \"%s\" UI event handler (ns=%s):\n%s", name, ns, msg); 773 } 774 775 void ui_call_event(char *name, Array args) 776 { 777 // Internal messages are considered unsafe and are executed in fast context. 778 bool fast = strcmp(name, "msg_show") == 0; 779 const char *not_fast[] = { 780 "empty", 781 "echo", 782 "echomsg", 783 "echoerr", 784 "list_cmd", 785 "lua_error", 786 "lua_print", 787 "progress", 788 NULL, 789 }; 790 791 for (int i = 0; fast && not_fast[i]; i++) { 792 fast = !strequal(not_fast[i], args.items[0].data.string.data); 793 } 794 795 // Don't impose textlock restrictions upon UI event handlers. 796 int save_expr_map_lock = expr_map_lock; 797 int save_textlock = textlock; 798 expr_map_lock = 0; 799 textlock = 0; 800 801 bool handled = false; 802 UIEventCallback *event_cb; 803 map_foreach(&ui_event_cbs, ui_event_ns_id, event_cb, { 804 Error err = ERROR_INIT; 805 uint32_t ns_id = ui_event_ns_id; 806 Object res = nlua_call_ref_ctx(fast, event_cb->cb, name, args, kRetNilBool, NULL, &err); 807 ui_event_ns_id = 0; 808 if (LUARET_TRUTHY(res)) { 809 handled = true; 810 } 811 if (ERROR_SET(&err)) { 812 ui_attach_error(ns_id, name, err.msg); 813 ui_remove_cb(ns_id, true); 814 } 815 api_clear_error(&err); 816 }) 817 expr_map_lock = save_expr_map_lock; 818 textlock = save_textlock; 819 820 if (!handled) { 821 UI_CALL(true, event, ui, name, args); 822 } 823 824 ui_log(name); 825 } 826 827 static void ui_cb_update_ext(void) 828 { 829 memset(ui_cb_ext, 0, ARRAY_SIZE(ui_cb_ext)); 830 831 for (size_t i = 0; i < kUIGlobalCount; i++) { 832 UIEventCallback *event_cb; 833 834 map_foreach_value(&ui_event_cbs, event_cb, { 835 if (event_cb->ext_widgets[i]) { 836 ui_cb_ext[i] = true; 837 break; 838 } 839 }) 840 } 841 } 842 843 static void free_ui_event_callback(UIEventCallback *event_cb) 844 { 845 api_free_luaref(event_cb->cb); 846 xfree(event_cb); 847 } 848 849 void ui_add_cb(uint32_t ns_id, LuaRef cb, bool *ext_widgets) 850 { 851 UIEventCallback *event_cb = xcalloc(1, sizeof(UIEventCallback)); 852 event_cb->cb = cb; 853 memcpy(event_cb->ext_widgets, ext_widgets, ARRAY_SIZE(event_cb->ext_widgets)); 854 if (event_cb->ext_widgets[kUIMessages]) { 855 event_cb->ext_widgets[kUICmdline] = true; 856 } 857 858 ptr_t *item = pmap_put_ref(uint32_t)(&ui_event_cbs, ns_id, NULL, NULL); 859 if (*item) { 860 free_ui_event_callback((UIEventCallback *)(*item)); 861 } 862 *item = event_cb; 863 864 ui_cb_update_ext(); 865 ui_refresh(); 866 } 867 868 void ui_remove_cb(uint32_t ns_id, bool checkerr) 869 { 870 UIEventCallback *item = pmap_get(uint32_t)(&ui_event_cbs, ns_id); 871 if (item && (!checkerr || ++item->errors > CB_MAX_ERROR)) { 872 pmap_del(uint32_t)(&ui_event_cbs, ns_id, NULL); 873 free_ui_event_callback(item); 874 ui_cb_update_ext(); 875 ui_refresh(); 876 if (checkerr) { 877 const char *ns = describe_ns((NS)ns_id, "(UNKNOWN PLUGIN)"); 878 msg_schedule_semsg("Excessive errors in vim.ui_attach() callback (ns=%s)", ns); 879 } 880 } 881 }