blob: 752f1832d40dd358a4569edb6f1a8aef1cfdfce3 [file] [log] [blame]
Bram Moolenaar4b779472005-10-04 09:12:31 +00001/* vi:set ts=8 sts=4 sw=4:
2 *
3 * VIM - Vi IMproved by Bram Moolenaar
4 *
5 * Do ":help uganda" in Vim to read copying and usage conditions.
6 * Do ":help credits" in Vim to see a list of people who contributed.
7 * See README.txt for an overview of the Vim source code.
8 */
9
10/*
11 * popupmenu.c: Popup menu (PUM)
12 */
13#include "vim.h"
14
15#if defined(FEAT_INS_EXPAND) || defined(PROTO)
16
Bram Moolenaar8b6144b2006-02-08 09:20:24 +000017static pumitem_T *pum_array = NULL; /* items of displayed pum */
Bram Moolenaar4b779472005-10-04 09:12:31 +000018static int pum_size; /* nr of items in "pum_array" */
19static int pum_selected; /* index of selected item or -1 */
20static int pum_first = 0; /* index of top item */
21
22static int pum_height; /* nr of displayed pum items */
23static int pum_width; /* width of displayed pum items */
Bram Moolenaar8b6144b2006-02-08 09:20:24 +000024static int pum_base_width; /* width of pum items base */
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +000025static int pum_kind_width; /* width of pum items kind column */
Bram Moolenaar4b779472005-10-04 09:12:31 +000026static int pum_scrollbar; /* TRUE when scrollbar present */
27
28static int pum_row; /* top row of pum */
29static int pum_col; /* left column of pum */
30
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +000031static int pum_do_redraw = FALSE; /* do redraw anyway */
32
33static int pum_set_selected __ARGS((int n));
34
Bram Moolenaar4b779472005-10-04 09:12:31 +000035#define PUM_DEF_HEIGHT 10
36#define PUM_DEF_WIDTH 15
37
38/*
39 * Show the popup menu with items "array[size]".
40 * "array" must remain valid until pum_undisplay() is called!
41 * When possible the leftmost character is aligned with screen column "col".
42 * The menu appears above the screen line "row" or at "row" + "height" - 1.
43 */
44 void
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +000045pum_display(array, size, selected)
Bram Moolenaar8b6144b2006-02-08 09:20:24 +000046 pumitem_T *array;
Bram Moolenaar4b779472005-10-04 09:12:31 +000047 int size;
Bram Moolenaar1f35bf92006-03-07 22:38:47 +000048 int selected; /* index of initially selected item, none if
49 out of range */
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +000050{
51 int w;
52 int def_width;
53 int max_width;
54 int kind_width;
55 int extra_width;
56 int i;
57 int top_clear;
Bram Moolenaar4b779472005-10-04 09:12:31 +000058 int row;
59 int height;
60 int col;
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +000061
62redo:
63 def_width = PUM_DEF_WIDTH;
64 max_width = 0;
65 kind_width = 0;
66 extra_width = 0;
67
68 validate_cursor_col();
69 row = curwin->w_cline_row + W_WINROW(curwin);
70 height = curwin->w_cline_height;
71 col = curwin->w_wcol + W_WINCOL(curwin) - curwin->w_leftcol;
72
73 if (firstwin->w_p_pvw)
74 top_clear = firstwin->w_height;
75 else
76 top_clear = 0;
Bram Moolenaar4b779472005-10-04 09:12:31 +000077
78 /*
79 * Figure out the size and position of the pum.
80 */
81 if (size < PUM_DEF_HEIGHT)
82 pum_height = size;
83 else
84 pum_height = PUM_DEF_HEIGHT;
85
86 /* Put the pum below "row" if possible. If there are few lines decide on
87 * where there is more room. */
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +000088 if (row >= cmdline_row - top_clear - pum_height
89 && row > (cmdline_row - top_clear - height) / 2)
Bram Moolenaar4b779472005-10-04 09:12:31 +000090 {
91 /* pum above "row" */
92 if (row >= size)
93 {
94 pum_row = row - size;
95 pum_height = size;
96 }
97 else
98 {
99 pum_row = 0;
100 pum_height = row;
101 }
102 }
103 else
104 {
105 /* pum below "row" */
106 pum_row = row + height;
107 if (size > cmdline_row - pum_row)
108 pum_height = cmdline_row - pum_row;
109 else
110 pum_height = size;
111 }
112
113 /* don't display when we only have room for one line */
Bram Moolenaara6557602006-02-04 22:43:20 +0000114 if (pum_height < 1 || (pum_height == 1 && size > 1))
Bram Moolenaar4b779472005-10-04 09:12:31 +0000115 return;
116
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000117 /* If there is a preview window at the top avoid drawing over it. */
118 if (firstwin->w_p_pvw
119 && pum_row < firstwin->w_height
120 && pum_height > firstwin->w_height + 4)
121 {
122 pum_row += firstwin->w_height;
123 pum_height -= firstwin->w_height;
124 }
125
Bram Moolenaar8b6144b2006-02-08 09:20:24 +0000126 /* Compute the width of the widest match and the widest extra. */
Bram Moolenaar4b779472005-10-04 09:12:31 +0000127 for (i = 0; i < size; ++i)
128 {
Bram Moolenaar8b6144b2006-02-08 09:20:24 +0000129 w = vim_strsize(array[i].pum_text);
Bram Moolenaar4b779472005-10-04 09:12:31 +0000130 if (max_width < w)
131 max_width = w;
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000132 if (array[i].pum_kind != NULL)
133 {
134 w = vim_strsize(array[i].pum_kind) + 1;
135 if (kind_width < w)
136 kind_width = w;
137 }
Bram Moolenaar8b6144b2006-02-08 09:20:24 +0000138 if (array[i].pum_extra != NULL)
139 {
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000140 w = vim_strsize(array[i].pum_extra + 1);
Bram Moolenaar8b6144b2006-02-08 09:20:24 +0000141 if (extra_width < w)
142 extra_width = w;
143 }
Bram Moolenaar4b779472005-10-04 09:12:31 +0000144 }
Bram Moolenaar8b6144b2006-02-08 09:20:24 +0000145 pum_base_width = max_width;
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000146 pum_kind_width = kind_width;
Bram Moolenaar4b779472005-10-04 09:12:31 +0000147
148 /* if there are more items than room we need a scrollbar */
149 if (pum_height < size)
150 {
151 pum_scrollbar = 1;
152 ++max_width;
153 }
154 else
155 pum_scrollbar = 0;
156
157 if (def_width < max_width)
158 def_width = max_width;
159
160 if (col < Columns - PUM_DEF_WIDTH || col < Columns - max_width)
161 {
162 /* align pum column with "col" */
163 pum_col = col;
164 pum_width = Columns - pum_col - pum_scrollbar;
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000165 if (pum_width > max_width + kind_width + extra_width + 1
Bram Moolenaar8b6144b2006-02-08 09:20:24 +0000166 && pum_width > PUM_DEF_WIDTH)
167 {
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000168 pum_width = max_width + kind_width + extra_width + 1;
Bram Moolenaar8b6144b2006-02-08 09:20:24 +0000169 if (pum_width < PUM_DEF_WIDTH)
170 pum_width = PUM_DEF_WIDTH;
171 }
Bram Moolenaar4b779472005-10-04 09:12:31 +0000172 }
173 else if (Columns < def_width)
174 {
175 /* not enough room, will use what we have */
176 pum_col = 0;
177 pum_width = Columns - 1;
178 }
179 else
180 {
181 if (max_width > PUM_DEF_WIDTH)
182 max_width = PUM_DEF_WIDTH; /* truncate */
183 pum_col = Columns - max_width;
184 pum_width = max_width - pum_scrollbar;
185 }
186
187 pum_array = array;
188 pum_size = size;
189
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000190 /* Set selected item and redraw. If the window size changed need to redo
191 * the positioning. */
192 if (pum_set_selected(selected))
193 goto redo;
Bram Moolenaar4b779472005-10-04 09:12:31 +0000194}
195
196/*
197 * Redraw the popup menu, using "pum_first" and "pum_selected".
198 */
199 void
200pum_redraw()
201{
202 int row = pum_row;
203 int col;
204 int attr_norm = highlight_attr[HLF_PNI];
205 int attr_select = highlight_attr[HLF_PSI];
206 int attr_scroll = highlight_attr[HLF_PSB];
207 int attr_thumb = highlight_attr[HLF_PST];
208 int attr;
209 int i;
210 int idx;
211 char_u *s;
212 char_u *p;
Bram Moolenaar8b6144b2006-02-08 09:20:24 +0000213 int totwidth, width, w;
Bram Moolenaar4b779472005-10-04 09:12:31 +0000214 int thumb_pos = 0;
215 int thumb_heigth = 1;
Bram Moolenaar8b6144b2006-02-08 09:20:24 +0000216 int round;
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000217 int n;
Bram Moolenaar4b779472005-10-04 09:12:31 +0000218
219 if (pum_scrollbar)
220 {
221 thumb_heigth = pum_height * pum_height / pum_size;
222 if (thumb_heigth == 0)
223 thumb_heigth = 1;
224 thumb_pos = (pum_first * (pum_height - thumb_heigth)
225 + (pum_size - pum_height) / 2)
226 / (pum_size - pum_height);
227 }
228
229 for (i = 0; i < pum_height; ++i)
230 {
231 idx = i + pum_first;
232 attr = (idx == pum_selected) ? attr_select : attr_norm;
233
234 /* prepend a space if there is room */
235 if (pum_col > 0)
236 screen_putchar(' ', row, pum_col - 1, attr);
237
Bram Moolenaar8b6144b2006-02-08 09:20:24 +0000238 /* Display each entry, use two spaces for a Tab.
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000239 * Do this 3 times: For the main text, kind and extra info */
Bram Moolenaar4b779472005-10-04 09:12:31 +0000240 col = pum_col;
Bram Moolenaar8b6144b2006-02-08 09:20:24 +0000241 totwidth = 0;
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000242 for (round = 1; round <= 3; ++round)
Bram Moolenaar4b779472005-10-04 09:12:31 +0000243 {
Bram Moolenaar8b6144b2006-02-08 09:20:24 +0000244 width = 0;
245 s = NULL;
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000246 switch (round)
Bram Moolenaar4b779472005-10-04 09:12:31 +0000247 {
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000248 case 1: p = pum_array[idx].pum_text; break;
249 case 2: p = pum_array[idx].pum_kind; break;
250 case 3: p = pum_array[idx].pum_extra; break;
Bram Moolenaar4b779472005-10-04 09:12:31 +0000251 }
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000252 if (p != NULL)
253 for ( ; ; mb_ptr_adv(p))
254 {
255 if (s == NULL)
256 s = p;
257 w = ptr2cells(p);
258 if (*p == NUL || *p == TAB || totwidth + w > pum_width)
259 {
260 /* Display the text that fits or comes before a Tab. */
261 screen_puts_len(s, p - s, row, col, attr);
262 col += width;
263
264 if (*p != TAB)
265 break;
266
267 /* Display two spaces for a Tab. */
268 screen_puts_len((char_u *)" ", 2, row, col, attr);
269 col += 2;
270 totwidth += 2;
271 s = NULL; /* start text at next char */
272 width = 0;
273 }
274 else
275 width += w;
276 }
277
278 if (round > 1)
279 n = pum_kind_width + 1;
280 else
281 n = 1;
282
283 /* Stop when there is nothing more to display. */
284 if (round == 3
285 || (round == 2 && pum_array[idx].pum_extra == NULL)
286 || (round == 1 && pum_array[idx].pum_kind == NULL
287 && pum_array[idx].pum_extra == NULL)
288 || pum_base_width + n >= pum_width)
Bram Moolenaar8b6144b2006-02-08 09:20:24 +0000289 break;
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000290 screen_fill(row, row + 1, col, pum_col + pum_base_width + n,
Bram Moolenaar8b6144b2006-02-08 09:20:24 +0000291 ' ', ' ', attr);
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000292 col = pum_col + pum_base_width + n;
293 totwidth = pum_base_width + n;
Bram Moolenaar4b779472005-10-04 09:12:31 +0000294 }
295
296 screen_fill(row, row + 1, col, pum_col + pum_width, ' ', ' ', attr);
297 if (pum_scrollbar > 0)
298 screen_putchar(' ', row, pum_col + pum_width,
299 i >= thumb_pos && i < thumb_pos + thumb_heigth
300 ? attr_thumb : attr_scroll);
301
302 ++row;
303 }
304}
305
306#if 0 /* not used yet */
307/*
308 * Return the index of the currently selected item.
309 */
310 int
311pum_get_selected()
312{
313 return pum_selected;
314}
315#endif
316
317/*
318 * Set the index of the currently selected item. The menu will scroll when
Bram Moolenaar1f35bf92006-03-07 22:38:47 +0000319 * necessary. When "n" is out of range don't scroll.
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000320 * Returns TRUE when the window was resized and the location of the popup menu
321 * must be recomputed.
Bram Moolenaar4b779472005-10-04 09:12:31 +0000322 */
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000323 static int
Bram Moolenaar4b779472005-10-04 09:12:31 +0000324pum_set_selected(n)
325 int n;
326{
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000327 int resized = FALSE;
328
Bram Moolenaar4b779472005-10-04 09:12:31 +0000329 pum_selected = n;
330
Bram Moolenaar1f35bf92006-03-07 22:38:47 +0000331 if (pum_selected >= 0 && pum_selected < pum_size)
Bram Moolenaar4b779472005-10-04 09:12:31 +0000332 {
Bram Moolenaare3226be2005-12-18 22:10:00 +0000333 if (pum_first > pum_selected - 4)
334 {
335 /* scroll down; when we did a jump it's probably a PageUp then
Bram Moolenaar7df351e2006-01-23 22:30:28 +0000336 * scroll a whole page */
Bram Moolenaare3226be2005-12-18 22:10:00 +0000337 if (pum_first > pum_selected - 2)
338 {
Bram Moolenaar7df351e2006-01-23 22:30:28 +0000339 pum_first -= pum_height - 2;
Bram Moolenaare3226be2005-12-18 22:10:00 +0000340 if (pum_first < 0)
341 pum_first = 0;
Bram Moolenaar7df351e2006-01-23 22:30:28 +0000342 else if (pum_first > pum_selected)
343 pum_first = pum_selected;
Bram Moolenaare3226be2005-12-18 22:10:00 +0000344 }
345 else
346 pum_first = pum_selected;
347 }
348 else if (pum_first < pum_selected - pum_height + 5)
349 {
350 /* scroll up; when we did a jump it's probably a PageDown then
Bram Moolenaar7df351e2006-01-23 22:30:28 +0000351 * scroll a whole page */
Bram Moolenaare3226be2005-12-18 22:10:00 +0000352 if (pum_first < pum_selected - pum_height + 1 + 2)
Bram Moolenaar7df351e2006-01-23 22:30:28 +0000353 {
354 pum_first += pum_height - 2;
355 if (pum_first < pum_selected - pum_height + 1)
356 pum_first = pum_selected - pum_height + 1;
357 }
Bram Moolenaare3226be2005-12-18 22:10:00 +0000358 else
359 pum_first = pum_selected - pum_height + 1;
360 }
Bram Moolenaar4b779472005-10-04 09:12:31 +0000361
362 if (pum_height > 6)
363 {
364 /* Give three lines of context when possible. */
365 if (pum_first > pum_selected - 3)
366 {
367 /* scroll down */
368 pum_first = pum_selected - 3;
369 if (pum_first < 0)
370 pum_first = 0;
371 }
372 else if (pum_first < pum_selected + 3 - pum_height + 1)
373 {
374 /* scroll up */
375 pum_first = pum_selected + 3 - pum_height + 1;
376 }
377 }
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000378
379#if defined(FEAT_WINDOWS) && defined(FEAT_QUICKFIX)
380 /* Show extra info in the preview window if there is something and
381 * 'completeopt' contains "preview". */
382 if (pum_array[pum_selected].pum_info != NULL
383 && vim_strchr(p_cot, 'p') != NULL)
384 {
385 win_T *curwin_save = curwin;
386 int res = OK;
387
388 /* Open a preview window. 3 lines by default. */
389 g_do_tagpreview = 3;
390 resized = prepare_tagpreview();
391 g_do_tagpreview = 0;
392
393 if (curwin->w_p_pvw)
394 {
395 if (curbuf->b_fname == NULL
396 && curbuf->b_p_bt[0] == 'n' && curbuf->b_p_bt[2] == 'f'
397 && curbuf->b_p_bh[0] == 'w')
398 {
399 /* Already a "wipeout" buffer, make it empty. */
400 while (!bufempty())
401 ml_delete((linenr_T)1, FALSE);
402 }
403 else if ((res = do_ecmd(0, NULL, NULL, NULL, ECMD_ONE, 0))
404 == OK)
405 {
406 /* Edit a new, empty buffer. Set options for a "wipeout"
407 * buffer. */
408 set_option_value((char_u *)"swf", 0L, NULL, OPT_LOCAL);
409 set_option_value((char_u *)"bt", 0L, (char_u *)"nofile",
410 OPT_LOCAL);
411 set_option_value((char_u *)"bh", 0L, (char_u *)"wipe",
412 OPT_LOCAL);
413 set_option_value((char_u *)"diff", 0L, (char_u *)"",
414 OPT_LOCAL);
415 }
416 if (res == OK)
417 {
418 char_u *p, *e;
419 linenr_T lnum = 0;
420
421 for (p = pum_array[pum_selected].pum_info; *p != NUL; )
422 {
423 e = vim_strchr(p, '\n');
424 if (e == NULL)
425 {
426 ml_append(lnum++, p, 0, FALSE);
427 break;
428 }
429 else
430 {
431 *e = NUL;
432 ml_append(lnum++, p, e - p + 1, FALSE);
433 *e = '\n';
434 p = e + 1;
435 }
436 }
437
438 /* Increase the height of the preview window to show the
439 * text, but no more than 'previewheight' lines. */
440 if (lnum > p_pvh)
441 lnum = p_pvh;
442 if (curwin->w_height < lnum)
443 {
444 win_setheight((int)lnum);
445 resized = TRUE;
446 }
447
448 curbuf->b_changed = 0;
449 curbuf->b_p_ma = FALSE;
450 curwin->w_cursor.lnum = 0;
451 curwin->w_cursor.col = 0;
452
453 if (curwin != curwin_save && win_valid(curwin_save))
454 {
455 /* Return cursor to where we were */
456 validate_cursor();
Bram Moolenaar600dddc2006-03-12 22:05:10 +0000457 redraw_later(SOME_VALID);
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000458
459 /* When the preview window was resized we need to
460 * update the view on the buffer. Only go back to
461 * the window when needed, otherwise it will always be
462 * redraw. */
463 if (resized)
464 {
465 win_enter(curwin_save, TRUE);
466 update_topline();
467 }
468
469 /* Update the screen before drawing the popup menu.
470 * Enable updating the status lines. */
471 pum_do_redraw = TRUE;
472 update_screen(0);
473 pum_do_redraw = FALSE;
474
475 if (win_valid(curwin_save))
476 win_enter(curwin_save, TRUE);
477 }
478 }
479 }
480 }
481#endif
Bram Moolenaar4b779472005-10-04 09:12:31 +0000482 }
483
484 /* Never display more than we have */
485 if (pum_first > pum_size - pum_height)
486 pum_first = pum_size - pum_height;
487
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000488 if (!resized)
489 pum_redraw();
490
491 return resized;
Bram Moolenaar4b779472005-10-04 09:12:31 +0000492}
493
494/*
495 * Undisplay the popup menu (later).
496 */
497 void
498pum_undisplay()
499{
500 pum_array = NULL;
Bram Moolenaar600dddc2006-03-12 22:05:10 +0000501 redraw_all_later(SOME_VALID);
Bram Moolenaar4b779472005-10-04 09:12:31 +0000502}
503
504/*
505 * Clear the popup menu. Currently only resets the offset to the first
506 * displayed item.
507 */
508 void
509pum_clear()
510{
511 pum_first = 0;
512}
513
514/*
515 * Return TRUE if the popup menu is displayed.
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000516 * Overruled when "pum_do_redraw" is set, used to redraw the status lines.
Bram Moolenaar4b779472005-10-04 09:12:31 +0000517 */
518 int
519pum_visible()
520{
Bram Moolenaar96d2c5b2006-03-11 21:27:59 +0000521 return !pum_do_redraw && pum_array != NULL;
Bram Moolenaar4b779472005-10-04 09:12:31 +0000522}
523
Bram Moolenaare3226be2005-12-18 22:10:00 +0000524/*
525 * Return the height of the popup menu, the number of entries visible.
526 * Only valid when pum_visible() returns TRUE!
527 */
528 int
529pum_get_height()
530{
531 return pum_height;
532}
533
Bram Moolenaar4b779472005-10-04 09:12:31 +0000534#endif