xref: /vim-8.2.3635/src/popupwin.c (revision 87fd0924)
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 a list of people who contributed.
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  * Implementation of popup windows.  See ":help popup".
12  */
13 
14 #include "vim.h"
15 
16 #if defined(FEAT_PROP_POPUP) || defined(PROTO)
17 
18 typedef struct {
19     char	*pp_name;
20     poppos_T	pp_val;
21 } poppos_entry_T;
22 
23 static poppos_entry_T poppos_entries[] = {
24     {"botleft", POPPOS_BOTLEFT},
25     {"topleft", POPPOS_TOPLEFT},
26     {"botright", POPPOS_BOTRIGHT},
27     {"topright", POPPOS_TOPRIGHT},
28     {"center", POPPOS_CENTER}
29 };
30 
31 static void popup_adjust_position(win_T *wp);
32 
33 /*
34  * Get option value for "key", which is "line" or "col".
35  * Handles "cursor+N" and "cursor-N".
36  * Returns MAXCOL if the entry is not present.
37  */
38     static int
popup_options_one(dict_T * dict,char_u * key)39 popup_options_one(dict_T *dict, char_u *key)
40 {
41     dictitem_T	*di;
42     char_u	*val;
43     char_u	*s;
44     char_u	*endp;
45     int		n = 0;
46 
47     di = dict_find(dict, key, -1);
48     if (di == NULL)
49 	return MAXCOL;
50 
51     val = tv_get_string(&di->di_tv);
52     if (STRNCMP(val, "cursor", 6) != 0)
53 	return dict_get_number_check(dict, key);
54 
55     setcursor_mayforce(TRUE);
56     s = val + 6;
57     if (*s != NUL)
58     {
59 	endp = s;
60 	if (*skipwhite(s) == '+' || *skipwhite(s) == '-')
61 	    n = strtol((char *)s, (char **)&endp, 10);
62 	if (endp != NULL && *skipwhite(endp) != NUL)
63 	{
64 	    semsg(_(e_invalid_expression_str), val);
65 	    return 0;
66 	}
67     }
68 
69     if (STRCMP(key, "line") == 0)
70 	n = screen_screenrow() + 1 + n;
71     else // "col"
72 	n = screen_screencol() + 1 + n;
73 
74     // Zero means "not set", use -1 instead.
75     if (n == 0)
76 	n = -1;
77     return n;
78 }
79 
80     static void
set_padding_border(dict_T * dict,int * array,char * name,int max_val)81 set_padding_border(dict_T *dict, int *array, char *name, int max_val)
82 {
83     dictitem_T	*di;
84 
85     di = dict_find(dict, (char_u *)name, -1);
86     if (di != NULL)
87     {
88 	if (di->di_tv.v_type != VAR_LIST)
89 	    emsg(_(e_listreq));
90 	else
91 	{
92 	    list_T	*list = di->di_tv.vval.v_list;
93 	    listitem_T	*li;
94 	    int		i;
95 	    int		nr;
96 
97 	    for (i = 0; i < 4; ++i)
98 		array[i] = 1;
99 	    if (list != NULL)
100 	    {
101 		CHECK_LIST_MATERIALIZE(list);
102 		for (i = 0, li = list->lv_first; i < 4 && i < list->lv_len;
103 							 ++i, li = li->li_next)
104 		{
105 		    nr = (int)tv_get_number(&li->li_tv);
106 		    if (nr >= 0)
107 			array[i] = nr > max_val ? max_val : nr;
108 		}
109 	    }
110 	}
111     }
112 }
113 
114 /*
115  * Used when popup options contain "moved": set default moved values.
116  */
117     static void
set_moved_values(win_T * wp)118 set_moved_values(win_T *wp)
119 {
120     wp->w_popup_curwin = curwin;
121     wp->w_popup_lnum = curwin->w_cursor.lnum;
122     wp->w_popup_mincol = curwin->w_cursor.col;
123     wp->w_popup_maxcol = curwin->w_cursor.col;
124 }
125 
126 /*
127  * Used when popup options contain "moved" with "word" or "WORD".
128  */
129     static void
set_moved_columns(win_T * wp,int flags)130 set_moved_columns(win_T *wp, int flags)
131 {
132     char_u	*ptr;
133     int		len = find_ident_under_cursor(&ptr, flags | FIND_NOERROR);
134 
135     if (len > 0)
136     {
137 	wp->w_popup_mincol = (int)(ptr - ml_get_curline());
138 	wp->w_popup_maxcol = wp->w_popup_mincol + len - 1;
139     }
140 }
141 
142 /*
143  * Used when popup options contain "mousemoved": set default moved values.
144  */
145     static void
set_mousemoved_values(win_T * wp)146 set_mousemoved_values(win_T *wp)
147 {
148     wp->w_popup_mouse_row = mouse_row;
149     wp->w_popup_mouse_mincol = mouse_col;
150     wp->w_popup_mouse_maxcol = mouse_col;
151 }
152 
153 /*
154  * Used when popup options contain "moved" with "word" or "WORD".
155  */
156     static void
set_mousemoved_columns(win_T * wp,int flags)157 set_mousemoved_columns(win_T *wp, int flags)
158 {
159     win_T	*textwp;
160     char_u	*text;
161     int		col;
162     pos_T	pos;
163     colnr_T	mcol;
164 
165     if (find_word_under_cursor(mouse_row, mouse_col, TRUE, flags,
166 				  &textwp, &pos.lnum, &text, NULL, &col) == OK)
167     {
168 	// convert text column to mouse column
169 	pos.col = col;
170 	pos.coladd = 0;
171 	getvcol(textwp, &pos, &mcol, NULL, NULL);
172 	wp->w_popup_mouse_mincol = mcol;
173 
174 	pos.col = col + (colnr_T)STRLEN(text) - 1;
175 	getvcol(textwp, &pos, NULL, NULL, &mcol);
176 	wp->w_popup_mouse_maxcol = mcol;
177 	vim_free(text);
178     }
179 }
180 
181 /*
182  * Return TRUE if "row"/"col" is on the border of the popup.
183  * The values are relative to the top-left corner.
184  */
185     int
popup_on_border(win_T * wp,int row,int col)186 popup_on_border(win_T *wp, int row, int col)
187 {
188     return (row == 0 && wp->w_popup_border[0] > 0)
189 	    || (row == popup_height(wp) - 1 && wp->w_popup_border[2] > 0)
190 	    || (col == 0 && wp->w_popup_border[3] > 0)
191 	    || (col == popup_width(wp) - 1 && wp->w_popup_border[1] > 0);
192 }
193 
194 /*
195  * Return TRUE and close the popup if "row"/"col" is on the "X" button of the
196  * popup and w_popup_close is POPCLOSE_BUTTON.
197  * The values are relative to the top-left corner.
198  * Caller should check the left mouse button was clicked.
199  * Return TRUE if the popup was closed.
200  */
201     int
popup_close_if_on_X(win_T * wp,int row,int col)202 popup_close_if_on_X(win_T *wp, int row, int col)
203 {
204     if (wp->w_popup_close == POPCLOSE_BUTTON
205 	    && row == 0 && col == popup_width(wp) - 1)
206     {
207 	popup_close_for_mouse_click(wp);
208 	return TRUE;
209     }
210     return FALSE;
211 }
212 
213 // Values set when dragging a popup window starts.
214 static int drag_start_row;
215 static int drag_start_col;
216 static int drag_start_wantline;
217 static int drag_start_wantcol;
218 static int drag_on_resize_handle;
219 
220 /*
221  * Mouse down on border of popup window: start dragging it.
222  * Uses mouse_col and mouse_row.
223  */
224     void
popup_start_drag(win_T * wp,int row,int col)225 popup_start_drag(win_T *wp, int row, int col)
226 {
227     drag_start_row = mouse_row;
228     drag_start_col = mouse_col;
229     if (wp->w_wantline <= 0)
230 	drag_start_wantline = wp->w_winrow + 1;
231     else
232 	drag_start_wantline = wp->w_wantline;
233     if (wp->w_wantcol == 0)
234 	drag_start_wantcol = wp->w_wincol + 1;
235     else
236 	drag_start_wantcol = wp->w_wantcol;
237 
238     // Stop centering the popup
239     if (wp->w_popup_pos == POPPOS_CENTER)
240 	wp->w_popup_pos = POPPOS_TOPLEFT;
241 
242     drag_on_resize_handle = wp->w_popup_border[1] > 0
243 			    && wp->w_popup_border[2] > 0
244 			    && row == popup_height(wp) - 1
245 			    && col == popup_width(wp) - 1;
246 
247     if (wp->w_popup_pos != POPPOS_TOPLEFT && drag_on_resize_handle)
248     {
249 	if (wp->w_popup_pos == POPPOS_TOPRIGHT
250 		|| wp->w_popup_pos == POPPOS_BOTRIGHT)
251 	    wp->w_wantcol = wp->w_wincol + 1;
252 	if (wp->w_popup_pos == POPPOS_BOTLEFT)
253 	    wp->w_wantline = wp->w_winrow + 1;
254 	wp->w_popup_pos = POPPOS_TOPLEFT;
255     }
256 }
257 
258 /*
259  * Mouse moved while dragging a popup window: adjust the window popup position
260  * or resize.
261  */
262     void
popup_drag(win_T * wp)263 popup_drag(win_T *wp)
264 {
265     // The popup may be closed before dragging stops.
266     if (!win_valid_popup(wp))
267 	return;
268 
269     if ((wp->w_popup_flags & POPF_RESIZE) && drag_on_resize_handle)
270     {
271 	int width_inc = mouse_col - drag_start_col;
272 	int height_inc = mouse_row - drag_start_row;
273 
274 	if (width_inc != 0)
275 	{
276 	    int width = wp->w_width + width_inc;
277 
278 	    if (width < 1)
279 		width = 1;
280 	    wp->w_minwidth = width;
281 	    wp->w_maxwidth = width;
282 	    drag_start_col = mouse_col;
283 	}
284 
285 	if (height_inc != 0)
286 	{
287 	    int height = wp->w_height + height_inc;
288 
289 	    if (height < 1)
290 		height = 1;
291 	    wp->w_minheight = height;
292 	    wp->w_maxheight = height;
293 	    drag_start_row = mouse_row;
294 	}
295 
296 	popup_adjust_position(wp);
297 	return;
298     }
299 
300     if (!(wp->w_popup_flags & POPF_DRAG))
301 	return;
302     wp->w_wantline = drag_start_wantline + (mouse_row - drag_start_row);
303     if (wp->w_wantline < 1)
304 	wp->w_wantline = 1;
305     if (wp->w_wantline > Rows)
306 	wp->w_wantline = Rows;
307     wp->w_wantcol = drag_start_wantcol + (mouse_col - drag_start_col);
308     if (wp->w_wantcol < 1)
309 	wp->w_wantcol = 1;
310     if (wp->w_wantcol > Columns)
311 	wp->w_wantcol = Columns;
312 
313     popup_adjust_position(wp);
314 }
315 
316 /*
317  * Set w_firstline to match the current "wp->w_topline".
318  */
319     void
popup_set_firstline(win_T * wp)320 popup_set_firstline(win_T *wp)
321 {
322     int	    height = wp->w_height;
323 
324     wp->w_firstline = wp->w_topline;
325     popup_adjust_position(wp);
326 
327     // we don't want the popup to get smaller, decrement the first line
328     // until it doesn't
329     while (wp->w_firstline > 1 && wp->w_height < height)
330     {
331 	--wp->w_firstline;
332 	popup_adjust_position(wp);
333     }
334 }
335 
336 /*
337  * Return TRUE if the position is in the popup window scrollbar.
338  */
339     int
popup_is_in_scrollbar(win_T * wp,int row,int col)340 popup_is_in_scrollbar(win_T *wp, int row, int col)
341 {
342     return wp->w_has_scrollbar
343 	&& row >= wp->w_popup_border[0]
344 	&& row < popup_height(wp) - wp->w_popup_border[2]
345 	&& col == popup_width(wp) - wp->w_popup_border[1] - 1;
346 }
347 
348 
349 /*
350  * Handle a click in a popup window, if it is in the scrollbar.
351  */
352     void
popup_handle_scrollbar_click(win_T * wp,int row,int col)353 popup_handle_scrollbar_click(win_T *wp, int row, int col)
354 {
355     int	    height = popup_height(wp);
356     int	    old_topline = wp->w_topline;
357 
358     if (popup_is_in_scrollbar(wp, row, col))
359     {
360 	if (row >= height / 2)
361 	{
362 	    // Click in lower half, scroll down.
363 	    if (wp->w_topline < wp->w_buffer->b_ml.ml_line_count)
364 		++wp->w_topline;
365 	}
366 	else if (wp->w_topline > 1)
367 	    // click on upper half, scroll up.
368 	    --wp->w_topline;
369 	if (wp->w_topline != old_topline)
370 	{
371 	    popup_set_firstline(wp);
372 	    redraw_win_later(wp, NOT_VALID);
373 	}
374     }
375 }
376 
377 #if defined(FEAT_TIMERS)
378     static void
popup_add_timeout(win_T * wp,int time)379 popup_add_timeout(win_T *wp, int time)
380 {
381     char_u	    cbbuf[50];
382     char_u	    *ptr = cbbuf;
383     typval_T	    tv;
384 
385     vim_snprintf((char *)cbbuf, sizeof(cbbuf),
386 				       "(_) => popup_close(%d)", wp->w_id);
387     if (get_lambda_tv_and_compile(&ptr, &tv, FALSE, &EVALARG_EVALUATE) == OK)
388     {
389 	wp->w_popup_timer = create_timer(time, 0);
390 	wp->w_popup_timer->tr_callback = get_callback(&tv);
391 	clear_tv(&tv);
392     }
393 }
394 #endif
395 
396     static poppos_T
get_pos_entry(dict_T * d,int give_error)397 get_pos_entry(dict_T *d, int give_error)
398 {
399     char_u  *str = dict_get_string(d, (char_u *)"pos", FALSE);
400     int	    nr;
401 
402     if (str == NULL)
403 	return POPPOS_NONE;
404 
405     for (nr = 0; nr < (int)ARRAY_LENGTH(poppos_entries); ++nr)
406 	if (STRCMP(str, poppos_entries[nr].pp_name) == 0)
407 	    return poppos_entries[nr].pp_val;
408 
409     if (give_error)
410 	semsg(_(e_invarg2), str);
411     return POPPOS_NONE;
412 }
413 
414 /*
415  * Shared between popup_create() and f_popup_move().
416  */
417     static void
apply_move_options(win_T * wp,dict_T * d)418 apply_move_options(win_T *wp, dict_T *d)
419 {
420     int		nr;
421     char_u	*str;
422     dictitem_T	*di;
423 
424     if ((nr = dict_get_number_def(d, (char_u *)"minwidth", -1)) >= 0)
425 	wp->w_minwidth = nr;
426     if ((nr = dict_get_number_def(d, (char_u *)"minheight", -1)) >= 0)
427 	wp->w_minheight = nr;
428     if ((nr = dict_get_number_def(d, (char_u *)"maxwidth", -1)) >= 0)
429 	wp->w_maxwidth = nr;
430     if ((nr = dict_get_number_def(d, (char_u *)"maxheight", -1)) >= 0)
431 	wp->w_maxheight = nr;
432 
433     nr = popup_options_one(d, (char_u *)"line");
434     if (nr != MAXCOL)
435 	wp->w_wantline = nr;
436     nr = popup_options_one(d, (char_u *)"col");
437     if (nr != MAXCOL)
438 	wp->w_wantcol = nr;
439 
440 
441     nr = dict_get_bool(d, (char_u *)"fixed", -1);
442     if (nr != -1)
443 	wp->w_popup_fixed = nr != 0;
444 
445     {
446 	poppos_T ppt = get_pos_entry(d, TRUE);
447 
448 	if (ppt != POPPOS_NONE)
449 	    wp->w_popup_pos = ppt;
450     }
451 
452     str = dict_get_string(d, (char_u *)"textprop", FALSE);
453     if (str != NULL)
454     {
455 	wp->w_popup_prop_type = 0;
456 	if (*str != NUL)
457 	{
458 	    wp->w_popup_prop_win = curwin;
459 	    di = dict_find(d, (char_u *)"textpropwin", -1);
460 	    if (di != NULL)
461 	    {
462 		wp->w_popup_prop_win = find_win_by_nr_or_id(&di->di_tv);
463 		if (!win_valid_any_tab(wp->w_popup_prop_win))
464 		    wp->w_popup_prop_win = curwin;
465 	    }
466 
467 	    nr = find_prop_type_id(str, wp->w_popup_prop_win->w_buffer);
468 	    if (nr <= 0)
469 		nr = find_prop_type_id(str, NULL);
470 	    if (nr <= 0)
471 		semsg(_(e_invarg2), str);
472 	    else
473 		wp->w_popup_prop_type = nr;
474 	}
475     }
476 
477     di = dict_find(d, (char_u *)"textpropid", -1);
478     if (di != NULL)
479 	wp->w_popup_prop_id = dict_get_number(d, (char_u *)"textpropid");
480 }
481 
482 /*
483  * Handle "moved" and "mousemoved" arguments.
484  */
485     static void
handle_moved_argument(win_T * wp,dictitem_T * di,int mousemoved)486 handle_moved_argument(win_T *wp, dictitem_T *di, int mousemoved)
487 {
488     if (di->di_tv.v_type == VAR_STRING && di->di_tv.vval.v_string != NULL)
489     {
490 	char_u  *s = di->di_tv.vval.v_string;
491 	int	flags = 0;
492 
493 	if (STRCMP(s, "word") == 0)
494 	    flags = FIND_IDENT | FIND_STRING;
495 	else if (STRCMP(s, "WORD") == 0)
496 	    flags = FIND_STRING;
497 	else if (STRCMP(s, "expr") == 0)
498 	    flags = FIND_IDENT | FIND_STRING | FIND_EVAL;
499 	else if (STRCMP(s, "any") != 0)
500 	    semsg(_(e_invarg2), s);
501 	if (flags != 0)
502 	{
503 	    if (mousemoved)
504 		set_mousemoved_columns(wp, flags);
505 	    else
506 		set_moved_columns(wp, flags);
507 	}
508     }
509     else if (di->di_tv.v_type == VAR_LIST
510 	    && di->di_tv.vval.v_list != NULL
511 	    && (di->di_tv.vval.v_list->lv_len == 2
512 	     || di->di_tv.vval.v_list->lv_len == 3))
513     {
514 	list_T	    *l = di->di_tv.vval.v_list;
515 	listitem_T  *li;
516 	int	    mincol;
517 	int	    maxcol;
518 
519 	CHECK_LIST_MATERIALIZE(l);
520 	li = l->lv_first;
521 	if (l->lv_len == 3)
522 	{
523 	    varnumber_T nr = tv_get_number(&l->lv_first->li_tv);
524 
525 	    // Three numbers, might be from popup_getoptions().
526 	    if (mousemoved)
527 		wp->w_popup_mouse_row = nr;
528 	    else
529 		wp->w_popup_lnum = nr;
530 	    li = li->li_next;
531 	    if (nr == 0)
532 		wp->w_popup_curwin = NULL;
533 	}
534 
535 	mincol = tv_get_number(&li->li_tv);
536 	maxcol = tv_get_number(&li->li_next->li_tv);
537 	if (mousemoved)
538 	{
539 	    wp->w_popup_mouse_mincol = mincol;
540 	    wp->w_popup_mouse_maxcol = maxcol;
541 	}
542 	else
543 	{
544 	    wp->w_popup_mincol = mincol;
545 	    wp->w_popup_maxcol = maxcol;
546 	}
547     }
548     else
549 	semsg(_(e_invarg2), tv_get_string(&di->di_tv));
550 }
551 
552     static void
check_highlight(dict_T * dict,char * name,char_u ** pval)553 check_highlight(dict_T *dict, char *name, char_u **pval)
554 {
555     dictitem_T  *di;
556     char_u	*str;
557 
558     di = dict_find(dict, (char_u *)name, -1);
559     if (di != NULL)
560     {
561 	if (di->di_tv.v_type != VAR_STRING)
562 	    semsg(_(e_invargval), name);
563 	else
564 	{
565 	    str = tv_get_string(&di->di_tv);
566 	    if (*str != NUL)
567 		*pval = vim_strsave(str);
568 	}
569     }
570 }
571 
572 /*
573  * Scroll to show the line with the cursor.
574  */
575     static void
popup_show_curline(win_T * wp)576 popup_show_curline(win_T *wp)
577 {
578     if (wp->w_cursor.lnum < wp->w_topline)
579 	wp->w_topline = wp->w_cursor.lnum;
580     else if (wp->w_cursor.lnum >= wp->w_botline
581 					      && (wp->w_valid & VALID_BOTLINE))
582     {
583 	wp->w_topline = wp->w_cursor.lnum - wp->w_height + 1;
584 	if (wp->w_topline < 1)
585 	    wp->w_topline = 1;
586 	else if (wp->w_topline > wp->w_buffer->b_ml.ml_line_count)
587 	    wp->w_topline = wp->w_buffer->b_ml.ml_line_count;
588 	while (wp->w_topline < wp->w_cursor.lnum
589 		&& wp->w_topline < wp->w_buffer->b_ml.ml_line_count
590 		&& plines_m_win(wp, wp->w_topline, wp->w_cursor.lnum)
591 								> wp->w_height)
592 	    ++wp->w_topline;
593     }
594 
595     // Don't let "firstline" cause a scroll.
596     if (wp->w_firstline > 0)
597 	wp->w_firstline = wp->w_topline;
598 }
599 
600 /*
601  * Get the sign group name for window "wp".
602  * Returns a pointer to a static buffer, overwritten on the next call.
603  */
604     static char_u *
popup_get_sign_name(win_T * wp)605 popup_get_sign_name(win_T *wp)
606 {
607     static char    buf[30];
608 
609     vim_snprintf(buf, sizeof(buf), "popup-%d", wp->w_id);
610     return (char_u *)buf;
611 }
612 
613 /*
614  * Highlight the line with the cursor.
615  * Also scrolls the text to put the cursor line in view.
616  */
617     static void
popup_highlight_curline(win_T * wp)618 popup_highlight_curline(win_T *wp)
619 {
620     int	    sign_id = 0;
621     char_u  *sign_name = popup_get_sign_name(wp);
622 
623     buf_delete_signs(wp->w_buffer, (char_u *)"PopUpMenu");
624 
625     if ((wp->w_popup_flags & POPF_CURSORLINE) != 0)
626     {
627 	popup_show_curline(wp);
628 
629 	if (!sign_exists_by_name(sign_name))
630 	{
631 	    char *linehl = "PopupSelected";
632 
633 	    if (syn_name2id((char_u *)linehl) == 0)
634 		linehl = "PmenuSel";
635 	    sign_define_by_name(sign_name, NULL, (char_u *)linehl, NULL, NULL);
636 	}
637 
638 	sign_place(&sign_id, (char_u *)"PopUpMenu", sign_name,
639 			       wp->w_buffer, wp->w_cursor.lnum, SIGN_DEF_PRIO);
640 	redraw_win_later(wp, NOT_VALID);
641     }
642     else
643 	sign_undefine_by_name(sign_name, FALSE);
644     wp->w_popup_last_curline = wp->w_cursor.lnum;
645 }
646 
647 /*
648  * Shared between popup_create() and f_popup_setoptions().
649  */
650     static void
apply_general_options(win_T * wp,dict_T * dict)651 apply_general_options(win_T *wp, dict_T *dict)
652 {
653     dictitem_T	*di;
654     int		nr;
655     char_u	*str;
656 
657     // TODO: flip
658 
659     di = dict_find(dict, (char_u *)"firstline", -1);
660     if (di != NULL)
661     {
662 	wp->w_firstline = dict_get_number(dict, (char_u *)"firstline");
663 	if (wp->w_firstline < 0)
664 	    wp->w_firstline = -1;
665     }
666 
667     nr = dict_get_bool(dict, (char_u *)"scrollbar", -1);
668     if (nr != -1)
669 	wp->w_want_scrollbar = nr;
670 
671     str = dict_get_string(dict, (char_u *)"title", FALSE);
672     if (str != NULL)
673     {
674 	vim_free(wp->w_popup_title);
675 	wp->w_popup_title = vim_strsave(str);
676     }
677 
678     nr = dict_get_bool(dict, (char_u *)"wrap", -1);
679     if (nr != -1)
680 	wp->w_p_wrap = nr != 0;
681 
682     nr = dict_get_bool(dict, (char_u *)"drag", -1);
683     if (nr != -1)
684     {
685 	if (nr)
686 	    wp->w_popup_flags |= POPF_DRAG;
687 	else
688 	    wp->w_popup_flags &= ~POPF_DRAG;
689     }
690 
691     nr = dict_get_bool(dict, (char_u *)"posinvert", -1);
692     if (nr != -1)
693     {
694 	if (nr)
695 	    wp->w_popup_flags |= POPF_POSINVERT;
696 	else
697 	    wp->w_popup_flags &= ~POPF_POSINVERT;
698     }
699 
700     nr = dict_get_bool(dict, (char_u *)"resize", -1);
701     if (nr != -1)
702     {
703 	if (nr)
704 	    wp->w_popup_flags |= POPF_RESIZE;
705 	else
706 	    wp->w_popup_flags &= ~POPF_RESIZE;
707     }
708 
709     di = dict_find(dict, (char_u *)"close", -1);
710     if (di != NULL)
711     {
712 	int ok = TRUE;
713 
714 	if (di->di_tv.v_type == VAR_STRING && di->di_tv.vval.v_string != NULL)
715 	{
716 	    char_u  *s = di->di_tv.vval.v_string;
717 
718 	    if (STRCMP(s, "none") == 0)
719 		wp->w_popup_close = POPCLOSE_NONE;
720 	    else if (STRCMP(s, "button") == 0)
721 		wp->w_popup_close = POPCLOSE_BUTTON;
722 	    else if (STRCMP(s, "click") == 0)
723 		wp->w_popup_close = POPCLOSE_CLICK;
724 	    else
725 		ok = FALSE;
726 	}
727 	else
728 	    ok = FALSE;
729 	if (!ok)
730 	    semsg(_(e_invargNval), "close", tv_get_string(&di->di_tv));
731     }
732 
733     str = dict_get_string(dict, (char_u *)"highlight", FALSE);
734     if (str != NULL)
735     {
736 	set_string_option_direct_in_win(wp, (char_u *)"wincolor", -1,
737 						   str, OPT_FREE|OPT_LOCAL, 0);
738 #ifdef FEAT_TERMINAL
739 	term_update_wincolor(wp);
740 #endif
741     }
742 
743     set_padding_border(dict, wp->w_popup_padding, "padding", 999);
744     set_padding_border(dict, wp->w_popup_border, "border", 1);
745 
746     di = dict_find(dict, (char_u *)"borderhighlight", -1);
747     if (di != NULL)
748     {
749 	if (di->di_tv.v_type != VAR_LIST || di->di_tv.vval.v_list == NULL)
750 	    emsg(_(e_listreq));
751 	else
752 	{
753 	    list_T	*list = di->di_tv.vval.v_list;
754 	    listitem_T	*li;
755 	    int		i;
756 
757 	    CHECK_LIST_MATERIALIZE(list);
758 	    for (i = 0, li = list->lv_first; i < 4 && i < list->lv_len;
759 						     ++i, li = li->li_next)
760 	    {
761 		str = tv_get_string(&li->li_tv);
762 		if (*str != NUL)
763 		{
764 		    vim_free(wp->w_border_highlight[i]);
765 		    wp->w_border_highlight[i] = vim_strsave(str);
766 		}
767 	    }
768 	    if (list->lv_len == 1 && wp->w_border_highlight[0] != NULL)
769 		for (i = 1; i < 4; ++i)
770 		{
771 		    vim_free(wp->w_border_highlight[i]);
772 		    wp->w_border_highlight[i] =
773 					vim_strsave(wp->w_border_highlight[0]);
774 		}
775 	}
776     }
777 
778     di = dict_find(dict, (char_u *)"borderchars", -1);
779     if (di != NULL)
780     {
781 	if (di->di_tv.v_type != VAR_LIST)
782 	    emsg(_(e_listreq));
783 	else
784 	{
785 	    list_T	*list = di->di_tv.vval.v_list;
786 	    listitem_T	*li;
787 	    int		i;
788 
789 	    if (list != NULL)
790 	    {
791 		CHECK_LIST_MATERIALIZE(list);
792 		for (i = 0, li = list->lv_first; i < 8 && i < list->lv_len;
793 							 ++i, li = li->li_next)
794 		{
795 		    str = tv_get_string(&li->li_tv);
796 		    if (*str != NUL)
797 			wp->w_border_char[i] = mb_ptr2char(str);
798 		}
799 		if (list->lv_len == 1)
800 		    for (i = 1; i < 8; ++i)
801 			wp->w_border_char[i] = wp->w_border_char[0];
802 		if (list->lv_len == 2)
803 		{
804 		    for (i = 4; i < 8; ++i)
805 			wp->w_border_char[i] = wp->w_border_char[1];
806 		    for (i = 1; i < 4; ++i)
807 			wp->w_border_char[i] = wp->w_border_char[0];
808 		}
809 	    }
810 	}
811     }
812 
813     check_highlight(dict, "scrollbarhighlight", &wp->w_scrollbar_highlight);
814     check_highlight(dict, "thumbhighlight", &wp->w_thumb_highlight);
815 
816     di = dict_find(dict, (char_u *)"zindex", -1);
817     if (di != NULL)
818     {
819 	wp->w_zindex = dict_get_number(dict, (char_u *)"zindex");
820 	if (wp->w_zindex < 1)
821 	    wp->w_zindex = POPUPWIN_DEFAULT_ZINDEX;
822 	if (wp->w_zindex > 32000)
823 	    wp->w_zindex = 32000;
824     }
825 
826     di = dict_find(dict, (char_u *)"mask", -1);
827     if (di != NULL)
828     {
829 	int ok = FALSE;
830 
831 	if (di->di_tv.v_type == VAR_LIST && di->di_tv.vval.v_list != NULL)
832 	{
833 	    listitem_T *li;
834 
835 	    ok = TRUE;
836 	    FOR_ALL_LIST_ITEMS(di->di_tv.vval.v_list, li)
837 	    {
838 		if (li->li_tv.v_type != VAR_LIST
839 			|| li->li_tv.vval.v_list == NULL
840 			|| li->li_tv.vval.v_list->lv_len != 4)
841 		{
842 		    ok = FALSE;
843 		    break;
844 		}
845 		else
846 		    CHECK_LIST_MATERIALIZE(li->li_tv.vval.v_list);
847 	    }
848 	}
849 	if (ok)
850 	{
851 	    wp->w_popup_mask = di->di_tv.vval.v_list;
852 	    ++wp->w_popup_mask->lv_refcount;
853 	    VIM_CLEAR(wp->w_popup_mask_cells);
854 	}
855 	else
856 	    semsg(_(e_invargval), "mask");
857     }
858 
859 #if defined(FEAT_TIMERS)
860     // Add timer to close the popup after some time.
861     nr = dict_get_number(dict, (char_u *)"time");
862     if (nr > 0)
863 	popup_add_timeout(wp, nr);
864 #endif
865 
866     di = dict_find(dict, (char_u *)"moved", -1);
867     if (di != NULL)
868     {
869 	set_moved_values(wp);
870 	handle_moved_argument(wp, di, FALSE);
871     }
872 
873     di = dict_find(dict, (char_u *)"mousemoved", -1);
874     if (di != NULL)
875     {
876 	set_mousemoved_values(wp);
877 	handle_moved_argument(wp, di, TRUE);
878     }
879 
880     nr = dict_get_bool(dict, (char_u *)"cursorline", -1);
881     if (nr != -1)
882     {
883 	if (nr != 0)
884 	    wp->w_popup_flags |= POPF_CURSORLINE;
885 	else
886 	    wp->w_popup_flags &= ~POPF_CURSORLINE;
887     }
888 
889     di = dict_find(dict, (char_u *)"filter", -1);
890     if (di != NULL)
891     {
892 	callback_T	callback = get_callback(&di->di_tv);
893 
894 	if (callback.cb_name != NULL)
895 	{
896 	    free_callback(&wp->w_filter_cb);
897 	    set_callback(&wp->w_filter_cb, &callback);
898 	}
899     }
900     nr = dict_get_bool(dict, (char_u *)"mapping", -1);
901     if (nr != -1)
902     {
903 	if (nr)
904 	    wp->w_popup_flags |= POPF_MAPPING;
905 	else
906 	    wp->w_popup_flags &= ~POPF_MAPPING;
907     }
908 
909     str = dict_get_string(dict, (char_u *)"filtermode", FALSE);
910     if (str != NULL)
911     {
912 	if (STRCMP(str, "a") == 0)
913 	    wp->w_filter_mode = MODE_ALL;
914 	else
915 	    wp->w_filter_mode = mode_str2flags(str);
916     }
917 
918     di = dict_find(dict, (char_u *)"callback", -1);
919     if (di != NULL)
920     {
921 	callback_T	callback = get_callback(&di->di_tv);
922 
923 	if (callback.cb_name != NULL)
924 	{
925 	    free_callback(&wp->w_close_cb);
926 	    set_callback(&wp->w_close_cb, &callback);
927 	}
928     }
929 }
930 
931 /*
932  * Go through the options in "dict" and apply them to popup window "wp".
933  * "create" is TRUE when creating a new popup window.
934  */
935     static void
apply_options(win_T * wp,dict_T * dict,int create)936 apply_options(win_T *wp, dict_T *dict, int create)
937 {
938     int		nr;
939 
940     apply_move_options(wp, dict);
941 
942     if (create)
943 	set_string_option_direct_in_win(wp, (char_u *)"signcolumn", -1,
944 					(char_u *)"no", OPT_FREE|OPT_LOCAL, 0);
945 
946     apply_general_options(wp, dict);
947 
948     nr = dict_get_bool(dict, (char_u *)"hidden", FALSE);
949     if (nr > 0)
950 	wp->w_popup_flags |= POPF_HIDDEN;
951 
952     // when "firstline" and "cursorline" are both set and the cursor would be
953     // above or below the displayed lines, move the cursor to "firstline".
954     if (wp->w_firstline > 0 && (wp->w_popup_flags & POPF_CURSORLINE))
955     {
956 	if (wp->w_firstline > wp->w_buffer->b_ml.ml_line_count)
957 	    wp->w_cursor.lnum = wp->w_buffer->b_ml.ml_line_count;
958 	else if (wp->w_cursor.lnum < wp->w_firstline
959 		|| wp->w_cursor.lnum >= wp->w_firstline + wp->w_height)
960 	    wp->w_cursor.lnum = wp->w_firstline;
961 	wp->w_topline = wp->w_firstline;
962 	wp->w_valid &= ~VALID_BOTLINE;
963     }
964 
965     popup_mask_refresh = TRUE;
966     popup_highlight_curline(wp);
967 }
968 
969 /*
970  * Add lines to the popup from a list of strings.
971  */
972     static void
add_popup_strings(buf_T * buf,list_T * l)973 add_popup_strings(buf_T *buf, list_T *l)
974 {
975     listitem_T  *li;
976     linenr_T    lnum = 0;
977     char_u	*p;
978 
979     FOR_ALL_LIST_ITEMS(l, li)
980 	if (li->li_tv.v_type == VAR_STRING)
981 	{
982 	    p = li->li_tv.vval.v_string;
983 	    ml_append_buf(buf, lnum++,
984 			       p == NULL ? (char_u *)"" : p, (colnr_T)0, TRUE);
985 	}
986 }
987 
988 /*
989  * Add lines to the popup from a list of dictionaries.
990  */
991     static void
add_popup_dicts(buf_T * buf,list_T * l)992 add_popup_dicts(buf_T *buf, list_T *l)
993 {
994     listitem_T  *li;
995     listitem_T  *pli;
996     linenr_T    lnum = 0;
997     char_u	*p;
998     dict_T	*dict;
999 
1000     // first add the text lines
1001     FOR_ALL_LIST_ITEMS(l, li)
1002     {
1003 	if (li->li_tv.v_type != VAR_DICT)
1004 	{
1005 	    emsg(_(e_dictreq));
1006 	    return;
1007 	}
1008 	dict = li->li_tv.vval.v_dict;
1009 	p = dict == NULL ? NULL
1010 			      : dict_get_string(dict, (char_u *)"text", FALSE);
1011 	ml_append_buf(buf, lnum++,
1012 			       p == NULL ? (char_u *)"" : p, (colnr_T)0, TRUE);
1013     }
1014 
1015     // add the text properties
1016     lnum = 1;
1017     for (li = l->lv_first; li != NULL; li = li->li_next, ++lnum)
1018     {
1019 	dictitem_T	*di;
1020 	list_T		*plist;
1021 
1022 	dict = li->li_tv.vval.v_dict;
1023 	di = dict_find(dict, (char_u *)"props", -1);
1024 	if (di != NULL)
1025 	{
1026 	    if (di->di_tv.v_type != VAR_LIST)
1027 	    {
1028 		emsg(_(e_listreq));
1029 		return;
1030 	    }
1031 	    plist = di->di_tv.vval.v_list;
1032 	    if (plist != NULL)
1033 	    {
1034 		FOR_ALL_LIST_ITEMS(plist, pli)
1035 		{
1036 		    if (pli->li_tv.v_type != VAR_DICT)
1037 		    {
1038 			emsg(_(e_dictreq));
1039 			return;
1040 		    }
1041 		    dict = pli->li_tv.vval.v_dict;
1042 		    if (dict != NULL)
1043 		    {
1044 			int col = dict_get_number(dict, (char_u *)"col");
1045 
1046 			prop_add_common( lnum, col, dict, buf, NULL);
1047 		    }
1048 		}
1049 	    }
1050 	}
1051     }
1052 }
1053 
1054 /*
1055  * Get the padding plus border at the top, adjusted to 1 if there is a title.
1056  */
1057     int
popup_top_extra(win_T * wp)1058 popup_top_extra(win_T *wp)
1059 {
1060     int	extra = wp->w_popup_border[0] + wp->w_popup_padding[0];
1061 
1062     if (extra == 0 && wp->w_popup_title != NULL && *wp->w_popup_title != NUL)
1063 	return 1;
1064     return extra;
1065 }
1066 
1067 /*
1068  * Get the padding plus border at the left.
1069  */
1070     int
popup_left_extra(win_T * wp)1071 popup_left_extra(win_T *wp)
1072 {
1073     return wp->w_popup_border[3] + wp->w_popup_padding[3];
1074 }
1075 
1076 /*
1077  * Return the height of popup window "wp", including border and padding.
1078  */
1079     int
popup_height(win_T * wp)1080 popup_height(win_T *wp)
1081 {
1082     return wp->w_height
1083 	    + popup_top_extra(wp)
1084 	    + wp->w_popup_padding[2] + wp->w_popup_border[2];
1085 }
1086 
1087 /*
1088  * Return the width of popup window "wp", including border, padding and
1089  * scrollbar.
1090  */
1091     int
popup_width(win_T * wp)1092 popup_width(win_T *wp)
1093 {
1094     // w_leftcol is how many columns of the core are left of the screen
1095     // w_popup_rightoff is how many columns of the core are right of the screen
1096     return wp->w_width + wp->w_leftcol
1097 	    + popup_extra_width(wp)
1098 	    + wp->w_popup_rightoff;
1099 }
1100 
1101 /*
1102  * Return the extra width of popup window "wp": border, padding and scrollbar.
1103  */
1104     int
popup_extra_width(win_T * wp)1105 popup_extra_width(win_T *wp)
1106 {
1107     return wp->w_popup_padding[3] + wp->w_popup_border[3]
1108 	    + wp->w_popup_padding[1] + wp->w_popup_border[1]
1109 	    + wp->w_has_scrollbar;
1110 }
1111 
1112 /*
1113  * Adjust the position and size of the popup to fit on the screen.
1114  */
1115     static void
popup_adjust_position(win_T * wp)1116 popup_adjust_position(win_T *wp)
1117 {
1118     linenr_T	lnum;
1119     int		wrapped = 0;
1120     int		maxwidth;
1121     int		used_maxwidth = FALSE;
1122     int		margin_width = 0;
1123     int		maxspace;
1124     int		center_vert = FALSE;
1125     int		center_hor = FALSE;
1126     int		allow_adjust_left = !wp->w_popup_fixed;
1127     int		top_extra = popup_top_extra(wp);
1128     int		right_extra = wp->w_popup_border[1] + wp->w_popup_padding[1];
1129     int		bot_extra = wp->w_popup_border[2] + wp->w_popup_padding[2];
1130     int		left_extra = wp->w_popup_border[3] + wp->w_popup_padding[3];
1131     int		extra_height = top_extra + bot_extra;
1132     int		extra_width = left_extra + right_extra;
1133     int		w_height_before_limit;
1134     int		org_winrow = wp->w_winrow;
1135     int		org_wincol = wp->w_wincol;
1136     int		org_width = wp->w_width;
1137     int		org_height = wp->w_height;
1138     int		org_leftcol = wp->w_leftcol;
1139     int		org_leftoff = wp->w_popup_leftoff;
1140     int		minwidth, minheight;
1141     int		maxheight = Rows;
1142     int		wantline = wp->w_wantline;  // adjusted for textprop
1143     int		wantcol = wp->w_wantcol;    // adjusted for textprop
1144     int		use_wantcol = wantcol != 0;
1145     int		adjust_height_for_top_aligned = FALSE;
1146 
1147     wp->w_winrow = 0;
1148     wp->w_wincol = 0;
1149     wp->w_leftcol = 0;
1150     wp->w_popup_leftoff = 0;
1151     wp->w_popup_rightoff = 0;
1152 
1153     // May need to update the "cursorline" highlighting, which may also change
1154     // "topline"
1155     if (wp->w_popup_last_curline != wp->w_cursor.lnum)
1156 	popup_highlight_curline(wp);
1157 
1158     if (wp->w_popup_prop_type > 0 && win_valid(wp->w_popup_prop_win))
1159     {
1160 	win_T	    *prop_win = wp->w_popup_prop_win;
1161 	textprop_T  prop;
1162 	linenr_T    prop_lnum;
1163 	pos_T	    pos;
1164 	int	    screen_row;
1165 	int	    screen_scol;
1166 	int	    screen_ccol;
1167 	int	    screen_ecol;
1168 
1169 	// Popup window is positioned relative to a text property.
1170 	if (find_visible_prop(prop_win,
1171 				wp->w_popup_prop_type, wp->w_popup_prop_id,
1172 				&prop, &prop_lnum) == FAIL)
1173 	{
1174 	    // Text property is no longer visible, hide the popup.
1175 	    // Unhiding the popup is done in check_popup_unhidden().
1176 	    if ((wp->w_popup_flags & POPF_HIDDEN) == 0)
1177 	    {
1178 		wp->w_popup_flags |= POPF_HIDDEN;
1179 		if (win_valid(wp->w_popup_prop_win))
1180 		    redraw_win_later(wp->w_popup_prop_win, SOME_VALID);
1181 	    }
1182 	    return;
1183 	}
1184 
1185 	// Compute the desired position from the position of the text
1186 	// property.  Use "wantline" and "wantcol" as offsets.
1187 	pos.lnum = prop_lnum;
1188 	pos.col = prop.tp_col;
1189 	if (wp->w_popup_pos == POPPOS_TOPLEFT
1190 		|| wp->w_popup_pos == POPPOS_BOTLEFT)
1191 	    pos.col += prop.tp_len - 1;
1192 	textpos2screenpos(prop_win, &pos, &screen_row,
1193 				     &screen_scol, &screen_ccol, &screen_ecol);
1194 
1195 	if (screen_scol == 0)
1196 	{
1197 	    // position is off screen, make the width zero to hide it.
1198 	    wp->w_width = 0;
1199 	    return;
1200 	}
1201 	if (wp->w_popup_pos == POPPOS_TOPLEFT
1202 		|| wp->w_popup_pos == POPPOS_TOPRIGHT)
1203 	    // below the text
1204 	    wantline = screen_row + wantline + 1;
1205 	else
1206 	    // above the text
1207 	    wantline = screen_row + wantline - 1;
1208 	center_vert = FALSE;
1209 	if (wp->w_popup_pos == POPPOS_TOPLEFT
1210 		|| wp->w_popup_pos == POPPOS_BOTLEFT)
1211 	    // right of the text
1212 	    wantcol = screen_ecol + wantcol;
1213 	else
1214 	    // left of the text
1215 	    wantcol = screen_scol + wantcol - 2;
1216 	use_wantcol = TRUE;
1217     }
1218     else
1219     {
1220 	// If no line was specified default to vertical centering.
1221 	if (wantline == 0)
1222 	    center_vert = TRUE;
1223 	else if (wantline < 0)
1224 	    // If "wantline" is negative it actually means zero.
1225 	    wantline = 0;
1226 	if (wantcol < 0)
1227 	    // If "wantcol" is negative it actually means zero.
1228 	    wantcol = 0;
1229     }
1230 
1231     if (wp->w_popup_pos == POPPOS_CENTER)
1232     {
1233 	// center after computing the size
1234 	center_vert = TRUE;
1235 	center_hor = TRUE;
1236     }
1237     else
1238     {
1239 	if (wantline > 0 && (wp->w_popup_pos == POPPOS_TOPLEFT
1240 					|| wp->w_popup_pos == POPPOS_TOPRIGHT))
1241 	{
1242 	    wp->w_winrow = wantline - 1;
1243 	    if (wp->w_winrow >= Rows)
1244 		wp->w_winrow = Rows - 1;
1245 	}
1246 
1247 	if (!use_wantcol)
1248 	    center_hor = TRUE;
1249 	else if (wantcol > 0 && (wp->w_popup_pos == POPPOS_TOPLEFT
1250 		|| wp->w_popup_pos == POPPOS_BOTLEFT))
1251 	{
1252 	    wp->w_wincol = wantcol - 1;
1253 	    // Need to see at least one character after the decoration.
1254 	    if (wp->w_wincol > Columns - left_extra - 1)
1255 		wp->w_wincol = Columns - left_extra - 1;
1256 	}
1257     }
1258 
1259     // When centering or right aligned, use maximum width.
1260     // When left aligned use the space available, but shift to the left when we
1261     // hit the right of the screen.
1262     maxspace = Columns - wp->w_wincol - left_extra;
1263     maxwidth = maxspace;
1264     if (wp->w_maxwidth > 0 && maxwidth > wp->w_maxwidth)
1265     {
1266 	allow_adjust_left = FALSE;
1267 	maxwidth = wp->w_maxwidth;
1268     }
1269 
1270     if (wp->w_p_nu || wp->w_p_rnu)
1271 	margin_width = number_width(wp) + 1;
1272 #ifdef FEAT_FOLDING
1273     margin_width += wp->w_p_fdc;
1274 #endif
1275 #ifdef FEAT_SIGNS
1276     if (signcolumn_on(wp))
1277 	margin_width += 2;
1278 #endif
1279     if (margin_width >= maxwidth)
1280 	margin_width = maxwidth - 1;
1281 
1282     minwidth = wp->w_minwidth;
1283     minheight = wp->w_minheight;
1284 #ifdef FEAT_TERMINAL
1285     // A terminal popup initially does not have content, use a default minimal
1286     // width of 20 characters and height of 5 lines.
1287     if (wp->w_buffer->b_term != NULL)
1288     {
1289 	if (minwidth == 0)
1290 	    minwidth = 20;
1291 	if (minheight == 0)
1292 	    minheight = 5;
1293     }
1294 #endif
1295 
1296     if (wp->w_maxheight > 0)
1297 	maxheight = wp->w_maxheight;
1298 
1299     // start at the desired first line
1300     if (wp->w_firstline > 0)
1301 	wp->w_topline = wp->w_firstline;
1302     if (wp->w_topline < 1)
1303 	wp->w_topline = 1;
1304     else if (wp->w_topline > wp->w_buffer->b_ml.ml_line_count)
1305 	wp->w_topline = wp->w_buffer->b_ml.ml_line_count;
1306 
1307     // Compute width based on longest text line and the 'wrap' option.
1308     // Use a minimum width of one, so that something shows when there is no
1309     // text.
1310     // When "firstline" is -1 then start with the last buffer line and go
1311     // backwards.
1312     // TODO: more accurate wrapping
1313     wp->w_width = 1;
1314     if (wp->w_firstline < 0)
1315 	lnum = wp->w_buffer->b_ml.ml_line_count;
1316     else
1317 	lnum = wp->w_topline;
1318     while (lnum >= 1 && lnum <= wp->w_buffer->b_ml.ml_line_count)
1319     {
1320 	int len;
1321 	int w_width = wp->w_width;
1322 
1323 	// Count Tabs for what they are worth and compute the length based on
1324 	// the maximum width (matters when 'showbreak' is set).
1325 	// "margin_width" is added to "len" where it matters.
1326 	if (wp->w_width < maxwidth)
1327 	    wp->w_width = maxwidth;
1328 	len = win_linetabsize(wp, ml_get_buf(wp->w_buffer, lnum, FALSE),
1329 							      (colnr_T)MAXCOL);
1330 	wp->w_width = w_width;
1331 
1332 	if (wp->w_p_wrap)
1333 	{
1334 	    while (len + margin_width > maxwidth)
1335 	    {
1336 		++wrapped;
1337 		len -= maxwidth - margin_width;
1338 		wp->w_width = maxwidth;
1339 		used_maxwidth = TRUE;
1340 	    }
1341 	}
1342 	else if (len + margin_width > maxwidth
1343 		&& allow_adjust_left
1344 		&& (wp->w_popup_pos == POPPOS_TOPLEFT
1345 		    || wp->w_popup_pos == POPPOS_BOTLEFT))
1346 	{
1347 	    // adjust leftwise to fit text on screen
1348 	    int shift_by = len + margin_width - maxwidth;
1349 
1350 	    if (shift_by > wp->w_wincol)
1351 	    {
1352 		int truncate_shift = shift_by - wp->w_wincol;
1353 
1354 		len -= truncate_shift;
1355 		shift_by -= truncate_shift;
1356 	    }
1357 
1358 	    wp->w_wincol -= shift_by;
1359 	    maxwidth += shift_by;
1360 	    wp->w_width = maxwidth;
1361 	}
1362 	if (wp->w_width < len + margin_width)
1363 	{
1364 	    wp->w_width = len + margin_width;
1365 	    if (wp->w_maxwidth > 0 && wp->w_width > wp->w_maxwidth)
1366 		wp->w_width = wp->w_maxwidth;
1367 	}
1368 
1369 	if (wp->w_firstline < 0)
1370 	    --lnum;
1371 	else
1372 	    ++lnum;
1373 
1374 	// do not use the width of lines we're not going to show
1375 	if (maxheight > 0
1376 		   && (wp->w_firstline >= 0
1377 			       ? lnum - wp->w_topline
1378 			       : wp->w_buffer->b_ml.ml_line_count - lnum)
1379 		       + wrapped >= maxheight)
1380 	    break;
1381     }
1382 
1383     if (wp->w_firstline < 0)
1384 	wp->w_topline = lnum > 0 ? lnum + 1 : lnum;
1385 
1386     wp->w_has_scrollbar = wp->w_want_scrollbar
1387 	   && (wp->w_topline > 1 || lnum <= wp->w_buffer->b_ml.ml_line_count);
1388 #ifdef FEAT_TERMINAL
1389     if (wp->w_buffer->b_term != NULL)
1390 	// Terminal window never has a scrollbar, adjusts to window height.
1391 	wp->w_has_scrollbar = FALSE;
1392 #endif
1393     if (wp->w_has_scrollbar)
1394     {
1395 	++right_extra;
1396 	++extra_width;
1397 	// make space for the scrollbar if needed, when lines wrap and when
1398 	// applying minwidth
1399 	if (maxwidth + right_extra >= maxspace
1400 		&& (used_maxwidth || (minwidth > 0 && wp->w_width < minwidth)))
1401 	    maxwidth -= wp->w_popup_padding[1] + 1;
1402     }
1403 
1404     if (wp->w_popup_title != NULL && *wp->w_popup_title != NUL)
1405     {
1406 	int title_len = vim_strsize(wp->w_popup_title) + 2 - extra_width;
1407 
1408 	if (minwidth < title_len)
1409 	    minwidth = title_len;
1410     }
1411 
1412     if (minwidth > 0 && wp->w_width < minwidth)
1413 	wp->w_width = minwidth;
1414     if (wp->w_width > maxwidth)
1415     {
1416 	if (wp->w_width > maxspace && !wp->w_p_wrap)
1417 	    // some columns cut off on the right
1418 	    wp->w_popup_rightoff = wp->w_width - maxspace;
1419 	wp->w_width = maxwidth;
1420     }
1421     if (center_hor)
1422     {
1423 	wp->w_wincol = (Columns - wp->w_width - extra_width) / 2;
1424 	if (wp->w_wincol < 0)
1425 	    wp->w_wincol = 0;
1426     }
1427     else if (wp->w_popup_pos == POPPOS_BOTRIGHT
1428 	    || wp->w_popup_pos == POPPOS_TOPRIGHT)
1429     {
1430 	int leftoff = wantcol - (wp->w_width + extra_width);
1431 
1432 	// Right aligned: move to the right if needed.
1433 	// No truncation, because that would change the height.
1434 	if (leftoff >= 0)
1435 	    wp->w_wincol = leftoff;
1436 	else if (wp->w_popup_fixed)
1437 	{
1438 	    // "col" specifies the right edge, but popup doesn't fit, skip some
1439 	    // columns when displaying the window, minus left border and
1440 	    // padding.
1441 	    if (-leftoff > left_extra)
1442 		wp->w_leftcol = -leftoff - left_extra;
1443 	    wp->w_width -= wp->w_leftcol;
1444 	    wp->w_popup_leftoff = -leftoff;
1445 	    if (wp->w_width < 0)
1446 		wp->w_width = 0;
1447 	}
1448     }
1449 
1450     if (wp->w_p_wrap || (!wp->w_popup_fixed
1451 			    && (wp->w_popup_pos == POPPOS_TOPLEFT
1452 				|| wp->w_popup_pos == POPPOS_BOTLEFT)))
1453     {
1454 	int want_col = 0;
1455 
1456 	// try to show the right border and any scrollbar
1457 	want_col = left_extra + wp->w_width + right_extra;
1458 	if (want_col > 0 && wp->w_wincol > 0
1459 					 && wp->w_wincol + want_col >= Columns)
1460 	{
1461 	    wp->w_wincol = Columns - want_col;
1462 	    if (wp->w_wincol < 0)
1463 		wp->w_wincol = 0;
1464 	}
1465     }
1466 
1467     wp->w_height = wp->w_buffer->b_ml.ml_line_count - wp->w_topline
1468 								 + 1 + wrapped;
1469     if (minheight > 0 && wp->w_height < minheight)
1470 	wp->w_height = minheight;
1471     if (maxheight > 0 && wp->w_height > maxheight)
1472 	wp->w_height = maxheight;
1473     w_height_before_limit = wp->w_height;
1474     if (wp->w_height > Rows - wp->w_winrow)
1475 	wp->w_height = Rows - wp->w_winrow;
1476 
1477     if (center_vert)
1478     {
1479 	wp->w_winrow = (Rows - wp->w_height - extra_height) / 2;
1480 	if (wp->w_winrow < 0)
1481 	    wp->w_winrow = 0;
1482     }
1483     else if (wp->w_popup_pos == POPPOS_BOTRIGHT
1484 		|| wp->w_popup_pos == POPPOS_BOTLEFT)
1485     {
1486 	if ((wp->w_height + extra_height) <= wantline)
1487 	    // bottom aligned: may move down
1488 	    wp->w_winrow = wantline - (wp->w_height + extra_height);
1489 	else if (wantline * 2 >= Rows || !(wp->w_popup_flags & POPF_POSINVERT))
1490 	{
1491 	    // Bottom aligned but does not fit, and less space on the other
1492 	    // side or "posinvert" is off: reduce height.
1493 	    wp->w_winrow = 0;
1494 	    wp->w_height = wantline - extra_height;
1495 	}
1496 	else
1497 	{
1498 	    // Not enough space and more space on the other side: make top
1499 	    // aligned.
1500 	    wp->w_winrow = (wantline < 0 ? 0 : wantline) + 1;
1501 	    adjust_height_for_top_aligned = TRUE;
1502 	}
1503     }
1504     else if (wp->w_popup_pos == POPPOS_TOPRIGHT
1505 		|| wp->w_popup_pos == POPPOS_TOPLEFT)
1506     {
1507 	if (wantline + (wp->w_height + extra_height) - 1 > Rows
1508 		&& wantline * 2 > Rows
1509 		&& (wp->w_popup_flags & POPF_POSINVERT))
1510 	{
1511 	    // top aligned and not enough space below but there is space above:
1512 	    // make bottom aligned and recompute the height
1513 	    wp->w_height = w_height_before_limit;
1514 	    wp->w_winrow = wantline - 2 - wp->w_height - extra_height;
1515 	    if (wp->w_winrow < 0)
1516 	    {
1517 		wp->w_height += wp->w_winrow;
1518 		wp->w_winrow = 0;
1519 	    }
1520 	}
1521 	else
1522 	{
1523 	    wp->w_winrow = wantline - 1;
1524 	    adjust_height_for_top_aligned = TRUE;
1525 	}
1526     }
1527 
1528     if (adjust_height_for_top_aligned && wp->w_want_scrollbar
1529 			  && wp->w_winrow + wp->w_height + extra_height > Rows)
1530     {
1531 	// Bottom of the popup goes below the last line, reduce the height and
1532 	// add a scrollbar.
1533 	wp->w_height = Rows - wp->w_winrow - extra_height;
1534 #ifdef FEAT_TERMINAL
1535 	if (wp->w_buffer->b_term == NULL)
1536 #endif
1537 	    wp->w_has_scrollbar = TRUE;
1538     }
1539 
1540     // make sure w_winrow is valid
1541     if (wp->w_winrow >= Rows)
1542 	wp->w_winrow = Rows - 1;
1543     else if (wp->w_winrow < 0)
1544 	wp->w_winrow = 0;
1545 
1546     if (wp->w_height != org_height)
1547 	win_comp_scroll(wp);
1548 
1549     wp->w_popup_last_changedtick = CHANGEDTICK(wp->w_buffer);
1550     if (win_valid(wp->w_popup_prop_win))
1551     {
1552 	wp->w_popup_prop_changedtick =
1553 				   CHANGEDTICK(wp->w_popup_prop_win->w_buffer);
1554 	wp->w_popup_prop_topline = wp->w_popup_prop_win->w_topline;
1555     }
1556 
1557     // Need to update popup_mask if the position or size changed.
1558     // And redraw windows and statuslines that were behind the popup.
1559     if (org_winrow != wp->w_winrow
1560 	    || org_wincol != wp->w_wincol
1561 	    || org_leftcol != wp->w_leftcol
1562 	    || org_leftoff != wp->w_popup_leftoff
1563 	    || org_width != wp->w_width
1564 	    || org_height != wp->w_height)
1565     {
1566 	redraw_win_later(wp, NOT_VALID);
1567 	if (wp->w_popup_flags & POPF_ON_CMDLINE)
1568 	    clear_cmdline = TRUE;
1569 	popup_mask_refresh = TRUE;
1570     }
1571 }
1572 
1573 typedef enum
1574 {
1575     TYPE_NORMAL,
1576     TYPE_ATCURSOR,
1577     TYPE_BEVAL,
1578     TYPE_NOTIFICATION,
1579     TYPE_DIALOG,
1580     TYPE_MENU,
1581     TYPE_PREVIEW,	// preview window
1582     TYPE_INFO		// popup menu info
1583 } create_type_T;
1584 
1585 /*
1586  * Make "buf" empty and set the contents to "text".
1587  * Used by popup_create() and popup_settext().
1588  */
1589     static void
popup_set_buffer_text(buf_T * buf,typval_T text)1590 popup_set_buffer_text(buf_T *buf, typval_T text)
1591 {
1592     int	    lnum;
1593 
1594     // Clear the buffer, then replace the lines.
1595     curbuf = buf;
1596     for (lnum = buf->b_ml.ml_line_count; lnum > 0; --lnum)
1597 	ml_delete(lnum);
1598     curbuf = curwin->w_buffer;
1599 
1600     // Add text to the buffer.
1601     if (text.v_type == VAR_STRING)
1602     {
1603 	char_u *s = text.vval.v_string;
1604 
1605 	// just a string
1606 	ml_append_buf(buf, 0, s == NULL ? (char_u *)"" : s, (colnr_T)0, TRUE);
1607     }
1608     else
1609     {
1610 	list_T *l = text.vval.v_list;
1611 
1612 	if (l != NULL && l->lv_len > 0)
1613 	{
1614 	    if (l->lv_first->li_tv.v_type == VAR_STRING)
1615 		// list of strings
1616 		add_popup_strings(buf, l);
1617 	    else
1618 		// list of dictionaries
1619 		add_popup_dicts(buf, l);
1620 	}
1621     }
1622 
1623     // delete the line that was in the empty buffer
1624     curbuf = buf;
1625     ml_delete(buf->b_ml.ml_line_count);
1626     curbuf = curwin->w_buffer;
1627 }
1628 
1629 /*
1630  * Parse the 'previewpopup' or 'completepopup' option and apply the values to
1631  * window "wp" if it is not NULL.
1632  * Return FAIL if the parsing fails.
1633  */
1634     static int
parse_popup_option(win_T * wp,int is_preview)1635 parse_popup_option(win_T *wp, int is_preview)
1636 {
1637     char_u *p =
1638 #ifdef FEAT_QUICKFIX
1639 	!is_preview ? p_cpp :
1640 #endif
1641 	p_pvp;
1642 
1643     if (wp != NULL)
1644 	wp->w_popup_flags &= ~POPF_INFO_MENU;
1645 
1646     for ( ; *p != NUL; p += (*p == ',' ? 1 : 0))
1647     {
1648 	char_u	*e, *dig;
1649 	char_u	*s = p;
1650 	int	x;
1651 
1652 	e = vim_strchr(p, ':');
1653 	if (e == NULL || e[1] == NUL)
1654 	    return FAIL;
1655 
1656 	p = vim_strchr(e, ',');
1657 	if (p == NULL)
1658 	    p = e + STRLEN(e);
1659 	dig = e + 1;
1660 	x = getdigits(&dig);
1661 
1662 	if (STRNCMP(s, "height:", 7) == 0)
1663 	{
1664 	    if (dig != p)
1665 		return FAIL;
1666 	    if (wp != NULL)
1667 	    {
1668 		if (is_preview)
1669 		    wp->w_minheight = x;
1670 		wp->w_maxheight = x;
1671 	    }
1672 	}
1673 	else if (STRNCMP(s, "width:", 6) == 0)
1674 	{
1675 	    if (dig != p)
1676 		return FAIL;
1677 	    if (wp != NULL)
1678 	    {
1679 		if (is_preview)
1680 		    wp->w_minwidth = x;
1681 		wp->w_maxwidth = x;
1682 		wp->w_maxwidth_opt = x;
1683 	    }
1684 	}
1685 	else if (STRNCMP(s, "highlight:", 10) == 0)
1686 	{
1687 	    if (wp != NULL)
1688 	    {
1689 		int c = *p;
1690 
1691 		*p = NUL;
1692 		set_string_option_direct_in_win(wp, (char_u *)"wincolor", -1,
1693 						s + 10, OPT_FREE|OPT_LOCAL, 0);
1694 		*p = c;
1695 	    }
1696 	}
1697 	else if (STRNCMP(s, "border:", 7) == 0)
1698 	{
1699 	    char_u	*arg = s + 7;
1700 	    int		on = STRNCMP(arg, "on", 2) == 0 && arg + 2 == p;
1701 	    int		off = STRNCMP(arg, "off", 3) == 0 && arg + 3 == p;
1702 	    int		i;
1703 
1704 	    if (!on && !off)
1705 		return FAIL;
1706 	    if (wp != NULL)
1707 	    {
1708 		for (i = 0; i < 4; ++i)
1709 		    wp->w_popup_border[i] = on ? 1 : 0;
1710 		if (off)
1711 		    // only show the X for close when there is a border
1712 		    wp->w_popup_close = POPCLOSE_NONE;
1713 	    }
1714 	}
1715 	else if (STRNCMP(s, "align:", 6) == 0)
1716 	{
1717 	    char_u	*arg = s + 6;
1718 	    int		item = STRNCMP(arg, "item", 4) == 0 && arg + 4 == p;
1719 	    int		menu = STRNCMP(arg, "menu", 4) == 0 && arg + 4 == p;
1720 
1721 	    if (!menu && !item)
1722 		return FAIL;
1723 	    if (wp != NULL && menu)
1724 		wp->w_popup_flags |= POPF_INFO_MENU;
1725 	}
1726 	else
1727 	    return FAIL;
1728     }
1729     return OK;
1730 }
1731 
1732 /*
1733  * Parse the 'previewpopup' option and apply the values to window "wp" if it
1734  * is not NULL.
1735  * Return FAIL if the parsing fails.
1736  */
1737     int
parse_previewpopup(win_T * wp)1738 parse_previewpopup(win_T *wp)
1739 {
1740     return parse_popup_option(wp, TRUE);
1741 }
1742 
1743 /*
1744  * Parse the 'completepopup' option and apply the values to window "wp" if it
1745  * is not NULL.
1746  * Return FAIL if the parsing fails.
1747  */
1748     int
parse_completepopup(win_T * wp)1749 parse_completepopup(win_T *wp)
1750 {
1751     return parse_popup_option(wp, FALSE);
1752 }
1753 
1754 /*
1755  * Set w_wantline and w_wantcol for the cursor position in the current window.
1756  * Keep at least "width" columns from the right of the screen.
1757  */
1758     void
popup_set_wantpos_cursor(win_T * wp,int width,dict_T * d)1759 popup_set_wantpos_cursor(win_T *wp, int width, dict_T *d)
1760 {
1761     poppos_T ppt = POPPOS_NONE;
1762 
1763     if (d != NULL)
1764 	ppt = get_pos_entry(d, FALSE);
1765 
1766     setcursor_mayforce(TRUE);
1767     if (ppt == POPPOS_TOPRIGHT || ppt == POPPOS_TOPLEFT)
1768     {
1769 	wp->w_wantline = curwin->w_winrow + curwin->w_wrow + 2;
1770     }
1771     else
1772     {
1773 	wp->w_wantline = curwin->w_winrow + curwin->w_wrow;
1774 	if (wp->w_wantline == 0)  // cursor in first line
1775 	{
1776 	    wp->w_wantline = 2;
1777 	    wp->w_popup_pos = ppt == POPPOS_BOTRIGHT
1778 					    ? POPPOS_TOPRIGHT : POPPOS_TOPLEFT;
1779 	}
1780     }
1781 
1782     wp->w_wantcol = curwin->w_wincol + curwin->w_wcol + 1;
1783     if (wp->w_wantcol > Columns - width)
1784     {
1785 	wp->w_wantcol = Columns - width;
1786 	if (wp->w_wantcol < 1)
1787 	    wp->w_wantcol = 1;
1788     }
1789 
1790     popup_adjust_position(wp);
1791 }
1792 
1793 /*
1794  * Set w_wantline and w_wantcol for the a given screen position.
1795  * Caller must take care of running into the window border.
1796  */
1797     void
popup_set_wantpos_rowcol(win_T * wp,int row,int col)1798 popup_set_wantpos_rowcol(win_T *wp, int row, int col)
1799 {
1800     wp->w_wantline = row;
1801     wp->w_wantcol = col;
1802     popup_adjust_position(wp);
1803 }
1804 
1805 /*
1806  * Add a border and lef&right padding.
1807  */
1808     static void
add_border_left_right_padding(win_T * wp)1809 add_border_left_right_padding(win_T *wp)
1810 {
1811     int i;
1812 
1813     for (i = 0; i < 4; ++i)
1814     {
1815 	wp->w_popup_border[i] = 1;
1816 	wp->w_popup_padding[i] = (i & 1) ? 1 : 0;
1817     }
1818 }
1819 
1820 #ifdef FEAT_TERMINAL
1821 /*
1822  * Return TRUE if there is any popup window with a terminal buffer.
1823  */
1824     static int
popup_terminal_exists(void)1825 popup_terminal_exists(void)
1826 {
1827     win_T	*wp;
1828     tabpage_T	*tp;
1829 
1830     FOR_ALL_POPUPWINS(wp)
1831 	if (wp->w_buffer->b_term != NULL)
1832 	    return TRUE;
1833     FOR_ALL_TABPAGES(tp)
1834 	FOR_ALL_POPUPWINS_IN_TAB(tp, wp)
1835 	    if (wp->w_buffer->b_term != NULL)
1836 		return TRUE;
1837     return FALSE;
1838 }
1839 #endif
1840 
1841 /*
1842  * popup_create({text}, {options})
1843  * popup_atcursor({text}, {options})
1844  * etc.
1845  * When creating a preview or info popup "argvars" and "rettv" are NULL.
1846  */
1847     static win_T *
popup_create(typval_T * argvars,typval_T * rettv,create_type_T type)1848 popup_create(typval_T *argvars, typval_T *rettv, create_type_T type)
1849 {
1850     win_T	*wp;
1851     tabpage_T	*tp = NULL;
1852     int		tabnr = 0;
1853     int		new_buffer;
1854     buf_T	*buf = NULL;
1855     dict_T	*d = NULL;
1856     int		nr;
1857     int		i;
1858 
1859     if (argvars != NULL)
1860     {
1861 	if (in_vim9script()
1862 		&& (check_for_string_or_number_or_list_arg(argvars, 0) == FAIL
1863 		    || check_for_dict_arg(argvars, 1) == FAIL))
1864 	    return NULL;
1865 
1866 	// Check that arguments look OK.
1867 	if (argvars[0].v_type == VAR_NUMBER)
1868 	{
1869 	    buf = buflist_findnr(argvars[0].vval.v_number);
1870 	    if (buf == NULL)
1871 	    {
1872 		semsg(_(e_nobufnr), argvars[0].vval.v_number);
1873 		return NULL;
1874 	    }
1875 #ifdef FEAT_TERMINAL
1876 	    if (buf->b_term != NULL && popup_terminal_exists())
1877 	    {
1878 		emsg(_("E861: Cannot open a second popup with a terminal"));
1879 		return NULL;
1880 	    }
1881 #endif
1882 	}
1883 	else if (!(argvars[0].v_type == VAR_STRING
1884 			&& argvars[0].vval.v_string != NULL)
1885 		    && !(argvars[0].v_type == VAR_LIST
1886 			&& argvars[0].vval.v_list != NULL))
1887 	{
1888 	    emsg(_("E450: buffer number, text or a list required"));
1889 	    return NULL;
1890 	}
1891 	if (argvars[1].v_type != VAR_DICT || argvars[1].vval.v_dict == NULL)
1892 	{
1893 	    emsg(_(e_dictreq));
1894 	    return NULL;
1895 	}
1896 	d = argvars[1].vval.v_dict;
1897     }
1898 
1899     if (d != NULL)
1900     {
1901 	if (dict_find(d, (char_u *)"tabpage", -1) != NULL)
1902 	    tabnr = (int)dict_get_number(d, (char_u *)"tabpage");
1903 	else if (type == TYPE_NOTIFICATION)
1904 	    tabnr = -1;  // notifications are global by default
1905 	else
1906 	    tabnr = 0;
1907 	if (tabnr > 0)
1908 	{
1909 	    tp = find_tabpage(tabnr);
1910 	    if (tp == NULL)
1911 	    {
1912 		semsg(_("E997: Tabpage not found: %d"), tabnr);
1913 		return NULL;
1914 	    }
1915 	}
1916     }
1917 
1918     // Create the window and buffer.
1919     wp = win_alloc_popup_win();
1920     if (wp == NULL)
1921 	return NULL;
1922     if (rettv != NULL)
1923 	rettv->vval.v_number = wp->w_id;
1924     wp->w_popup_pos = POPPOS_TOPLEFT;
1925     wp->w_popup_flags = POPF_IS_POPUP | POPF_MAPPING | POPF_POSINVERT;
1926 
1927     if (buf != NULL)
1928     {
1929 	// use existing buffer
1930 	new_buffer = FALSE;
1931 	win_init_popup_win(wp, buf);
1932 	set_local_options_default(wp, FALSE);
1933 	buffer_ensure_loaded(buf);
1934     }
1935     else
1936     {
1937 	// create a new buffer associated with the popup
1938 	new_buffer = TRUE;
1939 	buf = buflist_new(NULL, NULL, (linenr_T)0, BLN_NEW|BLN_DUMMY|BLN_REUSE);
1940 	if (buf == NULL)
1941 	    return NULL;
1942 	ml_open(buf);
1943 
1944 	win_init_popup_win(wp, buf);
1945 
1946 	set_local_options_default(wp, TRUE);
1947 	set_string_option_direct_in_buf(buf, (char_u *)"buftype", -1,
1948 				     (char_u *)"popup", OPT_FREE|OPT_LOCAL, 0);
1949 	set_string_option_direct_in_buf(buf, (char_u *)"bufhidden", -1,
1950 				      (char_u *)"wipe", OPT_FREE|OPT_LOCAL, 0);
1951 	buf->b_p_ul = -1;	// no undo
1952 	buf->b_p_swf = FALSE;   // no swap file
1953 	buf->b_p_bl = FALSE;    // unlisted buffer
1954 	buf->b_locked = TRUE;	// prevent deleting the buffer
1955 
1956 	// Avoid that 'buftype' is reset when this buffer is entered.
1957 	buf->b_p_initialized = TRUE;
1958     }
1959     wp->w_p_wrap = TRUE;	// 'wrap' is default on
1960     wp->w_p_so = 0;		// 'scrolloff' zero
1961 
1962     if (tp != NULL)
1963     {
1964 	// popup on specified tab page
1965 	wp->w_next = tp->tp_first_popupwin;
1966 	tp->tp_first_popupwin = wp;
1967     }
1968     else if (tabnr == 0)
1969     {
1970 	// popup on current tab page
1971 	wp->w_next = curtab->tp_first_popupwin;
1972 	curtab->tp_first_popupwin = wp;
1973     }
1974     else // (tabnr < 0)
1975     {
1976 	win_T *prev = first_popupwin;
1977 
1978 	// Global popup: add at the end, so that it gets displayed on top of
1979 	// older ones with the same zindex. Matters for notifications.
1980 	if (first_popupwin == NULL)
1981 	    first_popupwin = wp;
1982 	else
1983 	{
1984 	    while (prev->w_next != NULL)
1985 		prev = prev->w_next;
1986 	    prev->w_next = wp;
1987 	}
1988     }
1989 
1990     if (new_buffer && argvars != NULL)
1991 	popup_set_buffer_text(buf, argvars[0]);
1992 
1993     if (type == TYPE_ATCURSOR || type == TYPE_PREVIEW)
1994     {
1995 	wp->w_popup_pos = POPPOS_BOTLEFT;
1996     }
1997     if (type == TYPE_ATCURSOR)
1998     {
1999 	popup_set_wantpos_cursor(wp, 0, d);
2000 	set_moved_values(wp);
2001 	set_moved_columns(wp, FIND_STRING);
2002     }
2003 
2004     if (type == TYPE_BEVAL)
2005     {
2006 	wp->w_popup_pos = POPPOS_BOTLEFT;
2007 
2008 	// by default use the mouse position
2009 	wp->w_wantline = mouse_row;
2010 	if (wp->w_wantline <= 0)  // mouse on first line
2011 	{
2012 	    wp->w_wantline = 2;
2013 	    wp->w_popup_pos = POPPOS_TOPLEFT;
2014 	}
2015 	wp->w_wantcol = mouse_col + 1;
2016 	set_mousemoved_values(wp);
2017 	set_mousemoved_columns(wp, FIND_IDENT + FIND_STRING + FIND_EVAL);
2018     }
2019 
2020     // set default values
2021     wp->w_zindex = POPUPWIN_DEFAULT_ZINDEX;
2022     wp->w_popup_close = POPCLOSE_NONE;
2023 
2024     if (type == TYPE_NOTIFICATION)
2025     {
2026 	win_T  *twp, *nextwin;
2027 	int	height = buf->b_ml.ml_line_count + 3;
2028 
2029 	// Try to not overlap with another global popup.  Guess we need 3
2030 	// more screen lines than buffer lines.
2031 	wp->w_wantline = 1;
2032 	for (twp = first_popupwin; twp != NULL; twp = nextwin)
2033 	{
2034 	    nextwin = twp->w_next;
2035 	    if (twp != wp
2036 		    && twp->w_zindex == POPUPWIN_NOTIFICATION_ZINDEX
2037 		    && twp->w_winrow <= wp->w_wantline - 1 + height
2038 		    && twp->w_winrow + popup_height(twp) > wp->w_wantline - 1)
2039 	    {
2040 		// move to below this popup and restart the loop to check for
2041 		// overlap with other popups
2042 		wp->w_wantline = twp->w_winrow + popup_height(twp) + 1;
2043 		nextwin = first_popupwin;
2044 	    }
2045 	}
2046 	if (wp->w_wantline + height > Rows)
2047 	{
2048 	    // can't avoid overlap, put on top in the hope that message goes
2049 	    // away soon.
2050 	    wp->w_wantline = 1;
2051 	}
2052 
2053 	wp->w_wantcol = 10;
2054 	wp->w_zindex = POPUPWIN_NOTIFICATION_ZINDEX;
2055 	wp->w_minwidth = 20;
2056 	wp->w_popup_flags |= POPF_DRAG;
2057 	wp->w_popup_close = POPCLOSE_CLICK;
2058 	for (i = 0; i < 4; ++i)
2059 	    wp->w_popup_border[i] = 1;
2060 	wp->w_popup_padding[1] = 1;
2061 	wp->w_popup_padding[3] = 1;
2062 
2063 	nr = syn_name2id((char_u *)"PopupNotification");
2064 	set_string_option_direct_in_win(wp, (char_u *)"wincolor", -1,
2065 		(char_u *)(nr == 0 ? "WarningMsg" : "PopupNotification"),
2066 		OPT_FREE|OPT_LOCAL, 0);
2067     }
2068 
2069     if (type == TYPE_DIALOG || type == TYPE_MENU)
2070     {
2071 	wp->w_popup_pos = POPPOS_CENTER;
2072 	wp->w_zindex = POPUPWIN_DIALOG_ZINDEX;
2073 	wp->w_popup_flags |= POPF_DRAG;
2074 	wp->w_popup_flags &= ~POPF_MAPPING;
2075 	add_border_left_right_padding(wp);
2076     }
2077 
2078     if (type == TYPE_MENU)
2079     {
2080 	typval_T	tv;
2081 	callback_T	callback;
2082 
2083 	tv.v_type = VAR_STRING;
2084 	tv.vval.v_string = (char_u *)"popup_filter_menu";
2085 	callback = get_callback(&tv);
2086 	if (callback.cb_name != NULL)
2087 	    set_callback(&wp->w_filter_cb, &callback);
2088 
2089 	wp->w_p_wrap = 0;
2090 	wp->w_popup_flags |= POPF_CURSORLINE;
2091     }
2092 
2093     if (type == TYPE_PREVIEW)
2094     {
2095 	wp->w_popup_flags |= POPF_DRAG | POPF_RESIZE;
2096 	wp->w_popup_close = POPCLOSE_BUTTON;
2097 	for (i = 0; i < 4; ++i)
2098 	    wp->w_popup_border[i] = 1;
2099 	parse_previewpopup(wp);
2100 	popup_set_wantpos_cursor(wp, wp->w_minwidth, d);
2101     }
2102 # ifdef FEAT_QUICKFIX
2103     if (type == TYPE_INFO)
2104     {
2105 	wp->w_popup_pos = POPPOS_TOPLEFT;
2106 	wp->w_popup_flags |= POPF_DRAG | POPF_RESIZE;
2107 	wp->w_popup_close = POPCLOSE_BUTTON;
2108 	add_border_left_right_padding(wp);
2109 	parse_completepopup(wp);
2110     }
2111 # endif
2112 
2113     for (i = 0; i < 4; ++i)
2114 	VIM_CLEAR(wp->w_border_highlight[i]);
2115     for (i = 0; i < 8; ++i)
2116 	wp->w_border_char[i] = 0;
2117     wp->w_want_scrollbar = 1;
2118     wp->w_popup_fixed = 0;
2119     wp->w_filter_mode = MODE_ALL;
2120 
2121     if (d != NULL)
2122 	// Deal with options.
2123 	apply_options(wp, d, TRUE);
2124 
2125 #ifdef FEAT_TIMERS
2126     if (type == TYPE_NOTIFICATION && wp->w_popup_timer == NULL)
2127 	popup_add_timeout(wp, 3000);
2128 #endif
2129 
2130     popup_adjust_position(wp);
2131 
2132     wp->w_vsep_width = 0;
2133 
2134     redraw_all_later(NOT_VALID);
2135     popup_mask_refresh = TRUE;
2136 
2137 #ifdef FEAT_TERMINAL
2138     // When running a terminal in the popup it becomes the current window.
2139     if (buf->b_term != NULL)
2140 	win_enter(wp, FALSE);
2141 #endif
2142 
2143     return wp;
2144 }
2145 
2146 /*
2147  * popup_clear()
2148  */
2149     void
f_popup_clear(typval_T * argvars,typval_T * rettv UNUSED)2150 f_popup_clear(typval_T *argvars, typval_T *rettv UNUSED)
2151 {
2152     int force = FALSE;
2153 
2154     if (in_vim9script() && check_for_opt_bool_arg(argvars, 0) == FAIL)
2155 	return;
2156 
2157     if (argvars[0].v_type != VAR_UNKNOWN)
2158 	force = (int)tv_get_bool(&argvars[0]);
2159     close_all_popups(force);
2160 }
2161 
2162 /*
2163  * popup_create({text}, {options})
2164  */
2165     void
f_popup_create(typval_T * argvars,typval_T * rettv)2166 f_popup_create(typval_T *argvars, typval_T *rettv)
2167 {
2168     popup_create(argvars, rettv, TYPE_NORMAL);
2169 }
2170 
2171 /*
2172  * popup_atcursor({text}, {options})
2173  */
2174     void
f_popup_atcursor(typval_T * argvars,typval_T * rettv)2175 f_popup_atcursor(typval_T *argvars, typval_T *rettv)
2176 {
2177     popup_create(argvars, rettv, TYPE_ATCURSOR);
2178 }
2179 
2180 /*
2181  * popup_beval({text}, {options})
2182  */
2183     void
f_popup_beval(typval_T * argvars,typval_T * rettv)2184 f_popup_beval(typval_T *argvars, typval_T *rettv)
2185 {
2186     popup_create(argvars, rettv, TYPE_BEVAL);
2187 }
2188 
2189 /*
2190  * Invoke the close callback for window "wp" with value "result".
2191  * Careful: The callback may make "wp" invalid!
2192  */
2193     static void
invoke_popup_callback(win_T * wp,typval_T * result)2194 invoke_popup_callback(win_T *wp, typval_T *result)
2195 {
2196     typval_T	rettv;
2197     typval_T	argv[3];
2198 
2199     argv[0].v_type = VAR_NUMBER;
2200     argv[0].vval.v_number = (varnumber_T)wp->w_id;
2201 
2202     if (result != NULL && result->v_type != VAR_UNKNOWN)
2203 	copy_tv(result, &argv[1]);
2204     else
2205     {
2206 	argv[1].v_type = VAR_NUMBER;
2207 	argv[1].vval.v_number = 0;
2208     }
2209 
2210     argv[2].v_type = VAR_UNKNOWN;
2211 
2212     call_callback(&wp->w_close_cb, -1, &rettv, 2, argv);
2213     if (result != NULL)
2214 	clear_tv(&argv[1]);
2215     clear_tv(&rettv);
2216 }
2217 
2218 /*
2219  * Make "prevwin" the current window, unless it's equal to "wp".
2220  * Otherwise make "firstwin" the current window.
2221  */
2222     static void
back_to_prevwin(win_T * wp)2223 back_to_prevwin(win_T *wp)
2224 {
2225     if (win_valid(prevwin) && wp != prevwin)
2226 	win_enter(prevwin, FALSE);
2227     else
2228 	win_enter(firstwin, FALSE);
2229 }
2230 
2231 /*
2232  * Close popup "wp" and invoke any close callback for it.
2233  */
2234     static void
popup_close_and_callback(win_T * wp,typval_T * arg)2235 popup_close_and_callback(win_T *wp, typval_T *arg)
2236 {
2237     int id = wp->w_id;
2238 
2239 #ifdef FEAT_TERMINAL
2240     if (wp == curwin && curbuf->b_term != NULL)
2241     {
2242 	win_T *owp;
2243 
2244 	// Closing popup window with a terminal: put focus back on the first
2245 	// that works:
2246 	// - another popup window with a terminal
2247 	// - the previous window
2248 	// - the first one.
2249 	FOR_ALL_POPUPWINS(owp)
2250 	    if (owp != curwin && owp->w_buffer->b_term != NULL)
2251 		break;
2252 	if (owp != NULL)
2253 	    win_enter(owp, FALSE);
2254 	else
2255 	{
2256 	    for (owp = curtab->tp_first_popupwin; owp != NULL;
2257 							     owp = owp->w_next)
2258 		if (owp != curwin && owp->w_buffer->b_term != NULL)
2259 		    break;
2260 	    if (owp != NULL)
2261 		win_enter(owp, FALSE);
2262 	    else
2263 		back_to_prevwin(wp);
2264 	}
2265     }
2266 #endif
2267 
2268     // Just in case a check higher up is missing.
2269     if (wp == curwin && ERROR_IF_POPUP_WINDOW)
2270     {
2271 	// To avoid getting stuck when win_execute() does something that causes
2272 	// an error, stop calling the filter callback.
2273 	free_callback(&wp->w_filter_cb);
2274 
2275 	return;
2276     }
2277 
2278     CHECK_CURBUF;
2279     if (wp->w_close_cb.cb_name != NULL)
2280 	// Careful: This may make "wp" invalid.
2281 	invoke_popup_callback(wp, arg);
2282 
2283     popup_close(id, FALSE);
2284     CHECK_CURBUF;
2285 }
2286 
2287     void
popup_close_with_retval(win_T * wp,int retval)2288 popup_close_with_retval(win_T *wp, int retval)
2289 {
2290     typval_T res;
2291 
2292     res.v_type = VAR_NUMBER;
2293     res.vval.v_number = retval;
2294     popup_close_and_callback(wp, &res);
2295 }
2296 
2297 /*
2298  * Close popup "wp" because of a mouse click.
2299  */
2300     void
popup_close_for_mouse_click(win_T * wp)2301 popup_close_for_mouse_click(win_T *wp)
2302 {
2303     popup_close_with_retval(wp, -2);
2304 }
2305 
2306     static void
check_mouse_moved(win_T * wp,win_T * mouse_wp)2307 check_mouse_moved(win_T *wp, win_T *mouse_wp)
2308 {
2309     // Close the popup when all if these are true:
2310     // - the mouse is not on this popup
2311     // - "mousemoved" was used
2312     // - the mouse is no longer on the same screen row or the mouse column is
2313     //   outside of the relevant text
2314     if (wp != mouse_wp
2315 	    && wp->w_popup_mouse_row != 0
2316 	    && (wp->w_popup_mouse_row != mouse_row
2317 		|| mouse_col < wp->w_popup_mouse_mincol
2318 		|| mouse_col > wp->w_popup_mouse_maxcol))
2319     {
2320 	// Careful: this makes "wp" invalid.
2321 	popup_close_with_retval(wp, -2);
2322     }
2323 }
2324 
2325 /*
2326  * Called when the mouse moved: may close a popup with "mousemoved".
2327  */
2328     void
popup_handle_mouse_moved(void)2329 popup_handle_mouse_moved(void)
2330 {
2331     win_T   *wp, *nextwp;
2332     win_T   *mouse_wp;
2333     int	    row = mouse_row;
2334     int	    col = mouse_col;
2335 
2336     // find the window where the mouse is in
2337     mouse_wp = mouse_find_win(&row, &col, FIND_POPUP);
2338 
2339     for (wp = first_popupwin; wp != NULL; wp = nextwp)
2340     {
2341 	nextwp = wp->w_next;
2342 	check_mouse_moved(wp, mouse_wp);
2343     }
2344     for (wp = curtab->tp_first_popupwin; wp != NULL; wp = nextwp)
2345     {
2346 	nextwp = wp->w_next;
2347 	check_mouse_moved(wp, mouse_wp);
2348     }
2349 }
2350 
2351 /*
2352  * In a filter: check if the typed key is a mouse event that is used for
2353  * dragging the popup.
2354  */
2355     static void
filter_handle_drag(win_T * wp,int c,typval_T * rettv)2356 filter_handle_drag(win_T *wp, int c, typval_T *rettv)
2357 {
2358     int	row = mouse_row;
2359     int	col = mouse_col;
2360 
2361     if ((wp->w_popup_flags & POPF_DRAG)
2362 	    && is_mouse_key(c)
2363 	    && (wp == popup_dragwin
2364 			  || wp == mouse_find_win(&row, &col, FIND_POPUP)))
2365 	// do not consume the key, allow for dragging the popup
2366 	rettv->vval.v_number = 0;
2367 }
2368 
2369 /*
2370  * popup_filter_menu({id}, {key})
2371  */
2372     void
f_popup_filter_menu(typval_T * argvars,typval_T * rettv)2373 f_popup_filter_menu(typval_T *argvars, typval_T *rettv)
2374 {
2375     int		id;
2376     win_T	*wp;
2377     char_u	*key;
2378     typval_T	res;
2379     int		c;
2380     linenr_T	old_lnum;
2381 
2382     if (in_vim9script()
2383 	    && (check_for_number_arg(argvars, 0) == FAIL
2384 		|| check_for_string_arg(argvars, 1) == FAIL))
2385 	return;
2386 
2387     id = tv_get_number(&argvars[0]);
2388     wp = win_id2wp(id);
2389     key = tv_get_string(&argvars[1]);
2390     // If the popup has been closed do not consume the key.
2391     if (wp == NULL)
2392 	return;
2393 
2394     c = *key;
2395     if (c == K_SPECIAL && key[1] != NUL)
2396 	c = TO_SPECIAL(key[1], key[2]);
2397 
2398     // consume all keys until done
2399     rettv->v_type = VAR_BOOL;
2400     rettv->vval.v_number = VVAL_TRUE;
2401     res.v_type = VAR_NUMBER;
2402 
2403     old_lnum = wp->w_cursor.lnum;
2404     if ((c == 'k' || c == 'K' || c == K_UP || c == Ctrl_P)
2405 						      && wp->w_cursor.lnum > 1)
2406 	--wp->w_cursor.lnum;
2407     if ((c == 'j' || c == 'J' || c == K_DOWN || c == Ctrl_N)
2408 		       && wp->w_cursor.lnum < wp->w_buffer->b_ml.ml_line_count)
2409 	++wp->w_cursor.lnum;
2410     if (old_lnum != wp->w_cursor.lnum)
2411     {
2412 	// caller will call popup_highlight_curline()
2413 	return;
2414     }
2415 
2416     if (c == 'x' || c == 'X' || c == ESC || c == Ctrl_C)
2417     {
2418 	// Cancelled, invoke callback with -1
2419 	res.vval.v_number = -1;
2420 	popup_close_and_callback(wp, &res);
2421 	return;
2422     }
2423     if (c == ' ' || c == K_KENTER || c == CAR || c == NL)
2424     {
2425 	// Invoke callback with current index.
2426 	res.vval.v_number = wp->w_cursor.lnum;
2427 	popup_close_and_callback(wp, &res);
2428 	return;
2429     }
2430 
2431     filter_handle_drag(wp, c, rettv);
2432 }
2433 
2434 /*
2435  * popup_filter_yesno({id}, {key})
2436  */
2437     void
f_popup_filter_yesno(typval_T * argvars,typval_T * rettv)2438 f_popup_filter_yesno(typval_T *argvars, typval_T *rettv)
2439 {
2440     int		id;
2441     win_T	*wp;
2442     char_u	*key;
2443     typval_T	res;
2444     int		c;
2445 
2446     if (in_vim9script()
2447 	    && (check_for_number_arg(argvars, 0) == FAIL
2448 		|| check_for_string_arg(argvars, 1) == FAIL))
2449 	return;
2450 
2451     id = tv_get_number(&argvars[0]);
2452     wp = win_id2wp(id);
2453     key = tv_get_string(&argvars[1]);
2454     // If the popup has been closed don't consume the key.
2455     if (wp == NULL)
2456 	return;
2457 
2458     c = *key;
2459     if (c == K_SPECIAL && key[1] != NUL)
2460 	c = TO_SPECIAL(key[1], key[2]);
2461 
2462     // consume all keys until done
2463     rettv->v_type = VAR_BOOL;
2464     rettv->vval.v_number = VVAL_TRUE;
2465 
2466     if (c == 'y' || c == 'Y')
2467 	res.vval.v_number = 1;
2468     else if (c == 'n' || c == 'N' || c == 'x' || c == 'X' || c == ESC)
2469 	res.vval.v_number = 0;
2470     else
2471     {
2472 	filter_handle_drag(wp, c, rettv);
2473 	return;
2474     }
2475 
2476     // Invoke callback
2477     res.v_type = VAR_NUMBER;
2478     popup_close_and_callback(wp, &res);
2479 }
2480 
2481 /*
2482  * popup_dialog({text}, {options})
2483  */
2484     void
f_popup_dialog(typval_T * argvars,typval_T * rettv)2485 f_popup_dialog(typval_T *argvars, typval_T *rettv)
2486 {
2487     popup_create(argvars, rettv, TYPE_DIALOG);
2488 }
2489 
2490 /*
2491  * popup_menu({text}, {options})
2492  */
2493     void
f_popup_menu(typval_T * argvars,typval_T * rettv)2494 f_popup_menu(typval_T *argvars, typval_T *rettv)
2495 {
2496     popup_create(argvars, rettv, TYPE_MENU);
2497 }
2498 
2499 /*
2500  * popup_notification({text}, {options})
2501  */
2502     void
f_popup_notification(typval_T * argvars,typval_T * rettv)2503 f_popup_notification(typval_T *argvars, typval_T *rettv)
2504 {
2505     popup_create(argvars, rettv, TYPE_NOTIFICATION);
2506 }
2507 
2508 /*
2509  * Find the popup window with window-ID "id".
2510  * If the popup window does not exist NULL is returned.
2511  * If the window is not a popup window, and error message is given.
2512  */
2513     static win_T *
find_popup_win(int id)2514 find_popup_win(int id)
2515 {
2516     win_T *wp = win_id2wp(id);
2517 
2518     if (wp != NULL && !WIN_IS_POPUP(wp))
2519     {
2520 	semsg(_("E993: window %d is not a popup window"), id);
2521 	return NULL;
2522     }
2523     return wp;
2524 }
2525 
2526 /*
2527  * popup_close({id})
2528  */
2529     void
f_popup_close(typval_T * argvars,typval_T * rettv UNUSED)2530 f_popup_close(typval_T *argvars, typval_T *rettv UNUSED)
2531 {
2532     int		id;
2533     win_T	*wp;
2534 
2535     if (in_vim9script() && check_for_number_arg(argvars, 0) == FAIL)
2536 	return;
2537 
2538     id = (int)tv_get_number(argvars);
2539     if (
2540 # ifdef FEAT_TERMINAL
2541 	// if the popup contains a terminal it will become hidden
2542 	curbuf->b_term == NULL &&
2543 # endif
2544 	    ERROR_IF_ANY_POPUP_WINDOW)
2545 	return;
2546 
2547     wp = find_popup_win(id);
2548     if (wp != NULL)
2549 	popup_close_and_callback(wp, &argvars[1]);
2550 }
2551 
2552     void
popup_hide(win_T * wp)2553 popup_hide(win_T *wp)
2554 {
2555 #ifdef FEAT_TERMINAL
2556     if (error_if_term_popup_window())
2557 	return;
2558 #endif
2559     if ((wp->w_popup_flags & POPF_HIDDEN) == 0)
2560     {
2561 	wp->w_popup_flags |= POPF_HIDDEN;
2562 	// Do not decrement b_nwindows, we still reference the buffer.
2563 	redraw_all_later(NOT_VALID);
2564 	popup_mask_refresh = TRUE;
2565     }
2566 }
2567 
2568 /*
2569  * popup_hide({id})
2570  */
2571     void
f_popup_hide(typval_T * argvars,typval_T * rettv UNUSED)2572 f_popup_hide(typval_T *argvars, typval_T *rettv UNUSED)
2573 {
2574     int		id;
2575     win_T	*wp;
2576 
2577     if (in_vim9script() && check_for_number_arg(argvars, 0) == FAIL)
2578 	return;
2579 
2580     id = (int)tv_get_number(argvars);
2581     wp = find_popup_win(id);
2582     if (wp != NULL)
2583 	popup_hide(wp);
2584 }
2585 
2586     void
popup_show(win_T * wp)2587 popup_show(win_T *wp)
2588 {
2589     if ((wp->w_popup_flags & POPF_HIDDEN) != 0)
2590     {
2591 	wp->w_popup_flags &= ~POPF_HIDDEN;
2592 	redraw_all_later(NOT_VALID);
2593 	popup_mask_refresh = TRUE;
2594     }
2595 }
2596 
2597 /*
2598  * popup_show({id})
2599  */
2600     void
f_popup_show(typval_T * argvars,typval_T * rettv UNUSED)2601 f_popup_show(typval_T *argvars, typval_T *rettv UNUSED)
2602 {
2603     int		id;
2604     win_T	*wp;
2605 
2606     if (in_vim9script() && check_for_number_arg(argvars, 0) == FAIL)
2607 	return;
2608 
2609     id = (int)tv_get_number(argvars);
2610     wp = find_popup_win(id);
2611     if (wp != NULL)
2612     {
2613 	popup_show(wp);
2614 #ifdef FEAT_QUICKFIX
2615 	if (wp->w_popup_flags & POPF_INFO)
2616 	    pum_position_info_popup(wp);
2617 #endif
2618     }
2619 }
2620 
2621 /*
2622  * popup_settext({id}, {text})
2623  */
2624     void
f_popup_settext(typval_T * argvars,typval_T * rettv UNUSED)2625 f_popup_settext(typval_T *argvars, typval_T *rettv UNUSED)
2626 {
2627     int		id;
2628     win_T	*wp;
2629 
2630     if (in_vim9script()
2631 	    && (check_for_number_arg(argvars, 0) == FAIL
2632 		|| check_for_string_or_list_arg(argvars, 1) == FAIL))
2633 	return;
2634 
2635     id = (int)tv_get_number(&argvars[0]);
2636     wp = find_popup_win(id);
2637     if (wp != NULL)
2638     {
2639 	if (argvars[1].v_type != VAR_STRING && argvars[1].v_type != VAR_LIST)
2640 	    semsg(_(e_invarg2), tv_get_string(&argvars[1]));
2641 	else
2642 	{
2643 	    popup_set_buffer_text(wp->w_buffer, argvars[1]);
2644 	    redraw_win_later(wp, NOT_VALID);
2645 	    popup_adjust_position(wp);
2646 	}
2647     }
2648 }
2649 
2650     static void
popup_free(win_T * wp)2651 popup_free(win_T *wp)
2652 {
2653     sign_undefine_by_name(popup_get_sign_name(wp), FALSE);
2654     wp->w_buffer->b_locked = FALSE;
2655     if (wp->w_winrow + popup_height(wp) >= cmdline_row)
2656 	clear_cmdline = TRUE;
2657     win_free_popup(wp);
2658 
2659     redraw_all_later(NOT_VALID);
2660     popup_mask_refresh = TRUE;
2661 }
2662 
2663     static void
error_for_popup_window(void)2664 error_for_popup_window(void)
2665 {
2666     emsg(_("E994: Not allowed in a popup window"));
2667 }
2668 
2669     int
error_if_popup_window(int also_with_term UNUSED)2670 error_if_popup_window(int also_with_term UNUSED)
2671 {
2672     // win_execute() may set "curwin" to a popup window temporarily, but many
2673     // commands are disallowed then.  When a terminal runs in the popup most
2674     // things are allowed.  When a terminal is finished it can be closed.
2675     if (WIN_IS_POPUP(curwin)
2676 # ifdef FEAT_TERMINAL
2677 	    && (also_with_term || curbuf->b_term == NULL)
2678 	    && !term_is_finished(curbuf)
2679 # endif
2680 	    )
2681     {
2682 	error_for_popup_window();
2683 	return TRUE;
2684     }
2685     return FALSE;
2686 }
2687 
2688 /*
2689  * Close a popup window by Window-id.
2690  * Does not invoke the callback.
2691  * Return OK if the popup was closed, FAIL otherwise.
2692  */
2693     int
popup_close(int id,int force)2694 popup_close(int id, int force)
2695 {
2696     win_T	*wp;
2697     tabpage_T	*tp;
2698     win_T	*prev = NULL;
2699 
2700     // go through global popups
2701     for (wp = first_popupwin; wp != NULL; prev = wp, wp = wp->w_next)
2702 	if (wp->w_id == id)
2703 	{
2704 	    if (wp == curwin)
2705 	    {
2706 		if (!force)
2707 		{
2708 		    error_for_popup_window();
2709 		    return FAIL;
2710 		}
2711 		back_to_prevwin(wp);
2712 	    }
2713 	    if (prev == NULL)
2714 		first_popupwin = wp->w_next;
2715 	    else
2716 		prev->w_next = wp->w_next;
2717 	    popup_free(wp);
2718 	    return OK;
2719 	}
2720 
2721     // go through tab-local popups
2722     FOR_ALL_TABPAGES(tp)
2723 	if (popup_close_tabpage(tp, id, force) == OK)
2724 	    return OK;
2725     return FAIL;
2726 }
2727 
2728 /*
2729  * Close a popup window with Window-id "id" in tabpage "tp".
2730  */
2731     int
popup_close_tabpage(tabpage_T * tp,int id,int force)2732 popup_close_tabpage(tabpage_T *tp, int id, int force)
2733 {
2734     win_T	*wp;
2735     win_T	**root = &tp->tp_first_popupwin;
2736     win_T	*prev = NULL;
2737 
2738     for (wp = *root; wp != NULL; prev = wp, wp = wp->w_next)
2739 	if (wp->w_id == id)
2740 	{
2741 	    if (wp == curwin)
2742 	    {
2743 		if (!force)
2744 		{
2745 		    error_for_popup_window();
2746 		    return FAIL;
2747 		}
2748 		back_to_prevwin(wp);
2749 	    }
2750 	    if (prev == NULL)
2751 		*root = wp->w_next;
2752 	    else
2753 		prev->w_next = wp->w_next;
2754 	    popup_free(wp);
2755 	    return OK;
2756 	}
2757     return FAIL;
2758 }
2759 
2760     void
close_all_popups(int force)2761 close_all_popups(int force)
2762 {
2763     if (!force && ERROR_IF_ANY_POPUP_WINDOW)
2764 	return;
2765     while (first_popupwin != NULL)
2766 	if (popup_close(first_popupwin->w_id, force) == FAIL)
2767 	    return;
2768     while (curtab->tp_first_popupwin != NULL)
2769 	if (popup_close(curtab->tp_first_popupwin->w_id, force) == FAIL)
2770 	    return;
2771 }
2772 
2773 /*
2774  * popup_move({id}, {options})
2775  */
2776     void
f_popup_move(typval_T * argvars,typval_T * rettv UNUSED)2777 f_popup_move(typval_T *argvars, typval_T *rettv UNUSED)
2778 {
2779     dict_T	*dict;
2780     int		id;
2781     win_T	*wp;
2782 
2783     if (in_vim9script()
2784 	    && (check_for_number_arg(argvars, 0) == FAIL
2785 		|| check_for_dict_arg(argvars, 1) == FAIL))
2786 	return;
2787 
2788     id = (int)tv_get_number(argvars);
2789     wp = find_popup_win(id);
2790     if (wp == NULL)
2791 	return;  // invalid {id}
2792 
2793     if (argvars[1].v_type != VAR_DICT || argvars[1].vval.v_dict == NULL)
2794     {
2795 	emsg(_(e_dictreq));
2796 	return;
2797     }
2798     dict = argvars[1].vval.v_dict;
2799 
2800     apply_move_options(wp, dict);
2801 
2802     if (wp->w_winrow + wp->w_height >= cmdline_row)
2803 	clear_cmdline = TRUE;
2804     popup_adjust_position(wp);
2805 }
2806 
2807 /*
2808  * popup_setoptions({id}, {options})
2809  */
2810     void
f_popup_setoptions(typval_T * argvars,typval_T * rettv UNUSED)2811 f_popup_setoptions(typval_T *argvars, typval_T *rettv UNUSED)
2812 {
2813     dict_T	*dict;
2814     int		id;
2815     win_T	*wp;
2816     linenr_T	old_firstline;
2817 
2818     if (in_vim9script()
2819 	    && (check_for_number_arg(argvars, 0) == FAIL
2820 		|| check_for_dict_arg(argvars, 1) == FAIL))
2821 	return;
2822 
2823     id = (int)tv_get_number(argvars);
2824     wp = find_popup_win(id);
2825     if (wp == NULL)
2826 	return;  // invalid {id}
2827 
2828     if (argvars[1].v_type != VAR_DICT || argvars[1].vval.v_dict == NULL)
2829     {
2830 	emsg(_(e_dictreq));
2831 	return;
2832     }
2833     dict = argvars[1].vval.v_dict;
2834     old_firstline = wp->w_firstline;
2835 
2836     apply_options(wp, dict, FALSE);
2837 
2838     if (old_firstline != wp->w_firstline)
2839 	redraw_win_later(wp, NOT_VALID);
2840     popup_adjust_position(wp);
2841 }
2842 
2843 /*
2844  * popup_getpos({id})
2845  */
2846     void
f_popup_getpos(typval_T * argvars,typval_T * rettv)2847 f_popup_getpos(typval_T *argvars, typval_T *rettv)
2848 {
2849     dict_T	*dict;
2850     int		id;
2851     win_T	*wp;
2852     int		top_extra;
2853     int		left_extra;
2854 
2855     if (rettv_dict_alloc(rettv) == OK)
2856     {
2857 	if (in_vim9script() && check_for_number_arg(argvars, 0) == FAIL)
2858 	    return;
2859 
2860 	id = (int)tv_get_number(argvars);
2861 	wp = find_popup_win(id);
2862 	if (wp == NULL)
2863 	    return;  // invalid {id}
2864 	top_extra = popup_top_extra(wp);
2865 	left_extra = wp->w_popup_border[3] + wp->w_popup_padding[3];
2866 
2867 	// we know how much space we need, avoid resizing halfway
2868 	dict = rettv->vval.v_dict;
2869 	hash_lock_size(&dict->dv_hashtab, 11);
2870 
2871 	dict_add_number(dict, "line", wp->w_winrow + 1);
2872 	dict_add_number(dict, "col", wp->w_wincol + 1);
2873 	dict_add_number(dict, "width", wp->w_width + left_extra
2874 			     + wp->w_popup_border[1] + wp->w_popup_padding[1]);
2875 	dict_add_number(dict, "height", wp->w_height + top_extra
2876 			     + wp->w_popup_border[2] + wp->w_popup_padding[2]);
2877 
2878 	dict_add_number(dict, "core_line", wp->w_winrow + 1 + top_extra);
2879 	dict_add_number(dict, "core_col", wp->w_wincol + 1 + left_extra);
2880 	dict_add_number(dict, "core_width", wp->w_width);
2881 	dict_add_number(dict, "core_height", wp->w_height);
2882 
2883 	dict_add_number(dict, "scrollbar", wp->w_has_scrollbar);
2884 	dict_add_number(dict, "firstline", wp->w_topline);
2885 	dict_add_number(dict, "lastline", wp->w_botline - 1);
2886 	dict_add_number(dict, "visible",
2887 		      win_valid(wp) && (wp->w_popup_flags & POPF_HIDDEN) == 0);
2888 
2889 	hash_unlock(&dict->dv_hashtab);
2890     }
2891 }
2892 
2893 /*
2894  * popup_list()
2895  */
2896     void
f_popup_list(typval_T * argvars UNUSED,typval_T * rettv)2897 f_popup_list(typval_T *argvars UNUSED, typval_T *rettv)
2898 {
2899     win_T	*wp;
2900     tabpage_T	*tp;
2901 
2902     if (rettv_list_alloc(rettv) != OK)
2903 	return;
2904     FOR_ALL_POPUPWINS(wp)
2905 	list_append_number(rettv->vval.v_list, wp->w_id);
2906     FOR_ALL_TABPAGES(tp)
2907 	FOR_ALL_POPUPWINS_IN_TAB(tp, wp)
2908 	    list_append_number(rettv->vval.v_list, wp->w_id);
2909 }
2910 
2911 /*
2912  * popup_locate({row}, {col})
2913  */
2914     void
f_popup_locate(typval_T * argvars,typval_T * rettv)2915 f_popup_locate(typval_T *argvars, typval_T *rettv)
2916 {
2917     int		row;
2918     int		col;
2919     win_T	*wp;
2920 
2921     if (in_vim9script()
2922 	    && (check_for_number_arg(argvars, 0) == FAIL
2923 		|| check_for_number_arg(argvars, 1) == FAIL))
2924 	return;
2925 
2926     row = tv_get_number(&argvars[0]) - 1;
2927     col = tv_get_number(&argvars[1]) - 1;
2928     wp = mouse_find_win(&row, &col, FIND_POPUP);
2929     if (wp != NULL && WIN_IS_POPUP(wp))
2930 	rettv->vval.v_number = wp->w_id;
2931 }
2932 
2933 /*
2934  * For popup_getoptions(): add a "border" or "padding" entry to "dict".
2935  */
2936     static void
get_padding_border(dict_T * dict,int * array,char * name)2937 get_padding_border(dict_T *dict, int *array, char *name)
2938 {
2939     list_T  *list;
2940     int	    i;
2941 
2942     if (array[0] == 0 && array[1] == 0 && array[2] == 0 && array[3] == 0)
2943 	return;
2944 
2945     list = list_alloc();
2946     if (list != NULL)
2947     {
2948 	dict_add_list(dict, name, list);
2949 	if (array[0] != 1 || array[1] != 1 || array[2] != 1 || array[3] != 1)
2950 	    for (i = 0; i < 4; ++i)
2951 		list_append_number(list, array[i]);
2952     }
2953 }
2954 
2955 /*
2956  * For popup_getoptions(): add a "borderhighlight" entry to "dict".
2957  */
2958     static void
get_borderhighlight(dict_T * dict,win_T * wp)2959 get_borderhighlight(dict_T *dict, win_T *wp)
2960 {
2961     list_T  *list;
2962     int	    i;
2963 
2964     for (i = 0; i < 4; ++i)
2965 	if (wp->w_border_highlight[i] != NULL)
2966 	    break;
2967     if (i == 4)
2968 	return;
2969 
2970     list = list_alloc();
2971     if (list != NULL)
2972     {
2973 	dict_add_list(dict, "borderhighlight", list);
2974 	for (i = 0; i < 4; ++i)
2975 	    list_append_string(list, wp->w_border_highlight[i], -1);
2976     }
2977 }
2978 
2979 /*
2980  * For popup_getoptions(): add a "borderchars" entry to "dict".
2981  */
2982     static void
get_borderchars(dict_T * dict,win_T * wp)2983 get_borderchars(dict_T *dict, win_T *wp)
2984 {
2985     list_T  *list;
2986     int	    i;
2987     char_u  buf[NUMBUFLEN];
2988     int	    len;
2989 
2990     for (i = 0; i < 8; ++i)
2991 	if (wp->w_border_char[i] != 0)
2992 	    break;
2993     if (i == 8)
2994 	return;
2995 
2996     list = list_alloc();
2997     if (list != NULL)
2998     {
2999 	dict_add_list(dict, "borderchars", list);
3000 	for (i = 0; i < 8; ++i)
3001 	{
3002 	    len = mb_char2bytes(wp->w_border_char[i], buf);
3003 	    list_append_string(list, buf, len);
3004 	}
3005     }
3006 }
3007 
3008 /*
3009  * For popup_getoptions(): add a "moved" and "mousemoved" entry to "dict".
3010  */
3011     static void
get_moved_list(dict_T * dict,win_T * wp)3012 get_moved_list(dict_T *dict, win_T *wp)
3013 {
3014     list_T  *list;
3015 
3016     list = list_alloc();
3017     if (list != NULL)
3018     {
3019 	dict_add_list(dict, "moved", list);
3020 	list_append_number(list, wp->w_popup_lnum);
3021 	list_append_number(list, wp->w_popup_mincol);
3022 	list_append_number(list, wp->w_popup_maxcol);
3023     }
3024     list = list_alloc();
3025     if (list != NULL)
3026     {
3027 	dict_add_list(dict, "mousemoved", list);
3028 	list_append_number(list, wp->w_popup_mouse_row);
3029 	list_append_number(list, wp->w_popup_mouse_mincol);
3030 	list_append_number(list, wp->w_popup_mouse_maxcol);
3031     }
3032 }
3033 
3034 /*
3035  * popup_getoptions({id})
3036  */
3037     void
f_popup_getoptions(typval_T * argvars,typval_T * rettv)3038 f_popup_getoptions(typval_T *argvars, typval_T *rettv)
3039 {
3040     dict_T	*dict;
3041     int		id;
3042     win_T	*wp;
3043     tabpage_T	*tp;
3044     int		i;
3045 
3046     if (rettv_dict_alloc(rettv) == OK)
3047     {
3048 	if (in_vim9script() && check_for_number_arg(argvars, 0) == FAIL)
3049 	    return;
3050 
3051 	id = (int)tv_get_number(argvars);
3052 	wp = find_popup_win(id);
3053 	if (wp == NULL)
3054 	    return;
3055 
3056 	dict = rettv->vval.v_dict;
3057 	dict_add_number(dict, "line", wp->w_wantline);
3058 	dict_add_number(dict, "col", wp->w_wantcol);
3059 	dict_add_number(dict, "minwidth", wp->w_minwidth);
3060 	dict_add_number(dict, "minheight", wp->w_minheight);
3061 	dict_add_number(dict, "maxheight", wp->w_maxheight);
3062 	dict_add_number(dict, "maxwidth", wp->w_maxwidth);
3063 	dict_add_number(dict, "firstline", wp->w_firstline);
3064 	dict_add_number(dict, "scrollbar", wp->w_want_scrollbar);
3065 	dict_add_number(dict, "zindex", wp->w_zindex);
3066 	dict_add_number(dict, "fixed", wp->w_popup_fixed);
3067 	if (wp->w_popup_prop_type && win_valid_any_tab(wp->w_popup_prop_win))
3068 	{
3069 	    proptype_T *pt = text_prop_type_by_id(
3070 				 wp->w_popup_prop_win->w_buffer,
3071 				 wp->w_popup_prop_type);
3072 
3073 	    if (pt != NULL)
3074 		dict_add_string(dict, "textprop", pt->pt_name);
3075 	    dict_add_number(dict, "textpropwin", wp->w_popup_prop_win->w_id);
3076 	    dict_add_number(dict, "textpropid", wp->w_popup_prop_id);
3077 	}
3078 	dict_add_string(dict, "title", wp->w_popup_title);
3079 	dict_add_number(dict, "wrap", wp->w_p_wrap);
3080 	dict_add_number(dict, "drag", (wp->w_popup_flags & POPF_DRAG) != 0);
3081 	dict_add_number(dict, "mapping",
3082 				      (wp->w_popup_flags & POPF_MAPPING) != 0);
3083 	dict_add_number(dict, "resize", (wp->w_popup_flags & POPF_RESIZE) != 0);
3084 	dict_add_number(dict, "posinvert",
3085 				    (wp->w_popup_flags & POPF_POSINVERT) != 0);
3086 	dict_add_number(dict, "cursorline",
3087 				   (wp->w_popup_flags & POPF_CURSORLINE) != 0);
3088 	dict_add_string(dict, "highlight", wp->w_p_wcr);
3089 	if (wp->w_scrollbar_highlight != NULL)
3090 	    dict_add_string(dict, "scrollbarhighlight",
3091 						    wp->w_scrollbar_highlight);
3092 	if (wp->w_thumb_highlight != NULL)
3093 	    dict_add_string(dict, "thumbhighlight", wp->w_thumb_highlight);
3094 
3095 	// find the tabpage that holds this popup
3096 	i = 1;
3097 	FOR_ALL_TABPAGES(tp)
3098 	{
3099 	    win_T *twp;
3100 
3101 	    FOR_ALL_POPUPWINS_IN_TAB(tp, twp)
3102 		if (twp->w_id == id)
3103 		    break;
3104 	    if (twp != NULL)
3105 		break;
3106 	    ++i;
3107 	}
3108 	if (tp == NULL)
3109 	    i = -1;  // must be global
3110 	else if (tp == curtab)
3111 	    i = 0;
3112 	dict_add_number(dict, "tabpage", i);
3113 
3114 	get_padding_border(dict, wp->w_popup_padding, "padding");
3115 	get_padding_border(dict, wp->w_popup_border, "border");
3116 	get_borderhighlight(dict, wp);
3117 	get_borderchars(dict, wp);
3118 	get_moved_list(dict, wp);
3119 
3120 	if (wp->w_filter_cb.cb_name != NULL)
3121 	    dict_add_callback(dict, "filter", &wp->w_filter_cb);
3122 	if (wp->w_close_cb.cb_name != NULL)
3123 	    dict_add_callback(dict, "callback", &wp->w_close_cb);
3124 
3125 	for (i = 0; i < (int)ARRAY_LENGTH(poppos_entries); ++i)
3126 	    if (wp->w_popup_pos == poppos_entries[i].pp_val)
3127 	    {
3128 		dict_add_string(dict, "pos",
3129 					  (char_u *)poppos_entries[i].pp_name);
3130 		break;
3131 	    }
3132 
3133 	dict_add_string(dict, "close", (char_u *)(
3134 		    wp->w_popup_close == POPCLOSE_BUTTON ? "button"
3135 		    : wp->w_popup_close == POPCLOSE_CLICK ? "click" : "none"));
3136 
3137 # if defined(FEAT_TIMERS)
3138 	dict_add_number(dict, "time", wp->w_popup_timer != NULL
3139 				 ?  (long)wp->w_popup_timer->tr_interval : 0L);
3140 # endif
3141     }
3142 }
3143 
3144 # if defined(FEAT_TERMINAL) || defined(PROTO)
3145 /*
3146  * Return TRUE if the current window is running a terminal in a popup window.
3147  * Return FALSE when the job has ended.
3148  */
3149     int
error_if_term_popup_window()3150 error_if_term_popup_window()
3151 {
3152     if (WIN_IS_POPUP(curwin) && curbuf->b_term != NULL
3153 					   && term_job_running(curbuf->b_term))
3154     {
3155 	emsg(_("E863: Not allowed for a terminal in a popup window"));
3156 	return TRUE;
3157     }
3158     return FALSE;
3159 }
3160 # endif
3161 
3162 /*
3163  * Reset all the "handled_flag" flags in global popup windows and popup windows
3164  * in the current tab page.
3165  * Each calling function should use a different flag, see the list at
3166  * POPUP_HANDLED_1.  This won't work with recursive calls though.
3167  */
3168     void
popup_reset_handled(int handled_flag)3169 popup_reset_handled(int handled_flag)
3170 {
3171     win_T *wp;
3172 
3173     FOR_ALL_POPUPWINS(wp)
3174 	wp->w_popup_handled &= ~handled_flag;
3175     FOR_ALL_POPUPWINS_IN_TAB(curtab, wp)
3176 	wp->w_popup_handled &= ~handled_flag;
3177 }
3178 
3179 /*
3180  * Find the next visible popup where "handled_flag" is not set.
3181  * Must have called popup_reset_handled() first.
3182  * When "lowest" is TRUE find the popup with the lowest zindex, otherwise the
3183  * popup with the highest zindex.
3184  */
3185     win_T *
find_next_popup(int lowest,int handled_flag)3186 find_next_popup(int lowest, int handled_flag)
3187 {
3188     win_T   *wp;
3189     win_T   *found_wp;
3190     int	    found_zindex;
3191 
3192     found_zindex = lowest ? INT_MAX : 0;
3193     found_wp = NULL;
3194     FOR_ALL_POPUPWINS(wp)
3195 	if ((wp->w_popup_handled & handled_flag) == 0
3196 		&& (wp->w_popup_flags & POPF_HIDDEN) == 0
3197 		&& (lowest ? wp->w_zindex < found_zindex
3198 		    : wp->w_zindex > found_zindex))
3199 	{
3200 	    found_zindex = wp->w_zindex;
3201 	    found_wp = wp;
3202 	}
3203     FOR_ALL_POPUPWINS_IN_TAB(curtab, wp)
3204 	if ((wp->w_popup_handled & handled_flag) == 0
3205 		&& (wp->w_popup_flags & POPF_HIDDEN) == 0
3206 		&& (lowest ? wp->w_zindex < found_zindex
3207 		    : wp->w_zindex > found_zindex))
3208 	{
3209 	    found_zindex = wp->w_zindex;
3210 	    found_wp = wp;
3211 	}
3212 
3213     if (found_wp != NULL)
3214 	found_wp->w_popup_handled |= handled_flag;
3215     return found_wp;
3216 }
3217 
3218 /*
3219  * Invoke the filter callback for window "wp" with typed character "c".
3220  * Uses the global "mod_mask" for modifiers.
3221  * Returns the return value of the filter or -1 for CTRL-C in the current
3222  * window.
3223  * Careful: The filter may make "wp" invalid!
3224  */
3225     static int
invoke_popup_filter(win_T * wp,int c)3226 invoke_popup_filter(win_T *wp, int c)
3227 {
3228     int		res;
3229     typval_T	rettv;
3230     typval_T	argv[3];
3231     char_u	buf[NUMBUFLEN];
3232     linenr_T	old_lnum = wp->w_cursor.lnum;
3233     int		prev_did_emsg = did_emsg;
3234 
3235     // Emergency exit: CTRL-C closes the popup.
3236     if (c == Ctrl_C)
3237     {
3238 	int save_got_int = got_int;
3239 	int was_curwin = wp == curwin;
3240 
3241 	// Reset got_int to avoid the callback isn't called.
3242 	got_int = FALSE;
3243 	popup_close_with_retval(wp, -1);
3244 	got_int |= save_got_int;
3245 
3246 	// If the popup is the current window it probably fails to close.  Then
3247 	// do not consume the key.
3248 	if (was_curwin && wp == curwin)
3249 	    return -1;
3250 	return TRUE;
3251     }
3252 
3253     argv[0].v_type = VAR_NUMBER;
3254     argv[0].vval.v_number = (varnumber_T)wp->w_id;
3255 
3256     // Convert the number to a string, so that the function can use:
3257     //	    if a:c == "\<F2>"
3258     buf[special_to_buf(c, mod_mask, FALSE, buf)] = NUL;
3259     argv[1].v_type = VAR_STRING;
3260     argv[1].vval.v_string = vim_strsave(buf);
3261 
3262     argv[2].v_type = VAR_UNKNOWN;
3263 
3264     // NOTE: The callback might close the popup and make "wp" invalid.
3265     if (call_callback(&wp->w_filter_cb, -1, &rettv, 2, argv) == FAIL)
3266     {
3267 	// Cannot call the function, close the popup to avoid that the filter
3268 	// eats keys and the user is stuck.  Might as well eat the key.
3269 	popup_close_with_retval(wp, -1);
3270 	res = TRUE;
3271     }
3272     else
3273     {
3274 	if (win_valid_popup(wp) && old_lnum != wp->w_cursor.lnum)
3275 	    popup_highlight_curline(wp);
3276 
3277 	// If an error message was given always return FALSE, so that keys are
3278 	// not consumed and the user can type something.
3279 	// If we get three errors in a row then close the popup.  Decrement the
3280 	// error count by 1/10 if there are no errors, thus allowing up to 1 in
3281 	// 10 calls to cause an error.
3282 	if (win_valid_popup(wp) && did_emsg > prev_did_emsg)
3283 	{
3284 	    wp->w_filter_errors += 10;
3285 	    if (wp->w_filter_errors >= 30)
3286 		popup_close_with_retval(wp, -1);
3287 	    res = FALSE;
3288 	}
3289 	else
3290 	{
3291 	    if (win_valid_popup(wp) && wp->w_filter_errors > 0)
3292 		--wp->w_filter_errors;
3293 	    res = tv_get_bool(&rettv);
3294 	}
3295     }
3296 
3297     vim_free(argv[1].vval.v_string);
3298     clear_tv(&rettv);
3299     return res;
3300 }
3301 
3302 /*
3303  * Called when "c" was typed: invoke popup filter callbacks.
3304  * Returns TRUE when the character was consumed,
3305  */
3306     int
popup_do_filter(int c)3307 popup_do_filter(int c)
3308 {
3309     static int	recursive = FALSE;
3310     int		res = FALSE;
3311     win_T	*wp;
3312     int		save_KeyTyped = KeyTyped;
3313     int		state;
3314     int		was_must_redraw = must_redraw;
3315 
3316 #ifdef FEAT_TERMINAL
3317     // Popup window with terminal always gets focus.
3318     if (popup_is_popup(curwin) && curbuf->b_term != NULL)
3319 	return FALSE;
3320 #endif
3321 
3322     if (recursive)
3323 	return FALSE;
3324     recursive = TRUE;
3325 
3326     if (c == K_LEFTMOUSE)
3327     {
3328 	int row = mouse_row;
3329 	int col = mouse_col;
3330 
3331 	wp = mouse_find_win(&row, &col, FIND_POPUP);
3332 	if (wp != NULL && popup_close_if_on_X(wp, row, col))
3333 	    res = TRUE;
3334     }
3335 
3336     popup_reset_handled(POPUP_HANDLED_2);
3337     state = get_real_state();
3338     while (res == FALSE
3339 		     && (wp = find_next_popup(FALSE, POPUP_HANDLED_2)) != NULL)
3340 	if (wp->w_filter_cb.cb_name != NULL
3341 		&& (wp->w_filter_mode & state) != 0)
3342 	    res = invoke_popup_filter(wp, c);
3343 
3344     if (must_redraw > was_must_redraw)
3345     {
3346 	int save_got_int = got_int;
3347 
3348 	// Reset got_int to avoid a function used in the statusline aborts.
3349 	got_int = FALSE;
3350 	redraw_after_callback(FALSE);
3351 	got_int |= save_got_int;
3352     }
3353     recursive = FALSE;
3354     KeyTyped = save_KeyTyped;
3355 
3356     // When interrupted return FALSE to avoid looping.
3357     return res == -1 ? FALSE : res;
3358 }
3359 
3360 /*
3361  * Return TRUE if there is a popup visible with a filter callback and the
3362  * "mapping" property off.
3363  */
3364     int
popup_no_mapping(void)3365 popup_no_mapping(void)
3366 {
3367     int	    round;
3368     win_T   *wp;
3369 
3370     for (round = 1; round <= 2; ++round)
3371 	for (wp = round == 1 ? first_popupwin : curtab->tp_first_popupwin;
3372 						   wp != NULL; wp = wp->w_next)
3373 	    if (wp->w_filter_cb.cb_name != NULL
3374 		    && (wp->w_popup_flags & (POPF_HIDDEN | POPF_MAPPING)) == 0)
3375 		return TRUE;
3376     return FALSE;
3377 }
3378 
3379 /*
3380  * Called when the cursor moved: check if any popup needs to be closed if the
3381  * cursor moved far enough.
3382  */
3383     void
popup_check_cursor_pos()3384 popup_check_cursor_pos()
3385 {
3386     win_T *wp;
3387 
3388     popup_reset_handled(POPUP_HANDLED_3);
3389     while ((wp = find_next_popup(TRUE, POPUP_HANDLED_3)) != NULL)
3390 	if (wp->w_popup_curwin != NULL
3391 		&& (curwin != wp->w_popup_curwin
3392 		    || curwin->w_cursor.lnum != wp->w_popup_lnum
3393 		    || curwin->w_cursor.col < wp->w_popup_mincol
3394 		    || curwin->w_cursor.col > wp->w_popup_maxcol))
3395 	    popup_close_with_retval(wp, -1);
3396 }
3397 
3398 /*
3399  * Update "w_popup_mask_cells".
3400  */
3401     static void
popup_update_mask(win_T * wp,int width,int height)3402 popup_update_mask(win_T *wp, int width, int height)
3403 {
3404     listitem_T	*lio, *li;
3405     char_u	*cells;
3406     int		row, col;
3407 
3408     if (wp->w_popup_mask == NULL || width == 0 || height == 0)
3409     {
3410 	vim_free(wp->w_popup_mask_cells);
3411 	wp->w_popup_mask_cells = NULL;
3412 	return;
3413     }
3414     if (wp->w_popup_mask_cells != NULL
3415 	    && wp->w_popup_mask_height == height
3416 	    && wp->w_popup_mask_width == width)
3417 	return;  // cache is still valid
3418 
3419     vim_free(wp->w_popup_mask_cells);
3420     wp->w_popup_mask_cells = alloc_clear(width * height);
3421     if (wp->w_popup_mask_cells == NULL)
3422 	return;
3423     cells = wp->w_popup_mask_cells;
3424 
3425     FOR_ALL_LIST_ITEMS(wp->w_popup_mask, lio)
3426     {
3427 	int cols, cole;
3428 	int lines, linee;
3429 
3430 	li = lio->li_tv.vval.v_list->lv_first;
3431 	cols = tv_get_number(&li->li_tv);
3432 	if (cols < 0)
3433 	    cols = width + cols + 1;
3434 	if (cols <= 0)
3435 	    cols = 1;
3436 	li = li->li_next;
3437 	cole = tv_get_number(&li->li_tv);
3438 	if (cole < 0)
3439 	    cole = width + cole + 1;
3440 	if (cole > width)
3441 	    cole = width;
3442 	li = li->li_next;
3443 	lines = tv_get_number(&li->li_tv);
3444 	if (lines < 0)
3445 	    lines = height + lines + 1;
3446 	if (lines <= 0)
3447 	    lines = 1;
3448 	li = li->li_next;
3449 	linee = tv_get_number(&li->li_tv);
3450 	if (linee < 0)
3451 	    linee = height + linee + 1;
3452 	if (linee > height)
3453 	    linee = height;
3454 
3455 	for (row = lines - 1; row < linee; ++row)
3456 	    for (col = cols - 1; col < cole; ++col)
3457 		cells[row * width + col] = 1;
3458     }
3459 }
3460 
3461 /*
3462  * Return TRUE if "col" / "line" matches with an entry in w_popup_mask.
3463  * "col" and "line" are screen coordinates.
3464  */
3465     static int
popup_masked(win_T * wp,int width,int height,int screencol,int screenline)3466 popup_masked(win_T *wp, int width, int height, int screencol, int screenline)
3467 {
3468     int col = screencol - wp->w_wincol + wp->w_popup_leftoff;
3469     int line = screenline - wp->w_winrow;
3470 
3471     return col >= 0 && col < width
3472 	    && line >= 0 && line < height
3473 	    && wp->w_popup_mask_cells[line * width + col];
3474 }
3475 
3476 /*
3477  * Set flags in popup_transparent[] for window "wp" to "val".
3478  */
3479     static void
update_popup_transparent(win_T * wp,int val)3480 update_popup_transparent(win_T *wp, int val)
3481 {
3482     if (wp->w_popup_mask != NULL)
3483     {
3484 	int		width = popup_width(wp);
3485 	int		height = popup_height(wp);
3486 	listitem_T	*lio, *li;
3487 	int		cols, cole;
3488 	int		lines, linee;
3489 	int		col, line;
3490 
3491 	FOR_ALL_LIST_ITEMS(wp->w_popup_mask, lio)
3492 	{
3493 	    li = lio->li_tv.vval.v_list->lv_first;
3494 	    cols = tv_get_number(&li->li_tv);
3495 	    if (cols < 0)
3496 		cols = width + cols + 1;
3497 	    li = li->li_next;
3498 	    cole = tv_get_number(&li->li_tv);
3499 	    if (cole < 0)
3500 		cole = width + cole + 1;
3501 	    li = li->li_next;
3502 	    lines = tv_get_number(&li->li_tv);
3503 	    if (lines < 0)
3504 		lines = height + lines + 1;
3505 	    li = li->li_next;
3506 	    linee = tv_get_number(&li->li_tv);
3507 	    if (linee < 0)
3508 		linee = height + linee + 1;
3509 
3510 	    --cols;
3511 	    cols -= wp->w_popup_leftoff;
3512 	    if (cols < 0)
3513 		cols = 0;
3514 	    cole -= wp->w_popup_leftoff;
3515 	    --lines;
3516 	    if (lines < 0)
3517 		lines = 0;
3518 	    for (line = lines; line < linee
3519 				  && line + wp->w_winrow < screen_Rows; ++line)
3520 		for (col = cols; col < cole
3521 				&& col + wp->w_wincol < screen_Columns; ++col)
3522 		    popup_transparent[(line + wp->w_winrow) * screen_Columns
3523 						   + col + wp->w_wincol] = val;
3524 	}
3525     }
3526 }
3527 
3528 /*
3529  * Only called when popup window "wp" is hidden: If the window is positioned
3530  * next to a text property, and it is now visible, then  unhide the popup.
3531  * We don't check if visible popups become hidden, that is done in
3532  * popup_adjust_position().
3533  * Return TRUE if the popup became unhidden.
3534  */
3535     static int
check_popup_unhidden(win_T * wp)3536 check_popup_unhidden(win_T *wp)
3537 {
3538     if (wp->w_popup_prop_type > 0 && win_valid(wp->w_popup_prop_win))
3539     {
3540 	textprop_T  prop;
3541 	linenr_T    lnum;
3542 
3543 	if (find_visible_prop(wp->w_popup_prop_win,
3544 		    wp->w_popup_prop_type, wp->w_popup_prop_id,
3545 							   &prop, &lnum) == OK)
3546 	{
3547 	    wp->w_popup_flags &= ~POPF_HIDDEN;
3548 	    wp->w_popup_prop_topline = 0; // force repositioning
3549 	    return TRUE;
3550 	}
3551     }
3552     return FALSE;
3553 }
3554 
3555 /*
3556  * Return TRUE if popup_adjust_position() needs to be called for "wp".
3557  * That is when the buffer in the popup was changed, or the popup is following
3558  * a textprop and the referenced buffer was changed.
3559  * Or when the cursor line changed and "cursorline" is set.
3560  */
3561     static int
popup_need_position_adjust(win_T * wp)3562 popup_need_position_adjust(win_T *wp)
3563 {
3564     if (wp->w_popup_last_changedtick != CHANGEDTICK(wp->w_buffer))
3565 	return TRUE;
3566     if (win_valid(wp->w_popup_prop_win)
3567 	    && (wp->w_popup_prop_changedtick
3568 				 != CHANGEDTICK(wp->w_popup_prop_win->w_buffer)
3569 	       || wp->w_popup_prop_topline != wp->w_popup_prop_win->w_topline))
3570 	return TRUE;
3571 
3572     // May need to adjust the width if the cursor moved.
3573     return wp->w_cursor.lnum != wp->w_popup_last_curline;
3574 }
3575 
3576 /*
3577  * Update "popup_mask" if needed.
3578  * Also recomputes the popup size and positions.
3579  * Also updates "popup_visible".
3580  * Also marks window lines for redrawing.
3581  */
3582     void
may_update_popup_mask(int type)3583 may_update_popup_mask(int type)
3584 {
3585     win_T	*wp;
3586     short	*mask;
3587     int		line, col;
3588     int		redraw_all_popups = FALSE;
3589     int		redrawing_all_win;
3590 
3591     // Need to recompute when switching tabs.
3592     // Also recompute when the type is CLEAR or NOT_VALID, something basic
3593     // (such as the screen size) must have changed.
3594     if (popup_mask_tab != curtab || type >= NOT_VALID)
3595     {
3596 	popup_mask_refresh = TRUE;
3597 	redraw_all_popups = TRUE;
3598     }
3599 
3600     // Check if any popup window buffer has changed and if any popup connected
3601     // to a text property has become visible.
3602     FOR_ALL_POPUPWINS(wp)
3603 	if (wp->w_popup_flags & POPF_HIDDEN)
3604 	    popup_mask_refresh |= check_popup_unhidden(wp);
3605 	else if (popup_need_position_adjust(wp))
3606 	    popup_mask_refresh = TRUE;
3607     FOR_ALL_POPUPWINS_IN_TAB(curtab, wp)
3608 	if (wp->w_popup_flags & POPF_HIDDEN)
3609 	    popup_mask_refresh |= check_popup_unhidden(wp);
3610 	else if (popup_need_position_adjust(wp))
3611 	    popup_mask_refresh = TRUE;
3612 
3613     if (!popup_mask_refresh)
3614 	return;
3615 
3616     // Need to update the mask, something has changed.
3617     popup_mask_refresh = FALSE;
3618     popup_mask_tab = curtab;
3619     popup_visible = FALSE;
3620 
3621     // If redrawing all windows, just update "popup_mask".
3622     // If redrawing only what is needed, update "popup_mask_next" and then
3623     // compare with "popup_mask" to see what changed.
3624     redrawing_all_win = TRUE;
3625     FOR_ALL_WINDOWS(wp)
3626 	if (wp->w_redr_type < SOME_VALID)
3627 	    redrawing_all_win = FALSE;
3628     if (redrawing_all_win)
3629 	mask = popup_mask;
3630     else
3631 	mask = popup_mask_next;
3632     vim_memset(mask, 0, screen_Rows * screen_Columns * sizeof(short));
3633 
3634     // Find the window with the lowest zindex that hasn't been handled yet,
3635     // so that the window with a higher zindex overwrites the value in
3636     // popup_mask.
3637     popup_reset_handled(POPUP_HANDLED_4);
3638     while ((wp = find_next_popup(TRUE, POPUP_HANDLED_4)) != NULL)
3639     {
3640 	int width;
3641 	int height;
3642 
3643 	popup_visible = TRUE;
3644 
3645 	// Recompute the position if the text changed.  It may make the popup
3646 	// hidden if it's attach to a text property that is no longer visible.
3647 	if (redraw_all_popups || popup_need_position_adjust(wp))
3648 	{
3649 	    popup_adjust_position(wp);
3650 	    if (wp->w_popup_flags & POPF_HIDDEN)
3651 		continue;
3652 	}
3653 
3654 	width = popup_width(wp);
3655 	height = popup_height(wp);
3656 	popup_update_mask(wp, width, height);
3657 	for (line = wp->w_winrow;
3658 		line < wp->w_winrow + height && line < screen_Rows; ++line)
3659 	    for (col = wp->w_wincol;
3660 		 col < wp->w_wincol + width - wp->w_popup_leftoff
3661 						&& col < screen_Columns; ++col)
3662 		if (wp->w_zindex < POPUPMENU_ZINDEX
3663 			&& pum_visible()
3664 			&& pum_under_menu(line, col, FALSE))
3665 		    mask[line * screen_Columns + col] = POPUPMENU_ZINDEX;
3666 		else if (wp->w_popup_mask_cells == NULL
3667 				|| !popup_masked(wp, width, height, col, line))
3668 		    mask[line * screen_Columns + col] = wp->w_zindex;
3669     }
3670 
3671     // Only check which lines are to be updated if not already
3672     // updating all lines.
3673     if (mask == popup_mask_next)
3674     {
3675 	int	    *plines_cache = ALLOC_CLEAR_MULT(int, Rows);
3676 	win_T	    *prev_wp = NULL;
3677 
3678 	for (line = 0; line < screen_Rows; ++line)
3679 	{
3680 	    int	    col_done = 0;
3681 
3682 	    for (col = 0; col < screen_Columns; ++col)
3683 	    {
3684 		int off = line * screen_Columns + col;
3685 
3686 		if (popup_mask[off] != popup_mask_next[off])
3687 		{
3688 		    popup_mask[off] = popup_mask_next[off];
3689 
3690 		    if (line >= cmdline_row)
3691 		    {
3692 			// the command line needs to be cleared if text below
3693 			// the popup is now visible.
3694 			if (!msg_scrolled && popup_mask_next[off] == 0)
3695 			    clear_cmdline = TRUE;
3696 		    }
3697 		    else if (col >= col_done)
3698 		    {
3699 			linenr_T	lnum;
3700 			int		line_cp = line;
3701 			int		col_cp = col;
3702 
3703 			// The screen position "line" / "col" needs to be
3704 			// redrawn.  Figure out what window that is and update
3705 			// w_redraw_top and w_redr_bot.  Only needs to be done
3706 			// once for each window line.
3707 			wp = mouse_find_win(&line_cp, &col_cp, IGNORE_POPUP);
3708 			if (wp != NULL)
3709 			{
3710 #if defined(FEAT_TERMINAL)
3711 			    // A terminal window needs to be redrawn.
3712 			    if (bt_terminal(wp->w_buffer))
3713 				redraw_win_later(wp, NOT_VALID);
3714 			    else
3715 #endif
3716 			    {
3717 				if (wp != prev_wp)
3718 				{
3719 				    vim_memset(plines_cache, 0,
3720 							   sizeof(int) * Rows);
3721 				    prev_wp = wp;
3722 				}
3723 
3724 				if (line_cp >= wp->w_height)
3725 				    // In (or below) status line
3726 				    wp->w_redr_status = TRUE;
3727 				else
3728 				{
3729 				    // compute the position in the buffer line
3730 				    // from the position in the window
3731 				    mouse_comp_pos(wp, &line_cp, &col_cp,
3732 							  &lnum, plines_cache);
3733 				    redrawWinline(wp, lnum);
3734 				}
3735 			    }
3736 
3737 			    // This line is going to be redrawn, no need to
3738 			    // check until the right side of the window.
3739 			    col_done = wp->w_wincol + wp->w_width - 1;
3740 			}
3741 		    }
3742 		}
3743 	    }
3744 	}
3745 
3746 	vim_free(plines_cache);
3747     }
3748 }
3749 
3750 /*
3751  * If the current window is a popup and something relevant changed, recompute
3752  * the position and size.
3753  */
3754     void
may_update_popup_position(void)3755 may_update_popup_position(void)
3756 {
3757     if (popup_is_popup(curwin) && popup_need_position_adjust(curwin))
3758 	popup_adjust_position(curwin);
3759 }
3760 
3761 /*
3762  * Return a string of "len" spaces in IObuff.
3763  */
3764     static char_u *
get_spaces(int len)3765 get_spaces(int len)
3766 {
3767     vim_memset(IObuff, ' ', (size_t)len);
3768     IObuff[len] = NUL;
3769     return IObuff;
3770 }
3771 
3772 /*
3773  * Update popup windows.  They are drawn on top of normal windows.
3774  * "win_update" is called for each popup window, lowest zindex first.
3775  */
3776     void
update_popups(void (* win_update)(win_T * wp))3777 update_popups(void (*win_update)(win_T *wp))
3778 {
3779     win_T   *wp;
3780     int	    top_off;
3781     int	    left_extra;
3782     int	    total_width;
3783     int	    total_height;
3784     int	    top_padding;
3785     int	    popup_attr;
3786     int	    border_attr[4];
3787     int	    border_char[8];
3788     char_u  buf[MB_MAXBYTES];
3789     int	    row;
3790     int	    wincol;
3791     int	    padcol = 0;
3792     int	    padendcol = 0;
3793     int	    i;
3794     int	    sb_thumb_top = 0;
3795     int	    sb_thumb_height = 0;
3796     int	    attr_scroll = 0;
3797     int	    attr_thumb = 0;
3798 
3799     // hide the cursor until redrawing is done.
3800     cursor_off();
3801 
3802     // Find the window with the lowest zindex that hasn't been updated yet,
3803     // so that the window with a higher zindex is drawn later, thus goes on
3804     // top.
3805     popup_reset_handled(POPUP_HANDLED_5);
3806     while ((wp = find_next_popup(TRUE, POPUP_HANDLED_5)) != NULL)
3807     {
3808 	int	    title_len = 0;
3809 	int	    title_wincol;
3810 
3811 	// This drawing uses the zindex of the popup window, so that it's on
3812 	// top of the text but doesn't draw when another popup with higher
3813 	// zindex is on top of the character.
3814 	screen_zindex = wp->w_zindex;
3815 
3816 	// Set flags in popup_transparent[] for masked cells.
3817 	update_popup_transparent(wp, 1);
3818 
3819 	// adjust w_winrow and w_wincol for border and padding, since
3820 	// win_update() doesn't handle them.
3821 	top_off = popup_top_extra(wp);
3822 	left_extra = wp->w_popup_padding[3] + wp->w_popup_border[3]
3823 							 - wp->w_popup_leftoff;
3824 	if (wp->w_wincol + left_extra < 0)
3825 	    left_extra = -wp->w_wincol;
3826 	wp->w_winrow += top_off;
3827 	wp->w_wincol += left_extra;
3828 
3829 	// Draw the popup text, unless it's off screen.
3830 	if (wp->w_winrow < screen_Rows && wp->w_wincol < screen_Columns)
3831 	{
3832 	    // May need to update the "cursorline" highlighting, which may also
3833 	    // change "topline"
3834 	    if (wp->w_popup_last_curline != wp->w_cursor.lnum)
3835 		popup_highlight_curline(wp);
3836 
3837 	    win_update(wp);
3838 
3839 	    // move the cursor into the visible lines, otherwise executing
3840 	    // commands with win_execute() may cause the text to jump.
3841 	    if (wp->w_cursor.lnum < wp->w_topline)
3842 		wp->w_cursor.lnum = wp->w_topline;
3843 	    else if (wp->w_cursor.lnum >= wp->w_botline)
3844 		wp->w_cursor.lnum = wp->w_botline - 1;
3845 	}
3846 
3847 	wp->w_winrow -= top_off;
3848 	wp->w_wincol -= left_extra;
3849 
3850 	// Add offset for border and padding if not done already.
3851 	if ((wp->w_flags & WFLAG_WCOL_OFF_ADDED) == 0)
3852 	{
3853 	    wp->w_wcol += left_extra;
3854 	    wp->w_flags |= WFLAG_WCOL_OFF_ADDED;
3855 	}
3856 	if ((wp->w_flags & WFLAG_WROW_OFF_ADDED) == 0)
3857 	{
3858 	    wp->w_wrow += top_off;
3859 	    wp->w_flags |= WFLAG_WROW_OFF_ADDED;
3860 	}
3861 
3862 	total_width = popup_width(wp);
3863 	total_height = popup_height(wp);
3864 	popup_attr = get_wcr_attr(wp);
3865 
3866 	if (wp->w_winrow + total_height > cmdline_row)
3867 	    wp->w_popup_flags |= POPF_ON_CMDLINE;
3868 	else
3869 	    wp->w_popup_flags &= ~POPF_ON_CMDLINE;
3870 
3871 
3872 	// We can only use these line drawing characters when 'encoding' is
3873 	// "utf-8" and 'ambiwidth' is "single".
3874 	if (enc_utf8 && *p_ambw == 's')
3875 	{
3876 	    border_char[0] = border_char[2] = 0x2550;
3877 	    border_char[1] = border_char[3] = 0x2551;
3878 	    border_char[4] = 0x2554;
3879 	    border_char[5] = 0x2557;
3880 	    border_char[6] = (wp->w_popup_flags & POPF_RESIZE)
3881 							     ? 0x21f2 : 0x255d;
3882 	    border_char[7] = 0x255a;
3883 	}
3884 	else
3885 	{
3886 	    border_char[0] = border_char[2] = '-';
3887 	    border_char[1] = border_char[3] = '|';
3888 	    for (i = 4; i < 8; ++i)
3889 		border_char[i] = '+';
3890 	    if (wp->w_popup_flags & POPF_RESIZE)
3891 		border_char[6] = '@';
3892 	}
3893 	for (i = 0; i < 8; ++i)
3894 	    if (wp->w_border_char[i] != 0)
3895 		border_char[i] = wp->w_border_char[i];
3896 
3897 	for (i = 0; i < 4; ++i)
3898 	{
3899 	    border_attr[i] = popup_attr;
3900 	    if (wp->w_border_highlight[i] != NULL)
3901 		border_attr[i] = syn_name2attr(wp->w_border_highlight[i]);
3902 	}
3903 
3904 	// Title goes on top of border or padding.
3905 	title_wincol = wp->w_wincol + 1;
3906 	if (wp->w_popup_title != NULL)
3907 	{
3908 	    title_len = vim_strsize(wp->w_popup_title);
3909 
3910 	    // truncate the title if too long
3911 	    if (title_len > total_width - 2)
3912 	    {
3913 		int	title_byte_len = (int)STRLEN(wp->w_popup_title);
3914 		char_u  *title_text = alloc(title_byte_len + 1);
3915 
3916 		if (title_text != NULL)
3917 		{
3918 		    trunc_string(wp->w_popup_title, title_text,
3919 					  total_width - 2, title_byte_len + 1);
3920 		    screen_puts(title_text, wp->w_winrow, title_wincol,
3921 				  wp->w_popup_border[0] > 0
3922 						? border_attr[0] : popup_attr);
3923 		    vim_free(title_text);
3924 		}
3925 
3926 		title_len = total_width - 2;
3927 	    }
3928 	    else
3929 		screen_puts(wp->w_popup_title, wp->w_winrow, title_wincol,
3930 		      wp->w_popup_border[0] > 0 ? border_attr[0] : popup_attr);
3931 	}
3932 
3933 	wincol = wp->w_wincol - wp->w_popup_leftoff;
3934 	top_padding = wp->w_popup_padding[0];
3935 	if (wp->w_popup_border[0] > 0)
3936 	{
3937 	    // top border; do not draw over the title
3938 	    if (title_len > 0)
3939 	    {
3940 		screen_fill(wp->w_winrow, wp->w_winrow + 1,
3941 			wincol < 0 ? 0 : wincol, title_wincol,
3942 			wp->w_popup_border[3] != 0 && wp->w_popup_leftoff == 0
3943 					     ? border_char[4] : border_char[0],
3944 			border_char[0], border_attr[0]);
3945 		screen_fill(wp->w_winrow, wp->w_winrow + 1,
3946 			title_wincol + title_len, wincol + total_width,
3947 			border_char[0], border_char[0], border_attr[0]);
3948 	    }
3949 	    else
3950 	    {
3951 		screen_fill(wp->w_winrow, wp->w_winrow + 1,
3952 			wincol < 0 ? 0 : wincol, wincol + total_width,
3953 			wp->w_popup_border[3] != 0 && wp->w_popup_leftoff == 0
3954 					     ? border_char[4] : border_char[0],
3955 			border_char[0], border_attr[0]);
3956 	    }
3957 	    if (wp->w_popup_border[1] > 0 && wp->w_popup_rightoff == 0)
3958 	    {
3959 		buf[mb_char2bytes(border_char[5], buf)] = NUL;
3960 		screen_puts(buf, wp->w_winrow,
3961 			       wincol + total_width - 1, border_attr[1]);
3962 	    }
3963 	}
3964 	else if (wp->w_popup_padding[0] == 0 && popup_top_extra(wp) > 0)
3965 	    top_padding = 1;
3966 
3967 	if (top_padding > 0 || wp->w_popup_padding[2] > 0)
3968 	{
3969 	    padcol = wincol + wp->w_popup_border[3];
3970 	    padendcol = wp->w_wincol + total_width - wp->w_popup_border[1]
3971 							 - wp->w_has_scrollbar;
3972 	    if (padcol < 0)
3973 	    {
3974 		padendcol += padcol;
3975 		padcol = 0;
3976 	    }
3977 	}
3978 	if (top_padding > 0)
3979 	{
3980 	    row = wp->w_winrow + wp->w_popup_border[0];
3981 	    if (title_len > 0 && row == wp->w_winrow)
3982 	    {
3983 		// top padding and no border; do not draw over the title
3984 		screen_fill(row, row + 1, padcol, title_wincol,
3985 							 ' ', ' ', popup_attr);
3986 		screen_fill(row, row + 1, title_wincol + title_len,
3987 					      padendcol, ' ', ' ', popup_attr);
3988 		row += 1;
3989 		top_padding -= 1;
3990 	    }
3991 	    screen_fill(row, row + top_padding, padcol, padendcol,
3992 							 ' ', ' ', popup_attr);
3993 	}
3994 
3995 	// Compute scrollbar thumb position and size.
3996 	if (wp->w_has_scrollbar)
3997 	{
3998 	    linenr_T	linecount = wp->w_buffer->b_ml.ml_line_count;
3999 	    int		height = wp->w_height;
4000 
4001 	    sb_thumb_height = (height * height + linecount / 2) / linecount;
4002 	    if (wp->w_topline > 1 && sb_thumb_height == height)
4003 		--sb_thumb_height;  // scrolled, no full thumb
4004 	    if (sb_thumb_height == 0)
4005 		sb_thumb_height = 1;
4006 	    if (linecount <= wp->w_height)
4007 		// it just fits, avoid divide by zero
4008 		sb_thumb_top = 0;
4009 	    else
4010 		sb_thumb_top = (wp->w_topline - 1
4011 				+ (linecount / wp->w_height) / 2)
4012 				* (wp->w_height - sb_thumb_height)
4013 						  / (linecount - wp->w_height);
4014 	    if (wp->w_topline > 1 && sb_thumb_top == 0 && height > 1)
4015 		sb_thumb_top = 1;  // show it's scrolled
4016 
4017 	    if (wp->w_scrollbar_highlight != NULL)
4018 		attr_scroll = syn_name2attr(wp->w_scrollbar_highlight);
4019 	    else
4020 		attr_scroll = highlight_attr[HLF_PSB];
4021 	    if (wp->w_thumb_highlight != NULL)
4022 		attr_thumb = syn_name2attr(wp->w_thumb_highlight);
4023 	    else
4024 		attr_thumb = highlight_attr[HLF_PST];
4025 	}
4026 
4027 	for (i = wp->w_popup_border[0];
4028 				 i < total_height - wp->w_popup_border[2]; ++i)
4029 	{
4030 	    int	pad_left;
4031 	    // left and right padding only needed next to the body
4032 	    int do_padding =
4033 		    i >= wp->w_popup_border[0] + wp->w_popup_padding[0]
4034 		    && i < total_height - wp->w_popup_border[2]
4035 						 - wp->w_popup_padding[2];
4036 
4037 	    row = wp->w_winrow + i;
4038 
4039 	    // left border
4040 	    if (wp->w_popup_border[3] > 0 && wincol >= 0)
4041 	    {
4042 		buf[mb_char2bytes(border_char[3], buf)] = NUL;
4043 		screen_puts(buf, row, wincol, border_attr[3]);
4044 	    }
4045 	    if (do_padding && wp->w_popup_padding[3] > 0)
4046 	    {
4047 		int col = wincol + wp->w_popup_border[3];
4048 
4049 		// left padding
4050 		pad_left = wp->w_popup_padding[3];
4051 		if (col < 0)
4052 		{
4053 		    pad_left += col;
4054 		    col = 0;
4055 		}
4056 		if (pad_left > 0)
4057 		    screen_puts(get_spaces(pad_left), row, col, popup_attr);
4058 	    }
4059 	    // scrollbar
4060 	    if (wp->w_has_scrollbar)
4061 	    {
4062 		int line = i - top_off;
4063 		int scroll_col = wp->w_wincol + total_width - 1
4064 						       - wp->w_popup_border[1];
4065 
4066 		if (line >= 0 && line < wp->w_height)
4067 		    screen_putchar(' ', row, scroll_col,
4068 			    line >= sb_thumb_top
4069 				       && line < sb_thumb_top + sb_thumb_height
4070 						  ? attr_thumb : attr_scroll);
4071 		else
4072 		    screen_putchar(' ', row, scroll_col, popup_attr);
4073 	    }
4074 	    // right border
4075 	    if (wp->w_popup_border[1] > 0)
4076 	    {
4077 		buf[mb_char2bytes(border_char[1], buf)] = NUL;
4078 		screen_puts(buf, row, wincol + total_width - 1, border_attr[1]);
4079 	    }
4080 	    // right padding
4081 	    if (do_padding && wp->w_popup_padding[1] > 0)
4082 		screen_puts(get_spaces(wp->w_popup_padding[1]), row,
4083 			wincol + wp->w_popup_border[3]
4084 			+ wp->w_popup_padding[3] + wp->w_width + wp->w_leftcol,
4085 			popup_attr);
4086 	}
4087 
4088 	if (wp->w_popup_padding[2] > 0)
4089 	{
4090 	    // bottom padding
4091 	    row = wp->w_winrow + wp->w_popup_border[0]
4092 				       + wp->w_popup_padding[0] + wp->w_height;
4093 	    screen_fill(row, row + wp->w_popup_padding[2],
4094 				       padcol, padendcol, ' ', ' ', popup_attr);
4095 	}
4096 
4097 	if (wp->w_popup_border[2] > 0)
4098 	{
4099 	    // bottom border
4100 	    row = wp->w_winrow + total_height - 1;
4101 	    screen_fill(row , row + 1,
4102 		    wincol < 0 ? 0 : wincol,
4103 		    wincol + total_width,
4104 		    wp->w_popup_border[3] != 0 && wp->w_popup_leftoff == 0
4105 					     ? border_char[7] : border_char[2],
4106 		    border_char[2], border_attr[2]);
4107 	    if (wp->w_popup_border[1] > 0)
4108 	    {
4109 		buf[mb_char2bytes(border_char[6], buf)] = NUL;
4110 		screen_puts(buf, row, wincol + total_width - 1, border_attr[2]);
4111 	    }
4112 	}
4113 
4114 	if (wp->w_popup_close == POPCLOSE_BUTTON)
4115 	{
4116 	    // close button goes on top of anything at the top-right corner
4117 	    buf[mb_char2bytes('X', buf)] = NUL;
4118 	    screen_puts(buf, wp->w_winrow, wincol + total_width - 1,
4119 		      wp->w_popup_border[0] > 0 ? border_attr[0] : popup_attr);
4120 	}
4121 
4122 	update_popup_transparent(wp, 0);
4123 
4124 	// Back to the normal zindex.
4125 	screen_zindex = 0;
4126     }
4127 
4128 #if defined(FEAT_SEARCH_EXTRA)
4129     // In case win_update() called start_search_hl().
4130     end_search_hl();
4131 #endif
4132 }
4133 
4134 /*
4135  * Mark references in callbacks of one popup window.
4136  */
4137     static int
set_ref_in_one_popup(win_T * wp,int copyID)4138 set_ref_in_one_popup(win_T *wp, int copyID)
4139 {
4140     int		abort = FALSE;
4141     typval_T	tv;
4142 
4143     if (wp->w_close_cb.cb_partial != NULL)
4144     {
4145 	tv.v_type = VAR_PARTIAL;
4146 	tv.vval.v_partial = wp->w_close_cb.cb_partial;
4147 	abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
4148     }
4149     if (wp->w_filter_cb.cb_partial != NULL)
4150     {
4151 	tv.v_type = VAR_PARTIAL;
4152 	tv.vval.v_partial = wp->w_filter_cb.cb_partial;
4153 	abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
4154     }
4155     abort = abort || set_ref_in_list(wp->w_popup_mask, copyID);
4156     return abort;
4157 }
4158 
4159 /*
4160  * Set reference in callbacks of popup windows.
4161  */
4162     int
set_ref_in_popups(int copyID)4163 set_ref_in_popups(int copyID)
4164 {
4165     int		abort = FALSE;
4166     win_T	*wp;
4167     tabpage_T	*tp;
4168 
4169     for (wp = first_popupwin; !abort && wp != NULL; wp = wp->w_next)
4170 	abort = abort || set_ref_in_one_popup(wp, copyID);
4171 
4172     FOR_ALL_TABPAGES(tp)
4173     {
4174 	for (wp = tp->tp_first_popupwin; !abort && wp != NULL; wp = wp->w_next)
4175 	    abort = abort || set_ref_in_one_popup(wp, copyID);
4176 	if (abort)
4177 	    break;
4178     }
4179     return abort;
4180 }
4181 
4182     int
popup_is_popup(win_T * wp)4183 popup_is_popup(win_T *wp)
4184 {
4185     return wp->w_popup_flags != 0;
4186 }
4187 
4188 #if defined(FEAT_QUICKFIX) || defined(PROTO)
4189 /*
4190  * Find an existing popup used as the preview window, in the current tab page.
4191  * Return NULL if not found.
4192  */
4193     win_T *
popup_find_preview_window(void)4194 popup_find_preview_window(void)
4195 {
4196     win_T *wp;
4197 
4198     // Preview window popup is always local to tab page.
4199     FOR_ALL_POPUPWINS_IN_TAB(curtab, wp)
4200 	if (wp->w_p_pvw)
4201 	    return wp;
4202     return NULL;
4203 }
4204 
4205 /*
4206  * Find an existing popup used as the info window, in the current tab page.
4207  * Return NULL if not found.
4208  */
4209     win_T *
popup_find_info_window(void)4210 popup_find_info_window(void)
4211 {
4212     win_T *wp;
4213 
4214     // info window popup is always local to tab page.
4215     FOR_ALL_POPUPWINS_IN_TAB(curtab, wp)
4216 	if (wp->w_popup_flags & POPF_INFO)
4217 	    return wp;
4218     return NULL;
4219 }
4220 #endif
4221 
4222     void
f_popup_findinfo(typval_T * argvars UNUSED,typval_T * rettv)4223 f_popup_findinfo(typval_T *argvars UNUSED, typval_T *rettv)
4224 {
4225 #ifdef FEAT_QUICKFIX
4226     win_T   *wp = popup_find_info_window();
4227 
4228     rettv->vval.v_number = wp == NULL ? 0 : wp->w_id;
4229 #else
4230     rettv->vval.v_number = 0;
4231 #endif
4232 }
4233 
4234     void
f_popup_findpreview(typval_T * argvars UNUSED,typval_T * rettv)4235 f_popup_findpreview(typval_T *argvars UNUSED, typval_T *rettv)
4236 {
4237 #ifdef FEAT_QUICKFIX
4238     win_T   *wp = popup_find_preview_window();
4239 
4240     rettv->vval.v_number = wp == NULL ? 0 : wp->w_id;
4241 #else
4242     rettv->vval.v_number = 0;
4243 #endif
4244 }
4245 
4246 #if defined(FEAT_QUICKFIX) || defined(PROTO)
4247 /*
4248  * Create a popup to be used as the preview or info window.
4249  * NOTE: this makes the popup the current window, so that the file can be
4250  * edited.  However, it must not remain to be the current window, the caller
4251  * must make sure of that.
4252  */
4253     int
popup_create_preview_window(int info)4254 popup_create_preview_window(int info)
4255 {
4256     win_T *wp = popup_create(NULL, NULL, info ? TYPE_INFO : TYPE_PREVIEW);
4257 
4258     if (wp == NULL)
4259 	return FAIL;
4260     if (info)
4261 	wp->w_popup_flags |= POPF_INFO;
4262     else
4263 	wp->w_p_pvw = TRUE;
4264 
4265     // Set the width to a reasonable value, so that w_topline can be computed.
4266     if (wp->w_minwidth > 0)
4267 	wp->w_width = wp->w_minwidth;
4268     else if (wp->w_maxwidth > 0)
4269 	wp->w_width = wp->w_maxwidth;
4270     else
4271 	wp->w_width = curwin->w_width;
4272 
4273     // Will switch to another buffer soon, dummy one can be wiped.
4274     wp->w_buffer->b_locked = FALSE;
4275 
4276     win_enter(wp, FALSE);
4277     return OK;
4278 }
4279 
4280 /*
4281  * Close any preview popup.
4282  */
4283     void
popup_close_preview(void)4284 popup_close_preview(void)
4285 {
4286     win_T *wp = popup_find_preview_window();
4287 
4288     if (wp != NULL)
4289 	popup_close_with_retval(wp, -1);
4290 }
4291 
4292 /*
4293  * Hide the info popup.
4294  */
4295     void
popup_hide_info(void)4296 popup_hide_info(void)
4297 {
4298     win_T *wp = popup_find_info_window();
4299 
4300     if (wp != NULL)
4301 	popup_hide(wp);
4302 }
4303 
4304 /*
4305  * Close any info popup.
4306  */
4307     void
popup_close_info(void)4308 popup_close_info(void)
4309 {
4310     win_T *wp = popup_find_info_window();
4311 
4312     if (wp != NULL)
4313 	popup_close_with_retval(wp, -1);
4314 }
4315 #endif
4316 
4317 /*
4318  * Close any popup for a text property associated with "win".
4319  * Return TRUE if a popup was closed.
4320  */
4321     int
popup_win_closed(win_T * win)4322 popup_win_closed(win_T *win)
4323 {
4324     int	    round;
4325     win_T   *wp;
4326     win_T   *next;
4327     int	    ret = FALSE;
4328 
4329     for (round = 1; round <= 2; ++round)
4330 	for (wp = round == 1 ? first_popupwin : curtab->tp_first_popupwin;
4331 							 wp != NULL; wp = next)
4332 	{
4333 	    next = wp->w_next;
4334 	    if (wp->w_popup_prop_win == win)
4335 	    {
4336 		popup_close_with_retval(wp, -1);
4337 		ret = TRUE;
4338 	    }
4339 	}
4340     return ret;
4341 }
4342 
4343 /*
4344  * Set the title of the popup window to the file name.
4345  */
4346     void
popup_set_title(win_T * wp)4347 popup_set_title(win_T *wp)
4348 {
4349     if (wp->w_buffer->b_fname != NULL)
4350     {
4351 	char_u	dirname[MAXPATHL];
4352 	size_t	len;
4353 
4354 	mch_dirname(dirname, MAXPATHL);
4355 	shorten_buf_fname(wp->w_buffer, dirname, FALSE);
4356 
4357 	vim_free(wp->w_popup_title);
4358 	len = STRLEN(wp->w_buffer->b_fname) + 3;
4359 	wp->w_popup_title = alloc((int)len);
4360 	if (wp->w_popup_title != NULL)
4361 	    vim_snprintf((char *)wp->w_popup_title, len, " %s ",
4362 							wp->w_buffer->b_fname);
4363 	redraw_win_later(wp, VALID);
4364     }
4365 }
4366 
4367 # if defined(FEAT_QUICKFIX) || defined(PROTO)
4368 /*
4369  * If there is a preview window, update the title.
4370  * Used after changing directory.
4371  */
4372     void
popup_update_preview_title(void)4373 popup_update_preview_title(void)
4374 {
4375     win_T *wp = popup_find_preview_window();
4376 
4377     if (wp != NULL)
4378 	popup_set_title(wp);
4379 }
4380 # endif
4381 
4382 #endif // FEAT_PROP_POPUP
4383