blob: 107cabce6c472e5a62389671aa3fdee43647f535 [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
1894/*
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001895 * Display the Special "PopUp" menu as a pop-up at the current mouse
1896 * position. The "PopUpn" menu is for Normal mode, "PopUpi" for Insert mode,
1897 * etc.
1898 */
1899 void
1900show_popupmenu(void)
1901{
1902 vimmenu_T *menu;
1903 int mode;
1904
1905 mode = get_menu_mode();
1906 if (mode == MENU_INDEX_INVALID)
1907 return;
1908 mode = menu_mode_chars[mode];
1909
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001910 {
1911 char_u ename[2];
1912
1913 ename[0] = mode;
1914 ename[1] = NUL;
1915 apply_autocmds(EVENT_MENUPOPUP, ename, NULL, FALSE, curbuf);
1916 }
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001917
1918 for (menu = root_menu; menu != NULL; menu = menu->next)
1919 if (STRNCMP("PopUp", menu->name, 5) == 0 && menu->name[5] == mode)
1920 break;
1921
1922 /* Only show a popup when it is defined and has entries */
1923 if (menu != NULL && menu->children != NULL)
1924 {
1925# if defined(FEAT_GUI)
1926 if (gui.in_use)
1927 {
1928 /* Update the menus now, in case the MenuPopup autocommand did
1929 * anything. */
1930 gui_update_menus(0);
1931 gui_mch_show_popupmenu(menu);
1932 }
1933# endif
1934# if defined(FEAT_GUI) && defined(FEAT_TERM_POPUP_MENU)
1935 else
1936# endif
1937# if defined(FEAT_TERM_POPUP_MENU)
1938 pum_show_popupmenu(menu);
1939# endif
1940 }
1941}
1942#endif
1943
1944#if defined(FEAT_GUI) || defined(PROTO)
1945
1946/*
Bram Moolenaar968bbbe2006-08-16 19:41:08 +00001947 * Check that a pointer appears in the menu tree. Used to protect from using
1948 * a menu that was deleted after it was selected but before the event was
1949 * handled.
1950 * Return OK or FAIL. Used recursively.
1951 */
1952 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001953check_menu_pointer(vimmenu_T *root, vimmenu_T *menu_to_check)
Bram Moolenaar968bbbe2006-08-16 19:41:08 +00001954{
1955 vimmenu_T *p;
1956
1957 for (p = root; p != NULL; p = p->next)
1958 if (p == menu_to_check
1959 || (p->children != NULL
1960 && check_menu_pointer(p->children, menu_to_check) == OK))
1961 return OK;
1962 return FAIL;
1963}
1964
1965/*
Bram Moolenaar071d4272004-06-13 20:20:40 +00001966 * After we have started the GUI, then we can create any menus that have been
1967 * defined. This is done once here. add_menu_path() may have already been
1968 * called to define these menus, and may be called again. This function calls
1969 * itself recursively. Should be called at the top level with:
Bram Moolenaara06ecab2016-07-16 14:47:36 +02001970 * gui_create_initial_menus(root_menu);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001971 */
1972 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001973gui_create_initial_menus(vimmenu_T *menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001974{
1975 int idx = 0;
1976
1977 while (menu != NULL)
1978 {
1979 /* Don't add a menu when only a tip was defined. */
1980 if (menu->modes & MENU_ALL_MODES)
1981 {
1982 if (menu->children != NULL)
1983 {
1984 gui_mch_add_menu(menu, idx);
1985 gui_create_initial_menus(menu->children);
1986 }
1987 else
1988 gui_mch_add_menu_item(menu, idx);
1989 }
1990 menu = menu->next;
1991 ++idx;
1992 }
1993}
1994
1995/*
1996 * Used recursively by gui_update_menus (see below)
1997 */
1998 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001999gui_update_menus_recurse(vimmenu_T *menu, int mode)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002000{
2001 int grey;
2002
2003 while (menu)
2004 {
2005 if ((menu->modes & menu->enabled & mode)
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002006# if defined(FEAT_GUI_W32) && defined(FEAT_TEAROFF)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002007 || menu_is_tearoff(menu->dname)
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002008# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002009 )
2010 grey = FALSE;
2011 else
2012 grey = TRUE;
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002013# ifdef FEAT_GUI_ATHENA
Bram Moolenaar071d4272004-06-13 20:20:40 +00002014 /* Hiding menus doesn't work for Athena, it can cause a crash. */
2015 gui_mch_menu_grey(menu, grey);
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002016# else
Bram Moolenaar071d4272004-06-13 20:20:40 +00002017 /* Never hide a toplevel menu, it may make the menubar resize or
2018 * disappear. Same problem for ToolBar items. */
2019 if (vim_strchr(p_go, GO_GREY) != NULL || menu->parent == NULL
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002020# ifdef FEAT_TOOLBAR
Bram Moolenaar071d4272004-06-13 20:20:40 +00002021 || menu_is_toolbar(menu->parent->name)
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002022# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002023 )
2024 gui_mch_menu_grey(menu, grey);
2025 else
2026 gui_mch_menu_hidden(menu, grey);
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002027# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002028 gui_update_menus_recurse(menu->children, mode);
2029 menu = menu->next;
2030 }
2031}
2032
2033/*
2034 * Make sure only the valid menu items appear for this mode. If
2035 * force_menu_update is not TRUE, then we only do this if the mode has changed
2036 * since last time. If "modes" is not 0, then we use these modes instead.
2037 */
2038 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002039gui_update_menus(int modes)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002040{
2041 static int prev_mode = -1;
2042 int mode = 0;
2043
2044 if (modes != 0x0)
2045 mode = modes;
2046 else
2047 {
2048 mode = get_menu_mode();
2049 if (mode == MENU_INDEX_INVALID)
2050 mode = 0;
2051 else
2052 mode = (1 << mode);
2053 }
2054
2055 if (force_menu_update || mode != prev_mode)
2056 {
2057 gui_update_menus_recurse(root_menu, mode);
2058 gui_mch_draw_menubar();
2059 prev_mode = mode;
2060 force_menu_update = FALSE;
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002061# ifdef FEAT_GUI_W32
Bram Moolenaar071d4272004-06-13 20:20:40 +00002062 /* This can leave a tearoff as active window - make sure we
2063 * have the focus <negri>*/
2064 gui_mch_activate_window();
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002065# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002066 }
2067}
2068
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002069# if defined(FEAT_GUI_MSWIN) || defined(FEAT_GUI_MOTIF) \
Bram Moolenaar241a8aa2005-12-06 20:04:44 +00002070 || defined(FEAT_GUI_GTK) || defined(FEAT_GUI_PHOTON) || defined(PROTO)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002071/*
2072 * Check if a key is used as a mnemonic for a toplevel menu.
2073 * Case of the key is ignored.
2074 */
2075 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002076gui_is_menu_shortcut(int key)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002077{
2078 vimmenu_T *menu;
2079
2080 if (key < 256)
2081 key = TOLOWER_LOC(key);
2082 for (menu = root_menu; menu != NULL; menu = menu->next)
2083 if (menu->mnemonic == key
2084 || (menu->mnemonic < 256 && TOLOWER_LOC(menu->mnemonic) == key))
2085 return TRUE;
2086 return FALSE;
2087}
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002088# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002089#endif /* FEAT_GUI */
2090
2091#if (defined(FEAT_GUI_W32) && defined(FEAT_TEAROFF)) || defined(PROTO)
2092
2093/*
2094 * Deal with tearoff items that are added like a menu item.
2095 * Currently only for Win32 GUI. Others may follow later.
2096 */
2097
2098 void
2099gui_mch_toggle_tearoffs(int enable)
2100{
2101 int pri_tab[MENUDEPTH + 1];
2102 int i;
2103
2104 if (enable)
2105 {
2106 for (i = 0; i < MENUDEPTH; ++i)
2107 pri_tab[i] = 500;
2108 pri_tab[MENUDEPTH] = -1;
2109 gui_create_tearoffs_recurse(root_menu, (char_u *)"", pri_tab, 0);
2110 }
2111 else
2112 gui_destroy_tearoffs_recurse(root_menu);
2113 s_tearoffs = enable;
2114}
2115
2116/*
2117 * Recursively add tearoff items
2118 */
2119 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002120gui_create_tearoffs_recurse(
2121 vimmenu_T *menu,
2122 const char_u *pname,
2123 int *pri_tab,
2124 int pri_idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002125{
2126 char_u *newpname = NULL;
2127 int len;
2128 char_u *s;
2129 char_u *d;
2130
2131 if (pri_tab[pri_idx + 1] != -1)
2132 ++pri_idx;
2133 while (menu != NULL)
2134 {
2135 if (menu->children != NULL && menu_is_menubar(menu->name))
2136 {
2137 /* Add the menu name to the menu path. Insert a backslash before
2138 * dots (it's used to separate menu names). */
2139 len = (int)STRLEN(pname) + (int)STRLEN(menu->name);
2140 for (s = menu->name; *s; ++s)
2141 if (*s == '.' || *s == '\\')
2142 ++len;
2143 newpname = alloc(len + TEAR_LEN + 2);
2144 if (newpname != NULL)
2145 {
2146 STRCPY(newpname, pname);
2147 d = newpname + STRLEN(newpname);
2148 for (s = menu->name; *s; ++s)
2149 {
2150 if (*s == '.' || *s == '\\')
2151 *d++ = '\\';
2152 *d++ = *s;
2153 }
2154 *d = NUL;
2155
2156 /* check if tearoff already exists */
2157 if (STRCMP(menu->children->name, TEAR_STRING) != 0)
2158 {
2159 gui_add_tearoff(newpname, pri_tab, pri_idx - 1);
2160 *d = NUL; /* remove TEAR_STRING */
2161 }
2162
2163 STRCAT(newpname, ".");
2164 gui_create_tearoffs_recurse(menu->children, newpname,
2165 pri_tab, pri_idx);
2166 vim_free(newpname);
2167 }
2168 }
2169 menu = menu->next;
2170 }
2171}
2172
2173/*
2174 * Add tear-off menu item for a submenu.
2175 * "tearpath" is the menu path, and must have room to add TEAR_STRING.
2176 */
2177 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002178gui_add_tearoff(char_u *tearpath, int *pri_tab, int pri_idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002179{
2180 char_u *tbuf;
2181 int t;
2182 vimmenu_T menuarg;
2183
2184 tbuf = alloc(5 + (unsigned int)STRLEN(tearpath));
2185 if (tbuf != NULL)
2186 {
2187 tbuf[0] = K_SPECIAL;
2188 tbuf[1] = K_SECOND(K_TEAROFF);
2189 tbuf[2] = K_THIRD(K_TEAROFF);
2190 STRCPY(tbuf + 3, tearpath);
2191 STRCAT(tbuf + 3, "\r");
2192
2193 STRCAT(tearpath, ".");
2194 STRCAT(tearpath, TEAR_STRING);
2195
2196 /* Priority of tear-off is always 1 */
2197 t = pri_tab[pri_idx + 1];
2198 pri_tab[pri_idx + 1] = 1;
2199
2200#ifdef FEAT_TOOLBAR
2201 menuarg.iconfile = NULL;
2202 menuarg.iconidx = -1;
2203 menuarg.icon_builtin = FALSE;
2204#endif
2205 menuarg.noremap[0] = REMAP_NONE;
2206 menuarg.silent[0] = TRUE;
2207
2208 menuarg.modes = MENU_ALL_MODES;
2209 add_menu_path(tearpath, &menuarg, pri_tab, tbuf, FALSE);
2210
2211 menuarg.modes = MENU_TIP_MODE;
2212 add_menu_path(tearpath, &menuarg, pri_tab,
2213 (char_u *)_("Tear off this menu"), FALSE);
2214
2215 pri_tab[pri_idx + 1] = t;
2216 vim_free(tbuf);
2217 }
2218}
2219
2220/*
2221 * Recursively destroy tearoff items
2222 */
2223 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002224gui_destroy_tearoffs_recurse(vimmenu_T *menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002225{
2226 while (menu)
2227 {
2228 if (menu->children)
2229 {
2230 /* check if tearoff exists */
2231 if (STRCMP(menu->children->name, TEAR_STRING) == 0)
2232 {
2233 /* Disconnect the item and free the memory */
2234 free_menu(&menu->children);
2235 }
2236 if (menu->children != NULL) /* if not the last one */
2237 gui_destroy_tearoffs_recurse(menu->children);
2238 }
2239 menu = menu->next;
2240 }
2241}
2242
2243#endif /* FEAT_GUI_W32 && FEAT_TEAROFF */
2244
2245/*
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002246 * Execute "menu". Use by ":emenu" and the window toolbar.
2247 * "eap" is NULL for the window toolbar.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002248 */
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002249 void
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002250execute_menu(exarg_T *eap, vimmenu_T *menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002251{
Bram Moolenaar071d4272004-06-13 20:20:40 +00002252 char_u *mode;
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002253 int idx = -1;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002254
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002255 /* Use the Insert mode entry when returning to Insert mode. */
Bram Moolenaar4463f292005-09-25 22:20:24 +00002256 if (restart_edit
2257#ifdef FEAT_EVAL
2258 && !current_SID
2259#endif
2260 )
Bram Moolenaar071d4272004-06-13 20:20:40 +00002261 {
2262 mode = (char_u *)"Insert";
2263 idx = MENU_INDEX_INSERT;
2264 }
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002265 else if (VIsual_active)
2266 {
2267 mode = (char_u *)"Visual";
2268 idx = MENU_INDEX_VISUAL;
2269 }
2270 else if (eap != NULL && eap->addr_count)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002271 {
2272 pos_T tpos;
2273
2274 mode = (char_u *)"Visual";
2275 idx = MENU_INDEX_VISUAL;
2276
2277 /* GEDDES: This is not perfect - but it is a
2278 * quick way of detecting whether we are doing this from a
2279 * selection - see if the range matches up with the visual
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002280 * select start and end. */
Bram Moolenaareddf53b2006-02-27 00:11:10 +00002281 if ((curbuf->b_visual.vi_start.lnum == eap->line1)
2282 && (curbuf->b_visual.vi_end.lnum) == eap->line2)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002283 {
2284 /* Set it up for visual mode - equivalent to gv. */
Bram Moolenaareddf53b2006-02-27 00:11:10 +00002285 VIsual_mode = curbuf->b_visual.vi_mode;
2286 tpos = curbuf->b_visual.vi_end;
2287 curwin->w_cursor = curbuf->b_visual.vi_start;
2288 curwin->w_curswant = curbuf->b_visual.vi_curswant;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002289 }
2290 else
2291 {
2292 /* Set it up for line-wise visual mode */
2293 VIsual_mode = 'V';
2294 curwin->w_cursor.lnum = eap->line1;
2295 curwin->w_cursor.col = 1;
2296 tpos.lnum = eap->line2;
2297 tpos.col = MAXCOL;
Bram Moolenaar261bfea2006-03-01 22:12:31 +00002298#ifdef FEAT_VIRTUALEDIT
2299 tpos.coladd = 0;
2300#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002301 }
2302
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002303 /* Activate visual mode */
Bram Moolenaar071d4272004-06-13 20:20:40 +00002304 VIsual_active = TRUE;
2305 VIsual_reselect = TRUE;
2306 check_cursor();
2307 VIsual = curwin->w_cursor;
2308 curwin->w_cursor = tpos;
2309
2310 check_cursor();
2311
2312 /* Adjust the cursor to make sure it is in the correct pos
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002313 * for exclusive mode */
Bram Moolenaar071d4272004-06-13 20:20:40 +00002314 if (*p_sel == 'e' && gchar_cursor() != NUL)
2315 ++curwin->w_cursor.col;
2316 }
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002317
2318 /* For the WinBar menu always use the Normal mode menu. */
2319 if (idx == -1 || eap == NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002320 {
2321 mode = (char_u *)"Normal";
2322 idx = MENU_INDEX_NORMAL;
2323 }
2324
2325 if (idx != MENU_INDEX_INVALID && menu->strings[idx] != NULL)
2326 {
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002327 /* When executing a script or function execute the commands right now.
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002328 * Also for the window toolbar.
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002329 * Otherwise put them in the typeahead buffer. */
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002330 if (eap == NULL
Bram Moolenaar9c4b4ab2006-12-05 20:29:56 +00002331#ifdef FEAT_EVAL
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002332 || current_SID != 0
2333#endif
2334 )
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002335 {
2336 save_state_T save_state;
2337
2338 ++ex_normal_busy;
2339 if (save_current_state(&save_state))
2340 exec_normal_cmd(menu->strings[idx], menu->noremap[idx],
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002341 menu->silent[idx]);
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002342 restore_current_state(&save_state);
2343 --ex_normal_busy;
2344 }
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002345 else
2346 ins_typebuf(menu->strings[idx], menu->noremap[idx], 0,
Bram Moolenaar071d4272004-06-13 20:20:40 +00002347 TRUE, menu->silent[idx]);
2348 }
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002349 else if (eap != NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002350 EMSG2(_("E335: Menu not defined for %s mode"), mode);
2351}
2352
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002353/*
2354 * Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy and
2355 * execute it.
2356 */
2357 void
2358ex_emenu(exarg_T *eap)
2359{
2360 vimmenu_T *menu;
2361 char_u *name;
2362 char_u *saved_name;
2363 char_u *p;
2364
2365 saved_name = vim_strsave(eap->arg);
2366 if (saved_name == NULL)
2367 return;
2368
2369 menu = *get_root_menu(saved_name);
2370 name = saved_name;
2371 while (*name)
2372 {
2373 /* Find in the menu hierarchy */
2374 p = menu_name_skip(name);
2375
2376 while (menu != NULL)
2377 {
2378 if (menu_name_equal(name, menu))
2379 {
2380 if (*p == NUL && menu->children != NULL)
2381 {
2382 EMSG(_("E333: Menu path must lead to a menu item"));
2383 menu = NULL;
2384 }
2385 else if (*p != NUL && menu->children == NULL)
2386 {
2387 EMSG(_(e_notsubmenu));
2388 menu = NULL;
2389 }
2390 break;
2391 }
2392 menu = menu->next;
2393 }
2394 if (menu == NULL || *p == NUL)
2395 break;
2396 menu = menu->children;
2397 name = p;
2398 }
2399 vim_free(saved_name);
2400 if (menu == NULL)
2401 {
2402 EMSG2(_("E334: Menu not found: %s"), eap->arg);
2403 return;
2404 }
2405
2406 /* Found the menu, so execute. */
2407 execute_menu(eap, menu);
2408}
2409
2410/*
2411 * Handle a click in the window toolbar of "wp" at column "col".
2412 */
2413 void
2414winbar_click(win_T *wp, int col)
2415{
2416 int idx;
2417
2418 if (wp->w_winbar_items == NULL)
2419 return;
2420 for (idx = 0; wp->w_winbar_items[idx].wb_menu != NULL; ++idx)
2421 {
2422 winbar_item_T *item = &wp->w_winbar_items[idx];
2423
2424 if (col >= item->wb_startcol && col <= item->wb_endcol)
2425 {
2426 win_T *save_curwin = NULL;
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002427 pos_T save_visual = VIsual;
2428 int save_visual_active = VIsual_active;
2429 int save_visual_select = VIsual_select;
2430 int save_visual_reselect = VIsual_reselect;
2431 int save_visual_mode = VIsual_mode;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002432
2433 if (wp != curwin)
2434 {
2435 /* Clicking in the window toolbar of a not-current window.
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002436 * Make that window the current one and save Visual mode. */
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002437 save_curwin = curwin;
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002438 VIsual_active = FALSE;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002439 curwin = wp;
2440 curbuf = curwin->w_buffer;
2441 check_cursor();
2442 }
2443
2444 execute_menu(NULL, item->wb_menu);
2445
2446 if (save_curwin != NULL)
2447 {
2448 curwin = save_curwin;
2449 curbuf = curwin->w_buffer;
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002450 VIsual = save_visual;
2451 VIsual_active = save_visual_active;
2452 VIsual_select = save_visual_select;
2453 VIsual_reselect = save_visual_reselect;
2454 VIsual_mode = save_visual_mode;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002455 }
2456 }
2457 }
2458}
2459
2460#if defined(FEAT_GUI_MSWIN) || defined(FEAT_GUI_GTK) \
Bram Moolenaar071d4272004-06-13 20:20:40 +00002461 || defined(FEAT_BEVAL_TIP) || defined(PROTO)
2462/*
2463 * Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy.
2464 */
2465 vimmenu_T *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002466gui_find_menu(char_u *path_name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002467{
2468 vimmenu_T *menu = NULL;
2469 char_u *name;
2470 char_u *saved_name;
2471 char_u *p;
2472
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002473 menu = *get_root_menu(path_name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002474
2475 saved_name = vim_strsave(path_name);
2476 if (saved_name == NULL)
2477 return NULL;
2478
2479 name = saved_name;
2480 while (*name)
2481 {
2482 /* find the end of one dot-separated name and put a NUL at the dot */
2483 p = menu_name_skip(name);
2484
2485 while (menu != NULL)
2486 {
Bram Moolenaard91f7042011-01-04 17:49:32 +01002487 if (menu_name_equal(name, menu))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002488 {
2489 if (menu->children == NULL)
2490 {
2491 /* found a menu item instead of a sub-menu */
2492 if (*p == NUL)
2493 EMSG(_("E336: Menu path must lead to a sub-menu"));
2494 else
2495 EMSG(_(e_notsubmenu));
2496 menu = NULL;
2497 goto theend;
2498 }
2499 if (*p == NUL) /* found a full match */
2500 goto theend;
2501 break;
2502 }
2503 menu = menu->next;
2504 }
2505 if (menu == NULL) /* didn't find it */
2506 break;
2507
2508 /* Found a match, search the sub-menu. */
2509 menu = menu->children;
2510 name = p;
2511 }
2512
2513 if (menu == NULL)
2514 EMSG(_("E337: Menu not found - check menu names"));
2515theend:
2516 vim_free(saved_name);
2517 return menu;
2518}
2519#endif
2520
2521#ifdef FEAT_MULTI_LANG
2522/*
2523 * Translation of menu names. Just a simple lookup table.
2524 */
2525
2526typedef struct
2527{
2528 char_u *from; /* English name */
2529 char_u *from_noamp; /* same, without '&' */
2530 char_u *to; /* translated name */
2531} menutrans_T;
2532
2533static garray_T menutrans_ga = {0, 0, 0, 0, NULL};
2534#endif
2535
2536/*
2537 * ":menutrans".
2538 * This function is also defined without the +multi_lang feature, in which
2539 * case the commands are ignored.
2540 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00002541 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002542ex_menutranslate(exarg_T *eap UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002543{
2544#ifdef FEAT_MULTI_LANG
2545 char_u *arg = eap->arg;
2546 menutrans_T *tp;
2547 int i;
2548 char_u *from, *from_noamp, *to;
2549
2550 if (menutrans_ga.ga_itemsize == 0)
2551 ga_init2(&menutrans_ga, (int)sizeof(menutrans_T), 5);
2552
2553 /*
2554 * ":menutrans clear": clear all translations.
2555 */
2556 if (STRNCMP(arg, "clear", 5) == 0 && ends_excmd(*skipwhite(arg + 5)))
2557 {
2558 tp = (menutrans_T *)menutrans_ga.ga_data;
2559 for (i = 0; i < menutrans_ga.ga_len; ++i)
2560 {
2561 vim_free(tp[i].from);
2562 vim_free(tp[i].from_noamp);
2563 vim_free(tp[i].to);
2564 }
2565 ga_clear(&menutrans_ga);
2566# ifdef FEAT_EVAL
2567 /* Delete all "menutrans_" global variables. */
2568 del_menutrans_vars();
2569# endif
2570 }
2571 else
2572 {
2573 /* ":menutrans from to": add translation */
2574 from = arg;
2575 arg = menu_skip_part(arg);
2576 to = skipwhite(arg);
2577 *arg = NUL;
2578 arg = menu_skip_part(to);
2579 if (arg == to)
2580 EMSG(_(e_invarg));
2581 else
2582 {
2583 if (ga_grow(&menutrans_ga, 1) == OK)
2584 {
2585 tp = (menutrans_T *)menutrans_ga.ga_data;
2586 from = vim_strsave(from);
Bram Moolenaarfc1421e2006-04-20 22:17:20 +00002587 if (from != NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002588 {
Bram Moolenaarfc1421e2006-04-20 22:17:20 +00002589 from_noamp = menu_text(from, NULL, NULL);
2590 to = vim_strnsave(to, (int)(arg - to));
2591 if (from_noamp != NULL && to != NULL)
2592 {
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002593 menu_translate_tab_and_shift(from);
2594 menu_translate_tab_and_shift(to);
2595 menu_unescape_name(from);
2596 menu_unescape_name(to);
Bram Moolenaarfc1421e2006-04-20 22:17:20 +00002597 tp[menutrans_ga.ga_len].from = from;
2598 tp[menutrans_ga.ga_len].from_noamp = from_noamp;
2599 tp[menutrans_ga.ga_len].to = to;
2600 ++menutrans_ga.ga_len;
2601 }
2602 else
2603 {
2604 vim_free(from);
2605 vim_free(from_noamp);
2606 vim_free(to);
2607 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00002608 }
2609 }
2610 }
2611 }
2612#endif
2613}
2614
2615#if defined(FEAT_MULTI_LANG) || defined(FEAT_TOOLBAR)
2616/*
2617 * Find the character just after one part of a menu name.
2618 */
2619 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002620menu_skip_part(char_u *p)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002621{
Bram Moolenaar1c465442017-03-12 20:10:05 +01002622 while (*p != NUL && *p != '.' && !VIM_ISWHITE(*p))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002623 {
2624 if ((*p == '\\' || *p == Ctrl_V) && p[1] != NUL)
2625 ++p;
2626 ++p;
2627 }
2628 return p;
2629}
2630#endif
2631
2632#ifdef FEAT_MULTI_LANG
2633/*
2634 * Lookup part of a menu name in the translations.
2635 * Return a pointer to the translation or NULL if not found.
2636 */
2637 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002638menutrans_lookup(char_u *name, int len)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002639{
2640 menutrans_T *tp = (menutrans_T *)menutrans_ga.ga_data;
2641 int i;
2642 char_u *dname;
2643
2644 for (i = 0; i < menutrans_ga.ga_len; ++i)
Bram Moolenaar11dd8c12017-03-04 20:41:34 +01002645 if (STRNICMP(name, tp[i].from, len) == 0 && tp[i].from[len] == NUL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002646 return tp[i].to;
2647
2648 /* Now try again while ignoring '&' characters. */
2649 i = name[len];
2650 name[len] = NUL;
2651 dname = menu_text(name, NULL, NULL);
2652 name[len] = i;
2653 if (dname != NULL)
2654 {
2655 for (i = 0; i < menutrans_ga.ga_len; ++i)
Bram Moolenaar11dd8c12017-03-04 20:41:34 +01002656 if (STRICMP(dname, tp[i].from_noamp) == 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002657 {
2658 vim_free(dname);
2659 return tp[i].to;
2660 }
2661 vim_free(dname);
2662 }
2663
2664 return NULL;
2665}
Bram Moolenaar071d4272004-06-13 20:20:40 +00002666
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002667/*
2668 * Unescape the name in the translate dictionary table.
2669 */
2670 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002671menu_unescape_name(char_u *name)
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002672{
2673 char_u *p;
2674
Bram Moolenaar91acfff2017-03-12 19:22:36 +01002675 for (p = name; *p && *p != '.'; MB_PTR_ADV(p))
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002676 if (*p == '\\')
2677 STRMOVE(p, p + 1);
2678}
Bram Moolenaar56be9502010-06-06 14:20:26 +02002679#endif /* FEAT_MULTI_LANG */
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002680
2681/*
2682 * Isolate the menu name.
2683 * Skip the menu name, and translate <Tab> into a real TAB.
2684 */
2685 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002686menu_translate_tab_and_shift(char_u *arg_start)
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002687{
2688 char_u *arg = arg_start;
2689
Bram Moolenaar1c465442017-03-12 20:10:05 +01002690 while (*arg && !VIM_ISWHITE(*arg))
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002691 {
2692 if ((*arg == '\\' || *arg == Ctrl_V) && arg[1] != NUL)
2693 arg++;
2694 else if (STRNICMP(arg, "<TAB>", 5) == 0)
2695 {
2696 *arg = TAB;
2697 STRMOVE(arg + 1, arg + 5);
2698 }
2699 arg++;
2700 }
2701 if (*arg != NUL)
2702 *arg++ = NUL;
2703 arg = skipwhite(arg);
2704
2705 return arg;
2706}
2707
Bram Moolenaar071d4272004-06-13 20:20:40 +00002708#endif /* FEAT_MENU */