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