blob: cf2f2eff97e0087e11227a74327eebc55984fdcc [file] [log] [blame]
Bram Moolenaaredf3f972016-08-29 22:49:24 +02001/* vi:set ts=8 sts=4 sw=4 noet:
Bram Moolenaar4b779472005-10-04 09:12:31 +00002 *
3 * VIM - Vi IMproved by Bram Moolenaar
4 *
5 * Do ":help uganda" in Vim to read copying and usage conditions.
6 * Do ":help credits" in Vim to see a list of people who contributed.
7 * See README.txt for an overview of the Vim source code.
8 */
9
10/*
Bram Moolenaar30e8e732019-09-27 13:08:36 +020011 * popupmenu.c: Popup menu (PUM)
Bram Moolenaar4b779472005-10-04 09:12:31 +000012 */
13#include "vim.h"
14
Bram Moolenaar63d9e732019-12-05 21:10:38 +010015static pumitem_T *pum_array = NULL; // items of displayed pum
16static int pum_size; // nr of items in "pum_array"
17static int pum_selected; // index of selected item or -1
18static int pum_first = 0; // index of top item
Bram Moolenaar4b779472005-10-04 09:12:31 +000019
Bram Moolenaarae654382019-01-17 21:09:05 +010020static int call_update_screen = FALSE;
21
Bram Moolenaar63d9e732019-12-05 21:10:38 +010022static int pum_height; // nr of displayed pum items
23static int pum_width; // width of displayed pum items
24static int pum_base_width; // width of pum items base
25static int pum_kind_width; // width of pum items kind column
26static int pum_extra_width; // width of extra stuff
27static int pum_scrollbar; // TRUE when scrollbar present
Bram Moolenaar4b779472005-10-04 09:12:31 +000028
Bram Moolenaar63d9e732019-12-05 21:10:38 +010029static int pum_row; // top row of pum
30static int pum_col; // left column of pum
Bram Moolenaar4b779472005-10-04 09:12:31 +000031
Bram Moolenaar0e6e1792018-06-17 17:10:59 +020032static win_T *pum_window = NULL;
Bram Moolenaar491ac282018-06-17 14:47:55 +020033static int pum_win_row;
34static int pum_win_height;
35static int pum_win_col;
36static int pum_win_wcol;
37static int pum_win_width;
38
Bram Moolenaar04357fb2019-12-10 21:50:56 +010039// Some parts are not updated when a popup menu is visible. Setting this flag
40// makes pum_visible() return FALSE even when there is a popup menu.
41static int pum_pretend_not_visible = FALSE;
42
Bram Moolenaarbaaa7e92016-01-29 22:47:03 +010043static int pum_set_selected(int n, int repeat);
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +000044
Bram Moolenaar4b779472005-10-04 09:12:31 +000045#define PUM_DEF_HEIGHT 10
Bram Moolenaar4b779472005-10-04 09:12:31 +000046
Bram Moolenaar51b0f372017-11-18 18:52:04 +010047 static void
48pum_compute_size(void)
49{
50 int i;
51 int w;
52
Bram Moolenaar63d9e732019-12-05 21:10:38 +010053 // Compute the width of the widest match and the widest extra.
Bram Moolenaar51b0f372017-11-18 18:52:04 +010054 pum_base_width = 0;
55 pum_kind_width = 0;
56 pum_extra_width = 0;
57 for (i = 0; i < pum_size; ++i)
58 {
Bram Moolenaard58a6622020-05-02 14:52:57 +020059 if (pum_array[i].pum_text != NULL)
60 {
61 w = vim_strsize(pum_array[i].pum_text);
62 if (pum_base_width < w)
63 pum_base_width = w;
64 }
Bram Moolenaar51b0f372017-11-18 18:52:04 +010065 if (pum_array[i].pum_kind != NULL)
66 {
67 w = vim_strsize(pum_array[i].pum_kind) + 1;
68 if (pum_kind_width < w)
69 pum_kind_width = w;
70 }
71 if (pum_array[i].pum_extra != NULL)
72 {
73 w = vim_strsize(pum_array[i].pum_extra) + 1;
74 if (pum_extra_width < w)
75 pum_extra_width = w;
76 }
77 }
78}
79
Bram Moolenaar4b779472005-10-04 09:12:31 +000080/*
81 * Show the popup menu with items "array[size]".
82 * "array" must remain valid until pum_undisplay() is called!
Bram Moolenaar2b10bcb2018-02-24 21:25:44 +010083 * When possible the leftmost character is aligned with cursor column.
Bram Moolenaar4b779472005-10-04 09:12:31 +000084 * The menu appears above the screen line "row" or at "row" + "height" - 1.
85 */
86 void
Bram Moolenaar05540972016-01-30 20:31:25 +010087pum_display(
88 pumitem_T *array,
89 int size,
Bram Moolenaar63d9e732019-12-05 21:10:38 +010090 int selected) // index of initially selected item, none if
91 // out of range
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +000092{
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +000093 int def_width;
94 int max_width;
Bram Moolenaarc1a11ed2008-06-24 22:09:24 +000095 int context_lines;
Bram Moolenaar2b10bcb2018-02-24 21:25:44 +010096 int cursor_col;
Bram Moolenaar91e44a32016-11-04 20:08:52 +010097 int above_row;
98 int below_row;
Bram Moolenaarfc1421e2006-04-20 22:17:20 +000099 int redo_count = 0;
Bram Moolenaar4033c552017-09-16 20:54:51 +0200100#if defined(FEAT_QUICKFIX)
Bram Moolenaar91e44a32016-11-04 20:08:52 +0100101 win_T *pvwin;
Bram Moolenaaraab33832016-11-04 22:08:29 +0100102#endif
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000103
Bram Moolenaara5e66212017-09-29 22:42:33 +0200104 do
105 {
Bram Moolenaar42443c72018-02-10 18:28:52 +0100106 def_width = p_pw;
Bram Moolenaara5e66212017-09-29 22:42:33 +0200107 above_row = 0;
108 below_row = cmdline_row;
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000109
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100110 // Pretend the pum is already there to avoid that must_redraw is set
111 // when 'cuc' is on.
Bram Moolenaara5e66212017-09-29 22:42:33 +0200112 pum_array = (pumitem_T *)1;
113 validate_cursor_col();
114 pum_array = NULL;
Bram Moolenaar1e607892006-03-13 22:15:53 +0000115
Bram Moolenaar491ac282018-06-17 14:47:55 +0200116 // Remember the essential parts of the window position and size, so we
117 // can decide when to reposition the popup menu.
Bram Moolenaar0e6e1792018-06-17 17:10:59 +0200118 pum_window = curwin;
Yegappan Lakshmanan3908ef52022-02-08 12:08:07 +0000119 if (State == CMDLINE)
120 pum_win_row = cmdline_row;
121 else
122 pum_win_row = curwin->w_wrow + W_WINROW(curwin);
Bram Moolenaar491ac282018-06-17 14:47:55 +0200123 pum_win_height = curwin->w_height;
124 pum_win_col = curwin->w_wincol;
125 pum_win_wcol = curwin->w_wcol;
126 pum_win_width = curwin->w_width;
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000127
Bram Moolenaar4033c552017-09-16 20:54:51 +0200128#if defined(FEAT_QUICKFIX)
Bram Moolenaara5e66212017-09-29 22:42:33 +0200129 FOR_ALL_WINDOWS(pvwin)
130 if (pvwin->w_p_pvw)
131 break;
132 if (pvwin != NULL)
133 {
134 if (W_WINROW(pvwin) < W_WINROW(curwin))
135 above_row = W_WINROW(pvwin) + pvwin->w_height;
136 else if (W_WINROW(pvwin) > W_WINROW(curwin) + curwin->w_height)
137 below_row = W_WINROW(pvwin);
138 }
Bram Moolenaar0106e3d2016-02-23 18:55:43 +0100139#endif
Bram Moolenaarefd2bf12006-03-16 21:41:35 +0000140
Bram Moolenaara5e66212017-09-29 22:42:33 +0200141 /*
142 * Figure out the size and position of the pum.
143 */
144 if (size < PUM_DEF_HEIGHT)
Bram Moolenaar4b779472005-10-04 09:12:31 +0000145 pum_height = size;
Bram Moolenaar4b779472005-10-04 09:12:31 +0000146 else
Bram Moolenaara5e66212017-09-29 22:42:33 +0200147 pum_height = PUM_DEF_HEIGHT;
Bram Moolenaar7d47b6e2006-03-15 22:59:18 +0000148 if (p_ph > 0 && pum_height > p_ph)
149 pum_height = p_ph;
Bram Moolenaar4b779472005-10-04 09:12:31 +0000150
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100151 // Put the pum below "pum_win_row" if possible. If there are few lines
152 // decide on where there is more room.
Bram Moolenaar491ac282018-06-17 14:47:55 +0200153 if (pum_win_row + 2 >= below_row - pum_height
154 && pum_win_row - above_row > (below_row - above_row) / 2)
Bram Moolenaara5e66212017-09-29 22:42:33 +0200155 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100156 // pum above "pum_win_row"
Bram Moolenaara5e66212017-09-29 22:42:33 +0200157
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100158 // Leave two lines of context if possible
Bram Moolenaara5e66212017-09-29 22:42:33 +0200159 if (curwin->w_wrow - curwin->w_cline_row >= 2)
160 context_lines = 2;
161 else
162 context_lines = curwin->w_wrow - curwin->w_cline_row;
163
Bram Moolenaar491ac282018-06-17 14:47:55 +0200164 if (pum_win_row >= size + context_lines)
Bram Moolenaara5e66212017-09-29 22:42:33 +0200165 {
Bram Moolenaar491ac282018-06-17 14:47:55 +0200166 pum_row = pum_win_row - size - context_lines;
Bram Moolenaara5e66212017-09-29 22:42:33 +0200167 pum_height = size;
168 }
169 else
170 {
171 pum_row = 0;
Bram Moolenaar491ac282018-06-17 14:47:55 +0200172 pum_height = pum_win_row - context_lines;
Bram Moolenaara5e66212017-09-29 22:42:33 +0200173 }
174 if (p_ph > 0 && pum_height > p_ph)
175 {
176 pum_row += pum_height - p_ph;
177 pum_height = p_ph;
178 }
179 }
180 else
181 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100182 // pum below "pum_win_row"
Bram Moolenaara5e66212017-09-29 22:42:33 +0200183
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100184 // Leave two lines of context if possible
Bram Moolenaar09dd2bb2019-12-14 18:42:15 +0100185 validate_cheight();
Bram Moolenaara5e66212017-09-29 22:42:33 +0200186 if (curwin->w_cline_row
187 + curwin->w_cline_height - curwin->w_wrow >= 3)
188 context_lines = 3;
189 else
190 context_lines = curwin->w_cline_row
191 + curwin->w_cline_height - curwin->w_wrow;
192
Bram Moolenaar491ac282018-06-17 14:47:55 +0200193 pum_row = pum_win_row + context_lines;
Bram Moolenaara5e66212017-09-29 22:42:33 +0200194 if (size > below_row - pum_row)
195 pum_height = below_row - pum_row;
196 else
197 pum_height = size;
198 if (p_ph > 0 && pum_height > p_ph)
199 pum_height = p_ph;
200 }
201
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100202 // don't display when we only have room for one line
Bram Moolenaara5e66212017-09-29 22:42:33 +0200203 if (pum_height < 1 || (pum_height == 1 && size > 1))
204 return;
Bram Moolenaar4b779472005-10-04 09:12:31 +0000205
Bram Moolenaar4033c552017-09-16 20:54:51 +0200206#if defined(FEAT_QUICKFIX)
Bram Moolenaar614ab8a2018-12-01 11:59:00 +0100207 // If there is a preview window above avoid drawing over it.
208 if (pvwin != NULL && pum_row < above_row && pum_height > above_row)
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000209 {
Bram Moolenaar614ab8a2018-12-01 11:59:00 +0100210 pum_row = above_row;
211 pum_height = pum_win_row - above_row;
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000212 }
Bram Moolenaara5e66212017-09-29 22:42:33 +0200213#endif
214
Bram Moolenaar51b0f372017-11-18 18:52:04 +0100215 pum_array = array;
216 pum_size = size;
217 pum_compute_size();
218 max_width = pum_base_width;
Bram Moolenaar4b779472005-10-04 09:12:31 +0000219
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100220 // Calculate column
Yegappan Lakshmanan3908ef52022-02-08 12:08:07 +0000221#ifdef FEAT_WILDMENU
222 if (State == CMDLINE)
223 cursor_col = cmdline_compl_startcol();
224 else
225#endif
Bram Moolenaarabc97732007-08-08 20:49:37 +0000226#ifdef FEAT_RIGHTLEFT
227 if (curwin->w_p_rl)
Bram Moolenaar2b10bcb2018-02-24 21:25:44 +0100228 cursor_col = curwin->w_wincol + curwin->w_width
229 - curwin->w_wcol - 1;
Bram Moolenaarabc97732007-08-08 20:49:37 +0000230 else
231#endif
Bram Moolenaar2b10bcb2018-02-24 21:25:44 +0100232 cursor_col = curwin->w_wincol + curwin->w_wcol;
Bram Moolenaarabc97732007-08-08 20:49:37 +0000233
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100234 // if there are more items than room we need a scrollbar
Bram Moolenaara5e66212017-09-29 22:42:33 +0200235 if (pum_height < size)
Bram Moolenaar8b6144b2006-02-08 09:20:24 +0000236 {
Bram Moolenaara5e66212017-09-29 22:42:33 +0200237 pum_scrollbar = 1;
238 ++max_width;
Bram Moolenaar8b6144b2006-02-08 09:20:24 +0000239 }
Bram Moolenaarabc97732007-08-08 20:49:37 +0000240 else
Bram Moolenaara5e66212017-09-29 22:42:33 +0200241 pum_scrollbar = 0;
242
243 if (def_width < max_width)
244 def_width = max_width;
245
Bram Moolenaar2b10bcb2018-02-24 21:25:44 +0100246 if (((cursor_col < Columns - p_pw
247 || cursor_col < Columns - max_width)
Bram Moolenaarabc97732007-08-08 20:49:37 +0000248#ifdef FEAT_RIGHTLEFT
Bram Moolenaara5e66212017-09-29 22:42:33 +0200249 && !curwin->w_p_rl)
Bram Moolenaar2b10bcb2018-02-24 21:25:44 +0100250 || (curwin->w_p_rl
251 && (cursor_col > p_pw || cursor_col > max_width)
Bram Moolenaarabc97732007-08-08 20:49:37 +0000252#endif
Bram Moolenaara5e66212017-09-29 22:42:33 +0200253 ))
254 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100255 // align pum with "cursor_col"
Bram Moolenaar2b10bcb2018-02-24 21:25:44 +0100256 pum_col = cursor_col;
Bram Moolenaar4b779472005-10-04 09:12:31 +0000257
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100258 // start with the maximum space available
Bram Moolenaara5e66212017-09-29 22:42:33 +0200259#ifdef FEAT_RIGHTLEFT
260 if (curwin->w_p_rl)
261 pum_width = pum_col - pum_scrollbar + 1;
262 else
263#endif
264 pum_width = Columns - pum_col - pum_scrollbar;
Bram Moolenaar4b779472005-10-04 09:12:31 +0000265
Bram Moolenaar51b0f372017-11-18 18:52:04 +0100266 if (pum_width > max_width + pum_kind_width + pum_extra_width + 1
Bram Moolenaar42443c72018-02-10 18:28:52 +0100267 && pum_width > p_pw)
Bram Moolenaara5e66212017-09-29 22:42:33 +0200268 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100269 // the width is more than needed for the items, make it
270 // narrower
Bram Moolenaar51b0f372017-11-18 18:52:04 +0100271 pum_width = max_width + pum_kind_width + pum_extra_width + 1;
Bram Moolenaar42443c72018-02-10 18:28:52 +0100272 if (pum_width < p_pw)
273 pum_width = p_pw;
Bram Moolenaara5e66212017-09-29 22:42:33 +0200274 }
Bram Moolenaar2b10bcb2018-02-24 21:25:44 +0100275 else if (((cursor_col > p_pw || cursor_col > max_width)
Bram Moolenaara8f04aa2018-02-10 15:36:55 +0100276#ifdef FEAT_RIGHTLEFT
277 && !curwin->w_p_rl)
Bram Moolenaar2b10bcb2018-02-24 21:25:44 +0100278 || (curwin->w_p_rl && (cursor_col < Columns - p_pw
279 || cursor_col < Columns - max_width)
Bram Moolenaara8f04aa2018-02-10 15:36:55 +0100280#endif
281 ))
282 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100283 // align pum edge with "cursor_col"
Bram Moolenaara8f04aa2018-02-10 15:36:55 +0100284#ifdef FEAT_RIGHTLEFT
Bram Moolenaar4287ed32018-02-17 20:35:29 +0100285 if (curwin->w_p_rl
Bram Moolenaarbb008dd2018-02-24 18:59:55 +0100286 && W_ENDCOL(curwin) < max_width + pum_scrollbar + 1)
Bram Moolenaara8f04aa2018-02-10 15:36:55 +0100287 {
Bram Moolenaar2b10bcb2018-02-24 21:25:44 +0100288 pum_col = cursor_col + max_width + pum_scrollbar + 1;
Bram Moolenaara8f04aa2018-02-10 15:36:55 +0100289 if (pum_col >= Columns)
290 pum_col = Columns - 1;
291 }
Bram Moolenaar4287ed32018-02-17 20:35:29 +0100292 else if (!curwin->w_p_rl)
Bram Moolenaara8f04aa2018-02-10 15:36:55 +0100293#endif
294 {
Bram Moolenaar2b10bcb2018-02-24 21:25:44 +0100295 if (curwin->w_wincol > Columns - max_width - pum_scrollbar
296 && max_width <= p_pw)
Bram Moolenaar4287ed32018-02-17 20:35:29 +0100297 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100298 // use full width to end of the screen
Bram Moolenaar4287ed32018-02-17 20:35:29 +0100299 pum_col = Columns - max_width - pum_scrollbar;
300 if (pum_col < 0)
301 pum_col = 0;
302 }
Bram Moolenaara8f04aa2018-02-10 15:36:55 +0100303 }
304
305#ifdef FEAT_RIGHTLEFT
306 if (curwin->w_p_rl)
Bram Moolenaar4287ed32018-02-17 20:35:29 +0100307 pum_width = pum_col - pum_scrollbar + 1;
Bram Moolenaara8f04aa2018-02-10 15:36:55 +0100308 else
309#endif
Bram Moolenaar4287ed32018-02-17 20:35:29 +0100310 pum_width = Columns - pum_col - pum_scrollbar;
Bram Moolenaara8f04aa2018-02-10 15:36:55 +0100311
Bram Moolenaar42443c72018-02-10 18:28:52 +0100312 if (pum_width < p_pw)
Bram Moolenaara8f04aa2018-02-10 15:36:55 +0100313 {
Bram Moolenaar42443c72018-02-10 18:28:52 +0100314 pum_width = p_pw;
Bram Moolenaara8f04aa2018-02-10 15:36:55 +0100315#ifdef FEAT_RIGHTLEFT
316 if (curwin->w_p_rl)
317 {
318 if (pum_width > pum_col)
319 pum_width = pum_col;
320 }
321 else
322#endif
323 {
324 if (pum_width >= Columns - pum_col)
325 pum_width = Columns - pum_col - 1;
326 }
327 }
328 else if (pum_width > max_width + pum_kind_width
329 + pum_extra_width + 1
Bram Moolenaar42443c72018-02-10 18:28:52 +0100330 && pum_width > p_pw)
Bram Moolenaara8f04aa2018-02-10 15:36:55 +0100331 {
332 pum_width = max_width + pum_kind_width
333 + pum_extra_width + 1;
Bram Moolenaar42443c72018-02-10 18:28:52 +0100334 if (pum_width < p_pw)
335 pum_width = p_pw;
Bram Moolenaara8f04aa2018-02-10 15:36:55 +0100336 }
337 }
338
Bram Moolenaara5e66212017-09-29 22:42:33 +0200339 }
340 else if (Columns < def_width)
341 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100342 // not enough room, will use what we have
Bram Moolenaara5e66212017-09-29 22:42:33 +0200343#ifdef FEAT_RIGHTLEFT
344 if (curwin->w_p_rl)
345 pum_col = Columns - 1;
346 else
347#endif
348 pum_col = 0;
349 pum_width = Columns - 1;
350 }
351 else
352 {
Bram Moolenaar42443c72018-02-10 18:28:52 +0100353 if (max_width > p_pw)
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100354 max_width = p_pw; // truncate
Bram Moolenaara5e66212017-09-29 22:42:33 +0200355#ifdef FEAT_RIGHTLEFT
356 if (curwin->w_p_rl)
357 pum_col = max_width - 1;
358 else
359#endif
360 pum_col = Columns - max_width;
361 pum_width = max_width - pum_scrollbar;
362 }
363
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100364 // Set selected item and redraw. If the window size changed need to
365 // redo the positioning. Limit this to two times, when there is not
366 // much room the window size will keep changing.
Bram Moolenaara5e66212017-09-29 22:42:33 +0200367 } while (pum_set_selected(selected, redo_count) && ++redo_count <= 2);
Bram Moolenaar714cbe52020-11-16 19:12:00 +0100368
369 pum_redraw();
Bram Moolenaar4b779472005-10-04 09:12:31 +0000370}
371
372/*
Bram Moolenaarae654382019-01-17 21:09:05 +0100373 * Set a flag that when pum_redraw() is called it first calls update_screen().
374 * This will avoid clearing and redrawing the popup menu, prevent flicker.
375 */
376 void
377pum_call_update_screen()
378{
379 call_update_screen = TRUE;
380
381 // Update the cursor position to be able to compute the popup menu
382 // position. The cursor line length may have changed because of the
383 // inserted completion.
Bram Moolenaarc07ff5c2019-01-30 21:41:14 +0100384 curwin->w_valid &= ~(VALID_CROW|VALID_CHEIGHT);
Bram Moolenaarae654382019-01-17 21:09:05 +0100385 validate_cursor();
386}
387
388/*
389 * Return TRUE if we are going to redraw the popup menu and the screen position
390 * "row"/"col" is under the popup menu.
391 */
392 int
Bakudankun65555002021-11-17 20:40:16 +0000393pum_under_menu(int row, int col, int only_redrawing)
Bram Moolenaarae654382019-01-17 21:09:05 +0100394{
Bakudankun65555002021-11-17 20:40:16 +0000395 return (!only_redrawing || pum_will_redraw)
Bram Moolenaarae654382019-01-17 21:09:05 +0100396 && row >= pum_row
397 && row < pum_row + pum_height
398 && col >= pum_col - 1
Bram Moolenaare0c03c82021-04-23 21:01:34 +0200399 && col < pum_col + pum_width + pum_scrollbar;
Bram Moolenaarae654382019-01-17 21:09:05 +0100400}
401
402/*
Bram Moolenaar4b779472005-10-04 09:12:31 +0000403 * Redraw the popup menu, using "pum_first" and "pum_selected".
404 */
405 void
Bram Moolenaar05540972016-01-30 20:31:25 +0100406pum_redraw(void)
Bram Moolenaar4b779472005-10-04 09:12:31 +0000407{
408 int row = pum_row;
409 int col;
410 int attr_norm = highlight_attr[HLF_PNI];
411 int attr_select = highlight_attr[HLF_PSI];
412 int attr_scroll = highlight_attr[HLF_PSB];
413 int attr_thumb = highlight_attr[HLF_PST];
414 int attr;
415 int i;
416 int idx;
417 char_u *s;
Bram Moolenaar7d47b6e2006-03-15 22:59:18 +0000418 char_u *p = NULL;
Bram Moolenaar8b6144b2006-02-08 09:20:24 +0000419 int totwidth, width, w;
Bram Moolenaar4b779472005-10-04 09:12:31 +0000420 int thumb_pos = 0;
Bram Moolenaarbdace832019-03-02 10:13:42 +0100421 int thumb_height = 1;
Bram Moolenaar8b6144b2006-02-08 09:20:24 +0000422 int round;
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000423 int n;
Bram Moolenaar4b779472005-10-04 09:12:31 +0000424
Bram Moolenaarae654382019-01-17 21:09:05 +0100425 if (call_update_screen)
426 {
427 call_update_screen = FALSE;
Bram Moolenaar04357fb2019-12-10 21:50:56 +0100428 // Do not redraw in pum_may_redraw() and don't draw in the area where
429 // the popup menu will be.
430 pum_will_redraw = TRUE;
Bram Moolenaarae654382019-01-17 21:09:05 +0100431 update_screen(0);
Bram Moolenaar04357fb2019-12-10 21:50:56 +0100432 pum_will_redraw = FALSE;
Bram Moolenaarae654382019-01-17 21:09:05 +0100433 }
434
435 // never display more than we have
Bram Moolenaar4c1e6262013-11-06 04:04:33 +0100436 if (pum_first > pum_size - pum_height)
437 pum_first = pum_size - pum_height;
438
Bram Moolenaar4b779472005-10-04 09:12:31 +0000439 if (pum_scrollbar)
440 {
Bram Moolenaarbdace832019-03-02 10:13:42 +0100441 thumb_height = pum_height * pum_height / pum_size;
442 if (thumb_height == 0)
443 thumb_height = 1;
444 thumb_pos = (pum_first * (pum_height - thumb_height)
Bram Moolenaar4b779472005-10-04 09:12:31 +0000445 + (pum_size - pum_height) / 2)
446 / (pum_size - pum_height);
447 }
448
Bram Moolenaar05ad5ff2019-11-30 22:48:27 +0100449#ifdef FEAT_PROP_POPUP
Bram Moolenaar33796b32019-06-08 16:01:13 +0200450 // The popup menu is drawn over popup menus with zindex under
451 // POPUPMENU_ZINDEX.
452 screen_zindex = POPUPMENU_ZINDEX;
453#endif
454
Bram Moolenaar4b779472005-10-04 09:12:31 +0000455 for (i = 0; i < pum_height; ++i)
456 {
457 idx = i + pum_first;
458 attr = (idx == pum_selected) ? attr_select : attr_norm;
459
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100460 // prepend a space if there is room
Bram Moolenaarabc97732007-08-08 20:49:37 +0000461#ifdef FEAT_RIGHTLEFT
462 if (curwin->w_p_rl)
463 {
Bram Moolenaar02631462017-09-22 15:20:32 +0200464 if (pum_col < curwin->w_wincol + curwin->w_width - 1)
Bram Moolenaarabc97732007-08-08 20:49:37 +0000465 screen_putchar(' ', row, pum_col + 1, attr);
466 }
467 else
468#endif
469 if (pum_col > 0)
470 screen_putchar(' ', row, pum_col - 1, attr);
Bram Moolenaar4b779472005-10-04 09:12:31 +0000471
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100472 // Display each entry, use two spaces for a Tab.
473 // Do this 3 times: For the main text, kind and extra info
Bram Moolenaar4b779472005-10-04 09:12:31 +0000474 col = pum_col;
Bram Moolenaar8b6144b2006-02-08 09:20:24 +0000475 totwidth = 0;
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000476 for (round = 1; round <= 3; ++round)
Bram Moolenaar4b779472005-10-04 09:12:31 +0000477 {
Bram Moolenaar8b6144b2006-02-08 09:20:24 +0000478 width = 0;
479 s = NULL;
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000480 switch (round)
Bram Moolenaar4b779472005-10-04 09:12:31 +0000481 {
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000482 case 1: p = pum_array[idx].pum_text; break;
483 case 2: p = pum_array[idx].pum_kind; break;
484 case 3: p = pum_array[idx].pum_extra; break;
Bram Moolenaar4b779472005-10-04 09:12:31 +0000485 }
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000486 if (p != NULL)
Bram Moolenaar91acfff2017-03-12 19:22:36 +0100487 for ( ; ; MB_PTR_ADV(p))
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000488 {
489 if (s == NULL)
490 s = p;
491 w = ptr2cells(p);
492 if (*p == NUL || *p == TAB || totwidth + w > pum_width)
493 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100494 // Display the text that fits or comes before a Tab.
495 // First convert it to printable characters.
Bram Moolenaarabc97732007-08-08 20:49:37 +0000496 char_u *st;
497 int saved = *p;
Bram Moolenaar7cc36e92007-03-27 10:42:05 +0000498
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +0100499 if (saved != NUL)
500 *p = NUL;
Bram Moolenaar7cc36e92007-03-27 10:42:05 +0000501 st = transstr(s);
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +0100502 if (saved != NUL)
503 *p = saved;
Bram Moolenaarabc97732007-08-08 20:49:37 +0000504#ifdef FEAT_RIGHTLEFT
505 if (curwin->w_p_rl)
Bram Moolenaar7cc36e92007-03-27 10:42:05 +0000506 {
Bram Moolenaarabc97732007-08-08 20:49:37 +0000507 if (st != NULL)
508 {
509 char_u *rt = reverse_text(st);
Bram Moolenaarabc97732007-08-08 20:49:37 +0000510
511 if (rt != NULL)
512 {
Bram Moolenaard836bb92010-01-19 18:06:03 +0100513 char_u *rt_start = rt;
514 int size;
515
516 size = vim_strsize(rt);
517 if (size > pum_width)
Bram Moolenaarabc97732007-08-08 20:49:37 +0000518 {
Bram Moolenaard836bb92010-01-19 18:06:03 +0100519 do
520 {
521 size -= has_mbyte
522 ? (*mb_ptr2cells)(rt) : 1;
Bram Moolenaar91acfff2017-03-12 19:22:36 +0100523 MB_PTR_ADV(rt);
Bram Moolenaard836bb92010-01-19 18:06:03 +0100524 } while (size > pum_width);
525
526 if (size < pum_width)
527 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100528 // Most left character requires
529 // 2-cells but only 1 cell is
530 // available on screen. Put a
531 // '<' on the left of the pum
532 // item
Bram Moolenaard836bb92010-01-19 18:06:03 +0100533 *(--rt) = '<';
534 size++;
535 }
Bram Moolenaarabc97732007-08-08 20:49:37 +0000536 }
Bram Moolenaard836bb92010-01-19 18:06:03 +0100537 screen_puts_len(rt, (int)STRLEN(rt),
538 row, col - size + 1, attr);
539 vim_free(rt_start);
Bram Moolenaarabc97732007-08-08 20:49:37 +0000540 }
541 vim_free(st);
542 }
543 col -= width;
Bram Moolenaar7cc36e92007-03-27 10:42:05 +0000544 }
Bram Moolenaarabc97732007-08-08 20:49:37 +0000545 else
546#endif
547 {
548 if (st != NULL)
549 {
Bram Moolenaar714cbe52020-11-16 19:12:00 +0100550 int size = (int)STRLEN(st);
551 int cells = (*mb_string2cells)(st, size);
552
553 // only draw the text that fits
554 while (size > 0
555 && col + cells > pum_width + pum_col)
556 {
557 --size;
558 if (has_mbyte)
559 {
560 size -= (*mb_head_off)(st, st + size);
561 cells -= (*mb_ptr2cells)(st + size);
562 }
563 else
564 --cells;
565 }
566 screen_puts_len(st, size, row, col, attr);
Bram Moolenaarabc97732007-08-08 20:49:37 +0000567 vim_free(st);
568 }
569 col += width;
570 }
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000571
572 if (*p != TAB)
573 break;
574
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100575 // Display two spaces for a Tab.
Bram Moolenaarabc97732007-08-08 20:49:37 +0000576#ifdef FEAT_RIGHTLEFT
577 if (curwin->w_p_rl)
578 {
579 screen_puts_len((char_u *)" ", 2, row, col - 1,
580 attr);
581 col -= 2;
582 }
583 else
584#endif
585 {
586 screen_puts_len((char_u *)" ", 2, row, col, attr);
587 col += 2;
588 }
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000589 totwidth += 2;
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100590 s = NULL; // start text at next char
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000591 width = 0;
592 }
593 else
594 width += w;
595 }
596
597 if (round > 1)
598 n = pum_kind_width + 1;
599 else
600 n = 1;
601
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100602 // Stop when there is nothing more to display.
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000603 if (round == 3
604 || (round == 2 && pum_array[idx].pum_extra == NULL)
605 || (round == 1 && pum_array[idx].pum_kind == NULL
606 && pum_array[idx].pum_extra == NULL)
607 || pum_base_width + n >= pum_width)
Bram Moolenaar8b6144b2006-02-08 09:20:24 +0000608 break;
Bram Moolenaarabc97732007-08-08 20:49:37 +0000609#ifdef FEAT_RIGHTLEFT
610 if (curwin->w_p_rl)
611 {
612 screen_fill(row, row + 1, pum_col - pum_base_width - n + 1,
613 col + 1, ' ', ' ', attr);
614 col = pum_col - pum_base_width - n + 1;
615 }
616 else
617#endif
618 {
619 screen_fill(row, row + 1, col, pum_col + pum_base_width + n,
Bram Moolenaar8b6144b2006-02-08 09:20:24 +0000620 ' ', ' ', attr);
Bram Moolenaarabc97732007-08-08 20:49:37 +0000621 col = pum_col + pum_base_width + n;
622 }
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000623 totwidth = pum_base_width + n;
Bram Moolenaar4b779472005-10-04 09:12:31 +0000624 }
625
Bram Moolenaarabc97732007-08-08 20:49:37 +0000626#ifdef FEAT_RIGHTLEFT
627 if (curwin->w_p_rl)
628 screen_fill(row, row + 1, pum_col - pum_width + 1, col + 1, ' ',
629 ' ', attr);
630 else
631#endif
632 screen_fill(row, row + 1, col, pum_col + pum_width, ' ', ' ',
633 attr);
Bram Moolenaar4b779472005-10-04 09:12:31 +0000634 if (pum_scrollbar > 0)
Bram Moolenaarabc97732007-08-08 20:49:37 +0000635 {
636#ifdef FEAT_RIGHTLEFT
637 if (curwin->w_p_rl)
638 screen_putchar(' ', row, pum_col - pum_width,
Bram Moolenaarbdace832019-03-02 10:13:42 +0100639 i >= thumb_pos && i < thumb_pos + thumb_height
Bram Moolenaar4b779472005-10-04 09:12:31 +0000640 ? attr_thumb : attr_scroll);
Bram Moolenaarabc97732007-08-08 20:49:37 +0000641 else
642#endif
643 screen_putchar(' ', row, pum_col + pum_width,
Bram Moolenaarbdace832019-03-02 10:13:42 +0100644 i >= thumb_pos && i < thumb_pos + thumb_height
Bram Moolenaarabc97732007-08-08 20:49:37 +0000645 ? attr_thumb : attr_scroll);
646 }
Bram Moolenaar4b779472005-10-04 09:12:31 +0000647
648 ++row;
649 }
Bram Moolenaar33796b32019-06-08 16:01:13 +0200650
Bram Moolenaar05ad5ff2019-11-30 22:48:27 +0100651#ifdef FEAT_PROP_POPUP
Bram Moolenaar33796b32019-06-08 16:01:13 +0200652 screen_zindex = 0;
653#endif
Bram Moolenaar4b779472005-10-04 09:12:31 +0000654}
655
Bram Moolenaar5a4c3082019-12-01 15:23:11 +0100656#if (defined(FEAT_PROP_POPUP) && defined(FEAT_QUICKFIX)) || defined(PROTO)
Bram Moolenaardca7abe2019-10-20 18:17:57 +0200657/*
658 * Position the info popup relative to the popup menu item.
659 */
660 void
661pum_position_info_popup(win_T *wp)
Bram Moolenaarc1f87c92019-08-21 19:33:16 +0200662{
Bram Moolenaar202c3f72019-11-21 12:12:35 +0100663 int col = pum_col + pum_width + pum_scrollbar + 1;
Bram Moolenaarc1f87c92019-08-21 19:33:16 +0200664 int row = pum_row;
665 int botpos = POPPOS_BOTLEFT;
Bram Moolenaarde2396f2020-07-18 21:40:41 +0200666 int used_maxwidth_opt = FALSE;
Bram Moolenaarc1f87c92019-08-21 19:33:16 +0200667
Bram Moolenaardca7abe2019-10-20 18:17:57 +0200668 wp->w_popup_pos = POPPOS_TOPLEFT;
Bram Moolenaarc1f87c92019-08-21 19:33:16 +0200669 if (Columns - col < 20 && Columns - col < pum_col)
670 {
671 col = pum_col - 1;
Bram Moolenaardca7abe2019-10-20 18:17:57 +0200672 wp->w_popup_pos = POPPOS_TOPRIGHT;
Bram Moolenaarc1f87c92019-08-21 19:33:16 +0200673 botpos = POPPOS_BOTRIGHT;
Bram Moolenaardca7abe2019-10-20 18:17:57 +0200674 wp->w_maxwidth = pum_col - 1;
Bram Moolenaarc1f87c92019-08-21 19:33:16 +0200675 }
676 else
Bram Moolenaardca7abe2019-10-20 18:17:57 +0200677 wp->w_maxwidth = Columns - col + 1;
678 wp->w_maxwidth -= popup_extra_width(wp);
Bram Moolenaarde2396f2020-07-18 21:40:41 +0200679 if (wp->w_maxwidth_opt > 0 && wp->w_maxwidth > wp->w_maxwidth_opt)
680 {
681 // option value overrules computed value
682 wp->w_maxwidth = wp->w_maxwidth_opt;
683 used_maxwidth_opt = TRUE;
684 }
Bram Moolenaarc1f87c92019-08-21 19:33:16 +0200685
Bram Moolenaardca7abe2019-10-20 18:17:57 +0200686 row -= popup_top_extra(wp);
687 if (wp->w_popup_flags & POPF_INFO_MENU)
Bram Moolenaarc1f87c92019-08-21 19:33:16 +0200688 {
689 if (pum_row < pum_win_row)
690 {
691 // menu above cursor line, align with bottom
692 row += pum_height;
Bram Moolenaardca7abe2019-10-20 18:17:57 +0200693 wp->w_popup_pos = botpos;
Bram Moolenaarc1f87c92019-08-21 19:33:16 +0200694 }
695 else
696 // menu below cursor line, align with top
697 row += 1;
698 }
699 else
700 // align with the selected item
701 row += pum_selected - pum_first + 1;
702
Bram Moolenaarbef93ac2019-12-06 20:17:35 +0100703 wp->w_popup_flags &= ~POPF_HIDDEN;
Bram Moolenaarde2396f2020-07-18 21:40:41 +0200704 if (wp->w_maxwidth < 10 && !used_maxwidth_opt)
Bram Moolenaarbef93ac2019-12-06 20:17:35 +0100705 // The popup is not going to fit or will overlap with the cursor
706 // position, hide the popup.
707 wp->w_popup_flags |= POPF_HIDDEN;
708 else
709 popup_set_wantpos_rowcol(wp, row, col);
Bram Moolenaarc1f87c92019-08-21 19:33:16 +0200710}
711#endif
712
Bram Moolenaar4b779472005-10-04 09:12:31 +0000713/*
714 * Set the index of the currently selected item. The menu will scroll when
Bram Moolenaar1f35bf92006-03-07 22:38:47 +0000715 * necessary. When "n" is out of range don't scroll.
Bram Moolenaarfc1421e2006-04-20 22:17:20 +0000716 * This may be repeated when the preview window is used:
717 * "repeat" == 0: open preview window normally
718 * "repeat" == 1: open preview window but don't set the size
719 * "repeat" == 2: don't open preview window
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000720 * Returns TRUE when the window was resized and the location of the popup menu
721 * must be recomputed.
Bram Moolenaar4b779472005-10-04 09:12:31 +0000722 */
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000723 static int
Bram Moolenaar9cb698d2019-08-21 15:30:45 +0200724pum_set_selected(int n, int repeat UNUSED)
Bram Moolenaar4b779472005-10-04 09:12:31 +0000725{
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000726 int resized = FALSE;
Bram Moolenaar7d47b6e2006-03-15 22:59:18 +0000727 int context = pum_height / 2;
Bram Moolenaareaf35242019-08-20 23:14:15 +0200728#ifdef FEAT_QUICKFIX
Bram Moolenaarf0bc15c2019-08-18 19:23:45 +0200729 int prev_selected = pum_selected;
Bram Moolenaareaf35242019-08-20 23:14:15 +0200730#endif
Bram Moolenaar5a4c3082019-12-01 15:23:11 +0100731#if defined(FEAT_PROP_POPUP) && defined(FEAT_QUICKFIX)
Bram Moolenaar576a4a62019-08-18 15:25:17 +0200732 int has_info = FALSE;
733#endif
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000734
Bram Moolenaar4b779472005-10-04 09:12:31 +0000735 pum_selected = n;
736
Bram Moolenaar1f35bf92006-03-07 22:38:47 +0000737 if (pum_selected >= 0 && pum_selected < pum_size)
Bram Moolenaar4b779472005-10-04 09:12:31 +0000738 {
Bram Moolenaare3226be2005-12-18 22:10:00 +0000739 if (pum_first > pum_selected - 4)
740 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100741 // scroll down; when we did a jump it's probably a PageUp then
742 // scroll a whole page
Bram Moolenaare3226be2005-12-18 22:10:00 +0000743 if (pum_first > pum_selected - 2)
744 {
Bram Moolenaar7df351e2006-01-23 22:30:28 +0000745 pum_first -= pum_height - 2;
Bram Moolenaare3226be2005-12-18 22:10:00 +0000746 if (pum_first < 0)
747 pum_first = 0;
Bram Moolenaar7df351e2006-01-23 22:30:28 +0000748 else if (pum_first > pum_selected)
749 pum_first = pum_selected;
Bram Moolenaare3226be2005-12-18 22:10:00 +0000750 }
751 else
752 pum_first = pum_selected;
753 }
754 else if (pum_first < pum_selected - pum_height + 5)
755 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100756 // scroll up; when we did a jump it's probably a PageDown then
757 // scroll a whole page
Bram Moolenaare3226be2005-12-18 22:10:00 +0000758 if (pum_first < pum_selected - pum_height + 1 + 2)
Bram Moolenaar7df351e2006-01-23 22:30:28 +0000759 {
760 pum_first += pum_height - 2;
761 if (pum_first < pum_selected - pum_height + 1)
762 pum_first = pum_selected - pum_height + 1;
763 }
Bram Moolenaare3226be2005-12-18 22:10:00 +0000764 else
765 pum_first = pum_selected - pum_height + 1;
766 }
Bram Moolenaar4b779472005-10-04 09:12:31 +0000767
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100768 // Give a few lines of context when possible.
Bram Moolenaar7d47b6e2006-03-15 22:59:18 +0000769 if (context > 3)
770 context = 3;
771 if (pum_height > 2)
Bram Moolenaar4b779472005-10-04 09:12:31 +0000772 {
Bram Moolenaar7d47b6e2006-03-15 22:59:18 +0000773 if (pum_first > pum_selected - context)
Bram Moolenaar4b779472005-10-04 09:12:31 +0000774 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100775 // scroll down
Bram Moolenaar7d47b6e2006-03-15 22:59:18 +0000776 pum_first = pum_selected - context;
Bram Moolenaar4b779472005-10-04 09:12:31 +0000777 if (pum_first < 0)
778 pum_first = 0;
779 }
Bram Moolenaar7d47b6e2006-03-15 22:59:18 +0000780 else if (pum_first < pum_selected + context - pum_height + 1)
Bram Moolenaar4b779472005-10-04 09:12:31 +0000781 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100782 // scroll up
Bram Moolenaar7d47b6e2006-03-15 22:59:18 +0000783 pum_first = pum_selected + context - pum_height + 1;
Bram Moolenaar4b779472005-10-04 09:12:31 +0000784 }
785 }
Bram Moolenaar576a4a62019-08-18 15:25:17 +0200786 // adjust for the number of lines displayed
787 if (pum_first > pum_size - pum_height)
788 pum_first = pum_size - pum_height;
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000789
Bram Moolenaar4033c552017-09-16 20:54:51 +0200790#if defined(FEAT_QUICKFIX)
Bram Moolenaard2cec5b2006-03-28 21:08:56 +0000791 /*
792 * Show extra info in the preview window if there is something and
Bram Moolenaar202c3f72019-11-21 12:12:35 +0100793 * 'completeopt' contains "preview" or "popup" or "popuphidden".
Bram Moolenaarfc1421e2006-04-20 22:17:20 +0000794 * Skip this when tried twice already.
795 * Skip this also when there is not much room.
Bram Moolenaard2cec5b2006-03-28 21:08:56 +0000796 * NOTE: Be very careful not to sync undo!
797 */
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000798 if (pum_array[pum_selected].pum_info != NULL
Bram Moolenaarfc1421e2006-04-20 22:17:20 +0000799 && Rows > 10
800 && repeat <= 1
801 && vim_strchr(p_cot, 'p') != NULL)
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000802 {
803 win_T *curwin_save = curwin;
Bram Moolenaar9ad89c62017-10-26 22:04:04 +0200804 tabpage_T *curtab_save = curtab;
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000805 int res = OK;
Bram Moolenaar05ad5ff2019-11-30 22:48:27 +0100806# ifdef FEAT_PROP_POPUP
Bram Moolenaardca7abe2019-10-20 18:17:57 +0200807 use_popup_T use_popup;
Bram Moolenaar576a4a62019-08-18 15:25:17 +0200808# else
Bram Moolenaara2c2ae42019-11-30 20:58:46 +0100809# define use_popup USEPOPUP_NONE
Bram Moolenaar576a4a62019-08-18 15:25:17 +0200810# endif
Bram Moolenaar05ad5ff2019-11-30 22:48:27 +0100811# ifdef FEAT_PROP_POPUP
Bram Moolenaar576a4a62019-08-18 15:25:17 +0200812 has_info = TRUE;
Bram Moolenaardca7abe2019-10-20 18:17:57 +0200813 if (strstr((char *)p_cot, "popuphidden") != NULL)
814 use_popup = USEPOPUP_HIDDEN;
815 else if (strstr((char *)p_cot, "popup") != NULL)
816 use_popup = USEPOPUP_NORMAL;
817 else
818 use_popup = USEPOPUP_NONE;
Bram Moolenaar2dfae042020-11-15 14:09:37 +0100819 if (use_popup != USEPOPUP_NONE)
820 // don't use WinEnter or WinLeave autocommands for the info
821 // popup
822 block_autocmds();
Bram Moolenaard570ab92019-09-03 23:20:05 +0200823# endif
Bram Moolenaardca7abe2019-10-20 18:17:57 +0200824 // Open a preview window and set "curwin" to it.
825 // 3 lines by default, prefer 'previewheight' if set and smaller.
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000826 g_do_tagpreview = 3;
Bram Moolenaaree236d02010-10-27 17:11:15 +0200827 if (p_pvh > 0 && p_pvh < g_do_tagpreview)
828 g_do_tagpreview = p_pvh;
Bram Moolenaarc8045152014-07-09 19:58:24 +0200829 ++RedrawingDisabled;
Bram Moolenaar576a4a62019-08-18 15:25:17 +0200830 // Prevent undo sync here, if an autocommand syncs undo weird
831 // things can happen to the undo tree.
Bram Moolenaare7d13762015-10-30 14:23:33 +0100832 ++no_u_sync;
Bram Moolenaar576a4a62019-08-18 15:25:17 +0200833 resized = prepare_tagpreview(FALSE, FALSE, use_popup);
Bram Moolenaare7d13762015-10-30 14:23:33 +0100834 --no_u_sync;
Bram Moolenaarc8045152014-07-09 19:58:24 +0200835 --RedrawingDisabled;
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000836 g_do_tagpreview = 0;
837
Bram Moolenaar576a4a62019-08-18 15:25:17 +0200838 if (curwin->w_p_pvw
Bram Moolenaar05ad5ff2019-11-30 22:48:27 +0100839# ifdef FEAT_PROP_POPUP
Bram Moolenaar576a4a62019-08-18 15:25:17 +0200840 || (curwin->w_popup_flags & POPF_INFO)
841# endif
842 )
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000843 {
Bram Moolenaar50e53762016-10-27 14:49:15 +0200844 if (!resized
845 && curbuf->b_nwindows == 1
846 && curbuf->b_fname == NULL
Bram Moolenaar26910de2019-06-15 19:37:15 +0200847 && bt_nofile(curbuf)
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000848 && curbuf->b_p_bh[0] == 'w')
849 {
Bram Moolenaar576a4a62019-08-18 15:25:17 +0200850 // Already a "wipeout" buffer, make it empty.
Bram Moolenaarb5aedf32017-03-12 18:23:53 +0100851 while (!BUFEMPTY())
Bram Moolenaarca70c072020-05-30 20:30:46 +0200852 ml_delete((linenr_T)1);
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000853 }
Bram Moolenaar779b74b2006-04-10 14:55:34 +0000854 else
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000855 {
Bram Moolenaar576a4a62019-08-18 15:25:17 +0200856 // Don't want to sync undo in the current buffer.
Bram Moolenaar779b74b2006-04-10 14:55:34 +0000857 ++no_u_sync;
Bram Moolenaar701f7af2008-11-15 13:12:07 +0000858 res = do_ecmd(0, NULL, NULL, NULL, ECMD_ONE, 0, NULL);
Bram Moolenaar779b74b2006-04-10 14:55:34 +0000859 --no_u_sync;
860 if (res == OK)
861 {
Bram Moolenaar576a4a62019-08-18 15:25:17 +0200862 // Edit a new, empty buffer. Set options for a "wipeout"
863 // buffer.
Bram Moolenaar779b74b2006-04-10 14:55:34 +0000864 set_option_value((char_u *)"swf", 0L, NULL, OPT_LOCAL);
Bram Moolenaard356fc62020-12-09 18:13:44 +0100865 set_option_value((char_u *)"bl", 0L, NULL, OPT_LOCAL);
Bram Moolenaar779b74b2006-04-10 14:55:34 +0000866 set_option_value((char_u *)"bt", 0L,
867 (char_u *)"nofile", OPT_LOCAL);
868 set_option_value((char_u *)"bh", 0L,
869 (char_u *)"wipe", OPT_LOCAL);
870 set_option_value((char_u *)"diff", 0L,
Bram Moolenaar7f514742007-06-28 19:33:43 +0000871 NULL, OPT_LOCAL);
Bram Moolenaar779b74b2006-04-10 14:55:34 +0000872 }
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000873 }
874 if (res == OK)
875 {
876 char_u *p, *e;
877 linenr_T lnum = 0;
878
879 for (p = pum_array[pum_selected].pum_info; *p != NUL; )
880 {
881 e = vim_strchr(p, '\n');
882 if (e == NULL)
883 {
884 ml_append(lnum++, p, 0, FALSE);
885 break;
886 }
887 else
888 {
889 *e = NUL;
Bram Moolenaara93fa7e2006-04-17 22:14:47 +0000890 ml_append(lnum++, p, (int)(e - p + 1), FALSE);
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000891 *e = '\n';
892 p = e + 1;
893 }
894 }
Bram Moolenaar576a4a62019-08-18 15:25:17 +0200895 // delete the empty last line
Bram Moolenaarca70c072020-05-30 20:30:46 +0200896 ml_delete(curbuf->b_ml.ml_line_count);
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000897
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100898 // Increase the height of the preview window to show the
899 // text, but no more than 'previewheight' lines.
Bram Moolenaardca7abe2019-10-20 18:17:57 +0200900 if (repeat == 0 && use_popup == USEPOPUP_NONE)
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000901 {
Bram Moolenaarfc1421e2006-04-20 22:17:20 +0000902 if (lnum > p_pvh)
903 lnum = p_pvh;
904 if (curwin->w_height < lnum)
905 {
906 win_setheight((int)lnum);
907 resized = TRUE;
908 }
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000909 }
910
911 curbuf->b_changed = 0;
912 curbuf->b_p_ma = FALSE;
Bram Moolenaarf0bc15c2019-08-18 19:23:45 +0200913 if (pum_selected != prev_selected)
914 {
Bram Moolenaar05ad5ff2019-11-30 22:48:27 +0100915# ifdef FEAT_PROP_POPUP
Bram Moolenaarf0bc15c2019-08-18 19:23:45 +0200916 curwin->w_firstline = 1;
917# endif
918 curwin->w_topline = 1;
919 }
920 else if (curwin->w_topline > curbuf->b_ml.ml_line_count)
921 curwin->w_topline = curbuf->b_ml.ml_line_count;
922 curwin->w_cursor.lnum = curwin->w_topline;
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000923 curwin->w_cursor.col = 0;
Bram Moolenaar05ad5ff2019-11-30 22:48:27 +0100924# ifdef FEAT_PROP_POPUP
Bram Moolenaardca7abe2019-10-20 18:17:57 +0200925 if (use_popup != USEPOPUP_NONE)
Bram Moolenaarc1f87c92019-08-21 19:33:16 +0200926 {
Bram Moolenaardca7abe2019-10-20 18:17:57 +0200927 pum_position_info_popup(curwin);
Bram Moolenaarc1f87c92019-08-21 19:33:16 +0200928 if (win_valid(curwin_save))
929 redraw_win_later(curwin_save, SOME_VALID);
930 }
931# endif
Bram Moolenaar9ad89c62017-10-26 22:04:04 +0200932 if ((curwin != curwin_save && win_valid(curwin_save))
933 || (curtab != curtab_save
934 && valid_tabpage(curtab_save)))
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000935 {
Bram Moolenaarb80d2fb2021-04-27 20:06:57 +0200936 int save_redr_status;
937
Bram Moolenaar9ad89c62017-10-26 22:04:04 +0200938 if (curtab != curtab_save && valid_tabpage(curtab_save))
939 goto_tabpage_tp(curtab_save, FALSE, FALSE);
940
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100941 // When the first completion is done and the preview
942 // window is not resized, skip the preview window's
943 // status line redrawing.
Bram Moolenaar2bace3e2014-07-23 21:10:43 +0200944 if (ins_compl_active() && !resized)
945 curwin->w_redr_status = FALSE;
946
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100947 // Return cursor to where we were
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000948 validate_cursor();
Bram Moolenaar600dddc2006-03-12 22:05:10 +0000949 redraw_later(SOME_VALID);
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000950
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100951 // When the preview window was resized we need to
952 // update the view on the buffer. Only go back to
953 // the window when needed, otherwise it will always be
Bram Moolenaar5e5a98d2019-12-15 14:55:33 +0100954 // redrawn.
Bram Moolenaar5dd143e2019-08-15 21:34:34 +0200955 if (resized && win_valid(curwin_save))
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000956 {
Bram Moolenaare7d13762015-10-30 14:23:33 +0100957 ++no_u_sync;
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000958 win_enter(curwin_save, TRUE);
Bram Moolenaare7d13762015-10-30 14:23:33 +0100959 --no_u_sync;
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000960 update_topline();
961 }
962
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100963 // Update the screen before drawing the popup menu.
964 // Enable updating the status lines.
Bram Moolenaar04357fb2019-12-10 21:50:56 +0100965 pum_pretend_not_visible = TRUE;
Bram Moolenaarb80d2fb2021-04-27 20:06:57 +0200966
Bram Moolenaar04357fb2019-12-10 21:50:56 +0100967 // But don't draw text at the new popup menu position,
Bram Moolenaar5e5a98d2019-12-15 14:55:33 +0100968 // it causes flicker. When resizing we need to draw
969 // anyway, the position may change later.
Bram Moolenaarb80d2fb2021-04-27 20:06:57 +0200970 // Also do not redraw the status line of the original
971 // current window here, to avoid it gets drawn with
972 // StatusLineNC for a moment and cause flicker.
Bram Moolenaar5e5a98d2019-12-15 14:55:33 +0100973 pum_will_redraw = !resized;
Bram Moolenaarb80d2fb2021-04-27 20:06:57 +0200974 save_redr_status = curwin_save->w_redr_status;
975 curwin_save->w_redr_status = FALSE;
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000976 update_screen(0);
Bram Moolenaar04357fb2019-12-10 21:50:56 +0100977 pum_pretend_not_visible = FALSE;
978 pum_will_redraw = FALSE;
Bram Moolenaarb80d2fb2021-04-27 20:06:57 +0200979 curwin_save->w_redr_status = save_redr_status;
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000980
Bram Moolenaard2cec5b2006-03-28 21:08:56 +0000981 if (!resized && win_valid(curwin_save))
Bram Moolenaare7d13762015-10-30 14:23:33 +0100982 {
Bram Moolenaar05ad5ff2019-11-30 22:48:27 +0100983# ifdef FEAT_PROP_POPUP
Bram Moolenaardca7abe2019-10-20 18:17:57 +0200984 win_T *wp = curwin;
985# endif
Bram Moolenaare7d13762015-10-30 14:23:33 +0100986 ++no_u_sync;
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000987 win_enter(curwin_save, TRUE);
Bram Moolenaare7d13762015-10-30 14:23:33 +0100988 --no_u_sync;
Bram Moolenaar05ad5ff2019-11-30 22:48:27 +0100989# ifdef FEAT_PROP_POPUP
Bram Moolenaardca7abe2019-10-20 18:17:57 +0200990 if (use_popup == USEPOPUP_HIDDEN && win_valid(wp))
991 popup_hide(wp);
992# endif
Bram Moolenaare7d13762015-10-30 14:23:33 +0100993 }
Bram Moolenaar7d47b6e2006-03-15 22:59:18 +0000994
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100995 // May need to update the screen again when there are
996 // autocommands involved.
Bram Moolenaar04357fb2019-12-10 21:50:56 +0100997 pum_pretend_not_visible = TRUE;
Bram Moolenaar5e5a98d2019-12-15 14:55:33 +0100998 pum_will_redraw = !resized;
Bram Moolenaar7d47b6e2006-03-15 22:59:18 +0000999 update_screen(0);
Bram Moolenaar04357fb2019-12-10 21:50:56 +01001000 pum_pretend_not_visible = FALSE;
1001 pum_will_redraw = FALSE;
Bram Moolenaarae654382019-01-17 21:09:05 +01001002 call_update_screen = FALSE;
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +00001003 }
1004 }
1005 }
Bram Moolenaar05ad5ff2019-11-30 22:48:27 +01001006# if defined(FEAT_PROP_POPUP) && defined(FEAT_QUICKFIX)
Bram Moolenaar5dd143e2019-08-15 21:34:34 +02001007 if (WIN_IS_POPUP(curwin))
1008 // can't keep focus in a popup window
1009 win_enter(firstwin, TRUE);
1010# endif
Bram Moolenaar2dfae042020-11-15 14:09:37 +01001011# ifdef FEAT_PROP_POPUP
1012 if (use_popup != USEPOPUP_NONE)
1013 unblock_autocmds();
1014# endif
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +00001015 }
1016#endif
Bram Moolenaar4b779472005-10-04 09:12:31 +00001017 }
Bram Moolenaar05ad5ff2019-11-30 22:48:27 +01001018#if defined(FEAT_PROP_POPUP) && defined(FEAT_QUICKFIX)
Bram Moolenaar576a4a62019-08-18 15:25:17 +02001019 if (!has_info)
Bram Moolenaarc7c5f102019-08-21 18:31:03 +02001020 // hide any popup info window
1021 popup_hide_info();
Bram Moolenaare2c453d2019-08-21 14:37:09 +02001022#endif
Bram Moolenaar4b779472005-10-04 09:12:31 +00001023
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +00001024 return resized;
Bram Moolenaar4b779472005-10-04 09:12:31 +00001025}
1026
1027/*
1028 * Undisplay the popup menu (later).
1029 */
1030 void
Bram Moolenaar05540972016-01-30 20:31:25 +01001031pum_undisplay(void)
Bram Moolenaar4b779472005-10-04 09:12:31 +00001032{
1033 pum_array = NULL;
Bram Moolenaar0b565e52018-05-14 23:08:32 +02001034 redraw_all_later(NOT_VALID);
Bram Moolenaar2d694602006-08-22 19:48:48 +00001035 redraw_tabline = TRUE;
Bram Moolenaar1f4d4de2006-03-14 23:00:46 +00001036 status_redraw_all();
Bram Moolenaar05ad5ff2019-11-30 22:48:27 +01001037#if defined(FEAT_PROP_POPUP) && defined(FEAT_QUICKFIX)
Bram Moolenaarc7c5f102019-08-21 18:31:03 +02001038 // hide any popup info window
1039 popup_hide_info();
Bram Moolenaar576a4a62019-08-18 15:25:17 +02001040#endif
Bram Moolenaar4b779472005-10-04 09:12:31 +00001041}
1042
1043/*
1044 * Clear the popup menu. Currently only resets the offset to the first
1045 * displayed item.
1046 */
1047 void
Bram Moolenaar05540972016-01-30 20:31:25 +01001048pum_clear(void)
Bram Moolenaar4b779472005-10-04 09:12:31 +00001049{
1050 pum_first = 0;
1051}
1052
1053/*
Bram Moolenaar04357fb2019-12-10 21:50:56 +01001054 * Return TRUE if the popup menu is displayed. Used to avoid some redrawing
1055 * that could overwrite it. Overruled when "pum_pretend_not_visible" is set,
1056 * used to redraw the status lines.
Bram Moolenaar4b779472005-10-04 09:12:31 +00001057 */
1058 int
Bram Moolenaar05540972016-01-30 20:31:25 +01001059pum_visible(void)
Bram Moolenaar4b779472005-10-04 09:12:31 +00001060{
Bram Moolenaar04357fb2019-12-10 21:50:56 +01001061 return !pum_pretend_not_visible && pum_array != NULL;
Bram Moolenaar4b779472005-10-04 09:12:31 +00001062}
1063
Bram Moolenaare3226be2005-12-18 22:10:00 +00001064/*
Bram Moolenaare0c03c82021-04-23 21:01:34 +02001065 * Return TRUE if the popup can be redrawn in the same position.
1066 */
1067 static int
1068pum_in_same_position(void)
1069{
1070 return pum_window != curwin
1071 || (pum_win_row == curwin->w_wrow + W_WINROW(curwin)
1072 && pum_win_height == curwin->w_height
1073 && pum_win_col == curwin->w_wincol
1074 && pum_win_width == curwin->w_width);
1075}
1076
1077/*
1078 * Return TRUE when pum_may_redraw() will call pum_redraw().
1079 * This means that the pum area should not be overwritten to avoid flicker.
1080 */
1081 int
1082pum_redraw_in_same_position(void)
1083{
1084 if (!pum_visible() || pum_will_redraw)
1085 return FALSE; // nothing to do
1086
1087 return pum_in_same_position();
1088}
1089
1090/*
Bram Moolenaar491ac282018-06-17 14:47:55 +02001091 * Reposition the popup menu to adjust for window layout changes.
1092 */
1093 void
1094pum_may_redraw(void)
1095{
1096 pumitem_T *array = pum_array;
1097 int len = pum_size;
1098 int selected = pum_selected;
1099
Bram Moolenaar04357fb2019-12-10 21:50:56 +01001100 if (!pum_visible() || pum_will_redraw)
Bram Moolenaar491ac282018-06-17 14:47:55 +02001101 return; // nothing to do
1102
Bram Moolenaare0c03c82021-04-23 21:01:34 +02001103 if (pum_in_same_position())
Bram Moolenaar491ac282018-06-17 14:47:55 +02001104 {
1105 // window position didn't change, redraw in the same position
1106 pum_redraw();
1107 }
1108 else
1109 {
1110 int wcol = curwin->w_wcol;
1111
1112 // Window layout changed, recompute the position.
1113 // Use the remembered w_wcol value, the cursor may have moved when a
1114 // completion was inserted, but we want the menu in the same position.
1115 pum_undisplay();
1116 curwin->w_wcol = pum_win_wcol;
1117 curwin->w_valid |= VALID_WCOL;
1118 pum_display(array, len, selected);
1119 curwin->w_wcol = wcol;
1120 }
1121}
1122
1123/*
Bram Moolenaare3226be2005-12-18 22:10:00 +00001124 * Return the height of the popup menu, the number of entries visible.
1125 * Only valid when pum_visible() returns TRUE!
1126 */
1127 int
Bram Moolenaar05540972016-01-30 20:31:25 +01001128pum_get_height(void)
Bram Moolenaare3226be2005-12-18 22:10:00 +00001129{
1130 return pum_height;
1131}
1132
Bram Moolenaar9cb698d2019-08-21 15:30:45 +02001133#if defined(FEAT_EVAL) || defined(PROTO)
Bram Moolenaard7f246c2019-04-08 18:15:41 +02001134/*
1135 * Add size information about the pum to "dict".
1136 */
1137 void
1138pum_set_event_info(dict_T *dict)
1139{
1140 if (!pum_visible())
1141 return;
Bram Moolenaarc0300632020-03-16 20:07:16 +01001142 (void)dict_add_number(dict, "height", pum_height);
1143 (void)dict_add_number(dict, "width", pum_width);
1144 (void)dict_add_number(dict, "row", pum_row);
1145 (void)dict_add_number(dict, "col", pum_col);
1146 (void)dict_add_number(dict, "size", pum_size);
1147 (void)dict_add_bool(dict, "scrollbar",
1148 pum_scrollbar ? VVAL_TRUE : VVAL_FALSE);
Bram Moolenaard7f246c2019-04-08 18:15:41 +02001149}
Bram Moolenaar9cb698d2019-08-21 15:30:45 +02001150#endif
Bram Moolenaard7f246c2019-04-08 18:15:41 +02001151
Bram Moolenaare2c453d2019-08-21 14:37:09 +02001152#if defined(FEAT_BEVAL_TERM) || defined(FEAT_TERM_POPUP_MENU) || defined(PROTO)
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001153 static void
1154pum_position_at_mouse(int min_width)
1155{
1156 if (Rows - mouse_row > pum_size)
1157 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001158 // Enough space below the mouse row.
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001159 pum_row = mouse_row + 1;
1160 if (pum_height > Rows - pum_row)
1161 pum_height = Rows - pum_row;
1162 }
1163 else
1164 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001165 // Show above the mouse row, reduce height if it does not fit.
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001166 pum_row = mouse_row - pum_size;
1167 if (pum_row < 0)
1168 {
1169 pum_height += pum_row;
1170 pum_row = 0;
1171 }
1172 }
1173 if (Columns - mouse_col >= pum_base_width
1174 || Columns - mouse_col > min_width)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001175 // Enough space to show at mouse column.
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001176 pum_col = mouse_col;
1177 else
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001178 // Not enough space, right align with window.
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001179 pum_col = Columns - (pum_base_width > min_width
1180 ? min_width : pum_base_width);
1181
1182 pum_width = Columns - pum_col;
1183 if (pum_width > pum_base_width + 1)
1184 pum_width = pum_base_width + 1;
Bram Moolenaar0e6e1792018-06-17 17:10:59 +02001185
1186 // Do not redraw at cursor position.
1187 pum_window = NULL;
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001188}
1189
Bram Moolenaare2c453d2019-08-21 14:37:09 +02001190#endif
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001191
Bram Moolenaare2c453d2019-08-21 14:37:09 +02001192#if defined(FEAT_BEVAL_TERM) || defined(PROTO)
Bram Moolenaar51b0f372017-11-18 18:52:04 +01001193static pumitem_T *balloon_array = NULL;
1194static int balloon_arraysize;
Bram Moolenaar51b0f372017-11-18 18:52:04 +01001195
Bram Moolenaare2c453d2019-08-21 14:37:09 +02001196# define BALLOON_MIN_WIDTH 50
1197# define BALLOON_MIN_HEIGHT 10
Bram Moolenaar51b0f372017-11-18 18:52:04 +01001198
Bram Moolenaar246fe032017-11-19 19:56:27 +01001199typedef struct {
1200 char_u *start;
1201 int bytelen;
1202 int cells;
1203 int indent;
1204} balpart_T;
1205
1206/*
1207 * Split a string into parts to display in the balloon.
1208 * Aimed at output from gdb. Attempts to split at white space, preserve quoted
1209 * strings and make a struct look good.
1210 * Resulting array is stored in "array" and returns the size of the array.
1211 */
1212 int
1213split_message(char_u *mesg, pumitem_T **array)
1214{
1215 garray_T ga;
1216 char_u *p;
1217 balpart_T *item;
1218 int quoted = FALSE;
1219 int height;
1220 int line;
1221 int item_idx;
1222 int indent = 0;
1223 int max_cells = 0;
Bram Moolenaard1c1c822019-11-09 16:59:14 +01001224 int max_height = Rows / 2 - 1;
Bram Moolenaar246fe032017-11-19 19:56:27 +01001225 int long_item_count = 0;
1226 int split_long_items = FALSE;
1227
1228 ga_init2(&ga, sizeof(balpart_T), 20);
1229 p = mesg;
1230
1231 while (*p != NUL)
1232 {
1233 if (ga_grow(&ga, 1) == FAIL)
1234 goto failed;
1235 item = ((balpart_T *)ga.ga_data) + ga.ga_len;
1236 item->start = p;
1237 item->indent = indent;
1238 item->cells = indent * 2;
1239 ++ga.ga_len;
1240 while (*p != NUL)
1241 {
1242 if (*p == '"')
1243 quoted = !quoted;
Bram Moolenaard1c1c822019-11-09 16:59:14 +01001244 else if (*p == '\n')
1245 break;
Bram Moolenaar246fe032017-11-19 19:56:27 +01001246 else if (*p == '\\' && p[1] != NUL)
1247 ++p;
1248 else if (!quoted)
1249 {
1250 if ((*p == ',' && p[1] == ' ') || *p == '{' || *p == '}')
1251 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001252 // Looks like a good point to break.
Bram Moolenaar246fe032017-11-19 19:56:27 +01001253 if (*p == '{')
1254 ++indent;
1255 else if (*p == '}' && indent > 0)
1256 --indent;
1257 ++item->cells;
1258 p = skipwhite(p + 1);
1259 break;
1260 }
1261 }
1262 item->cells += ptr2cells(p);
Bram Moolenaar1614a142019-10-06 22:00:13 +02001263 p += mb_ptr2len(p);
Bram Moolenaar246fe032017-11-19 19:56:27 +01001264 }
1265 item->bytelen = p - item->start;
Bram Moolenaard1c1c822019-11-09 16:59:14 +01001266 if (*p == '\n')
1267 ++p;
Bram Moolenaar246fe032017-11-19 19:56:27 +01001268 if (item->cells > max_cells)
1269 max_cells = item->cells;
Bram Moolenaara3571eb2017-11-26 16:53:16 +01001270 long_item_count += (item->cells - 1) / BALLOON_MIN_WIDTH;
Bram Moolenaar246fe032017-11-19 19:56:27 +01001271 }
1272
1273 height = 2 + ga.ga_len;
1274
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001275 // If there are long items and the height is below the limit: split lines
Bram Moolenaar246fe032017-11-19 19:56:27 +01001276 if (long_item_count > 0 && height + long_item_count <= max_height)
1277 {
1278 split_long_items = TRUE;
1279 height += long_item_count;
1280 }
1281
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001282 // Limit to half the window height, it has to fit above or below the mouse
1283 // position.
Bram Moolenaar246fe032017-11-19 19:56:27 +01001284 if (height > max_height)
1285 height = max_height;
Bram Moolenaarc799fe22019-05-28 23:08:19 +02001286 *array = ALLOC_CLEAR_MULT(pumitem_T, height);
Bram Moolenaar246fe032017-11-19 19:56:27 +01001287 if (*array == NULL)
1288 goto failed;
1289
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001290 // Add an empty line above and below, looks better.
Bram Moolenaar246fe032017-11-19 19:56:27 +01001291 (*array)->pum_text = vim_strsave((char_u *)"");
1292 (*array + height - 1)->pum_text = vim_strsave((char_u *)"");
1293
1294 for (line = 1, item_idx = 0; line < height - 1; ++item_idx)
1295 {
1296 int skip;
1297 int thislen;
1298 int copylen;
1299 int ind;
1300 int cells;
1301
1302 item = ((balpart_T *)ga.ga_data) + item_idx;
Bram Moolenaar9ae862e2019-11-21 13:27:06 +01001303 if (item->bytelen == 0)
1304 (*array)[line++].pum_text = vim_strsave((char_u *)"");
1305 else
1306 for (skip = 0; skip < item->bytelen; skip += thislen)
Bram Moolenaar246fe032017-11-19 19:56:27 +01001307 {
Bram Moolenaar9ae862e2019-11-21 13:27:06 +01001308 if (split_long_items && item->cells >= BALLOON_MIN_WIDTH)
1309 {
1310 cells = item->indent * 2;
1311 for (p = item->start + skip;
1312 p < item->start + item->bytelen;
Bram Moolenaar1614a142019-10-06 22:00:13 +02001313 p += mb_ptr2len(p))
Bram Moolenaar9ae862e2019-11-21 13:27:06 +01001314 if ((cells += ptr2cells(p)) > BALLOON_MIN_WIDTH)
1315 break;
1316 thislen = p - (item->start + skip);
1317 }
1318 else
1319 thislen = item->bytelen;
1320
1321 // put indent at the start
1322 p = alloc(thislen + item->indent * 2 + 1);
1323 if (p == NULL)
1324 {
1325 for (line = 0; line <= height - 1; ++line)
1326 vim_free((*array)[line].pum_text);
1327 vim_free(*array);
1328 goto failed;
1329 }
1330 for (ind = 0; ind < item->indent * 2; ++ind)
1331 p[ind] = ' ';
1332
1333 // exclude spaces at the end of the string
1334 for (copylen = thislen; copylen > 0; --copylen)
1335 if (item->start[skip + copylen - 1] != ' ')
Bram Moolenaar246fe032017-11-19 19:56:27 +01001336 break;
Bram Moolenaar9ae862e2019-11-21 13:27:06 +01001337
1338 vim_strncpy(p + ind, item->start + skip, copylen);
1339 (*array)[line].pum_text = p;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001340 item->indent = 0; // wrapped line has no indent
Bram Moolenaar9ae862e2019-11-21 13:27:06 +01001341 ++line;
Bram Moolenaar246fe032017-11-19 19:56:27 +01001342 }
Bram Moolenaar246fe032017-11-19 19:56:27 +01001343 }
1344 ga_clear(&ga);
1345 return height;
1346
1347failed:
1348 ga_clear(&ga);
1349 return 0;
1350}
1351
Bram Moolenaar51b0f372017-11-18 18:52:04 +01001352 void
1353ui_remove_balloon(void)
1354{
1355 if (balloon_array != NULL)
1356 {
1357 pum_undisplay();
1358 while (balloon_arraysize > 0)
1359 vim_free(balloon_array[--balloon_arraysize].pum_text);
Bram Moolenaard23a8232018-02-10 18:45:26 +01001360 VIM_CLEAR(balloon_array);
Bram Moolenaar51b0f372017-11-18 18:52:04 +01001361 }
1362}
1363
1364/*
1365 * Terminal version of a balloon, uses the popup menu code.
1366 */
1367 void
Bram Moolenaar246fe032017-11-19 19:56:27 +01001368ui_post_balloon(char_u *mesg, list_T *list)
Bram Moolenaar51b0f372017-11-18 18:52:04 +01001369{
1370 ui_remove_balloon();
1371
Bram Moolenaar246fe032017-11-19 19:56:27 +01001372 if (mesg == NULL && list == NULL)
Bram Moolenaarbe0a2592019-05-09 13:50:16 +02001373 {
1374 pum_undisplay();
Bram Moolenaar246fe032017-11-19 19:56:27 +01001375 return;
Bram Moolenaarbe0a2592019-05-09 13:50:16 +02001376 }
Bram Moolenaar246fe032017-11-19 19:56:27 +01001377 if (list != NULL)
Bram Moolenaar51b0f372017-11-18 18:52:04 +01001378 {
Bram Moolenaar246fe032017-11-19 19:56:27 +01001379 listitem_T *li;
1380 int idx;
Bram Moolenaar51b0f372017-11-18 18:52:04 +01001381
Bram Moolenaar246fe032017-11-19 19:56:27 +01001382 balloon_arraysize = list->lv_len;
Bram Moolenaarc799fe22019-05-28 23:08:19 +02001383 balloon_array = ALLOC_CLEAR_MULT(pumitem_T, list->lv_len);
Bram Moolenaar246fe032017-11-19 19:56:27 +01001384 if (balloon_array == NULL)
1385 return;
Bram Moolenaar7e9f3512020-05-13 22:44:22 +02001386 CHECK_LIST_MATERIALIZE(list);
Bram Moolenaar246fe032017-11-19 19:56:27 +01001387 for (idx = 0, li = list->lv_first; li != NULL; li = li->li_next, ++idx)
1388 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +01001389 char_u *text = tv_get_string_chk(&li->li_tv);
Bram Moolenaar246fe032017-11-19 19:56:27 +01001390
1391 balloon_array[idx].pum_text = vim_strsave(
1392 text == NULL ? (char_u *)"" : text);
1393 }
1394 }
1395 else
1396 balloon_arraysize = split_message(mesg, &balloon_array);
1397
1398 if (balloon_arraysize > 0)
1399 {
Bram Moolenaar51b0f372017-11-18 18:52:04 +01001400 pum_array = balloon_array;
1401 pum_size = balloon_arraysize;
1402 pum_compute_size();
1403 pum_scrollbar = 0;
1404 pum_height = balloon_arraysize;
1405
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001406 pum_position_at_mouse(BALLOON_MIN_WIDTH);
Bram Moolenaar51b0f372017-11-18 18:52:04 +01001407 pum_selected = -1;
1408 pum_first = 0;
1409 pum_redraw();
1410 }
1411}
1412
1413/*
1414 * Called when the mouse moved, may remove any displayed balloon.
1415 */
1416 void
1417ui_may_remove_balloon(void)
1418{
Bram Moolenaarb3d17a22019-07-07 18:28:14 +02001419 // For now: remove the balloon whenever the mouse moves to another screen
1420 // cell.
1421 ui_remove_balloon();
Bram Moolenaar51b0f372017-11-18 18:52:04 +01001422}
Bram Moolenaare2c453d2019-08-21 14:37:09 +02001423#endif
Bram Moolenaara8f04aa2018-02-10 15:36:55 +01001424
Bram Moolenaare2c453d2019-08-21 14:37:09 +02001425#if defined(FEAT_TERM_POPUP_MENU) || defined(PROTO)
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001426/*
1427 * Select the pum entry at the mouse position.
1428 */
1429 static void
1430pum_select_mouse_pos(void)
1431{
1432 int idx = mouse_row - pum_row;
1433
1434 if (idx < 0 || idx >= pum_size)
1435 pum_selected = -1;
1436 else if (*pum_array[idx].pum_text != NUL)
1437 pum_selected = idx;
1438}
1439
1440/*
1441 * Execute the currently selected popup menu item.
1442 */
1443 static void
Bram Moolenaar987723e2018-03-06 11:43:04 +01001444pum_execute_menu(vimmenu_T *menu, int mode)
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001445{
1446 vimmenu_T *mp;
1447 int idx = 0;
1448 exarg_T ea;
1449
Bram Moolenaar00d253e2020-04-06 22:13:01 +02001450 FOR_ALL_CHILD_MENUS(menu, mp)
Bram Moolenaar987723e2018-03-06 11:43:04 +01001451 if ((mp->modes & mp->enabled & mode) && idx++ == pum_selected)
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001452 {
Bram Moolenaara80faa82020-04-12 19:37:17 +02001453 CLEAR_FIELD(ea);
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001454 execute_menu(&ea, mp, -1);
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001455 break;
1456 }
1457}
1458
1459/*
1460 * Open the terminal version of the popup menu and don't return until it is
1461 * closed.
1462 */
1463 void
1464pum_show_popupmenu(vimmenu_T *menu)
1465{
1466 vimmenu_T *mp;
1467 int idx = 0;
1468 pumitem_T *array;
Bram Moolenaare2c453d2019-08-21 14:37:09 +02001469# ifdef FEAT_BEVAL_TERM
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001470 int save_bevalterm = p_bevalterm;
Bram Moolenaare2c453d2019-08-21 14:37:09 +02001471# endif
Bram Moolenaar29a2c082018-03-05 21:06:23 +01001472 int mode;
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001473
1474 pum_undisplay();
1475 pum_size = 0;
Bram Moolenaar29a2c082018-03-05 21:06:23 +01001476 mode = get_menu_mode_flag();
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001477
Bram Moolenaar00d253e2020-04-06 22:13:01 +02001478 FOR_ALL_CHILD_MENUS(menu, mp)
Bram Moolenaar29a2c082018-03-05 21:06:23 +01001479 if (menu_is_separator(mp->dname)
1480 || (mp->modes & mp->enabled & mode))
1481 ++pum_size;
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001482
Bram Moolenaarf42b45d2019-01-06 13:11:05 +01001483 // When there are only Terminal mode menus, using "popup Edit" results in
1484 // pum_size being zero.
1485 if (pum_size <= 0)
1486 {
Bram Moolenaar3a846e62022-01-01 16:21:00 +00001487 emsg(e_menu_only_exists_in_another_mode);
Bram Moolenaarf42b45d2019-01-06 13:11:05 +01001488 return;
1489 }
1490
Bram Moolenaarc799fe22019-05-28 23:08:19 +02001491 array = ALLOC_CLEAR_MULT(pumitem_T, pum_size);
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001492 if (array == NULL)
1493 return;
1494
Bram Moolenaar00d253e2020-04-06 22:13:01 +02001495 FOR_ALL_CHILD_MENUS(menu, mp)
Bram Moolenaar38455a92020-12-24 18:39:02 +01001496 {
1497 char_u *s = NULL;
1498
1499 // Make a copy of the text, the menu may be redefined in a callback.
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001500 if (menu_is_separator(mp->dname))
Bram Moolenaar38455a92020-12-24 18:39:02 +01001501 s = (char_u *)"";
Bram Moolenaar29a2c082018-03-05 21:06:23 +01001502 else if (mp->modes & mp->enabled & mode)
Bram Moolenaar38455a92020-12-24 18:39:02 +01001503 s = mp->dname;
1504 if (s != NULL)
1505 {
1506 s = vim_strsave(s);
1507 if (s != NULL)
1508 array[idx++].pum_text = s;
1509 }
1510 }
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001511
1512 pum_array = array;
1513 pum_compute_size();
1514 pum_scrollbar = 0;
1515 pum_height = pum_size;
1516 pum_position_at_mouse(20);
1517
1518 pum_selected = -1;
1519 pum_first = 0;
Bram Moolenaare2c453d2019-08-21 14:37:09 +02001520# ifdef FEAT_BEVAL_TERM
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001521 p_bevalterm = TRUE; // track mouse movement
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001522 mch_setmouse(TRUE);
Bram Moolenaare2c453d2019-08-21 14:37:09 +02001523# endif
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001524
1525 for (;;)
1526 {
1527 int c;
1528
1529 pum_redraw();
Bram Moolenaar987723e2018-03-06 11:43:04 +01001530 setcursor_mayforce(TRUE);
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001531 out_flush();
1532
1533 c = vgetc();
Bram Moolenaar5c3fb042019-05-30 15:53:29 +02001534
1535 // Bail out when typing Esc, CTRL-C or some callback closed the popup
1536 // menu.
1537 if (c == ESC || c == Ctrl_C || pum_array == NULL)
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001538 break;
1539 else if (c == CAR || c == NL)
1540 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001541 // enter: select current item, if any, and close
Bram Moolenaar987723e2018-03-06 11:43:04 +01001542 pum_execute_menu(menu, mode);
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001543 break;
1544 }
1545 else if (c == 'k' || c == K_UP || c == K_MOUSEUP)
1546 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001547 // cursor up: select previous item
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001548 while (pum_selected > 0)
1549 {
1550 --pum_selected;
1551 if (*array[pum_selected].pum_text != NUL)
1552 break;
1553 }
1554 }
1555 else if (c == 'j' || c == K_DOWN || c == K_MOUSEDOWN)
1556 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001557 // cursor down: select next item
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001558 while (pum_selected < pum_size - 1)
1559 {
1560 ++pum_selected;
1561 if (*array[pum_selected].pum_text != NUL)
1562 break;
1563 }
1564 }
1565 else if (c == K_RIGHTMOUSE)
1566 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001567 // Right mouse down: reposition the menu.
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001568 vungetc(c);
1569 break;
1570 }
1571 else if (c == K_LEFTDRAG || c == K_RIGHTDRAG || c == K_MOUSEMOVE)
1572 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001573 // mouse moved: select item in the mouse row
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001574 pum_select_mouse_pos();
1575 }
1576 else if (c == K_LEFTMOUSE || c == K_LEFTMOUSE_NM || c == K_RIGHTRELEASE)
1577 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001578 // left mouse click: select clicked item, if any, and close;
1579 // right mouse release: select clicked item, close if any
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001580 pum_select_mouse_pos();
1581 if (pum_selected >= 0)
1582 {
Bram Moolenaar987723e2018-03-06 11:43:04 +01001583 pum_execute_menu(menu, mode);
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001584 break;
1585 }
1586 if (c == K_LEFTMOUSE || c == K_LEFTMOUSE_NM)
1587 break;
1588 }
1589 }
1590
Bram Moolenaar38455a92020-12-24 18:39:02 +01001591 for (idx = 0; idx < pum_size; ++idx)
1592 vim_free(array[idx].pum_text);
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001593 vim_free(array);
1594 pum_undisplay();
Bram Moolenaare2c453d2019-08-21 14:37:09 +02001595# ifdef FEAT_BEVAL_TERM
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001596 p_bevalterm = save_bevalterm;
1597 mch_setmouse(TRUE);
Bram Moolenaare2c453d2019-08-21 14:37:09 +02001598# endif
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001599}
Bram Moolenaar29a2c082018-03-05 21:06:23 +01001600
1601 void
1602pum_make_popup(char_u *path_name, int use_mouse_pos)
1603{
1604 vimmenu_T *menu;
1605
1606 if (!use_mouse_pos)
1607 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001608 // Hack: set mouse position at the cursor so that the menu pops up
1609 // around there.
Bram Moolenaar29a2c082018-03-05 21:06:23 +01001610 mouse_row = curwin->w_winrow + curwin->w_wrow;
1611 mouse_col = curwin->w_wincol + curwin->w_wcol;
1612 }
1613
1614 menu = gui_find_menu(path_name);
1615 if (menu != NULL)
1616 pum_show_popupmenu(menu);
1617}
Bram Moolenaar4b779472005-10-04 09:12:31 +00001618#endif