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/// @file popupmnu.c
5///
6/// Popup menu (PUM)
7
8#include <assert.h>
9#include <inttypes.h>
10#include <stdbool.h>
11
12#include "nvim/vim.h"
13#include "nvim/api/private/helpers.h"
14#include "nvim/ascii.h"
15#include "nvim/eval/typval.h"
16#include "nvim/popupmnu.h"
17#include "nvim/charset.h"
18#include "nvim/ex_cmds.h"
19#include "nvim/memline.h"
20#include "nvim/move.h"
21#include "nvim/option.h"
22#include "nvim/screen.h"
23#include "nvim/ui_compositor.h"
24#include "nvim/search.h"
25#include "nvim/strings.h"
26#include "nvim/memory.h"
27#include "nvim/window.h"
28#include "nvim/edit.h"
29#include "nvim/ui.h"
30
31static pumitem_T *pum_array = NULL; // items of displayed pum
32static int pum_size; // nr of items in "pum_array"
33static int pum_selected; // index of selected item or -1
34static int pum_first = 0; // index of top item
35
36static int pum_height; // nr of displayed pum items
37static int pum_width; // width of displayed pum items
38static int pum_base_width; // width of pum items base
39static int pum_kind_width; // width of pum items kind column
40static int pum_extra_width; // width of extra stuff
41static int pum_scrollbar; // TRUE when scrollbar present
42
43static int pum_anchor_grid; // grid where position is defined
44static int pum_row; // top row of pum
45static int pum_col; // left column of pum
46static bool pum_above; // pum is drawn above cursor line
47
48static bool pum_is_visible = false;
49static bool pum_is_drawn = false;
50static bool pum_external = false;
51static bool pum_invalid = false; // the screen was just cleared
52
53#ifdef INCLUDE_GENERATED_DECLARATIONS
54# include "popupmnu.c.generated.h"
55#endif
56#define PUM_DEF_HEIGHT 10
57#define PUM_DEF_WIDTH 15
58
59static void pum_compute_size(void)
60{
61 // Compute the width of the widest match and the widest extra.
62 pum_base_width = 0;
63 pum_kind_width = 0;
64 pum_extra_width = 0;
65 for (int i = 0; i < pum_size; i++) {
66 int w = vim_strsize(pum_array[i].pum_text);
67 if (pum_base_width < w) {
68 pum_base_width = w;
69 }
70 if (pum_array[i].pum_kind != NULL) {
71 w = vim_strsize(pum_array[i].pum_kind) + 1;
72 if (pum_kind_width < w) {
73 pum_kind_width = w;
74 }
75 }
76 if (pum_array[i].pum_extra != NULL) {
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 screen column "col".
88/// The menu appears above the screen line "row" or at "row" + "height" - 1.
89///
90/// @param array
91/// @param size
92/// @param selected index of initially selected item, none if out of range
93/// @param array_changed if true, array contains different items since last call
94/// if false, a new item is selected, but the array
95/// is the same
96/// @param cmd_startcol only for cmdline mode: column of completed match
97void pum_display(pumitem_T *array, int size, int selected, bool array_changed,
98 int cmd_startcol)
99{
100 int def_width;
101 int context_lines;
102 int above_row;
103 int below_row;
104 int redo_count = 0;
105 int row;
106 int col;
107
108 if (!pum_is_visible) {
109 // To keep the code simple, we only allow changing the
110 // draw mode when the popup menu is not being displayed
111 pum_external = ui_has(kUIPopupmenu)
112 || (State == CMDLINE && ui_has(kUIWildmenu));
113 }
114
115 do {
116 // Mark the pum as visible already here,
117 // to avoid that must_redraw is set when 'cursorcolumn' is on.
118 pum_is_visible = true;
119 pum_is_drawn = true;
120 validate_cursor_col();
121 above_row = 0;
122 below_row = cmdline_row;
123
124 // wildoptions=pum
125 if (State == CMDLINE) {
126 row = ui_has(kUICmdline) ? 0 : cmdline_row;
127 col = cmd_startcol;
128 pum_anchor_grid = ui_has(kUICmdline) ? -1 : DEFAULT_GRID_HANDLE;
129 } else {
130 // anchor position: the start of the completed word
131 row = curwin->w_wrow;
132 if (curwin->w_p_rl) {
133 col = curwin->w_width - curwin->w_wcol - 1;
134 } else {
135 col = curwin->w_wcol;
136 }
137
138 pum_anchor_grid = (int)curwin->w_grid.handle;
139 if (!ui_has(kUIMultigrid)) {
140 pum_anchor_grid = (int)default_grid.handle;
141 row += curwin->w_winrow;
142 col += curwin->w_wincol;
143 }
144 }
145
146 if (pum_external) {
147 if (array_changed) {
148 Array arr = ARRAY_DICT_INIT;
149 for (int i = 0; i < size; i++) {
150 Array item = ARRAY_DICT_INIT;
151 ADD(item, STRING_OBJ(cstr_to_string((char *)array[i].pum_text)));
152 ADD(item, STRING_OBJ(cstr_to_string((char *)array[i].pum_kind)));
153 ADD(item, STRING_OBJ(cstr_to_string((char *)array[i].pum_extra)));
154 ADD(item, STRING_OBJ(cstr_to_string((char *)array[i].pum_info)));
155 ADD(arr, ARRAY_OBJ(item));
156 }
157 ui_call_popupmenu_show(arr, selected, row, col, pum_anchor_grid);
158 } else {
159 ui_call_popupmenu_select(selected);
160 return;
161 }
162 }
163
164 def_width = PUM_DEF_WIDTH;
165
166 win_T *pvwin = NULL;
167 FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
168 if (wp->w_p_pvw) {
169 pvwin = wp;
170 break;
171 }
172 }
173
174 if (pvwin != NULL) {
175 if (pvwin->w_winrow < curwin->w_winrow) {
176 above_row = pvwin->w_winrow + pvwin->w_height;
177 } else if (pvwin->w_winrow > curwin->w_winrow + curwin->w_height) {
178 below_row = pvwin->w_winrow;
179 }
180 }
181
182 // Figure out the size and position of the pum.
183 if (size < PUM_DEF_HEIGHT) {
184 pum_height = size;
185 } else {
186 pum_height = PUM_DEF_HEIGHT;
187 }
188
189 if ((p_ph > 0) && (pum_height > p_ph)) {
190 pum_height = (int)p_ph;
191 }
192
193 // Put the pum below "row" if possible. If there are few lines decide on
194 // where there is more room.
195 if (row + 2 >= below_row - pum_height
196 && row - above_row > (below_row - above_row) / 2) {
197 // pum above "row"
198 pum_above = true;
199
200 // Leave two lines of context if possible
201 if (curwin->w_wrow - curwin->w_cline_row >= 2) {
202 context_lines = 2;
203 } else {
204 context_lines = curwin->w_wrow - curwin->w_cline_row;
205 }
206
207 if (row >= size + context_lines) {
208 pum_row = row - size - context_lines;
209 pum_height = size;
210 } else {
211 pum_row = 0;
212 pum_height = row - context_lines;
213 }
214
215 if ((p_ph > 0) && (pum_height > p_ph)) {
216 pum_row += pum_height - (int)p_ph;
217 pum_height = (int)p_ph;
218 }
219 } else {
220 // pum below "row"
221 pum_above = false;
222
223 // Leave two lines of context if possible
224 if (curwin->w_cline_row + curwin->w_cline_height - curwin->w_wrow >= 3) {
225 context_lines = 3;
226 } else {
227 context_lines = curwin->w_cline_row
228 + curwin->w_cline_height - curwin->w_wrow;
229 }
230
231 pum_row = row + context_lines;
232 if (size > below_row - pum_row) {
233 pum_height = below_row - pum_row;
234 } else {
235 pum_height = size;
236 }
237
238 if ((p_ph > 0) && (pum_height > p_ph)) {
239 pum_height = (int)p_ph;
240 }
241 }
242
243 // don't display when we only have room for one line
244 if ((pum_height < 1) || ((pum_height == 1) && (size > 1))) {
245 return;
246 }
247
248 // If there is a preview window above, avoid drawing over it.
249 // Do keep at least 10 entries.
250 if (pvwin != NULL && pum_row < above_row && pum_height > 10) {
251 if (row - above_row < 10) {
252 pum_row = row - 10;
253 pum_height = 10;
254 } else {
255 pum_row = above_row;
256 pum_height = row - above_row;
257 }
258 }
259 if (pum_external) {
260 return;
261 }
262
263 pum_array = array;
264 pum_size = size;
265 pum_compute_size();
266 int max_width = pum_base_width;
267
268 // if there are more items than room we need a scrollbar
269 if (pum_height < size) {
270 pum_scrollbar = 1;
271 max_width++;
272 } else {
273 pum_scrollbar = 0;
274 }
275
276 if (def_width < max_width) {
277 def_width = max_width;
278 }
279
280 if ((((col < Columns - PUM_DEF_WIDTH) || (col < Columns - max_width))
281 && !curwin->w_p_rl)
282 || (curwin->w_p_rl && ((col > PUM_DEF_WIDTH) || (col > max_width)))) {
283 // align pum column with "col"
284 pum_col = col;
285 if (curwin->w_p_rl) {
286 pum_width = pum_col - pum_scrollbar + 1;
287 } else {
288 assert(Columns - pum_col - pum_scrollbar >= INT_MIN
289 && Columns - pum_col - pum_scrollbar <= INT_MAX);
290 pum_width = (int)(Columns - pum_col - pum_scrollbar);
291 }
292
293 if ((pum_width > max_width + pum_kind_width + pum_extra_width + 1)
294 && (pum_width > PUM_DEF_WIDTH)) {
295 pum_width = max_width + pum_kind_width + pum_extra_width + 1;
296
297 if (pum_width < PUM_DEF_WIDTH) {
298 pum_width = PUM_DEF_WIDTH;
299 }
300 }
301 } else if (Columns < def_width) {
302 // not enough room, will use what we have
303 if (curwin->w_p_rl) {
304 assert(Columns - 1 >= INT_MIN);
305 pum_col = (int)(Columns - 1);
306 } else {
307 pum_col = 0;
308 }
309 assert(Columns - 1 >= INT_MIN);
310 pum_width = (int)(Columns - 1);
311 } else {
312 if (max_width > PUM_DEF_WIDTH) {
313 // truncate
314 max_width = PUM_DEF_WIDTH;
315 }
316
317 if (curwin->w_p_rl) {
318 pum_col = max_width - 1;
319 } else {
320 assert(Columns - max_width >= INT_MIN
321 && Columns - max_width <= INT_MAX);
322 pum_col = (int)(Columns - max_width);
323 }
324 pum_width = max_width - pum_scrollbar;
325 }
326
327 // Set selected item and redraw. If the window size changed need to redo
328 // the positioning. Limit this to two times, when there is not much
329 // room the window size will keep changing.
330 } while (pum_set_selected(selected, redo_count) && (++redo_count <= 2));
331}
332
333/// Redraw the popup menu, using "pum_first" and "pum_selected".
334void pum_redraw(void)
335{
336 int row = 0;
337 int col;
338 int attr_norm = win_hl_attr(curwin, HLF_PNI);
339 int attr_select = win_hl_attr(curwin, HLF_PSI);
340 int attr_scroll = win_hl_attr(curwin, HLF_PSB);
341 int attr_thumb = win_hl_attr(curwin, HLF_PST);
342 int attr;
343 int i;
344 int idx;
345 char_u *s;
346 char_u *p = NULL;
347 int totwidth, width, w;
348 int thumb_pos = 0;
349 int thumb_heigth = 1;
350 int round;
351 int n;
352
353 int grid_width = pum_width;
354 int col_off = 0;
355 bool extra_space = false;
356 if (curwin->w_p_rl) {
357 col_off = pum_width;
358 if (pum_col < curwin->w_wincol + curwin->w_width - 1) {
359 grid_width += 1;
360 extra_space = true;
361 }
362 } else if (pum_col > 0) {
363 grid_width += 1;
364 col_off = 1;
365 extra_space = true;
366 }
367 if (pum_scrollbar > 0) {
368 grid_width++;
369 }
370
371 grid_assign_handle(&pum_grid);
372 bool moved = ui_comp_put_grid(&pum_grid, pum_row, pum_col-col_off,
373 pum_height, grid_width, false, true);
374 bool invalid_grid = moved || pum_invalid;
375 pum_invalid = false;
376 must_redraw_pum = false;
377
378 if (!pum_grid.chars
379 || pum_grid.Rows != pum_height || pum_grid.Columns != grid_width) {
380 grid_alloc(&pum_grid, pum_height, grid_width, !invalid_grid, false);
381 ui_call_grid_resize(pum_grid.handle, pum_grid.Columns, pum_grid.Rows);
382 } else if (invalid_grid) {
383 grid_invalidate(&pum_grid);
384 }
385 if (ui_has(kUIMultigrid)) {
386 const char *anchor = pum_above ? "SW" : "NW";
387 int row_off = pum_above ? pum_height : 0;
388 ui_call_win_float_pos(pum_grid.handle, -1, cstr_to_string(anchor),
389 pum_anchor_grid, pum_row-row_off, pum_col-col_off,
390 false);
391 }
392
393
394 // Never display more than we have
395 if (pum_first > pum_size - pum_height) {
396 pum_first = pum_size - pum_height;
397 }
398
399 if (pum_scrollbar) {
400 thumb_heigth = pum_height * pum_height / pum_size;
401 if (thumb_heigth == 0) {
402 thumb_heigth = 1;
403 }
404 thumb_pos = (pum_first * (pum_height - thumb_heigth)
405 + (pum_size - pum_height) / 2)
406 / (pum_size - pum_height);
407 }
408
409 for (i = 0; i < pum_height; ++i) {
410 idx = i + pum_first;
411 attr = (idx == pum_selected) ? attr_select : attr_norm;
412
413 grid_puts_line_start(&pum_grid, row);
414
415 // prepend a space if there is room
416 if (extra_space) {
417 if (curwin->w_p_rl) {
418 grid_putchar(&pum_grid, ' ', row, col_off + 1, attr);
419 } else {
420 grid_putchar(&pum_grid, ' ', row, col_off - 1, attr);
421 }
422 }
423
424 // Display each entry, use two spaces for a Tab.
425 // Do this 3 times: For the main text, kind and extra info
426 col = col_off;
427 totwidth = 0;
428
429 for (round = 1; round <= 3; ++round) {
430 width = 0;
431 s = NULL;
432
433 switch (round) {
434 case 1:
435 p = pum_array[idx].pum_text;
436 break;
437
438 case 2:
439 p = pum_array[idx].pum_kind;
440 break;
441
442 case 3:
443 p = pum_array[idx].pum_extra;
444 break;
445 }
446
447 if (p != NULL) {
448 for (;; MB_PTR_ADV(p)) {
449 if (s == NULL) {
450 s = p;
451 }
452 w = ptr2cells(p);
453
454 if ((*p == NUL) || (*p == TAB) || (totwidth + w > pum_width)) {
455 // Display the text that fits or comes before a Tab.
456 // First convert it to printable characters.
457 char_u *st;
458 char_u saved = *p;
459
460 *p = NUL;
461 st = (char_u *)transstr((const char *)s);
462 *p = saved;
463
464 if (curwin->w_p_rl) {
465 char_u *rt = reverse_text(st);
466 char_u *rt_start = rt;
467 int size = vim_strsize(rt);
468
469 if (size > pum_width) {
470 do {
471 size -= utf_ptr2cells(rt);
472 MB_PTR_ADV(rt);
473 } while (size > pum_width);
474
475 if (size < pum_width) {
476 // Most left character requires 2-cells but only 1 cell
477 // is available on screen. Put a '<' on the left of the
478 // pum item
479 *(--rt) = '<';
480 size++;
481 }
482 }
483 grid_puts_len(&pum_grid, rt, (int)STRLEN(rt), row,
484 col - size + 1, attr);
485 xfree(rt_start);
486 xfree(st);
487 col -= width;
488 } else {
489 grid_puts_len(&pum_grid, st, (int)STRLEN(st), row, col, attr);
490 xfree(st);
491 col += width;
492 }
493
494 if (*p != TAB) {
495 break;
496 }
497
498 // Display two spaces for a Tab.
499 if (curwin->w_p_rl) {
500 grid_puts_len(&pum_grid, (char_u *)" ", 2, row, col - 1,
501 attr);
502 col -= 2;
503 } else {
504 grid_puts_len(&pum_grid, (char_u *)" ", 2, row, col, attr);
505 col += 2;
506 }
507 totwidth += 2;
508 // start text at next char
509 s = NULL;
510 width = 0;
511 } else {
512 width += w;
513 }
514 }
515 }
516
517 if (round > 1) {
518 n = pum_kind_width + 1;
519 } else {
520 n = 1;
521 }
522
523 // Stop when there is nothing more to display.
524 if ((round == 3)
525 || ((round == 2)
526 && (pum_array[idx].pum_extra == NULL))
527 || ((round == 1)
528 && (pum_array[idx].pum_kind == NULL)
529 && (pum_array[idx].pum_extra == NULL))
530 || (pum_base_width + n >= pum_width)) {
531 break;
532 }
533
534 if (curwin->w_p_rl) {
535 grid_fill(&pum_grid, row, row + 1, col_off - pum_base_width - n + 1,
536 col + 1, ' ', ' ', attr);
537 col = col_off - pum_base_width - n + 1;
538 } else {
539 grid_fill(&pum_grid, row, row + 1, col,
540 col_off + pum_base_width + n, ' ', ' ', attr);
541 col = col_off + pum_base_width + n;
542 }
543 totwidth = pum_base_width + n;
544 }
545
546 if (curwin->w_p_rl) {
547 grid_fill(&pum_grid, row, row + 1, col_off - pum_width + 1, col + 1,
548 ' ', ' ', attr);
549 } else {
550 grid_fill(&pum_grid, row, row + 1, col, col_off + pum_width, ' ', ' ',
551 attr);
552 }
553
554 if (pum_scrollbar > 0) {
555 if (curwin->w_p_rl) {
556 grid_putchar(&pum_grid, ' ', row, col_off - pum_width,
557 i >= thumb_pos && i < thumb_pos + thumb_heigth
558 ? attr_thumb : attr_scroll);
559 } else {
560 grid_putchar(&pum_grid, ' ', row, col_off + pum_width,
561 i >= thumb_pos && i < thumb_pos + thumb_heigth
562 ? attr_thumb : attr_scroll);
563 }
564 }
565 grid_puts_line_flush(false);
566 row++;
567 }
568}
569
570/// Set the index of the currently selected item. The menu will scroll when
571/// necessary. When "n" is out of range don't scroll.
572/// This may be repeated when the preview window is used:
573/// "repeat" == 0: open preview window normally
574/// "repeat" == 1: open preview window but don't set the size
575/// "repeat" == 2: don't open preview window
576///
577/// @param n
578/// @param repeat
579///
580/// @returns TRUE when the window was resized and the location of the popup
581/// menu must be recomputed.
582static int pum_set_selected(int n, int repeat)
583{
584 int resized = FALSE;
585 int context = pum_height / 2;
586
587 pum_selected = n;
588
589 if ((pum_selected >= 0) && (pum_selected < pum_size)) {
590 if (pum_first > pum_selected - 4) {
591 // scroll down; when we did a jump it's probably a PageUp then
592 // scroll a whole page
593 if (pum_first > pum_selected - 2) {
594 pum_first -= pum_height - 2;
595 if (pum_first < 0) {
596 pum_first = 0;
597 } else if (pum_first > pum_selected) {
598 pum_first = pum_selected;
599 }
600 } else {
601 pum_first = pum_selected;
602 }
603 } else if (pum_first < pum_selected - pum_height + 5) {
604 // scroll up; when we did a jump it's probably a PageDown then
605 // scroll a whole page
606 if (pum_first < pum_selected - pum_height + 1 + 2) {
607 pum_first += pum_height - 2;
608 if (pum_first < pum_selected - pum_height + 1) {
609 pum_first = pum_selected - pum_height + 1;
610 }
611 } else {
612 pum_first = pum_selected - pum_height + 1;
613 }
614 }
615
616 // Give a few lines of context when possible.
617 if (context > 3) {
618 context = 3;
619 }
620
621 if (pum_height > 2) {
622 if (pum_first > pum_selected - context) {
623 // scroll down
624 pum_first = pum_selected - context;
625
626 if (pum_first < 0) {
627 pum_first = 0;
628 }
629 } else if (pum_first < pum_selected + context - pum_height + 1) {
630 // scroll up
631 pum_first = pum_selected + context - pum_height + 1;
632 }
633 }
634
635 // Show extra info in the preview window if there is something and
636 // 'completeopt' contains "preview".
637 // Skip this when tried twice already.
638 // Skip this also when there is not much room.
639 // NOTE: Be very careful not to sync undo!
640 if ((pum_array[pum_selected].pum_info != NULL)
641 && (Rows > 10)
642 && (repeat <= 1)
643 && (vim_strchr(p_cot, 'p') != NULL)) {
644 win_T *curwin_save = curwin;
645 tabpage_T *curtab_save = curtab;
646 int res = OK;
647
648 // Open a preview window. 3 lines by default. Prefer
649 // 'previewheight' if set and smaller.
650 g_do_tagpreview = 3;
651
652 if ((p_pvh > 0) && (p_pvh < g_do_tagpreview)) {
653 g_do_tagpreview = (int)p_pvh;
654 }
655 RedrawingDisabled++;
656 // Prevent undo sync here, if an autocommand syncs undo weird
657 // things can happen to the undo tree.
658 no_u_sync++;
659 resized = prepare_tagpreview(false);
660 no_u_sync--;
661 RedrawingDisabled--;
662 g_do_tagpreview = 0;
663
664 if (curwin->w_p_pvw) {
665 if (!resized
666 && (curbuf->b_nwindows == 1)
667 && (curbuf->b_fname == NULL)
668 && (curbuf->b_p_bt[0] == 'n')
669 && (curbuf->b_p_bt[2] == 'f')
670 && (curbuf->b_p_bh[0] == 'w')) {
671 // Already a "wipeout" buffer, make it empty.
672 while (!BUFEMPTY()) {
673 ml_delete((linenr_T)1, false);
674 }
675 } else {
676 // Don't want to sync undo in the current buffer.
677 no_u_sync++;
678 res = do_ecmd(0, NULL, NULL, NULL, ECMD_ONE, 0, NULL);
679 no_u_sync--;
680
681 if (res == OK) {
682 // Edit a new, empty buffer. Set options for a "wipeout"
683 // buffer.
684 set_option_value("swf", 0L, NULL, OPT_LOCAL);
685 set_option_value("bt", 0L, "nofile", OPT_LOCAL);
686 set_option_value("bh", 0L, "wipe", OPT_LOCAL);
687 set_option_value("diff", 0L, NULL, OPT_LOCAL);
688 }
689 }
690
691 if (res == OK) {
692 char_u *p, *e;
693 linenr_T lnum = 0;
694
695 for (p = pum_array[pum_selected].pum_info; *p != NUL;) {
696 e = vim_strchr(p, '\n');
697 if (e == NULL) {
698 ml_append(lnum++, p, 0, false);
699 break;
700 } else {
701 *e = NUL;
702 ml_append(lnum++, p, (int)(e - p + 1), false);
703 *e = '\n';
704 p = e + 1;
705 }
706 }
707
708 // Increase the height of the preview window to show the
709 // text, but no more than 'previewheight' lines.
710 if (repeat == 0) {
711 if (lnum > p_pvh) {
712 lnum = p_pvh;
713 }
714
715 if (curwin->w_height < lnum) {
716 win_setheight((int)lnum);
717 resized = TRUE;
718 }
719 }
720
721 curbuf->b_changed = false;
722 curbuf->b_p_ma = FALSE;
723 curwin->w_cursor.lnum = 1;
724 curwin->w_cursor.col = 0;
725
726 if ((curwin != curwin_save && win_valid(curwin_save))
727 || (curtab != curtab_save && valid_tabpage(curtab_save))) {
728 if (curtab != curtab_save && valid_tabpage(curtab_save)) {
729 goto_tabpage_tp(curtab_save, false, false);
730 }
731
732 // When the first completion is done and the preview
733 // window is not resized, skip the preview window's
734 // status line redrawing.
735 if (ins_compl_active() && !resized) {
736 curwin->w_redr_status = FALSE;
737 }
738
739 // Return cursor to where we were
740 validate_cursor();
741 redraw_later(SOME_VALID);
742
743 // When the preview window was resized we need to
744 // update the view on the buffer. Only go back to
745 // the window when needed, otherwise it will always be
746 // redraw.
747 if (resized) {
748 no_u_sync++;
749 win_enter(curwin_save, true);
750 no_u_sync--;
751 update_topline();
752 }
753
754 // Update the screen before drawing the popup menu.
755 // Enable updating the status lines.
756 // TODO(bfredl): can simplify, get rid of the flag munging?
757 // or at least eliminate extra redraw before win_enter()?
758 pum_is_visible = false;
759 update_screen(0);
760 pum_is_visible = true;
761
762 if (!resized && win_valid(curwin_save)) {
763 no_u_sync++;
764 win_enter(curwin_save, true);
765 no_u_sync--;
766 }
767
768 // May need to update the screen again when there are
769 // autocommands involved.
770 pum_is_visible = false;
771 update_screen(0);
772 pum_is_visible = true;
773 }
774 }
775 }
776 }
777 }
778
779 if (!resized) {
780 pum_redraw();
781 }
782
783 return resized;
784}
785
786/// Undisplay the popup menu (later).
787void pum_undisplay(bool immediate)
788{
789 pum_is_visible = false;
790 pum_array = NULL;
791 must_redraw_pum = false;
792
793 if (immediate) {
794 pum_check_clear();
795 }
796}
797
798void pum_check_clear(void)
799{
800 if (!pum_is_visible && pum_is_drawn) {
801 if (pum_external) {
802 ui_call_popupmenu_hide();
803 } else {
804 ui_comp_remove_grid(&pum_grid);
805 if (ui_has(kUIMultigrid)) {
806 ui_call_win_close(pum_grid.handle);
807 ui_call_grid_destroy(pum_grid.handle);
808 }
809 // TODO(bfredl): consider keeping float grids allocated.
810 grid_free(&pum_grid);
811 }
812 pum_is_drawn = false;
813 }
814}
815
816/// Clear the popup menu. Currently only resets the offset to the first
817/// displayed item.
818void pum_clear(void)
819{
820 pum_first = 0;
821}
822
823/// @return true if the popup menu is displayed.
824bool pum_visible(void)
825{
826 return pum_is_visible;
827}
828
829/// @return true if the popup menu is displayed and drawn on the grid.
830bool pum_drawn(void)
831{
832 return pum_visible() && !pum_external;
833}
834
835/// Screen was cleared, need to redraw next time
836void pum_invalidate(void)
837{
838 pum_invalid = true;
839}
840
841void pum_recompose(void)
842{
843 ui_comp_compose_grid(&pum_grid);
844}
845
846/// Gets the height of the menu.
847///
848/// @return the height of the popup menu, the number of entries visible.
849/// Only valid when pum_visible() returns TRUE!
850int pum_get_height(void)
851{
852 if (pum_external) {
853 int ui_pum_height = ui_pum_get_height();
854 if (ui_pum_height) {
855 return ui_pum_height;
856 }
857 }
858 return pum_height;
859}
860
861/// Add size information about the pum to "dict".
862void pum_set_event_info(dict_T *dict)
863{
864 if (!pum_visible()) {
865 return;
866 }
867 tv_dict_add_nr(dict, S_LEN("height"), pum_height);
868 tv_dict_add_nr(dict, S_LEN("width"), pum_width);
869 tv_dict_add_nr(dict, S_LEN("row"), pum_row);
870 tv_dict_add_nr(dict, S_LEN("col"), pum_col);
871 tv_dict_add_nr(dict, S_LEN("size"), pum_size);
872 tv_dict_add_special(dict, S_LEN("scrollbar"),
873 pum_scrollbar ? kSpecialVarTrue : kSpecialVarFalse);
874}
875