blob: 310cf5928f0e73e32e793d9873e7f9bf42f90bb1 [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#ifdef FEAT_GUI
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010038static int get_menu_mode(void);
39static void gui_update_menus_recurse(vimmenu_T *, int);
Bram Moolenaar071d4272004-06-13 20:20:40 +000040#endif
41
42#if defined(FEAT_GUI_W32) && defined(FEAT_TEAROFF)
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010043static void gui_create_tearoffs_recurse(vimmenu_T *menu, const char_u *pname, int *pri_tab, int pri_idx);
44static void gui_add_tearoff(char_u *tearpath, int *pri_tab, int pri_idx);
45static void gui_destroy_tearoffs_recurse(vimmenu_T *menu);
Bram Moolenaar071d4272004-06-13 20:20:40 +000046static int s_tearoffs = FALSE;
47#endif
48
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010049static int menu_is_hidden(char_u *name);
Bram Moolenaar071d4272004-06-13 20:20:40 +000050#if defined(FEAT_CMDL_COMPL) || (defined(FEAT_GUI_W32) && defined(FEAT_TEAROFF))
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010051static int menu_is_tearoff(char_u *name);
Bram Moolenaar071d4272004-06-13 20:20:40 +000052#endif
53
54#if defined(FEAT_MULTI_LANG) || defined(FEAT_TOOLBAR)
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010055static char_u *menu_skip_part(char_u *p);
Bram Moolenaar071d4272004-06-13 20:20:40 +000056#endif
57#ifdef FEAT_MULTI_LANG
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010058static char_u *menutrans_lookup(char_u *name, int len);
59static void menu_unescape_name(char_u *p);
Bram Moolenaar071d4272004-06-13 20:20:40 +000060#endif
61
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010062static char_u *menu_translate_tab_and_shift(char_u *arg_start);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +020063
Bram Moolenaar071d4272004-06-13 20:20:40 +000064/* The character for each menu mode */
Bram Moolenaareb3593b2006-04-22 22:33:57 +000065static char_u menu_mode_chars[] = {'n', 'v', 's', 'o', 'i', 'c', 't'};
Bram Moolenaar071d4272004-06-13 20:20:40 +000066
67static char_u e_notsubmenu[] = N_("E327: Part of menu-item path is not sub-menu");
68static char_u e_othermode[] = N_("E328: Menu only exists in another mode");
Bram Moolenaar342337a2005-07-21 21:11:17 +000069static char_u e_nomenu[] = N_("E329: No menu \"%s\"");
Bram Moolenaar071d4272004-06-13 20:20:40 +000070
71#ifdef FEAT_TOOLBAR
72static const char *toolbar_names[] =
73{
74 /* 0 */ "New", "Open", "Save", "Undo", "Redo",
75 /* 5 */ "Cut", "Copy", "Paste", "Print", "Help",
76 /* 10 */ "Find", "SaveAll", "SaveSesn", "NewSesn", "LoadSesn",
77 /* 15 */ "RunScript", "Replace", "WinClose", "WinMax", "WinMin",
78 /* 20 */ "WinSplit", "Shell", "FindPrev", "FindNext", "FindHelp",
79 /* 25 */ "Make", "TagJump", "RunCtags", "WinVSplit", "WinMaxWidth",
80 /* 30 */ "WinMinWidth", "Exit"
81};
82# define TOOLBAR_NAME_COUNT (sizeof(toolbar_names) / sizeof(char *))
83#endif
84
85/*
Bram Moolenaar1b9645d2017-09-17 23:03:31 +020086 * Return TRUE if "name" is a window toolbar menu name.
87 */
88 static int
89menu_is_winbar(char_u *name)
90{
Bram Moolenaar378daf82017-09-23 23:58:28 +020091 return (STRNCMP(name, "WinBar", 6) == 0);
Bram Moolenaar1b9645d2017-09-17 23:03:31 +020092}
93
94 int
95winbar_height(win_T *wp)
96{
97 if (wp->w_winbar != NULL && wp->w_winbar->children != NULL)
98 return 1;
99 return 0;
100}
101
102 static vimmenu_T **
103get_root_menu(char_u *name)
104{
105 if (menu_is_winbar(name))
106 return &curwin->w_winbar;
107 return &root_menu;
108}
109
110/*
Bram Moolenaar071d4272004-06-13 20:20:40 +0000111 * Do the :menu command and relatives.
112 */
113 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100114ex_menu(
115 exarg_T *eap) /* Ex command arguments */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000116{
117 char_u *menu_path;
118 int modes;
119 char_u *map_to;
120 int noremap;
121 int silent = FALSE;
Bram Moolenaar8b2d9c42006-05-03 21:28:47 +0000122 int special = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000123 int unmenu;
124 char_u *map_buf;
125 char_u *arg;
126 char_u *p;
127 int i;
Bram Moolenaar241a8aa2005-12-06 20:04:44 +0000128#if defined(FEAT_GUI) && !defined(FEAT_GUI_GTK)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000129 int old_menu_height;
Bram Moolenaare89ff042016-02-20 22:17:05 +0100130# if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_W32)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000131 int old_toolbar_height;
132# endif
133#endif
134 int pri_tab[MENUDEPTH + 1];
135 int enable = MAYBE; /* TRUE for "menu enable", FALSE for "menu
136 * disable */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000137#ifdef FEAT_TOOLBAR
138 char_u *icon = NULL;
139#endif
140 vimmenu_T menuarg;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200141 vimmenu_T **root_menu_ptr;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000142
143 modes = get_menu_cmd_modes(eap->cmd, eap->forceit, &noremap, &unmenu);
144 arg = eap->arg;
145
146 for (;;)
147 {
148 if (STRNCMP(arg, "<script>", 8) == 0)
149 {
150 noremap = REMAP_SCRIPT;
151 arg = skipwhite(arg + 8);
152 continue;
153 }
154 if (STRNCMP(arg, "<silent>", 8) == 0)
155 {
156 silent = TRUE;
157 arg = skipwhite(arg + 8);
158 continue;
159 }
Bram Moolenaar8b2d9c42006-05-03 21:28:47 +0000160 if (STRNCMP(arg, "<special>", 9) == 0)
161 {
162 special = TRUE;
163 arg = skipwhite(arg + 9);
164 continue;
165 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000166 break;
167 }
168
169
170 /* Locate an optional "icon=filename" argument. */
171 if (STRNCMP(arg, "icon=", 5) == 0)
172 {
173 arg += 5;
174#ifdef FEAT_TOOLBAR
175 icon = arg;
176#endif
177 while (*arg != NUL && *arg != ' ')
178 {
179 if (*arg == '\\')
Bram Moolenaar8c8de832008-06-24 22:58:06 +0000180 STRMOVE(arg, arg + 1);
Bram Moolenaar91acfff2017-03-12 19:22:36 +0100181 MB_PTR_ADV(arg);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000182 }
183 if (*arg != NUL)
184 {
185 *arg++ = NUL;
186 arg = skipwhite(arg);
187 }
188 }
189
190 /*
191 * Fill in the priority table.
192 */
193 for (p = arg; *p; ++p)
194 if (!VIM_ISDIGIT(*p) && *p != '.')
195 break;
Bram Moolenaar1c465442017-03-12 20:10:05 +0100196 if (VIM_ISWHITE(*p))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000197 {
Bram Moolenaar1c465442017-03-12 20:10:05 +0100198 for (i = 0; i < MENUDEPTH && !VIM_ISWHITE(*arg); ++i)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000199 {
200 pri_tab[i] = getdigits(&arg);
201 if (pri_tab[i] == 0)
202 pri_tab[i] = 500;
203 if (*arg == '.')
204 ++arg;
205 }
206 arg = skipwhite(arg);
207 }
208 else if (eap->addr_count && eap->line2 != 0)
209 {
210 pri_tab[0] = eap->line2;
211 i = 1;
212 }
213 else
214 i = 0;
215 while (i < MENUDEPTH)
216 pri_tab[i++] = 500;
217 pri_tab[MENUDEPTH] = -1; /* mark end of the table */
218
219 /*
220 * Check for "disable" or "enable" argument.
221 */
Bram Moolenaar1c465442017-03-12 20:10:05 +0100222 if (STRNCMP(arg, "enable", 6) == 0 && VIM_ISWHITE(arg[6]))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000223 {
224 enable = TRUE;
225 arg = skipwhite(arg + 6);
226 }
Bram Moolenaar1c465442017-03-12 20:10:05 +0100227 else if (STRNCMP(arg, "disable", 7) == 0 && VIM_ISWHITE(arg[7]))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000228 {
229 enable = FALSE;
230 arg = skipwhite(arg + 7);
231 }
232
233 /*
234 * If there is no argument, display all menus.
235 */
236 if (*arg == NUL)
237 {
238 show_menus(arg, modes);
239 return;
240 }
241
242#ifdef FEAT_TOOLBAR
243 /*
244 * Need to get the toolbar icon index before doing the translation.
245 */
246 menuarg.iconidx = -1;
247 menuarg.icon_builtin = FALSE;
248 if (menu_is_toolbar(arg))
249 {
250 menu_path = menu_skip_part(arg);
251 if (*menu_path == '.')
252 {
253 p = menu_skip_part(++menu_path);
254 if (STRNCMP(menu_path, "BuiltIn", 7) == 0)
255 {
256 if (skipdigits(menu_path + 7) == p)
257 {
258 menuarg.iconidx = atoi((char *)menu_path + 7);
Bram Moolenaaraf0167f2009-05-16 15:31:32 +0000259 if (menuarg.iconidx >= (int)TOOLBAR_NAME_COUNT)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000260 menuarg.iconidx = -1;
261 else
262 menuarg.icon_builtin = TRUE;
263 }
264 }
265 else
266 {
Bram Moolenaaraf0167f2009-05-16 15:31:32 +0000267 for (i = 0; i < (int)TOOLBAR_NAME_COUNT; ++i)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000268 if (STRNCMP(toolbar_names[i], menu_path, p - menu_path)
269 == 0)
270 {
271 menuarg.iconidx = i;
272 break;
273 }
274 }
275 }
276 }
277#endif
278
Bram Moolenaar071d4272004-06-13 20:20:40 +0000279 menu_path = arg;
280 if (*menu_path == '.')
281 {
282 EMSG2(_(e_invarg2), menu_path);
283 goto theend;
284 }
285
Bram Moolenaar70b11cd2010-05-14 22:24:40 +0200286 map_to = menu_translate_tab_and_shift(arg);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000287
288 /*
289 * If there is only a menu name, display menus with that name.
290 */
291 if (*map_to == NUL && !unmenu && enable == MAYBE)
292 {
293 show_menus(menu_path, modes);
294 goto theend;
295 }
296 else if (*map_to != NUL && (unmenu || enable != MAYBE))
297 {
298 EMSG(_(e_trailing));
299 goto theend;
300 }
Bram Moolenaar241a8aa2005-12-06 20:04:44 +0000301#if defined(FEAT_GUI) && !(defined(FEAT_GUI_GTK) || defined(FEAT_GUI_PHOTON))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000302 old_menu_height = gui.menu_height;
Bram Moolenaare89ff042016-02-20 22:17:05 +0100303# if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_W32)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000304 old_toolbar_height = gui.toolbar_height;
305# endif
306#endif
307
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200308 root_menu_ptr = get_root_menu(menu_path);
309 if (root_menu_ptr == &curwin->w_winbar)
310 /* Assume the window toolbar menu will change. */
311 redraw_later(NOT_VALID);
312
Bram Moolenaar071d4272004-06-13 20:20:40 +0000313 if (enable != MAYBE)
314 {
315 /*
316 * Change sensitivity of the menu.
317 * For the PopUp menu, remove a menu for each mode separately.
318 * Careful: menu_nable_recurse() changes menu_path.
319 */
320 if (STRCMP(menu_path, "*") == 0) /* meaning: do all menus */
321 menu_path = (char_u *)"";
322
323 if (menu_is_popup(menu_path))
324 {
325 for (i = 0; i < MENU_INDEX_TIP; ++i)
326 if (modes & (1 << i))
327 {
328 p = popup_mode_name(menu_path, i);
329 if (p != NULL)
330 {
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200331 menu_nable_recurse(*root_menu_ptr, p, MENU_ALL_MODES,
Bram Moolenaar071d4272004-06-13 20:20:40 +0000332 enable);
333 vim_free(p);
334 }
335 }
336 }
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200337 menu_nable_recurse(*root_menu_ptr, menu_path, modes, enable);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000338 }
339 else if (unmenu)
340 {
341 /*
342 * Delete menu(s).
343 */
344 if (STRCMP(menu_path, "*") == 0) /* meaning: remove all menus */
345 menu_path = (char_u *)"";
346
347 /*
348 * For the PopUp menu, remove a menu for each mode separately.
349 */
350 if (menu_is_popup(menu_path))
351 {
352 for (i = 0; i < MENU_INDEX_TIP; ++i)
353 if (modes & (1 << i))
354 {
355 p = popup_mode_name(menu_path, i);
356 if (p != NULL)
357 {
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200358 remove_menu(root_menu_ptr, p, MENU_ALL_MODES, TRUE);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000359 vim_free(p);
360 }
361 }
362 }
363
364 /* Careful: remove_menu() changes menu_path */
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200365 remove_menu(root_menu_ptr, menu_path, modes, FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000366 }
367 else
368 {
369 /*
370 * Add menu(s).
371 * Replace special key codes.
372 */
373 if (STRICMP(map_to, "<nop>") == 0) /* "<Nop>" means nothing */
374 {
375 map_to = (char_u *)"";
376 map_buf = NULL;
377 }
Bram Moolenaar3fdfa4a2004-10-07 21:02:47 +0000378 else if (modes & MENU_TIP_MODE)
379 map_buf = NULL; /* Menu tips are plain text. */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000380 else
Bram Moolenaar8b2d9c42006-05-03 21:28:47 +0000381 map_to = replace_termcodes(map_to, &map_buf, FALSE, TRUE, special);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000382 menuarg.modes = modes;
383#ifdef FEAT_TOOLBAR
384 menuarg.iconfile = icon;
385#endif
386 menuarg.noremap[0] = noremap;
387 menuarg.silent[0] = silent;
388 add_menu_path(menu_path, &menuarg, pri_tab, map_to
389#ifdef FEAT_GUI_W32
390 , TRUE
391#endif
392 );
393
394 /*
395 * For the PopUp menu, add a menu for each mode separately.
396 */
397 if (menu_is_popup(menu_path))
398 {
399 for (i = 0; i < MENU_INDEX_TIP; ++i)
400 if (modes & (1 << i))
401 {
402 p = popup_mode_name(menu_path, i);
403 if (p != NULL)
404 {
405 /* Include all modes, to make ":amenu" work */
406 menuarg.modes = modes;
407#ifdef FEAT_TOOLBAR
408 menuarg.iconfile = NULL;
409 menuarg.iconidx = -1;
410 menuarg.icon_builtin = FALSE;
411#endif
412 add_menu_path(p, &menuarg, pri_tab, map_to
413#ifdef FEAT_GUI_W32
414 , TRUE
415#endif
416 );
417 vim_free(p);
418 }
419 }
420 }
421
422 vim_free(map_buf);
423 }
424
Bram Moolenaar241a8aa2005-12-06 20:04:44 +0000425#if defined(FEAT_GUI) && !(defined(FEAT_GUI_GTK))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000426 /* If the menubar height changed, resize the window */
427 if (gui.in_use
428 && (gui.menu_height != old_menu_height
Bram Moolenaare89ff042016-02-20 22:17:05 +0100429# if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_W32)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000430 || gui.toolbar_height != old_toolbar_height
431# endif
432 ))
Bram Moolenaar04a9d452006-03-27 21:03:26 +0000433 gui_set_shellsize(FALSE, FALSE, RESIZE_VERT);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000434#endif
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200435 if (root_menu_ptr == &curwin->w_winbar)
436 {
437 int h = winbar_height(curwin);
438
439 if (h != curwin->w_winbar_height)
440 {
441 if (h == 0)
442 ++curwin->w_height;
443 else if (curwin->w_height > 0)
444 --curwin->w_height;
445 curwin->w_winbar_height = h;
446 }
447 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000448
449theend:
Bram Moolenaar071d4272004-06-13 20:20:40 +0000450 ;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000451}
452
453/*
454 * Add the menu with the given name to the menu hierarchy
455 */
456 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100457add_menu_path(
458 char_u *menu_path,
459 vimmenu_T *menuarg, /* passes modes, iconfile, iconidx,
Bram Moolenaar071d4272004-06-13 20:20:40 +0000460 icon_builtin, silent[0], noremap[0] */
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100461 int *pri_tab,
462 char_u *call_data
Bram Moolenaar071d4272004-06-13 20:20:40 +0000463#ifdef FEAT_GUI_W32
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100464 , int addtearoff /* may add tearoff item */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000465#endif
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100466 )
Bram Moolenaar071d4272004-06-13 20:20:40 +0000467{
468 char_u *path_name;
469 int modes = menuarg->modes;
470 vimmenu_T **menup;
471 vimmenu_T *menu = NULL;
472 vimmenu_T *parent;
473 vimmenu_T **lower_pri;
474 char_u *p;
475 char_u *name;
476 char_u *dname;
477 char_u *next_name;
478 int i;
479 int c;
Bram Moolenaar7871a502010-05-14 21:19:23 +0200480 int d;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000481#ifdef FEAT_GUI
482 int idx;
483 int new_idx;
484#endif
485 int pri_idx = 0;
486 int old_modes = 0;
487 int amenu;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +0200488#ifdef FEAT_MULTI_LANG
489 char_u *en_name;
490 char_u *map_to = NULL;
491#endif
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200492 vimmenu_T **root_menu_ptr;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000493
494 /* Make a copy so we can stuff around with it, since it could be const */
495 path_name = vim_strsave(menu_path);
496 if (path_name == NULL)
497 return FAIL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200498 root_menu_ptr = get_root_menu(menu_path);
499 menup = root_menu_ptr;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000500 parent = NULL;
501 name = path_name;
502 while (*name)
503 {
504 /* Get name of this element in the menu hierarchy, and the simplified
505 * name (without mnemonic and accelerator text). */
506 next_name = menu_name_skip(name);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +0200507#ifdef FEAT_MULTI_LANG
Bram Moolenaar442b4222010-05-24 21:34:22 +0200508 map_to = menutrans_lookup(name, (int)STRLEN(name));
Bram Moolenaar70b11cd2010-05-14 22:24:40 +0200509 if (map_to != NULL)
510 {
511 en_name = name;
512 name = map_to;
513 }
514 else
515 en_name = NULL;
516#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000517 dname = menu_text(name, NULL, NULL);
Bram Moolenaar18a0b122006-08-16 13:55:16 +0000518 if (dname == NULL)
519 goto erret;
520 if (*dname == NUL)
521 {
522 /* Only a mnemonic or accelerator is not valid. */
523 EMSG(_("E792: Empty menu name"));
524 goto erret;
525 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000526
527 /* See if it's already there */
528 lower_pri = menup;
529#ifdef FEAT_GUI
530 idx = 0;
531 new_idx = 0;
532#endif
533 menu = *menup;
534 while (menu != NULL)
535 {
536 if (menu_name_equal(name, menu) || menu_name_equal(dname, menu))
537 {
538 if (*next_name == NUL && menu->children != NULL)
539 {
540 if (!sys_menu)
541 EMSG(_("E330: Menu path must not lead to a sub-menu"));
542 goto erret;
543 }
544 if (*next_name != NUL && menu->children == NULL
545#ifdef FEAT_GUI_W32
546 && addtearoff
547#endif
548 )
549 {
550 if (!sys_menu)
551 EMSG(_(e_notsubmenu));
552 goto erret;
553 }
554 break;
555 }
556 menup = &menu->next;
557
558 /* Count menus, to find where this one needs to be inserted.
559 * Ignore menus that are not in the menubar (PopUp and Toolbar) */
560 if (parent != NULL || menu_is_menubar(menu->name))
561 {
562#ifdef FEAT_GUI
563 ++idx;
564#endif
565 if (menu->priority <= pri_tab[pri_idx])
566 {
567 lower_pri = menup;
568#ifdef FEAT_GUI
569 new_idx = idx;
570#endif
571 }
572 }
573 menu = menu->next;
574 }
575
576 if (menu == NULL)
577 {
578 if (*next_name == NUL && parent == NULL)
579 {
580 EMSG(_("E331: Must not add menu items directly to menu bar"));
581 goto erret;
582 }
583
584 if (menu_is_separator(dname) && *next_name != NUL)
585 {
586 EMSG(_("E332: Separator cannot be part of a menu path"));
587 goto erret;
588 }
589
590 /* Not already there, so lets add it */
591 menu = (vimmenu_T *)alloc_clear((unsigned)sizeof(vimmenu_T));
592 if (menu == NULL)
593 goto erret;
594
595 menu->modes = modes;
596 menu->enabled = MENU_ALL_MODES;
597 menu->name = vim_strsave(name);
598 /* separate mnemonic and accelerator text from actual menu name */
599 menu->dname = menu_text(name, &menu->mnemonic, &menu->actext);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +0200600#ifdef FEAT_MULTI_LANG
601 if (en_name != NULL)
602 {
603 menu->en_name = vim_strsave(en_name);
604 menu->en_dname = menu_text(en_name, NULL, NULL);
605 }
606 else
607 {
608 menu->en_name = NULL;
609 menu->en_dname = NULL;
610 }
611#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000612 menu->priority = pri_tab[pri_idx];
613 menu->parent = parent;
614#ifdef FEAT_GUI_MOTIF
615 menu->sensitive = TRUE; /* the default */
616#endif
617#ifdef FEAT_BEVAL_TIP
618 menu->tip = NULL;
619#endif
620#ifdef FEAT_GUI_ATHENA
621 menu->image = None; /* X-Windows definition for NULL*/
622#endif
623
624 /*
625 * Add after menu that has lower priority.
626 */
627 menu->next = *lower_pri;
628 *lower_pri = menu;
629
630 old_modes = 0;
631
632#ifdef FEAT_TOOLBAR
633 menu->iconidx = menuarg->iconidx;
634 menu->icon_builtin = menuarg->icon_builtin;
635 if (*next_name == NUL && menuarg->iconfile != NULL)
636 menu->iconfile = vim_strsave(menuarg->iconfile);
637#endif
638#if defined(FEAT_GUI_W32) && defined(FEAT_TEAROFF)
639 /* the tearoff item must be present in the modes of each item. */
640 if (parent != NULL && menu_is_tearoff(parent->children->dname))
641 parent->children->modes |= modes;
642#endif
643 }
644 else
645 {
646 old_modes = menu->modes;
647
648 /*
649 * If this menu option was previously only available in other
650 * modes, then make sure it's available for this one now
651 * Also enable a menu when it's created or changed.
652 */
653#ifdef FEAT_GUI_W32
654 /* If adding a tearbar (addtearoff == FALSE) don't update modes */
655 if (addtearoff)
656#endif
657 {
658 menu->modes |= modes;
659 menu->enabled |= modes;
660 }
661 }
662
663#ifdef FEAT_GUI
664 /*
665 * Add the menu item when it's used in one of the modes, but not when
666 * only a tooltip is defined.
667 */
668 if ((old_modes & MENU_ALL_MODES) == 0
669 && (menu->modes & MENU_ALL_MODES) != 0)
670 {
671 if (gui.in_use) /* Otherwise it will be added when GUI starts */
672 {
673 if (*next_name == NUL)
674 {
675 /* Real menu item, not sub-menu */
676 gui_mch_add_menu_item(menu, new_idx);
677
678 /* Want to update menus now even if mode not changed */
679 force_menu_update = TRUE;
680 }
681 else
682 {
683 /* Sub-menu (not at end of path yet) */
684 gui_mch_add_menu(menu, new_idx);
685 }
686 }
687
688# if defined(FEAT_GUI_W32) & defined(FEAT_TEAROFF)
689 /* When adding a new submenu, may add a tearoff item */
690 if ( addtearoff
691 && *next_name
692 && vim_strchr(p_go, GO_TEAROFF) != NULL
693 && menu_is_menubar(name))
694 {
695 char_u *tearpath;
696
697 /*
698 * The pointers next_name & path_name refer to a string with
699 * \'s and ^V's stripped out. But menu_path is a "raw"
700 * string, so we must correct for special characters.
701 */
702 tearpath = alloc((unsigned int)STRLEN(menu_path) + TEAR_LEN + 2);
703 if (tearpath != NULL)
704 {
705 char_u *s;
706 int idx;
707
708 STRCPY(tearpath, menu_path);
709 idx = (int)(next_name - path_name - 1);
Bram Moolenaar91acfff2017-03-12 19:22:36 +0100710 for (s = tearpath; *s && s < tearpath + idx; MB_PTR_ADV(s))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000711 {
712 if ((*s == '\\' || *s == Ctrl_V) && s[1])
713 {
714 ++idx;
715 ++s;
716 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000717 }
718 tearpath[idx] = NUL;
719 gui_add_tearoff(tearpath, pri_tab, pri_idx);
720 vim_free(tearpath);
721 }
722 }
723# endif
724 }
725#endif /* FEAT_GUI */
726
727 menup = &menu->children;
728 parent = menu;
729 name = next_name;
Bram Moolenaard23a8232018-02-10 18:45:26 +0100730 VIM_CLEAR(dname);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000731 if (pri_tab[pri_idx + 1] != -1)
732 ++pri_idx;
733 }
734 vim_free(path_name);
735
736 /*
737 * Only add system menu items which have not been defined yet.
738 * First check if this was an ":amenu".
739 */
740 amenu = ((modes & (MENU_NORMAL_MODE | MENU_INSERT_MODE)) ==
741 (MENU_NORMAL_MODE | MENU_INSERT_MODE));
742 if (sys_menu)
743 modes &= ~old_modes;
744
745 if (menu != NULL && modes)
746 {
747#ifdef FEAT_GUI
748 menu->cb = gui_menu_cb;
749#endif
750 p = (call_data == NULL) ? NULL : vim_strsave(call_data);
751
752 /* loop over all modes, may add more than one */
753 for (i = 0; i < MENU_MODES; ++i)
754 {
755 if (modes & (1 << i))
756 {
757 /* free any old menu */
758 free_menu_string(menu, i);
759
760 /* For "amenu", may insert an extra character.
761 * Don't do this if adding a tearbar (addtearoff == FALSE).
762 * Don't do this for "<Nop>". */
763 c = 0;
Bram Moolenaar7871a502010-05-14 21:19:23 +0200764 d = 0;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000765 if (amenu && call_data != NULL && *call_data != NUL
766#ifdef FEAT_GUI_W32
767 && addtearoff
768#endif
769 )
770 {
771 switch (1 << i)
772 {
773 case MENU_VISUAL_MODE:
Bram Moolenaarb3656ed2006-03-20 21:59:49 +0000774 case MENU_SELECT_MODE:
Bram Moolenaar071d4272004-06-13 20:20:40 +0000775 case MENU_OP_PENDING_MODE:
776 case MENU_CMDLINE_MODE:
777 c = Ctrl_C;
778 break;
779 case MENU_INSERT_MODE:
Bram Moolenaar7871a502010-05-14 21:19:23 +0200780 c = Ctrl_BSL;
781 d = Ctrl_O;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000782 break;
783 }
784 }
785
Bram Moolenaar7871a502010-05-14 21:19:23 +0200786 if (c != 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000787 {
Bram Moolenaar7871a502010-05-14 21:19:23 +0200788 menu->strings[i] = alloc((unsigned)(STRLEN(call_data) + 5 ));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000789 if (menu->strings[i] != NULL)
790 {
791 menu->strings[i][0] = c;
Bram Moolenaar7871a502010-05-14 21:19:23 +0200792 if (d == 0)
793 STRCPY(menu->strings[i] + 1, call_data);
794 else
795 {
796 menu->strings[i][1] = d;
797 STRCPY(menu->strings[i] + 2, call_data);
798 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000799 if (c == Ctrl_C)
800 {
Bram Moolenaara93fa7e2006-04-17 22:14:47 +0000801 int len = (int)STRLEN(menu->strings[i]);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000802
803 /* Append CTRL-\ CTRL-G to obey 'insertmode'. */
804 menu->strings[i][len] = Ctrl_BSL;
805 menu->strings[i][len + 1] = Ctrl_G;
806 menu->strings[i][len + 2] = NUL;
807 }
808 }
809 }
810 else
811 menu->strings[i] = p;
812 menu->noremap[i] = menuarg->noremap[0];
813 menu->silent[i] = menuarg->silent[0];
814 }
815 }
816#if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_W32) \
Bram Moolenaarc3719bd2017-11-18 22:13:31 +0100817 && (defined(FEAT_BEVAL_GUI) || defined(FEAT_GUI_GTK))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000818 /* Need to update the menu tip. */
819 if (modes & MENU_TIP_MODE)
820 gui_mch_menu_set_tip(menu);
821#endif
822 }
823 return OK;
824
825erret:
826 vim_free(path_name);
827 vim_free(dname);
Bram Moolenaar18a0b122006-08-16 13:55:16 +0000828
829 /* Delete any empty submenu we added before discovering the error. Repeat
830 * for higher levels. */
831 while (parent != NULL && parent->children == NULL)
832 {
833 if (parent->parent == NULL)
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200834 menup = root_menu_ptr;
Bram Moolenaar18a0b122006-08-16 13:55:16 +0000835 else
836 menup = &parent->parent->children;
837 for ( ; *menup != NULL && *menup != parent; menup = &((*menup)->next))
838 ;
839 if (*menup == NULL) /* safety check */
840 break;
841 parent = parent->parent;
842 free_menu(menup);
843 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000844 return FAIL;
845}
846
847/*
848 * Set the (sub)menu with the given name to enabled or disabled.
849 * Called recursively.
850 */
851 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100852menu_nable_recurse(
853 vimmenu_T *menu,
854 char_u *name,
855 int modes,
856 int enable)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000857{
858 char_u *p;
859
860 if (menu == NULL)
861 return OK; /* Got to bottom of hierarchy */
862
863 /* Get name of this element in the menu hierarchy */
864 p = menu_name_skip(name);
865
866 /* Find the menu */
867 while (menu != NULL)
868 {
869 if (*name == NUL || *name == '*' || menu_name_equal(name, menu))
870 {
871 if (*p != NUL)
872 {
873 if (menu->children == NULL)
874 {
875 EMSG(_(e_notsubmenu));
876 return FAIL;
877 }
878 if (menu_nable_recurse(menu->children, p, modes, enable)
879 == FAIL)
880 return FAIL;
881 }
882 else
883 if (enable)
884 menu->enabled |= modes;
885 else
886 menu->enabled &= ~modes;
887
888 /*
889 * When name is empty, we are doing all menu items for the given
890 * modes, so keep looping, otherwise we are just doing the named
891 * menu item (which has been found) so break here.
892 */
893 if (*name != NUL && *name != '*')
894 break;
895 }
896 menu = menu->next;
897 }
898 if (*name != NUL && *name != '*' && menu == NULL)
899 {
Bram Moolenaar342337a2005-07-21 21:11:17 +0000900 EMSG2(_(e_nomenu), name);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000901 return FAIL;
902 }
903
904#ifdef FEAT_GUI
905 /* Want to update menus now even if mode not changed */
906 force_menu_update = TRUE;
907#endif
908
909 return OK;
910}
911
912/*
913 * Remove the (sub)menu with the given name from the menu hierarchy
914 * Called recursively.
915 */
916 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100917remove_menu(
918 vimmenu_T **menup,
919 char_u *name,
920 int modes,
921 int silent) /* don't give error messages */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000922{
923 vimmenu_T *menu;
924 vimmenu_T *child;
925 char_u *p;
926
927 if (*menup == NULL)
928 return OK; /* Got to bottom of hierarchy */
929
930 /* Get name of this element in the menu hierarchy */
931 p = menu_name_skip(name);
932
933 /* Find the menu */
934 while ((menu = *menup) != NULL)
935 {
936 if (*name == NUL || menu_name_equal(name, menu))
937 {
938 if (*p != NUL && menu->children == NULL)
939 {
940 if (!silent)
941 EMSG(_(e_notsubmenu));
942 return FAIL;
943 }
944 if ((menu->modes & modes) != 0x0)
945 {
946#if defined(FEAT_GUI_W32) & defined(FEAT_TEAROFF)
947 /*
948 * If we are removing all entries for this menu,MENU_ALL_MODES,
949 * Then kill any tearoff before we start
950 */
951 if (*p == NUL && modes == MENU_ALL_MODES)
952 {
953 if (IsWindow(menu->tearoff_handle))
954 DestroyWindow(menu->tearoff_handle);
955 }
956#endif
957 if (remove_menu(&menu->children, p, modes, silent) == FAIL)
958 return FAIL;
959 }
960 else if (*name != NUL)
961 {
962 if (!silent)
963 EMSG(_(e_othermode));
964 return FAIL;
965 }
966
967 /*
968 * When name is empty, we are removing all menu items for the given
969 * modes, so keep looping, otherwise we are just removing the named
970 * menu item (which has been found) so break here.
971 */
972 if (*name != NUL)
973 break;
974
975 /* Remove the menu item for the given mode[s]. If the menu item
976 * is no longer valid in ANY mode, delete it */
977 menu->modes &= ~modes;
978 if (modes & MENU_TIP_MODE)
979 free_menu_string(menu, MENU_INDEX_TIP);
980 if ((menu->modes & MENU_ALL_MODES) == 0)
981 free_menu(menup);
982 else
983 menup = &menu->next;
984 }
985 else
986 menup = &menu->next;
987 }
988 if (*name != NUL)
989 {
990 if (menu == NULL)
991 {
992 if (!silent)
Bram Moolenaar342337a2005-07-21 21:11:17 +0000993 EMSG2(_(e_nomenu), name);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000994 return FAIL;
995 }
996
997
998 /* Recalculate modes for menu based on the new updated children */
999 menu->modes &= ~modes;
1000#if defined(FEAT_GUI_W32) & defined(FEAT_TEAROFF)
1001 if ((s_tearoffs) && (menu->children != NULL)) /* there's a tear bar.. */
1002 child = menu->children->next; /* don't count tearoff bar */
1003 else
1004#endif
1005 child = menu->children;
1006 for ( ; child != NULL; child = child->next)
1007 menu->modes |= child->modes;
1008 if (modes & MENU_TIP_MODE)
1009 {
1010 free_menu_string(menu, MENU_INDEX_TIP);
1011#if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_W32) \
Bram Moolenaarc3719bd2017-11-18 22:13:31 +01001012 && (defined(FEAT_BEVAL_GUI) || defined(FEAT_GUI_GTK))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001013 /* Need to update the menu tip. */
1014 if (gui.in_use)
1015 gui_mch_menu_set_tip(menu);
1016#endif
1017 }
1018 if ((menu->modes & MENU_ALL_MODES) == 0)
1019 {
1020 /* The menu item is no longer valid in ANY mode, so delete it */
1021#if defined(FEAT_GUI_W32) & defined(FEAT_TEAROFF)
1022 if (s_tearoffs && menu->children != NULL) /* there's a tear bar.. */
1023 free_menu(&menu->children);
1024#endif
1025 *menup = menu;
1026 free_menu(menup);
1027 }
1028 }
1029
1030 return OK;
1031}
1032
1033/*
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001034 * Remove the WinBar menu from window "wp".
1035 */
1036 void
1037remove_winbar(win_T *wp)
1038{
1039 remove_menu(&wp->w_winbar, (char_u *)"", MENU_ALL_MODES, TRUE);
1040 vim_free(wp->w_winbar_items);
1041}
1042
1043/*
Bram Moolenaar071d4272004-06-13 20:20:40 +00001044 * Free the given menu structure and remove it from the linked list.
1045 */
1046 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001047free_menu(vimmenu_T **menup)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001048{
1049 int i;
1050 vimmenu_T *menu;
1051
1052 menu = *menup;
1053
1054#ifdef FEAT_GUI
1055 /* Free machine specific menu structures (only when already created) */
1056 /* Also may rebuild a tearoff'ed menu */
1057 if (gui.in_use)
1058 gui_mch_destroy_menu(menu);
1059#endif
1060
1061 /* Don't change *menup until after calling gui_mch_destroy_menu(). The
1062 * MacOS code needs the original structure to properly delete the menu. */
1063 *menup = menu->next;
1064 vim_free(menu->name);
1065 vim_free(menu->dname);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001066#ifdef FEAT_MULTI_LANG
1067 vim_free(menu->en_name);
1068 vim_free(menu->en_dname);
1069#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001070 vim_free(menu->actext);
1071#ifdef FEAT_TOOLBAR
1072 vim_free(menu->iconfile);
Bram Moolenaarbee0c5b2005-02-07 22:03:36 +00001073# ifdef FEAT_GUI_MOTIF
1074 vim_free(menu->xpm_fname);
1075# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001076#endif
1077 for (i = 0; i < MENU_MODES; i++)
1078 free_menu_string(menu, i);
1079 vim_free(menu);
1080
1081#ifdef FEAT_GUI
1082 /* Want to update menus now even if mode not changed */
1083 force_menu_update = TRUE;
1084#endif
1085}
1086
1087/*
1088 * Free the menu->string with the given index.
1089 */
1090 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001091free_menu_string(vimmenu_T *menu, int idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001092{
1093 int count = 0;
1094 int i;
1095
1096 for (i = 0; i < MENU_MODES; i++)
1097 if (menu->strings[i] == menu->strings[idx])
1098 count++;
1099 if (count == 1)
1100 vim_free(menu->strings[idx]);
1101 menu->strings[idx] = NULL;
1102}
1103
1104/*
1105 * Show the mapping associated with a menu item or hierarchy in a sub-menu.
1106 */
1107 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001108show_menus(char_u *path_name, int modes)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001109{
1110 char_u *p;
1111 char_u *name;
1112 vimmenu_T *menu;
1113 vimmenu_T *parent = NULL;
1114
Bram Moolenaar071d4272004-06-13 20:20:40 +00001115 name = path_name = vim_strsave(path_name);
1116 if (path_name == NULL)
1117 return FAIL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001118 menu = *get_root_menu(path_name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001119
1120 /* First, find the (sub)menu with the given name */
1121 while (*name)
1122 {
1123 p = menu_name_skip(name);
1124 while (menu != NULL)
1125 {
1126 if (menu_name_equal(name, menu))
1127 {
1128 /* Found menu */
1129 if (*p != NUL && menu->children == NULL)
1130 {
1131 EMSG(_(e_notsubmenu));
1132 vim_free(path_name);
1133 return FAIL;
1134 }
1135 else if ((menu->modes & modes) == 0x0)
1136 {
1137 EMSG(_(e_othermode));
1138 vim_free(path_name);
1139 return FAIL;
1140 }
1141 break;
1142 }
1143 menu = menu->next;
1144 }
1145 if (menu == NULL)
1146 {
Bram Moolenaar342337a2005-07-21 21:11:17 +00001147 EMSG2(_(e_nomenu), name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001148 vim_free(path_name);
1149 return FAIL;
1150 }
1151 name = p;
1152 parent = menu;
1153 menu = menu->children;
1154 }
Bram Moolenaaracbd4422008-08-17 21:44:45 +00001155 vim_free(path_name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001156
1157 /* Now we have found the matching menu, and we list the mappings */
1158 /* Highlight title */
1159 MSG_PUTS_TITLE(_("\n--- Menus ---"));
1160
1161 show_menus_recursive(parent, modes, 0);
1162 return OK;
1163}
1164
1165/*
1166 * Recursively show the mappings associated with the menus under the given one
1167 */
1168 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001169show_menus_recursive(vimmenu_T *menu, int modes, int depth)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001170{
1171 int i;
1172 int bit;
1173
1174 if (menu != NULL && (menu->modes & modes) == 0x0)
1175 return;
1176
1177 if (menu != NULL)
1178 {
1179 msg_putchar('\n');
1180 if (got_int) /* "q" hit for "--more--" */
1181 return;
1182 for (i = 0; i < depth; i++)
1183 MSG_PUTS(" ");
1184 if (menu->priority)
1185 {
1186 msg_outnum((long)menu->priority);
1187 MSG_PUTS(" ");
1188 }
1189 /* Same highlighting as for directories!? */
Bram Moolenaar8820b482017-03-16 17:23:31 +01001190 msg_outtrans_attr(menu->name, HL_ATTR(HLF_D));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001191 }
1192
1193 if (menu != NULL && menu->children == NULL)
1194 {
1195 for (bit = 0; bit < MENU_MODES; bit++)
1196 if ((menu->modes & modes & (1 << bit)) != 0)
1197 {
1198 msg_putchar('\n');
1199 if (got_int) /* "q" hit for "--more--" */
1200 return;
1201 for (i = 0; i < depth + 2; i++)
1202 MSG_PUTS(" ");
1203 msg_putchar(menu_mode_chars[bit]);
1204 if (menu->noremap[bit] == REMAP_NONE)
1205 msg_putchar('*');
1206 else if (menu->noremap[bit] == REMAP_SCRIPT)
1207 msg_putchar('&');
1208 else
1209 msg_putchar(' ');
1210 if (menu->silent[bit])
1211 msg_putchar('s');
1212 else
1213 msg_putchar(' ');
1214 if ((menu->modes & menu->enabled & (1 << bit)) == 0)
1215 msg_putchar('-');
1216 else
1217 msg_putchar(' ');
1218 MSG_PUTS(" ");
1219 if (*menu->strings[bit] == NUL)
Bram Moolenaar8820b482017-03-16 17:23:31 +01001220 msg_puts_attr((char_u *)"<Nop>", HL_ATTR(HLF_8));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001221 else
1222 msg_outtrans_special(menu->strings[bit], FALSE);
1223 }
1224 }
1225 else
1226 {
1227 if (menu == NULL)
1228 {
1229 menu = root_menu;
1230 depth--;
1231 }
1232 else
1233 menu = menu->children;
1234
1235 /* recursively show all children. Skip PopUp[nvoci]. */
1236 for (; menu != NULL && !got_int; menu = menu->next)
1237 if (!menu_is_hidden(menu->dname))
1238 show_menus_recursive(menu, modes, depth + 1);
1239 }
1240}
1241
1242#ifdef FEAT_CMDL_COMPL
1243
1244/*
1245 * Used when expanding menu names.
1246 */
1247static vimmenu_T *expand_menu = NULL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001248static vimmenu_T *expand_menu_alt = NULL;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001249static int expand_modes = 0x0;
1250static int expand_emenu; /* TRUE for ":emenu" command */
1251
1252/*
1253 * Work out what to complete when doing command line completion of menu names.
1254 */
1255 char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001256set_context_in_menu_cmd(
1257 expand_T *xp,
1258 char_u *cmd,
1259 char_u *arg,
1260 int forceit)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001261{
1262 char_u *after_dot;
1263 char_u *p;
1264 char_u *path_name = NULL;
1265 char_u *name;
1266 int unmenu;
1267 vimmenu_T *menu;
1268 int expand_menus;
1269
1270 xp->xp_context = EXPAND_UNSUCCESSFUL;
1271
1272
1273 /* Check for priority numbers, enable and disable */
1274 for (p = arg; *p; ++p)
1275 if (!VIM_ISDIGIT(*p) && *p != '.')
1276 break;
1277
Bram Moolenaar1c465442017-03-12 20:10:05 +01001278 if (!VIM_ISWHITE(*p))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001279 {
1280 if (STRNCMP(arg, "enable", 6) == 0
Bram Moolenaar1c465442017-03-12 20:10:05 +01001281 && (arg[6] == NUL || VIM_ISWHITE(arg[6])))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001282 p = arg + 6;
1283 else if (STRNCMP(arg, "disable", 7) == 0
Bram Moolenaar1c465442017-03-12 20:10:05 +01001284 && (arg[7] == NUL || VIM_ISWHITE(arg[7])))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001285 p = arg + 7;
1286 else
1287 p = arg;
1288 }
1289
Bram Moolenaar1c465442017-03-12 20:10:05 +01001290 while (*p != NUL && VIM_ISWHITE(*p))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001291 ++p;
1292
1293 arg = after_dot = p;
1294
Bram Moolenaar1c465442017-03-12 20:10:05 +01001295 for (; *p && !VIM_ISWHITE(*p); ++p)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001296 {
1297 if ((*p == '\\' || *p == Ctrl_V) && p[1] != NUL)
1298 p++;
1299 else if (*p == '.')
1300 after_dot = p + 1;
1301 }
1302
1303 /* ":tearoff" and ":popup" only use menus, not entries */
1304 expand_menus = !((*cmd == 't' && cmd[1] == 'e') || *cmd == 'p');
1305 expand_emenu = (*cmd == 'e');
Bram Moolenaar1c465442017-03-12 20:10:05 +01001306 if (expand_menus && VIM_ISWHITE(*p))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001307 return NULL; /* TODO: check for next command? */
1308 if (*p == NUL) /* Complete the menu name */
1309 {
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001310 int try_alt_menu = TRUE;
1311
Bram Moolenaar071d4272004-06-13 20:20:40 +00001312 /*
1313 * With :unmenu, you only want to match menus for the appropriate mode.
1314 * With :menu though you might want to add a menu with the same name as
1315 * one in another mode, so match menus from other modes too.
1316 */
1317 expand_modes = get_menu_cmd_modes(cmd, forceit, NULL, &unmenu);
1318 if (!unmenu)
1319 expand_modes = MENU_ALL_MODES;
1320
1321 menu = root_menu;
1322 if (after_dot != arg)
1323 {
1324 path_name = alloc((unsigned)(after_dot - arg));
1325 if (path_name == NULL)
1326 return NULL;
Bram Moolenaarce0842a2005-07-18 21:58:11 +00001327 vim_strncpy(path_name, arg, after_dot - arg - 1);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001328 }
1329 name = path_name;
1330 while (name != NULL && *name)
1331 {
1332 p = menu_name_skip(name);
1333 while (menu != NULL)
1334 {
1335 if (menu_name_equal(name, menu))
1336 {
1337 /* Found menu */
1338 if ((*p != NUL && menu->children == NULL)
1339 || ((menu->modes & expand_modes) == 0x0))
1340 {
1341 /*
1342 * Menu path continues, but we have reached a leaf.
1343 * Or menu exists only in another mode.
1344 */
1345 vim_free(path_name);
1346 return NULL;
1347 }
1348 break;
1349 }
1350 menu = menu->next;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001351 if (menu == NULL && try_alt_menu)
1352 {
1353 menu = curwin->w_winbar;
1354 try_alt_menu = FALSE;
1355 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001356 }
1357 if (menu == NULL)
1358 {
1359 /* No menu found with the name we were looking for */
1360 vim_free(path_name);
1361 return NULL;
1362 }
1363 name = p;
1364 menu = menu->children;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001365 try_alt_menu = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001366 }
Bram Moolenaareb3593b2006-04-22 22:33:57 +00001367 vim_free(path_name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001368
1369 xp->xp_context = expand_menus ? EXPAND_MENUNAMES : EXPAND_MENUS;
1370 xp->xp_pattern = after_dot;
1371 expand_menu = menu;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001372 if (expand_menu == root_menu)
1373 expand_menu_alt = curwin->w_winbar;
1374 else
1375 expand_menu_alt = NULL;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001376 }
1377 else /* We're in the mapping part */
1378 xp->xp_context = EXPAND_NOTHING;
1379 return NULL;
1380}
1381
1382/*
1383 * Function given to ExpandGeneric() to obtain the list of (sub)menus (not
1384 * entries).
1385 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001386 char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001387get_menu_name(expand_T *xp UNUSED, int idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001388{
1389 static vimmenu_T *menu = NULL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001390 static int did_alt_menu = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001391 char_u *str;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001392#ifdef FEAT_MULTI_LANG
1393 static int should_advance = FALSE;
1394#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001395
1396 if (idx == 0) /* first call: start at first item */
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001397 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001398 menu = expand_menu;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001399 did_alt_menu = FALSE;
Bram Moolenaar41375642010-05-16 12:49:27 +02001400#ifdef FEAT_MULTI_LANG
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001401 should_advance = FALSE;
Bram Moolenaar41375642010-05-16 12:49:27 +02001402#endif
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001403 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001404
1405 /* Skip PopUp[nvoci]. */
1406 while (menu != NULL && (menu_is_hidden(menu->dname)
1407 || menu_is_separator(menu->dname)
1408 || menu_is_tearoff(menu->dname)
1409 || menu->children == NULL))
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001410 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001411 menu = menu->next;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001412 if (menu == NULL && !did_alt_menu)
1413 {
1414 menu = expand_menu_alt;
1415 did_alt_menu = TRUE;
1416 }
1417 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001418
1419 if (menu == NULL) /* at end of linked list */
1420 return NULL;
1421
1422 if (menu->modes & expand_modes)
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001423#ifdef FEAT_MULTI_LANG
1424 if (should_advance)
1425 str = menu->en_dname;
1426 else
1427 {
1428#endif
1429 str = menu->dname;
1430#ifdef FEAT_MULTI_LANG
1431 if (menu->en_dname == NULL)
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001432 should_advance = TRUE;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001433 }
1434#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001435 else
1436 str = (char_u *)"";
1437
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001438#ifdef FEAT_MULTI_LANG
1439 if (should_advance)
1440#endif
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001441 {
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001442 /* Advance to next menu entry. */
1443 menu = menu->next;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001444 if (menu == NULL && !did_alt_menu)
1445 {
1446 menu = expand_menu_alt;
1447 did_alt_menu = TRUE;
1448 }
1449 }
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001450
1451#ifdef FEAT_MULTI_LANG
1452 should_advance = !should_advance;
1453#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001454
1455 return str;
1456}
1457
1458/*
1459 * Function given to ExpandGeneric() to obtain the list of menus and menu
1460 * entries.
1461 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001462 char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001463get_menu_names(expand_T *xp UNUSED, int idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001464{
1465 static vimmenu_T *menu = NULL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001466 static int did_alt_menu = FALSE;
Bram Moolenaaref9d6aa2011-04-11 16:56:35 +02001467#define TBUFFER_LEN 256
1468 static char_u tbuffer[TBUFFER_LEN]; /*hack*/
Bram Moolenaar071d4272004-06-13 20:20:40 +00001469 char_u *str;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001470#ifdef FEAT_MULTI_LANG
1471 static int should_advance = FALSE;
1472#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001473
1474 if (idx == 0) /* first call: start at first item */
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001475 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001476 menu = expand_menu;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001477 did_alt_menu = FALSE;
Bram Moolenaar41375642010-05-16 12:49:27 +02001478#ifdef FEAT_MULTI_LANG
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001479 should_advance = FALSE;
Bram Moolenaar41375642010-05-16 12:49:27 +02001480#endif
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001481 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001482
1483 /* Skip Browse-style entries, popup menus and separators. */
1484 while (menu != NULL
1485 && ( menu_is_hidden(menu->dname)
1486 || (expand_emenu && menu_is_separator(menu->dname))
1487 || menu_is_tearoff(menu->dname)
1488#ifndef FEAT_BROWSE
1489 || menu->dname[STRLEN(menu->dname) - 1] == '.'
1490#endif
1491 ))
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001492 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001493 menu = menu->next;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001494 if (menu == NULL && !did_alt_menu)
1495 {
1496 menu = expand_menu_alt;
1497 did_alt_menu = TRUE;
1498 }
1499 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001500
1501 if (menu == NULL) /* at end of linked list */
1502 return NULL;
1503
1504 if (menu->modes & expand_modes)
1505 {
1506 if (menu->children != NULL)
1507 {
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001508#ifdef FEAT_MULTI_LANG
1509 if (should_advance)
Bram Moolenaaref9d6aa2011-04-11 16:56:35 +02001510 vim_strncpy(tbuffer, menu->en_dname, TBUFFER_LEN - 2);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001511 else
1512 {
1513#endif
Bram Moolenaaref9d6aa2011-04-11 16:56:35 +02001514 vim_strncpy(tbuffer, menu->dname, TBUFFER_LEN - 2);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001515#ifdef FEAT_MULTI_LANG
1516 if (menu->en_dname == NULL)
1517 should_advance = TRUE;
1518 }
1519#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001520 /* hack on menu separators: use a 'magic' char for the separator
1521 * so that '.' in names gets escaped properly */
1522 STRCAT(tbuffer, "\001");
1523 str = tbuffer;
1524 }
1525 else
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001526#ifdef FEAT_MULTI_LANG
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001527 {
1528 if (should_advance)
1529 str = menu->en_dname;
1530 else
1531 {
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001532#endif
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001533 str = menu->dname;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001534#ifdef FEAT_MULTI_LANG
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001535 if (menu->en_dname == NULL)
1536 should_advance = TRUE;
1537 }
1538 }
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001539#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001540 }
1541 else
1542 str = (char_u *)"";
1543
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001544#ifdef FEAT_MULTI_LANG
1545 if (should_advance)
1546#endif
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001547 {
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001548 /* Advance to next menu entry. */
1549 menu = menu->next;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001550 if (menu == NULL && !did_alt_menu)
1551 {
1552 menu = expand_menu_alt;
1553 did_alt_menu = TRUE;
1554 }
1555 }
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001556
1557#ifdef FEAT_MULTI_LANG
1558 should_advance = !should_advance;
1559#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001560
1561 return str;
1562}
1563#endif /* FEAT_CMDL_COMPL */
1564
1565/*
1566 * Skip over this element of the menu path and return the start of the next
1567 * element. Any \ and ^Vs are removed from the current element.
Bram Moolenaar342337a2005-07-21 21:11:17 +00001568 * "name" may be modified.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001569 */
1570 char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001571menu_name_skip(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001572{
1573 char_u *p;
1574
Bram Moolenaar91acfff2017-03-12 19:22:36 +01001575 for (p = name; *p && *p != '.'; MB_PTR_ADV(p))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001576 {
1577 if (*p == '\\' || *p == Ctrl_V)
1578 {
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001579 STRMOVE(p, p + 1);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001580 if (*p == NUL)
1581 break;
1582 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001583 }
1584 if (*p)
1585 *p++ = NUL;
1586 return p;
1587}
1588
1589/*
1590 * Return TRUE when "name" matches with menu "menu". The name is compared in
1591 * two ways: raw menu name and menu name without '&'. ignore part after a TAB.
1592 */
1593 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001594menu_name_equal(char_u *name, vimmenu_T *menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001595{
Bram Moolenaar41375642010-05-16 12:49:27 +02001596#ifdef FEAT_MULTI_LANG
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001597 if (menu->en_name != NULL
Bram Moolenaard91f7042011-01-04 17:49:32 +01001598 && (menu_namecmp(name, menu->en_name)
1599 || menu_namecmp(name, menu->en_dname)))
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001600 return TRUE;
Bram Moolenaar41375642010-05-16 12:49:27 +02001601#endif
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001602 return menu_namecmp(name, menu->name) || menu_namecmp(name, menu->dname);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001603}
1604
1605 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001606menu_namecmp(char_u *name, char_u *mname)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001607{
1608 int i;
1609
1610 for (i = 0; name[i] != NUL && name[i] != TAB; ++i)
1611 if (name[i] != mname[i])
1612 break;
1613 return ((name[i] == NUL || name[i] == TAB)
1614 && (mname[i] == NUL || mname[i] == TAB));
1615}
1616
1617/*
1618 * Return the modes specified by the given menu command (eg :menu! returns
1619 * MENU_CMDLINE_MODE | MENU_INSERT_MODE).
1620 * If "noremap" is not NULL, then the flag it points to is set according to
1621 * whether the command is a "nore" command.
1622 * If "unmenu" is not NULL, then the flag it points to is set according to
1623 * whether the command is an "unmenu" command.
1624 */
1625 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001626get_menu_cmd_modes(
1627 char_u *cmd,
1628 int forceit, /* Was there a "!" after the command? */
1629 int *noremap,
1630 int *unmenu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001631{
1632 int modes;
1633
1634 switch (*cmd++)
1635 {
1636 case 'v': /* vmenu, vunmenu, vnoremenu */
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001637 modes = MENU_VISUAL_MODE | MENU_SELECT_MODE;
1638 break;
1639 case 'x': /* xmenu, xunmenu, xnoremenu */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001640 modes = MENU_VISUAL_MODE;
1641 break;
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001642 case 's': /* smenu, sunmenu, snoremenu */
1643 modes = MENU_SELECT_MODE;
1644 break;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001645 case 'o': /* omenu */
1646 modes = MENU_OP_PENDING_MODE;
1647 break;
1648 case 'i': /* imenu */
1649 modes = MENU_INSERT_MODE;
1650 break;
1651 case 't':
1652 modes = MENU_TIP_MODE; /* tmenu */
1653 break;
1654 case 'c': /* cmenu */
1655 modes = MENU_CMDLINE_MODE;
1656 break;
1657 case 'a': /* amenu */
1658 modes = MENU_INSERT_MODE | MENU_CMDLINE_MODE | MENU_NORMAL_MODE
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001659 | MENU_VISUAL_MODE | MENU_SELECT_MODE
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00001660 | MENU_OP_PENDING_MODE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001661 break;
1662 case 'n':
1663 if (*cmd != 'o') /* nmenu, not noremenu */
1664 {
1665 modes = MENU_NORMAL_MODE;
1666 break;
1667 }
1668 /* FALLTHROUGH */
1669 default:
1670 --cmd;
1671 if (forceit) /* menu!! */
1672 modes = MENU_INSERT_MODE | MENU_CMDLINE_MODE;
1673 else /* menu */
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001674 modes = MENU_NORMAL_MODE | MENU_VISUAL_MODE | MENU_SELECT_MODE
Bram Moolenaar071d4272004-06-13 20:20:40 +00001675 | MENU_OP_PENDING_MODE;
1676 }
1677
1678 if (noremap != NULL)
1679 *noremap = (*cmd == 'n' ? REMAP_NONE : REMAP_YES);
1680 if (unmenu != NULL)
1681 *unmenu = (*cmd == 'u');
1682 return modes;
1683}
1684
1685/*
1686 * Modify a menu name starting with "PopUp" to include the mode character.
1687 * Returns the name in allocated memory (NULL for failure).
1688 */
1689 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001690popup_mode_name(char_u *name, int idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001691{
1692 char_u *p;
1693 int len = (int)STRLEN(name);
1694
1695 p = vim_strnsave(name, len + 1);
1696 if (p != NULL)
1697 {
1698 mch_memmove(p + 6, p + 5, (size_t)(len - 4));
1699 p[5] = menu_mode_chars[idx];
1700 }
1701 return p;
1702}
1703
1704#if defined(FEAT_GUI) || defined(PROTO)
1705/*
1706 * Return the index into the menu->strings or menu->noremap arrays for the
1707 * current state. Returns MENU_INDEX_INVALID if there is no mapping for the
1708 * given menu in the current mode.
1709 */
1710 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001711get_menu_index(vimmenu_T *menu, int state)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001712{
1713 int idx;
1714
1715 if ((state & INSERT))
1716 idx = MENU_INDEX_INSERT;
1717 else if (state & CMDLINE)
1718 idx = MENU_INDEX_CMDLINE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001719 else if (VIsual_active)
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001720 {
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00001721 if (VIsual_select)
1722 idx = MENU_INDEX_SELECT;
1723 else
1724 idx = MENU_INDEX_VISUAL;
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001725 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001726 else if (state == HITRETURN || state == ASKMORE)
1727 idx = MENU_INDEX_CMDLINE;
1728 else if (finish_op)
1729 idx = MENU_INDEX_OP_PENDING;
1730 else if ((state & NORMAL))
1731 idx = MENU_INDEX_NORMAL;
1732 else
1733 idx = MENU_INDEX_INVALID;
1734
1735 if (idx != MENU_INDEX_INVALID && menu->strings[idx] == NULL)
1736 idx = MENU_INDEX_INVALID;
1737 return idx;
1738}
1739#endif
1740
1741/*
1742 * Duplicate the menu item text and then process to see if a mnemonic key
1743 * and/or accelerator text has been identified.
1744 * Returns a pointer to allocated memory, or NULL for failure.
1745 * If mnemonic != NULL, *mnemonic is set to the character after the first '&'.
1746 * If actext != NULL, *actext is set to the text after the first TAB.
1747 */
1748 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001749menu_text(char_u *str, int *mnemonic, char_u **actext)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001750{
1751 char_u *p;
1752 char_u *text;
1753
1754 /* Locate accelerator text, after the first TAB */
1755 p = vim_strchr(str, TAB);
1756 if (p != NULL)
1757 {
1758 if (actext != NULL)
1759 *actext = vim_strsave(p + 1);
1760 text = vim_strnsave(str, (int)(p - str));
1761 }
1762 else
1763 text = vim_strsave(str);
1764
1765 /* Find mnemonic characters "&a" and reduce "&&" to "&". */
1766 for (p = text; p != NULL; )
1767 {
1768 p = vim_strchr(p, '&');
1769 if (p != NULL)
1770 {
1771 if (p[1] == NUL) /* trailing "&" */
1772 break;
1773 if (mnemonic != NULL && p[1] != '&')
1774#if !defined(__MVS__) || defined(MOTIF390_MNEMONIC_FIXED)
1775 *mnemonic = p[1];
1776#else
1777 {
1778 /*
1779 * Well there is a bug in the Motif libraries on OS390 Unix.
1780 * The mnemonic keys needs to be converted to ASCII values
1781 * first.
1782 * This behavior has been seen in 2.8 and 2.9.
1783 */
1784 char c = p[1];
1785 __etoa_l(&c, 1);
1786 *mnemonic = c;
1787 }
1788#endif
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001789 STRMOVE(p, p + 1);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001790 p = p + 1;
1791 }
1792 }
1793 return text;
1794}
1795
1796/*
1797 * Return TRUE if "name" can be a menu in the MenuBar.
1798 */
1799 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001800menu_is_menubar(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001801{
1802 return (!menu_is_popup(name)
1803 && !menu_is_toolbar(name)
Bram Moolenaar378daf82017-09-23 23:58:28 +02001804 && !menu_is_winbar(name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001805 && *name != MNU_HIDDEN_CHAR);
1806}
1807
1808/*
1809 * Return TRUE if "name" is a popup menu name.
1810 */
1811 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001812menu_is_popup(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001813{
1814 return (STRNCMP(name, "PopUp", 5) == 0);
1815}
1816
1817#if (defined(FEAT_GUI_MOTIF) && (XmVersion <= 1002)) || defined(PROTO)
1818/*
1819 * Return TRUE if "name" is part of a popup menu.
1820 */
1821 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001822menu_is_child_of_popup(vimmenu_T *menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001823{
1824 while (menu->parent != NULL)
1825 menu = menu->parent;
1826 return menu_is_popup(menu->name);
1827}
1828#endif
1829
1830/*
1831 * Return TRUE if "name" is a toolbar menu name.
1832 */
1833 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001834menu_is_toolbar(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001835{
1836 return (STRNCMP(name, "ToolBar", 7) == 0);
1837}
1838
1839/*
1840 * Return TRUE if the name is a menu separator identifier: Starts and ends
1841 * with '-'
1842 */
1843 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001844menu_is_separator(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001845{
1846 return (name[0] == '-' && name[STRLEN(name) - 1] == '-');
1847}
1848
1849/*
1850 * Return TRUE if the menu is hidden: Starts with ']'
1851 */
1852 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001853menu_is_hidden(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001854{
1855 return (name[0] == ']') || (menu_is_popup(name) && name[5] != NUL);
1856}
1857
1858#if defined(FEAT_CMDL_COMPL) \
1859 || (defined(FEAT_GUI_W32) && defined(FEAT_TEAROFF))
1860/*
1861 * Return TRUE if the menu is the tearoff menu.
1862 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001863 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001864menu_is_tearoff(char_u *name UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001865{
1866#ifdef FEAT_GUI
1867 return (STRCMP(name, TEAR_STRING) == 0);
1868#else
1869 return FALSE;
1870#endif
1871}
1872#endif
1873
1874#ifdef FEAT_GUI
1875
1876 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001877get_menu_mode(void)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001878{
Bram Moolenaar071d4272004-06-13 20:20:40 +00001879 if (VIsual_active)
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001880 {
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00001881 if (VIsual_select)
1882 return MENU_INDEX_SELECT;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001883 return MENU_INDEX_VISUAL;
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001884 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001885 if (State & INSERT)
1886 return MENU_INDEX_INSERT;
1887 if ((State & CMDLINE) || State == ASKMORE || State == HITRETURN)
1888 return MENU_INDEX_CMDLINE;
1889 if (finish_op)
1890 return MENU_INDEX_OP_PENDING;
1891 if (State & NORMAL)
1892 return MENU_INDEX_NORMAL;
1893 if (State & LANGMAP) /* must be a "r" command, like Insert mode */
1894 return MENU_INDEX_INSERT;
1895 return MENU_INDEX_INVALID;
1896}
1897
1898/*
Bram Moolenaar968bbbe2006-08-16 19:41:08 +00001899 * Check that a pointer appears in the menu tree. Used to protect from using
1900 * a menu that was deleted after it was selected but before the event was
1901 * handled.
1902 * Return OK or FAIL. Used recursively.
1903 */
1904 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001905check_menu_pointer(vimmenu_T *root, vimmenu_T *menu_to_check)
Bram Moolenaar968bbbe2006-08-16 19:41:08 +00001906{
1907 vimmenu_T *p;
1908
1909 for (p = root; p != NULL; p = p->next)
1910 if (p == menu_to_check
1911 || (p->children != NULL
1912 && check_menu_pointer(p->children, menu_to_check) == OK))
1913 return OK;
1914 return FAIL;
1915}
1916
1917/*
Bram Moolenaar071d4272004-06-13 20:20:40 +00001918 * After we have started the GUI, then we can create any menus that have been
1919 * defined. This is done once here. add_menu_path() may have already been
1920 * called to define these menus, and may be called again. This function calls
1921 * itself recursively. Should be called at the top level with:
Bram Moolenaara06ecab2016-07-16 14:47:36 +02001922 * gui_create_initial_menus(root_menu);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001923 */
1924 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001925gui_create_initial_menus(vimmenu_T *menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001926{
1927 int idx = 0;
1928
1929 while (menu != NULL)
1930 {
1931 /* Don't add a menu when only a tip was defined. */
1932 if (menu->modes & MENU_ALL_MODES)
1933 {
1934 if (menu->children != NULL)
1935 {
1936 gui_mch_add_menu(menu, idx);
1937 gui_create_initial_menus(menu->children);
1938 }
1939 else
1940 gui_mch_add_menu_item(menu, idx);
1941 }
1942 menu = menu->next;
1943 ++idx;
1944 }
1945}
1946
1947/*
1948 * Used recursively by gui_update_menus (see below)
1949 */
1950 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001951gui_update_menus_recurse(vimmenu_T *menu, int mode)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001952{
1953 int grey;
1954
1955 while (menu)
1956 {
1957 if ((menu->modes & menu->enabled & mode)
1958#if defined(FEAT_GUI_W32) && defined(FEAT_TEAROFF)
1959 || menu_is_tearoff(menu->dname)
1960#endif
1961 )
1962 grey = FALSE;
1963 else
1964 grey = TRUE;
1965#ifdef FEAT_GUI_ATHENA
1966 /* Hiding menus doesn't work for Athena, it can cause a crash. */
1967 gui_mch_menu_grey(menu, grey);
1968#else
1969 /* Never hide a toplevel menu, it may make the menubar resize or
1970 * disappear. Same problem for ToolBar items. */
1971 if (vim_strchr(p_go, GO_GREY) != NULL || menu->parent == NULL
1972# ifdef FEAT_TOOLBAR
1973 || menu_is_toolbar(menu->parent->name)
1974# endif
1975 )
1976 gui_mch_menu_grey(menu, grey);
1977 else
1978 gui_mch_menu_hidden(menu, grey);
1979#endif
1980 gui_update_menus_recurse(menu->children, mode);
1981 menu = menu->next;
1982 }
1983}
1984
1985/*
1986 * Make sure only the valid menu items appear for this mode. If
1987 * force_menu_update is not TRUE, then we only do this if the mode has changed
1988 * since last time. If "modes" is not 0, then we use these modes instead.
1989 */
1990 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001991gui_update_menus(int modes)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001992{
1993 static int prev_mode = -1;
1994 int mode = 0;
1995
1996 if (modes != 0x0)
1997 mode = modes;
1998 else
1999 {
2000 mode = get_menu_mode();
2001 if (mode == MENU_INDEX_INVALID)
2002 mode = 0;
2003 else
2004 mode = (1 << mode);
2005 }
2006
2007 if (force_menu_update || mode != prev_mode)
2008 {
2009 gui_update_menus_recurse(root_menu, mode);
2010 gui_mch_draw_menubar();
2011 prev_mode = mode;
2012 force_menu_update = FALSE;
2013#ifdef FEAT_GUI_W32
2014 /* This can leave a tearoff as active window - make sure we
2015 * have the focus <negri>*/
2016 gui_mch_activate_window();
2017#endif
2018 }
2019}
2020
Bram Moolenaar241a8aa2005-12-06 20:04:44 +00002021#if defined(FEAT_GUI_MSWIN) || defined(FEAT_GUI_MOTIF) \
2022 || defined(FEAT_GUI_GTK) || defined(FEAT_GUI_PHOTON) || defined(PROTO)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002023/*
2024 * Check if a key is used as a mnemonic for a toplevel menu.
2025 * Case of the key is ignored.
2026 */
2027 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002028gui_is_menu_shortcut(int key)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002029{
2030 vimmenu_T *menu;
2031
2032 if (key < 256)
2033 key = TOLOWER_LOC(key);
2034 for (menu = root_menu; menu != NULL; menu = menu->next)
2035 if (menu->mnemonic == key
2036 || (menu->mnemonic < 256 && TOLOWER_LOC(menu->mnemonic) == key))
2037 return TRUE;
2038 return FALSE;
2039}
2040#endif
2041
2042/*
2043 * Display the Special "PopUp" menu as a pop-up at the current mouse
2044 * position. The "PopUpn" menu is for Normal mode, "PopUpi" for Insert mode,
2045 * etc.
2046 */
2047 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002048gui_show_popupmenu(void)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002049{
2050 vimmenu_T *menu;
2051 int mode;
2052
2053 mode = get_menu_mode();
2054 if (mode == MENU_INDEX_INVALID)
2055 return;
2056 mode = menu_mode_chars[mode];
2057
Bram Moolenaar342337a2005-07-21 21:11:17 +00002058#ifdef FEAT_AUTOCMD
2059 {
2060 char_u ename[2];
2061
2062 ename[0] = mode;
2063 ename[1] = NUL;
2064 apply_autocmds(EVENT_MENUPOPUP, ename, NULL, FALSE, curbuf);
2065 }
2066#endif
2067
Bram Moolenaar071d4272004-06-13 20:20:40 +00002068 for (menu = root_menu; menu != NULL; menu = menu->next)
2069 if (STRNCMP("PopUp", menu->name, 5) == 0 && menu->name[5] == mode)
2070 break;
2071
2072 /* Only show a popup when it is defined and has entries */
2073 if (menu != NULL && menu->children != NULL)
Bram Moolenaar2a67ed82016-06-10 21:52:42 +02002074 {
2075 /* Update the menus now, in case the MenuPopup autocommand did
2076 * anything. */
2077 gui_update_menus(0);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002078 gui_mch_show_popupmenu(menu);
Bram Moolenaar2a67ed82016-06-10 21:52:42 +02002079 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00002080}
2081#endif /* FEAT_GUI */
2082
2083#if (defined(FEAT_GUI_W32) && defined(FEAT_TEAROFF)) || defined(PROTO)
2084
2085/*
2086 * Deal with tearoff items that are added like a menu item.
2087 * Currently only for Win32 GUI. Others may follow later.
2088 */
2089
2090 void
2091gui_mch_toggle_tearoffs(int enable)
2092{
2093 int pri_tab[MENUDEPTH + 1];
2094 int i;
2095
2096 if (enable)
2097 {
2098 for (i = 0; i < MENUDEPTH; ++i)
2099 pri_tab[i] = 500;
2100 pri_tab[MENUDEPTH] = -1;
2101 gui_create_tearoffs_recurse(root_menu, (char_u *)"", pri_tab, 0);
2102 }
2103 else
2104 gui_destroy_tearoffs_recurse(root_menu);
2105 s_tearoffs = enable;
2106}
2107
2108/*
2109 * Recursively add tearoff items
2110 */
2111 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002112gui_create_tearoffs_recurse(
2113 vimmenu_T *menu,
2114 const char_u *pname,
2115 int *pri_tab,
2116 int pri_idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002117{
2118 char_u *newpname = NULL;
2119 int len;
2120 char_u *s;
2121 char_u *d;
2122
2123 if (pri_tab[pri_idx + 1] != -1)
2124 ++pri_idx;
2125 while (menu != NULL)
2126 {
2127 if (menu->children != NULL && menu_is_menubar(menu->name))
2128 {
2129 /* Add the menu name to the menu path. Insert a backslash before
2130 * dots (it's used to separate menu names). */
2131 len = (int)STRLEN(pname) + (int)STRLEN(menu->name);
2132 for (s = menu->name; *s; ++s)
2133 if (*s == '.' || *s == '\\')
2134 ++len;
2135 newpname = alloc(len + TEAR_LEN + 2);
2136 if (newpname != NULL)
2137 {
2138 STRCPY(newpname, pname);
2139 d = newpname + STRLEN(newpname);
2140 for (s = menu->name; *s; ++s)
2141 {
2142 if (*s == '.' || *s == '\\')
2143 *d++ = '\\';
2144 *d++ = *s;
2145 }
2146 *d = NUL;
2147
2148 /* check if tearoff already exists */
2149 if (STRCMP(menu->children->name, TEAR_STRING) != 0)
2150 {
2151 gui_add_tearoff(newpname, pri_tab, pri_idx - 1);
2152 *d = NUL; /* remove TEAR_STRING */
2153 }
2154
2155 STRCAT(newpname, ".");
2156 gui_create_tearoffs_recurse(menu->children, newpname,
2157 pri_tab, pri_idx);
2158 vim_free(newpname);
2159 }
2160 }
2161 menu = menu->next;
2162 }
2163}
2164
2165/*
2166 * Add tear-off menu item for a submenu.
2167 * "tearpath" is the menu path, and must have room to add TEAR_STRING.
2168 */
2169 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002170gui_add_tearoff(char_u *tearpath, int *pri_tab, int pri_idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002171{
2172 char_u *tbuf;
2173 int t;
2174 vimmenu_T menuarg;
2175
2176 tbuf = alloc(5 + (unsigned int)STRLEN(tearpath));
2177 if (tbuf != NULL)
2178 {
2179 tbuf[0] = K_SPECIAL;
2180 tbuf[1] = K_SECOND(K_TEAROFF);
2181 tbuf[2] = K_THIRD(K_TEAROFF);
2182 STRCPY(tbuf + 3, tearpath);
2183 STRCAT(tbuf + 3, "\r");
2184
2185 STRCAT(tearpath, ".");
2186 STRCAT(tearpath, TEAR_STRING);
2187
2188 /* Priority of tear-off is always 1 */
2189 t = pri_tab[pri_idx + 1];
2190 pri_tab[pri_idx + 1] = 1;
2191
2192#ifdef FEAT_TOOLBAR
2193 menuarg.iconfile = NULL;
2194 menuarg.iconidx = -1;
2195 menuarg.icon_builtin = FALSE;
2196#endif
2197 menuarg.noremap[0] = REMAP_NONE;
2198 menuarg.silent[0] = TRUE;
2199
2200 menuarg.modes = MENU_ALL_MODES;
2201 add_menu_path(tearpath, &menuarg, pri_tab, tbuf, FALSE);
2202
2203 menuarg.modes = MENU_TIP_MODE;
2204 add_menu_path(tearpath, &menuarg, pri_tab,
2205 (char_u *)_("Tear off this menu"), FALSE);
2206
2207 pri_tab[pri_idx + 1] = t;
2208 vim_free(tbuf);
2209 }
2210}
2211
2212/*
2213 * Recursively destroy tearoff items
2214 */
2215 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002216gui_destroy_tearoffs_recurse(vimmenu_T *menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002217{
2218 while (menu)
2219 {
2220 if (menu->children)
2221 {
2222 /* check if tearoff exists */
2223 if (STRCMP(menu->children->name, TEAR_STRING) == 0)
2224 {
2225 /* Disconnect the item and free the memory */
2226 free_menu(&menu->children);
2227 }
2228 if (menu->children != NULL) /* if not the last one */
2229 gui_destroy_tearoffs_recurse(menu->children);
2230 }
2231 menu = menu->next;
2232 }
2233}
2234
2235#endif /* FEAT_GUI_W32 && FEAT_TEAROFF */
2236
2237/*
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002238 * Execute "menu". Use by ":emenu" and the window toolbar.
2239 * "eap" is NULL for the window toolbar.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002240 */
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002241 static void
2242execute_menu(exarg_T *eap, vimmenu_T *menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002243{
Bram Moolenaar071d4272004-06-13 20:20:40 +00002244 char_u *mode;
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002245 int idx = -1;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002246
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002247 /* Use the Insert mode entry when returning to Insert mode. */
Bram Moolenaar4463f292005-09-25 22:20:24 +00002248 if (restart_edit
2249#ifdef FEAT_EVAL
2250 && !current_SID
2251#endif
2252 )
Bram Moolenaar071d4272004-06-13 20:20:40 +00002253 {
2254 mode = (char_u *)"Insert";
2255 idx = MENU_INDEX_INSERT;
2256 }
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002257 else if (VIsual_active)
2258 {
2259 mode = (char_u *)"Visual";
2260 idx = MENU_INDEX_VISUAL;
2261 }
2262 else if (eap != NULL && eap->addr_count)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002263 {
2264 pos_T tpos;
2265
2266 mode = (char_u *)"Visual";
2267 idx = MENU_INDEX_VISUAL;
2268
2269 /* GEDDES: This is not perfect - but it is a
2270 * quick way of detecting whether we are doing this from a
2271 * selection - see if the range matches up with the visual
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002272 * select start and end. */
Bram Moolenaareddf53b2006-02-27 00:11:10 +00002273 if ((curbuf->b_visual.vi_start.lnum == eap->line1)
2274 && (curbuf->b_visual.vi_end.lnum) == eap->line2)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002275 {
2276 /* Set it up for visual mode - equivalent to gv. */
Bram Moolenaareddf53b2006-02-27 00:11:10 +00002277 VIsual_mode = curbuf->b_visual.vi_mode;
2278 tpos = curbuf->b_visual.vi_end;
2279 curwin->w_cursor = curbuf->b_visual.vi_start;
2280 curwin->w_curswant = curbuf->b_visual.vi_curswant;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002281 }
2282 else
2283 {
2284 /* Set it up for line-wise visual mode */
2285 VIsual_mode = 'V';
2286 curwin->w_cursor.lnum = eap->line1;
2287 curwin->w_cursor.col = 1;
2288 tpos.lnum = eap->line2;
2289 tpos.col = MAXCOL;
Bram Moolenaar261bfea2006-03-01 22:12:31 +00002290#ifdef FEAT_VIRTUALEDIT
2291 tpos.coladd = 0;
2292#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002293 }
2294
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002295 /* Activate visual mode */
Bram Moolenaar071d4272004-06-13 20:20:40 +00002296 VIsual_active = TRUE;
2297 VIsual_reselect = TRUE;
2298 check_cursor();
2299 VIsual = curwin->w_cursor;
2300 curwin->w_cursor = tpos;
2301
2302 check_cursor();
2303
2304 /* Adjust the cursor to make sure it is in the correct pos
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002305 * for exclusive mode */
Bram Moolenaar071d4272004-06-13 20:20:40 +00002306 if (*p_sel == 'e' && gchar_cursor() != NUL)
2307 ++curwin->w_cursor.col;
2308 }
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002309
2310 /* For the WinBar menu always use the Normal mode menu. */
2311 if (idx == -1 || eap == NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002312 {
2313 mode = (char_u *)"Normal";
2314 idx = MENU_INDEX_NORMAL;
2315 }
2316
2317 if (idx != MENU_INDEX_INVALID && menu->strings[idx] != NULL)
2318 {
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002319 /* When executing a script or function execute the commands right now.
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002320 * Also for the window toolbar.
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002321 * Otherwise put them in the typeahead buffer. */
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002322 if (eap == NULL
Bram Moolenaar9c4b4ab2006-12-05 20:29:56 +00002323#ifdef FEAT_EVAL
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002324 || current_SID != 0
2325#endif
2326 )
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002327 {
2328 save_state_T save_state;
2329
2330 ++ex_normal_busy;
2331 if (save_current_state(&save_state))
2332 exec_normal_cmd(menu->strings[idx], menu->noremap[idx],
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002333 menu->silent[idx]);
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002334 restore_current_state(&save_state);
2335 --ex_normal_busy;
2336 }
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002337 else
2338 ins_typebuf(menu->strings[idx], menu->noremap[idx], 0,
Bram Moolenaar071d4272004-06-13 20:20:40 +00002339 TRUE, menu->silent[idx]);
2340 }
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002341 else if (eap != NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002342 EMSG2(_("E335: Menu not defined for %s mode"), mode);
2343}
2344
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002345/*
2346 * Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy and
2347 * execute it.
2348 */
2349 void
2350ex_emenu(exarg_T *eap)
2351{
2352 vimmenu_T *menu;
2353 char_u *name;
2354 char_u *saved_name;
2355 char_u *p;
2356
2357 saved_name = vim_strsave(eap->arg);
2358 if (saved_name == NULL)
2359 return;
2360
2361 menu = *get_root_menu(saved_name);
2362 name = saved_name;
2363 while (*name)
2364 {
2365 /* Find in the menu hierarchy */
2366 p = menu_name_skip(name);
2367
2368 while (menu != NULL)
2369 {
2370 if (menu_name_equal(name, menu))
2371 {
2372 if (*p == NUL && menu->children != NULL)
2373 {
2374 EMSG(_("E333: Menu path must lead to a menu item"));
2375 menu = NULL;
2376 }
2377 else if (*p != NUL && menu->children == NULL)
2378 {
2379 EMSG(_(e_notsubmenu));
2380 menu = NULL;
2381 }
2382 break;
2383 }
2384 menu = menu->next;
2385 }
2386 if (menu == NULL || *p == NUL)
2387 break;
2388 menu = menu->children;
2389 name = p;
2390 }
2391 vim_free(saved_name);
2392 if (menu == NULL)
2393 {
2394 EMSG2(_("E334: Menu not found: %s"), eap->arg);
2395 return;
2396 }
2397
2398 /* Found the menu, so execute. */
2399 execute_menu(eap, menu);
2400}
2401
2402/*
2403 * Handle a click in the window toolbar of "wp" at column "col".
2404 */
2405 void
2406winbar_click(win_T *wp, int col)
2407{
2408 int idx;
2409
2410 if (wp->w_winbar_items == NULL)
2411 return;
2412 for (idx = 0; wp->w_winbar_items[idx].wb_menu != NULL; ++idx)
2413 {
2414 winbar_item_T *item = &wp->w_winbar_items[idx];
2415
2416 if (col >= item->wb_startcol && col <= item->wb_endcol)
2417 {
2418 win_T *save_curwin = NULL;
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002419 pos_T save_visual = VIsual;
2420 int save_visual_active = VIsual_active;
2421 int save_visual_select = VIsual_select;
2422 int save_visual_reselect = VIsual_reselect;
2423 int save_visual_mode = VIsual_mode;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002424
2425 if (wp != curwin)
2426 {
2427 /* Clicking in the window toolbar of a not-current window.
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002428 * Make that window the current one and save Visual mode. */
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002429 save_curwin = curwin;
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002430 VIsual_active = FALSE;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002431 curwin = wp;
2432 curbuf = curwin->w_buffer;
2433 check_cursor();
2434 }
2435
2436 execute_menu(NULL, item->wb_menu);
2437
2438 if (save_curwin != NULL)
2439 {
2440 curwin = save_curwin;
2441 curbuf = curwin->w_buffer;
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002442 VIsual = save_visual;
2443 VIsual_active = save_visual_active;
2444 VIsual_select = save_visual_select;
2445 VIsual_reselect = save_visual_reselect;
2446 VIsual_mode = save_visual_mode;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002447 }
2448 }
2449 }
2450}
2451
2452#if defined(FEAT_GUI_MSWIN) || defined(FEAT_GUI_GTK) \
Bram Moolenaar071d4272004-06-13 20:20:40 +00002453 || defined(FEAT_BEVAL_TIP) || defined(PROTO)
2454/*
2455 * Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy.
2456 */
2457 vimmenu_T *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002458gui_find_menu(char_u *path_name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002459{
2460 vimmenu_T *menu = NULL;
2461 char_u *name;
2462 char_u *saved_name;
2463 char_u *p;
2464
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002465 menu = *get_root_menu(path_name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002466
2467 saved_name = vim_strsave(path_name);
2468 if (saved_name == NULL)
2469 return NULL;
2470
2471 name = saved_name;
2472 while (*name)
2473 {
2474 /* find the end of one dot-separated name and put a NUL at the dot */
2475 p = menu_name_skip(name);
2476
2477 while (menu != NULL)
2478 {
Bram Moolenaard91f7042011-01-04 17:49:32 +01002479 if (menu_name_equal(name, menu))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002480 {
2481 if (menu->children == NULL)
2482 {
2483 /* found a menu item instead of a sub-menu */
2484 if (*p == NUL)
2485 EMSG(_("E336: Menu path must lead to a sub-menu"));
2486 else
2487 EMSG(_(e_notsubmenu));
2488 menu = NULL;
2489 goto theend;
2490 }
2491 if (*p == NUL) /* found a full match */
2492 goto theend;
2493 break;
2494 }
2495 menu = menu->next;
2496 }
2497 if (menu == NULL) /* didn't find it */
2498 break;
2499
2500 /* Found a match, search the sub-menu. */
2501 menu = menu->children;
2502 name = p;
2503 }
2504
2505 if (menu == NULL)
2506 EMSG(_("E337: Menu not found - check menu names"));
2507theend:
2508 vim_free(saved_name);
2509 return menu;
2510}
2511#endif
2512
2513#ifdef FEAT_MULTI_LANG
2514/*
2515 * Translation of menu names. Just a simple lookup table.
2516 */
2517
2518typedef struct
2519{
2520 char_u *from; /* English name */
2521 char_u *from_noamp; /* same, without '&' */
2522 char_u *to; /* translated name */
2523} menutrans_T;
2524
2525static garray_T menutrans_ga = {0, 0, 0, 0, NULL};
2526#endif
2527
2528/*
2529 * ":menutrans".
2530 * This function is also defined without the +multi_lang feature, in which
2531 * case the commands are ignored.
2532 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00002533 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002534ex_menutranslate(exarg_T *eap UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002535{
2536#ifdef FEAT_MULTI_LANG
2537 char_u *arg = eap->arg;
2538 menutrans_T *tp;
2539 int i;
2540 char_u *from, *from_noamp, *to;
2541
2542 if (menutrans_ga.ga_itemsize == 0)
2543 ga_init2(&menutrans_ga, (int)sizeof(menutrans_T), 5);
2544
2545 /*
2546 * ":menutrans clear": clear all translations.
2547 */
2548 if (STRNCMP(arg, "clear", 5) == 0 && ends_excmd(*skipwhite(arg + 5)))
2549 {
2550 tp = (menutrans_T *)menutrans_ga.ga_data;
2551 for (i = 0; i < menutrans_ga.ga_len; ++i)
2552 {
2553 vim_free(tp[i].from);
2554 vim_free(tp[i].from_noamp);
2555 vim_free(tp[i].to);
2556 }
2557 ga_clear(&menutrans_ga);
2558# ifdef FEAT_EVAL
2559 /* Delete all "menutrans_" global variables. */
2560 del_menutrans_vars();
2561# endif
2562 }
2563 else
2564 {
2565 /* ":menutrans from to": add translation */
2566 from = arg;
2567 arg = menu_skip_part(arg);
2568 to = skipwhite(arg);
2569 *arg = NUL;
2570 arg = menu_skip_part(to);
2571 if (arg == to)
2572 EMSG(_(e_invarg));
2573 else
2574 {
2575 if (ga_grow(&menutrans_ga, 1) == OK)
2576 {
2577 tp = (menutrans_T *)menutrans_ga.ga_data;
2578 from = vim_strsave(from);
Bram Moolenaarfc1421e2006-04-20 22:17:20 +00002579 if (from != NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002580 {
Bram Moolenaarfc1421e2006-04-20 22:17:20 +00002581 from_noamp = menu_text(from, NULL, NULL);
2582 to = vim_strnsave(to, (int)(arg - to));
2583 if (from_noamp != NULL && to != NULL)
2584 {
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002585 menu_translate_tab_and_shift(from);
2586 menu_translate_tab_and_shift(to);
2587 menu_unescape_name(from);
2588 menu_unescape_name(to);
Bram Moolenaarfc1421e2006-04-20 22:17:20 +00002589 tp[menutrans_ga.ga_len].from = from;
2590 tp[menutrans_ga.ga_len].from_noamp = from_noamp;
2591 tp[menutrans_ga.ga_len].to = to;
2592 ++menutrans_ga.ga_len;
2593 }
2594 else
2595 {
2596 vim_free(from);
2597 vim_free(from_noamp);
2598 vim_free(to);
2599 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00002600 }
2601 }
2602 }
2603 }
2604#endif
2605}
2606
2607#if defined(FEAT_MULTI_LANG) || defined(FEAT_TOOLBAR)
2608/*
2609 * Find the character just after one part of a menu name.
2610 */
2611 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002612menu_skip_part(char_u *p)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002613{
Bram Moolenaar1c465442017-03-12 20:10:05 +01002614 while (*p != NUL && *p != '.' && !VIM_ISWHITE(*p))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002615 {
2616 if ((*p == '\\' || *p == Ctrl_V) && p[1] != NUL)
2617 ++p;
2618 ++p;
2619 }
2620 return p;
2621}
2622#endif
2623
2624#ifdef FEAT_MULTI_LANG
2625/*
2626 * Lookup part of a menu name in the translations.
2627 * Return a pointer to the translation or NULL if not found.
2628 */
2629 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002630menutrans_lookup(char_u *name, int len)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002631{
2632 menutrans_T *tp = (menutrans_T *)menutrans_ga.ga_data;
2633 int i;
2634 char_u *dname;
2635
2636 for (i = 0; i < menutrans_ga.ga_len; ++i)
Bram Moolenaar11dd8c12017-03-04 20:41:34 +01002637 if (STRNICMP(name, tp[i].from, len) == 0 && tp[i].from[len] == NUL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002638 return tp[i].to;
2639
2640 /* Now try again while ignoring '&' characters. */
2641 i = name[len];
2642 name[len] = NUL;
2643 dname = menu_text(name, NULL, NULL);
2644 name[len] = i;
2645 if (dname != NULL)
2646 {
2647 for (i = 0; i < menutrans_ga.ga_len; ++i)
Bram Moolenaar11dd8c12017-03-04 20:41:34 +01002648 if (STRICMP(dname, tp[i].from_noamp) == 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002649 {
2650 vim_free(dname);
2651 return tp[i].to;
2652 }
2653 vim_free(dname);
2654 }
2655
2656 return NULL;
2657}
Bram Moolenaar071d4272004-06-13 20:20:40 +00002658
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002659/*
2660 * Unescape the name in the translate dictionary table.
2661 */
2662 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002663menu_unescape_name(char_u *name)
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002664{
2665 char_u *p;
2666
Bram Moolenaar91acfff2017-03-12 19:22:36 +01002667 for (p = name; *p && *p != '.'; MB_PTR_ADV(p))
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002668 if (*p == '\\')
2669 STRMOVE(p, p + 1);
2670}
Bram Moolenaar56be9502010-06-06 14:20:26 +02002671#endif /* FEAT_MULTI_LANG */
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002672
2673/*
2674 * Isolate the menu name.
2675 * Skip the menu name, and translate <Tab> into a real TAB.
2676 */
2677 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002678menu_translate_tab_and_shift(char_u *arg_start)
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002679{
2680 char_u *arg = arg_start;
2681
Bram Moolenaar1c465442017-03-12 20:10:05 +01002682 while (*arg && !VIM_ISWHITE(*arg))
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002683 {
2684 if ((*arg == '\\' || *arg == Ctrl_V) && arg[1] != NUL)
2685 arg++;
2686 else if (STRNICMP(arg, "<TAB>", 5) == 0)
2687 {
2688 *arg = TAB;
2689 STRMOVE(arg + 1, arg + 5);
2690 }
2691 arg++;
2692 }
2693 if (*arg != NUL)
2694 *arg++ = NUL;
2695 arg = skipwhite(arg);
2696
2697 return arg;
2698}
2699
Bram Moolenaar071d4272004-06-13 20:20:40 +00002700#endif /* FEAT_MENU */