blob: b6bb593ea9c57e9b8f8feec481e3409987ffff5c [file] [log] [blame]
Bram Moolenaar4d784b22019-05-25 19:51:39 +02001/* 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#ifdef FEAT_TEXT_PROP
17
Bram Moolenaarac1f1bc2019-05-30 21:24:26 +020018typedef struct {
19 char *pp_name;
20 poppos_T pp_val;
21} poppos_entry_T;
22
23static 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
Bram Moolenaar4d784b22019-05-25 19:51:39 +020031/*
Bram Moolenaarb0ebbda2019-06-02 16:51:21 +020032 * Get option value for "key", which is "line" or "col".
Bram Moolenaarcc31ad92019-05-30 19:25:06 +020033 * Handles "cursor+N" and "cursor-N".
34 */
35 static int
Bram Moolenaarac1f1bc2019-05-30 21:24:26 +020036popup_options_one(dict_T *dict, char_u *key)
Bram Moolenaarcc31ad92019-05-30 19:25:06 +020037{
38 dictitem_T *di;
39 char_u *val;
40 char_u *s;
41 char_u *endp;
42 int n = 0;
43
44 di = dict_find(dict, key, -1);
45 if (di == NULL)
46 return 0;
47
48 val = tv_get_string(&di->di_tv);
49 if (STRNCMP(val, "cursor", 6) != 0)
Bram Moolenaarb0ebbda2019-06-02 16:51:21 +020050 return dict_get_number_check(dict, key);
Bram Moolenaarcc31ad92019-05-30 19:25:06 +020051
52 setcursor_mayforce(TRUE);
53 s = val + 6;
54 if (*s != NUL)
55 {
Bram Moolenaarb0ebbda2019-06-02 16:51:21 +020056 endp = s;
57 if (*skipwhite(s) == '+' || *skipwhite(s) == '-')
58 n = strtol((char *)s, (char **)&endp, 10);
Bram Moolenaarcc31ad92019-05-30 19:25:06 +020059 if (endp != NULL && *skipwhite(endp) != NUL)
60 {
61 semsg(_(e_invexpr2), val);
62 return 0;
63 }
64 }
65
66 if (STRCMP(key, "line") == 0)
67 n = screen_screenrow() + 1 + n;
68 else // "col"
69 n = screen_screencol() + 1 + n;
70
71 if (n < 1)
72 n = 1;
73 return n;
74}
75
Bram Moolenaarac1f1bc2019-05-30 21:24:26 +020076 static void
77get_pos_options(win_T *wp, dict_T *dict)
78{
79 char_u *str;
80 int nr;
81
82 nr = popup_options_one(dict, (char_u *)"line");
83 if (nr > 0)
84 wp->w_wantline = nr;
85 nr = popup_options_one(dict, (char_u *)"col");
86 if (nr > 0)
87 wp->w_wantcol = nr;
88
Bram Moolenaar042fb4b2019-06-02 14:49:56 +020089 wp->w_popup_fixed = dict_get_number(dict, (char_u *)"fixed") != 0;
90
Bram Moolenaarac1f1bc2019-05-30 21:24:26 +020091 str = dict_get_string(dict, (char_u *)"pos", FALSE);
92 if (str != NULL)
93 {
94 for (nr = 0;
95 nr < (int)(sizeof(poppos_entries) / sizeof(poppos_entry_T));
96 ++nr)
97 if (STRCMP(str, poppos_entries[nr].pp_name) == 0)
98 {
99 wp->w_popup_pos = poppos_entries[nr].pp_val;
100 nr = -1;
101 break;
102 }
103 if (nr != -1)
104 semsg(_(e_invarg2), str);
105 }
106}
107
Bram Moolenaar2fd8e352019-06-01 20:16:48 +0200108 static void
Bram Moolenaarae943152019-06-16 22:54:14 +0200109set_padding_border(dict_T *dict, int *array, char *name, int max_val)
Bram Moolenaar2fd8e352019-06-01 20:16:48 +0200110{
111 dictitem_T *di;
112
Bram Moolenaar2fd8e352019-06-01 20:16:48 +0200113 di = dict_find(dict, (char_u *)name, -1);
114 if (di != NULL)
115 {
116 if (di->di_tv.v_type != VAR_LIST)
117 emsg(_(e_listreq));
118 else
119 {
120 list_T *list = di->di_tv.vval.v_list;
121 listitem_T *li;
122 int i;
123 int nr;
124
125 for (i = 0; i < 4; ++i)
126 array[i] = 1;
127 if (list != NULL)
128 for (i = 0, li = list->lv_first; i < 4 && i < list->lv_len;
129 ++i, li = li->li_next)
130 {
131 nr = (int)tv_get_number(&li->li_tv);
132 if (nr >= 0)
133 array[i] = nr > max_val ? max_val : nr;
134 }
135 }
136 }
137}
138
Bram Moolenaarcc31ad92019-05-30 19:25:06 +0200139/*
Bram Moolenaar17627312019-06-02 19:53:44 +0200140 * Used when popup options contain "moved": set default moved values.
Bram Moolenaar4d784b22019-05-25 19:51:39 +0200141 */
142 static void
Bram Moolenaar17627312019-06-02 19:53:44 +0200143set_moved_values(win_T *wp)
144{
145 wp->w_popup_curwin = curwin;
146 wp->w_popup_lnum = curwin->w_cursor.lnum;
147 wp->w_popup_mincol = curwin->w_cursor.col;
148 wp->w_popup_maxcol = curwin->w_cursor.col;
149}
150
151/*
152 * Used when popup options contain "moved" with "word" or "WORD".
153 */
154 static void
155set_moved_columns(win_T *wp, int flags)
156{
157 char_u *ptr;
158 int len = find_ident_under_cursor(&ptr, flags | FIND_NOERROR);
159
160 if (len > 0)
161 {
162 wp->w_popup_mincol = (int)(ptr - ml_get_curline());
163 wp->w_popup_maxcol = wp->w_popup_mincol + len - 1;
164 }
165}
166
Bram Moolenaarb53fb312019-06-13 23:59:52 +0200167/*
168 * Return TRUE if "row"/"col" is on the border of the popup.
169 * The values are relative to the top-left corner.
170 */
171 int
172popup_on_border(win_T *wp, int row, int col)
173{
174 return (row == 0 && wp->w_popup_border[0] > 0)
175 || (row == popup_height(wp) - 1 && wp->w_popup_border[2] > 0)
176 || (col == 0 && wp->w_popup_border[3] > 0)
177 || (col == popup_width(wp) - 1 && wp->w_popup_border[1] > 0);
178}
179
180// Values set when dragging a popup window starts.
181static int drag_start_row;
182static int drag_start_col;
183static int drag_start_wantline;
184static int drag_start_wantcol;
185
186/*
187 * Mouse down on border of popup window: start dragging it.
188 * Uses mouse_col and mouse_row.
189 */
190 void
191popup_start_drag(win_T *wp)
192{
193 drag_start_row = mouse_row;
194 drag_start_col = mouse_col;
195 // TODO: handle using different corner
196 if (wp->w_wantline == 0)
197 drag_start_wantline = wp->w_winrow + 1;
198 else
199 drag_start_wantline = wp->w_wantline;
200 if (wp->w_wantcol == 0)
201 drag_start_wantcol = wp->w_wincol + 1;
202 else
203 drag_start_wantcol = wp->w_wantcol;
Bram Moolenaara42d9452019-06-15 21:46:30 +0200204
205 // Stop centering the popup
206 if (wp->w_popup_pos == POPPOS_CENTER)
207 wp->w_popup_pos = POPPOS_TOPLEFT;
Bram Moolenaarb53fb312019-06-13 23:59:52 +0200208}
209
210/*
211 * Mouse moved while dragging a popup window: adjust the window popup position.
212 */
213 void
214popup_drag(win_T *wp)
215{
216 // The popup may be closed before dragging stops.
217 if (!win_valid_popup(wp))
218 return;
219
220 wp->w_wantline = drag_start_wantline + (mouse_row - drag_start_row);
221 if (wp->w_wantline < 1)
222 wp->w_wantline = 1;
223 if (wp->w_wantline > Rows)
224 wp->w_wantline = Rows;
225 wp->w_wantcol = drag_start_wantcol + (mouse_col - drag_start_col);
226 if (wp->w_wantcol < 1)
227 wp->w_wantcol = 1;
228 if (wp->w_wantcol > Columns)
229 wp->w_wantcol = Columns;
230
231 popup_adjust_position(wp);
232}
Bram Moolenaar68d48f42019-06-12 22:42:41 +0200233
234#if defined(FEAT_TIMERS)
235 static void
236popup_add_timeout(win_T *wp, int time)
237{
238 char_u cbbuf[50];
239 char_u *ptr = cbbuf;
240 typval_T tv;
241
242 vim_snprintf((char *)cbbuf, sizeof(cbbuf),
243 "{_ -> popup_close(%d)}", wp->w_id);
244 if (get_lambda_tv(&ptr, &tv, TRUE) == OK)
245 {
246 wp->w_popup_timer = create_timer(time, 0);
247 wp->w_popup_timer->tr_callback = get_callback(&tv);
248 clear_tv(&tv);
249 }
250}
251#endif
252
Bram Moolenaar17627312019-06-02 19:53:44 +0200253/*
Bram Moolenaarae943152019-06-16 22:54:14 +0200254 * Shared between popup_create() and f_popup_move().
Bram Moolenaar17627312019-06-02 19:53:44 +0200255 */
256 static void
Bram Moolenaarae943152019-06-16 22:54:14 +0200257apply_move_options(win_T *wp, dict_T *d)
Bram Moolenaar4d784b22019-05-25 19:51:39 +0200258{
Bram Moolenaarae943152019-06-16 22:54:14 +0200259 int nr;
260
261 if ((nr = dict_get_number(d, (char_u *)"minwidth")) > 0)
262 wp->w_minwidth = nr;
263 if ((nr = dict_get_number(d, (char_u *)"minheight")) > 0)
264 wp->w_minheight = nr;
265 if ((nr = dict_get_number(d, (char_u *)"maxwidth")) > 0)
266 wp->w_maxwidth = nr;
267 if ((nr = dict_get_number(d, (char_u *)"maxheight")) > 0)
268 wp->w_maxheight = nr;
269 get_pos_options(wp, d);
270}
271
272/*
273 * Shared between popup_create() and f_popup_setoptions().
274 */
275 static void
276apply_general_options(win_T *wp, dict_T *dict)
277{
278 dictitem_T *di;
Bram Moolenaar402502d2019-05-30 22:07:36 +0200279 int nr;
280 char_u *str;
Bram Moolenaar51fe3b12019-05-26 20:10:06 +0200281
Bram Moolenaarae943152019-06-16 22:54:14 +0200282 // TODO: flip
283
284 di = dict_find(dict, (char_u *)"firstline", -1);
Bram Moolenaardfa97f22019-06-15 14:31:55 +0200285 if (di != NULL)
Bram Moolenaarae943152019-06-16 22:54:14 +0200286 wp->w_firstline = dict_get_number(dict, (char_u *)"firstline");
287 if (wp->w_firstline < 1)
288 wp->w_firstline = 1;
Bram Moolenaarbf0eff02019-06-01 17:13:36 +0200289
Bram Moolenaareb2310d2019-06-16 20:09:10 +0200290 str = dict_get_string(dict, (char_u *)"title", FALSE);
291 if (str != NULL)
292 {
293 vim_free(wp->w_popup_title);
294 wp->w_popup_title = vim_strsave(str);
295 }
296
Bram Moolenaar402502d2019-05-30 22:07:36 +0200297 di = dict_find(dict, (char_u *)"wrap", -1);
298 if (di != NULL)
299 {
300 nr = dict_get_number(dict, (char_u *)"wrap");
301 wp->w_p_wrap = nr != 0;
302 }
Bram Moolenaarbf0eff02019-06-01 17:13:36 +0200303
Bram Moolenaara42d9452019-06-15 21:46:30 +0200304 di = dict_find(dict, (char_u *)"drag", -1);
305 if (di != NULL)
306 wp->w_popup_drag = dict_get_number(dict, (char_u *)"drag");
Bram Moolenaarb53fb312019-06-13 23:59:52 +0200307
Bram Moolenaarae943152019-06-16 22:54:14 +0200308 str = dict_get_string(dict, (char_u *)"highlight", FALSE);
309 if (str != NULL)
310 set_string_option_direct_in_win(wp, (char_u *)"wincolor", -1,
311 str, OPT_FREE|OPT_LOCAL, 0);
Bram Moolenaar9eaac892019-06-01 22:49:29 +0200312
Bram Moolenaarae943152019-06-16 22:54:14 +0200313 set_padding_border(dict, wp->w_popup_padding, "padding", 999);
314 set_padding_border(dict, wp->w_popup_border, "border", 1);
Bram Moolenaar9eaac892019-06-01 22:49:29 +0200315
Bram Moolenaar790498b2019-06-01 22:15:29 +0200316 di = dict_find(dict, (char_u *)"borderhighlight", -1);
317 if (di != NULL)
318 {
319 if (di->di_tv.v_type != VAR_LIST)
320 emsg(_(e_listreq));
321 else
322 {
323 list_T *list = di->di_tv.vval.v_list;
324 listitem_T *li;
Bram Moolenaarae943152019-06-16 22:54:14 +0200325 int i;
Bram Moolenaar790498b2019-06-01 22:15:29 +0200326
327 if (list != NULL)
328 for (i = 0, li = list->lv_first; i < 4 && i < list->lv_len;
329 ++i, li = li->li_next)
330 {
331 str = tv_get_string(&li->li_tv);
332 if (*str != NUL)
333 wp->w_border_highlight[i] = vim_strsave(str);
334 }
335 if (list->lv_len == 1 && wp->w_border_highlight[0] != NULL)
336 for (i = 1; i < 4; ++i)
337 wp->w_border_highlight[i] =
338 vim_strsave(wp->w_border_highlight[0]);
339 }
340 }
341
Bram Moolenaar790498b2019-06-01 22:15:29 +0200342 di = dict_find(dict, (char_u *)"borderchars", -1);
343 if (di != NULL)
344 {
345 if (di->di_tv.v_type != VAR_LIST)
346 emsg(_(e_listreq));
347 else
348 {
349 list_T *list = di->di_tv.vval.v_list;
350 listitem_T *li;
Bram Moolenaarae943152019-06-16 22:54:14 +0200351 int i;
Bram Moolenaar790498b2019-06-01 22:15:29 +0200352
353 if (list != NULL)
354 for (i = 0, li = list->lv_first; i < 8 && i < list->lv_len;
355 ++i, li = li->li_next)
356 {
357 str = tv_get_string(&li->li_tv);
358 if (*str != NUL)
359 wp->w_border_char[i] = mb_ptr2char(str);
360 }
361 if (list->lv_len == 1)
362 for (i = 1; i < 8; ++i)
363 wp->w_border_char[i] = wp->w_border_char[0];
364 if (list->lv_len == 2)
365 {
366 for (i = 4; i < 8; ++i)
367 wp->w_border_char[i] = wp->w_border_char[1];
368 for (i = 1; i < 4; ++i)
369 wp->w_border_char[i] = wp->w_border_char[0];
370 }
371 }
372 }
Bram Moolenaar3397f742019-06-02 18:40:06 +0200373
Bram Moolenaarae943152019-06-16 22:54:14 +0200374 di = dict_find(dict, (char_u *)"zindex", -1);
375 if (di != NULL)
376 {
377 wp->w_zindex = dict_get_number(dict, (char_u *)"zindex");
378 if (wp->w_zindex < 1)
379 wp->w_zindex = POPUPWIN_DEFAULT_ZINDEX;
380 if (wp->w_zindex > 32000)
381 wp->w_zindex = 32000;
382 }
383
384#if defined(FEAT_TIMERS)
385 // Add timer to close the popup after some time.
386 nr = dict_get_number(dict, (char_u *)"time");
387 if (nr > 0)
388 popup_add_timeout(wp, nr);
389#endif
390
Bram Moolenaar3397f742019-06-02 18:40:06 +0200391 di = dict_find(dict, (char_u *)"moved", -1);
392 if (di != NULL)
393 {
Bram Moolenaar17627312019-06-02 19:53:44 +0200394 set_moved_values(wp);
Bram Moolenaar3397f742019-06-02 18:40:06 +0200395 if (di->di_tv.v_type == VAR_STRING && di->di_tv.vval.v_string != NULL)
396 {
397 char_u *s = di->di_tv.vval.v_string;
398 int flags = 0;
399
400 if (STRCMP(s, "word") == 0)
401 flags = FIND_IDENT | FIND_STRING;
402 else if (STRCMP(s, "WORD") == 0)
403 flags = FIND_STRING;
404 else if (STRCMP(s, "any") != 0)
405 semsg(_(e_invarg2), s);
406 if (flags != 0)
Bram Moolenaar17627312019-06-02 19:53:44 +0200407 set_moved_columns(wp, flags);
Bram Moolenaar3397f742019-06-02 18:40:06 +0200408 }
409 else if (di->di_tv.v_type == VAR_LIST
410 && di->di_tv.vval.v_list != NULL
411 && di->di_tv.vval.v_list->lv_len == 2)
412 {
413 list_T *l = di->di_tv.vval.v_list;
414
415 wp->w_popup_mincol = tv_get_number(&l->lv_first->li_tv);
416 wp->w_popup_maxcol = tv_get_number(&l->lv_first->li_next->li_tv);
417 }
418 else
419 semsg(_(e_invarg2), tv_get_string(&di->di_tv));
420 }
Bram Moolenaar33796b32019-06-08 16:01:13 +0200421
Bram Moolenaarae943152019-06-16 22:54:14 +0200422 di = dict_find(dict, (char_u *)"filter", -1);
423 if (di != NULL)
424 {
425 callback_T callback = get_callback(&di->di_tv);
426
427 if (callback.cb_name != NULL)
428 {
429 free_callback(&wp->w_filter_cb);
430 set_callback(&wp->w_filter_cb, &callback);
431 }
432 }
433
434 di = dict_find(dict, (char_u *)"callback", -1);
435 if (di != NULL)
436 {
437 callback_T callback = get_callback(&di->di_tv);
438
439 if (callback.cb_name != NULL)
440 {
441 free_callback(&wp->w_close_cb);
442 set_callback(&wp->w_close_cb, &callback);
443 }
444 }
445}
446
447/*
448 * Go through the options in "dict" and apply them to popup window "wp".
449 */
450 static void
451apply_options(win_T *wp, dict_T *dict)
452{
453 int nr;
454
455 apply_move_options(wp, dict);
456 apply_general_options(wp, dict);
457
Bram Moolenaar6313c4f2019-06-16 20:39:13 +0200458 nr = dict_get_number(dict, (char_u *)"hidden");
459 if (nr > 0)
460 {
461 wp->w_popup_flags |= POPF_HIDDEN;
462 --wp->w_buffer->b_nwindows;
463 }
464
Bram Moolenaar33796b32019-06-08 16:01:13 +0200465 popup_mask_refresh = TRUE;
Bram Moolenaar4d784b22019-05-25 19:51:39 +0200466}
467
468/*
Bram Moolenaar7a8d0272019-05-26 23:32:06 +0200469 * Add lines to the popup from a list of strings.
470 */
471 static void
472add_popup_strings(buf_T *buf, list_T *l)
473{
474 listitem_T *li;
475 linenr_T lnum = 0;
476 char_u *p;
477
478 for (li = l->lv_first; li != NULL; li = li->li_next)
479 if (li->li_tv.v_type == VAR_STRING)
480 {
481 p = li->li_tv.vval.v_string;
482 ml_append_buf(buf, lnum++,
483 p == NULL ? (char_u *)"" : p, (colnr_T)0, TRUE);
484 }
485}
486
487/*
488 * Add lines to the popup from a list of dictionaries.
489 */
490 static void
491add_popup_dicts(buf_T *buf, list_T *l)
492{
493 listitem_T *li;
494 listitem_T *pli;
495 linenr_T lnum = 0;
496 char_u *p;
497 dict_T *dict;
498
499 // first add the text lines
500 for (li = l->lv_first; li != NULL; li = li->li_next)
501 {
502 if (li->li_tv.v_type != VAR_DICT)
503 {
504 emsg(_(e_dictreq));
505 return;
506 }
507 dict = li->li_tv.vval.v_dict;
508 p = dict == NULL ? NULL
509 : dict_get_string(dict, (char_u *)"text", FALSE);
510 ml_append_buf(buf, lnum++,
511 p == NULL ? (char_u *)"" : p, (colnr_T)0, TRUE);
512 }
513
514 // add the text properties
515 lnum = 1;
516 for (li = l->lv_first; li != NULL; li = li->li_next, ++lnum)
517 {
518 dictitem_T *di;
519 list_T *plist;
520
521 dict = li->li_tv.vval.v_dict;
522 di = dict_find(dict, (char_u *)"props", -1);
523 if (di != NULL)
524 {
525 if (di->di_tv.v_type != VAR_LIST)
526 {
527 emsg(_(e_listreq));
528 return;
529 }
530 plist = di->di_tv.vval.v_list;
531 if (plist != NULL)
532 {
533 for (pli = plist->lv_first; pli != NULL; pli = pli->li_next)
534 {
535 if (pli->li_tv.v_type != VAR_DICT)
536 {
537 emsg(_(e_dictreq));
538 return;
539 }
540 dict = pli->li_tv.vval.v_dict;
541 if (dict != NULL)
542 {
543 int col = dict_get_number(dict, (char_u *)"col");
544
545 prop_add_common( lnum, col, dict, buf, NULL);
546 }
547 }
548 }
549 }
550 }
551}
552
553/*
Bram Moolenaar451d4b52019-06-12 20:22:27 +0200554 * Return the height of popup window "wp", including border and padding.
555 */
556 int
557popup_height(win_T *wp)
558{
559 return wp->w_height
560 + wp->w_popup_padding[0] + wp->w_popup_border[0]
561 + wp->w_popup_padding[2] + wp->w_popup_border[2];
562}
563
564/*
565 * Return the width of popup window "wp", including border and padding.
566 */
567 int
568popup_width(win_T *wp)
569{
570 return wp->w_width
571 + wp->w_popup_padding[3] + wp->w_popup_border[3]
572 + wp->w_popup_padding[1] + wp->w_popup_border[1];
573}
574
575/*
Bram Moolenaareb2310d2019-06-16 20:09:10 +0200576 * Get the padding plus border at the top, adjusted to 1 if there is a title.
577 */
578 static int
579popup_top_extra(win_T *wp)
580{
581 int extra = wp->w_popup_border[0] + wp->w_popup_padding[0];
582
583 if (extra == 0 && wp->w_popup_title != NULL && *wp->w_popup_title != NUL)
584 return 1;
585 return extra;
586}
587
588/*
Bram Moolenaar60cdb302019-05-27 21:54:10 +0200589 * Adjust the position and size of the popup to fit on the screen.
590 */
Bram Moolenaar17146962019-05-30 00:12:11 +0200591 void
Bram Moolenaar60cdb302019-05-27 21:54:10 +0200592popup_adjust_position(win_T *wp)
593{
Bram Moolenaar88c4e1f2019-05-29 23:14:28 +0200594 linenr_T lnum;
595 int wrapped = 0;
596 int maxwidth;
Bram Moolenaarac1f1bc2019-05-30 21:24:26 +0200597 int center_vert = FALSE;
598 int center_hor = FALSE;
Bram Moolenaar042fb4b2019-06-02 14:49:56 +0200599 int allow_adjust_left = !wp->w_popup_fixed;
Bram Moolenaareb2310d2019-06-16 20:09:10 +0200600 int top_extra = popup_top_extra(wp);
Bram Moolenaar399d8982019-06-02 15:34:29 +0200601 int right_extra = wp->w_popup_border[1] + wp->w_popup_padding[1];
602 int bot_extra = wp->w_popup_border[2] + wp->w_popup_padding[2];
603 int left_extra = wp->w_popup_border[3] + wp->w_popup_padding[3];
604 int extra_height = top_extra + bot_extra;
605 int extra_width = left_extra + right_extra;
Bram Moolenaar33796b32019-06-08 16:01:13 +0200606 int org_winrow = wp->w_winrow;
607 int org_wincol = wp->w_wincol;
608 int org_width = wp->w_width;
609 int org_height = wp->w_height;
Bram Moolenaareb2310d2019-06-16 20:09:10 +0200610 int minwidth;
Bram Moolenaar88c4e1f2019-05-29 23:14:28 +0200611
Bram Moolenaarac1f1bc2019-05-30 21:24:26 +0200612 wp->w_winrow = 0;
613 wp->w_wincol = 0;
614 if (wp->w_popup_pos == POPPOS_CENTER)
615 {
616 // center after computing the size
617 center_vert = TRUE;
618 center_hor = TRUE;
619 }
Bram Moolenaar60cdb302019-05-27 21:54:10 +0200620 else
Bram Moolenaarac1f1bc2019-05-30 21:24:26 +0200621 {
622 if (wp->w_wantline == 0)
623 center_vert = TRUE;
624 else if (wp->w_popup_pos == POPPOS_TOPLEFT
625 || wp->w_popup_pos == POPPOS_TOPRIGHT)
626 {
627 wp->w_winrow = wp->w_wantline - 1;
628 if (wp->w_winrow >= Rows)
629 wp->w_winrow = Rows - 1;
630 }
Bram Moolenaar60cdb302019-05-27 21:54:10 +0200631
Bram Moolenaarac1f1bc2019-05-30 21:24:26 +0200632 if (wp->w_wantcol == 0)
633 center_hor = TRUE;
634 else if (wp->w_popup_pos == POPPOS_TOPLEFT
635 || wp->w_popup_pos == POPPOS_BOTLEFT)
636 {
637 wp->w_wincol = wp->w_wantcol - 1;
638 if (wp->w_wincol >= Columns - 3)
639 wp->w_wincol = Columns - 3;
640 }
641 }
Bram Moolenaar60cdb302019-05-27 21:54:10 +0200642
Bram Moolenaarac1f1bc2019-05-30 21:24:26 +0200643 // When centering or right aligned, use maximum width.
Bram Moolenaar042fb4b2019-06-02 14:49:56 +0200644 // When left aligned use the space available, but shift to the left when we
645 // hit the right of the screen.
Bram Moolenaar51c31312019-06-15 22:27:23 +0200646 maxwidth = Columns - wp->w_wincol - left_extra;
Bram Moolenaar88c4e1f2019-05-29 23:14:28 +0200647 if (wp->w_maxwidth > 0 && maxwidth > wp->w_maxwidth)
Bram Moolenaar042fb4b2019-06-02 14:49:56 +0200648 {
649 allow_adjust_left = FALSE;
Bram Moolenaar88c4e1f2019-05-29 23:14:28 +0200650 maxwidth = wp->w_maxwidth;
Bram Moolenaar042fb4b2019-06-02 14:49:56 +0200651 }
Bram Moolenaar88c4e1f2019-05-29 23:14:28 +0200652
Bram Moolenaar8d241042019-06-12 23:40:01 +0200653 // start at the desired first line
654 wp->w_topline = wp->w_firstline;
655 if (wp->w_topline > wp->w_buffer->b_ml.ml_line_count)
656 wp->w_topline = wp->w_buffer->b_ml.ml_line_count;
657
Bram Moolenaar88c4e1f2019-05-29 23:14:28 +0200658 // Compute width based on longest text line and the 'wrap' option.
Bram Moolenaardc2ce582019-06-16 15:32:14 +0200659 // Use a minimum width of one, so that something shows when there is no
660 // text.
Bram Moolenaar88c4e1f2019-05-29 23:14:28 +0200661 // TODO: more accurate wrapping
Bram Moolenaardc2ce582019-06-16 15:32:14 +0200662 wp->w_width = 1;
Bram Moolenaar8d241042019-06-12 23:40:01 +0200663 for (lnum = wp->w_topline; lnum <= wp->w_buffer->b_ml.ml_line_count; ++lnum)
Bram Moolenaar88c4e1f2019-05-29 23:14:28 +0200664 {
665 int len = vim_strsize(ml_get_buf(wp->w_buffer, lnum, FALSE));
666
Bram Moolenaar042fb4b2019-06-02 14:49:56 +0200667 if (wp->w_p_wrap)
Bram Moolenaar88c4e1f2019-05-29 23:14:28 +0200668 {
Bram Moolenaar042fb4b2019-06-02 14:49:56 +0200669 while (len > maxwidth)
670 {
671 ++wrapped;
672 len -= maxwidth;
673 wp->w_width = maxwidth;
674 }
675 }
676 else if (len > maxwidth
677 && allow_adjust_left
678 && (wp->w_popup_pos == POPPOS_TOPLEFT
679 || wp->w_popup_pos == POPPOS_BOTLEFT))
680 {
681 // adjust leftwise to fit text on screen
Bram Moolenaar51c31312019-06-15 22:27:23 +0200682 int shift_by = len - maxwidth;
Bram Moolenaar042fb4b2019-06-02 14:49:56 +0200683
Bram Moolenaar51c31312019-06-15 22:27:23 +0200684 if (shift_by > wp->w_wincol)
Bram Moolenaar042fb4b2019-06-02 14:49:56 +0200685 {
686 int truncate_shift = shift_by - wp->w_wincol;
Bram Moolenaar51c31312019-06-15 22:27:23 +0200687
Bram Moolenaar042fb4b2019-06-02 14:49:56 +0200688 len -= truncate_shift;
689 shift_by -= truncate_shift;
690 }
691
692 wp->w_wincol -= shift_by;
693 maxwidth += shift_by;
Bram Moolenaar88c4e1f2019-05-29 23:14:28 +0200694 wp->w_width = maxwidth;
695 }
696 if (wp->w_width < len)
697 wp->w_width = len;
Bram Moolenaar8d241042019-06-12 23:40:01 +0200698 // do not use the width of lines we're not going to show
699 if (wp->w_maxheight > 0 && wp->w_buffer->b_ml.ml_line_count
700 - wp->w_topline + 1 + wrapped > wp->w_maxheight)
701 break;
Bram Moolenaar88c4e1f2019-05-29 23:14:28 +0200702 }
703
Bram Moolenaareb2310d2019-06-16 20:09:10 +0200704 minwidth = wp->w_minwidth;
705 if (wp->w_popup_title != NULL && *wp->w_popup_title != NUL)
706 {
707 int title_len = vim_strsize(wp->w_popup_title) + 2 - extra_width;
708
709 if (minwidth < title_len)
710 minwidth = title_len;
711 }
712
713 if (minwidth > 0 && wp->w_width < minwidth)
714 wp->w_width = minwidth;
Bram Moolenaar88c4e1f2019-05-29 23:14:28 +0200715 if (wp->w_width > maxwidth)
716 wp->w_width = maxwidth;
Bram Moolenaarac1f1bc2019-05-30 21:24:26 +0200717 if (center_hor)
Bram Moolenaara730e552019-06-16 19:05:31 +0200718 {
719 wp->w_wincol = (Columns - wp->w_width - extra_width) / 2;
720 if (wp->w_wincol < 0)
721 wp->w_wincol = 0;
722 }
Bram Moolenaarac1f1bc2019-05-30 21:24:26 +0200723 else if (wp->w_popup_pos == POPPOS_BOTRIGHT
724 || wp->w_popup_pos == POPPOS_TOPRIGHT)
725 {
726 // Right aligned: move to the right if needed.
727 // No truncation, because that would change the height.
Bram Moolenaar399d8982019-06-02 15:34:29 +0200728 if (wp->w_width + extra_width < wp->w_wantcol)
729 wp->w_wincol = wp->w_wantcol - (wp->w_width + extra_width);
Bram Moolenaarac1f1bc2019-05-30 21:24:26 +0200730 }
Bram Moolenaar60cdb302019-05-27 21:54:10 +0200731
Bram Moolenaar8d241042019-06-12 23:40:01 +0200732 wp->w_height = wp->w_buffer->b_ml.ml_line_count - wp->w_topline
733 + 1 + wrapped;
Bram Moolenaar60cdb302019-05-27 21:54:10 +0200734 if (wp->w_minheight > 0 && wp->w_height < wp->w_minheight)
735 wp->w_height = wp->w_minheight;
736 if (wp->w_maxheight > 0 && wp->w_height > wp->w_maxheight)
737 wp->w_height = wp->w_maxheight;
738 if (wp->w_height > Rows - wp->w_winrow)
739 wp->w_height = Rows - wp->w_winrow;
Bram Moolenaar17146962019-05-30 00:12:11 +0200740
Bram Moolenaarac1f1bc2019-05-30 21:24:26 +0200741 if (center_vert)
Bram Moolenaara730e552019-06-16 19:05:31 +0200742 {
743 wp->w_winrow = (Rows - wp->w_height - extra_height) / 2;
744 if (wp->w_winrow < 0)
745 wp->w_winrow = 0;
746 }
Bram Moolenaarac1f1bc2019-05-30 21:24:26 +0200747 else if (wp->w_popup_pos == POPPOS_BOTRIGHT
748 || wp->w_popup_pos == POPPOS_BOTLEFT)
749 {
Bram Moolenaar399d8982019-06-02 15:34:29 +0200750 if ((wp->w_height + extra_height) <= wp->w_wantline)
Bram Moolenaarac1f1bc2019-05-30 21:24:26 +0200751 // bottom aligned: may move down
Bram Moolenaar399d8982019-06-02 15:34:29 +0200752 wp->w_winrow = wp->w_wantline - (wp->w_height + extra_height);
Bram Moolenaarac1f1bc2019-05-30 21:24:26 +0200753 else
754 // not enough space, make top aligned
755 wp->w_winrow = wp->w_wantline + 1;
756 }
757
Bram Moolenaar17146962019-05-30 00:12:11 +0200758 wp->w_popup_last_changedtick = CHANGEDTICK(wp->w_buffer);
Bram Moolenaar33796b32019-06-08 16:01:13 +0200759
760 // Need to update popup_mask if the position or size changed.
Bram Moolenaaracc682b2019-06-08 17:15:51 +0200761 // And redraw windows that were behind the popup.
Bram Moolenaar33796b32019-06-08 16:01:13 +0200762 if (org_winrow != wp->w_winrow
763 || org_wincol != wp->w_wincol
764 || org_width != wp->w_width
765 || org_height != wp->w_height)
766 {
Bram Moolenaar4c063a02019-06-10 21:24:12 +0200767 redraw_all_later(VALID);
Bram Moolenaar33796b32019-06-08 16:01:13 +0200768 popup_mask_refresh = TRUE;
769 }
Bram Moolenaar60cdb302019-05-27 21:54:10 +0200770}
771
Bram Moolenaar17627312019-06-02 19:53:44 +0200772typedef enum
773{
774 TYPE_NORMAL,
Bram Moolenaar68d48f42019-06-12 22:42:41 +0200775 TYPE_ATCURSOR,
Bram Moolenaara42d9452019-06-15 21:46:30 +0200776 TYPE_NOTIFICATION,
Bram Moolenaara730e552019-06-16 19:05:31 +0200777 TYPE_DIALOG,
778 TYPE_MENU
Bram Moolenaar17627312019-06-02 19:53:44 +0200779} create_type_T;
780
Bram Moolenaar60cdb302019-05-27 21:54:10 +0200781/*
Bram Moolenaardc2ce582019-06-16 15:32:14 +0200782 * Make "buf" empty and set the contents to "text".
783 * Used by popup_create() and popup_settext().
784 */
785 static void
786popup_set_buffer_text(buf_T *buf, typval_T text)
787{
788 int lnum;
789
790 // Clear the buffer, then replace the lines.
791 curbuf = buf;
792 for (lnum = buf->b_ml.ml_line_count; lnum > 0; --lnum)
793 ml_delete(lnum, FALSE);
794 curbuf = curwin->w_buffer;
795
796 // Add text to the buffer.
797 if (text.v_type == VAR_STRING)
798 {
799 // just a string
800 ml_append_buf(buf, 0, text.vval.v_string, (colnr_T)0, TRUE);
801 }
802 else
803 {
804 list_T *l = text.vval.v_list;
805
806 if (l->lv_len > 0)
807 {
808 if (l->lv_first->li_tv.v_type == VAR_STRING)
809 // list of strings
810 add_popup_strings(buf, l);
811 else
812 // list of dictionaries
813 add_popup_dicts(buf, l);
814 }
815 }
816
817 // delete the line that was in the empty buffer
818 curbuf = buf;
819 ml_delete(buf->b_ml.ml_line_count, FALSE);
820 curbuf = curwin->w_buffer;
821}
822
823/*
Bram Moolenaar4d784b22019-05-25 19:51:39 +0200824 * popup_create({text}, {options})
Bram Moolenaarcc31ad92019-05-30 19:25:06 +0200825 * popup_atcursor({text}, {options})
Bram Moolenaar4d784b22019-05-25 19:51:39 +0200826 */
Bram Moolenaara730e552019-06-16 19:05:31 +0200827 static win_T *
Bram Moolenaar17627312019-06-02 19:53:44 +0200828popup_create(typval_T *argvars, typval_T *rettv, create_type_T type)
Bram Moolenaar4d784b22019-05-25 19:51:39 +0200829{
Bram Moolenaara3fce622019-06-20 02:31:49 +0200830 win_T *wp;
831 tabpage_T *tp = NULL;
832 int tabnr;
833 buf_T *buf;
834 dict_T *d;
835 int nr;
836 int i;
Bram Moolenaar4d784b22019-05-25 19:51:39 +0200837
838 // Check arguments look OK.
Bram Moolenaar7b29dd82019-06-02 13:22:11 +0200839 if (!(argvars[0].v_type == VAR_STRING && argvars[0].vval.v_string != NULL)
840 && !(argvars[0].v_type == VAR_LIST && argvars[0].vval.v_list != NULL))
Bram Moolenaar4d784b22019-05-25 19:51:39 +0200841 {
842 emsg(_(e_listreq));
Bram Moolenaara730e552019-06-16 19:05:31 +0200843 return NULL;
Bram Moolenaar4d784b22019-05-25 19:51:39 +0200844 }
845 if (argvars[1].v_type != VAR_DICT || argvars[1].vval.v_dict == NULL)
846 {
847 emsg(_(e_dictreq));
Bram Moolenaara730e552019-06-16 19:05:31 +0200848 return NULL;
Bram Moolenaar4d784b22019-05-25 19:51:39 +0200849 }
850 d = argvars[1].vval.v_dict;
851
Bram Moolenaara3fce622019-06-20 02:31:49 +0200852 if (dict_find(d, (char_u *)"tabpage", -1) != NULL)
853 tabnr = (int)dict_get_number(d, (char_u *)"tabpage");
854 else if (type == TYPE_NOTIFICATION)
855 tabnr = -1; // notifications are global by default
856 else
857 tabnr = 0;
858 if (tabnr > 0)
859 {
860 tp = find_tabpage(tabnr);
861 if (tp == NULL)
862 {
863 semsg(_("E996: Tabpage not found: %d"), tabnr);
864 return NULL;
865 }
866 }
867
Bram Moolenaar4d784b22019-05-25 19:51:39 +0200868 // Create the window and buffer.
869 wp = win_alloc_popup_win();
870 if (wp == NULL)
Bram Moolenaara730e552019-06-16 19:05:31 +0200871 return NULL;
Bram Moolenaar4d784b22019-05-25 19:51:39 +0200872 rettv->vval.v_number = wp->w_id;
Bram Moolenaarac1f1bc2019-05-30 21:24:26 +0200873 wp->w_popup_pos = POPPOS_TOPLEFT;
Bram Moolenaar4d784b22019-05-25 19:51:39 +0200874
875 buf = buflist_new(NULL, NULL, (linenr_T)0, BLN_NEW|BLN_LISTED|BLN_DUMMY);
876 if (buf == NULL)
Bram Moolenaara730e552019-06-16 19:05:31 +0200877 return NULL;
Bram Moolenaar4d784b22019-05-25 19:51:39 +0200878 ml_open(buf);
Bram Moolenaarcacc6a52019-05-30 15:22:43 +0200879
880 win_init_popup_win(wp, buf);
881
882 set_local_options_default(wp);
Bram Moolenaar20c023a2019-05-26 21:03:24 +0200883 set_string_option_direct_in_buf(buf, (char_u *)"buftype", -1,
Bram Moolenaar4d784b22019-05-25 19:51:39 +0200884 (char_u *)"popup", OPT_FREE|OPT_LOCAL, 0);
Bram Moolenaar20c023a2019-05-26 21:03:24 +0200885 set_string_option_direct_in_buf(buf, (char_u *)"bufhidden", -1,
Bram Moolenaar4d784b22019-05-25 19:51:39 +0200886 (char_u *)"hide", OPT_FREE|OPT_LOCAL, 0);
Bram Moolenaar4d784b22019-05-25 19:51:39 +0200887 buf->b_p_ul = -1; // no undo
888 buf->b_p_swf = FALSE; // no swap file
889 buf->b_p_bl = FALSE; // unlisted buffer
Bram Moolenaar868b7b62019-05-29 21:44:40 +0200890 buf->b_locked = TRUE;
Bram Moolenaarac1f1bc2019-05-30 21:24:26 +0200891 wp->w_p_wrap = TRUE; // 'wrap' is default on
892
Bram Moolenaar54fabd42019-05-30 19:03:22 +0200893 // Avoid that 'buftype' is reset when this buffer is entered.
894 buf->b_p_initialized = TRUE;
Bram Moolenaar4d784b22019-05-25 19:51:39 +0200895
Bram Moolenaara3fce622019-06-20 02:31:49 +0200896 if (tp != NULL)
897 {
898 // popup on specified tab page
899 wp->w_next = tp->tp_first_popupwin;
900 tp->tp_first_popupwin = wp;
901 }
902 else if (tabnr == 0)
Bram Moolenaar4d784b22019-05-25 19:51:39 +0200903 {
Bram Moolenaarfc06cbb2019-06-15 14:14:31 +0200904 // popup on current tab page
Bram Moolenaar9c27b1c2019-05-26 18:48:13 +0200905 wp->w_next = curtab->tp_first_popupwin;
906 curtab->tp_first_popupwin = wp;
Bram Moolenaar4d784b22019-05-25 19:51:39 +0200907 }
Bram Moolenaara3fce622019-06-20 02:31:49 +0200908 else // (tabnr < 0)
Bram Moolenaar4d784b22019-05-25 19:51:39 +0200909 {
Bram Moolenaar68d48f42019-06-12 22:42:41 +0200910 win_T *prev = first_popupwin;
911
912 // Global popup: add at the end, so that it gets displayed on top of
913 // older ones with the same zindex. Matters for notifications.
914 if (first_popupwin == NULL)
915 first_popupwin = wp;
916 else
917 {
918 while (prev->w_next != NULL)
919 prev = prev->w_next;
920 prev->w_next = wp;
921 }
Bram Moolenaar4d784b22019-05-25 19:51:39 +0200922 }
Bram Moolenaar4d784b22019-05-25 19:51:39 +0200923
Bram Moolenaardc2ce582019-06-16 15:32:14 +0200924 popup_set_buffer_text(buf, argvars[0]);
Bram Moolenaar4d784b22019-05-25 19:51:39 +0200925
Bram Moolenaar17627312019-06-02 19:53:44 +0200926 if (type == TYPE_ATCURSOR)
927 {
928 wp->w_popup_pos = POPPOS_BOTLEFT;
929 setcursor_mayforce(TRUE);
930 wp->w_wantline = screen_screenrow();
931 if (wp->w_wantline == 0) // cursor in first line
932 {
933 wp->w_wantline = 2;
934 wp->w_popup_pos = POPPOS_TOPLEFT;
935 }
936 wp->w_wantcol = screen_screencol() + 1;
937 set_moved_values(wp);
938 set_moved_columns(wp, FIND_STRING);
939 }
940
Bram Moolenaar33796b32019-06-08 16:01:13 +0200941 // set default values
942 wp->w_zindex = POPUPWIN_DEFAULT_ZINDEX;
943
Bram Moolenaar68d48f42019-06-12 22:42:41 +0200944 if (type == TYPE_NOTIFICATION)
945 {
946 win_T *twp, *nextwin;
947 int height = buf->b_ml.ml_line_count + 3;
Bram Moolenaar68d48f42019-06-12 22:42:41 +0200948
949 // Try to not overlap with another global popup. Guess we need 3
950 // more screen lines than buffer lines.
951 wp->w_wantline = 1;
952 for (twp = first_popupwin; twp != NULL; twp = nextwin)
953 {
954 nextwin = twp->w_next;
955 if (twp != wp
956 && twp->w_zindex == POPUPWIN_NOTIFICATION_ZINDEX
957 && twp->w_winrow <= wp->w_wantline - 1 + height
958 && twp->w_winrow + popup_height(twp) > wp->w_wantline - 1)
959 {
960 // move to below this popup and restart the loop to check for
961 // overlap with other popups
962 wp->w_wantline = twp->w_winrow + popup_height(twp) + 1;
963 nextwin = first_popupwin;
964 }
965 }
966 if (wp->w_wantline + height > Rows)
967 {
968 // can't avoid overlap, put on top in the hope that message goes
969 // away soon.
970 wp->w_wantline = 1;
971 }
972
973 wp->w_wantcol = 10;
974 wp->w_zindex = POPUPWIN_NOTIFICATION_ZINDEX;
Bram Moolenaardfa97f22019-06-15 14:31:55 +0200975 wp->w_minwidth = 20;
976 wp->w_popup_drag = 1;
Bram Moolenaar68d48f42019-06-12 22:42:41 +0200977 for (i = 0; i < 4; ++i)
978 wp->w_popup_border[i] = 1;
979 wp->w_popup_padding[1] = 1;
980 wp->w_popup_padding[3] = 1;
Bram Moolenaardfa97f22019-06-15 14:31:55 +0200981
982 nr = syn_name2id((char_u *)"PopupNotification");
Bram Moolenaar68d48f42019-06-12 22:42:41 +0200983 set_string_option_direct_in_win(wp, (char_u *)"wincolor", -1,
Bram Moolenaardfa97f22019-06-15 14:31:55 +0200984 (char_u *)(nr == 0 ? "WarningMsg" : "PopupNotification"),
985 OPT_FREE|OPT_LOCAL, 0);
Bram Moolenaar68d48f42019-06-12 22:42:41 +0200986 }
987
Bram Moolenaara730e552019-06-16 19:05:31 +0200988 if (type == TYPE_DIALOG || type == TYPE_MENU)
Bram Moolenaara42d9452019-06-15 21:46:30 +0200989 {
Bram Moolenaara42d9452019-06-15 21:46:30 +0200990 wp->w_popup_pos = POPPOS_CENTER;
991 wp->w_zindex = POPUPWIN_DIALOG_ZINDEX;
992 wp->w_popup_drag = 1;
993 for (i = 0; i < 4; ++i)
994 {
995 wp->w_popup_border[i] = 1;
996 wp->w_popup_padding[i] = 1;
997 }
998 }
999
Bram Moolenaara730e552019-06-16 19:05:31 +02001000 if (type == TYPE_MENU)
1001 {
1002 typval_T tv;
1003 callback_T callback;
1004
1005 tv.v_type = VAR_STRING;
1006 tv.vval.v_string = (char_u *)"popup_filter_menu";
1007 callback = get_callback(&tv);
1008 if (callback.cb_name != NULL)
1009 set_callback(&wp->w_filter_cb, &callback);
1010
1011 wp->w_p_wrap = 0;
1012 }
1013
Bram Moolenaarae943152019-06-16 22:54:14 +02001014 for (i = 0; i < 4; ++i)
1015 VIM_CLEAR(wp->w_border_highlight[i]);
1016 for (i = 0; i < 8; ++i)
1017 wp->w_border_char[i] = 0;
1018
Bram Moolenaar4d784b22019-05-25 19:51:39 +02001019 // Deal with options.
Bram Moolenaarae943152019-06-16 22:54:14 +02001020 apply_options(wp, argvars[1].vval.v_dict);
Bram Moolenaar4d784b22019-05-25 19:51:39 +02001021
Bram Moolenaar68d48f42019-06-12 22:42:41 +02001022 if (type == TYPE_NOTIFICATION && wp->w_popup_timer == NULL)
1023 popup_add_timeout(wp, 3000);
1024
Bram Moolenaar60cdb302019-05-27 21:54:10 +02001025 popup_adjust_position(wp);
Bram Moolenaar4d784b22019-05-25 19:51:39 +02001026
1027 wp->w_vsep_width = 0;
1028
1029 redraw_all_later(NOT_VALID);
Bram Moolenaar33796b32019-06-08 16:01:13 +02001030 popup_mask_refresh = TRUE;
Bram Moolenaara730e552019-06-16 19:05:31 +02001031
1032 return wp;
Bram Moolenaar4d784b22019-05-25 19:51:39 +02001033}
1034
1035/*
Bram Moolenaar3ff5f0f2019-06-10 13:11:22 +02001036 * popup_clear()
1037 */
1038 void
1039f_popup_clear(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
1040{
1041 close_all_popups();
1042}
1043
1044/*
Bram Moolenaarcc31ad92019-05-30 19:25:06 +02001045 * popup_create({text}, {options})
1046 */
1047 void
1048f_popup_create(typval_T *argvars, typval_T *rettv)
1049{
Bram Moolenaar17627312019-06-02 19:53:44 +02001050 popup_create(argvars, rettv, TYPE_NORMAL);
Bram Moolenaarcc31ad92019-05-30 19:25:06 +02001051}
1052
1053/*
1054 * popup_atcursor({text}, {options})
1055 */
1056 void
1057f_popup_atcursor(typval_T *argvars, typval_T *rettv)
1058{
Bram Moolenaar17627312019-06-02 19:53:44 +02001059 popup_create(argvars, rettv, TYPE_ATCURSOR);
Bram Moolenaarcc31ad92019-05-30 19:25:06 +02001060}
1061
1062/*
Bram Moolenaar9eaac892019-06-01 22:49:29 +02001063 * Invoke the close callback for window "wp" with value "result".
1064 * Careful: The callback may make "wp" invalid!
1065 */
1066 static void
1067invoke_popup_callback(win_T *wp, typval_T *result)
1068{
1069 typval_T rettv;
1070 int dummy;
1071 typval_T argv[3];
1072
1073 argv[0].v_type = VAR_NUMBER;
1074 argv[0].vval.v_number = (varnumber_T)wp->w_id;
1075
1076 if (result != NULL && result->v_type != VAR_UNKNOWN)
1077 copy_tv(result, &argv[1]);
1078 else
1079 {
1080 argv[1].v_type = VAR_NUMBER;
1081 argv[1].vval.v_number = 0;
1082 }
1083
1084 argv[2].v_type = VAR_UNKNOWN;
1085
1086 call_callback(&wp->w_close_cb, -1,
1087 &rettv, 2, argv, NULL, 0L, 0L, &dummy, TRUE, NULL);
1088 if (result != NULL)
1089 clear_tv(&argv[1]);
1090 clear_tv(&rettv);
1091}
1092
1093/*
Bram Moolenaar3397f742019-06-02 18:40:06 +02001094 * Close popup "wp" and invoke any close callback for it.
1095 */
1096 static void
1097popup_close_and_callback(win_T *wp, typval_T *arg)
1098{
1099 int id = wp->w_id;
1100
1101 if (wp->w_close_cb.cb_name != NULL)
1102 // Careful: This may make "wp" invalid.
1103 invoke_popup_callback(wp, arg);
1104
1105 popup_close(id);
1106}
1107
1108/*
Bram Moolenaara730e552019-06-16 19:05:31 +02001109 * In a filter: check if the typed key is a mouse event that is used for
1110 * dragging the popup.
1111 */
1112 static void
1113filter_handle_drag(win_T *wp, int c, typval_T *rettv)
1114{
1115 int row = mouse_row;
1116 int col = mouse_col;
1117
1118 if (wp->w_popup_drag
1119 && is_mouse_key(c)
1120 && (wp == popup_dragwin
1121 || wp == mouse_find_win(&row, &col, FIND_POPUP)))
1122 // do not consume the key, allow for dragging the popup
1123 rettv->vval.v_number = 0;
1124}
1125
1126 static void
1127popup_highlight_curline(win_T *wp)
1128{
1129 int id;
1130 char buf[100];
1131
1132 match_delete(wp, 1, FALSE);
1133
1134 id = syn_name2id((char_u *)"PopupSelected");
1135 vim_snprintf(buf, sizeof(buf), "\\%%%dl.*", (int)wp->w_cursor.lnum);
1136 match_add(wp, (char_u *)(id == 0 ? "PmenuSel" : "PopupSelected"),
1137 (char_u *)buf, 10, 1, NULL, NULL);
1138}
1139
1140/*
1141 * popup_filter_menu({text}, {options})
1142 */
1143 void
1144f_popup_filter_menu(typval_T *argvars, typval_T *rettv)
1145{
1146 int id = tv_get_number(&argvars[0]);
1147 win_T *wp = win_id2wp(id);
1148 char_u *key = tv_get_string(&argvars[1]);
1149 typval_T res;
1150 int c;
1151 linenr_T old_lnum;
1152
1153 // If the popup has been closed do not consume the key.
1154 if (wp == NULL)
1155 return;
1156
1157 c = *key;
1158 if (c == K_SPECIAL && key[1] != NUL)
1159 c = TO_SPECIAL(key[1], key[2]);
1160
1161 // consume all keys until done
1162 rettv->vval.v_number = 1;
1163 res.v_type = VAR_NUMBER;
1164
1165 old_lnum = wp->w_cursor.lnum;
1166 if ((c == 'k' || c == 'K' || c == K_UP) && wp->w_cursor.lnum > 1)
1167 --wp->w_cursor.lnum;
1168 if ((c == 'j' || c == 'J' || c == K_DOWN)
1169 && wp->w_cursor.lnum < wp->w_buffer->b_ml.ml_line_count)
1170 ++wp->w_cursor.lnum;
1171 if (old_lnum != wp->w_cursor.lnum)
1172 {
1173 popup_highlight_curline(wp);
1174 return;
1175 }
1176
1177 if (c == 'x' || c == 'X' || c == ESC || c == Ctrl_C)
1178 {
1179 // Cancelled, invoke callback with -1
1180 res.vval.v_number = -1;
1181 popup_close_and_callback(wp, &res);
1182 return;
1183 }
1184 if (c == ' ' || c == K_KENTER || c == CAR || c == NL)
1185 {
1186 // Invoke callback with current index.
1187 res.vval.v_number = wp->w_cursor.lnum;
1188 popup_close_and_callback(wp, &res);
1189 return;
1190 }
1191
1192 filter_handle_drag(wp, c, rettv);
1193}
1194
1195/*
Bram Moolenaara42d9452019-06-15 21:46:30 +02001196 * popup_filter_yesno({text}, {options})
1197 */
1198 void
1199f_popup_filter_yesno(typval_T *argvars, typval_T *rettv)
1200{
1201 int id = tv_get_number(&argvars[0]);
1202 win_T *wp = win_id2wp(id);
1203 char_u *key = tv_get_string(&argvars[1]);
1204 typval_T res;
Bram Moolenaara730e552019-06-16 19:05:31 +02001205 int c;
Bram Moolenaara42d9452019-06-15 21:46:30 +02001206
1207 // If the popup has been closed don't consume the key.
1208 if (wp == NULL)
1209 return;
1210
Bram Moolenaara730e552019-06-16 19:05:31 +02001211 c = *key;
1212 if (c == K_SPECIAL && key[1] != NUL)
1213 c = TO_SPECIAL(key[1], key[2]);
1214
Bram Moolenaara42d9452019-06-15 21:46:30 +02001215 // consume all keys until done
1216 rettv->vval.v_number = 1;
1217
Bram Moolenaara730e552019-06-16 19:05:31 +02001218 if (c == 'y' || c == 'Y')
Bram Moolenaara42d9452019-06-15 21:46:30 +02001219 res.vval.v_number = 1;
Bram Moolenaara730e552019-06-16 19:05:31 +02001220 else if (c == 'n' || c == 'N' || c == 'x' || c == 'X' || c == ESC)
Bram Moolenaara42d9452019-06-15 21:46:30 +02001221 res.vval.v_number = 0;
1222 else
1223 {
Bram Moolenaara730e552019-06-16 19:05:31 +02001224 filter_handle_drag(wp, c, rettv);
Bram Moolenaara42d9452019-06-15 21:46:30 +02001225 return;
1226 }
1227
1228 // Invoke callback
1229 res.v_type = VAR_NUMBER;
1230 popup_close_and_callback(wp, &res);
1231}
1232
1233/*
1234 * popup_dialog({text}, {options})
1235 */
1236 void
1237f_popup_dialog(typval_T *argvars, typval_T *rettv)
1238{
1239 popup_create(argvars, rettv, TYPE_DIALOG);
1240}
1241
1242/*
Bram Moolenaara730e552019-06-16 19:05:31 +02001243 * popup_menu({text}, {options})
1244 */
1245 void
1246f_popup_menu(typval_T *argvars, typval_T *rettv)
1247{
1248 win_T *wp = popup_create(argvars, rettv, TYPE_MENU);
1249
1250 if (wp != NULL)
1251 popup_highlight_curline(wp);
1252}
1253
1254/*
Bram Moolenaara42d9452019-06-15 21:46:30 +02001255 * popup_notification({text}, {options})
1256 */
1257 void
1258f_popup_notification(typval_T *argvars, typval_T *rettv)
1259{
1260 popup_create(argvars, rettv, TYPE_NOTIFICATION);
1261}
1262
1263/*
1264 * Find the popup window with window-ID "id".
1265 * If the popup window does not exist NULL is returned.
1266 * If the window is not a popup window, and error message is given.
1267 */
1268 static win_T *
1269find_popup_win(int id)
1270{
1271 win_T *wp = win_id2wp(id);
1272
1273 if (wp != NULL && !bt_popup(wp->w_buffer))
1274 {
1275 semsg(_("E993: window %d is not a popup window"), id);
1276 return NULL;
1277 }
1278 return wp;
1279}
1280
1281/*
Bram Moolenaar4d784b22019-05-25 19:51:39 +02001282 * popup_close({id})
1283 */
1284 void
1285f_popup_close(typval_T *argvars, typval_T *rettv UNUSED)
1286{
Bram Moolenaar2cd0dce2019-05-26 22:17:52 +02001287 int id = (int)tv_get_number(argvars);
Bram Moolenaar9eaac892019-06-01 22:49:29 +02001288 win_T *wp = find_popup_win(id);
Bram Moolenaar4d784b22019-05-25 19:51:39 +02001289
Bram Moolenaar9eaac892019-06-01 22:49:29 +02001290 if (wp != NULL)
Bram Moolenaar3397f742019-06-02 18:40:06 +02001291 popup_close_and_callback(wp, &argvars[1]);
Bram Moolenaar2cd0dce2019-05-26 22:17:52 +02001292}
1293
1294/*
1295 * popup_hide({id})
1296 */
1297 void
1298f_popup_hide(typval_T *argvars, typval_T *rettv UNUSED)
1299{
1300 int id = (int)tv_get_number(argvars);
1301 win_T *wp = find_popup_win(id);
1302
Bram Moolenaarbf0ecb22019-05-27 10:04:40 +02001303 if (wp != NULL && (wp->w_popup_flags & POPF_HIDDEN) == 0)
Bram Moolenaar2cd0dce2019-05-26 22:17:52 +02001304 {
Bram Moolenaarbf0ecb22019-05-27 10:04:40 +02001305 wp->w_popup_flags |= POPF_HIDDEN;
Bram Moolenaarc6896e22019-05-30 22:32:34 +02001306 --wp->w_buffer->b_nwindows;
Bram Moolenaar2cd0dce2019-05-26 22:17:52 +02001307 redraw_all_later(NOT_VALID);
Bram Moolenaar33796b32019-06-08 16:01:13 +02001308 popup_mask_refresh = TRUE;
Bram Moolenaar2cd0dce2019-05-26 22:17:52 +02001309 }
1310}
1311
1312/*
1313 * popup_show({id})
1314 */
1315 void
1316f_popup_show(typval_T *argvars, typval_T *rettv UNUSED)
1317{
1318 int id = (int)tv_get_number(argvars);
1319 win_T *wp = find_popup_win(id);
1320
Bram Moolenaarbf0ecb22019-05-27 10:04:40 +02001321 if (wp != NULL && (wp->w_popup_flags & POPF_HIDDEN) != 0)
Bram Moolenaar2cd0dce2019-05-26 22:17:52 +02001322 {
Bram Moolenaarbf0ecb22019-05-27 10:04:40 +02001323 wp->w_popup_flags &= ~POPF_HIDDEN;
Bram Moolenaarc6896e22019-05-30 22:32:34 +02001324 ++wp->w_buffer->b_nwindows;
Bram Moolenaar2cd0dce2019-05-26 22:17:52 +02001325 redraw_all_later(NOT_VALID);
Bram Moolenaar33796b32019-06-08 16:01:13 +02001326 popup_mask_refresh = TRUE;
Bram Moolenaar2cd0dce2019-05-26 22:17:52 +02001327 }
Bram Moolenaar4d784b22019-05-25 19:51:39 +02001328}
1329
Bram Moolenaardc2ce582019-06-16 15:32:14 +02001330/*
1331 * popup_settext({id}, {text})
1332 */
1333 void
1334f_popup_settext(typval_T *argvars, typval_T *rettv UNUSED)
1335{
1336 int id = (int)tv_get_number(&argvars[0]);
1337 win_T *wp = find_popup_win(id);
1338
1339 if (wp != NULL)
1340 {
1341 popup_set_buffer_text(wp->w_buffer, argvars[1]);
1342 popup_adjust_position(wp);
1343 }
1344}
1345
Bram Moolenaar51fe3b12019-05-26 20:10:06 +02001346 static void
Bram Moolenaar2cd0dce2019-05-26 22:17:52 +02001347popup_free(win_T *wp)
Bram Moolenaar51fe3b12019-05-26 20:10:06 +02001348{
Bram Moolenaar868b7b62019-05-29 21:44:40 +02001349 wp->w_buffer->b_locked = FALSE;
Bram Moolenaar51fe3b12019-05-26 20:10:06 +02001350 if (wp->w_winrow + wp->w_height >= cmdline_row)
1351 clear_cmdline = TRUE;
1352 win_free_popup(wp);
1353 redraw_all_later(NOT_VALID);
Bram Moolenaar33796b32019-06-08 16:01:13 +02001354 popup_mask_refresh = TRUE;
Bram Moolenaar51fe3b12019-05-26 20:10:06 +02001355}
1356
Bram Moolenaarec583842019-05-26 14:11:23 +02001357/*
1358 * Close a popup window by Window-id.
Bram Moolenaar9eaac892019-06-01 22:49:29 +02001359 * Does not invoke the callback.
Bram Moolenaarec583842019-05-26 14:11:23 +02001360 */
Bram Moolenaar4d784b22019-05-25 19:51:39 +02001361 void
Bram Moolenaarec583842019-05-26 14:11:23 +02001362popup_close(int id)
Bram Moolenaar4d784b22019-05-25 19:51:39 +02001363{
1364 win_T *wp;
Bram Moolenaarec583842019-05-26 14:11:23 +02001365 tabpage_T *tp;
Bram Moolenaar4d784b22019-05-25 19:51:39 +02001366 win_T *prev = NULL;
1367
Bram Moolenaarec583842019-05-26 14:11:23 +02001368 // go through global popups
Bram Moolenaar4d784b22019-05-25 19:51:39 +02001369 for (wp = first_popupwin; wp != NULL; prev = wp, wp = wp->w_next)
Bram Moolenaarec583842019-05-26 14:11:23 +02001370 if (wp->w_id == id)
Bram Moolenaar4d784b22019-05-25 19:51:39 +02001371 {
1372 if (prev == NULL)
1373 first_popupwin = wp->w_next;
1374 else
1375 prev->w_next = wp->w_next;
Bram Moolenaar2cd0dce2019-05-26 22:17:52 +02001376 popup_free(wp);
Bram Moolenaarec583842019-05-26 14:11:23 +02001377 return;
Bram Moolenaar4d784b22019-05-25 19:51:39 +02001378 }
1379
Bram Moolenaarec583842019-05-26 14:11:23 +02001380 // go through tab-local popups
1381 FOR_ALL_TABPAGES(tp)
1382 popup_close_tabpage(tp, id);
1383}
1384
1385/*
1386 * Close a popup window with Window-id "id" in tabpage "tp".
1387 */
1388 void
1389popup_close_tabpage(tabpage_T *tp, int id)
1390{
1391 win_T *wp;
Bram Moolenaar9c27b1c2019-05-26 18:48:13 +02001392 win_T **root = &tp->tp_first_popupwin;
Bram Moolenaarec583842019-05-26 14:11:23 +02001393 win_T *prev = NULL;
1394
Bram Moolenaarec583842019-05-26 14:11:23 +02001395 for (wp = *root; wp != NULL; prev = wp, wp = wp->w_next)
1396 if (wp->w_id == id)
1397 {
1398 if (prev == NULL)
1399 *root = wp->w_next;
1400 else
1401 prev->w_next = wp->w_next;
Bram Moolenaar2cd0dce2019-05-26 22:17:52 +02001402 popup_free(wp);
Bram Moolenaarec583842019-05-26 14:11:23 +02001403 return;
1404 }
Bram Moolenaar4d784b22019-05-25 19:51:39 +02001405}
1406
1407 void
1408close_all_popups(void)
1409{
1410 while (first_popupwin != NULL)
1411 popup_close(first_popupwin->w_id);
Bram Moolenaar9c27b1c2019-05-26 18:48:13 +02001412 while (curtab->tp_first_popupwin != NULL)
1413 popup_close(curtab->tp_first_popupwin->w_id);
Bram Moolenaar4d784b22019-05-25 19:51:39 +02001414}
1415
Bram Moolenaar60cdb302019-05-27 21:54:10 +02001416/*
1417 * popup_move({id}, {options})
1418 */
1419 void
1420f_popup_move(typval_T *argvars, typval_T *rettv UNUSED)
1421{
Bram Moolenaarae943152019-06-16 22:54:14 +02001422 dict_T *dict;
Bram Moolenaar60cdb302019-05-27 21:54:10 +02001423 int id = (int)tv_get_number(argvars);
1424 win_T *wp = find_popup_win(id);
1425
1426 if (wp == NULL)
1427 return; // invalid {id}
1428
1429 if (argvars[1].v_type != VAR_DICT || argvars[1].vval.v_dict == NULL)
1430 {
1431 emsg(_(e_dictreq));
1432 return;
1433 }
Bram Moolenaarae943152019-06-16 22:54:14 +02001434 dict = argvars[1].vval.v_dict;
Bram Moolenaar60cdb302019-05-27 21:54:10 +02001435
Bram Moolenaarae943152019-06-16 22:54:14 +02001436 apply_move_options(wp, dict);
Bram Moolenaar60cdb302019-05-27 21:54:10 +02001437
1438 if (wp->w_winrow + wp->w_height >= cmdline_row)
1439 clear_cmdline = TRUE;
1440 popup_adjust_position(wp);
Bram Moolenaar60cdb302019-05-27 21:54:10 +02001441}
1442
Bram Moolenaarbc133542019-05-29 20:26:46 +02001443/*
Bram Moolenaarae943152019-06-16 22:54:14 +02001444 * popup_setoptions({id}, {options})
1445 */
1446 void
1447f_popup_setoptions(typval_T *argvars, typval_T *rettv UNUSED)
1448{
1449 dict_T *dict;
1450 int id = (int)tv_get_number(argvars);
1451 win_T *wp = find_popup_win(id);
1452
1453 if (wp == NULL)
1454 return; // invalid {id}
1455
1456 if (argvars[1].v_type != VAR_DICT || argvars[1].vval.v_dict == NULL)
1457 {
1458 emsg(_(e_dictreq));
1459 return;
1460 }
1461 dict = argvars[1].vval.v_dict;
1462
1463 apply_move_options(wp, dict);
1464 apply_general_options(wp, dict);
1465
Bram Moolenaarad24a712019-06-17 20:05:45 +02001466 popup_mask_refresh = TRUE;
Bram Moolenaarae943152019-06-16 22:54:14 +02001467 popup_adjust_position(wp);
1468}
1469
1470/*
Bram Moolenaarac1f1bc2019-05-30 21:24:26 +02001471 * popup_getpos({id})
Bram Moolenaarbc133542019-05-29 20:26:46 +02001472 */
1473 void
Bram Moolenaarac1f1bc2019-05-30 21:24:26 +02001474f_popup_getpos(typval_T *argvars, typval_T *rettv)
Bram Moolenaarbc133542019-05-29 20:26:46 +02001475{
1476 dict_T *dict;
1477 int id = (int)tv_get_number(argvars);
1478 win_T *wp = find_popup_win(id);
Bram Moolenaar2fd8e352019-06-01 20:16:48 +02001479 int top_extra;
1480 int left_extra;
Bram Moolenaarbc133542019-05-29 20:26:46 +02001481
1482 if (rettv_dict_alloc(rettv) == OK)
1483 {
1484 if (wp == NULL)
1485 return; // invalid {id}
Bram Moolenaareb2310d2019-06-16 20:09:10 +02001486 top_extra = popup_top_extra(wp);
Bram Moolenaar2fd8e352019-06-01 20:16:48 +02001487 left_extra = wp->w_popup_border[3] + wp->w_popup_padding[3];
1488
Bram Moolenaarbc133542019-05-29 20:26:46 +02001489 dict = rettv->vval.v_dict;
Bram Moolenaar2fd8e352019-06-01 20:16:48 +02001490
Bram Moolenaarbc133542019-05-29 20:26:46 +02001491 dict_add_number(dict, "line", wp->w_winrow + 1);
1492 dict_add_number(dict, "col", wp->w_wincol + 1);
Bram Moolenaar68d48f42019-06-12 22:42:41 +02001493 dict_add_number(dict, "width", wp->w_width + left_extra
1494 + wp->w_popup_border[1] + wp->w_popup_padding[1]);
1495 dict_add_number(dict, "height", wp->w_height + top_extra
1496 + wp->w_popup_border[2] + wp->w_popup_padding[2]);
Bram Moolenaar2fd8e352019-06-01 20:16:48 +02001497
1498 dict_add_number(dict, "core_line", wp->w_winrow + 1 + top_extra);
1499 dict_add_number(dict, "core_col", wp->w_wincol + 1 + left_extra);
1500 dict_add_number(dict, "core_width", wp->w_width);
1501 dict_add_number(dict, "core_height", wp->w_height);
1502
Bram Moolenaar8c2a6002019-05-30 14:29:45 +02001503 dict_add_number(dict, "visible",
Bram Moolenaarb0ebbda2019-06-02 16:51:21 +02001504 win_valid(wp) && (wp->w_popup_flags & POPF_HIDDEN) == 0);
Bram Moolenaar8c2a6002019-05-30 14:29:45 +02001505 }
1506}
1507
1508/*
Bram Moolenaarae943152019-06-16 22:54:14 +02001509 * For popup_getoptions(): add a "border" or "padding" entry to "dict".
1510 */
1511 static void
1512get_padding_border(dict_T *dict, int *array, char *name)
1513{
1514 list_T *list;
1515 int i;
1516
1517 if (array[0] == 0 && array[1] == 0 && array[2] == 0 && array[3] == 0)
1518 return;
1519
1520 list = list_alloc();
1521 if (list != NULL)
1522 {
1523 dict_add_list(dict, name, list);
1524 if (array[0] != 1 || array[1] != 1 || array[2] != 1 || array[3] != 1)
1525 for (i = 0; i < 4; ++i)
1526 list_append_number(list, array[i]);
1527 }
1528}
1529
1530/*
1531 * For popup_getoptions(): add a "borderhighlight" entry to "dict".
1532 */
1533 static void
1534get_borderhighlight(dict_T *dict, win_T *wp)
1535{
1536 list_T *list;
1537 int i;
1538
1539 for (i = 0; i < 4; ++i)
1540 if (wp->w_border_highlight[i] != NULL)
1541 break;
1542 if (i == 4)
1543 return;
1544
1545 list = list_alloc();
1546 if (list != NULL)
1547 {
1548 dict_add_list(dict, "borderhighlight", list);
1549 for (i = 0; i < 4; ++i)
1550 list_append_string(list, wp->w_border_highlight[i], -1);
1551 }
1552}
1553
1554/*
1555 * For popup_getoptions(): add a "borderchars" entry to "dict".
1556 */
1557 static void
1558get_borderchars(dict_T *dict, win_T *wp)
1559{
1560 list_T *list;
1561 int i;
1562 char_u buf[NUMBUFLEN];
1563 int len;
1564
1565 for (i = 0; i < 8; ++i)
1566 if (wp->w_border_char[i] != 0)
1567 break;
1568 if (i == 8)
1569 return;
1570
1571 list = list_alloc();
1572 if (list != NULL)
1573 {
1574 dict_add_list(dict, "borderchars", list);
1575 for (i = 0; i < 8; ++i)
1576 {
1577 len = mb_char2bytes(wp->w_border_char[i], buf);
1578 list_append_string(list, buf, len);
1579 }
1580 }
1581}
1582
1583/*
1584 * For popup_getoptions(): add a "moved" entry to "dict".
1585 */
1586 static void
1587get_moved_list(dict_T *dict, win_T *wp)
1588{
1589 list_T *list;
1590
1591 list = list_alloc();
1592 if (list != NULL)
1593 {
1594 dict_add_list(dict, "moved", list);
1595 list_append_number(list, wp->w_popup_mincol);
1596 list_append_number(list, wp->w_popup_maxcol);
1597 }
1598}
1599
1600/*
Bram Moolenaar33796b32019-06-08 16:01:13 +02001601 * popup_getoptions({id})
Bram Moolenaar8c2a6002019-05-30 14:29:45 +02001602 */
1603 void
1604f_popup_getoptions(typval_T *argvars, typval_T *rettv)
1605{
1606 dict_T *dict;
1607 int id = (int)tv_get_number(argvars);
1608 win_T *wp = find_popup_win(id);
Bram Moolenaara3fce622019-06-20 02:31:49 +02001609 tabpage_T *tp;
Bram Moolenaarac1f1bc2019-05-30 21:24:26 +02001610 int i;
Bram Moolenaar8c2a6002019-05-30 14:29:45 +02001611
1612 if (rettv_dict_alloc(rettv) == OK)
1613 {
1614 if (wp == NULL)
1615 return;
1616
1617 dict = rettv->vval.v_dict;
1618 dict_add_number(dict, "line", wp->w_wantline);
1619 dict_add_number(dict, "col", wp->w_wantcol);
1620 dict_add_number(dict, "minwidth", wp->w_minwidth);
1621 dict_add_number(dict, "minheight", wp->w_minheight);
1622 dict_add_number(dict, "maxheight", wp->w_maxheight);
1623 dict_add_number(dict, "maxwidth", wp->w_maxwidth);
Bram Moolenaar8d241042019-06-12 23:40:01 +02001624 dict_add_number(dict, "firstline", wp->w_firstline);
Bram Moolenaar8c2a6002019-05-30 14:29:45 +02001625 dict_add_number(dict, "zindex", wp->w_zindex);
Bram Moolenaar042fb4b2019-06-02 14:49:56 +02001626 dict_add_number(dict, "fixed", wp->w_popup_fixed);
Bram Moolenaarae943152019-06-16 22:54:14 +02001627 dict_add_string(dict, "title", wp->w_popup_title);
1628 dict_add_number(dict, "wrap", wp->w_p_wrap);
1629 dict_add_number(dict, "drag", wp->w_popup_drag);
1630 dict_add_string(dict, "highlight", wp->w_p_wcr);
1631
Bram Moolenaara3fce622019-06-20 02:31:49 +02001632 // find the tabpage that holds this popup
1633 i = 1;
1634 FOR_ALL_TABPAGES(tp)
1635 {
1636 win_T *p;
1637
1638 for (p = tp->tp_first_popupwin; p != NULL; p = wp->w_next)
1639 if (p->w_id == id)
1640 break;
1641 if (p != NULL)
1642 break;
1643 ++i;
1644 }
1645 if (tp == NULL)
1646 i = -1; // must be global
1647 else if (tp == curtab)
1648 i = 0;
1649 dict_add_number(dict, "tabpage", i);
1650
Bram Moolenaarae943152019-06-16 22:54:14 +02001651 get_padding_border(dict, wp->w_popup_padding, "padding");
1652 get_padding_border(dict, wp->w_popup_border, "border");
1653 get_borderhighlight(dict, wp);
1654 get_borderchars(dict, wp);
1655 get_moved_list(dict, wp);
1656
1657 if (wp->w_filter_cb.cb_name != NULL)
1658 dict_add_callback(dict, "filter", &wp->w_filter_cb);
1659 if (wp->w_close_cb.cb_name != NULL)
1660 dict_add_callback(dict, "callback", &wp->w_close_cb);
Bram Moolenaarac1f1bc2019-05-30 21:24:26 +02001661
1662 for (i = 0; i < (int)(sizeof(poppos_entries) / sizeof(poppos_entry_T));
1663 ++i)
1664 if (wp->w_popup_pos == poppos_entries[i].pp_val)
1665 {
1666 dict_add_string(dict, "pos",
1667 (char_u *)poppos_entries[i].pp_name);
1668 break;
1669 }
1670
Bram Moolenaar8c2a6002019-05-30 14:29:45 +02001671# if defined(FEAT_TIMERS)
1672 dict_add_number(dict, "time", wp->w_popup_timer != NULL
1673 ? (long)wp->w_popup_timer->tr_interval : 0L);
1674# endif
Bram Moolenaarbc133542019-05-29 20:26:46 +02001675 }
1676}
Bram Moolenaar815b76b2019-06-01 14:15:52 +02001677
1678 int
Bram Moolenaar8cdbd5b2019-06-16 15:50:45 +02001679error_if_popup_window()
Bram Moolenaar815b76b2019-06-01 14:15:52 +02001680{
1681 if (bt_popup(curwin->w_buffer))
1682 {
1683 emsg(_("E994: Not allowed in a popup window"));
1684 return TRUE;
1685 }
1686 return FALSE;
1687}
1688
Bram Moolenaarbf0eff02019-06-01 17:13:36 +02001689/*
1690 * Reset all the POPF_HANDLED flags in global popup windows and popup windows
Bram Moolenaarfc06cbb2019-06-15 14:14:31 +02001691 * in the current tab page.
Bram Moolenaarbf0eff02019-06-01 17:13:36 +02001692 */
1693 void
1694popup_reset_handled()
1695{
1696 win_T *wp;
1697
1698 for (wp = first_popupwin; wp != NULL; wp = wp->w_next)
1699 wp->w_popup_flags &= ~POPF_HANDLED;
1700 for (wp = curtab->tp_first_popupwin; wp != NULL; wp = wp->w_next)
1701 wp->w_popup_flags &= ~POPF_HANDLED;
1702}
1703
1704/*
1705 * Find the next visible popup where POPF_HANDLED is not set.
1706 * Must have called popup_reset_handled() first.
1707 * When "lowest" is TRUE find the popup with the lowest zindex, otherwise the
1708 * popup with the highest zindex.
1709 */
1710 win_T *
1711find_next_popup(int lowest)
1712{
1713 win_T *wp;
1714 win_T *found_wp;
1715 int found_zindex;
1716
1717 found_zindex = lowest ? INT_MAX : 0;
1718 found_wp = NULL;
1719 for (wp = first_popupwin; wp != NULL; wp = wp->w_next)
1720 if ((wp->w_popup_flags & (POPF_HANDLED|POPF_HIDDEN)) == 0
1721 && (lowest ? wp->w_zindex < found_zindex
1722 : wp->w_zindex > found_zindex))
1723 {
1724 found_zindex = wp->w_zindex;
1725 found_wp = wp;
1726 }
1727 for (wp = curtab->tp_first_popupwin; wp != NULL; wp = wp->w_next)
1728 if ((wp->w_popup_flags & (POPF_HANDLED|POPF_HIDDEN)) == 0
1729 && (lowest ? wp->w_zindex < found_zindex
1730 : wp->w_zindex > found_zindex))
1731 {
1732 found_zindex = wp->w_zindex;
1733 found_wp = wp;
1734 }
1735
1736 if (found_wp != NULL)
1737 found_wp->w_popup_flags |= POPF_HANDLED;
1738 return found_wp;
1739}
1740
1741/*
1742 * Invoke the filter callback for window "wp" with typed character "c".
1743 * Uses the global "mod_mask" for modifiers.
1744 * Returns the return value of the filter.
1745 * Careful: The filter may make "wp" invalid!
1746 */
1747 static int
1748invoke_popup_filter(win_T *wp, int c)
1749{
1750 int res;
1751 typval_T rettv;
1752 int dummy;
1753 typval_T argv[3];
1754 char_u buf[NUMBUFLEN];
1755
Bram Moolenaara42d9452019-06-15 21:46:30 +02001756 // Emergency exit: CTRL-C closes the popup.
1757 if (c == Ctrl_C)
1758 {
1759 rettv.v_type = VAR_NUMBER;
1760 rettv.vval.v_number = -1;
1761 popup_close_and_callback(wp, &rettv);
1762 return 1;
1763 }
1764
Bram Moolenaarbf0eff02019-06-01 17:13:36 +02001765 argv[0].v_type = VAR_NUMBER;
1766 argv[0].vval.v_number = (varnumber_T)wp->w_id;
1767
1768 // Convert the number to a string, so that the function can use:
1769 // if a:c == "\<F2>"
1770 buf[special_to_buf(c, mod_mask, TRUE, buf)] = NUL;
1771 argv[1].v_type = VAR_STRING;
1772 argv[1].vval.v_string = vim_strsave(buf);
1773
1774 argv[2].v_type = VAR_UNKNOWN;
1775
Bram Moolenaara42d9452019-06-15 21:46:30 +02001776 // NOTE: The callback might close the popup, thus make "wp" invalid.
Bram Moolenaarbf0eff02019-06-01 17:13:36 +02001777 call_callback(&wp->w_filter_cb, -1,
1778 &rettv, 2, argv, NULL, 0L, 0L, &dummy, TRUE, NULL);
1779 res = tv_get_number(&rettv);
1780 vim_free(argv[1].vval.v_string);
1781 clear_tv(&rettv);
1782 return res;
1783}
1784
1785/*
1786 * Called when "c" was typed: invoke popup filter callbacks.
1787 * Returns TRUE when the character was consumed,
1788 */
1789 int
1790popup_do_filter(int c)
1791{
1792 int res = FALSE;
Bram Moolenaara42d9452019-06-15 21:46:30 +02001793 win_T *wp;
Bram Moolenaarbf0eff02019-06-01 17:13:36 +02001794
1795 popup_reset_handled();
1796
1797 while (!res && (wp = find_next_popup(FALSE)) != NULL)
1798 if (wp->w_filter_cb.cb_name != NULL)
1799 res = invoke_popup_filter(wp, c);
1800
1801 return res;
1802}
1803
Bram Moolenaar3397f742019-06-02 18:40:06 +02001804/*
1805 * Called when the cursor moved: check if any popup needs to be closed if the
1806 * cursor moved far enough.
1807 */
1808 void
1809popup_check_cursor_pos()
1810{
1811 win_T *wp;
1812 typval_T tv;
1813
1814 popup_reset_handled();
1815 while ((wp = find_next_popup(TRUE)) != NULL)
1816 if (wp->w_popup_curwin != NULL
1817 && (curwin != wp->w_popup_curwin
1818 || curwin->w_cursor.lnum != wp->w_popup_lnum
1819 || curwin->w_cursor.col < wp->w_popup_mincol
1820 || curwin->w_cursor.col > wp->w_popup_maxcol))
1821 {
1822 tv.v_type = VAR_NUMBER;
1823 tv.vval.v_number = -1;
1824 popup_close_and_callback(wp, &tv);
1825 }
1826}
1827
Bram Moolenaara540f8a2019-06-14 19:23:57 +02001828/*
1829 * Update "popup_mask" if needed.
1830 * Also recomputes the popup size and positions.
1831 * Also updates "popup_visible".
1832 * Also marks window lines for redrawing.
1833 */
1834 void
1835may_update_popup_mask(int type)
1836{
1837 win_T *wp;
1838 short *mask;
1839 int line, col;
1840 int redraw_all = FALSE;
1841
1842 // Need to recompute when switching tabs.
1843 // Also recompute when the type is CLEAR or NOT_VALID, something basic
1844 // (such as the screen size) must have changed.
1845 if (popup_mask_tab != curtab || type >= NOT_VALID)
1846 {
1847 popup_mask_refresh = TRUE;
1848 redraw_all = TRUE;
1849 }
1850 if (!popup_mask_refresh)
1851 {
1852 // Check if any buffer has changed.
1853 for (wp = first_popupwin; wp != NULL; wp = wp->w_next)
1854 if (wp->w_popup_last_changedtick != CHANGEDTICK(wp->w_buffer))
1855 popup_mask_refresh = TRUE;
1856 for (wp = curtab->tp_first_popupwin; wp != NULL; wp = wp->w_next)
1857 if (wp->w_popup_last_changedtick != CHANGEDTICK(wp->w_buffer))
1858 popup_mask_refresh = TRUE;
1859 if (!popup_mask_refresh)
1860 return;
1861 }
1862
1863 // Need to update the mask, something has changed.
1864 popup_mask_refresh = FALSE;
1865 popup_mask_tab = curtab;
1866 popup_visible = FALSE;
1867
1868 // If redrawing everything, just update "popup_mask".
1869 // If redrawing only what is needed, update "popup_mask_next" and then
1870 // compare with "popup_mask" to see what changed.
1871 if (type >= SOME_VALID)
1872 mask = popup_mask;
1873 else
1874 mask = popup_mask_next;
1875 vim_memset(mask, 0, screen_Rows * screen_Columns * sizeof(short));
1876
1877 // Find the window with the lowest zindex that hasn't been handled yet,
1878 // so that the window with a higher zindex overwrites the value in
1879 // popup_mask.
1880 popup_reset_handled();
1881 while ((wp = find_next_popup(TRUE)) != NULL)
1882 {
1883 popup_visible = TRUE;
1884
1885 // Recompute the position if the text changed.
1886 if (redraw_all
1887 || wp->w_popup_last_changedtick != CHANGEDTICK(wp->w_buffer))
1888 popup_adjust_position(wp);
1889
1890 for (line = wp->w_winrow;
1891 line < wp->w_winrow + popup_height(wp)
1892 && line < screen_Rows; ++line)
1893 for (col = wp->w_wincol;
1894 col < wp->w_wincol + popup_width(wp)
1895 && col < screen_Columns; ++col)
1896 mask[line * screen_Columns + col] = wp->w_zindex;
1897 }
1898
1899 // Only check which lines are to be updated if not already
1900 // updating all lines.
1901 if (mask == popup_mask_next)
1902 for (line = 0; line < screen_Rows; ++line)
1903 {
1904 int col_done = 0;
1905
1906 for (col = 0; col < screen_Columns; ++col)
1907 {
1908 int off = line * screen_Columns + col;
1909
1910 if (popup_mask[off] != popup_mask_next[off])
1911 {
1912 popup_mask[off] = popup_mask_next[off];
1913
1914 if (line >= cmdline_row)
1915 {
1916 // the command line needs to be cleared if text below
1917 // the popup is now visible.
1918 if (!msg_scrolled && popup_mask_next[off] == 0)
1919 clear_cmdline = TRUE;
1920 }
1921 else if (col >= col_done)
1922 {
1923 linenr_T lnum;
1924 int line_cp = line;
1925 int col_cp = col;
1926
1927 // The screen position "line" / "col" needs to be
1928 // redrawn. Figure out what window that is and update
1929 // w_redraw_top and w_redr_bot. Only needs to be done
1930 // once for each window line.
1931 wp = mouse_find_win(&line_cp, &col_cp, IGNORE_POPUP);
1932 if (wp != NULL)
1933 {
1934 if (line_cp >= wp->w_height)
1935 // In (or below) status line
1936 wp->w_redr_status = TRUE;
1937 // compute the position in the buffer line from the
1938 // position on the screen
1939 else if (mouse_comp_pos(wp, &line_cp, &col_cp,
1940 &lnum))
1941 // past bottom
1942 wp->w_redr_status = TRUE;
1943 else
1944 redrawWinline(wp, lnum);
1945
1946 // This line is going to be redrawn, no need to
1947 // check until the right side of the window.
1948 col_done = wp->w_wincol + wp->w_width - 1;
1949 }
1950 }
1951 }
1952 }
1953 }
1954}
1955
1956/*
1957 * Return a string of "len" spaces in IObuff.
1958 */
1959 static char_u *
1960get_spaces(int len)
1961{
1962 vim_memset(IObuff, ' ', (size_t)len);
1963 IObuff[len] = NUL;
1964 return IObuff;
1965}
1966
1967/*
1968 * Update popup windows. They are drawn on top of normal windows.
1969 * "win_update" is called for each popup window, lowest zindex first.
1970 */
1971 void
1972update_popups(void (*win_update)(win_T *wp))
1973{
1974 win_T *wp;
1975 int top_off;
1976 int left_off;
1977 int total_width;
1978 int total_height;
Bram Moolenaareb2310d2019-06-16 20:09:10 +02001979 int top_padding;
Bram Moolenaara540f8a2019-06-14 19:23:57 +02001980 int popup_attr;
1981 int border_attr[4];
1982 int border_char[8];
1983 char_u buf[MB_MAXBYTES];
1984 int row;
1985 int i;
1986
1987 // Find the window with the lowest zindex that hasn't been updated yet,
1988 // so that the window with a higher zindex is drawn later, thus goes on
1989 // top.
1990 popup_reset_handled();
1991 while ((wp = find_next_popup(TRUE)) != NULL)
1992 {
1993 // This drawing uses the zindex of the popup window, so that it's on
1994 // top of the text but doesn't draw when another popup with higher
1995 // zindex is on top of the character.
1996 screen_zindex = wp->w_zindex;
1997
1998 // adjust w_winrow and w_wincol for border and padding, since
1999 // win_update() doesn't handle them.
Bram Moolenaareb2310d2019-06-16 20:09:10 +02002000 top_off = popup_top_extra(wp);
Bram Moolenaara540f8a2019-06-14 19:23:57 +02002001 left_off = wp->w_popup_padding[3] + wp->w_popup_border[3];
2002 wp->w_winrow += top_off;
2003 wp->w_wincol += left_off;
2004
2005 // Draw the popup text.
2006 win_update(wp);
2007
2008 wp->w_winrow -= top_off;
2009 wp->w_wincol -= left_off;
2010
2011 total_width = wp->w_popup_border[3] + wp->w_popup_padding[3]
2012 + wp->w_width + wp->w_popup_padding[1] + wp->w_popup_border[1];
Bram Moolenaareb2310d2019-06-16 20:09:10 +02002013 total_height = popup_top_extra(wp)
Bram Moolenaara540f8a2019-06-14 19:23:57 +02002014 + wp->w_height + wp->w_popup_padding[2] + wp->w_popup_border[2];
2015 popup_attr = get_wcr_attr(wp);
2016
2017 // We can only use these line drawing characters when 'encoding' is
2018 // "utf-8" and 'ambiwidth' is "single".
2019 if (enc_utf8 && *p_ambw == 's')
2020 {
2021 border_char[0] = border_char[2] = 0x2550;
2022 border_char[1] = border_char[3] = 0x2551;
2023 border_char[4] = 0x2554;
2024 border_char[5] = 0x2557;
2025 border_char[6] = 0x255d;
2026 border_char[7] = 0x255a;
2027 }
2028 else
2029 {
2030 border_char[0] = border_char[2] = '-';
2031 border_char[1] = border_char[3] = '|';
2032 for (i = 4; i < 8; ++i)
2033 border_char[i] = '+';
2034 }
2035 for (i = 0; i < 8; ++i)
2036 if (wp->w_border_char[i] != 0)
2037 border_char[i] = wp->w_border_char[i];
2038
2039 for (i = 0; i < 4; ++i)
2040 {
2041 border_attr[i] = popup_attr;
2042 if (wp->w_border_highlight[i] != NULL)
2043 border_attr[i] = syn_name2attr(wp->w_border_highlight[i]);
2044 }
2045
Bram Moolenaareb2310d2019-06-16 20:09:10 +02002046 top_padding = wp->w_popup_padding[0];
Bram Moolenaara540f8a2019-06-14 19:23:57 +02002047 if (wp->w_popup_border[0] > 0)
2048 {
2049 // top border
2050 screen_fill(wp->w_winrow, wp->w_winrow + 1,
2051 wp->w_wincol,
2052 wp->w_wincol + total_width,
2053 wp->w_popup_border[3] != 0
2054 ? border_char[4] : border_char[0],
2055 border_char[0], border_attr[0]);
2056 if (wp->w_popup_border[1] > 0)
2057 {
2058 buf[mb_char2bytes(border_char[5], buf)] = NUL;
2059 screen_puts(buf, wp->w_winrow,
2060 wp->w_wincol + total_width - 1, border_attr[1]);
2061 }
2062 }
Bram Moolenaareb2310d2019-06-16 20:09:10 +02002063 else if (wp->w_popup_padding[0] == 0 && popup_top_extra(wp) > 0)
2064 top_padding = 1;
Bram Moolenaara540f8a2019-06-14 19:23:57 +02002065
Bram Moolenaareb2310d2019-06-16 20:09:10 +02002066 if (top_padding > 0)
Bram Moolenaara540f8a2019-06-14 19:23:57 +02002067 {
2068 // top padding
2069 row = wp->w_winrow + wp->w_popup_border[0];
Bram Moolenaareb2310d2019-06-16 20:09:10 +02002070 screen_fill(row, row + top_padding,
Bram Moolenaara540f8a2019-06-14 19:23:57 +02002071 wp->w_wincol + wp->w_popup_border[3],
2072 wp->w_wincol + total_width - wp->w_popup_border[1],
2073 ' ', ' ', popup_attr);
2074 }
2075
Bram Moolenaareb2310d2019-06-16 20:09:10 +02002076 // Title goes on top of border or padding.
2077 if (wp->w_popup_title != NULL)
2078 screen_puts(wp->w_popup_title, wp->w_winrow, wp->w_wincol + 1,
2079 wp->w_popup_border[0] > 0 ? border_attr[0] : popup_attr);
2080
Bram Moolenaara540f8a2019-06-14 19:23:57 +02002081 for (row = wp->w_winrow + wp->w_popup_border[0];
2082 row < wp->w_winrow + total_height - wp->w_popup_border[2];
2083 ++row)
2084 {
2085 // left border
2086 if (wp->w_popup_border[3] > 0)
2087 {
2088 buf[mb_char2bytes(border_char[3], buf)] = NUL;
2089 screen_puts(buf, row, wp->w_wincol, border_attr[3]);
2090 }
2091 // left padding
2092 if (wp->w_popup_padding[3] > 0)
2093 screen_puts(get_spaces(wp->w_popup_padding[3]), row,
2094 wp->w_wincol + wp->w_popup_border[3], popup_attr);
2095 // right border
2096 if (wp->w_popup_border[1] > 0)
2097 {
2098 buf[mb_char2bytes(border_char[1], buf)] = NUL;
2099 screen_puts(buf, row,
2100 wp->w_wincol + total_width - 1, border_attr[1]);
2101 }
2102 // right padding
2103 if (wp->w_popup_padding[1] > 0)
2104 screen_puts(get_spaces(wp->w_popup_padding[1]), row,
2105 wp->w_wincol + wp->w_popup_border[3]
2106 + wp->w_popup_padding[3] + wp->w_width, popup_attr);
2107 }
2108
2109 if (wp->w_popup_padding[2] > 0)
2110 {
2111 // bottom padding
2112 row = wp->w_winrow + wp->w_popup_border[0]
2113 + wp->w_popup_padding[0] + wp->w_height;
2114 screen_fill(row, row + wp->w_popup_padding[2],
2115 wp->w_wincol + wp->w_popup_border[3],
2116 wp->w_wincol + total_width - wp->w_popup_border[1],
2117 ' ', ' ', popup_attr);
2118 }
2119
2120 if (wp->w_popup_border[2] > 0)
2121 {
2122 // bottom border
2123 row = wp->w_winrow + total_height - 1;
2124 screen_fill(row , row + 1,
2125 wp->w_wincol,
2126 wp->w_wincol + total_width,
2127 wp->w_popup_border[3] != 0
2128 ? border_char[7] : border_char[2],
2129 border_char[2], border_attr[2]);
2130 if (wp->w_popup_border[1] > 0)
2131 {
2132 buf[mb_char2bytes(border_char[6], buf)] = NUL;
2133 screen_puts(buf, row,
2134 wp->w_wincol + total_width - 1, border_attr[2]);
2135 }
2136 }
2137
2138 // Back to the normal zindex.
2139 screen_zindex = 0;
2140 }
2141}
2142
Bram Moolenaar75a1a942019-06-20 03:45:36 +02002143/*
2144 * Mark references in callbacks of one popup window.
2145 */
2146 static int
2147set_ref_in_one_popup(win_T *wp, int copyID)
2148{
2149 int abort = FALSE;
2150 typval_T tv;
2151
2152 if (wp->w_close_cb.cb_partial != NULL)
2153 {
2154 tv.v_type = VAR_PARTIAL;
2155 tv.vval.v_partial = wp->w_close_cb.cb_partial;
2156 abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
2157 }
2158 if (wp->w_filter_cb.cb_partial != NULL)
2159 {
2160 tv.v_type = VAR_PARTIAL;
2161 tv.vval.v_partial = wp->w_filter_cb.cb_partial;
2162 abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
2163 }
2164 return abort;
2165}
2166
2167/*
2168 * Set reference in callbacks of popup windows.
2169 */
2170 int
2171set_ref_in_popups(int copyID)
2172{
2173 int abort = FALSE;
2174 win_T *wp;
2175 tabpage_T *tp;
2176
2177 for (wp = first_popupwin; !abort && wp != NULL; wp = wp->w_next)
2178 abort = abort || set_ref_in_one_popup(wp, copyID);
2179
2180 FOR_ALL_TABPAGES(tp)
2181 {
2182 for (wp = tp->tp_first_popupwin; !abort && wp != NULL; wp = wp->w_next)
2183 abort = abort || set_ref_in_one_popup(wp, copyID);
2184 if (abort)
2185 break;
2186 }
2187 return abort;
2188}
Bram Moolenaar4d784b22019-05-25 19:51:39 +02002189#endif // FEAT_TEXT_PROP