1 /* vi:set ts=8 sts=4 sw=4 noet: 2 * 3 * VIM - Vi IMproved by Bram Moolenaar 4 * 5 * Do ":help uganda" in Vim to read copying and usage conditions. 6 * Do ":help credits" in Vim to see a list of people who contributed. 7 * See README.txt for an overview of the Vim source code. 8 */ 9 10 /* 11 * popupmenu.c: Popup menu (PUM) 12 */ 13 #include "vim.h" 14 15 static pumitem_T *pum_array = NULL; // items of displayed pum 16 static int pum_size; // nr of items in "pum_array" 17 static int pum_selected; // index of selected item or -1 18 static int pum_first = 0; // index of top item 19 20 static int call_update_screen = FALSE; 21 22 static int pum_height; // nr of displayed pum items 23 static int pum_width; // width of displayed pum items 24 static int pum_base_width; // width of pum items base 25 static int pum_kind_width; // width of pum items kind column 26 static int pum_extra_width; // width of extra stuff 27 static int pum_scrollbar; // TRUE when scrollbar present 28 29 static int pum_row; // top row of pum 30 static int pum_col; // left column of pum 31 32 static win_T *pum_window = NULL; 33 static int pum_win_row; 34 static int pum_win_height; 35 static int pum_win_col; 36 static int pum_win_wcol; 37 static int pum_win_width; 38 39 // Some parts are not updated when a popup menu is visible. Setting this flag 40 // makes pum_visible() return FALSE even when there is a popup menu. 41 static int pum_pretend_not_visible = FALSE; 42 43 // When set the popup menu will redraw soon using the pum_win_ values. Do not 44 // draw over the poup menu area to avoid flicker. 45 static int pum_will_redraw = FALSE; 46 47 static int pum_set_selected(int n, int repeat); 48 49 #define PUM_DEF_HEIGHT 10 50 51 static void 52 pum_compute_size(void) 53 { 54 int i; 55 int w; 56 57 // Compute the width of the widest match and the widest extra. 58 pum_base_width = 0; 59 pum_kind_width = 0; 60 pum_extra_width = 0; 61 for (i = 0; i < pum_size; ++i) 62 { 63 if (pum_array[i].pum_text != NULL) 64 { 65 w = vim_strsize(pum_array[i].pum_text); 66 if (pum_base_width < w) 67 pum_base_width = w; 68 } 69 if (pum_array[i].pum_kind != NULL) 70 { 71 w = vim_strsize(pum_array[i].pum_kind) + 1; 72 if (pum_kind_width < w) 73 pum_kind_width = w; 74 } 75 if (pum_array[i].pum_extra != NULL) 76 { 77 w = vim_strsize(pum_array[i].pum_extra) + 1; 78 if (pum_extra_width < w) 79 pum_extra_width = w; 80 } 81 } 82 } 83 84 /* 85 * Show the popup menu with items "array[size]". 86 * "array" must remain valid until pum_undisplay() is called! 87 * When possible the leftmost character is aligned with cursor column. 88 * The menu appears above the screen line "row" or at "row" + "height" - 1. 89 */ 90 void 91 pum_display( 92 pumitem_T *array, 93 int size, 94 int selected) // index of initially selected item, none if 95 // out of range 96 { 97 int def_width; 98 int max_width; 99 int context_lines; 100 int cursor_col; 101 int above_row; 102 int below_row; 103 int redo_count = 0; 104 #if defined(FEAT_QUICKFIX) 105 win_T *pvwin; 106 #endif 107 108 do 109 { 110 def_width = p_pw; 111 above_row = 0; 112 below_row = cmdline_row; 113 114 // Pretend the pum is already there to avoid that must_redraw is set 115 // when 'cuc' is on. 116 pum_array = (pumitem_T *)1; 117 validate_cursor_col(); 118 pum_array = NULL; 119 120 // Remember the essential parts of the window position and size, so we 121 // can decide when to reposition the popup menu. 122 pum_window = curwin; 123 pum_win_row = curwin->w_wrow + W_WINROW(curwin); 124 pum_win_height = curwin->w_height; 125 pum_win_col = curwin->w_wincol; 126 pum_win_wcol = curwin->w_wcol; 127 pum_win_width = curwin->w_width; 128 129 #if defined(FEAT_QUICKFIX) 130 FOR_ALL_WINDOWS(pvwin) 131 if (pvwin->w_p_pvw) 132 break; 133 if (pvwin != NULL) 134 { 135 if (W_WINROW(pvwin) < W_WINROW(curwin)) 136 above_row = W_WINROW(pvwin) + pvwin->w_height; 137 else if (W_WINROW(pvwin) > W_WINROW(curwin) + curwin->w_height) 138 below_row = W_WINROW(pvwin); 139 } 140 #endif 141 142 /* 143 * Figure out the size and position of the pum. 144 */ 145 if (size < PUM_DEF_HEIGHT) 146 pum_height = size; 147 else 148 pum_height = PUM_DEF_HEIGHT; 149 if (p_ph > 0 && pum_height > p_ph) 150 pum_height = p_ph; 151 152 // Put the pum below "pum_win_row" if possible. If there are few lines 153 // decide on where there is more room. 154 if (pum_win_row + 2 >= below_row - pum_height 155 && pum_win_row - above_row > (below_row - above_row) / 2) 156 { 157 // pum above "pum_win_row" 158 159 // Leave two lines of context if possible 160 if (curwin->w_wrow - curwin->w_cline_row >= 2) 161 context_lines = 2; 162 else 163 context_lines = curwin->w_wrow - curwin->w_cline_row; 164 165 if (pum_win_row >= size + context_lines) 166 { 167 pum_row = pum_win_row - size - context_lines; 168 pum_height = size; 169 } 170 else 171 { 172 pum_row = 0; 173 pum_height = pum_win_row - context_lines; 174 } 175 if (p_ph > 0 && pum_height > p_ph) 176 { 177 pum_row += pum_height - p_ph; 178 pum_height = p_ph; 179 } 180 } 181 else 182 { 183 // pum below "pum_win_row" 184 185 // Leave two lines of context if possible 186 validate_cheight(); 187 if (curwin->w_cline_row 188 + curwin->w_cline_height - curwin->w_wrow >= 3) 189 context_lines = 3; 190 else 191 context_lines = curwin->w_cline_row 192 + curwin->w_cline_height - curwin->w_wrow; 193 194 pum_row = pum_win_row + context_lines; 195 if (size > below_row - pum_row) 196 pum_height = below_row - pum_row; 197 else 198 pum_height = size; 199 if (p_ph > 0 && pum_height > p_ph) 200 pum_height = p_ph; 201 } 202 203 // don't display when we only have room for one line 204 if (pum_height < 1 || (pum_height == 1 && size > 1)) 205 return; 206 207 #if defined(FEAT_QUICKFIX) 208 // If there is a preview window above avoid drawing over it. 209 if (pvwin != NULL && pum_row < above_row && pum_height > above_row) 210 { 211 pum_row = above_row; 212 pum_height = pum_win_row - above_row; 213 } 214 #endif 215 216 pum_array = array; 217 pum_size = size; 218 pum_compute_size(); 219 max_width = pum_base_width; 220 221 // Calculate column 222 #ifdef FEAT_RIGHTLEFT 223 if (curwin->w_p_rl) 224 cursor_col = curwin->w_wincol + curwin->w_width 225 - curwin->w_wcol - 1; 226 else 227 #endif 228 cursor_col = curwin->w_wincol + curwin->w_wcol; 229 230 // if there are more items than room we need a scrollbar 231 if (pum_height < size) 232 { 233 pum_scrollbar = 1; 234 ++max_width; 235 } 236 else 237 pum_scrollbar = 0; 238 239 if (def_width < max_width) 240 def_width = max_width; 241 242 if (((cursor_col < Columns - p_pw 243 || cursor_col < Columns - max_width) 244 #ifdef FEAT_RIGHTLEFT 245 && !curwin->w_p_rl) 246 || (curwin->w_p_rl 247 && (cursor_col > p_pw || cursor_col > max_width) 248 #endif 249 )) 250 { 251 // align pum with "cursor_col" 252 pum_col = cursor_col; 253 254 // start with the maximum space available 255 #ifdef FEAT_RIGHTLEFT 256 if (curwin->w_p_rl) 257 pum_width = pum_col - pum_scrollbar + 1; 258 else 259 #endif 260 pum_width = Columns - pum_col - pum_scrollbar; 261 262 if (pum_width > max_width + pum_kind_width + pum_extra_width + 1 263 && pum_width > p_pw) 264 { 265 // the width is more than needed for the items, make it 266 // narrower 267 pum_width = max_width + pum_kind_width + pum_extra_width + 1; 268 if (pum_width < p_pw) 269 pum_width = p_pw; 270 } 271 else if (((cursor_col > p_pw || cursor_col > max_width) 272 #ifdef FEAT_RIGHTLEFT 273 && !curwin->w_p_rl) 274 || (curwin->w_p_rl && (cursor_col < Columns - p_pw 275 || cursor_col < Columns - max_width) 276 #endif 277 )) 278 { 279 // align pum edge with "cursor_col" 280 #ifdef FEAT_RIGHTLEFT 281 if (curwin->w_p_rl 282 && W_ENDCOL(curwin) < max_width + pum_scrollbar + 1) 283 { 284 pum_col = cursor_col + max_width + pum_scrollbar + 1; 285 if (pum_col >= Columns) 286 pum_col = Columns - 1; 287 } 288 else if (!curwin->w_p_rl) 289 #endif 290 { 291 if (curwin->w_wincol > Columns - max_width - pum_scrollbar 292 && max_width <= p_pw) 293 { 294 // use full width to end of the screen 295 pum_col = Columns - max_width - pum_scrollbar; 296 if (pum_col < 0) 297 pum_col = 0; 298 } 299 } 300 301 #ifdef FEAT_RIGHTLEFT 302 if (curwin->w_p_rl) 303 pum_width = pum_col - pum_scrollbar + 1; 304 else 305 #endif 306 pum_width = Columns - pum_col - pum_scrollbar; 307 308 if (pum_width < p_pw) 309 { 310 pum_width = p_pw; 311 #ifdef FEAT_RIGHTLEFT 312 if (curwin->w_p_rl) 313 { 314 if (pum_width > pum_col) 315 pum_width = pum_col; 316 } 317 else 318 #endif 319 { 320 if (pum_width >= Columns - pum_col) 321 pum_width = Columns - pum_col - 1; 322 } 323 } 324 else if (pum_width > max_width + pum_kind_width 325 + pum_extra_width + 1 326 && pum_width > p_pw) 327 { 328 pum_width = max_width + pum_kind_width 329 + pum_extra_width + 1; 330 if (pum_width < p_pw) 331 pum_width = p_pw; 332 } 333 } 334 335 } 336 else if (Columns < def_width) 337 { 338 // not enough room, will use what we have 339 #ifdef FEAT_RIGHTLEFT 340 if (curwin->w_p_rl) 341 pum_col = Columns - 1; 342 else 343 #endif 344 pum_col = 0; 345 pum_width = Columns - 1; 346 } 347 else 348 { 349 if (max_width > p_pw) 350 max_width = p_pw; // truncate 351 #ifdef FEAT_RIGHTLEFT 352 if (curwin->w_p_rl) 353 pum_col = max_width - 1; 354 else 355 #endif 356 pum_col = Columns - max_width; 357 pum_width = max_width - pum_scrollbar; 358 } 359 360 // Set selected item and redraw. If the window size changed need to 361 // redo the positioning. Limit this to two times, when there is not 362 // much room the window size will keep changing. 363 } while (pum_set_selected(selected, redo_count) && ++redo_count <= 2); 364 } 365 366 /* 367 * Set a flag that when pum_redraw() is called it first calls update_screen(). 368 * This will avoid clearing and redrawing the popup menu, prevent flicker. 369 */ 370 void 371 pum_call_update_screen() 372 { 373 call_update_screen = TRUE; 374 375 // Update the cursor position to be able to compute the popup menu 376 // position. The cursor line length may have changed because of the 377 // inserted completion. 378 curwin->w_valid &= ~(VALID_CROW|VALID_CHEIGHT); 379 validate_cursor(); 380 } 381 382 /* 383 * Return TRUE if we are going to redraw the popup menu and the screen position 384 * "row"/"col" is under the popup menu. 385 */ 386 int 387 pum_under_menu(int row, int col) 388 { 389 return pum_will_redraw 390 && row >= pum_row 391 && row < pum_row + pum_height 392 && col >= pum_col - 1 393 && col < pum_col + pum_width; 394 } 395 396 /* 397 * Redraw the popup menu, using "pum_first" and "pum_selected". 398 */ 399 void 400 pum_redraw(void) 401 { 402 int row = pum_row; 403 int col; 404 int attr_norm = highlight_attr[HLF_PNI]; 405 int attr_select = highlight_attr[HLF_PSI]; 406 int attr_scroll = highlight_attr[HLF_PSB]; 407 int attr_thumb = highlight_attr[HLF_PST]; 408 int attr; 409 int i; 410 int idx; 411 char_u *s; 412 char_u *p = NULL; 413 int totwidth, width, w; 414 int thumb_pos = 0; 415 int thumb_height = 1; 416 int round; 417 int n; 418 419 if (call_update_screen) 420 { 421 call_update_screen = FALSE; 422 // Do not redraw in pum_may_redraw() and don't draw in the area where 423 // the popup menu will be. 424 pum_will_redraw = TRUE; 425 update_screen(0); 426 pum_will_redraw = FALSE; 427 } 428 429 // never display more than we have 430 if (pum_first > pum_size - pum_height) 431 pum_first = pum_size - pum_height; 432 433 if (pum_scrollbar) 434 { 435 thumb_height = pum_height * pum_height / pum_size; 436 if (thumb_height == 0) 437 thumb_height = 1; 438 thumb_pos = (pum_first * (pum_height - thumb_height) 439 + (pum_size - pum_height) / 2) 440 / (pum_size - pum_height); 441 } 442 443 #ifdef FEAT_PROP_POPUP 444 // The popup menu is drawn over popup menus with zindex under 445 // POPUPMENU_ZINDEX. 446 screen_zindex = POPUPMENU_ZINDEX; 447 #endif 448 449 for (i = 0; i < pum_height; ++i) 450 { 451 idx = i + pum_first; 452 attr = (idx == pum_selected) ? attr_select : attr_norm; 453 454 // prepend a space if there is room 455 #ifdef FEAT_RIGHTLEFT 456 if (curwin->w_p_rl) 457 { 458 if (pum_col < curwin->w_wincol + curwin->w_width - 1) 459 screen_putchar(' ', row, pum_col + 1, attr); 460 } 461 else 462 #endif 463 if (pum_col > 0) 464 screen_putchar(' ', row, pum_col - 1, attr); 465 466 // Display each entry, use two spaces for a Tab. 467 // Do this 3 times: For the main text, kind and extra info 468 col = pum_col; 469 totwidth = 0; 470 for (round = 1; round <= 3; ++round) 471 { 472 width = 0; 473 s = NULL; 474 switch (round) 475 { 476 case 1: p = pum_array[idx].pum_text; break; 477 case 2: p = pum_array[idx].pum_kind; break; 478 case 3: p = pum_array[idx].pum_extra; break; 479 } 480 if (p != NULL) 481 for ( ; ; MB_PTR_ADV(p)) 482 { 483 if (s == NULL) 484 s = p; 485 w = ptr2cells(p); 486 if (*p == NUL || *p == TAB || totwidth + w > pum_width) 487 { 488 // Display the text that fits or comes before a Tab. 489 // First convert it to printable characters. 490 char_u *st; 491 int saved = *p; 492 493 if (saved != NUL) 494 *p = NUL; 495 st = transstr(s); 496 if (saved != NUL) 497 *p = saved; 498 #ifdef FEAT_RIGHTLEFT 499 if (curwin->w_p_rl) 500 { 501 if (st != NULL) 502 { 503 char_u *rt = reverse_text(st); 504 505 if (rt != NULL) 506 { 507 char_u *rt_start = rt; 508 int size; 509 510 size = vim_strsize(rt); 511 if (size > pum_width) 512 { 513 do 514 { 515 size -= has_mbyte 516 ? (*mb_ptr2cells)(rt) : 1; 517 MB_PTR_ADV(rt); 518 } while (size > pum_width); 519 520 if (size < pum_width) 521 { 522 // Most left character requires 523 // 2-cells but only 1 cell is 524 // available on screen. Put a 525 // '<' on the left of the pum 526 // item 527 *(--rt) = '<'; 528 size++; 529 } 530 } 531 screen_puts_len(rt, (int)STRLEN(rt), 532 row, col - size + 1, attr); 533 vim_free(rt_start); 534 } 535 vim_free(st); 536 } 537 col -= width; 538 } 539 else 540 #endif 541 { 542 if (st != NULL) 543 { 544 screen_puts_len(st, (int)STRLEN(st), row, col, 545 attr); 546 vim_free(st); 547 } 548 col += width; 549 } 550 551 if (*p != TAB) 552 break; 553 554 // Display two spaces for a Tab. 555 #ifdef FEAT_RIGHTLEFT 556 if (curwin->w_p_rl) 557 { 558 screen_puts_len((char_u *)" ", 2, row, col - 1, 559 attr); 560 col -= 2; 561 } 562 else 563 #endif 564 { 565 screen_puts_len((char_u *)" ", 2, row, col, attr); 566 col += 2; 567 } 568 totwidth += 2; 569 s = NULL; // start text at next char 570 width = 0; 571 } 572 else 573 width += w; 574 } 575 576 if (round > 1) 577 n = pum_kind_width + 1; 578 else 579 n = 1; 580 581 // Stop when there is nothing more to display. 582 if (round == 3 583 || (round == 2 && pum_array[idx].pum_extra == NULL) 584 || (round == 1 && pum_array[idx].pum_kind == NULL 585 && pum_array[idx].pum_extra == NULL) 586 || pum_base_width + n >= pum_width) 587 break; 588 #ifdef FEAT_RIGHTLEFT 589 if (curwin->w_p_rl) 590 { 591 screen_fill(row, row + 1, pum_col - pum_base_width - n + 1, 592 col + 1, ' ', ' ', attr); 593 col = pum_col - pum_base_width - n + 1; 594 } 595 else 596 #endif 597 { 598 screen_fill(row, row + 1, col, pum_col + pum_base_width + n, 599 ' ', ' ', attr); 600 col = pum_col + pum_base_width + n; 601 } 602 totwidth = pum_base_width + n; 603 } 604 605 #ifdef FEAT_RIGHTLEFT 606 if (curwin->w_p_rl) 607 screen_fill(row, row + 1, pum_col - pum_width + 1, col + 1, ' ', 608 ' ', attr); 609 else 610 #endif 611 screen_fill(row, row + 1, col, pum_col + pum_width, ' ', ' ', 612 attr); 613 if (pum_scrollbar > 0) 614 { 615 #ifdef FEAT_RIGHTLEFT 616 if (curwin->w_p_rl) 617 screen_putchar(' ', row, pum_col - pum_width, 618 i >= thumb_pos && i < thumb_pos + thumb_height 619 ? attr_thumb : attr_scroll); 620 else 621 #endif 622 screen_putchar(' ', row, pum_col + pum_width, 623 i >= thumb_pos && i < thumb_pos + thumb_height 624 ? attr_thumb : attr_scroll); 625 } 626 627 ++row; 628 } 629 630 #ifdef FEAT_PROP_POPUP 631 screen_zindex = 0; 632 #endif 633 } 634 635 #if (defined(FEAT_PROP_POPUP) && defined(FEAT_QUICKFIX)) || defined(PROTO) 636 /* 637 * Position the info popup relative to the popup menu item. 638 */ 639 void 640 pum_position_info_popup(win_T *wp) 641 { 642 int col = pum_col + pum_width + pum_scrollbar + 1; 643 int row = pum_row; 644 int botpos = POPPOS_BOTLEFT; 645 646 wp->w_popup_pos = POPPOS_TOPLEFT; 647 if (Columns - col < 20 && Columns - col < pum_col) 648 { 649 col = pum_col - 1; 650 wp->w_popup_pos = POPPOS_TOPRIGHT; 651 botpos = POPPOS_BOTRIGHT; 652 wp->w_maxwidth = pum_col - 1; 653 } 654 else 655 wp->w_maxwidth = Columns - col + 1; 656 wp->w_maxwidth -= popup_extra_width(wp); 657 658 row -= popup_top_extra(wp); 659 if (wp->w_popup_flags & POPF_INFO_MENU) 660 { 661 if (pum_row < pum_win_row) 662 { 663 // menu above cursor line, align with bottom 664 row += pum_height; 665 wp->w_popup_pos = botpos; 666 } 667 else 668 // menu below cursor line, align with top 669 row += 1; 670 } 671 else 672 // align with the selected item 673 row += pum_selected - pum_first + 1; 674 675 wp->w_popup_flags &= ~POPF_HIDDEN; 676 if (wp->w_maxwidth < 10) 677 // The popup is not going to fit or will overlap with the cursor 678 // position, hide the popup. 679 wp->w_popup_flags |= POPF_HIDDEN; 680 else 681 popup_set_wantpos_rowcol(wp, row, col); 682 } 683 #endif 684 685 /* 686 * Set the index of the currently selected item. The menu will scroll when 687 * necessary. When "n" is out of range don't scroll. 688 * This may be repeated when the preview window is used: 689 * "repeat" == 0: open preview window normally 690 * "repeat" == 1: open preview window but don't set the size 691 * "repeat" == 2: don't open preview window 692 * Returns TRUE when the window was resized and the location of the popup menu 693 * must be recomputed. 694 */ 695 static int 696 pum_set_selected(int n, int repeat UNUSED) 697 { 698 int resized = FALSE; 699 int context = pum_height / 2; 700 #ifdef FEAT_QUICKFIX 701 int prev_selected = pum_selected; 702 #endif 703 #if defined(FEAT_PROP_POPUP) && defined(FEAT_QUICKFIX) 704 int has_info = FALSE; 705 #endif 706 707 pum_selected = n; 708 709 if (pum_selected >= 0 && pum_selected < pum_size) 710 { 711 if (pum_first > pum_selected - 4) 712 { 713 // scroll down; when we did a jump it's probably a PageUp then 714 // scroll a whole page 715 if (pum_first > pum_selected - 2) 716 { 717 pum_first -= pum_height - 2; 718 if (pum_first < 0) 719 pum_first = 0; 720 else if (pum_first > pum_selected) 721 pum_first = pum_selected; 722 } 723 else 724 pum_first = pum_selected; 725 } 726 else if (pum_first < pum_selected - pum_height + 5) 727 { 728 // scroll up; when we did a jump it's probably a PageDown then 729 // scroll a whole page 730 if (pum_first < pum_selected - pum_height + 1 + 2) 731 { 732 pum_first += pum_height - 2; 733 if (pum_first < pum_selected - pum_height + 1) 734 pum_first = pum_selected - pum_height + 1; 735 } 736 else 737 pum_first = pum_selected - pum_height + 1; 738 } 739 740 // Give a few lines of context when possible. 741 if (context > 3) 742 context = 3; 743 if (pum_height > 2) 744 { 745 if (pum_first > pum_selected - context) 746 { 747 // scroll down 748 pum_first = pum_selected - context; 749 if (pum_first < 0) 750 pum_first = 0; 751 } 752 else if (pum_first < pum_selected + context - pum_height + 1) 753 { 754 // scroll up 755 pum_first = pum_selected + context - pum_height + 1; 756 } 757 } 758 // adjust for the number of lines displayed 759 if (pum_first > pum_size - pum_height) 760 pum_first = pum_size - pum_height; 761 762 #if defined(FEAT_QUICKFIX) 763 /* 764 * Show extra info in the preview window if there is something and 765 * 'completeopt' contains "preview" or "popup" or "popuphidden". 766 * Skip this when tried twice already. 767 * Skip this also when there is not much room. 768 * NOTE: Be very careful not to sync undo! 769 */ 770 if (pum_array[pum_selected].pum_info != NULL 771 && Rows > 10 772 && repeat <= 1 773 && vim_strchr(p_cot, 'p') != NULL) 774 { 775 win_T *curwin_save = curwin; 776 tabpage_T *curtab_save = curtab; 777 int res = OK; 778 # ifdef FEAT_PROP_POPUP 779 use_popup_T use_popup; 780 # else 781 # define use_popup USEPOPUP_NONE 782 # endif 783 # ifdef FEAT_PROP_POPUP 784 has_info = TRUE; 785 if (strstr((char *)p_cot, "popuphidden") != NULL) 786 use_popup = USEPOPUP_HIDDEN; 787 else if (strstr((char *)p_cot, "popup") != NULL) 788 use_popup = USEPOPUP_NORMAL; 789 else 790 use_popup = USEPOPUP_NONE; 791 # endif 792 // Open a preview window and set "curwin" to it. 793 // 3 lines by default, prefer 'previewheight' if set and smaller. 794 g_do_tagpreview = 3; 795 if (p_pvh > 0 && p_pvh < g_do_tagpreview) 796 g_do_tagpreview = p_pvh; 797 ++RedrawingDisabled; 798 // Prevent undo sync here, if an autocommand syncs undo weird 799 // things can happen to the undo tree. 800 ++no_u_sync; 801 resized = prepare_tagpreview(FALSE, FALSE, use_popup); 802 --no_u_sync; 803 --RedrawingDisabled; 804 g_do_tagpreview = 0; 805 806 if (curwin->w_p_pvw 807 # ifdef FEAT_PROP_POPUP 808 || (curwin->w_popup_flags & POPF_INFO) 809 # endif 810 ) 811 { 812 if (!resized 813 && curbuf->b_nwindows == 1 814 && curbuf->b_fname == NULL 815 && bt_nofile(curbuf) 816 && curbuf->b_p_bh[0] == 'w') 817 { 818 // Already a "wipeout" buffer, make it empty. 819 while (!BUFEMPTY()) 820 ml_delete((linenr_T)1, FALSE); 821 } 822 else 823 { 824 // Don't want to sync undo in the current buffer. 825 ++no_u_sync; 826 res = do_ecmd(0, NULL, NULL, NULL, ECMD_ONE, 0, NULL); 827 --no_u_sync; 828 if (res == OK) 829 { 830 // Edit a new, empty buffer. Set options for a "wipeout" 831 // buffer. 832 set_option_value((char_u *)"swf", 0L, NULL, OPT_LOCAL); 833 set_option_value((char_u *)"bt", 0L, 834 (char_u *)"nofile", OPT_LOCAL); 835 set_option_value((char_u *)"bh", 0L, 836 (char_u *)"wipe", OPT_LOCAL); 837 set_option_value((char_u *)"diff", 0L, 838 NULL, OPT_LOCAL); 839 } 840 } 841 if (res == OK) 842 { 843 char_u *p, *e; 844 linenr_T lnum = 0; 845 846 for (p = pum_array[pum_selected].pum_info; *p != NUL; ) 847 { 848 e = vim_strchr(p, '\n'); 849 if (e == NULL) 850 { 851 ml_append(lnum++, p, 0, FALSE); 852 break; 853 } 854 else 855 { 856 *e = NUL; 857 ml_append(lnum++, p, (int)(e - p + 1), FALSE); 858 *e = '\n'; 859 p = e + 1; 860 } 861 } 862 // delete the empty last line 863 ml_delete(curbuf->b_ml.ml_line_count, FALSE); 864 865 // Increase the height of the preview window to show the 866 // text, but no more than 'previewheight' lines. 867 if (repeat == 0 && use_popup == USEPOPUP_NONE) 868 { 869 if (lnum > p_pvh) 870 lnum = p_pvh; 871 if (curwin->w_height < lnum) 872 { 873 win_setheight((int)lnum); 874 resized = TRUE; 875 } 876 } 877 878 curbuf->b_changed = 0; 879 curbuf->b_p_ma = FALSE; 880 if (pum_selected != prev_selected) 881 { 882 # ifdef FEAT_PROP_POPUP 883 curwin->w_firstline = 1; 884 # endif 885 curwin->w_topline = 1; 886 } 887 else if (curwin->w_topline > curbuf->b_ml.ml_line_count) 888 curwin->w_topline = curbuf->b_ml.ml_line_count; 889 curwin->w_cursor.lnum = curwin->w_topline; 890 curwin->w_cursor.col = 0; 891 # ifdef FEAT_PROP_POPUP 892 if (use_popup != USEPOPUP_NONE) 893 { 894 pum_position_info_popup(curwin); 895 if (win_valid(curwin_save)) 896 redraw_win_later(curwin_save, SOME_VALID); 897 } 898 # endif 899 if ((curwin != curwin_save && win_valid(curwin_save)) 900 || (curtab != curtab_save 901 && valid_tabpage(curtab_save))) 902 { 903 if (curtab != curtab_save && valid_tabpage(curtab_save)) 904 goto_tabpage_tp(curtab_save, FALSE, FALSE); 905 906 // When the first completion is done and the preview 907 // window is not resized, skip the preview window's 908 // status line redrawing. 909 if (ins_compl_active() && !resized) 910 curwin->w_redr_status = FALSE; 911 912 // Return cursor to where we were 913 validate_cursor(); 914 redraw_later(SOME_VALID); 915 916 // When the preview window was resized we need to 917 // update the view on the buffer. Only go back to 918 // the window when needed, otherwise it will always be 919 // redrawn. 920 if (resized && win_valid(curwin_save)) 921 { 922 ++no_u_sync; 923 win_enter(curwin_save, TRUE); 924 --no_u_sync; 925 update_topline(); 926 } 927 928 // Update the screen before drawing the popup menu. 929 // Enable updating the status lines. 930 pum_pretend_not_visible = TRUE; 931 // But don't draw text at the new popup menu position, 932 // it causes flicker. When resizing we need to draw 933 // anyway, the position may change later. 934 pum_will_redraw = !resized; 935 update_screen(0); 936 pum_pretend_not_visible = FALSE; 937 pum_will_redraw = FALSE; 938 939 if (!resized && win_valid(curwin_save)) 940 { 941 # ifdef FEAT_PROP_POPUP 942 win_T *wp = curwin; 943 # endif 944 ++no_u_sync; 945 win_enter(curwin_save, TRUE); 946 --no_u_sync; 947 # ifdef FEAT_PROP_POPUP 948 if (use_popup == USEPOPUP_HIDDEN && win_valid(wp)) 949 popup_hide(wp); 950 # endif 951 } 952 953 // May need to update the screen again when there are 954 // autocommands involved. 955 pum_pretend_not_visible = TRUE; 956 pum_will_redraw = !resized; 957 update_screen(0); 958 pum_pretend_not_visible = FALSE; 959 pum_will_redraw = FALSE; 960 call_update_screen = FALSE; 961 } 962 } 963 } 964 # if defined(FEAT_PROP_POPUP) && defined(FEAT_QUICKFIX) 965 if (WIN_IS_POPUP(curwin)) 966 // can't keep focus in a popup window 967 win_enter(firstwin, TRUE); 968 # endif 969 } 970 #endif 971 } 972 #if defined(FEAT_PROP_POPUP) && defined(FEAT_QUICKFIX) 973 if (!has_info) 974 // hide any popup info window 975 popup_hide_info(); 976 #endif 977 978 if (!resized) 979 pum_redraw(); 980 981 return resized; 982 } 983 984 /* 985 * Undisplay the popup menu (later). 986 */ 987 void 988 pum_undisplay(void) 989 { 990 pum_array = NULL; 991 redraw_all_later(NOT_VALID); 992 redraw_tabline = TRUE; 993 status_redraw_all(); 994 #if defined(FEAT_PROP_POPUP) && defined(FEAT_QUICKFIX) 995 // hide any popup info window 996 popup_hide_info(); 997 #endif 998 } 999 1000 /* 1001 * Clear the popup menu. Currently only resets the offset to the first 1002 * displayed item. 1003 */ 1004 void 1005 pum_clear(void) 1006 { 1007 pum_first = 0; 1008 } 1009 1010 /* 1011 * Return TRUE if the popup menu is displayed. Used to avoid some redrawing 1012 * that could overwrite it. Overruled when "pum_pretend_not_visible" is set, 1013 * used to redraw the status lines. 1014 */ 1015 int 1016 pum_visible(void) 1017 { 1018 return !pum_pretend_not_visible && pum_array != NULL; 1019 } 1020 1021 /* 1022 * Reposition the popup menu to adjust for window layout changes. 1023 */ 1024 void 1025 pum_may_redraw(void) 1026 { 1027 pumitem_T *array = pum_array; 1028 int len = pum_size; 1029 int selected = pum_selected; 1030 1031 if (!pum_visible() || pum_will_redraw) 1032 return; // nothing to do 1033 1034 if (pum_window != curwin 1035 || (pum_win_row == curwin->w_wrow + W_WINROW(curwin) 1036 && pum_win_height == curwin->w_height 1037 && pum_win_col == curwin->w_wincol 1038 && pum_win_width == curwin->w_width)) 1039 { 1040 // window position didn't change, redraw in the same position 1041 pum_redraw(); 1042 } 1043 else 1044 { 1045 int wcol = curwin->w_wcol; 1046 1047 // Window layout changed, recompute the position. 1048 // Use the remembered w_wcol value, the cursor may have moved when a 1049 // completion was inserted, but we want the menu in the same position. 1050 pum_undisplay(); 1051 curwin->w_wcol = pum_win_wcol; 1052 curwin->w_valid |= VALID_WCOL; 1053 pum_display(array, len, selected); 1054 curwin->w_wcol = wcol; 1055 } 1056 } 1057 1058 /* 1059 * Return the height of the popup menu, the number of entries visible. 1060 * Only valid when pum_visible() returns TRUE! 1061 */ 1062 int 1063 pum_get_height(void) 1064 { 1065 return pum_height; 1066 } 1067 1068 #if defined(FEAT_EVAL) || defined(PROTO) 1069 /* 1070 * Add size information about the pum to "dict". 1071 */ 1072 void 1073 pum_set_event_info(dict_T *dict) 1074 { 1075 if (!pum_visible()) 1076 return; 1077 (void)dict_add_number(dict, "height", pum_height); 1078 (void)dict_add_number(dict, "width", pum_width); 1079 (void)dict_add_number(dict, "row", pum_row); 1080 (void)dict_add_number(dict, "col", pum_col); 1081 (void)dict_add_number(dict, "size", pum_size); 1082 (void)dict_add_bool(dict, "scrollbar", 1083 pum_scrollbar ? VVAL_TRUE : VVAL_FALSE); 1084 } 1085 #endif 1086 1087 #if defined(FEAT_BEVAL_TERM) || defined(FEAT_TERM_POPUP_MENU) || defined(PROTO) 1088 static void 1089 pum_position_at_mouse(int min_width) 1090 { 1091 if (Rows - mouse_row > pum_size) 1092 { 1093 // Enough space below the mouse row. 1094 pum_row = mouse_row + 1; 1095 if (pum_height > Rows - pum_row) 1096 pum_height = Rows - pum_row; 1097 } 1098 else 1099 { 1100 // Show above the mouse row, reduce height if it does not fit. 1101 pum_row = mouse_row - pum_size; 1102 if (pum_row < 0) 1103 { 1104 pum_height += pum_row; 1105 pum_row = 0; 1106 } 1107 } 1108 if (Columns - mouse_col >= pum_base_width 1109 || Columns - mouse_col > min_width) 1110 // Enough space to show at mouse column. 1111 pum_col = mouse_col; 1112 else 1113 // Not enough space, right align with window. 1114 pum_col = Columns - (pum_base_width > min_width 1115 ? min_width : pum_base_width); 1116 1117 pum_width = Columns - pum_col; 1118 if (pum_width > pum_base_width + 1) 1119 pum_width = pum_base_width + 1; 1120 1121 // Do not redraw at cursor position. 1122 pum_window = NULL; 1123 } 1124 1125 #endif 1126 1127 #if defined(FEAT_BEVAL_TERM) || defined(PROTO) 1128 static pumitem_T *balloon_array = NULL; 1129 static int balloon_arraysize; 1130 1131 # define BALLOON_MIN_WIDTH 50 1132 # define BALLOON_MIN_HEIGHT 10 1133 1134 typedef struct { 1135 char_u *start; 1136 int bytelen; 1137 int cells; 1138 int indent; 1139 } balpart_T; 1140 1141 /* 1142 * Split a string into parts to display in the balloon. 1143 * Aimed at output from gdb. Attempts to split at white space, preserve quoted 1144 * strings and make a struct look good. 1145 * Resulting array is stored in "array" and returns the size of the array. 1146 */ 1147 int 1148 split_message(char_u *mesg, pumitem_T **array) 1149 { 1150 garray_T ga; 1151 char_u *p; 1152 balpart_T *item; 1153 int quoted = FALSE; 1154 int height; 1155 int line; 1156 int item_idx; 1157 int indent = 0; 1158 int max_cells = 0; 1159 int max_height = Rows / 2 - 1; 1160 int long_item_count = 0; 1161 int split_long_items = FALSE; 1162 1163 ga_init2(&ga, sizeof(balpart_T), 20); 1164 p = mesg; 1165 1166 while (*p != NUL) 1167 { 1168 if (ga_grow(&ga, 1) == FAIL) 1169 goto failed; 1170 item = ((balpart_T *)ga.ga_data) + ga.ga_len; 1171 item->start = p; 1172 item->indent = indent; 1173 item->cells = indent * 2; 1174 ++ga.ga_len; 1175 while (*p != NUL) 1176 { 1177 if (*p == '"') 1178 quoted = !quoted; 1179 else if (*p == '\n') 1180 break; 1181 else if (*p == '\\' && p[1] != NUL) 1182 ++p; 1183 else if (!quoted) 1184 { 1185 if ((*p == ',' && p[1] == ' ') || *p == '{' || *p == '}') 1186 { 1187 // Looks like a good point to break. 1188 if (*p == '{') 1189 ++indent; 1190 else if (*p == '}' && indent > 0) 1191 --indent; 1192 ++item->cells; 1193 p = skipwhite(p + 1); 1194 break; 1195 } 1196 } 1197 item->cells += ptr2cells(p); 1198 p += mb_ptr2len(p); 1199 } 1200 item->bytelen = p - item->start; 1201 if (*p == '\n') 1202 ++p; 1203 if (item->cells > max_cells) 1204 max_cells = item->cells; 1205 long_item_count += (item->cells - 1) / BALLOON_MIN_WIDTH; 1206 } 1207 1208 height = 2 + ga.ga_len; 1209 1210 // If there are long items and the height is below the limit: split lines 1211 if (long_item_count > 0 && height + long_item_count <= max_height) 1212 { 1213 split_long_items = TRUE; 1214 height += long_item_count; 1215 } 1216 1217 // Limit to half the window height, it has to fit above or below the mouse 1218 // position. 1219 if (height > max_height) 1220 height = max_height; 1221 *array = ALLOC_CLEAR_MULT(pumitem_T, height); 1222 if (*array == NULL) 1223 goto failed; 1224 1225 // Add an empty line above and below, looks better. 1226 (*array)->pum_text = vim_strsave((char_u *)""); 1227 (*array + height - 1)->pum_text = vim_strsave((char_u *)""); 1228 1229 for (line = 1, item_idx = 0; line < height - 1; ++item_idx) 1230 { 1231 int skip; 1232 int thislen; 1233 int copylen; 1234 int ind; 1235 int cells; 1236 1237 item = ((balpart_T *)ga.ga_data) + item_idx; 1238 if (item->bytelen == 0) 1239 (*array)[line++].pum_text = vim_strsave((char_u *)""); 1240 else 1241 for (skip = 0; skip < item->bytelen; skip += thislen) 1242 { 1243 if (split_long_items && item->cells >= BALLOON_MIN_WIDTH) 1244 { 1245 cells = item->indent * 2; 1246 for (p = item->start + skip; 1247 p < item->start + item->bytelen; 1248 p += mb_ptr2len(p)) 1249 if ((cells += ptr2cells(p)) > BALLOON_MIN_WIDTH) 1250 break; 1251 thislen = p - (item->start + skip); 1252 } 1253 else 1254 thislen = item->bytelen; 1255 1256 // put indent at the start 1257 p = alloc(thislen + item->indent * 2 + 1); 1258 if (p == NULL) 1259 { 1260 for (line = 0; line <= height - 1; ++line) 1261 vim_free((*array)[line].pum_text); 1262 vim_free(*array); 1263 goto failed; 1264 } 1265 for (ind = 0; ind < item->indent * 2; ++ind) 1266 p[ind] = ' '; 1267 1268 // exclude spaces at the end of the string 1269 for (copylen = thislen; copylen > 0; --copylen) 1270 if (item->start[skip + copylen - 1] != ' ') 1271 break; 1272 1273 vim_strncpy(p + ind, item->start + skip, copylen); 1274 (*array)[line].pum_text = p; 1275 item->indent = 0; // wrapped line has no indent 1276 ++line; 1277 } 1278 } 1279 ga_clear(&ga); 1280 return height; 1281 1282 failed: 1283 ga_clear(&ga); 1284 return 0; 1285 } 1286 1287 void 1288 ui_remove_balloon(void) 1289 { 1290 if (balloon_array != NULL) 1291 { 1292 pum_undisplay(); 1293 while (balloon_arraysize > 0) 1294 vim_free(balloon_array[--balloon_arraysize].pum_text); 1295 VIM_CLEAR(balloon_array); 1296 } 1297 } 1298 1299 /* 1300 * Terminal version of a balloon, uses the popup menu code. 1301 */ 1302 void 1303 ui_post_balloon(char_u *mesg, list_T *list) 1304 { 1305 ui_remove_balloon(); 1306 1307 if (mesg == NULL && list == NULL) 1308 { 1309 pum_undisplay(); 1310 return; 1311 } 1312 if (list != NULL) 1313 { 1314 listitem_T *li; 1315 int idx; 1316 1317 balloon_arraysize = list->lv_len; 1318 balloon_array = ALLOC_CLEAR_MULT(pumitem_T, list->lv_len); 1319 if (balloon_array == NULL) 1320 return; 1321 CHECK_LIST_MATERIALIZE(list); 1322 for (idx = 0, li = list->lv_first; li != NULL; li = li->li_next, ++idx) 1323 { 1324 char_u *text = tv_get_string_chk(&li->li_tv); 1325 1326 balloon_array[idx].pum_text = vim_strsave( 1327 text == NULL ? (char_u *)"" : text); 1328 } 1329 } 1330 else 1331 balloon_arraysize = split_message(mesg, &balloon_array); 1332 1333 if (balloon_arraysize > 0) 1334 { 1335 pum_array = balloon_array; 1336 pum_size = balloon_arraysize; 1337 pum_compute_size(); 1338 pum_scrollbar = 0; 1339 pum_height = balloon_arraysize; 1340 1341 pum_position_at_mouse(BALLOON_MIN_WIDTH); 1342 pum_selected = -1; 1343 pum_first = 0; 1344 pum_redraw(); 1345 } 1346 } 1347 1348 /* 1349 * Called when the mouse moved, may remove any displayed balloon. 1350 */ 1351 void 1352 ui_may_remove_balloon(void) 1353 { 1354 // For now: remove the balloon whenever the mouse moves to another screen 1355 // cell. 1356 ui_remove_balloon(); 1357 } 1358 #endif 1359 1360 #if defined(FEAT_TERM_POPUP_MENU) || defined(PROTO) 1361 /* 1362 * Select the pum entry at the mouse position. 1363 */ 1364 static void 1365 pum_select_mouse_pos(void) 1366 { 1367 int idx = mouse_row - pum_row; 1368 1369 if (idx < 0 || idx >= pum_size) 1370 pum_selected = -1; 1371 else if (*pum_array[idx].pum_text != NUL) 1372 pum_selected = idx; 1373 } 1374 1375 /* 1376 * Execute the currently selected popup menu item. 1377 */ 1378 static void 1379 pum_execute_menu(vimmenu_T *menu, int mode) 1380 { 1381 vimmenu_T *mp; 1382 int idx = 0; 1383 exarg_T ea; 1384 1385 FOR_ALL_CHILD_MENUS(menu, mp) 1386 if ((mp->modes & mp->enabled & mode) && idx++ == pum_selected) 1387 { 1388 CLEAR_FIELD(ea); 1389 execute_menu(&ea, mp, -1); 1390 break; 1391 } 1392 } 1393 1394 /* 1395 * Open the terminal version of the popup menu and don't return until it is 1396 * closed. 1397 */ 1398 void 1399 pum_show_popupmenu(vimmenu_T *menu) 1400 { 1401 vimmenu_T *mp; 1402 int idx = 0; 1403 pumitem_T *array; 1404 # ifdef FEAT_BEVAL_TERM 1405 int save_bevalterm = p_bevalterm; 1406 # endif 1407 int mode; 1408 1409 pum_undisplay(); 1410 pum_size = 0; 1411 mode = get_menu_mode_flag(); 1412 1413 FOR_ALL_CHILD_MENUS(menu, mp) 1414 if (menu_is_separator(mp->dname) 1415 || (mp->modes & mp->enabled & mode)) 1416 ++pum_size; 1417 1418 // When there are only Terminal mode menus, using "popup Edit" results in 1419 // pum_size being zero. 1420 if (pum_size <= 0) 1421 { 1422 emsg(e_menuothermode); 1423 return; 1424 } 1425 1426 array = ALLOC_CLEAR_MULT(pumitem_T, pum_size); 1427 if (array == NULL) 1428 return; 1429 1430 FOR_ALL_CHILD_MENUS(menu, mp) 1431 if (menu_is_separator(mp->dname)) 1432 array[idx++].pum_text = (char_u *)""; 1433 else if (mp->modes & mp->enabled & mode) 1434 array[idx++].pum_text = mp->dname; 1435 1436 pum_array = array; 1437 pum_compute_size(); 1438 pum_scrollbar = 0; 1439 pum_height = pum_size; 1440 pum_position_at_mouse(20); 1441 1442 pum_selected = -1; 1443 pum_first = 0; 1444 # ifdef FEAT_BEVAL_TERM 1445 p_bevalterm = TRUE; // track mouse movement 1446 mch_setmouse(TRUE); 1447 # endif 1448 1449 for (;;) 1450 { 1451 int c; 1452 1453 pum_redraw(); 1454 setcursor_mayforce(TRUE); 1455 out_flush(); 1456 1457 c = vgetc(); 1458 1459 // Bail out when typing Esc, CTRL-C or some callback closed the popup 1460 // menu. 1461 if (c == ESC || c == Ctrl_C || pum_array == NULL) 1462 break; 1463 else if (c == CAR || c == NL) 1464 { 1465 // enter: select current item, if any, and close 1466 pum_execute_menu(menu, mode); 1467 break; 1468 } 1469 else if (c == 'k' || c == K_UP || c == K_MOUSEUP) 1470 { 1471 // cursor up: select previous item 1472 while (pum_selected > 0) 1473 { 1474 --pum_selected; 1475 if (*array[pum_selected].pum_text != NUL) 1476 break; 1477 } 1478 } 1479 else if (c == 'j' || c == K_DOWN || c == K_MOUSEDOWN) 1480 { 1481 // cursor down: select next item 1482 while (pum_selected < pum_size - 1) 1483 { 1484 ++pum_selected; 1485 if (*array[pum_selected].pum_text != NUL) 1486 break; 1487 } 1488 } 1489 else if (c == K_RIGHTMOUSE) 1490 { 1491 // Right mouse down: reposition the menu. 1492 vungetc(c); 1493 break; 1494 } 1495 else if (c == K_LEFTDRAG || c == K_RIGHTDRAG || c == K_MOUSEMOVE) 1496 { 1497 // mouse moved: select item in the mouse row 1498 pum_select_mouse_pos(); 1499 } 1500 else if (c == K_LEFTMOUSE || c == K_LEFTMOUSE_NM || c == K_RIGHTRELEASE) 1501 { 1502 // left mouse click: select clicked item, if any, and close; 1503 // right mouse release: select clicked item, close if any 1504 pum_select_mouse_pos(); 1505 if (pum_selected >= 0) 1506 { 1507 pum_execute_menu(menu, mode); 1508 break; 1509 } 1510 if (c == K_LEFTMOUSE || c == K_LEFTMOUSE_NM) 1511 break; 1512 } 1513 } 1514 1515 vim_free(array); 1516 pum_undisplay(); 1517 # ifdef FEAT_BEVAL_TERM 1518 p_bevalterm = save_bevalterm; 1519 mch_setmouse(TRUE); 1520 # endif 1521 } 1522 1523 void 1524 pum_make_popup(char_u *path_name, int use_mouse_pos) 1525 { 1526 vimmenu_T *menu; 1527 1528 if (!use_mouse_pos) 1529 { 1530 // Hack: set mouse position at the cursor so that the menu pops up 1531 // around there. 1532 mouse_row = curwin->w_winrow + curwin->w_wrow; 1533 mouse_col = curwin->w_wincol + curwin->w_wcol; 1534 } 1535 1536 menu = gui_find_menu(path_name); 1537 if (menu != NULL) 1538 pum_show_popupmenu(menu); 1539 } 1540 #endif 1541