blob: c04cd49bbdeb99fb02097842eb89d259e6dccd84 [file] [log] [blame]
Bram Moolenaaredf3f972016-08-29 22:49:24 +02001/* vi:set ts=8 sts=4 sw=4 noet:
Bram Moolenaar071d4272004-06-13 20:20:40 +00002 *
3 * VIM - Vi IMproved by Bram Moolenaar
4 * GUI/Motif support by Robert Webb
5 *
6 * Do ":help uganda" in Vim to read copying and usage conditions.
7 * Do ":help credits" in Vim to see a list of people who contributed.
8 * See README.txt for an overview of the Vim source code.
9 */
10
11/*
12 * Code for menus. Used for the GUI and 'wildmenu'.
13 */
14
15#include "vim.h"
16
17#if defined(FEAT_MENU) || defined(PROTO)
18
19#define MENUDEPTH 10 /* maximum depth of menus */
20
21#ifdef FEAT_GUI_W32
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010022static int add_menu_path(char_u *, vimmenu_T *, int *, char_u *, int);
Bram Moolenaar071d4272004-06-13 20:20:40 +000023#else
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010024static int add_menu_path(char_u *, vimmenu_T *, int *, char_u *);
Bram Moolenaar071d4272004-06-13 20:20:40 +000025#endif
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010026static int menu_nable_recurse(vimmenu_T *menu, char_u *name, int modes, int enable);
27static int remove_menu(vimmenu_T **, char_u *, int, int silent);
28static void free_menu(vimmenu_T **menup);
29static void free_menu_string(vimmenu_T *, int);
30static int show_menus(char_u *, int);
31static void show_menus_recursive(vimmenu_T *, int, int);
32static int menu_name_equal(char_u *name, vimmenu_T *menu);
33static int menu_namecmp(char_u *name, char_u *mname);
34static int get_menu_cmd_modes(char_u *, int, int *, int *);
35static char_u *popup_mode_name(char_u *name, int idx);
36static char_u *menu_text(char_u *text, int *mnemonic, char_u **actext);
Bram Moolenaar071d4272004-06-13 20:20:40 +000037
38#if defined(FEAT_GUI_W32) && defined(FEAT_TEAROFF)
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010039static void gui_create_tearoffs_recurse(vimmenu_T *menu, const char_u *pname, int *pri_tab, int pri_idx);
40static void gui_add_tearoff(char_u *tearpath, int *pri_tab, int pri_idx);
41static void gui_destroy_tearoffs_recurse(vimmenu_T *menu);
Bram Moolenaar071d4272004-06-13 20:20:40 +000042static int s_tearoffs = FALSE;
43#endif
44
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010045static int menu_is_hidden(char_u *name);
Bram Moolenaar071d4272004-06-13 20:20:40 +000046#if defined(FEAT_CMDL_COMPL) || (defined(FEAT_GUI_W32) && defined(FEAT_TEAROFF))
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010047static int menu_is_tearoff(char_u *name);
Bram Moolenaar071d4272004-06-13 20:20:40 +000048#endif
49
50#if defined(FEAT_MULTI_LANG) || defined(FEAT_TOOLBAR)
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010051static char_u *menu_skip_part(char_u *p);
Bram Moolenaar071d4272004-06-13 20:20:40 +000052#endif
53#ifdef FEAT_MULTI_LANG
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010054static char_u *menutrans_lookup(char_u *name, int len);
55static void menu_unescape_name(char_u *p);
Bram Moolenaar071d4272004-06-13 20:20:40 +000056#endif
57
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010058static char_u *menu_translate_tab_and_shift(char_u *arg_start);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +020059
Bram Moolenaar071d4272004-06-13 20:20:40 +000060/* The character for each menu mode */
Bram Moolenaareb3593b2006-04-22 22:33:57 +000061static char_u menu_mode_chars[] = {'n', 'v', 's', 'o', 'i', 'c', 't'};
Bram Moolenaar071d4272004-06-13 20:20:40 +000062
63static char_u e_notsubmenu[] = N_("E327: Part of menu-item path is not sub-menu");
64static char_u e_othermode[] = N_("E328: Menu only exists in another mode");
Bram Moolenaar342337a2005-07-21 21:11:17 +000065static char_u e_nomenu[] = N_("E329: No menu \"%s\"");
Bram Moolenaar071d4272004-06-13 20:20:40 +000066
67#ifdef FEAT_TOOLBAR
68static const char *toolbar_names[] =
69{
70 /* 0 */ "New", "Open", "Save", "Undo", "Redo",
71 /* 5 */ "Cut", "Copy", "Paste", "Print", "Help",
72 /* 10 */ "Find", "SaveAll", "SaveSesn", "NewSesn", "LoadSesn",
73 /* 15 */ "RunScript", "Replace", "WinClose", "WinMax", "WinMin",
74 /* 20 */ "WinSplit", "Shell", "FindPrev", "FindNext", "FindHelp",
75 /* 25 */ "Make", "TagJump", "RunCtags", "WinVSplit", "WinMaxWidth",
76 /* 30 */ "WinMinWidth", "Exit"
77};
78# define TOOLBAR_NAME_COUNT (sizeof(toolbar_names) / sizeof(char *))
79#endif
80
81/*
Bram Moolenaar1b9645d2017-09-17 23:03:31 +020082 * Return TRUE if "name" is a window toolbar menu name.
83 */
84 static int
85menu_is_winbar(char_u *name)
86{
Bram Moolenaar378daf82017-09-23 23:58:28 +020087 return (STRNCMP(name, "WinBar", 6) == 0);
Bram Moolenaar1b9645d2017-09-17 23:03:31 +020088}
89
90 int
91winbar_height(win_T *wp)
92{
93 if (wp->w_winbar != NULL && wp->w_winbar->children != NULL)
94 return 1;
95 return 0;
96}
97
98 static vimmenu_T **
99get_root_menu(char_u *name)
100{
101 if (menu_is_winbar(name))
102 return &curwin->w_winbar;
103 return &root_menu;
104}
105
106/*
Bram Moolenaar071d4272004-06-13 20:20:40 +0000107 * Do the :menu command and relatives.
108 */
109 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100110ex_menu(
111 exarg_T *eap) /* Ex command arguments */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000112{
113 char_u *menu_path;
114 int modes;
115 char_u *map_to;
116 int noremap;
117 int silent = FALSE;
Bram Moolenaar8b2d9c42006-05-03 21:28:47 +0000118 int special = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000119 int unmenu;
120 char_u *map_buf;
121 char_u *arg;
122 char_u *p;
123 int i;
Bram Moolenaar241a8aa2005-12-06 20:04:44 +0000124#if defined(FEAT_GUI) && !defined(FEAT_GUI_GTK)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000125 int old_menu_height;
Bram Moolenaare89ff042016-02-20 22:17:05 +0100126# if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_W32)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000127 int old_toolbar_height;
128# endif
129#endif
130 int pri_tab[MENUDEPTH + 1];
131 int enable = MAYBE; /* TRUE for "menu enable", FALSE for "menu
132 * disable */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000133#ifdef FEAT_TOOLBAR
134 char_u *icon = NULL;
135#endif
136 vimmenu_T menuarg;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200137 vimmenu_T **root_menu_ptr;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000138
139 modes = get_menu_cmd_modes(eap->cmd, eap->forceit, &noremap, &unmenu);
140 arg = eap->arg;
141
142 for (;;)
143 {
144 if (STRNCMP(arg, "<script>", 8) == 0)
145 {
146 noremap = REMAP_SCRIPT;
147 arg = skipwhite(arg + 8);
148 continue;
149 }
150 if (STRNCMP(arg, "<silent>", 8) == 0)
151 {
152 silent = TRUE;
153 arg = skipwhite(arg + 8);
154 continue;
155 }
Bram Moolenaar8b2d9c42006-05-03 21:28:47 +0000156 if (STRNCMP(arg, "<special>", 9) == 0)
157 {
158 special = TRUE;
159 arg = skipwhite(arg + 9);
160 continue;
161 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000162 break;
163 }
164
165
166 /* Locate an optional "icon=filename" argument. */
167 if (STRNCMP(arg, "icon=", 5) == 0)
168 {
169 arg += 5;
170#ifdef FEAT_TOOLBAR
171 icon = arg;
172#endif
173 while (*arg != NUL && *arg != ' ')
174 {
175 if (*arg == '\\')
Bram Moolenaar8c8de832008-06-24 22:58:06 +0000176 STRMOVE(arg, arg + 1);
Bram Moolenaar91acfff2017-03-12 19:22:36 +0100177 MB_PTR_ADV(arg);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000178 }
179 if (*arg != NUL)
180 {
181 *arg++ = NUL;
182 arg = skipwhite(arg);
183 }
184 }
185
186 /*
187 * Fill in the priority table.
188 */
189 for (p = arg; *p; ++p)
190 if (!VIM_ISDIGIT(*p) && *p != '.')
191 break;
Bram Moolenaar1c465442017-03-12 20:10:05 +0100192 if (VIM_ISWHITE(*p))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000193 {
Bram Moolenaar1c465442017-03-12 20:10:05 +0100194 for (i = 0; i < MENUDEPTH && !VIM_ISWHITE(*arg); ++i)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000195 {
196 pri_tab[i] = getdigits(&arg);
197 if (pri_tab[i] == 0)
198 pri_tab[i] = 500;
199 if (*arg == '.')
200 ++arg;
201 }
202 arg = skipwhite(arg);
203 }
204 else if (eap->addr_count && eap->line2 != 0)
205 {
206 pri_tab[0] = eap->line2;
207 i = 1;
208 }
209 else
210 i = 0;
211 while (i < MENUDEPTH)
212 pri_tab[i++] = 500;
213 pri_tab[MENUDEPTH] = -1; /* mark end of the table */
214
215 /*
216 * Check for "disable" or "enable" argument.
217 */
Bram Moolenaar1c465442017-03-12 20:10:05 +0100218 if (STRNCMP(arg, "enable", 6) == 0 && VIM_ISWHITE(arg[6]))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000219 {
220 enable = TRUE;
221 arg = skipwhite(arg + 6);
222 }
Bram Moolenaar1c465442017-03-12 20:10:05 +0100223 else if (STRNCMP(arg, "disable", 7) == 0 && VIM_ISWHITE(arg[7]))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000224 {
225 enable = FALSE;
226 arg = skipwhite(arg + 7);
227 }
228
229 /*
230 * If there is no argument, display all menus.
231 */
232 if (*arg == NUL)
233 {
234 show_menus(arg, modes);
235 return;
236 }
237
238#ifdef FEAT_TOOLBAR
239 /*
240 * Need to get the toolbar icon index before doing the translation.
241 */
242 menuarg.iconidx = -1;
243 menuarg.icon_builtin = FALSE;
244 if (menu_is_toolbar(arg))
245 {
246 menu_path = menu_skip_part(arg);
247 if (*menu_path == '.')
248 {
249 p = menu_skip_part(++menu_path);
250 if (STRNCMP(menu_path, "BuiltIn", 7) == 0)
251 {
252 if (skipdigits(menu_path + 7) == p)
253 {
254 menuarg.iconidx = atoi((char *)menu_path + 7);
Bram Moolenaaraf0167f2009-05-16 15:31:32 +0000255 if (menuarg.iconidx >= (int)TOOLBAR_NAME_COUNT)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000256 menuarg.iconidx = -1;
257 else
258 menuarg.icon_builtin = TRUE;
259 }
260 }
261 else
262 {
Bram Moolenaaraf0167f2009-05-16 15:31:32 +0000263 for (i = 0; i < (int)TOOLBAR_NAME_COUNT; ++i)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000264 if (STRNCMP(toolbar_names[i], menu_path, p - menu_path)
265 == 0)
266 {
267 menuarg.iconidx = i;
268 break;
269 }
270 }
271 }
272 }
273#endif
274
Bram Moolenaar071d4272004-06-13 20:20:40 +0000275 menu_path = arg;
276 if (*menu_path == '.')
277 {
278 EMSG2(_(e_invarg2), menu_path);
279 goto theend;
280 }
281
Bram Moolenaar70b11cd2010-05-14 22:24:40 +0200282 map_to = menu_translate_tab_and_shift(arg);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000283
284 /*
285 * If there is only a menu name, display menus with that name.
286 */
287 if (*map_to == NUL && !unmenu && enable == MAYBE)
288 {
289 show_menus(menu_path, modes);
290 goto theend;
291 }
292 else if (*map_to != NUL && (unmenu || enable != MAYBE))
293 {
294 EMSG(_(e_trailing));
295 goto theend;
296 }
Bram Moolenaar241a8aa2005-12-06 20:04:44 +0000297#if defined(FEAT_GUI) && !(defined(FEAT_GUI_GTK) || defined(FEAT_GUI_PHOTON))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000298 old_menu_height = gui.menu_height;
Bram Moolenaare89ff042016-02-20 22:17:05 +0100299# if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_W32)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000300 old_toolbar_height = gui.toolbar_height;
301# endif
302#endif
303
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200304 root_menu_ptr = get_root_menu(menu_path);
305 if (root_menu_ptr == &curwin->w_winbar)
306 /* Assume the window toolbar menu will change. */
307 redraw_later(NOT_VALID);
308
Bram Moolenaar071d4272004-06-13 20:20:40 +0000309 if (enable != MAYBE)
310 {
311 /*
312 * Change sensitivity of the menu.
313 * For the PopUp menu, remove a menu for each mode separately.
314 * Careful: menu_nable_recurse() changes menu_path.
315 */
316 if (STRCMP(menu_path, "*") == 0) /* meaning: do all menus */
317 menu_path = (char_u *)"";
318
319 if (menu_is_popup(menu_path))
320 {
321 for (i = 0; i < MENU_INDEX_TIP; ++i)
322 if (modes & (1 << i))
323 {
324 p = popup_mode_name(menu_path, i);
325 if (p != NULL)
326 {
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200327 menu_nable_recurse(*root_menu_ptr, p, MENU_ALL_MODES,
Bram Moolenaar071d4272004-06-13 20:20:40 +0000328 enable);
329 vim_free(p);
330 }
331 }
332 }
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200333 menu_nable_recurse(*root_menu_ptr, menu_path, modes, enable);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000334 }
335 else if (unmenu)
336 {
337 /*
338 * Delete menu(s).
339 */
340 if (STRCMP(menu_path, "*") == 0) /* meaning: remove all menus */
341 menu_path = (char_u *)"";
342
343 /*
344 * For the PopUp menu, remove a menu for each mode separately.
345 */
346 if (menu_is_popup(menu_path))
347 {
348 for (i = 0; i < MENU_INDEX_TIP; ++i)
349 if (modes & (1 << i))
350 {
351 p = popup_mode_name(menu_path, i);
352 if (p != NULL)
353 {
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200354 remove_menu(root_menu_ptr, p, MENU_ALL_MODES, TRUE);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000355 vim_free(p);
356 }
357 }
358 }
359
360 /* Careful: remove_menu() changes menu_path */
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200361 remove_menu(root_menu_ptr, menu_path, modes, FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000362 }
363 else
364 {
365 /*
366 * Add menu(s).
367 * Replace special key codes.
368 */
369 if (STRICMP(map_to, "<nop>") == 0) /* "<Nop>" means nothing */
370 {
371 map_to = (char_u *)"";
372 map_buf = NULL;
373 }
Bram Moolenaar3fdfa4a2004-10-07 21:02:47 +0000374 else if (modes & MENU_TIP_MODE)
375 map_buf = NULL; /* Menu tips are plain text. */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000376 else
Bram Moolenaar8b2d9c42006-05-03 21:28:47 +0000377 map_to = replace_termcodes(map_to, &map_buf, FALSE, TRUE, special);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000378 menuarg.modes = modes;
379#ifdef FEAT_TOOLBAR
380 menuarg.iconfile = icon;
381#endif
382 menuarg.noremap[0] = noremap;
383 menuarg.silent[0] = silent;
384 add_menu_path(menu_path, &menuarg, pri_tab, map_to
385#ifdef FEAT_GUI_W32
386 , TRUE
387#endif
388 );
389
390 /*
391 * For the PopUp menu, add a menu for each mode separately.
392 */
393 if (menu_is_popup(menu_path))
394 {
395 for (i = 0; i < MENU_INDEX_TIP; ++i)
396 if (modes & (1 << i))
397 {
398 p = popup_mode_name(menu_path, i);
399 if (p != NULL)
400 {
401 /* Include all modes, to make ":amenu" work */
402 menuarg.modes = modes;
403#ifdef FEAT_TOOLBAR
404 menuarg.iconfile = NULL;
405 menuarg.iconidx = -1;
406 menuarg.icon_builtin = FALSE;
407#endif
408 add_menu_path(p, &menuarg, pri_tab, map_to
409#ifdef FEAT_GUI_W32
410 , TRUE
411#endif
412 );
413 vim_free(p);
414 }
415 }
416 }
417
418 vim_free(map_buf);
419 }
420
Bram Moolenaar241a8aa2005-12-06 20:04:44 +0000421#if defined(FEAT_GUI) && !(defined(FEAT_GUI_GTK))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000422 /* If the menubar height changed, resize the window */
423 if (gui.in_use
424 && (gui.menu_height != old_menu_height
Bram Moolenaare89ff042016-02-20 22:17:05 +0100425# if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_W32)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000426 || gui.toolbar_height != old_toolbar_height
427# endif
428 ))
Bram Moolenaar04a9d452006-03-27 21:03:26 +0000429 gui_set_shellsize(FALSE, FALSE, RESIZE_VERT);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000430#endif
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200431 if (root_menu_ptr == &curwin->w_winbar)
432 {
433 int h = winbar_height(curwin);
434
435 if (h != curwin->w_winbar_height)
436 {
437 if (h == 0)
438 ++curwin->w_height;
439 else if (curwin->w_height > 0)
440 --curwin->w_height;
441 curwin->w_winbar_height = h;
442 }
443 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000444
445theend:
Bram Moolenaar071d4272004-06-13 20:20:40 +0000446 ;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000447}
448
449/*
450 * Add the menu with the given name to the menu hierarchy
451 */
452 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100453add_menu_path(
454 char_u *menu_path,
455 vimmenu_T *menuarg, /* passes modes, iconfile, iconidx,
Bram Moolenaar071d4272004-06-13 20:20:40 +0000456 icon_builtin, silent[0], noremap[0] */
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100457 int *pri_tab,
458 char_u *call_data
Bram Moolenaar071d4272004-06-13 20:20:40 +0000459#ifdef FEAT_GUI_W32
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100460 , int addtearoff /* may add tearoff item */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000461#endif
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100462 )
Bram Moolenaar071d4272004-06-13 20:20:40 +0000463{
464 char_u *path_name;
465 int modes = menuarg->modes;
466 vimmenu_T **menup;
467 vimmenu_T *menu = NULL;
468 vimmenu_T *parent;
469 vimmenu_T **lower_pri;
470 char_u *p;
471 char_u *name;
472 char_u *dname;
473 char_u *next_name;
474 int i;
475 int c;
Bram Moolenaar7871a502010-05-14 21:19:23 +0200476 int d;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000477#ifdef FEAT_GUI
478 int idx;
479 int new_idx;
480#endif
481 int pri_idx = 0;
482 int old_modes = 0;
483 int amenu;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +0200484#ifdef FEAT_MULTI_LANG
485 char_u *en_name;
486 char_u *map_to = NULL;
487#endif
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200488 vimmenu_T **root_menu_ptr;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000489
490 /* Make a copy so we can stuff around with it, since it could be const */
491 path_name = vim_strsave(menu_path);
492 if (path_name == NULL)
493 return FAIL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200494 root_menu_ptr = get_root_menu(menu_path);
495 menup = root_menu_ptr;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000496 parent = NULL;
497 name = path_name;
498 while (*name)
499 {
500 /* Get name of this element in the menu hierarchy, and the simplified
501 * name (without mnemonic and accelerator text). */
502 next_name = menu_name_skip(name);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +0200503#ifdef FEAT_MULTI_LANG
Bram Moolenaar442b4222010-05-24 21:34:22 +0200504 map_to = menutrans_lookup(name, (int)STRLEN(name));
Bram Moolenaar70b11cd2010-05-14 22:24:40 +0200505 if (map_to != NULL)
506 {
507 en_name = name;
508 name = map_to;
509 }
510 else
511 en_name = NULL;
512#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000513 dname = menu_text(name, NULL, NULL);
Bram Moolenaar18a0b122006-08-16 13:55:16 +0000514 if (dname == NULL)
515 goto erret;
516 if (*dname == NUL)
517 {
518 /* Only a mnemonic or accelerator is not valid. */
519 EMSG(_("E792: Empty menu name"));
520 goto erret;
521 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000522
523 /* See if it's already there */
524 lower_pri = menup;
525#ifdef FEAT_GUI
526 idx = 0;
527 new_idx = 0;
528#endif
529 menu = *menup;
530 while (menu != NULL)
531 {
532 if (menu_name_equal(name, menu) || menu_name_equal(dname, menu))
533 {
534 if (*next_name == NUL && menu->children != NULL)
535 {
536 if (!sys_menu)
537 EMSG(_("E330: Menu path must not lead to a sub-menu"));
538 goto erret;
539 }
540 if (*next_name != NUL && menu->children == NULL
541#ifdef FEAT_GUI_W32
542 && addtearoff
543#endif
544 )
545 {
546 if (!sys_menu)
547 EMSG(_(e_notsubmenu));
548 goto erret;
549 }
550 break;
551 }
552 menup = &menu->next;
553
554 /* Count menus, to find where this one needs to be inserted.
555 * Ignore menus that are not in the menubar (PopUp and Toolbar) */
556 if (parent != NULL || menu_is_menubar(menu->name))
557 {
558#ifdef FEAT_GUI
559 ++idx;
560#endif
561 if (menu->priority <= pri_tab[pri_idx])
562 {
563 lower_pri = menup;
564#ifdef FEAT_GUI
565 new_idx = idx;
566#endif
567 }
568 }
569 menu = menu->next;
570 }
571
572 if (menu == NULL)
573 {
574 if (*next_name == NUL && parent == NULL)
575 {
576 EMSG(_("E331: Must not add menu items directly to menu bar"));
577 goto erret;
578 }
579
580 if (menu_is_separator(dname) && *next_name != NUL)
581 {
582 EMSG(_("E332: Separator cannot be part of a menu path"));
583 goto erret;
584 }
585
586 /* Not already there, so lets add it */
587 menu = (vimmenu_T *)alloc_clear((unsigned)sizeof(vimmenu_T));
588 if (menu == NULL)
589 goto erret;
590
591 menu->modes = modes;
592 menu->enabled = MENU_ALL_MODES;
593 menu->name = vim_strsave(name);
594 /* separate mnemonic and accelerator text from actual menu name */
595 menu->dname = menu_text(name, &menu->mnemonic, &menu->actext);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +0200596#ifdef FEAT_MULTI_LANG
597 if (en_name != NULL)
598 {
599 menu->en_name = vim_strsave(en_name);
600 menu->en_dname = menu_text(en_name, NULL, NULL);
601 }
602 else
603 {
604 menu->en_name = NULL;
605 menu->en_dname = NULL;
606 }
607#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000608 menu->priority = pri_tab[pri_idx];
609 menu->parent = parent;
610#ifdef FEAT_GUI_MOTIF
611 menu->sensitive = TRUE; /* the default */
612#endif
613#ifdef FEAT_BEVAL_TIP
614 menu->tip = NULL;
615#endif
616#ifdef FEAT_GUI_ATHENA
617 menu->image = None; /* X-Windows definition for NULL*/
618#endif
619
620 /*
621 * Add after menu that has lower priority.
622 */
623 menu->next = *lower_pri;
624 *lower_pri = menu;
625
626 old_modes = 0;
627
628#ifdef FEAT_TOOLBAR
629 menu->iconidx = menuarg->iconidx;
630 menu->icon_builtin = menuarg->icon_builtin;
631 if (*next_name == NUL && menuarg->iconfile != NULL)
632 menu->iconfile = vim_strsave(menuarg->iconfile);
633#endif
634#if defined(FEAT_GUI_W32) && defined(FEAT_TEAROFF)
635 /* the tearoff item must be present in the modes of each item. */
636 if (parent != NULL && menu_is_tearoff(parent->children->dname))
637 parent->children->modes |= modes;
638#endif
639 }
640 else
641 {
642 old_modes = menu->modes;
643
644 /*
645 * If this menu option was previously only available in other
646 * modes, then make sure it's available for this one now
647 * Also enable a menu when it's created or changed.
648 */
649#ifdef FEAT_GUI_W32
650 /* If adding a tearbar (addtearoff == FALSE) don't update modes */
651 if (addtearoff)
652#endif
653 {
654 menu->modes |= modes;
655 menu->enabled |= modes;
656 }
657 }
658
659#ifdef FEAT_GUI
660 /*
661 * Add the menu item when it's used in one of the modes, but not when
662 * only a tooltip is defined.
663 */
664 if ((old_modes & MENU_ALL_MODES) == 0
665 && (menu->modes & MENU_ALL_MODES) != 0)
666 {
667 if (gui.in_use) /* Otherwise it will be added when GUI starts */
668 {
669 if (*next_name == NUL)
670 {
671 /* Real menu item, not sub-menu */
672 gui_mch_add_menu_item(menu, new_idx);
673
674 /* Want to update menus now even if mode not changed */
675 force_menu_update = TRUE;
676 }
677 else
678 {
679 /* Sub-menu (not at end of path yet) */
680 gui_mch_add_menu(menu, new_idx);
681 }
682 }
683
684# if defined(FEAT_GUI_W32) & defined(FEAT_TEAROFF)
685 /* When adding a new submenu, may add a tearoff item */
686 if ( addtearoff
687 && *next_name
688 && vim_strchr(p_go, GO_TEAROFF) != NULL
689 && menu_is_menubar(name))
690 {
691 char_u *tearpath;
692
693 /*
694 * The pointers next_name & path_name refer to a string with
695 * \'s and ^V's stripped out. But menu_path is a "raw"
696 * string, so we must correct for special characters.
697 */
698 tearpath = alloc((unsigned int)STRLEN(menu_path) + TEAR_LEN + 2);
699 if (tearpath != NULL)
700 {
701 char_u *s;
702 int idx;
703
704 STRCPY(tearpath, menu_path);
705 idx = (int)(next_name - path_name - 1);
Bram Moolenaar91acfff2017-03-12 19:22:36 +0100706 for (s = tearpath; *s && s < tearpath + idx; MB_PTR_ADV(s))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000707 {
708 if ((*s == '\\' || *s == Ctrl_V) && s[1])
709 {
710 ++idx;
711 ++s;
712 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000713 }
714 tearpath[idx] = NUL;
715 gui_add_tearoff(tearpath, pri_tab, pri_idx);
716 vim_free(tearpath);
717 }
718 }
719# endif
720 }
721#endif /* FEAT_GUI */
722
723 menup = &menu->children;
724 parent = menu;
725 name = next_name;
Bram Moolenaard23a8232018-02-10 18:45:26 +0100726 VIM_CLEAR(dname);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000727 if (pri_tab[pri_idx + 1] != -1)
728 ++pri_idx;
729 }
730 vim_free(path_name);
731
732 /*
733 * Only add system menu items which have not been defined yet.
734 * First check if this was an ":amenu".
735 */
736 amenu = ((modes & (MENU_NORMAL_MODE | MENU_INSERT_MODE)) ==
737 (MENU_NORMAL_MODE | MENU_INSERT_MODE));
738 if (sys_menu)
739 modes &= ~old_modes;
740
741 if (menu != NULL && modes)
742 {
743#ifdef FEAT_GUI
744 menu->cb = gui_menu_cb;
745#endif
746 p = (call_data == NULL) ? NULL : vim_strsave(call_data);
747
748 /* loop over all modes, may add more than one */
749 for (i = 0; i < MENU_MODES; ++i)
750 {
751 if (modes & (1 << i))
752 {
753 /* free any old menu */
754 free_menu_string(menu, i);
755
756 /* For "amenu", may insert an extra character.
757 * Don't do this if adding a tearbar (addtearoff == FALSE).
758 * Don't do this for "<Nop>". */
759 c = 0;
Bram Moolenaar7871a502010-05-14 21:19:23 +0200760 d = 0;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000761 if (amenu && call_data != NULL && *call_data != NUL
762#ifdef FEAT_GUI_W32
763 && addtearoff
764#endif
765 )
766 {
767 switch (1 << i)
768 {
769 case MENU_VISUAL_MODE:
Bram Moolenaarb3656ed2006-03-20 21:59:49 +0000770 case MENU_SELECT_MODE:
Bram Moolenaar071d4272004-06-13 20:20:40 +0000771 case MENU_OP_PENDING_MODE:
772 case MENU_CMDLINE_MODE:
773 c = Ctrl_C;
774 break;
775 case MENU_INSERT_MODE:
Bram Moolenaar7871a502010-05-14 21:19:23 +0200776 c = Ctrl_BSL;
777 d = Ctrl_O;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000778 break;
779 }
780 }
781
Bram Moolenaar7871a502010-05-14 21:19:23 +0200782 if (c != 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000783 {
Bram Moolenaar7871a502010-05-14 21:19:23 +0200784 menu->strings[i] = alloc((unsigned)(STRLEN(call_data) + 5 ));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000785 if (menu->strings[i] != NULL)
786 {
787 menu->strings[i][0] = c;
Bram Moolenaar7871a502010-05-14 21:19:23 +0200788 if (d == 0)
789 STRCPY(menu->strings[i] + 1, call_data);
790 else
791 {
792 menu->strings[i][1] = d;
793 STRCPY(menu->strings[i] + 2, call_data);
794 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000795 if (c == Ctrl_C)
796 {
Bram Moolenaara93fa7e2006-04-17 22:14:47 +0000797 int len = (int)STRLEN(menu->strings[i]);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000798
799 /* Append CTRL-\ CTRL-G to obey 'insertmode'. */
800 menu->strings[i][len] = Ctrl_BSL;
801 menu->strings[i][len + 1] = Ctrl_G;
802 menu->strings[i][len + 2] = NUL;
803 }
804 }
805 }
806 else
807 menu->strings[i] = p;
808 menu->noremap[i] = menuarg->noremap[0];
809 menu->silent[i] = menuarg->silent[0];
810 }
811 }
812#if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_W32) \
Bram Moolenaarc3719bd2017-11-18 22:13:31 +0100813 && (defined(FEAT_BEVAL_GUI) || defined(FEAT_GUI_GTK))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000814 /* Need to update the menu tip. */
815 if (modes & MENU_TIP_MODE)
816 gui_mch_menu_set_tip(menu);
817#endif
818 }
819 return OK;
820
821erret:
822 vim_free(path_name);
823 vim_free(dname);
Bram Moolenaar18a0b122006-08-16 13:55:16 +0000824
825 /* Delete any empty submenu we added before discovering the error. Repeat
826 * for higher levels. */
827 while (parent != NULL && parent->children == NULL)
828 {
829 if (parent->parent == NULL)
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200830 menup = root_menu_ptr;
Bram Moolenaar18a0b122006-08-16 13:55:16 +0000831 else
832 menup = &parent->parent->children;
833 for ( ; *menup != NULL && *menup != parent; menup = &((*menup)->next))
834 ;
835 if (*menup == NULL) /* safety check */
836 break;
837 parent = parent->parent;
838 free_menu(menup);
839 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000840 return FAIL;
841}
842
843/*
844 * Set the (sub)menu with the given name to enabled or disabled.
845 * Called recursively.
846 */
847 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100848menu_nable_recurse(
849 vimmenu_T *menu,
850 char_u *name,
851 int modes,
852 int enable)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000853{
854 char_u *p;
855
856 if (menu == NULL)
857 return OK; /* Got to bottom of hierarchy */
858
859 /* Get name of this element in the menu hierarchy */
860 p = menu_name_skip(name);
861
862 /* Find the menu */
863 while (menu != NULL)
864 {
865 if (*name == NUL || *name == '*' || menu_name_equal(name, menu))
866 {
867 if (*p != NUL)
868 {
869 if (menu->children == NULL)
870 {
871 EMSG(_(e_notsubmenu));
872 return FAIL;
873 }
874 if (menu_nable_recurse(menu->children, p, modes, enable)
875 == FAIL)
876 return FAIL;
877 }
878 else
879 if (enable)
880 menu->enabled |= modes;
881 else
882 menu->enabled &= ~modes;
883
884 /*
885 * When name is empty, we are doing all menu items for the given
886 * modes, so keep looping, otherwise we are just doing the named
887 * menu item (which has been found) so break here.
888 */
889 if (*name != NUL && *name != '*')
890 break;
891 }
892 menu = menu->next;
893 }
894 if (*name != NUL && *name != '*' && menu == NULL)
895 {
Bram Moolenaar342337a2005-07-21 21:11:17 +0000896 EMSG2(_(e_nomenu), name);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000897 return FAIL;
898 }
899
900#ifdef FEAT_GUI
901 /* Want to update menus now even if mode not changed */
902 force_menu_update = TRUE;
903#endif
904
905 return OK;
906}
907
908/*
909 * Remove the (sub)menu with the given name from the menu hierarchy
910 * Called recursively.
911 */
912 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100913remove_menu(
914 vimmenu_T **menup,
915 char_u *name,
916 int modes,
917 int silent) /* don't give error messages */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000918{
919 vimmenu_T *menu;
920 vimmenu_T *child;
921 char_u *p;
922
923 if (*menup == NULL)
924 return OK; /* Got to bottom of hierarchy */
925
926 /* Get name of this element in the menu hierarchy */
927 p = menu_name_skip(name);
928
929 /* Find the menu */
930 while ((menu = *menup) != NULL)
931 {
932 if (*name == NUL || menu_name_equal(name, menu))
933 {
934 if (*p != NUL && menu->children == NULL)
935 {
936 if (!silent)
937 EMSG(_(e_notsubmenu));
938 return FAIL;
939 }
940 if ((menu->modes & modes) != 0x0)
941 {
942#if defined(FEAT_GUI_W32) & defined(FEAT_TEAROFF)
943 /*
944 * If we are removing all entries for this menu,MENU_ALL_MODES,
945 * Then kill any tearoff before we start
946 */
947 if (*p == NUL && modes == MENU_ALL_MODES)
948 {
949 if (IsWindow(menu->tearoff_handle))
950 DestroyWindow(menu->tearoff_handle);
951 }
952#endif
953 if (remove_menu(&menu->children, p, modes, silent) == FAIL)
954 return FAIL;
955 }
956 else if (*name != NUL)
957 {
958 if (!silent)
959 EMSG(_(e_othermode));
960 return FAIL;
961 }
962
963 /*
964 * When name is empty, we are removing all menu items for the given
965 * modes, so keep looping, otherwise we are just removing the named
966 * menu item (which has been found) so break here.
967 */
968 if (*name != NUL)
969 break;
970
971 /* Remove the menu item for the given mode[s]. If the menu item
972 * is no longer valid in ANY mode, delete it */
973 menu->modes &= ~modes;
974 if (modes & MENU_TIP_MODE)
975 free_menu_string(menu, MENU_INDEX_TIP);
976 if ((menu->modes & MENU_ALL_MODES) == 0)
977 free_menu(menup);
978 else
979 menup = &menu->next;
980 }
981 else
982 menup = &menu->next;
983 }
984 if (*name != NUL)
985 {
986 if (menu == NULL)
987 {
988 if (!silent)
Bram Moolenaar342337a2005-07-21 21:11:17 +0000989 EMSG2(_(e_nomenu), name);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000990 return FAIL;
991 }
992
993
994 /* Recalculate modes for menu based on the new updated children */
995 menu->modes &= ~modes;
996#if defined(FEAT_GUI_W32) & defined(FEAT_TEAROFF)
997 if ((s_tearoffs) && (menu->children != NULL)) /* there's a tear bar.. */
998 child = menu->children->next; /* don't count tearoff bar */
999 else
1000#endif
1001 child = menu->children;
1002 for ( ; child != NULL; child = child->next)
1003 menu->modes |= child->modes;
1004 if (modes & MENU_TIP_MODE)
1005 {
1006 free_menu_string(menu, MENU_INDEX_TIP);
1007#if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_W32) \
Bram Moolenaarc3719bd2017-11-18 22:13:31 +01001008 && (defined(FEAT_BEVAL_GUI) || defined(FEAT_GUI_GTK))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001009 /* Need to update the menu tip. */
1010 if (gui.in_use)
1011 gui_mch_menu_set_tip(menu);
1012#endif
1013 }
1014 if ((menu->modes & MENU_ALL_MODES) == 0)
1015 {
1016 /* The menu item is no longer valid in ANY mode, so delete it */
1017#if defined(FEAT_GUI_W32) & defined(FEAT_TEAROFF)
1018 if (s_tearoffs && menu->children != NULL) /* there's a tear bar.. */
1019 free_menu(&menu->children);
1020#endif
1021 *menup = menu;
1022 free_menu(menup);
1023 }
1024 }
1025
1026 return OK;
1027}
1028
1029/*
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001030 * Remove the WinBar menu from window "wp".
1031 */
1032 void
1033remove_winbar(win_T *wp)
1034{
1035 remove_menu(&wp->w_winbar, (char_u *)"", MENU_ALL_MODES, TRUE);
1036 vim_free(wp->w_winbar_items);
1037}
1038
1039/*
Bram Moolenaar071d4272004-06-13 20:20:40 +00001040 * Free the given menu structure and remove it from the linked list.
1041 */
1042 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001043free_menu(vimmenu_T **menup)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001044{
1045 int i;
1046 vimmenu_T *menu;
1047
1048 menu = *menup;
1049
1050#ifdef FEAT_GUI
1051 /* Free machine specific menu structures (only when already created) */
1052 /* Also may rebuild a tearoff'ed menu */
1053 if (gui.in_use)
1054 gui_mch_destroy_menu(menu);
1055#endif
1056
1057 /* Don't change *menup until after calling gui_mch_destroy_menu(). The
1058 * MacOS code needs the original structure to properly delete the menu. */
1059 *menup = menu->next;
1060 vim_free(menu->name);
1061 vim_free(menu->dname);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001062#ifdef FEAT_MULTI_LANG
1063 vim_free(menu->en_name);
1064 vim_free(menu->en_dname);
1065#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001066 vim_free(menu->actext);
1067#ifdef FEAT_TOOLBAR
1068 vim_free(menu->iconfile);
Bram Moolenaarbee0c5b2005-02-07 22:03:36 +00001069# ifdef FEAT_GUI_MOTIF
1070 vim_free(menu->xpm_fname);
1071# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001072#endif
1073 for (i = 0; i < MENU_MODES; i++)
1074 free_menu_string(menu, i);
1075 vim_free(menu);
1076
1077#ifdef FEAT_GUI
1078 /* Want to update menus now even if mode not changed */
1079 force_menu_update = TRUE;
1080#endif
1081}
1082
1083/*
1084 * Free the menu->string with the given index.
1085 */
1086 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001087free_menu_string(vimmenu_T *menu, int idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001088{
1089 int count = 0;
1090 int i;
1091
1092 for (i = 0; i < MENU_MODES; i++)
1093 if (menu->strings[i] == menu->strings[idx])
1094 count++;
1095 if (count == 1)
1096 vim_free(menu->strings[idx]);
1097 menu->strings[idx] = NULL;
1098}
1099
1100/*
1101 * Show the mapping associated with a menu item or hierarchy in a sub-menu.
1102 */
1103 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001104show_menus(char_u *path_name, int modes)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001105{
1106 char_u *p;
1107 char_u *name;
1108 vimmenu_T *menu;
1109 vimmenu_T *parent = NULL;
1110
Bram Moolenaar071d4272004-06-13 20:20:40 +00001111 name = path_name = vim_strsave(path_name);
1112 if (path_name == NULL)
1113 return FAIL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001114 menu = *get_root_menu(path_name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001115
1116 /* First, find the (sub)menu with the given name */
1117 while (*name)
1118 {
1119 p = menu_name_skip(name);
1120 while (menu != NULL)
1121 {
1122 if (menu_name_equal(name, menu))
1123 {
1124 /* Found menu */
1125 if (*p != NUL && menu->children == NULL)
1126 {
1127 EMSG(_(e_notsubmenu));
1128 vim_free(path_name);
1129 return FAIL;
1130 }
1131 else if ((menu->modes & modes) == 0x0)
1132 {
1133 EMSG(_(e_othermode));
1134 vim_free(path_name);
1135 return FAIL;
1136 }
1137 break;
1138 }
1139 menu = menu->next;
1140 }
1141 if (menu == NULL)
1142 {
Bram Moolenaar342337a2005-07-21 21:11:17 +00001143 EMSG2(_(e_nomenu), name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001144 vim_free(path_name);
1145 return FAIL;
1146 }
1147 name = p;
1148 parent = menu;
1149 menu = menu->children;
1150 }
Bram Moolenaaracbd4422008-08-17 21:44:45 +00001151 vim_free(path_name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001152
1153 /* Now we have found the matching menu, and we list the mappings */
1154 /* Highlight title */
1155 MSG_PUTS_TITLE(_("\n--- Menus ---"));
1156
1157 show_menus_recursive(parent, modes, 0);
1158 return OK;
1159}
1160
1161/*
1162 * Recursively show the mappings associated with the menus under the given one
1163 */
1164 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001165show_menus_recursive(vimmenu_T *menu, int modes, int depth)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001166{
1167 int i;
1168 int bit;
1169
1170 if (menu != NULL && (menu->modes & modes) == 0x0)
1171 return;
1172
1173 if (menu != NULL)
1174 {
1175 msg_putchar('\n');
1176 if (got_int) /* "q" hit for "--more--" */
1177 return;
1178 for (i = 0; i < depth; i++)
1179 MSG_PUTS(" ");
1180 if (menu->priority)
1181 {
1182 msg_outnum((long)menu->priority);
1183 MSG_PUTS(" ");
1184 }
1185 /* Same highlighting as for directories!? */
Bram Moolenaar8820b482017-03-16 17:23:31 +01001186 msg_outtrans_attr(menu->name, HL_ATTR(HLF_D));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001187 }
1188
1189 if (menu != NULL && menu->children == NULL)
1190 {
1191 for (bit = 0; bit < MENU_MODES; bit++)
1192 if ((menu->modes & modes & (1 << bit)) != 0)
1193 {
1194 msg_putchar('\n');
1195 if (got_int) /* "q" hit for "--more--" */
1196 return;
1197 for (i = 0; i < depth + 2; i++)
1198 MSG_PUTS(" ");
1199 msg_putchar(menu_mode_chars[bit]);
1200 if (menu->noremap[bit] == REMAP_NONE)
1201 msg_putchar('*');
1202 else if (menu->noremap[bit] == REMAP_SCRIPT)
1203 msg_putchar('&');
1204 else
1205 msg_putchar(' ');
1206 if (menu->silent[bit])
1207 msg_putchar('s');
1208 else
1209 msg_putchar(' ');
1210 if ((menu->modes & menu->enabled & (1 << bit)) == 0)
1211 msg_putchar('-');
1212 else
1213 msg_putchar(' ');
1214 MSG_PUTS(" ");
1215 if (*menu->strings[bit] == NUL)
Bram Moolenaar8820b482017-03-16 17:23:31 +01001216 msg_puts_attr((char_u *)"<Nop>", HL_ATTR(HLF_8));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001217 else
1218 msg_outtrans_special(menu->strings[bit], FALSE);
1219 }
1220 }
1221 else
1222 {
1223 if (menu == NULL)
1224 {
1225 menu = root_menu;
1226 depth--;
1227 }
1228 else
1229 menu = menu->children;
1230
1231 /* recursively show all children. Skip PopUp[nvoci]. */
1232 for (; menu != NULL && !got_int; menu = menu->next)
1233 if (!menu_is_hidden(menu->dname))
1234 show_menus_recursive(menu, modes, depth + 1);
1235 }
1236}
1237
1238#ifdef FEAT_CMDL_COMPL
1239
1240/*
1241 * Used when expanding menu names.
1242 */
1243static vimmenu_T *expand_menu = NULL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001244static vimmenu_T *expand_menu_alt = NULL;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001245static int expand_modes = 0x0;
1246static int expand_emenu; /* TRUE for ":emenu" command */
1247
1248/*
1249 * Work out what to complete when doing command line completion of menu names.
1250 */
1251 char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001252set_context_in_menu_cmd(
1253 expand_T *xp,
1254 char_u *cmd,
1255 char_u *arg,
1256 int forceit)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001257{
1258 char_u *after_dot;
1259 char_u *p;
1260 char_u *path_name = NULL;
1261 char_u *name;
1262 int unmenu;
1263 vimmenu_T *menu;
1264 int expand_menus;
1265
1266 xp->xp_context = EXPAND_UNSUCCESSFUL;
1267
1268
1269 /* Check for priority numbers, enable and disable */
1270 for (p = arg; *p; ++p)
1271 if (!VIM_ISDIGIT(*p) && *p != '.')
1272 break;
1273
Bram Moolenaar1c465442017-03-12 20:10:05 +01001274 if (!VIM_ISWHITE(*p))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001275 {
1276 if (STRNCMP(arg, "enable", 6) == 0
Bram Moolenaar1c465442017-03-12 20:10:05 +01001277 && (arg[6] == NUL || VIM_ISWHITE(arg[6])))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001278 p = arg + 6;
1279 else if (STRNCMP(arg, "disable", 7) == 0
Bram Moolenaar1c465442017-03-12 20:10:05 +01001280 && (arg[7] == NUL || VIM_ISWHITE(arg[7])))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001281 p = arg + 7;
1282 else
1283 p = arg;
1284 }
1285
Bram Moolenaar1c465442017-03-12 20:10:05 +01001286 while (*p != NUL && VIM_ISWHITE(*p))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001287 ++p;
1288
1289 arg = after_dot = p;
1290
Bram Moolenaar1c465442017-03-12 20:10:05 +01001291 for (; *p && !VIM_ISWHITE(*p); ++p)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001292 {
1293 if ((*p == '\\' || *p == Ctrl_V) && p[1] != NUL)
1294 p++;
1295 else if (*p == '.')
1296 after_dot = p + 1;
1297 }
1298
1299 /* ":tearoff" and ":popup" only use menus, not entries */
1300 expand_menus = !((*cmd == 't' && cmd[1] == 'e') || *cmd == 'p');
1301 expand_emenu = (*cmd == 'e');
Bram Moolenaar1c465442017-03-12 20:10:05 +01001302 if (expand_menus && VIM_ISWHITE(*p))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001303 return NULL; /* TODO: check for next command? */
1304 if (*p == NUL) /* Complete the menu name */
1305 {
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001306 int try_alt_menu = TRUE;
1307
Bram Moolenaar071d4272004-06-13 20:20:40 +00001308 /*
1309 * With :unmenu, you only want to match menus for the appropriate mode.
1310 * With :menu though you might want to add a menu with the same name as
1311 * one in another mode, so match menus from other modes too.
1312 */
1313 expand_modes = get_menu_cmd_modes(cmd, forceit, NULL, &unmenu);
1314 if (!unmenu)
1315 expand_modes = MENU_ALL_MODES;
1316
1317 menu = root_menu;
1318 if (after_dot != arg)
1319 {
1320 path_name = alloc((unsigned)(after_dot - arg));
1321 if (path_name == NULL)
1322 return NULL;
Bram Moolenaarce0842a2005-07-18 21:58:11 +00001323 vim_strncpy(path_name, arg, after_dot - arg - 1);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001324 }
1325 name = path_name;
1326 while (name != NULL && *name)
1327 {
1328 p = menu_name_skip(name);
1329 while (menu != NULL)
1330 {
1331 if (menu_name_equal(name, menu))
1332 {
1333 /* Found menu */
1334 if ((*p != NUL && menu->children == NULL)
1335 || ((menu->modes & expand_modes) == 0x0))
1336 {
1337 /*
1338 * Menu path continues, but we have reached a leaf.
1339 * Or menu exists only in another mode.
1340 */
1341 vim_free(path_name);
1342 return NULL;
1343 }
1344 break;
1345 }
1346 menu = menu->next;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001347 if (menu == NULL && try_alt_menu)
1348 {
1349 menu = curwin->w_winbar;
1350 try_alt_menu = FALSE;
1351 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001352 }
1353 if (menu == NULL)
1354 {
1355 /* No menu found with the name we were looking for */
1356 vim_free(path_name);
1357 return NULL;
1358 }
1359 name = p;
1360 menu = menu->children;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001361 try_alt_menu = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001362 }
Bram Moolenaareb3593b2006-04-22 22:33:57 +00001363 vim_free(path_name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001364
1365 xp->xp_context = expand_menus ? EXPAND_MENUNAMES : EXPAND_MENUS;
1366 xp->xp_pattern = after_dot;
1367 expand_menu = menu;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001368 if (expand_menu == root_menu)
1369 expand_menu_alt = curwin->w_winbar;
1370 else
1371 expand_menu_alt = NULL;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001372 }
1373 else /* We're in the mapping part */
1374 xp->xp_context = EXPAND_NOTHING;
1375 return NULL;
1376}
1377
1378/*
1379 * Function given to ExpandGeneric() to obtain the list of (sub)menus (not
1380 * entries).
1381 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001382 char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001383get_menu_name(expand_T *xp UNUSED, int idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001384{
1385 static vimmenu_T *menu = NULL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001386 static int did_alt_menu = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001387 char_u *str;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001388#ifdef FEAT_MULTI_LANG
1389 static int should_advance = FALSE;
1390#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001391
1392 if (idx == 0) /* first call: start at first item */
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001393 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001394 menu = expand_menu;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001395 did_alt_menu = FALSE;
Bram Moolenaar41375642010-05-16 12:49:27 +02001396#ifdef FEAT_MULTI_LANG
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001397 should_advance = FALSE;
Bram Moolenaar41375642010-05-16 12:49:27 +02001398#endif
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001399 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001400
1401 /* Skip PopUp[nvoci]. */
1402 while (menu != NULL && (menu_is_hidden(menu->dname)
1403 || menu_is_separator(menu->dname)
1404 || menu_is_tearoff(menu->dname)
1405 || menu->children == NULL))
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001406 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001407 menu = menu->next;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001408 if (menu == NULL && !did_alt_menu)
1409 {
1410 menu = expand_menu_alt;
1411 did_alt_menu = TRUE;
1412 }
1413 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001414
1415 if (menu == NULL) /* at end of linked list */
1416 return NULL;
1417
1418 if (menu->modes & expand_modes)
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001419#ifdef FEAT_MULTI_LANG
1420 if (should_advance)
1421 str = menu->en_dname;
1422 else
1423 {
1424#endif
1425 str = menu->dname;
1426#ifdef FEAT_MULTI_LANG
1427 if (menu->en_dname == NULL)
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001428 should_advance = TRUE;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001429 }
1430#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001431 else
1432 str = (char_u *)"";
1433
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001434#ifdef FEAT_MULTI_LANG
1435 if (should_advance)
1436#endif
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001437 {
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001438 /* Advance to next menu entry. */
1439 menu = menu->next;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001440 if (menu == NULL && !did_alt_menu)
1441 {
1442 menu = expand_menu_alt;
1443 did_alt_menu = TRUE;
1444 }
1445 }
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001446
1447#ifdef FEAT_MULTI_LANG
1448 should_advance = !should_advance;
1449#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001450
1451 return str;
1452}
1453
1454/*
1455 * Function given to ExpandGeneric() to obtain the list of menus and menu
1456 * entries.
1457 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001458 char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001459get_menu_names(expand_T *xp UNUSED, int idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001460{
1461 static vimmenu_T *menu = NULL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001462 static int did_alt_menu = FALSE;
Bram Moolenaaref9d6aa2011-04-11 16:56:35 +02001463#define TBUFFER_LEN 256
1464 static char_u tbuffer[TBUFFER_LEN]; /*hack*/
Bram Moolenaar071d4272004-06-13 20:20:40 +00001465 char_u *str;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001466#ifdef FEAT_MULTI_LANG
1467 static int should_advance = FALSE;
1468#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001469
1470 if (idx == 0) /* first call: start at first item */
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001471 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001472 menu = expand_menu;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001473 did_alt_menu = FALSE;
Bram Moolenaar41375642010-05-16 12:49:27 +02001474#ifdef FEAT_MULTI_LANG
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001475 should_advance = FALSE;
Bram Moolenaar41375642010-05-16 12:49:27 +02001476#endif
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001477 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001478
1479 /* Skip Browse-style entries, popup menus and separators. */
1480 while (menu != NULL
1481 && ( menu_is_hidden(menu->dname)
1482 || (expand_emenu && menu_is_separator(menu->dname))
1483 || menu_is_tearoff(menu->dname)
1484#ifndef FEAT_BROWSE
1485 || menu->dname[STRLEN(menu->dname) - 1] == '.'
1486#endif
1487 ))
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001488 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001489 menu = menu->next;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001490 if (menu == NULL && !did_alt_menu)
1491 {
1492 menu = expand_menu_alt;
1493 did_alt_menu = TRUE;
1494 }
1495 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001496
1497 if (menu == NULL) /* at end of linked list */
1498 return NULL;
1499
1500 if (menu->modes & expand_modes)
1501 {
1502 if (menu->children != NULL)
1503 {
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001504#ifdef FEAT_MULTI_LANG
1505 if (should_advance)
Bram Moolenaaref9d6aa2011-04-11 16:56:35 +02001506 vim_strncpy(tbuffer, menu->en_dname, TBUFFER_LEN - 2);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001507 else
1508 {
1509#endif
Bram Moolenaaref9d6aa2011-04-11 16:56:35 +02001510 vim_strncpy(tbuffer, menu->dname, TBUFFER_LEN - 2);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001511#ifdef FEAT_MULTI_LANG
1512 if (menu->en_dname == NULL)
1513 should_advance = TRUE;
1514 }
1515#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001516 /* hack on menu separators: use a 'magic' char for the separator
1517 * so that '.' in names gets escaped properly */
1518 STRCAT(tbuffer, "\001");
1519 str = tbuffer;
1520 }
1521 else
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001522#ifdef FEAT_MULTI_LANG
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001523 {
1524 if (should_advance)
1525 str = menu->en_dname;
1526 else
1527 {
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001528#endif
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001529 str = menu->dname;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001530#ifdef FEAT_MULTI_LANG
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001531 if (menu->en_dname == NULL)
1532 should_advance = TRUE;
1533 }
1534 }
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001535#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001536 }
1537 else
1538 str = (char_u *)"";
1539
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001540#ifdef FEAT_MULTI_LANG
1541 if (should_advance)
1542#endif
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001543 {
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001544 /* Advance to next menu entry. */
1545 menu = menu->next;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001546 if (menu == NULL && !did_alt_menu)
1547 {
1548 menu = expand_menu_alt;
1549 did_alt_menu = TRUE;
1550 }
1551 }
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001552
1553#ifdef FEAT_MULTI_LANG
1554 should_advance = !should_advance;
1555#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001556
1557 return str;
1558}
1559#endif /* FEAT_CMDL_COMPL */
1560
1561/*
1562 * Skip over this element of the menu path and return the start of the next
1563 * element. Any \ and ^Vs are removed from the current element.
Bram Moolenaar342337a2005-07-21 21:11:17 +00001564 * "name" may be modified.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001565 */
1566 char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001567menu_name_skip(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001568{
1569 char_u *p;
1570
Bram Moolenaar91acfff2017-03-12 19:22:36 +01001571 for (p = name; *p && *p != '.'; MB_PTR_ADV(p))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001572 {
1573 if (*p == '\\' || *p == Ctrl_V)
1574 {
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001575 STRMOVE(p, p + 1);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001576 if (*p == NUL)
1577 break;
1578 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001579 }
1580 if (*p)
1581 *p++ = NUL;
1582 return p;
1583}
1584
1585/*
1586 * Return TRUE when "name" matches with menu "menu". The name is compared in
1587 * two ways: raw menu name and menu name without '&'. ignore part after a TAB.
1588 */
1589 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001590menu_name_equal(char_u *name, vimmenu_T *menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001591{
Bram Moolenaar41375642010-05-16 12:49:27 +02001592#ifdef FEAT_MULTI_LANG
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001593 if (menu->en_name != NULL
Bram Moolenaard91f7042011-01-04 17:49:32 +01001594 && (menu_namecmp(name, menu->en_name)
1595 || menu_namecmp(name, menu->en_dname)))
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001596 return TRUE;
Bram Moolenaar41375642010-05-16 12:49:27 +02001597#endif
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001598 return menu_namecmp(name, menu->name) || menu_namecmp(name, menu->dname);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001599}
1600
1601 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001602menu_namecmp(char_u *name, char_u *mname)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001603{
1604 int i;
1605
1606 for (i = 0; name[i] != NUL && name[i] != TAB; ++i)
1607 if (name[i] != mname[i])
1608 break;
1609 return ((name[i] == NUL || name[i] == TAB)
1610 && (mname[i] == NUL || mname[i] == TAB));
1611}
1612
1613/*
1614 * Return the modes specified by the given menu command (eg :menu! returns
1615 * MENU_CMDLINE_MODE | MENU_INSERT_MODE).
1616 * If "noremap" is not NULL, then the flag it points to is set according to
1617 * whether the command is a "nore" command.
1618 * If "unmenu" is not NULL, then the flag it points to is set according to
1619 * whether the command is an "unmenu" command.
1620 */
1621 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001622get_menu_cmd_modes(
1623 char_u *cmd,
1624 int forceit, /* Was there a "!" after the command? */
1625 int *noremap,
1626 int *unmenu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001627{
1628 int modes;
1629
1630 switch (*cmd++)
1631 {
1632 case 'v': /* vmenu, vunmenu, vnoremenu */
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001633 modes = MENU_VISUAL_MODE | MENU_SELECT_MODE;
1634 break;
1635 case 'x': /* xmenu, xunmenu, xnoremenu */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001636 modes = MENU_VISUAL_MODE;
1637 break;
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001638 case 's': /* smenu, sunmenu, snoremenu */
1639 modes = MENU_SELECT_MODE;
1640 break;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001641 case 'o': /* omenu */
1642 modes = MENU_OP_PENDING_MODE;
1643 break;
1644 case 'i': /* imenu */
1645 modes = MENU_INSERT_MODE;
1646 break;
1647 case 't':
1648 modes = MENU_TIP_MODE; /* tmenu */
1649 break;
1650 case 'c': /* cmenu */
1651 modes = MENU_CMDLINE_MODE;
1652 break;
1653 case 'a': /* amenu */
1654 modes = MENU_INSERT_MODE | MENU_CMDLINE_MODE | MENU_NORMAL_MODE
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001655 | MENU_VISUAL_MODE | MENU_SELECT_MODE
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00001656 | MENU_OP_PENDING_MODE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001657 break;
1658 case 'n':
1659 if (*cmd != 'o') /* nmenu, not noremenu */
1660 {
1661 modes = MENU_NORMAL_MODE;
1662 break;
1663 }
1664 /* FALLTHROUGH */
1665 default:
1666 --cmd;
1667 if (forceit) /* menu!! */
1668 modes = MENU_INSERT_MODE | MENU_CMDLINE_MODE;
1669 else /* menu */
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001670 modes = MENU_NORMAL_MODE | MENU_VISUAL_MODE | MENU_SELECT_MODE
Bram Moolenaar071d4272004-06-13 20:20:40 +00001671 | MENU_OP_PENDING_MODE;
1672 }
1673
1674 if (noremap != NULL)
1675 *noremap = (*cmd == 'n' ? REMAP_NONE : REMAP_YES);
1676 if (unmenu != NULL)
1677 *unmenu = (*cmd == 'u');
1678 return modes;
1679}
1680
1681/*
1682 * Modify a menu name starting with "PopUp" to include the mode character.
1683 * Returns the name in allocated memory (NULL for failure).
1684 */
1685 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001686popup_mode_name(char_u *name, int idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001687{
1688 char_u *p;
1689 int len = (int)STRLEN(name);
1690
1691 p = vim_strnsave(name, len + 1);
1692 if (p != NULL)
1693 {
1694 mch_memmove(p + 6, p + 5, (size_t)(len - 4));
1695 p[5] = menu_mode_chars[idx];
1696 }
1697 return p;
1698}
1699
1700#if defined(FEAT_GUI) || defined(PROTO)
1701/*
1702 * Return the index into the menu->strings or menu->noremap arrays for the
1703 * current state. Returns MENU_INDEX_INVALID if there is no mapping for the
1704 * given menu in the current mode.
1705 */
1706 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001707get_menu_index(vimmenu_T *menu, int state)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001708{
1709 int idx;
1710
1711 if ((state & INSERT))
1712 idx = MENU_INDEX_INSERT;
1713 else if (state & CMDLINE)
1714 idx = MENU_INDEX_CMDLINE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001715 else if (VIsual_active)
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001716 {
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00001717 if (VIsual_select)
1718 idx = MENU_INDEX_SELECT;
1719 else
1720 idx = MENU_INDEX_VISUAL;
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001721 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001722 else if (state == HITRETURN || state == ASKMORE)
1723 idx = MENU_INDEX_CMDLINE;
1724 else if (finish_op)
1725 idx = MENU_INDEX_OP_PENDING;
1726 else if ((state & NORMAL))
1727 idx = MENU_INDEX_NORMAL;
1728 else
1729 idx = MENU_INDEX_INVALID;
1730
1731 if (idx != MENU_INDEX_INVALID && menu->strings[idx] == NULL)
1732 idx = MENU_INDEX_INVALID;
1733 return idx;
1734}
1735#endif
1736
1737/*
1738 * Duplicate the menu item text and then process to see if a mnemonic key
1739 * and/or accelerator text has been identified.
1740 * Returns a pointer to allocated memory, or NULL for failure.
1741 * If mnemonic != NULL, *mnemonic is set to the character after the first '&'.
1742 * If actext != NULL, *actext is set to the text after the first TAB.
1743 */
1744 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001745menu_text(char_u *str, int *mnemonic, char_u **actext)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001746{
1747 char_u *p;
1748 char_u *text;
1749
1750 /* Locate accelerator text, after the first TAB */
1751 p = vim_strchr(str, TAB);
1752 if (p != NULL)
1753 {
1754 if (actext != NULL)
1755 *actext = vim_strsave(p + 1);
1756 text = vim_strnsave(str, (int)(p - str));
1757 }
1758 else
1759 text = vim_strsave(str);
1760
1761 /* Find mnemonic characters "&a" and reduce "&&" to "&". */
1762 for (p = text; p != NULL; )
1763 {
1764 p = vim_strchr(p, '&');
1765 if (p != NULL)
1766 {
1767 if (p[1] == NUL) /* trailing "&" */
1768 break;
1769 if (mnemonic != NULL && p[1] != '&')
1770#if !defined(__MVS__) || defined(MOTIF390_MNEMONIC_FIXED)
1771 *mnemonic = p[1];
1772#else
1773 {
1774 /*
1775 * Well there is a bug in the Motif libraries on OS390 Unix.
1776 * The mnemonic keys needs to be converted to ASCII values
1777 * first.
1778 * This behavior has been seen in 2.8 and 2.9.
1779 */
1780 char c = p[1];
1781 __etoa_l(&c, 1);
1782 *mnemonic = c;
1783 }
1784#endif
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001785 STRMOVE(p, p + 1);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001786 p = p + 1;
1787 }
1788 }
1789 return text;
1790}
1791
1792/*
1793 * Return TRUE if "name" can be a menu in the MenuBar.
1794 */
1795 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001796menu_is_menubar(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001797{
1798 return (!menu_is_popup(name)
1799 && !menu_is_toolbar(name)
Bram Moolenaar378daf82017-09-23 23:58:28 +02001800 && !menu_is_winbar(name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001801 && *name != MNU_HIDDEN_CHAR);
1802}
1803
1804/*
1805 * Return TRUE if "name" is a popup menu name.
1806 */
1807 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001808menu_is_popup(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001809{
1810 return (STRNCMP(name, "PopUp", 5) == 0);
1811}
1812
1813#if (defined(FEAT_GUI_MOTIF) && (XmVersion <= 1002)) || defined(PROTO)
1814/*
1815 * Return TRUE if "name" is part of a popup menu.
1816 */
1817 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001818menu_is_child_of_popup(vimmenu_T *menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001819{
1820 while (menu->parent != NULL)
1821 menu = menu->parent;
1822 return menu_is_popup(menu->name);
1823}
1824#endif
1825
1826/*
1827 * Return TRUE if "name" is a toolbar menu name.
1828 */
1829 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001830menu_is_toolbar(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001831{
1832 return (STRNCMP(name, "ToolBar", 7) == 0);
1833}
1834
1835/*
1836 * Return TRUE if the name is a menu separator identifier: Starts and ends
1837 * with '-'
1838 */
1839 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001840menu_is_separator(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001841{
1842 return (name[0] == '-' && name[STRLEN(name) - 1] == '-');
1843}
1844
1845/*
1846 * Return TRUE if the menu is hidden: Starts with ']'
1847 */
1848 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001849menu_is_hidden(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001850{
1851 return (name[0] == ']') || (menu_is_popup(name) && name[5] != NUL);
1852}
1853
1854#if defined(FEAT_CMDL_COMPL) \
1855 || (defined(FEAT_GUI_W32) && defined(FEAT_TEAROFF))
1856/*
1857 * Return TRUE if the menu is the tearoff menu.
1858 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001859 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001860menu_is_tearoff(char_u *name UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001861{
1862#ifdef FEAT_GUI
1863 return (STRCMP(name, TEAR_STRING) == 0);
1864#else
1865 return FALSE;
1866#endif
1867}
1868#endif
1869
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001870#if defined(FEAT_GUI) || defined(FEAT_TERM_POPUP_MENU) || defined(PROTO)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001871
1872 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001873get_menu_mode(void)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001874{
Bram Moolenaar071d4272004-06-13 20:20:40 +00001875 if (VIsual_active)
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001876 {
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00001877 if (VIsual_select)
1878 return MENU_INDEX_SELECT;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001879 return MENU_INDEX_VISUAL;
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001880 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001881 if (State & INSERT)
1882 return MENU_INDEX_INSERT;
1883 if ((State & CMDLINE) || State == ASKMORE || State == HITRETURN)
1884 return MENU_INDEX_CMDLINE;
1885 if (finish_op)
1886 return MENU_INDEX_OP_PENDING;
1887 if (State & NORMAL)
1888 return MENU_INDEX_NORMAL;
1889 if (State & LANGMAP) /* must be a "r" command, like Insert mode */
1890 return MENU_INDEX_INSERT;
1891 return MENU_INDEX_INVALID;
1892}
1893
Bram Moolenaar29a2c082018-03-05 21:06:23 +01001894 int
1895get_menu_mode_flag(void)
1896{
1897 int mode = get_menu_mode();
1898
1899 if (mode == MENU_INDEX_INVALID)
1900 return 0;
1901 return 1 << mode;
1902}
1903
Bram Moolenaar071d4272004-06-13 20:20:40 +00001904/*
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001905 * Display the Special "PopUp" menu as a pop-up at the current mouse
1906 * position. The "PopUpn" menu is for Normal mode, "PopUpi" for Insert mode,
1907 * etc.
1908 */
1909 void
1910show_popupmenu(void)
1911{
1912 vimmenu_T *menu;
1913 int mode;
1914
1915 mode = get_menu_mode();
1916 if (mode == MENU_INDEX_INVALID)
1917 return;
1918 mode = menu_mode_chars[mode];
1919
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001920 {
1921 char_u ename[2];
1922
1923 ename[0] = mode;
1924 ename[1] = NUL;
1925 apply_autocmds(EVENT_MENUPOPUP, ename, NULL, FALSE, curbuf);
1926 }
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001927
1928 for (menu = root_menu; menu != NULL; menu = menu->next)
1929 if (STRNCMP("PopUp", menu->name, 5) == 0 && menu->name[5] == mode)
1930 break;
1931
1932 /* Only show a popup when it is defined and has entries */
1933 if (menu != NULL && menu->children != NULL)
1934 {
1935# if defined(FEAT_GUI)
1936 if (gui.in_use)
1937 {
1938 /* Update the menus now, in case the MenuPopup autocommand did
1939 * anything. */
1940 gui_update_menus(0);
1941 gui_mch_show_popupmenu(menu);
1942 }
1943# endif
1944# if defined(FEAT_GUI) && defined(FEAT_TERM_POPUP_MENU)
1945 else
1946# endif
1947# if defined(FEAT_TERM_POPUP_MENU)
1948 pum_show_popupmenu(menu);
1949# endif
1950 }
1951}
1952#endif
1953
1954#if defined(FEAT_GUI) || defined(PROTO)
1955
1956/*
Bram Moolenaar968bbbe2006-08-16 19:41:08 +00001957 * Check that a pointer appears in the menu tree. Used to protect from using
1958 * a menu that was deleted after it was selected but before the event was
1959 * handled.
1960 * Return OK or FAIL. Used recursively.
1961 */
1962 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001963check_menu_pointer(vimmenu_T *root, vimmenu_T *menu_to_check)
Bram Moolenaar968bbbe2006-08-16 19:41:08 +00001964{
1965 vimmenu_T *p;
1966
1967 for (p = root; p != NULL; p = p->next)
1968 if (p == menu_to_check
1969 || (p->children != NULL
1970 && check_menu_pointer(p->children, menu_to_check) == OK))
1971 return OK;
1972 return FAIL;
1973}
1974
1975/*
Bram Moolenaar071d4272004-06-13 20:20:40 +00001976 * After we have started the GUI, then we can create any menus that have been
1977 * defined. This is done once here. add_menu_path() may have already been
1978 * called to define these menus, and may be called again. This function calls
1979 * itself recursively. Should be called at the top level with:
Bram Moolenaara06ecab2016-07-16 14:47:36 +02001980 * gui_create_initial_menus(root_menu);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001981 */
1982 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001983gui_create_initial_menus(vimmenu_T *menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001984{
1985 int idx = 0;
1986
1987 while (menu != NULL)
1988 {
1989 /* Don't add a menu when only a tip was defined. */
1990 if (menu->modes & MENU_ALL_MODES)
1991 {
1992 if (menu->children != NULL)
1993 {
1994 gui_mch_add_menu(menu, idx);
1995 gui_create_initial_menus(menu->children);
1996 }
1997 else
1998 gui_mch_add_menu_item(menu, idx);
1999 }
2000 menu = menu->next;
2001 ++idx;
2002 }
2003}
2004
2005/*
2006 * Used recursively by gui_update_menus (see below)
2007 */
2008 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002009gui_update_menus_recurse(vimmenu_T *menu, int mode)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002010{
2011 int grey;
2012
2013 while (menu)
2014 {
2015 if ((menu->modes & menu->enabled & mode)
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002016# if defined(FEAT_GUI_W32) && defined(FEAT_TEAROFF)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002017 || menu_is_tearoff(menu->dname)
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002018# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002019 )
2020 grey = FALSE;
2021 else
2022 grey = TRUE;
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002023# ifdef FEAT_GUI_ATHENA
Bram Moolenaar071d4272004-06-13 20:20:40 +00002024 /* Hiding menus doesn't work for Athena, it can cause a crash. */
2025 gui_mch_menu_grey(menu, grey);
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002026# else
Bram Moolenaar071d4272004-06-13 20:20:40 +00002027 /* Never hide a toplevel menu, it may make the menubar resize or
2028 * disappear. Same problem for ToolBar items. */
2029 if (vim_strchr(p_go, GO_GREY) != NULL || menu->parent == NULL
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002030# ifdef FEAT_TOOLBAR
Bram Moolenaar071d4272004-06-13 20:20:40 +00002031 || menu_is_toolbar(menu->parent->name)
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002032# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002033 )
2034 gui_mch_menu_grey(menu, grey);
2035 else
2036 gui_mch_menu_hidden(menu, grey);
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002037# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002038 gui_update_menus_recurse(menu->children, mode);
2039 menu = menu->next;
2040 }
2041}
2042
2043/*
2044 * Make sure only the valid menu items appear for this mode. If
2045 * force_menu_update is not TRUE, then we only do this if the mode has changed
2046 * since last time. If "modes" is not 0, then we use these modes instead.
2047 */
2048 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002049gui_update_menus(int modes)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002050{
2051 static int prev_mode = -1;
2052 int mode = 0;
2053
2054 if (modes != 0x0)
2055 mode = modes;
2056 else
Bram Moolenaar29a2c082018-03-05 21:06:23 +01002057 mode = get_menu_mode_flag();
Bram Moolenaar071d4272004-06-13 20:20:40 +00002058
2059 if (force_menu_update || mode != prev_mode)
2060 {
2061 gui_update_menus_recurse(root_menu, mode);
2062 gui_mch_draw_menubar();
2063 prev_mode = mode;
2064 force_menu_update = FALSE;
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002065# ifdef FEAT_GUI_W32
Bram Moolenaar071d4272004-06-13 20:20:40 +00002066 /* This can leave a tearoff as active window - make sure we
2067 * have the focus <negri>*/
2068 gui_mch_activate_window();
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002069# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002070 }
2071}
2072
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002073# if defined(FEAT_GUI_MSWIN) || defined(FEAT_GUI_MOTIF) \
Bram Moolenaar241a8aa2005-12-06 20:04:44 +00002074 || defined(FEAT_GUI_GTK) || defined(FEAT_GUI_PHOTON) || defined(PROTO)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002075/*
2076 * Check if a key is used as a mnemonic for a toplevel menu.
2077 * Case of the key is ignored.
2078 */
2079 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002080gui_is_menu_shortcut(int key)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002081{
2082 vimmenu_T *menu;
2083
2084 if (key < 256)
2085 key = TOLOWER_LOC(key);
2086 for (menu = root_menu; menu != NULL; menu = menu->next)
2087 if (menu->mnemonic == key
2088 || (menu->mnemonic < 256 && TOLOWER_LOC(menu->mnemonic) == key))
2089 return TRUE;
2090 return FALSE;
2091}
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002092# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002093#endif /* FEAT_GUI */
2094
2095#if (defined(FEAT_GUI_W32) && defined(FEAT_TEAROFF)) || defined(PROTO)
2096
2097/*
2098 * Deal with tearoff items that are added like a menu item.
2099 * Currently only for Win32 GUI. Others may follow later.
2100 */
2101
2102 void
2103gui_mch_toggle_tearoffs(int enable)
2104{
2105 int pri_tab[MENUDEPTH + 1];
2106 int i;
2107
2108 if (enable)
2109 {
2110 for (i = 0; i < MENUDEPTH; ++i)
2111 pri_tab[i] = 500;
2112 pri_tab[MENUDEPTH] = -1;
2113 gui_create_tearoffs_recurse(root_menu, (char_u *)"", pri_tab, 0);
2114 }
2115 else
2116 gui_destroy_tearoffs_recurse(root_menu);
2117 s_tearoffs = enable;
2118}
2119
2120/*
2121 * Recursively add tearoff items
2122 */
2123 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002124gui_create_tearoffs_recurse(
2125 vimmenu_T *menu,
2126 const char_u *pname,
2127 int *pri_tab,
2128 int pri_idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002129{
2130 char_u *newpname = NULL;
2131 int len;
2132 char_u *s;
2133 char_u *d;
2134
2135 if (pri_tab[pri_idx + 1] != -1)
2136 ++pri_idx;
2137 while (menu != NULL)
2138 {
2139 if (menu->children != NULL && menu_is_menubar(menu->name))
2140 {
2141 /* Add the menu name to the menu path. Insert a backslash before
2142 * dots (it's used to separate menu names). */
2143 len = (int)STRLEN(pname) + (int)STRLEN(menu->name);
2144 for (s = menu->name; *s; ++s)
2145 if (*s == '.' || *s == '\\')
2146 ++len;
2147 newpname = alloc(len + TEAR_LEN + 2);
2148 if (newpname != NULL)
2149 {
2150 STRCPY(newpname, pname);
2151 d = newpname + STRLEN(newpname);
2152 for (s = menu->name; *s; ++s)
2153 {
2154 if (*s == '.' || *s == '\\')
2155 *d++ = '\\';
2156 *d++ = *s;
2157 }
2158 *d = NUL;
2159
2160 /* check if tearoff already exists */
2161 if (STRCMP(menu->children->name, TEAR_STRING) != 0)
2162 {
2163 gui_add_tearoff(newpname, pri_tab, pri_idx - 1);
2164 *d = NUL; /* remove TEAR_STRING */
2165 }
2166
2167 STRCAT(newpname, ".");
2168 gui_create_tearoffs_recurse(menu->children, newpname,
2169 pri_tab, pri_idx);
2170 vim_free(newpname);
2171 }
2172 }
2173 menu = menu->next;
2174 }
2175}
2176
2177/*
2178 * Add tear-off menu item for a submenu.
2179 * "tearpath" is the menu path, and must have room to add TEAR_STRING.
2180 */
2181 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002182gui_add_tearoff(char_u *tearpath, int *pri_tab, int pri_idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002183{
2184 char_u *tbuf;
2185 int t;
2186 vimmenu_T menuarg;
2187
2188 tbuf = alloc(5 + (unsigned int)STRLEN(tearpath));
2189 if (tbuf != NULL)
2190 {
2191 tbuf[0] = K_SPECIAL;
2192 tbuf[1] = K_SECOND(K_TEAROFF);
2193 tbuf[2] = K_THIRD(K_TEAROFF);
2194 STRCPY(tbuf + 3, tearpath);
2195 STRCAT(tbuf + 3, "\r");
2196
2197 STRCAT(tearpath, ".");
2198 STRCAT(tearpath, TEAR_STRING);
2199
2200 /* Priority of tear-off is always 1 */
2201 t = pri_tab[pri_idx + 1];
2202 pri_tab[pri_idx + 1] = 1;
2203
2204#ifdef FEAT_TOOLBAR
2205 menuarg.iconfile = NULL;
2206 menuarg.iconidx = -1;
2207 menuarg.icon_builtin = FALSE;
2208#endif
2209 menuarg.noremap[0] = REMAP_NONE;
2210 menuarg.silent[0] = TRUE;
2211
2212 menuarg.modes = MENU_ALL_MODES;
2213 add_menu_path(tearpath, &menuarg, pri_tab, tbuf, FALSE);
2214
2215 menuarg.modes = MENU_TIP_MODE;
2216 add_menu_path(tearpath, &menuarg, pri_tab,
2217 (char_u *)_("Tear off this menu"), FALSE);
2218
2219 pri_tab[pri_idx + 1] = t;
2220 vim_free(tbuf);
2221 }
2222}
2223
2224/*
2225 * Recursively destroy tearoff items
2226 */
2227 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002228gui_destroy_tearoffs_recurse(vimmenu_T *menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002229{
2230 while (menu)
2231 {
2232 if (menu->children)
2233 {
2234 /* check if tearoff exists */
2235 if (STRCMP(menu->children->name, TEAR_STRING) == 0)
2236 {
2237 /* Disconnect the item and free the memory */
2238 free_menu(&menu->children);
2239 }
2240 if (menu->children != NULL) /* if not the last one */
2241 gui_destroy_tearoffs_recurse(menu->children);
2242 }
2243 menu = menu->next;
2244 }
2245}
2246
2247#endif /* FEAT_GUI_W32 && FEAT_TEAROFF */
2248
2249/*
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002250 * Execute "menu". Use by ":emenu" and the window toolbar.
2251 * "eap" is NULL for the window toolbar.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002252 */
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002253 void
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002254execute_menu(exarg_T *eap, vimmenu_T *menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002255{
Bram Moolenaar071d4272004-06-13 20:20:40 +00002256 char_u *mode;
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002257 int idx = -1;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002258
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002259 /* Use the Insert mode entry when returning to Insert mode. */
Bram Moolenaar4463f292005-09-25 22:20:24 +00002260 if (restart_edit
2261#ifdef FEAT_EVAL
2262 && !current_SID
2263#endif
2264 )
Bram Moolenaar071d4272004-06-13 20:20:40 +00002265 {
2266 mode = (char_u *)"Insert";
2267 idx = MENU_INDEX_INSERT;
2268 }
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002269 else if (VIsual_active)
2270 {
2271 mode = (char_u *)"Visual";
2272 idx = MENU_INDEX_VISUAL;
2273 }
2274 else if (eap != NULL && eap->addr_count)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002275 {
2276 pos_T tpos;
2277
2278 mode = (char_u *)"Visual";
2279 idx = MENU_INDEX_VISUAL;
2280
2281 /* GEDDES: This is not perfect - but it is a
2282 * quick way of detecting whether we are doing this from a
2283 * selection - see if the range matches up with the visual
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002284 * select start and end. */
Bram Moolenaareddf53b2006-02-27 00:11:10 +00002285 if ((curbuf->b_visual.vi_start.lnum == eap->line1)
2286 && (curbuf->b_visual.vi_end.lnum) == eap->line2)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002287 {
2288 /* Set it up for visual mode - equivalent to gv. */
Bram Moolenaareddf53b2006-02-27 00:11:10 +00002289 VIsual_mode = curbuf->b_visual.vi_mode;
2290 tpos = curbuf->b_visual.vi_end;
2291 curwin->w_cursor = curbuf->b_visual.vi_start;
2292 curwin->w_curswant = curbuf->b_visual.vi_curswant;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002293 }
2294 else
2295 {
2296 /* Set it up for line-wise visual mode */
2297 VIsual_mode = 'V';
2298 curwin->w_cursor.lnum = eap->line1;
2299 curwin->w_cursor.col = 1;
2300 tpos.lnum = eap->line2;
2301 tpos.col = MAXCOL;
Bram Moolenaar261bfea2006-03-01 22:12:31 +00002302#ifdef FEAT_VIRTUALEDIT
2303 tpos.coladd = 0;
2304#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002305 }
2306
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002307 /* Activate visual mode */
Bram Moolenaar071d4272004-06-13 20:20:40 +00002308 VIsual_active = TRUE;
2309 VIsual_reselect = TRUE;
2310 check_cursor();
2311 VIsual = curwin->w_cursor;
2312 curwin->w_cursor = tpos;
2313
2314 check_cursor();
2315
2316 /* Adjust the cursor to make sure it is in the correct pos
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002317 * for exclusive mode */
Bram Moolenaar071d4272004-06-13 20:20:40 +00002318 if (*p_sel == 'e' && gchar_cursor() != NUL)
2319 ++curwin->w_cursor.col;
2320 }
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002321
2322 /* For the WinBar menu always use the Normal mode menu. */
2323 if (idx == -1 || eap == NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002324 {
2325 mode = (char_u *)"Normal";
2326 idx = MENU_INDEX_NORMAL;
2327 }
2328
2329 if (idx != MENU_INDEX_INVALID && menu->strings[idx] != NULL)
2330 {
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002331 /* When executing a script or function execute the commands right now.
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002332 * Also for the window toolbar.
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002333 * Otherwise put them in the typeahead buffer. */
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002334 if (eap == NULL
Bram Moolenaar9c4b4ab2006-12-05 20:29:56 +00002335#ifdef FEAT_EVAL
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002336 || current_SID != 0
2337#endif
2338 )
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002339 {
2340 save_state_T save_state;
2341
2342 ++ex_normal_busy;
2343 if (save_current_state(&save_state))
2344 exec_normal_cmd(menu->strings[idx], menu->noremap[idx],
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002345 menu->silent[idx]);
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002346 restore_current_state(&save_state);
2347 --ex_normal_busy;
2348 }
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002349 else
2350 ins_typebuf(menu->strings[idx], menu->noremap[idx], 0,
Bram Moolenaar071d4272004-06-13 20:20:40 +00002351 TRUE, menu->silent[idx]);
2352 }
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002353 else if (eap != NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002354 EMSG2(_("E335: Menu not defined for %s mode"), mode);
2355}
2356
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002357/*
2358 * Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy and
2359 * execute it.
2360 */
2361 void
2362ex_emenu(exarg_T *eap)
2363{
2364 vimmenu_T *menu;
2365 char_u *name;
2366 char_u *saved_name;
2367 char_u *p;
2368
2369 saved_name = vim_strsave(eap->arg);
2370 if (saved_name == NULL)
2371 return;
2372
2373 menu = *get_root_menu(saved_name);
2374 name = saved_name;
2375 while (*name)
2376 {
2377 /* Find in the menu hierarchy */
2378 p = menu_name_skip(name);
2379
2380 while (menu != NULL)
2381 {
2382 if (menu_name_equal(name, menu))
2383 {
2384 if (*p == NUL && menu->children != NULL)
2385 {
2386 EMSG(_("E333: Menu path must lead to a menu item"));
2387 menu = NULL;
2388 }
2389 else if (*p != NUL && menu->children == NULL)
2390 {
2391 EMSG(_(e_notsubmenu));
2392 menu = NULL;
2393 }
2394 break;
2395 }
2396 menu = menu->next;
2397 }
2398 if (menu == NULL || *p == NUL)
2399 break;
2400 menu = menu->children;
2401 name = p;
2402 }
2403 vim_free(saved_name);
2404 if (menu == NULL)
2405 {
2406 EMSG2(_("E334: Menu not found: %s"), eap->arg);
2407 return;
2408 }
2409
2410 /* Found the menu, so execute. */
2411 execute_menu(eap, menu);
2412}
2413
2414/*
2415 * Handle a click in the window toolbar of "wp" at column "col".
2416 */
2417 void
2418winbar_click(win_T *wp, int col)
2419{
2420 int idx;
2421
2422 if (wp->w_winbar_items == NULL)
2423 return;
2424 for (idx = 0; wp->w_winbar_items[idx].wb_menu != NULL; ++idx)
2425 {
2426 winbar_item_T *item = &wp->w_winbar_items[idx];
2427
2428 if (col >= item->wb_startcol && col <= item->wb_endcol)
2429 {
2430 win_T *save_curwin = NULL;
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002431 pos_T save_visual = VIsual;
2432 int save_visual_active = VIsual_active;
2433 int save_visual_select = VIsual_select;
2434 int save_visual_reselect = VIsual_reselect;
2435 int save_visual_mode = VIsual_mode;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002436
2437 if (wp != curwin)
2438 {
2439 /* Clicking in the window toolbar of a not-current window.
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002440 * Make that window the current one and save Visual mode. */
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002441 save_curwin = curwin;
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002442 VIsual_active = FALSE;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002443 curwin = wp;
2444 curbuf = curwin->w_buffer;
2445 check_cursor();
2446 }
2447
2448 execute_menu(NULL, item->wb_menu);
2449
2450 if (save_curwin != NULL)
2451 {
2452 curwin = save_curwin;
2453 curbuf = curwin->w_buffer;
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002454 VIsual = save_visual;
2455 VIsual_active = save_visual_active;
2456 VIsual_select = save_visual_select;
2457 VIsual_reselect = save_visual_reselect;
2458 VIsual_mode = save_visual_mode;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002459 }
2460 }
2461 }
2462}
2463
2464#if defined(FEAT_GUI_MSWIN) || defined(FEAT_GUI_GTK) \
Bram Moolenaar071d4272004-06-13 20:20:40 +00002465 || defined(FEAT_BEVAL_TIP) || defined(PROTO)
2466/*
2467 * Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy.
2468 */
2469 vimmenu_T *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002470gui_find_menu(char_u *path_name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002471{
2472 vimmenu_T *menu = NULL;
2473 char_u *name;
2474 char_u *saved_name;
2475 char_u *p;
2476
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002477 menu = *get_root_menu(path_name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002478
2479 saved_name = vim_strsave(path_name);
2480 if (saved_name == NULL)
2481 return NULL;
2482
2483 name = saved_name;
2484 while (*name)
2485 {
2486 /* find the end of one dot-separated name and put a NUL at the dot */
2487 p = menu_name_skip(name);
2488
2489 while (menu != NULL)
2490 {
Bram Moolenaard91f7042011-01-04 17:49:32 +01002491 if (menu_name_equal(name, menu))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002492 {
2493 if (menu->children == NULL)
2494 {
2495 /* found a menu item instead of a sub-menu */
2496 if (*p == NUL)
2497 EMSG(_("E336: Menu path must lead to a sub-menu"));
2498 else
2499 EMSG(_(e_notsubmenu));
2500 menu = NULL;
2501 goto theend;
2502 }
2503 if (*p == NUL) /* found a full match */
2504 goto theend;
2505 break;
2506 }
2507 menu = menu->next;
2508 }
2509 if (menu == NULL) /* didn't find it */
2510 break;
2511
2512 /* Found a match, search the sub-menu. */
2513 menu = menu->children;
2514 name = p;
2515 }
2516
2517 if (menu == NULL)
2518 EMSG(_("E337: Menu not found - check menu names"));
2519theend:
2520 vim_free(saved_name);
2521 return menu;
2522}
2523#endif
2524
2525#ifdef FEAT_MULTI_LANG
2526/*
2527 * Translation of menu names. Just a simple lookup table.
2528 */
2529
2530typedef struct
2531{
2532 char_u *from; /* English name */
2533 char_u *from_noamp; /* same, without '&' */
2534 char_u *to; /* translated name */
2535} menutrans_T;
2536
2537static garray_T menutrans_ga = {0, 0, 0, 0, NULL};
2538#endif
2539
2540/*
2541 * ":menutrans".
2542 * This function is also defined without the +multi_lang feature, in which
2543 * case the commands are ignored.
2544 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00002545 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002546ex_menutranslate(exarg_T *eap UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002547{
2548#ifdef FEAT_MULTI_LANG
2549 char_u *arg = eap->arg;
2550 menutrans_T *tp;
2551 int i;
2552 char_u *from, *from_noamp, *to;
2553
2554 if (menutrans_ga.ga_itemsize == 0)
2555 ga_init2(&menutrans_ga, (int)sizeof(menutrans_T), 5);
2556
2557 /*
2558 * ":menutrans clear": clear all translations.
2559 */
2560 if (STRNCMP(arg, "clear", 5) == 0 && ends_excmd(*skipwhite(arg + 5)))
2561 {
2562 tp = (menutrans_T *)menutrans_ga.ga_data;
2563 for (i = 0; i < menutrans_ga.ga_len; ++i)
2564 {
2565 vim_free(tp[i].from);
2566 vim_free(tp[i].from_noamp);
2567 vim_free(tp[i].to);
2568 }
2569 ga_clear(&menutrans_ga);
2570# ifdef FEAT_EVAL
2571 /* Delete all "menutrans_" global variables. */
2572 del_menutrans_vars();
2573# endif
2574 }
2575 else
2576 {
2577 /* ":menutrans from to": add translation */
2578 from = arg;
2579 arg = menu_skip_part(arg);
2580 to = skipwhite(arg);
2581 *arg = NUL;
2582 arg = menu_skip_part(to);
2583 if (arg == to)
2584 EMSG(_(e_invarg));
2585 else
2586 {
2587 if (ga_grow(&menutrans_ga, 1) == OK)
2588 {
2589 tp = (menutrans_T *)menutrans_ga.ga_data;
2590 from = vim_strsave(from);
Bram Moolenaarfc1421e2006-04-20 22:17:20 +00002591 if (from != NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002592 {
Bram Moolenaarfc1421e2006-04-20 22:17:20 +00002593 from_noamp = menu_text(from, NULL, NULL);
2594 to = vim_strnsave(to, (int)(arg - to));
2595 if (from_noamp != NULL && to != NULL)
2596 {
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002597 menu_translate_tab_and_shift(from);
2598 menu_translate_tab_and_shift(to);
2599 menu_unescape_name(from);
2600 menu_unescape_name(to);
Bram Moolenaarfc1421e2006-04-20 22:17:20 +00002601 tp[menutrans_ga.ga_len].from = from;
2602 tp[menutrans_ga.ga_len].from_noamp = from_noamp;
2603 tp[menutrans_ga.ga_len].to = to;
2604 ++menutrans_ga.ga_len;
2605 }
2606 else
2607 {
2608 vim_free(from);
2609 vim_free(from_noamp);
2610 vim_free(to);
2611 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00002612 }
2613 }
2614 }
2615 }
2616#endif
2617}
2618
2619#if defined(FEAT_MULTI_LANG) || defined(FEAT_TOOLBAR)
2620/*
2621 * Find the character just after one part of a menu name.
2622 */
2623 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002624menu_skip_part(char_u *p)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002625{
Bram Moolenaar1c465442017-03-12 20:10:05 +01002626 while (*p != NUL && *p != '.' && !VIM_ISWHITE(*p))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002627 {
2628 if ((*p == '\\' || *p == Ctrl_V) && p[1] != NUL)
2629 ++p;
2630 ++p;
2631 }
2632 return p;
2633}
2634#endif
2635
2636#ifdef FEAT_MULTI_LANG
2637/*
2638 * Lookup part of a menu name in the translations.
2639 * Return a pointer to the translation or NULL if not found.
2640 */
2641 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002642menutrans_lookup(char_u *name, int len)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002643{
2644 menutrans_T *tp = (menutrans_T *)menutrans_ga.ga_data;
2645 int i;
2646 char_u *dname;
2647
2648 for (i = 0; i < menutrans_ga.ga_len; ++i)
Bram Moolenaar11dd8c12017-03-04 20:41:34 +01002649 if (STRNICMP(name, tp[i].from, len) == 0 && tp[i].from[len] == NUL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002650 return tp[i].to;
2651
2652 /* Now try again while ignoring '&' characters. */
2653 i = name[len];
2654 name[len] = NUL;
2655 dname = menu_text(name, NULL, NULL);
2656 name[len] = i;
2657 if (dname != NULL)
2658 {
2659 for (i = 0; i < menutrans_ga.ga_len; ++i)
Bram Moolenaar11dd8c12017-03-04 20:41:34 +01002660 if (STRICMP(dname, tp[i].from_noamp) == 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002661 {
2662 vim_free(dname);
2663 return tp[i].to;
2664 }
2665 vim_free(dname);
2666 }
2667
2668 return NULL;
2669}
Bram Moolenaar071d4272004-06-13 20:20:40 +00002670
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002671/*
2672 * Unescape the name in the translate dictionary table.
2673 */
2674 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002675menu_unescape_name(char_u *name)
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002676{
2677 char_u *p;
2678
Bram Moolenaar91acfff2017-03-12 19:22:36 +01002679 for (p = name; *p && *p != '.'; MB_PTR_ADV(p))
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002680 if (*p == '\\')
2681 STRMOVE(p, p + 1);
2682}
Bram Moolenaar56be9502010-06-06 14:20:26 +02002683#endif /* FEAT_MULTI_LANG */
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002684
2685/*
2686 * Isolate the menu name.
2687 * Skip the menu name, and translate <Tab> into a real TAB.
2688 */
2689 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002690menu_translate_tab_and_shift(char_u *arg_start)
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002691{
2692 char_u *arg = arg_start;
2693
Bram Moolenaar1c465442017-03-12 20:10:05 +01002694 while (*arg && !VIM_ISWHITE(*arg))
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002695 {
2696 if ((*arg == '\\' || *arg == Ctrl_V) && arg[1] != NUL)
2697 arg++;
2698 else if (STRNICMP(arg, "<TAB>", 5) == 0)
2699 {
2700 *arg = TAB;
2701 STRMOVE(arg + 1, arg + 5);
2702 }
2703 arg++;
2704 }
2705 if (*arg != NUL)
2706 *arg++ = NUL;
2707 arg = skipwhite(arg);
2708
2709 return arg;
2710}
2711
Bram Moolenaar071d4272004-06-13 20:20:40 +00002712#endif /* FEAT_MENU */