xref: /vim-8.2.3635/src/menu.c (revision 51491adf)
1 /* vi:set ts=8 sts=4 sw=4 noet:
2  *
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_MSWIN
22 static int add_menu_path(char_u *, vimmenu_T *, int *, char_u *, int);
23 #else
24 static int add_menu_path(char_u *, vimmenu_T *, int *, char_u *);
25 #endif
26 static int menu_nable_recurse(vimmenu_T *menu, char_u *name, int modes, int enable);
27 static int remove_menu(vimmenu_T **, char_u *, int, int silent);
28 static void free_menu(vimmenu_T **menup);
29 static void free_menu_string(vimmenu_T *, int);
30 static int show_menus(char_u *, int);
31 static void show_menus_recursive(vimmenu_T *, int, int);
32 static char_u *menu_name_skip(char_u *name);
33 static int menu_name_equal(char_u *name, vimmenu_T *menu);
34 static int menu_namecmp(char_u *name, char_u *mname);
35 static int get_menu_cmd_modes(char_u *, int, int *, int *);
36 static char_u *popup_mode_name(char_u *name, int idx);
37 static char_u *menu_text(char_u *text, int *mnemonic, char_u **actext);
38 
39 #if defined(FEAT_GUI_MSWIN) && defined(FEAT_TEAROFF)
40 static void gui_create_tearoffs_recurse(vimmenu_T *menu, const char_u *pname, int *pri_tab, int pri_idx);
41 static void gui_add_tearoff(char_u *tearpath, int *pri_tab, int pri_idx);
42 static void gui_destroy_tearoffs_recurse(vimmenu_T *menu);
43 static int s_tearoffs = FALSE;
44 #endif
45 
46 static int menu_is_hidden(char_u *name);
47 static int menu_is_tearoff(char_u *name);
48 
49 #if defined(FEAT_MULTI_LANG) || defined(FEAT_TOOLBAR)
50 static char_u *menu_skip_part(char_u *p);
51 #endif
52 #ifdef FEAT_MULTI_LANG
53 static char_u *menutrans_lookup(char_u *name, int len);
54 static void menu_unescape_name(char_u	*p);
55 #endif
56 
57 static char_u *menu_translate_tab_and_shift(char_u *arg_start);
58 
59 // The character for each menu mode
60 static char *menu_mode_chars[] = {"n", "v", "s", "o", "i", "c", "tl", "t"};
61 
62 static char_u e_notsubmenu[] = N_("E327: Part of menu-item path is not sub-menu");
63 static char_u e_nomenu[] = N_("E329: No menu \"%s\"");
64 
65 #ifdef FEAT_TOOLBAR
66 static const char *toolbar_names[] =
67 {
68     /*  0 */ "New", "Open", "Save", "Undo", "Redo",
69     /*  5 */ "Cut", "Copy", "Paste", "Print", "Help",
70     /* 10 */ "Find", "SaveAll", "SaveSesn", "NewSesn", "LoadSesn",
71     /* 15 */ "RunScript", "Replace", "WinClose", "WinMax", "WinMin",
72     /* 20 */ "WinSplit", "Shell", "FindPrev", "FindNext", "FindHelp",
73     /* 25 */ "Make", "TagJump", "RunCtags", "WinVSplit", "WinMaxWidth",
74     /* 30 */ "WinMinWidth", "Exit"
75 };
76 # define TOOLBAR_NAME_COUNT ARRAY_LENGTH(toolbar_names)
77 #endif
78 
79 /*
80  * Return TRUE if "name" is a window toolbar menu name.
81  */
82     static int
menu_is_winbar(char_u * name)83 menu_is_winbar(char_u *name)
84 {
85     return (STRNCMP(name, "WinBar", 6) == 0);
86 }
87 
88     int
winbar_height(win_T * wp)89 winbar_height(win_T *wp)
90 {
91     if (wp->w_winbar != NULL && wp->w_winbar->children != NULL)
92 	return 1;
93     return 0;
94 }
95 
96     static vimmenu_T **
get_root_menu(char_u * name)97 get_root_menu(char_u *name)
98 {
99     if (menu_is_winbar(name))
100 	return &curwin->w_winbar;
101     return &root_menu;
102 }
103 
104 /*
105  * Do the :menu command and relatives.
106  */
107     void
ex_menu(exarg_T * eap)108 ex_menu(
109     exarg_T	*eap)		    // Ex command arguments
110 {
111     char_u	*menu_path;
112     int		modes;
113     char_u	*map_to;
114     int		noremap;
115     int		silent = FALSE;
116     int		special = FALSE;
117     int		unmenu;
118     char_u	*map_buf;
119     char_u	*arg;
120     char_u	*p;
121     int		i;
122 #if defined(FEAT_GUI) && !defined(FEAT_GUI_GTK)
123     int		old_menu_height;
124 # if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_MSWIN)
125     int		old_toolbar_height;
126 # endif
127 #endif
128     int		pri_tab[MENUDEPTH + 1];
129     int		enable = MAYBE;	    // TRUE for "menu enable", FALSE for "menu
130 				    // disable
131 #ifdef FEAT_TOOLBAR
132     char_u	*icon = NULL;
133 #endif
134     vimmenu_T	menuarg;
135     vimmenu_T	**root_menu_ptr;
136 
137     modes = get_menu_cmd_modes(eap->cmd, eap->forceit, &noremap, &unmenu);
138     arg = eap->arg;
139 
140     for (;;)
141     {
142 	if (STRNCMP(arg, "<script>", 8) == 0)
143 	{
144 	    noremap = REMAP_SCRIPT;
145 	    arg = skipwhite(arg + 8);
146 	    continue;
147 	}
148 	if (STRNCMP(arg, "<silent>", 8) == 0)
149 	{
150 	    silent = TRUE;
151 	    arg = skipwhite(arg + 8);
152 	    continue;
153 	}
154 	if (STRNCMP(arg, "<special>", 9) == 0)
155 	{
156 	    special = TRUE;
157 	    arg = skipwhite(arg + 9);
158 	    continue;
159 	}
160 	break;
161     }
162 
163 
164     // Locate an optional "icon=filename" argument.
165     if (STRNCMP(arg, "icon=", 5) == 0)
166     {
167 	arg += 5;
168 #ifdef FEAT_TOOLBAR
169 	icon = arg;
170 #endif
171 	while (*arg != NUL && *arg != ' ')
172 	{
173 	    if (*arg == '\\')
174 		STRMOVE(arg, arg + 1);
175 	    MB_PTR_ADV(arg);
176 	}
177 	if (*arg != NUL)
178 	{
179 	    *arg++ = NUL;
180 	    arg = skipwhite(arg);
181 	}
182     }
183 
184     /*
185      * Fill in the priority table.
186      */
187     for (p = arg; *p; ++p)
188 	if (!VIM_ISDIGIT(*p) && *p != '.')
189 	    break;
190     if (VIM_ISWHITE(*p))
191     {
192 	for (i = 0; i < MENUDEPTH && !VIM_ISWHITE(*arg); ++i)
193 	{
194 	    pri_tab[i] = getdigits(&arg);
195 	    if (pri_tab[i] == 0)
196 		pri_tab[i] = 500;
197 	    if (*arg == '.')
198 		++arg;
199 	}
200 	arg = skipwhite(arg);
201     }
202     else if (eap->addr_count && eap->line2 != 0)
203     {
204 	pri_tab[0] = eap->line2;
205 	i = 1;
206     }
207     else
208 	i = 0;
209     while (i < MENUDEPTH)
210 	pri_tab[i++] = 500;
211     pri_tab[MENUDEPTH] = -1;		// mark end of the table
212 
213     /*
214      * Check for "disable" or "enable" argument.
215      */
216     if (STRNCMP(arg, "enable", 6) == 0 && VIM_ISWHITE(arg[6]))
217     {
218 	enable = TRUE;
219 	arg = skipwhite(arg + 6);
220     }
221     else if (STRNCMP(arg, "disable", 7) == 0 && VIM_ISWHITE(arg[7]))
222     {
223 	enable = FALSE;
224 	arg = skipwhite(arg + 7);
225     }
226 
227     /*
228      * If there is no argument, display all menus.
229      */
230     if (*arg == NUL)
231     {
232 	show_menus(arg, modes);
233 	return;
234     }
235 
236 #ifdef FEAT_TOOLBAR
237     /*
238      * Need to get the toolbar icon index before doing the translation.
239      */
240     menuarg.iconidx = -1;
241     menuarg.icon_builtin = FALSE;
242     if (menu_is_toolbar(arg))
243     {
244 	menu_path = menu_skip_part(arg);
245 	if (*menu_path == '.')
246 	{
247 	    p = menu_skip_part(++menu_path);
248 	    if (STRNCMP(menu_path, "BuiltIn", 7) == 0)
249 	    {
250 		if (skipdigits(menu_path + 7) == p)
251 		{
252 		    menuarg.iconidx = atoi((char *)menu_path + 7);
253 		    if (menuarg.iconidx >= (int)TOOLBAR_NAME_COUNT)
254 			menuarg.iconidx = -1;
255 		    else
256 			menuarg.icon_builtin = TRUE;
257 		}
258 	    }
259 	    else
260 	    {
261 		for (i = 0; i < (int)TOOLBAR_NAME_COUNT; ++i)
262 		    if (STRNCMP(toolbar_names[i], menu_path, p - menu_path)
263 									 == 0)
264 		    {
265 			menuarg.iconidx = i;
266 			break;
267 		    }
268 	    }
269 	}
270     }
271 #endif
272 
273     menu_path = arg;
274     if (*menu_path == '.')
275     {
276 	semsg(_(e_invarg2), menu_path);
277 	goto theend;
278     }
279 
280     map_to = menu_translate_tab_and_shift(arg);
281 
282     /*
283      * If there is only a menu name, display menus with that name.
284      */
285     if (*map_to == NUL && !unmenu && enable == MAYBE)
286     {
287 	show_menus(menu_path, modes);
288 	goto theend;
289     }
290     else if (*map_to != NUL && (unmenu || enable != MAYBE))
291     {
292 	semsg(_(e_trailing_arg), map_to);
293 	goto theend;
294     }
295 #if defined(FEAT_GUI) && !(defined(FEAT_GUI_GTK) || defined(FEAT_GUI_PHOTON))
296     old_menu_height = gui.menu_height;
297 # if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_MSWIN)
298     old_toolbar_height = gui.toolbar_height;
299 # endif
300 #endif
301 
302     root_menu_ptr = get_root_menu(menu_path);
303     if (root_menu_ptr == &curwin->w_winbar)
304 	// Assume the window toolbar menu will change.
305 	redraw_later(NOT_VALID);
306 
307     if (enable != MAYBE)
308     {
309 	/*
310 	 * Change sensitivity of the menu.
311 	 * For the PopUp menu, remove a menu for each mode separately.
312 	 * Careful: menu_nable_recurse() changes menu_path.
313 	 */
314 	if (STRCMP(menu_path, "*") == 0)	// meaning: do all menus
315 	    menu_path = (char_u *)"";
316 
317 	if (menu_is_popup(menu_path))
318 	{
319 	    for (i = 0; i < MENU_INDEX_TIP; ++i)
320 		if (modes & (1 << i))
321 		{
322 		    p = popup_mode_name(menu_path, i);
323 		    if (p != NULL)
324 		    {
325 			menu_nable_recurse(*root_menu_ptr, p, MENU_ALL_MODES,
326 								      enable);
327 			vim_free(p);
328 		    }
329 		}
330 	}
331 	menu_nable_recurse(*root_menu_ptr, menu_path, modes, enable);
332     }
333     else if (unmenu)
334     {
335 	/*
336 	 * Delete menu(s).
337 	 */
338 	if (STRCMP(menu_path, "*") == 0)	// meaning: remove all menus
339 	    menu_path = (char_u *)"";
340 
341 	/*
342 	 * For the PopUp menu, remove a menu for each mode separately.
343 	 */
344 	if (menu_is_popup(menu_path))
345 	{
346 	    for (i = 0; i < MENU_INDEX_TIP; ++i)
347 		if (modes & (1 << i))
348 		{
349 		    p = popup_mode_name(menu_path, i);
350 		    if (p != NULL)
351 		    {
352 			remove_menu(root_menu_ptr, p, MENU_ALL_MODES, TRUE);
353 			vim_free(p);
354 		    }
355 		}
356 	}
357 
358 	// Careful: remove_menu() changes menu_path
359 	remove_menu(root_menu_ptr, menu_path, modes, FALSE);
360     }
361     else
362     {
363 	/*
364 	 * Add menu(s).
365 	 * Replace special key codes.
366 	 */
367 	if (STRICMP(map_to, "<nop>") == 0)	// "<Nop>" means nothing
368 	{
369 	    map_to = (char_u *)"";
370 	    map_buf = NULL;
371 	}
372 	else if (modes & MENU_TIP_MODE)
373 	    map_buf = NULL;	// Menu tips are plain text.
374 	else
375 	    map_to = replace_termcodes(map_to, &map_buf,
376 			REPTERM_DO_LT | (special ? REPTERM_SPECIAL : 0), NULL);
377 	menuarg.modes = modes;
378 #ifdef FEAT_TOOLBAR
379 	menuarg.iconfile = icon;
380 #endif
381 	menuarg.noremap[0] = noremap;
382 	menuarg.silent[0] = silent;
383 	add_menu_path(menu_path, &menuarg, pri_tab, map_to
384 #ifdef FEAT_GUI_MSWIN
385 		, TRUE
386 #endif
387 		);
388 
389 	/*
390 	 * For the PopUp menu, add a menu for each mode separately.
391 	 */
392 	if (menu_is_popup(menu_path))
393 	{
394 	    for (i = 0; i < MENU_INDEX_TIP; ++i)
395 		if (modes & (1 << i))
396 		{
397 		    p = popup_mode_name(menu_path, i);
398 		    if (p != NULL)
399 		    {
400 			// Include all modes, to make ":amenu" work
401 			menuarg.modes = modes;
402 #ifdef FEAT_TOOLBAR
403 			menuarg.iconfile = NULL;
404 			menuarg.iconidx = -1;
405 			menuarg.icon_builtin = FALSE;
406 #endif
407 			add_menu_path(p, &menuarg, pri_tab, map_to
408 #ifdef FEAT_GUI_MSWIN
409 				, TRUE
410 #endif
411 					 );
412 			vim_free(p);
413 		    }
414 		}
415 	}
416 
417 	vim_free(map_buf);
418     }
419 
420 #if defined(FEAT_GUI) && !(defined(FEAT_GUI_GTK))
421     // If the menubar height changed, resize the window
422     if (gui.in_use
423 	    && (gui.menu_height != old_menu_height
424 # if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_MSWIN)
425 		|| gui.toolbar_height != old_toolbar_height
426 # endif
427 	    ))
428 	gui_set_shellsize(FALSE, FALSE, RESIZE_VERT);
429 #endif
430     if (root_menu_ptr == &curwin->w_winbar)
431     {
432 	int h = winbar_height(curwin);
433 
434 	if (h != curwin->w_winbar_height)
435 	{
436 	    if (h == 0)
437 		++curwin->w_height;
438 	    else if (curwin->w_height > 0)
439 		--curwin->w_height;
440 	    curwin->w_winbar_height = h;
441 	}
442     }
443 
444 theend:
445     ;
446 }
447 
448 /*
449  * Add the menu with the given name to the menu hierarchy
450  */
451     static int
add_menu_path(char_u * menu_path,vimmenu_T * menuarg,int * pri_tab,char_u * call_data,int addtearoff)452 add_menu_path(
453     char_u	*menu_path,
454     vimmenu_T	*menuarg,	// passes modes, iconfile, iconidx,
455 				// icon_builtin, silent[0], noremap[0]
456     int		*pri_tab,
457     char_u	*call_data
458 #ifdef FEAT_GUI_MSWIN
459     , int	addtearoff	// may add tearoff item
460 #endif
461     )
462 {
463     char_u	*path_name;
464     int		modes = menuarg->modes;
465     vimmenu_T	**menup;
466     vimmenu_T	*menu = NULL;
467     vimmenu_T	*parent;
468     vimmenu_T	**lower_pri;
469     char_u	*p;
470     char_u	*name;
471     char_u	*dname;
472     char_u	*next_name;
473     int		i;
474     int		c;
475     int		d;
476 #ifdef FEAT_GUI
477     int		idx;
478     int		new_idx;
479 #endif
480     int		pri_idx = 0;
481     int		old_modes = 0;
482     int		amenu;
483 #ifdef FEAT_MULTI_LANG
484     char_u	*en_name;
485     char_u	*map_to = NULL;
486 #endif
487     vimmenu_T	**root_menu_ptr;
488 
489     // Make a copy so we can stuff around with it, since it could be const
490     path_name = vim_strsave(menu_path);
491     if (path_name == NULL)
492 	return FAIL;
493     root_menu_ptr = get_root_menu(menu_path);
494     menup = root_menu_ptr;
495     parent = NULL;
496     name = path_name;
497     while (*name)
498     {
499 	// Get name of this element in the menu hierarchy, and the simplified
500 	// name (without mnemonic and accelerator text).
501 	next_name = menu_name_skip(name);
502 #ifdef	FEAT_MULTI_LANG
503 	map_to = menutrans_lookup(name, (int)STRLEN(name));
504 	if (map_to != NULL)
505 	{
506 	    en_name = name;
507 	    name = map_to;
508 	}
509 	else
510 	    en_name = NULL;
511 #endif
512 	dname = menu_text(name, NULL, NULL);
513 	if (dname == NULL)
514 	    goto erret;
515 	if (*dname == NUL)
516 	{
517 	    // Only a mnemonic or accelerator is not valid.
518 	    emsg(_("E792: Empty menu name"));
519 	    goto erret;
520 	}
521 
522 	// See if it's already there
523 	lower_pri = menup;
524 #ifdef FEAT_GUI
525 	idx = 0;
526 	new_idx = 0;
527 #endif
528 	menu = *menup;
529 	while (menu != NULL)
530 	{
531 	    if (menu_name_equal(name, menu) || menu_name_equal(dname, menu))
532 	    {
533 		if (*next_name == NUL && menu->children != NULL)
534 		{
535 		    if (!sys_menu)
536 			emsg(_("E330: Menu path must not lead to a sub-menu"));
537 		    goto erret;
538 		}
539 		if (*next_name != NUL && menu->children == NULL
540 #ifdef FEAT_GUI_MSWIN
541 			&& addtearoff
542 #endif
543 			)
544 		{
545 		    if (!sys_menu)
546 			emsg(_(e_notsubmenu));
547 		    goto erret;
548 		}
549 		break;
550 	    }
551 	    menup = &menu->next;
552 
553 	    // Count menus, to find where this one needs to be inserted.
554 	    // Ignore menus that are not in the menubar (PopUp and Toolbar)
555 	    if (parent != NULL || menu_is_menubar(menu->name))
556 	    {
557 #ifdef FEAT_GUI
558 		++idx;
559 #endif
560 		if (menu->priority <= pri_tab[pri_idx])
561 		{
562 		    lower_pri = menup;
563 #ifdef FEAT_GUI
564 		    new_idx = idx;
565 #endif
566 		}
567 	    }
568 	    menu = menu->next;
569 	}
570 
571 	if (menu == NULL)
572 	{
573 	    if (*next_name == NUL && parent == NULL)
574 	    {
575 		emsg(_("E331: Must not add menu items directly to menu bar"));
576 		goto erret;
577 	    }
578 
579 	    if (menu_is_separator(dname) && *next_name != NUL)
580 	    {
581 		emsg(_("E332: Separator cannot be part of a menu path"));
582 		goto erret;
583 	    }
584 
585 	    // Not already there, so lets add it
586 	    menu = ALLOC_CLEAR_ONE(vimmenu_T);
587 	    if (menu == NULL)
588 		goto erret;
589 
590 	    menu->modes = modes;
591 	    menu->enabled = MENU_ALL_MODES;
592 	    menu->name = vim_strsave(name);
593 	    // separate mnemonic and accelerator text from actual menu name
594 	    menu->dname = menu_text(name, &menu->mnemonic, &menu->actext);
595 #ifdef	FEAT_MULTI_LANG
596 	    if (en_name != NULL)
597 	    {
598 		menu->en_name = vim_strsave(en_name);
599 		menu->en_dname = menu_text(en_name, NULL, NULL);
600 	    }
601 	    else
602 	    {
603 		menu->en_name = NULL;
604 		menu->en_dname = NULL;
605 	    }
606 #endif
607 	    menu->priority = pri_tab[pri_idx];
608 	    menu->parent = parent;
609 #ifdef FEAT_GUI_MOTIF
610 	    menu->sensitive = TRUE;	    // the default
611 #endif
612 #ifdef FEAT_BEVAL_TIP
613 	    menu->tip = NULL;
614 #endif
615 #ifdef FEAT_GUI_ATHENA
616 	    menu->image = None;		    // X-Windows definition for NULL
617 #endif
618 
619 	    /*
620 	     * Add after menu that has lower priority.
621 	     */
622 	    menu->next = *lower_pri;
623 	    *lower_pri = menu;
624 
625 	    old_modes = 0;
626 
627 #ifdef FEAT_TOOLBAR
628 	    menu->iconidx = menuarg->iconidx;
629 	    menu->icon_builtin = menuarg->icon_builtin;
630 	    if (*next_name == NUL && menuarg->iconfile != NULL)
631 		menu->iconfile = vim_strsave(menuarg->iconfile);
632 #endif
633 #if defined(FEAT_GUI_MSWIN) && defined(FEAT_TEAROFF)
634 	    // the tearoff item must be present in the modes of each item.
635 	    if (parent != NULL && menu_is_tearoff(parent->children->dname))
636 		parent->children->modes |= modes;
637 #endif
638 	}
639 	else
640 	{
641 	    old_modes = menu->modes;
642 
643 	    /*
644 	     * If this menu option was previously only available in other
645 	     * modes, then make sure it's available for this one now
646 	     * Also enable a menu when it's created or changed.
647 	     */
648 #ifdef FEAT_GUI_MSWIN
649 	    // If adding a tearbar (addtearoff == FALSE) don't update modes
650 	    if (addtearoff)
651 #endif
652 	    {
653 		menu->modes |= modes;
654 		menu->enabled |= modes;
655 	    }
656 	}
657 
658 #ifdef FEAT_GUI
659 	/*
660 	 * Add the menu item when it's used in one of the modes, but not when
661 	 * only a tooltip is defined.
662 	 */
663 	if ((old_modes & MENU_ALL_MODES) == 0
664 		&& (menu->modes & MENU_ALL_MODES) != 0)
665 	{
666 	    if (gui.in_use)  // Otherwise it will be added when GUI starts
667 	    {
668 		if (*next_name == NUL)
669 		{
670 		    // Real menu item, not sub-menu
671 		    gui_mch_add_menu_item(menu, new_idx);
672 
673 		    // Want to update menus now even if mode not changed
674 		    force_menu_update = TRUE;
675 		}
676 		else
677 		{
678 		    // Sub-menu (not at end of path yet)
679 		    gui_mch_add_menu(menu, new_idx);
680 		}
681 	    }
682 
683 # if defined(FEAT_GUI_MSWIN) & defined(FEAT_TEAROFF)
684 	    // When adding a new submenu, may add a tearoff item
685 	    if (	addtearoff
686 		    && *next_name
687 		    && vim_strchr(p_go, GO_TEAROFF) != NULL
688 		    && menu_is_menubar(name)
689 #  ifdef VIMDLL
690 		    && (gui.in_use || gui.starting)
691 #  endif
692 		    )
693 	    {
694 		char_u		*tearpath;
695 
696 		/*
697 		 * The pointers next_name & path_name refer to a string with
698 		 * \'s and ^V's stripped out. But menu_path is a "raw"
699 		 * string, so we must correct for special characters.
700 		 */
701 		tearpath = alloc(STRLEN(menu_path) + TEAR_LEN + 2);
702 		if (tearpath != NULL)
703 		{
704 		    char_u  *s;
705 		    int	    idx;
706 
707 		    STRCPY(tearpath, menu_path);
708 		    idx = (int)(next_name - path_name - 1);
709 		    for (s = tearpath; *s && s < tearpath + idx; MB_PTR_ADV(s))
710 		    {
711 			if ((*s == '\\' || *s == Ctrl_V) && s[1])
712 			{
713 			    ++idx;
714 			    ++s;
715 			}
716 		    }
717 		    tearpath[idx] = NUL;
718 		    gui_add_tearoff(tearpath, pri_tab, pri_idx);
719 		    vim_free(tearpath);
720 		}
721 	    }
722 # endif
723 	}
724 #endif // FEAT_GUI
725 
726 	menup = &menu->children;
727 	parent = menu;
728 	name = next_name;
729 	VIM_CLEAR(dname);
730 	if (pri_tab[pri_idx + 1] != -1)
731 	    ++pri_idx;
732     }
733     vim_free(path_name);
734 
735     /*
736      * Only add system menu items which have not been defined yet.
737      * First check if this was an ":amenu".
738      */
739     amenu = ((modes & (MENU_NORMAL_MODE | MENU_INSERT_MODE)) ==
740 				       (MENU_NORMAL_MODE | MENU_INSERT_MODE));
741     if (sys_menu)
742 	modes &= ~old_modes;
743 
744     if (menu != NULL && modes)
745     {
746 #ifdef FEAT_GUI
747 	menu->cb = gui_menu_cb;
748 #endif
749 	p = (call_data == NULL) ? NULL : vim_strsave(call_data);
750 
751 	// loop over all modes, may add more than one
752 	for (i = 0; i < MENU_MODES; ++i)
753 	{
754 	    if (modes & (1 << i))
755 	    {
756 		// free any old menu
757 		free_menu_string(menu, i);
758 
759 		// For "amenu", may insert an extra character.
760 		// Don't do this if adding a tearbar (addtearoff == FALSE).
761 		// Don't do this for "<Nop>".
762 		c = 0;
763 		d = 0;
764 		if (amenu && call_data != NULL && *call_data != NUL
765 #ifdef FEAT_GUI_MSWIN
766 		       && addtearoff
767 #endif
768 				       )
769 		{
770 		    switch (1 << i)
771 		    {
772 			case MENU_VISUAL_MODE:
773 			case MENU_SELECT_MODE:
774 			case MENU_OP_PENDING_MODE:
775 			case MENU_CMDLINE_MODE:
776 			    c = Ctrl_C;
777 			    break;
778 			case MENU_INSERT_MODE:
779 			    c = Ctrl_BSL;
780 			    d = Ctrl_O;
781 			    break;
782 		    }
783 		}
784 
785 		if (c != 0)
786 		{
787 		    menu->strings[i] = alloc(STRLEN(call_data) + 5);
788 		    if (menu->strings[i] != NULL)
789 		    {
790 			menu->strings[i][0] = c;
791 			if (d == 0)
792 			    STRCPY(menu->strings[i] + 1, call_data);
793 			else
794 			{
795 			    menu->strings[i][1] = d;
796 			    STRCPY(menu->strings[i] + 2, call_data);
797 			}
798 			if (c == Ctrl_C)
799 			{
800 			    int	    len = (int)STRLEN(menu->strings[i]);
801 
802 			    // Append CTRL-\ CTRL-G to obey 'insertmode'.
803 			    menu->strings[i][len] = Ctrl_BSL;
804 			    menu->strings[i][len + 1] = Ctrl_G;
805 			    menu->strings[i][len + 2] = NUL;
806 			}
807 		    }
808 		}
809 		else
810 		    menu->strings[i] = p;
811 		menu->noremap[i] = menuarg->noremap[0];
812 		menu->silent[i] = menuarg->silent[0];
813 	    }
814 	}
815 #if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_MSWIN) \
816 	&& (defined(FEAT_BEVAL_GUI) || defined(FEAT_GUI_GTK))
817 	// Need to update the menu tip.
818 	if (modes & MENU_TIP_MODE)
819 	    gui_mch_menu_set_tip(menu);
820 #endif
821     }
822     return OK;
823 
824 erret:
825     vim_free(path_name);
826     vim_free(dname);
827 
828     // Delete any empty submenu we added before discovering the error.  Repeat
829     // for higher levels.
830     while (parent != NULL && parent->children == NULL)
831     {
832 	if (parent->parent == NULL)
833 	    menup = root_menu_ptr;
834 	else
835 	    menup = &parent->parent->children;
836 	for ( ; *menup != NULL && *menup != parent; menup = &((*menup)->next))
837 	    ;
838 	if (*menup == NULL) // safety check
839 	    break;
840 	parent = parent->parent;
841 	free_menu(menup);
842     }
843     return FAIL;
844 }
845 
846 /*
847  * Set the (sub)menu with the given name to enabled or disabled.
848  * Called recursively.
849  */
850     static int
menu_nable_recurse(vimmenu_T * menu,char_u * name,int modes,int enable)851 menu_nable_recurse(
852     vimmenu_T	*menu,
853     char_u	*name,
854     int		modes,
855     int		enable)
856 {
857     char_u	*p;
858 
859     if (menu == NULL)
860 	return OK;		// Got to bottom of hierarchy
861 
862     // Get name of this element in the menu hierarchy
863     p = menu_name_skip(name);
864 
865     // Find the menu
866     while (menu != NULL)
867     {
868 	if (*name == NUL || *name == '*' || menu_name_equal(name, menu))
869 	{
870 	    if (*p != NUL)
871 	    {
872 		if (menu->children == NULL)
873 		{
874 		    emsg(_(e_notsubmenu));
875 		    return FAIL;
876 		}
877 		if (menu_nable_recurse(menu->children, p, modes, enable)
878 								      == FAIL)
879 		    return FAIL;
880 	    }
881 	    else
882 		if (enable)
883 		    menu->enabled |= modes;
884 		else
885 		    menu->enabled &= ~modes;
886 
887 	    /*
888 	     * When name is empty, we are doing all menu items for the given
889 	     * modes, so keep looping, otherwise we are just doing the named
890 	     * menu item (which has been found) so break here.
891 	     */
892 	    if (*name != NUL && *name != '*')
893 		break;
894 	}
895 	menu = menu->next;
896     }
897     if (*name != NUL && *name != '*' && menu == NULL)
898     {
899 	semsg(_(e_nomenu), name);
900 	return FAIL;
901     }
902 
903 #ifdef FEAT_GUI
904     // Want to update menus now even if mode not changed
905     force_menu_update = TRUE;
906 #endif
907 
908     return OK;
909 }
910 
911 /*
912  * Remove the (sub)menu with the given name from the menu hierarchy
913  * Called recursively.
914  */
915     static int
remove_menu(vimmenu_T ** menup,char_u * name,int modes,int silent)916 remove_menu(
917     vimmenu_T	**menup,
918     char_u	*name,
919     int		modes,
920     int		silent)		// don't give error messages
921 {
922     vimmenu_T	*menu;
923     vimmenu_T	*child;
924     char_u	*p;
925 
926     if (*menup == NULL)
927 	return OK;		// Got to bottom of hierarchy
928 
929     // Get name of this element in the menu hierarchy
930     p = menu_name_skip(name);
931 
932     // Find the menu
933     while ((menu = *menup) != NULL)
934     {
935 	if (*name == NUL || menu_name_equal(name, menu))
936 	{
937 	    if (*p != NUL && menu->children == NULL)
938 	    {
939 		if (!silent)
940 		    emsg(_(e_notsubmenu));
941 		return FAIL;
942 	    }
943 	    if ((menu->modes & modes) != 0x0)
944 	    {
945 #if defined(FEAT_GUI_MSWIN) & defined(FEAT_TEAROFF)
946 		/*
947 		 * If we are removing all entries for this menu,MENU_ALL_MODES,
948 		 * Then kill any tearoff before we start
949 		 */
950 		if (*p == NUL && modes == MENU_ALL_MODES)
951 		{
952 		    if (IsWindow(menu->tearoff_handle))
953 			DestroyWindow(menu->tearoff_handle);
954 		}
955 #endif
956 		if (remove_menu(&menu->children, p, modes, silent) == FAIL)
957 		    return FAIL;
958 	    }
959 	    else if (*name != NUL)
960 	    {
961 		if (!silent)
962 		    emsg(_(e_menuothermode));
963 		return FAIL;
964 	    }
965 
966 	    /*
967 	     * When name is empty, we are removing all menu items for the given
968 	     * modes, so keep looping, otherwise we are just removing the named
969 	     * menu item (which has been found) so break here.
970 	     */
971 	    if (*name != NUL)
972 		break;
973 
974 	    // Remove the menu item for the given mode[s].  If the menu item
975 	    // is no longer valid in ANY mode, delete it
976 	    menu->modes &= ~modes;
977 	    if (modes & MENU_TIP_MODE)
978 		free_menu_string(menu, MENU_INDEX_TIP);
979 	    if ((menu->modes & MENU_ALL_MODES) == 0)
980 		free_menu(menup);
981 	    else
982 		menup = &menu->next;
983 	}
984 	else
985 	    menup = &menu->next;
986     }
987     if (*name != NUL)
988     {
989 	if (menu == NULL)
990 	{
991 	    if (!silent)
992 		semsg(_(e_nomenu), name);
993 	    return FAIL;
994 	}
995 
996 
997 	// Recalculate modes for menu based on the new updated children
998 	menu->modes &= ~modes;
999 #if defined(FEAT_GUI_MSWIN) & defined(FEAT_TEAROFF)
1000 	if ((s_tearoffs) && (menu->children != NULL)) // there's a tear bar..
1001 	    child = menu->children->next; // don't count tearoff bar
1002 	else
1003 #endif
1004 	    child = menu->children;
1005 	for ( ; child != NULL; child = child->next)
1006 	    menu->modes |= child->modes;
1007 	if (modes & MENU_TIP_MODE)
1008 	{
1009 	    free_menu_string(menu, MENU_INDEX_TIP);
1010 #if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_MSWIN) \
1011 	    && (defined(FEAT_BEVAL_GUI) || defined(FEAT_GUI_GTK))
1012 	    // Need to update the menu tip.
1013 	    if (gui.in_use)
1014 		gui_mch_menu_set_tip(menu);
1015 #endif
1016 	}
1017 	if ((menu->modes & MENU_ALL_MODES) == 0)
1018 	{
1019 	    // The menu item is no longer valid in ANY mode, so delete it
1020 #if defined(FEAT_GUI_MSWIN) & defined(FEAT_TEAROFF)
1021 	    if (s_tearoffs && menu->children != NULL) // there's a tear bar..
1022 		free_menu(&menu->children);
1023 #endif
1024 	    *menup = menu;
1025 	    free_menu(menup);
1026 	}
1027     }
1028 
1029     return OK;
1030 }
1031 
1032 /*
1033  * Remove the WinBar menu from window "wp".
1034  */
1035     void
remove_winbar(win_T * wp)1036 remove_winbar(win_T *wp)
1037 {
1038     remove_menu(&wp->w_winbar, (char_u *)"", MENU_ALL_MODES, TRUE);
1039     vim_free(wp->w_winbar_items);
1040 }
1041 
1042 /*
1043  * Free the given menu structure and remove it from the linked list.
1044  */
1045     static void
free_menu(vimmenu_T ** menup)1046 free_menu(vimmenu_T **menup)
1047 {
1048     int		i;
1049     vimmenu_T	*menu;
1050 
1051     menu = *menup;
1052 
1053 #ifdef FEAT_GUI
1054     // Free machine specific menu structures (only when already created)
1055     // Also may rebuild a tearoff'ed menu
1056     if (gui.in_use)
1057 	gui_mch_destroy_menu(menu);
1058 #endif
1059 
1060     // Don't change *menup until after calling gui_mch_destroy_menu(). The
1061     // MacOS code needs the original structure to properly delete the menu.
1062     *menup = menu->next;
1063     vim_free(menu->name);
1064     vim_free(menu->dname);
1065 #ifdef FEAT_MULTI_LANG
1066     vim_free(menu->en_name);
1067     vim_free(menu->en_dname);
1068 #endif
1069     vim_free(menu->actext);
1070 #ifdef FEAT_TOOLBAR
1071     vim_free(menu->iconfile);
1072 # ifdef FEAT_GUI_MOTIF
1073     vim_free(menu->xpm_fname);
1074 # endif
1075 #endif
1076     for (i = 0; i < MENU_MODES; i++)
1077 	free_menu_string(menu, i);
1078     vim_free(menu);
1079 
1080 #ifdef FEAT_GUI
1081     // Want to update menus now even if mode not changed
1082     force_menu_update = TRUE;
1083 #endif
1084 }
1085 
1086 /*
1087  * Free the menu->string with the given index.
1088  */
1089     static void
free_menu_string(vimmenu_T * menu,int idx)1090 free_menu_string(vimmenu_T *menu, int idx)
1091 {
1092     int		count = 0;
1093     int		i;
1094 
1095     for (i = 0; i < MENU_MODES; i++)
1096 	if (menu->strings[i] == menu->strings[idx])
1097 	    count++;
1098     if (count == 1)
1099 	vim_free(menu->strings[idx]);
1100     menu->strings[idx] = NULL;
1101 }
1102 
1103 /*
1104  * Show the mapping associated with a menu item or hierarchy in a sub-menu.
1105  */
1106     static int
show_menus(char_u * path_name,int modes)1107 show_menus(char_u *path_name, int modes)
1108 {
1109     char_u	*p;
1110     char_u	*name;
1111     vimmenu_T	*menu;
1112     vimmenu_T	*parent = NULL;
1113 
1114     name = path_name = vim_strsave(path_name);
1115     if (path_name == NULL)
1116 	return FAIL;
1117     menu = *get_root_menu(path_name);
1118 
1119     // First, find the (sub)menu with the given name
1120     while (*name)
1121     {
1122 	p = menu_name_skip(name);
1123 	while (menu != NULL)
1124 	{
1125 	    if (menu_name_equal(name, menu))
1126 	    {
1127 		// Found menu
1128 		if (*p != NUL && menu->children == NULL)
1129 		{
1130 		    emsg(_(e_notsubmenu));
1131 		    vim_free(path_name);
1132 		    return FAIL;
1133 		}
1134 		else if ((menu->modes & modes) == 0x0)
1135 		{
1136 		    emsg(_(e_menuothermode));
1137 		    vim_free(path_name);
1138 		    return FAIL;
1139 		}
1140 		break;
1141 	    }
1142 	    menu = menu->next;
1143 	}
1144 	if (menu == NULL)
1145 	{
1146 	    semsg(_(e_nomenu), name);
1147 	    vim_free(path_name);
1148 	    return FAIL;
1149 	}
1150 	name = p;
1151 	parent = menu;
1152 	menu = menu->children;
1153     }
1154     vim_free(path_name);
1155 
1156     // Now we have found the matching menu, and we list the mappings
1157 						    // Highlight title
1158     msg_puts_title(_("\n--- Menus ---"));
1159 
1160     show_menus_recursive(parent, modes, 0);
1161     return OK;
1162 }
1163 
1164 /*
1165  * Recursively show the mappings associated with the menus under the given one
1166  */
1167     static void
show_menus_recursive(vimmenu_T * menu,int modes,int depth)1168 show_menus_recursive(vimmenu_T *menu, int modes, int depth)
1169 {
1170     int		i;
1171     int		bit;
1172 
1173     if (menu != NULL && (menu->modes & modes) == 0x0)
1174 	return;
1175 
1176     if (menu != NULL)
1177     {
1178 	msg_putchar('\n');
1179 	if (got_int)		// "q" hit for "--more--"
1180 	    return;
1181 	for (i = 0; i < depth; i++)
1182 	    msg_puts("  ");
1183 	if (menu->priority)
1184 	{
1185 	    msg_outnum((long)menu->priority);
1186 	    msg_puts(" ");
1187 	}
1188 				// Same highlighting as for directories!?
1189 	msg_outtrans_attr(menu->name, HL_ATTR(HLF_D));
1190     }
1191 
1192     if (menu != NULL && menu->children == NULL)
1193     {
1194 	for (bit = 0; bit < MENU_MODES; bit++)
1195 	    if ((menu->modes & modes & (1 << bit)) != 0)
1196 	    {
1197 		msg_putchar('\n');
1198 		if (got_int)		// "q" hit for "--more--"
1199 		    return;
1200 		for (i = 0; i < depth + 2; i++)
1201 		    msg_puts("  ");
1202 		msg_puts(menu_mode_chars[bit]);
1203 		if (menu->noremap[bit] == REMAP_NONE)
1204 		    msg_putchar('*');
1205 		else if (menu->noremap[bit] == REMAP_SCRIPT)
1206 		    msg_putchar('&');
1207 		else
1208 		    msg_putchar(' ');
1209 		if (menu->silent[bit])
1210 		    msg_putchar('s');
1211 		else
1212 		    msg_putchar(' ');
1213 		if ((menu->modes & menu->enabled & (1 << bit)) == 0)
1214 		    msg_putchar('-');
1215 		else
1216 		    msg_putchar(' ');
1217 		msg_puts(" ");
1218 		if (*menu->strings[bit] == NUL)
1219 		    msg_puts_attr("<Nop>", HL_ATTR(HLF_8));
1220 		else
1221 		    msg_outtrans_special(menu->strings[bit], FALSE, 0);
1222 	    }
1223     }
1224     else
1225     {
1226 	if (menu == NULL)
1227 	{
1228 	    menu = root_menu;
1229 	    depth--;
1230 	}
1231 	else
1232 	    menu = menu->children;
1233 
1234 	// recursively show all children.  Skip PopUp[nvoci].
1235 	for (; menu != NULL && !got_int; menu = menu->next)
1236 	    if (!menu_is_hidden(menu->dname))
1237 		show_menus_recursive(menu, modes, depth + 1);
1238     }
1239 }
1240 
1241 /*
1242  * Used when expanding menu names.
1243  */
1244 static vimmenu_T	*expand_menu = NULL;
1245 static vimmenu_T	*expand_menu_alt = NULL;
1246 static int		expand_modes = 0x0;
1247 static int		expand_emenu;	// TRUE for ":emenu" command
1248 
1249 /*
1250  * Work out what to complete when doing command line completion of menu names.
1251  */
1252     char_u *
set_context_in_menu_cmd(expand_T * xp,char_u * cmd,char_u * arg,int forceit)1253 set_context_in_menu_cmd(
1254     expand_T	*xp,
1255     char_u	*cmd,
1256     char_u	*arg,
1257     int		forceit)
1258 {
1259     char_u	*after_dot;
1260     char_u	*p;
1261     char_u	*path_name = NULL;
1262     char_u	*name;
1263     int		unmenu;
1264     vimmenu_T	*menu;
1265     int		expand_menus;
1266 
1267     xp->xp_context = EXPAND_UNSUCCESSFUL;
1268 
1269 
1270     // Check for priority numbers, enable and disable
1271     for (p = arg; *p; ++p)
1272 	if (!VIM_ISDIGIT(*p) && *p != '.')
1273 	    break;
1274 
1275     if (!VIM_ISWHITE(*p))
1276     {
1277 	if (STRNCMP(arg, "enable", 6) == 0
1278 		&& (arg[6] == NUL ||  VIM_ISWHITE(arg[6])))
1279 	    p = arg + 6;
1280 	else if (STRNCMP(arg, "disable", 7) == 0
1281 		&& (arg[7] == NUL || VIM_ISWHITE(arg[7])))
1282 	    p = arg + 7;
1283 	else
1284 	    p = arg;
1285     }
1286 
1287     while (*p != NUL && VIM_ISWHITE(*p))
1288 	++p;
1289 
1290     arg = after_dot = p;
1291 
1292     for (; *p && !VIM_ISWHITE(*p); ++p)
1293     {
1294 	if ((*p == '\\' || *p == Ctrl_V) && p[1] != NUL)
1295 	    p++;
1296 	else if (*p == '.')
1297 	    after_dot = p + 1;
1298     }
1299 
1300     // ":tearoff" and ":popup" only use menus, not entries
1301     expand_menus = !((*cmd == 't' && cmd[1] == 'e') || *cmd == 'p');
1302     expand_emenu = (*cmd == 'e');
1303     if (expand_menus && VIM_ISWHITE(*p))
1304 	return NULL;	// TODO: check for next command?
1305     if (*p == NUL)		// Complete the menu name
1306     {
1307 	int try_alt_menu = TRUE;
1308 
1309 	/*
1310 	 * With :unmenu, you only want to match menus for the appropriate mode.
1311 	 * With :menu though you might want to add a menu with the same name as
1312 	 * one in another mode, so match menus from other modes too.
1313 	 */
1314 	expand_modes = get_menu_cmd_modes(cmd, forceit, NULL, &unmenu);
1315 	if (!unmenu)
1316 	    expand_modes = MENU_ALL_MODES;
1317 
1318 	menu = root_menu;
1319 	if (after_dot != arg)
1320 	{
1321 	    path_name = alloc(after_dot - arg);
1322 	    if (path_name == NULL)
1323 		return NULL;
1324 	    vim_strncpy(path_name, arg, after_dot - arg - 1);
1325 	}
1326 	name = path_name;
1327 	while (name != NULL && *name)
1328 	{
1329 	    p = menu_name_skip(name);
1330 	    while (menu != NULL)
1331 	    {
1332 		if (menu_name_equal(name, menu))
1333 		{
1334 		    // Found menu
1335 		    if ((*p != NUL && menu->children == NULL)
1336 			|| ((menu->modes & expand_modes) == 0x0))
1337 		    {
1338 			/*
1339 			 * Menu path continues, but we have reached a leaf.
1340 			 * Or menu exists only in another mode.
1341 			 */
1342 			vim_free(path_name);
1343 			return NULL;
1344 		    }
1345 		    break;
1346 		}
1347 		menu = menu->next;
1348 		if (menu == NULL && try_alt_menu)
1349 		{
1350 		    menu = curwin->w_winbar;
1351 		    try_alt_menu = FALSE;
1352 		}
1353 	    }
1354 	    if (menu == NULL)
1355 	    {
1356 		// No menu found with the name we were looking for
1357 		vim_free(path_name);
1358 		return NULL;
1359 	    }
1360 	    name = p;
1361 	    menu = menu->children;
1362 	    try_alt_menu = FALSE;
1363 	}
1364 	vim_free(path_name);
1365 
1366 	xp->xp_context = expand_menus ? EXPAND_MENUNAMES : EXPAND_MENUS;
1367 	xp->xp_pattern = after_dot;
1368 	expand_menu = menu;
1369 	if (expand_menu == root_menu)
1370 	    expand_menu_alt = curwin->w_winbar;
1371 	else
1372 	    expand_menu_alt = NULL;
1373     }
1374     else			// We're in the mapping part
1375 	xp->xp_context = EXPAND_NOTHING;
1376     return NULL;
1377 }
1378 
1379 /*
1380  * Function given to ExpandGeneric() to obtain the list of (sub)menus (not
1381  * entries).
1382  */
1383     char_u *
get_menu_name(expand_T * xp UNUSED,int idx)1384 get_menu_name(expand_T *xp UNUSED, int idx)
1385 {
1386     static vimmenu_T	*menu = NULL;
1387     static int		did_alt_menu = FALSE;
1388     char_u		*str;
1389 #ifdef FEAT_MULTI_LANG
1390     static  int		should_advance = FALSE;
1391 #endif
1392 
1393     if (idx == 0)	    // first call: start at first item
1394     {
1395 	menu = expand_menu;
1396 	did_alt_menu = FALSE;
1397 #ifdef FEAT_MULTI_LANG
1398 	should_advance = FALSE;
1399 #endif
1400     }
1401 
1402     // Skip PopUp[nvoci].
1403     while (menu != NULL && (menu_is_hidden(menu->dname)
1404 	    || menu_is_separator(menu->dname)
1405 	    || menu_is_tearoff(menu->dname)
1406 	    || menu->children == NULL))
1407     {
1408 	menu = menu->next;
1409 	if (menu == NULL && !did_alt_menu)
1410 	{
1411 	    menu = expand_menu_alt;
1412 	    did_alt_menu = TRUE;
1413 	}
1414     }
1415 
1416     if (menu == NULL)	    // at end of linked list
1417 	return NULL;
1418 
1419     if (menu->modes & expand_modes)
1420 #ifdef FEAT_MULTI_LANG
1421 	if (should_advance)
1422 	    str = menu->en_dname;
1423 	else
1424 	{
1425 #endif
1426 	    str = menu->dname;
1427 #ifdef FEAT_MULTI_LANG
1428 	    if (menu->en_dname == NULL)
1429 		should_advance = TRUE;
1430 	}
1431 #endif
1432     else
1433 	str = (char_u *)"";
1434 
1435 #ifdef FEAT_MULTI_LANG
1436     if (should_advance)
1437 #endif
1438     {
1439 	// Advance to next menu entry.
1440 	menu = menu->next;
1441 	if (menu == NULL && !did_alt_menu)
1442 	{
1443 	    menu = expand_menu_alt;
1444 	    did_alt_menu = TRUE;
1445 	}
1446     }
1447 
1448 #ifdef FEAT_MULTI_LANG
1449     should_advance = !should_advance;
1450 #endif
1451 
1452     return str;
1453 }
1454 
1455 /*
1456  * Function given to ExpandGeneric() to obtain the list of menus and menu
1457  * entries.
1458  */
1459     char_u *
get_menu_names(expand_T * xp UNUSED,int idx)1460 get_menu_names(expand_T *xp UNUSED, int idx)
1461 {
1462     static vimmenu_T	*menu = NULL;
1463     static int		did_alt_menu = FALSE;
1464 #define TBUFFER_LEN 256
1465     static char_u	tbuffer[TBUFFER_LEN]; //hack
1466     char_u		*str;
1467 #ifdef FEAT_MULTI_LANG
1468     static  int		should_advance = FALSE;
1469 #endif
1470 
1471     if (idx == 0)	    // first call: start at first item
1472     {
1473 	menu = expand_menu;
1474 	did_alt_menu = FALSE;
1475 #ifdef FEAT_MULTI_LANG
1476 	should_advance = FALSE;
1477 #endif
1478     }
1479 
1480     // Skip Browse-style entries, popup menus and separators.
1481     while (menu != NULL
1482 	    && (   menu_is_hidden(menu->dname)
1483 		|| (expand_emenu && menu_is_separator(menu->dname))
1484 		|| menu_is_tearoff(menu->dname)
1485 #ifndef FEAT_BROWSE
1486 		|| menu->dname[STRLEN(menu->dname) - 1] == '.'
1487 #endif
1488 	       ))
1489     {
1490 	menu = menu->next;
1491 	if (menu == NULL && !did_alt_menu)
1492 	{
1493 	    menu = expand_menu_alt;
1494 	    did_alt_menu = TRUE;
1495 	}
1496     }
1497 
1498     if (menu == NULL)	    // at end of linked list
1499 	return NULL;
1500 
1501     if (menu->modes & expand_modes)
1502     {
1503 	if (menu->children != NULL)
1504 	{
1505 #ifdef FEAT_MULTI_LANG
1506 	    if (should_advance)
1507 		vim_strncpy(tbuffer, menu->en_dname, TBUFFER_LEN - 2);
1508 	    else
1509 	    {
1510 #endif
1511 		vim_strncpy(tbuffer, menu->dname,  TBUFFER_LEN - 2);
1512 #ifdef FEAT_MULTI_LANG
1513 		if (menu->en_dname == NULL)
1514 		    should_advance = TRUE;
1515 	    }
1516 #endif
1517 	    // hack on menu separators:  use a 'magic' char for the separator
1518 	    // so that '.' in names gets escaped properly
1519 	    STRCAT(tbuffer, "\001");
1520 	    str = tbuffer;
1521 	}
1522 	else
1523 #ifdef FEAT_MULTI_LANG
1524 	{
1525 	    if (should_advance)
1526 		str = menu->en_dname;
1527 	    else
1528 	    {
1529 #endif
1530 		str = menu->dname;
1531 #ifdef FEAT_MULTI_LANG
1532 		if (menu->en_dname == NULL)
1533 		    should_advance = TRUE;
1534 	    }
1535 	}
1536 #endif
1537     }
1538     else
1539 	str = (char_u *)"";
1540 
1541 #ifdef FEAT_MULTI_LANG
1542     if (should_advance)
1543 #endif
1544     {
1545 	// Advance to next menu entry.
1546 	menu = menu->next;
1547 	if (menu == NULL && !did_alt_menu)
1548 	{
1549 	    menu = expand_menu_alt;
1550 	    did_alt_menu = TRUE;
1551 	}
1552     }
1553 
1554 #ifdef FEAT_MULTI_LANG
1555     should_advance = !should_advance;
1556 #endif
1557 
1558     return str;
1559 }
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.
1564  * "name" may be modified.
1565  */
1566     static char_u *
menu_name_skip(char_u * name)1567 menu_name_skip(char_u *name)
1568 {
1569     char_u  *p;
1570 
1571     for (p = name; *p && *p != '.'; MB_PTR_ADV(p))
1572     {
1573 	if (*p == '\\' || *p == Ctrl_V)
1574 	{
1575 	    STRMOVE(p, p + 1);
1576 	    if (*p == NUL)
1577 		break;
1578 	}
1579     }
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
menu_name_equal(char_u * name,vimmenu_T * menu)1590 menu_name_equal(char_u *name, vimmenu_T *menu)
1591 {
1592 #ifdef FEAT_MULTI_LANG
1593     if (menu->en_name != NULL
1594 	    && (menu_namecmp(name, menu->en_name)
1595 		|| menu_namecmp(name, menu->en_dname)))
1596 	return TRUE;
1597 #endif
1598     return menu_namecmp(name, menu->name) || menu_namecmp(name, menu->dname);
1599 }
1600 
1601     static int
menu_namecmp(char_u * name,char_u * mname)1602 menu_namecmp(char_u *name, char_u *mname)
1603 {
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
get_menu_cmd_modes(char_u * cmd,int forceit,int * noremap,int * unmenu)1622 get_menu_cmd_modes(
1623     char_u  *cmd,
1624     int	    forceit,	    // Was there a "!" after the command?
1625     int	    *noremap,
1626     int	    *unmenu)
1627 {
1628     int	    modes;
1629 
1630     switch (*cmd++)
1631     {
1632 	case 'v':			// vmenu, vunmenu, vnoremenu
1633 	    modes = MENU_VISUAL_MODE | MENU_SELECT_MODE;
1634 	    break;
1635 	case 'x':			// xmenu, xunmenu, xnoremenu
1636 	    modes = MENU_VISUAL_MODE;
1637 	    break;
1638 	case 's':			// smenu, sunmenu, snoremenu
1639 	    modes = MENU_SELECT_MODE;
1640 	    break;
1641 	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 	    if (*cmd == 'l')            // tlmenu, tlunmenu, tlnoremenu
1649 	    {
1650 		modes = MENU_TERMINAL_MODE;
1651 		++cmd;
1652 		break;
1653 	    }
1654 	    modes = MENU_TIP_MODE;	// tmenu
1655 	    break;
1656 	case 'c':			// cmenu
1657 	    modes = MENU_CMDLINE_MODE;
1658 	    break;
1659 	case 'a':			// amenu
1660 	    modes = MENU_INSERT_MODE | MENU_CMDLINE_MODE | MENU_NORMAL_MODE
1661 				    | MENU_VISUAL_MODE | MENU_SELECT_MODE
1662 				    | MENU_OP_PENDING_MODE;
1663 	    break;
1664 	case 'n':
1665 	    if (*cmd != 'o')		// nmenu, not noremenu
1666 	    {
1667 		modes = MENU_NORMAL_MODE;
1668 		break;
1669 	    }
1670 	    // FALLTHROUGH
1671 	default:
1672 	    --cmd;
1673 	    if (forceit)		// menu!!
1674 		modes = MENU_INSERT_MODE | MENU_CMDLINE_MODE;
1675 	    else			// menu
1676 		modes = MENU_NORMAL_MODE | MENU_VISUAL_MODE | MENU_SELECT_MODE
1677 						       | MENU_OP_PENDING_MODE;
1678     }
1679 
1680     if (noremap != NULL)
1681 	*noremap = (*cmd == 'n' ? REMAP_NONE : REMAP_YES);
1682     if (unmenu != NULL)
1683 	*unmenu = (*cmd == 'u');
1684     return modes;
1685 }
1686 
1687 /*
1688  * Return the string representation of the menu modes. Does the opposite
1689  * of get_menu_cmd_modes().
1690  */
1691     static char_u *
get_menu_mode_str(int modes)1692 get_menu_mode_str(int modes)
1693 {
1694     if ((modes & (MENU_INSERT_MODE | MENU_CMDLINE_MODE | MENU_NORMAL_MODE |
1695 		  MENU_VISUAL_MODE | MENU_SELECT_MODE | MENU_OP_PENDING_MODE))
1696 	    == (MENU_INSERT_MODE | MENU_CMDLINE_MODE | MENU_NORMAL_MODE |
1697 		MENU_VISUAL_MODE | MENU_SELECT_MODE | MENU_OP_PENDING_MODE))
1698 	return (char_u *)"a";
1699     if ((modes & (MENU_NORMAL_MODE | MENU_VISUAL_MODE | MENU_SELECT_MODE |
1700 		  MENU_OP_PENDING_MODE))
1701 	    == (MENU_NORMAL_MODE | MENU_VISUAL_MODE | MENU_SELECT_MODE |
1702 		MENU_OP_PENDING_MODE))
1703 	return (char_u *)" ";
1704     if ((modes & (MENU_INSERT_MODE | MENU_CMDLINE_MODE))
1705 	    == (MENU_INSERT_MODE | MENU_CMDLINE_MODE))
1706 	return (char_u *)"!";
1707     if ((modes & (MENU_VISUAL_MODE | MENU_SELECT_MODE))
1708 	    == (MENU_VISUAL_MODE | MENU_SELECT_MODE))
1709 	return (char_u *)"v";
1710     if (modes & MENU_VISUAL_MODE)
1711 	return (char_u *)"x";
1712     if (modes & MENU_SELECT_MODE)
1713 	return (char_u *)"s";
1714     if (modes & MENU_OP_PENDING_MODE)
1715 	return (char_u *)"o";
1716     if (modes & MENU_INSERT_MODE)
1717 	return (char_u *)"i";
1718     if (modes & MENU_TERMINAL_MODE)
1719 	return (char_u *)"tl";
1720     if (modes & MENU_CMDLINE_MODE)
1721 	return (char_u *)"c";
1722     if (modes & MENU_NORMAL_MODE)
1723 	return (char_u *)"n";
1724     if (modes & MENU_TIP_MODE)
1725 	return (char_u *)"t";
1726 
1727     return (char_u *)"";
1728 }
1729 
1730 /*
1731  * Modify a menu name starting with "PopUp" to include the mode character.
1732  * Returns the name in allocated memory (NULL for failure).
1733  */
1734     static char_u *
popup_mode_name(char_u * name,int idx)1735 popup_mode_name(char_u *name, int idx)
1736 {
1737     char_u	*p;
1738     int		len = (int)STRLEN(name);
1739     char	*mode_chars = menu_mode_chars[idx];
1740     int		mode_chars_len = (int)strlen(mode_chars);
1741     int		i;
1742 
1743     p = vim_strnsave(name, len + mode_chars_len);
1744     if (p != NULL)
1745     {
1746 	mch_memmove(p + 5 + mode_chars_len, p + 5, (size_t)(len - 4));
1747 	for (i = 0; i < mode_chars_len; ++i)
1748 	    p[5 + i] = menu_mode_chars[idx][i];
1749     }
1750     return p;
1751 }
1752 
1753 #if defined(FEAT_GUI) || defined(PROTO)
1754 /*
1755  * Return the index into the menu->strings or menu->noremap arrays for the
1756  * current state.  Returns MENU_INDEX_INVALID if there is no mapping for the
1757  * given menu in the current mode.
1758  */
1759     int
get_menu_index(vimmenu_T * menu,int state)1760 get_menu_index(vimmenu_T *menu, int state)
1761 {
1762     int		idx;
1763 
1764     if ((state & INSERT))
1765 	idx = MENU_INDEX_INSERT;
1766     else if (state & CMDLINE)
1767 	idx = MENU_INDEX_CMDLINE;
1768 #ifdef FEAT_TERMINAL
1769     else if (term_use_loop())
1770 	idx = MENU_INDEX_TERMINAL;
1771 #endif
1772     else if (VIsual_active)
1773     {
1774 	if (VIsual_select)
1775 	    idx = MENU_INDEX_SELECT;
1776 	else
1777 	    idx = MENU_INDEX_VISUAL;
1778     }
1779     else if (state == HITRETURN || state == ASKMORE)
1780 	idx = MENU_INDEX_CMDLINE;
1781     else if (finish_op)
1782 	idx = MENU_INDEX_OP_PENDING;
1783     else if ((state & NORMAL))
1784 	idx = MENU_INDEX_NORMAL;
1785     else
1786 	idx = MENU_INDEX_INVALID;
1787 
1788     if (idx != MENU_INDEX_INVALID && menu->strings[idx] == NULL)
1789 	idx = MENU_INDEX_INVALID;
1790     return idx;
1791 }
1792 #endif
1793 
1794 /*
1795  * Duplicate the menu item text and then process to see if a mnemonic key
1796  * and/or accelerator text has been identified.
1797  * Returns a pointer to allocated memory, or NULL for failure.
1798  * If mnemonic != NULL, *mnemonic is set to the character after the first '&'.
1799  * If actext != NULL, *actext is set to the text after the first TAB.
1800  */
1801     static char_u *
menu_text(char_u * str,int * mnemonic,char_u ** actext)1802 menu_text(char_u *str, int *mnemonic, char_u **actext)
1803 {
1804     char_u	*p;
1805     char_u	*text;
1806 
1807     // Locate accelerator text, after the first TAB
1808     p = vim_strchr(str, TAB);
1809     if (p != NULL)
1810     {
1811 	if (actext != NULL)
1812 	    *actext = vim_strsave(p + 1);
1813 	text = vim_strnsave(str, p - str);
1814     }
1815     else
1816 	text = vim_strsave(str);
1817 
1818     // Find mnemonic characters "&a" and reduce "&&" to "&".
1819     for (p = text; p != NULL; )
1820     {
1821 	p = vim_strchr(p, '&');
1822 	if (p != NULL)
1823 	{
1824 	    if (p[1] == NUL)	    // trailing "&"
1825 		break;
1826 	    if (mnemonic != NULL && p[1] != '&')
1827 #if !defined(__MVS__) || defined(MOTIF390_MNEMONIC_FIXED)
1828 		*mnemonic = p[1];
1829 #else
1830 	    {
1831 		/*
1832 		 * Well there is a bug in the Motif libraries on OS390 Unix.
1833 		 * The mnemonic keys needs to be converted to ASCII values
1834 		 * first.
1835 		 * This behavior has been seen in 2.8 and 2.9.
1836 		 */
1837 		char c = p[1];
1838 		__etoa_l(&c, 1);
1839 		*mnemonic = c;
1840 	    }
1841 #endif
1842 	    STRMOVE(p, p + 1);
1843 	    p = p + 1;
1844 	}
1845     }
1846     return text;
1847 }
1848 
1849 /*
1850  * Return TRUE if "name" can be a menu in the MenuBar.
1851  */
1852     int
menu_is_menubar(char_u * name)1853 menu_is_menubar(char_u *name)
1854 {
1855     return (!menu_is_popup(name)
1856 	    && !menu_is_toolbar(name)
1857 	    && !menu_is_winbar(name)
1858 	    && *name != MNU_HIDDEN_CHAR);
1859 }
1860 
1861 /*
1862  * Return TRUE if "name" is a popup menu name.
1863  */
1864     int
menu_is_popup(char_u * name)1865 menu_is_popup(char_u *name)
1866 {
1867     return (STRNCMP(name, "PopUp", 5) == 0);
1868 }
1869 
1870 #if (defined(FEAT_GUI_MOTIF) && (XmVersion <= 1002)) || defined(PROTO)
1871 /*
1872  * Return TRUE if "name" is part of a popup menu.
1873  */
1874     int
menu_is_child_of_popup(vimmenu_T * menu)1875 menu_is_child_of_popup(vimmenu_T *menu)
1876 {
1877     while (menu->parent != NULL)
1878 	menu = menu->parent;
1879     return menu_is_popup(menu->name);
1880 }
1881 #endif
1882 
1883 /*
1884  * Return TRUE if "name" is a toolbar menu name.
1885  */
1886     int
menu_is_toolbar(char_u * name)1887 menu_is_toolbar(char_u *name)
1888 {
1889     return (STRNCMP(name, "ToolBar", 7) == 0);
1890 }
1891 
1892 /*
1893  * Return TRUE if the name is a menu separator identifier: Starts and ends
1894  * with '-'
1895  */
1896     int
menu_is_separator(char_u * name)1897 menu_is_separator(char_u *name)
1898 {
1899     return (name[0] == '-' && name[STRLEN(name) - 1] == '-');
1900 }
1901 
1902 /*
1903  * Return TRUE if the menu is hidden:  Starts with ']'
1904  */
1905     static int
menu_is_hidden(char_u * name)1906 menu_is_hidden(char_u *name)
1907 {
1908     return (name[0] == ']') || (menu_is_popup(name) && name[5] != NUL);
1909 }
1910 
1911 /*
1912  * Return TRUE if the menu is the tearoff menu.
1913  */
1914     static int
menu_is_tearoff(char_u * name UNUSED)1915 menu_is_tearoff(char_u *name UNUSED)
1916 {
1917 #ifdef FEAT_GUI
1918     return (STRCMP(name, TEAR_STRING) == 0);
1919 #else
1920     return FALSE;
1921 #endif
1922 }
1923 
1924 #if defined(FEAT_GUI) || defined(FEAT_TERM_POPUP_MENU) || defined(PROTO)
1925 
1926     static int
get_menu_mode(void)1927 get_menu_mode(void)
1928 {
1929 #ifdef FEAT_TERMINAL
1930     if (term_use_loop())
1931 	return MENU_INDEX_TERMINAL;
1932 #endif
1933     if (VIsual_active)
1934     {
1935 	if (VIsual_select)
1936 	    return MENU_INDEX_SELECT;
1937 	return MENU_INDEX_VISUAL;
1938     }
1939     if (State & INSERT)
1940 	return MENU_INDEX_INSERT;
1941     if ((State & CMDLINE) || State == ASKMORE || State == HITRETURN)
1942 	return MENU_INDEX_CMDLINE;
1943     if (finish_op)
1944 	return MENU_INDEX_OP_PENDING;
1945     if (State & NORMAL)
1946 	return MENU_INDEX_NORMAL;
1947     if (State & LANGMAP)	// must be a "r" command, like Insert mode
1948 	return MENU_INDEX_INSERT;
1949     return MENU_INDEX_INVALID;
1950 }
1951 
1952     int
get_menu_mode_flag(void)1953 get_menu_mode_flag(void)
1954 {
1955     int mode = get_menu_mode();
1956 
1957     if (mode == MENU_INDEX_INVALID)
1958 	return 0;
1959     return 1 << mode;
1960 }
1961 
1962 /*
1963  * Display the Special "PopUp" menu as a pop-up at the current mouse
1964  * position.  The "PopUpn" menu is for Normal mode, "PopUpi" for Insert mode,
1965  * etc.
1966  */
1967     void
show_popupmenu(void)1968 show_popupmenu(void)
1969 {
1970     vimmenu_T	*menu;
1971     int		menu_mode;
1972     char*	mode;
1973     int		mode_len;
1974 
1975     menu_mode = get_menu_mode();
1976     if (menu_mode == MENU_INDEX_INVALID)
1977 	return;
1978     mode = menu_mode_chars[menu_mode];
1979     mode_len = (int)strlen(mode);
1980 
1981     apply_autocmds(EVENT_MENUPOPUP, (char_u*)mode, NULL, FALSE, curbuf);
1982 
1983     FOR_ALL_MENUS(menu)
1984 	if (STRNCMP("PopUp", menu->name, 5) == 0 && STRNCMP(menu->name + 5, mode, mode_len) == 0)
1985 	    break;
1986 
1987     // Only show a popup when it is defined and has entries
1988     if (menu != NULL && menu->children != NULL)
1989     {
1990 # if defined(FEAT_GUI)
1991 	if (gui.in_use)
1992 	{
1993 	    // Update the menus now, in case the MenuPopup autocommand did
1994 	    // anything.
1995 	    gui_update_menus(0);
1996 	    gui_mch_show_popupmenu(menu);
1997 	}
1998 # endif
1999 #  if defined(FEAT_GUI) && defined(FEAT_TERM_POPUP_MENU)
2000 	else
2001 #  endif
2002 #  if defined(FEAT_TERM_POPUP_MENU)
2003 	    pum_show_popupmenu(menu);
2004 #  endif
2005     }
2006 }
2007 #endif
2008 
2009 #if defined(FEAT_GUI) || defined(PROTO)
2010 
2011 /*
2012  * Check that a pointer appears in the menu tree.  Used to protect from using
2013  * a menu that was deleted after it was selected but before the event was
2014  * handled.
2015  * Return OK or FAIL.  Used recursively.
2016  */
2017     int
check_menu_pointer(vimmenu_T * root,vimmenu_T * menu_to_check)2018 check_menu_pointer(vimmenu_T *root, vimmenu_T *menu_to_check)
2019 {
2020     vimmenu_T	*p;
2021 
2022     for (p = root; p != NULL; p = p->next)
2023 	if (p == menu_to_check
2024 		|| (p->children != NULL
2025 		    && check_menu_pointer(p->children, menu_to_check) == OK))
2026 	    return OK;
2027     return FAIL;
2028 }
2029 
2030 /*
2031  * After we have started the GUI, then we can create any menus that have been
2032  * defined.  This is done once here.  add_menu_path() may have already been
2033  * called to define these menus, and may be called again.  This function calls
2034  * itself recursively.	Should be called at the top level with:
2035  * gui_create_initial_menus(root_menu);
2036  */
2037     void
gui_create_initial_menus(vimmenu_T * menu)2038 gui_create_initial_menus(vimmenu_T *menu)
2039 {
2040     int		idx = 0;
2041 
2042     while (menu != NULL)
2043     {
2044 	// Don't add a menu when only a tip was defined.
2045 	if (menu->modes & MENU_ALL_MODES)
2046 	{
2047 	    if (menu->children != NULL)
2048 	    {
2049 		gui_mch_add_menu(menu, idx);
2050 		gui_create_initial_menus(menu->children);
2051 	    }
2052 	    else
2053 		gui_mch_add_menu_item(menu, idx);
2054 	}
2055 	menu = menu->next;
2056 	++idx;
2057     }
2058 }
2059 
2060 /*
2061  * Used recursively by gui_update_menus (see below)
2062  */
2063     static void
gui_update_menus_recurse(vimmenu_T * menu,int mode)2064 gui_update_menus_recurse(vimmenu_T *menu, int mode)
2065 {
2066     int		grey;
2067 
2068     while (menu)
2069     {
2070 	if ((menu->modes & menu->enabled & mode)
2071 # if defined(FEAT_GUI_MSWIN) && defined(FEAT_TEAROFF)
2072 		|| menu_is_tearoff(menu->dname)
2073 # endif
2074 	   )
2075 	    grey = FALSE;
2076 	else
2077 	    grey = TRUE;
2078 # ifdef FEAT_GUI_ATHENA
2079 	// Hiding menus doesn't work for Athena, it can cause a crash.
2080 	gui_mch_menu_grey(menu, grey);
2081 # else
2082 	// Never hide a toplevel menu, it may make the menubar resize or
2083 	// disappear. Same problem for ToolBar items.
2084 	if (vim_strchr(p_go, GO_GREY) != NULL || menu->parent == NULL
2085 #  ifdef FEAT_TOOLBAR
2086 		|| menu_is_toolbar(menu->parent->name)
2087 #  endif
2088 		   )
2089 	    gui_mch_menu_grey(menu, grey);
2090 	else
2091 	    gui_mch_menu_hidden(menu, grey);
2092 # endif
2093 	gui_update_menus_recurse(menu->children, mode);
2094 	menu = menu->next;
2095     }
2096 }
2097 
2098 /*
2099  * Make sure only the valid menu items appear for this mode.  If
2100  * force_menu_update is not TRUE, then we only do this if the mode has changed
2101  * since last time.  If "modes" is not 0, then we use these modes instead.
2102  */
2103     void
gui_update_menus(int modes)2104 gui_update_menus(int modes)
2105 {
2106     static int	    prev_mode = -1;
2107     int		    mode = 0;
2108 
2109     if (modes != 0x0)
2110 	mode = modes;
2111     else
2112 	mode = get_menu_mode_flag();
2113 
2114     if (force_menu_update || mode != prev_mode)
2115     {
2116 	gui_update_menus_recurse(root_menu, mode);
2117 	gui_mch_draw_menubar();
2118 	prev_mode = mode;
2119 	force_menu_update = FALSE;
2120     }
2121 }
2122 
2123 # if defined(FEAT_GUI_MSWIN) || defined(FEAT_GUI_MOTIF) \
2124     || defined(FEAT_GUI_GTK) || defined(FEAT_GUI_PHOTON) || defined(PROTO)
2125 /*
2126  * Check if a key is used as a mnemonic for a toplevel menu.
2127  * Case of the key is ignored.
2128  */
2129     int
gui_is_menu_shortcut(int key)2130 gui_is_menu_shortcut(int key)
2131 {
2132     vimmenu_T	*menu;
2133 
2134     if (key < 256)
2135 	key = TOLOWER_LOC(key);
2136     FOR_ALL_MENUS(menu)
2137 	if (menu->mnemonic == key
2138 		|| (menu->mnemonic < 256 && TOLOWER_LOC(menu->mnemonic) == key))
2139 	    return TRUE;
2140     return FALSE;
2141 }
2142 # endif
2143 #endif // FEAT_GUI
2144 
2145 #if (defined(FEAT_GUI_MSWIN) && defined(FEAT_TEAROFF)) || defined(PROTO)
2146 
2147 /*
2148  * Deal with tearoff items that are added like a menu item.
2149  * Currently only for Win32 GUI.  Others may follow later.
2150  */
2151 
2152     void
gui_mch_toggle_tearoffs(int enable)2153 gui_mch_toggle_tearoffs(int enable)
2154 {
2155     int		pri_tab[MENUDEPTH + 1];
2156     int		i;
2157 
2158     if (enable)
2159     {
2160 	for (i = 0; i < MENUDEPTH; ++i)
2161 	    pri_tab[i] = 500;
2162 	pri_tab[MENUDEPTH] = -1;
2163 	gui_create_tearoffs_recurse(root_menu, (char_u *)"", pri_tab, 0);
2164     }
2165     else
2166 	gui_destroy_tearoffs_recurse(root_menu);
2167     s_tearoffs = enable;
2168 }
2169 
2170 /*
2171  * Recursively add tearoff items
2172  */
2173     static void
gui_create_tearoffs_recurse(vimmenu_T * menu,const char_u * pname,int * pri_tab,int pri_idx)2174 gui_create_tearoffs_recurse(
2175     vimmenu_T		*menu,
2176     const char_u	*pname,
2177     int			*pri_tab,
2178     int			pri_idx)
2179 {
2180     char_u	*newpname = NULL;
2181     int		len;
2182     char_u	*s;
2183     char_u	*d;
2184 
2185     if (pri_tab[pri_idx + 1] != -1)
2186 	++pri_idx;
2187     while (menu != NULL)
2188     {
2189 	if (menu->children != NULL && menu_is_menubar(menu->name))
2190 	{
2191 	    // Add the menu name to the menu path.  Insert a backslash before
2192 	    // dots (it's used to separate menu names).
2193 	    len = (int)STRLEN(pname) + (int)STRLEN(menu->name);
2194 	    for (s = menu->name; *s; ++s)
2195 		if (*s == '.' || *s == '\\')
2196 		    ++len;
2197 	    newpname = alloc(len + TEAR_LEN + 2);
2198 	    if (newpname != NULL)
2199 	    {
2200 		STRCPY(newpname, pname);
2201 		d = newpname + STRLEN(newpname);
2202 		for (s = menu->name; *s; ++s)
2203 		{
2204 		    if (*s == '.' || *s == '\\')
2205 			*d++ = '\\';
2206 		    *d++ = *s;
2207 		}
2208 		*d = NUL;
2209 
2210 		// check if tearoff already exists
2211 		if (STRCMP(menu->children->name, TEAR_STRING) != 0)
2212 		{
2213 		    gui_add_tearoff(newpname, pri_tab, pri_idx - 1);
2214 		    *d = NUL;			// remove TEAR_STRING
2215 		}
2216 
2217 		STRCAT(newpname, ".");
2218 		gui_create_tearoffs_recurse(menu->children, newpname,
2219 							    pri_tab, pri_idx);
2220 		vim_free(newpname);
2221 	    }
2222 	}
2223 	menu = menu->next;
2224     }
2225 }
2226 
2227 /*
2228  * Add tear-off menu item for a submenu.
2229  * "tearpath" is the menu path, and must have room to add TEAR_STRING.
2230  */
2231     static void
gui_add_tearoff(char_u * tearpath,int * pri_tab,int pri_idx)2232 gui_add_tearoff(char_u *tearpath, int *pri_tab, int pri_idx)
2233 {
2234     char_u	*tbuf;
2235     int		t;
2236     vimmenu_T	menuarg;
2237 
2238     tbuf = alloc(5 + (unsigned int)STRLEN(tearpath));
2239     if (tbuf != NULL)
2240     {
2241 	tbuf[0] = K_SPECIAL;
2242 	tbuf[1] = K_SECOND(K_TEAROFF);
2243 	tbuf[2] = K_THIRD(K_TEAROFF);
2244 	STRCPY(tbuf + 3, tearpath);
2245 	STRCAT(tbuf + 3, "\r");
2246 
2247 	STRCAT(tearpath, ".");
2248 	STRCAT(tearpath, TEAR_STRING);
2249 
2250 	// Priority of tear-off is always 1
2251 	t = pri_tab[pri_idx + 1];
2252 	pri_tab[pri_idx + 1] = 1;
2253 
2254 #ifdef FEAT_TOOLBAR
2255 	menuarg.iconfile = NULL;
2256 	menuarg.iconidx = -1;
2257 	menuarg.icon_builtin = FALSE;
2258 #endif
2259 	menuarg.noremap[0] = REMAP_NONE;
2260 	menuarg.silent[0] = TRUE;
2261 
2262 	menuarg.modes = MENU_ALL_MODES;
2263 	add_menu_path(tearpath, &menuarg, pri_tab, tbuf, FALSE);
2264 
2265 	menuarg.modes = MENU_TIP_MODE;
2266 	add_menu_path(tearpath, &menuarg, pri_tab,
2267 		(char_u *)_("Tear off this menu"), FALSE);
2268 
2269 	pri_tab[pri_idx + 1] = t;
2270 	vim_free(tbuf);
2271     }
2272 }
2273 
2274 /*
2275  * Recursively destroy tearoff items
2276  */
2277     static void
gui_destroy_tearoffs_recurse(vimmenu_T * menu)2278 gui_destroy_tearoffs_recurse(vimmenu_T *menu)
2279 {
2280     while (menu)
2281     {
2282 	if (menu->children)
2283 	{
2284 	    // check if tearoff exists
2285 	    if (STRCMP(menu->children->name, TEAR_STRING) == 0)
2286 	    {
2287 		// Disconnect the item and free the memory
2288 		free_menu(&menu->children);
2289 	    }
2290 	    if (menu->children != NULL) // if not the last one
2291 		gui_destroy_tearoffs_recurse(menu->children);
2292 	}
2293 	menu = menu->next;
2294     }
2295 }
2296 
2297 #endif // FEAT_GUI_MSWIN && FEAT_TEAROFF
2298 
2299 /*
2300  * Execute "menu".  Use by ":emenu" and the window toolbar.
2301  * "eap" is NULL for the window toolbar.
2302  * "mode_idx" specifies a MENU_INDEX_ value, use -1 to depend on the current
2303  * state.
2304  */
2305     void
execute_menu(exarg_T * eap,vimmenu_T * menu,int mode_idx)2306 execute_menu(exarg_T *eap, vimmenu_T *menu, int mode_idx)
2307 {
2308     int		idx = mode_idx;
2309 
2310     if (idx < 0)
2311     {
2312 	// Use the Insert mode entry when returning to Insert mode.
2313 	if (restart_edit && !current_sctx.sc_sid)
2314 	{
2315 	    idx = MENU_INDEX_INSERT;
2316 	}
2317 #ifdef FEAT_TERMINAL
2318 	else if (term_use_loop())
2319 	{
2320 	    idx = MENU_INDEX_TERMINAL;
2321 	}
2322 #endif
2323 	else if (VIsual_active)
2324 	{
2325 	    idx = MENU_INDEX_VISUAL;
2326 	}
2327 	else if (eap != NULL && eap->addr_count)
2328 	{
2329 	    pos_T	tpos;
2330 
2331 	    idx = MENU_INDEX_VISUAL;
2332 
2333 	    // GEDDES: This is not perfect - but it is a
2334 	    // quick way of detecting whether we are doing this from a
2335 	    // selection - see if the range matches up with the visual
2336 	    // select start and end.
2337 	    if ((curbuf->b_visual.vi_start.lnum == eap->line1)
2338 		    && (curbuf->b_visual.vi_end.lnum) == eap->line2)
2339 	    {
2340 		// Set it up for visual mode - equivalent to gv.
2341 		VIsual_mode = curbuf->b_visual.vi_mode;
2342 		tpos = curbuf->b_visual.vi_end;
2343 		curwin->w_cursor = curbuf->b_visual.vi_start;
2344 		curwin->w_curswant = curbuf->b_visual.vi_curswant;
2345 	    }
2346 	    else
2347 	    {
2348 		// Set it up for line-wise visual mode
2349 		VIsual_mode = 'V';
2350 		curwin->w_cursor.lnum = eap->line1;
2351 		curwin->w_cursor.col = 1;
2352 		tpos.lnum = eap->line2;
2353 		tpos.col = MAXCOL;
2354 		tpos.coladd = 0;
2355 	    }
2356 
2357 	    // Activate visual mode
2358 	    VIsual_active = TRUE;
2359 	    VIsual_reselect = TRUE;
2360 	    check_cursor();
2361 	    VIsual = curwin->w_cursor;
2362 	    curwin->w_cursor = tpos;
2363 
2364 	    check_cursor();
2365 
2366 	    // Adjust the cursor to make sure it is in the correct pos
2367 	    // for exclusive mode
2368 	    if (*p_sel == 'e' && gchar_cursor() != NUL)
2369 		++curwin->w_cursor.col;
2370 	}
2371     }
2372 
2373     // For the WinBar menu always use the Normal mode menu.
2374     if (idx == -1 || eap == NULL)
2375 	idx = MENU_INDEX_NORMAL;
2376 
2377     if (idx != MENU_INDEX_INVALID && menu->strings[idx] != NULL
2378 						 && (menu->modes & (1 << idx)))
2379     {
2380 	// When executing a script or function execute the commands right now.
2381 	// Also for the window toolbar.
2382 	// Otherwise put them in the typeahead buffer.
2383 	if (eap == NULL || current_sctx.sc_sid != 0)
2384 	{
2385 	    save_state_T save_state;
2386 
2387 	    ++ex_normal_busy;
2388 	    if (save_current_state(&save_state))
2389 		exec_normal_cmd(menu->strings[idx], menu->noremap[idx],
2390 							   menu->silent[idx]);
2391 	    restore_current_state(&save_state);
2392 	    --ex_normal_busy;
2393 	}
2394 	else
2395 	    ins_typebuf(menu->strings[idx], menu->noremap[idx], 0,
2396 						     TRUE, menu->silent[idx]);
2397     }
2398     else if (eap != NULL)
2399     {
2400 	char_u	*mode;
2401 
2402 	switch (idx)
2403 	{
2404 	    case MENU_INDEX_VISUAL:
2405 		mode = (char_u *)"Visual";
2406 		break;
2407 	    case MENU_INDEX_SELECT:
2408 		mode = (char_u *)"Select";
2409 		break;
2410 	    case MENU_INDEX_OP_PENDING:
2411 		mode = (char_u *)"Op-pending";
2412 		break;
2413 	    case MENU_INDEX_TERMINAL:
2414 		mode = (char_u *)"Terminal";
2415 		break;
2416 	    case MENU_INDEX_INSERT:
2417 		mode = (char_u *)"Insert";
2418 		break;
2419 	    case MENU_INDEX_CMDLINE:
2420 		mode = (char_u *)"Cmdline";
2421 		break;
2422 	    // case MENU_INDEX_TIP: cannot happen
2423 	    default:
2424 		mode = (char_u *)"Normal";
2425 	}
2426 	semsg(_("E335: Menu not defined for %s mode"), mode);
2427     }
2428 }
2429 
2430 /*
2431  * Lookup a menu by the descriptor name e.g. "File.New"
2432  * Returns NULL if the menu is not found
2433  */
2434     static vimmenu_T *
menu_getbyname(char_u * name_arg)2435 menu_getbyname(char_u *name_arg)
2436 {
2437     char_u	*name;
2438     char_u	*saved_name;
2439     vimmenu_T	*menu;
2440     char_u	*p;
2441     int		gave_emsg = FALSE;
2442 
2443     saved_name = vim_strsave(name_arg);
2444     if (saved_name == NULL)
2445 	return NULL;
2446 
2447     menu = *get_root_menu(saved_name);
2448     name = saved_name;
2449     while (*name)
2450     {
2451 	// Find in the menu hierarchy
2452 	p = menu_name_skip(name);
2453 
2454 	while (menu != NULL)
2455 	{
2456 	    if (menu_name_equal(name, menu))
2457 	    {
2458 		if (*p == NUL && menu->children != NULL)
2459 		{
2460 		    emsg(_("E333: Menu path must lead to a menu item"));
2461 		    gave_emsg = TRUE;
2462 		    menu = NULL;
2463 		}
2464 		else if (*p != NUL && menu->children == NULL)
2465 		{
2466 		    emsg(_(e_notsubmenu));
2467 		    menu = NULL;
2468 		}
2469 		break;
2470 	    }
2471 	    menu = menu->next;
2472 	}
2473 	if (menu == NULL || *p == NUL)
2474 	    break;
2475 	menu = menu->children;
2476 	name = p;
2477     }
2478     vim_free(saved_name);
2479     if (menu == NULL)
2480     {
2481 	if (!gave_emsg)
2482 	    semsg(_("E334: Menu not found: %s"), name_arg);
2483 	return NULL;
2484     }
2485 
2486     return menu;
2487 }
2488 
2489 /*
2490  * Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy and
2491  * execute it.
2492  */
2493     void
ex_emenu(exarg_T * eap)2494 ex_emenu(exarg_T *eap)
2495 {
2496     vimmenu_T	*menu;
2497     char_u	*arg = eap->arg;
2498     int		mode_idx = -1;
2499 
2500     if (arg[0] && VIM_ISWHITE(arg[1]))
2501     {
2502 	switch (arg[0])
2503 	{
2504 	    case 'n': mode_idx = MENU_INDEX_NORMAL; break;
2505 	    case 'v': mode_idx = MENU_INDEX_VISUAL; break;
2506 	    case 's': mode_idx = MENU_INDEX_SELECT; break;
2507 	    case 'o': mode_idx = MENU_INDEX_OP_PENDING; break;
2508 	    case 't': mode_idx = MENU_INDEX_TERMINAL; break;
2509 	    case 'i': mode_idx = MENU_INDEX_INSERT; break;
2510 	    case 'c': mode_idx = MENU_INDEX_CMDLINE; break;
2511 	    default: semsg(_(e_invarg2), arg);
2512 		     return;
2513 	}
2514 	arg = skipwhite(arg + 2);
2515     }
2516 
2517     menu = menu_getbyname(arg);
2518     if (menu == NULL)
2519 	return;
2520 
2521     // Found the menu, so execute.
2522     execute_menu(eap, menu, mode_idx);
2523 }
2524 
2525 /*
2526  * Handle a click in the window toolbar of "wp" at column "col".
2527  */
2528     void
winbar_click(win_T * wp,int col)2529 winbar_click(win_T *wp, int col)
2530 {
2531     int		idx;
2532 
2533     if (wp->w_winbar_items == NULL)
2534 	return;
2535     for (idx = 0; wp->w_winbar_items[idx].wb_menu != NULL; ++idx)
2536     {
2537 	winbar_item_T *item = &wp->w_winbar_items[idx];
2538 
2539 	if (col >= item->wb_startcol && col <= item->wb_endcol)
2540 	{
2541 	    win_T   *save_curwin = NULL;
2542 	    pos_T   save_visual = VIsual;
2543 	    int	    save_visual_active = VIsual_active;
2544 	    int	    save_visual_select = VIsual_select;
2545 	    int	    save_visual_reselect = VIsual_reselect;
2546 	    int	    save_visual_mode = VIsual_mode;
2547 
2548 	    if (wp != curwin)
2549 	    {
2550 		// Clicking in the window toolbar of a not-current window.
2551 		// Make that window the current one and save Visual mode.
2552 		save_curwin = curwin;
2553 		VIsual_active = FALSE;
2554 		curwin = wp;
2555 		curbuf = curwin->w_buffer;
2556 		check_cursor();
2557 	    }
2558 
2559 	    // Note: the command might close the current window.
2560 	    execute_menu(NULL, item->wb_menu, -1);
2561 
2562 	    if (save_curwin != NULL && win_valid(save_curwin))
2563 	    {
2564 		curwin = save_curwin;
2565 		curbuf = curwin->w_buffer;
2566 		VIsual = save_visual;
2567 		VIsual_active = save_visual_active;
2568 		VIsual_select = save_visual_select;
2569 		VIsual_reselect = save_visual_reselect;
2570 		VIsual_mode = save_visual_mode;
2571 	    }
2572 	    if (!win_valid(wp))
2573 		break;
2574 	}
2575     }
2576 }
2577 
2578 #if defined(FEAT_GUI_MSWIN) || defined(FEAT_GUI_GTK) \
2579 	|| defined(FEAT_TERM_POPUP_MENU) || defined(FEAT_GUI_HAIKU) \
2580 	|| defined(FEAT_BEVAL_TIP) || defined(PROTO)
2581 /*
2582  * Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy.
2583  */
2584     vimmenu_T *
gui_find_menu(char_u * path_name)2585 gui_find_menu(char_u *path_name)
2586 {
2587     vimmenu_T	*menu = NULL;
2588     char_u	*name;
2589     char_u	*saved_name;
2590     char_u	*p;
2591 
2592     menu = *get_root_menu(path_name);
2593 
2594     saved_name = vim_strsave(path_name);
2595     if (saved_name == NULL)
2596 	return NULL;
2597 
2598     name = saved_name;
2599     while (*name)
2600     {
2601 	// find the end of one dot-separated name and put a NUL at the dot
2602 	p = menu_name_skip(name);
2603 
2604 	while (menu != NULL)
2605 	{
2606 	    if (menu_name_equal(name, menu))
2607 	    {
2608 		if (menu->children == NULL)
2609 		{
2610 		    // found a menu item instead of a sub-menu
2611 		    if (*p == NUL)
2612 			emsg(_("E336: Menu path must lead to a sub-menu"));
2613 		    else
2614 			emsg(_(e_notsubmenu));
2615 		    menu = NULL;
2616 		    goto theend;
2617 		}
2618 		if (*p == NUL)	    // found a full match
2619 		    goto theend;
2620 		break;
2621 	    }
2622 	    menu = menu->next;
2623 	}
2624 	if (menu == NULL)	// didn't find it
2625 	    break;
2626 
2627 	// Found a match, search the sub-menu.
2628 	menu = menu->children;
2629 	name = p;
2630     }
2631 
2632     if (menu == NULL)
2633 	emsg(_("E337: Menu not found - check menu names"));
2634 theend:
2635     vim_free(saved_name);
2636     return menu;
2637 }
2638 #endif
2639 
2640 #ifdef FEAT_MULTI_LANG
2641 /*
2642  * Translation of menu names.  Just a simple lookup table.
2643  */
2644 
2645 typedef struct
2646 {
2647     char_u	*from;		// English name
2648     char_u	*from_noamp;	// same, without '&'
2649     char_u	*to;		// translated name
2650 } menutrans_T;
2651 
2652 static garray_T menutrans_ga = {0, 0, 0, 0, NULL};
2653 #endif
2654 
2655 /*
2656  * ":menutrans".
2657  * This function is also defined without the +multi_lang feature, in which
2658  * case the commands are ignored.
2659  */
2660     void
ex_menutranslate(exarg_T * eap UNUSED)2661 ex_menutranslate(exarg_T *eap UNUSED)
2662 {
2663 #ifdef FEAT_MULTI_LANG
2664     char_u		*arg = eap->arg;
2665     menutrans_T		*tp;
2666     int			i;
2667     char_u		*from, *from_noamp, *to;
2668 
2669     if (menutrans_ga.ga_itemsize == 0)
2670 	ga_init2(&menutrans_ga, (int)sizeof(menutrans_T), 5);
2671 
2672     /*
2673      * ":menutrans clear": clear all translations.
2674      */
2675     if (STRNCMP(arg, "clear", 5) == 0 && ends_excmd2(arg, skipwhite(arg + 5)))
2676     {
2677 	tp = (menutrans_T *)menutrans_ga.ga_data;
2678 	for (i = 0; i < menutrans_ga.ga_len; ++i)
2679 	{
2680 	    vim_free(tp[i].from);
2681 	    vim_free(tp[i].from_noamp);
2682 	    vim_free(tp[i].to);
2683 	}
2684 	ga_clear(&menutrans_ga);
2685 # ifdef FEAT_EVAL
2686 	// Delete all "menutrans_" global variables.
2687 	del_menutrans_vars();
2688 # endif
2689     }
2690     else
2691     {
2692 	// ":menutrans from to": add translation
2693 	from = arg;
2694 	arg = menu_skip_part(arg);
2695 	to = skipwhite(arg);
2696 	*arg = NUL;
2697 	arg = menu_skip_part(to);
2698 	if (arg == to || ends_excmd2(eap->arg, from)
2699 		      || ends_excmd2(eap->arg, to)
2700 		      || !ends_excmd2(eap->arg, skipwhite(arg)))
2701 	    emsg(_(e_invarg));
2702 	else
2703 	{
2704 	    if (ga_grow(&menutrans_ga, 1) == OK)
2705 	    {
2706 		tp = (menutrans_T *)menutrans_ga.ga_data;
2707 		from = vim_strsave(from);
2708 		if (from != NULL)
2709 		{
2710 		    from_noamp = menu_text(from, NULL, NULL);
2711 		    to = vim_strnsave(to, arg - to);
2712 		    if (from_noamp != NULL && to != NULL)
2713 		    {
2714 			menu_translate_tab_and_shift(from);
2715 			menu_translate_tab_and_shift(to);
2716 			menu_unescape_name(from);
2717 			menu_unescape_name(to);
2718 			tp[menutrans_ga.ga_len].from = from;
2719 			tp[menutrans_ga.ga_len].from_noamp = from_noamp;
2720 			tp[menutrans_ga.ga_len].to = to;
2721 			++menutrans_ga.ga_len;
2722 		    }
2723 		    else
2724 		    {
2725 			vim_free(from);
2726 			vim_free(from_noamp);
2727 			vim_free(to);
2728 		    }
2729 		}
2730 	    }
2731 	}
2732     }
2733 #endif
2734 }
2735 
2736 #if defined(FEAT_MULTI_LANG) || defined(FEAT_TOOLBAR)
2737 /*
2738  * Find the character just after one part of a menu name.
2739  */
2740     static char_u *
menu_skip_part(char_u * p)2741 menu_skip_part(char_u *p)
2742 {
2743     while (*p != NUL && *p != '.' && !VIM_ISWHITE(*p))
2744     {
2745 	if ((*p == '\\' || *p == Ctrl_V) && p[1] != NUL)
2746 	    ++p;
2747 	++p;
2748     }
2749     return p;
2750 }
2751 #endif
2752 
2753 #ifdef FEAT_MULTI_LANG
2754 /*
2755  * Lookup part of a menu name in the translations.
2756  * Return a pointer to the translation or NULL if not found.
2757  */
2758     static char_u *
menutrans_lookup(char_u * name,int len)2759 menutrans_lookup(char_u *name, int len)
2760 {
2761     menutrans_T		*tp = (menutrans_T *)menutrans_ga.ga_data;
2762     int			i;
2763     char_u		*dname;
2764 
2765     for (i = 0; i < menutrans_ga.ga_len; ++i)
2766 	if (STRNICMP(name, tp[i].from, len) == 0 && tp[i].from[len] == NUL)
2767 	    return tp[i].to;
2768 
2769     // Now try again while ignoring '&' characters.
2770     i = name[len];
2771     name[len] = NUL;
2772     dname = menu_text(name, NULL, NULL);
2773     name[len] = i;
2774     if (dname != NULL)
2775     {
2776 	for (i = 0; i < menutrans_ga.ga_len; ++i)
2777 	    if (STRICMP(dname, tp[i].from_noamp) == 0)
2778 	    {
2779 		vim_free(dname);
2780 		return tp[i].to;
2781 	    }
2782 	vim_free(dname);
2783     }
2784 
2785     return NULL;
2786 }
2787 
2788 /*
2789  * Unescape the name in the translate dictionary table.
2790  */
2791     static void
menu_unescape_name(char_u * name)2792 menu_unescape_name(char_u *name)
2793 {
2794     char_u  *p;
2795 
2796     for (p = name; *p && *p != '.'; MB_PTR_ADV(p))
2797 	if (*p == '\\')
2798 	    STRMOVE(p, p + 1);
2799 }
2800 #endif // FEAT_MULTI_LANG
2801 
2802 /*
2803  * Isolate the menu name.
2804  * Skip the menu name, and translate <Tab> into a real TAB.
2805  */
2806     static char_u *
menu_translate_tab_and_shift(char_u * arg_start)2807 menu_translate_tab_and_shift(char_u *arg_start)
2808 {
2809     char_u	*arg = arg_start;
2810 
2811     while (*arg && !VIM_ISWHITE(*arg))
2812     {
2813 	if ((*arg == '\\' || *arg == Ctrl_V) && arg[1] != NUL)
2814 	    arg++;
2815 	else if (STRNICMP(arg, "<TAB>", 5) == 0)
2816 	{
2817 	    *arg = TAB;
2818 	    STRMOVE(arg + 1, arg + 5);
2819 	}
2820 	arg++;
2821     }
2822     if (*arg != NUL)
2823 	*arg++ = NUL;
2824     arg = skipwhite(arg);
2825 
2826     return arg;
2827 }
2828 
2829 /*
2830  * Get the information about a menu item in mode 'which'
2831  */
2832     static int
menuitem_getinfo(char_u * menu_name,vimmenu_T * menu,int modes,dict_T * dict)2833 menuitem_getinfo(char_u *menu_name, vimmenu_T *menu, int modes, dict_T *dict)
2834 {
2835     int		status;
2836     list_T	*l;
2837 
2838     if (*menu_name == NUL)
2839     {
2840 	// Return all the top-level menus
2841 	vimmenu_T	*topmenu;
2842 
2843 	l = list_alloc();
2844 	if (l == NULL)
2845 	    return FAIL;
2846 
2847 	dict_add_list(dict, "submenus", l);
2848 	// get all the children.  Skip PopUp[nvoci].
2849 	for (topmenu = menu; topmenu != NULL; topmenu = topmenu->next)
2850 	    if (!menu_is_hidden(topmenu->dname))
2851 		list_append_string(l, topmenu->dname, -1);
2852 	return OK;
2853     }
2854 
2855     if (menu_is_tearoff(menu->dname))		// skip tearoff menu item
2856 	return OK;
2857 
2858     status = dict_add_string(dict, "name", menu->name);
2859     if (status == OK)
2860 	status = dict_add_string(dict, "display", menu->dname);
2861     if (status == OK && menu->actext != NULL)
2862 	status = dict_add_string(dict, "accel", menu->actext);
2863     if (status == OK)
2864 	status = dict_add_number(dict, "priority", menu->priority);
2865     if (status == OK)
2866 	status = dict_add_string(dict, "modes",
2867 					get_menu_mode_str(menu->modes));
2868 #ifdef FEAT_TOOLBAR
2869     if (status == OK && menu->iconfile != NULL)
2870 	status = dict_add_string(dict, "icon", menu->iconfile);
2871     if (status == OK && menu->iconidx >= 0)
2872 	status = dict_add_number(dict, "iconidx", menu->iconidx);
2873 #endif
2874     if (status == OK)
2875     {
2876 	char_u	buf[NUMBUFLEN];
2877 
2878 	if (has_mbyte)
2879 	    buf[utf_char2bytes(menu->mnemonic, buf)] = NUL;
2880 	else
2881 	{
2882 	    buf[0] = (char_u)menu->mnemonic;
2883 	    buf[1] = NUL;
2884 	}
2885 	status = dict_add_string(dict, "shortcut", buf);
2886     }
2887     if (status == OK && menu->children == NULL)
2888     {
2889 	int		bit;
2890 
2891 	// Get the first mode in which the menu is available
2892 	for (bit = 0; bit < MENU_MODES && !((1 << bit) & modes); bit++)
2893 	    ;
2894 	if (bit < MENU_MODES) // just in case, avoid Coverity warning
2895 	{
2896 	    if (menu->strings[bit] != NULL)
2897 	    {
2898 		char_u *tofree = NULL;
2899 
2900 		status = dict_add_string(dict, "rhs",
2901 			*menu->strings[bit] == NUL
2902 				? (char_u *)"<Nop>"
2903 				: (tofree = str2special_save(
2904 						  menu->strings[bit], FALSE)));
2905 		vim_free(tofree);
2906 	    }
2907 	    if (status == OK)
2908 		status = dict_add_bool(dict, "noremenu",
2909 					     menu->noremap[bit] == REMAP_NONE);
2910 	    if (status == OK)
2911 		status = dict_add_bool(dict, "script",
2912 					   menu->noremap[bit] == REMAP_SCRIPT);
2913 	    if (status == OK)
2914 		status = dict_add_bool(dict, "silent", menu->silent[bit]);
2915 	    if (status == OK)
2916 		status = dict_add_bool(dict, "enabled",
2917 					  ((menu->enabled & (1 << bit)) != 0));
2918 	}
2919     }
2920 
2921     // If there are submenus, add all the submenu display names
2922     if (status == OK && menu->children != NULL)
2923     {
2924 	vimmenu_T	*child;
2925 
2926 	l = list_alloc();
2927 	if (l == NULL)
2928 	    return FAIL;
2929 
2930 	dict_add_list(dict, "submenus", l);
2931 	child = menu->children;
2932 	while (child)
2933 	{
2934 	    if (!menu_is_tearoff(child->dname))		// skip tearoff menu
2935 		list_append_string(l, child->dname, -1);
2936 	    child = child->next;
2937 	}
2938     }
2939 
2940     return status;
2941 }
2942 
2943 /*
2944  * "menu_info()" function
2945  * Return information about a menu (including all the child menus)
2946  */
2947     void
f_menu_info(typval_T * argvars,typval_T * rettv)2948 f_menu_info(typval_T *argvars, typval_T *rettv)
2949 {
2950     char_u	*menu_name;
2951     char_u	*which;
2952     int		modes;
2953     char_u	*saved_name;
2954     char_u	*name;
2955     vimmenu_T	*menu;
2956     dict_T	*retdict;
2957 
2958     if (rettv_dict_alloc(rettv) != OK)
2959 	return;
2960     retdict = rettv->vval.v_dict;
2961 
2962     if (in_vim9script()
2963 	    && (check_for_string_arg(argvars, 0) == FAIL
2964 		|| check_for_opt_string_arg(argvars, 1) == FAIL))
2965 	return;
2966 
2967     menu_name = tv_get_string_chk(&argvars[0]);
2968     if (menu_name == NULL)
2969 	return;
2970 
2971     // menu mode
2972     if (argvars[1].v_type != VAR_UNKNOWN)
2973 	which = tv_get_string_chk(&argvars[1]);
2974     else
2975 	which = (char_u *)"";	    // Default is modes for "menu"
2976     if (which == NULL)
2977 	return;
2978 
2979     modes = get_menu_cmd_modes(which, *which == '!', NULL, NULL);
2980 
2981     // Locate the specified menu or menu item
2982     menu = *get_root_menu(menu_name);
2983     saved_name = vim_strsave(menu_name);
2984     if (saved_name == NULL)
2985 	return;
2986     if (*saved_name != NUL)
2987     {
2988 	char_u	*p;
2989 
2990 	name = saved_name;
2991 	while (*name)
2992 	{
2993 	    // Find in the menu hierarchy
2994 	    p = menu_name_skip(name);
2995 	    while (menu != NULL)
2996 	    {
2997 		if (menu_name_equal(name, menu))
2998 		    break;
2999 		menu = menu->next;
3000 	    }
3001 	    if (menu == NULL || *p == NUL)
3002 		break;
3003 	    menu = menu->children;
3004 	    name = p;
3005 	}
3006     }
3007     vim_free(saved_name);
3008 
3009     if (menu == NULL)		// specified menu not found
3010 	return;
3011 
3012     if (menu->modes & modes)
3013 	menuitem_getinfo(menu_name, menu, modes, retdict);
3014 }
3015 
3016 #endif // FEAT_MENU
3017