1// This is an open source non-commercial project. Dear PVS-Studio, please check
2// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
3
4/*
5 * Code for menus. Used for the GUI and 'wildmenu'.
6 * GUI/Motif support by Robert Webb
7 */
8
9#include <assert.h>
10#include <inttypes.h>
11#include <string.h>
12
13#include "nvim/vim.h"
14#include "nvim/ascii.h"
15#include "nvim/menu.h"
16#include "nvim/charset.h"
17#include "nvim/cursor.h"
18#include "nvim/eval.h"
19#include "nvim/ex_docmd.h"
20#include "nvim/getchar.h"
21#include "nvim/memory.h"
22#include "nvim/message.h"
23#include "nvim/misc1.h"
24#include "nvim/keymap.h"
25#include "nvim/garray.h"
26#include "nvim/state.h"
27#include "nvim/strings.h"
28#include "nvim/ui.h"
29#include "nvim/eval/typval.h"
30
31#define MENUDEPTH 10 /* maximum depth of menus */
32
33
34#ifdef INCLUDE_GENERATED_DECLARATIONS
35# include "menu.c.generated.h"
36#endif
37
38
39
40
41/// The character for each menu mode
42static char_u menu_mode_chars[] = { 'n', 'v', 's', 'o', 'i', 'c', 't' };
43
44static char_u e_notsubmenu[] = N_(
45 "E327: Part of menu-item path is not sub-menu");
46static char_u e_othermode[] = N_("E328: Menu only exists in another mode");
47static char_u e_nomenu[] = N_("E329: No menu \"%s\"");
48
49
50/// Do the :menu command and relatives.
51/// @param eap Ex command arguments
52void
53ex_menu(exarg_T *eap)
54{
55 char_u *menu_path;
56 int modes;
57 char_u *map_to; // command mapped to the menu entry
58 int noremap;
59 bool silent = false;
60 int unmenu;
61 char_u *map_buf;
62 char_u *arg;
63 char_u *p;
64 int i;
65 long pri_tab[MENUDEPTH + 1];
66 TriState enable = kNone; // kTrue for "menu enable",
67 // kFalse for "menu disable
68 vimmenu_T menuarg;
69
70 modes = get_menu_cmd_modes(eap->cmd, eap->forceit, &noremap, &unmenu);
71 arg = eap->arg;
72
73 for (;; ) {
74 if (STRNCMP(arg, "<script>", 8) == 0) {
75 noremap = REMAP_SCRIPT;
76 arg = skipwhite(arg + 8);
77 continue;
78 }
79 if (STRNCMP(arg, "<silent>", 8) == 0) {
80 silent = true;
81 arg = skipwhite(arg + 8);
82 continue;
83 }
84 if (STRNCMP(arg, "<special>", 9) == 0) {
85 // Ignore obsolete "<special>" modifier.
86 arg = skipwhite(arg + 9);
87 continue;
88 }
89 break;
90 }
91
92
93 // Locate an optional "icon=filename" argument
94 // TODO(nvim): Currently this is only parsed. Should expose it to UIs.
95 if (STRNCMP(arg, "icon=", 5) == 0) {
96 arg += 5;
97 while (*arg != NUL && *arg != ' ') {
98 if (*arg == '\\')
99 STRMOVE(arg, arg + 1);
100 MB_PTR_ADV(arg);
101 }
102 if (*arg != NUL) {
103 *arg++ = NUL;
104 arg = skipwhite(arg);
105 }
106 }
107
108 // Fill in the priority table.
109 for (p = arg; *p; p++) {
110 if (!ascii_isdigit(*p) && *p != '.') {
111 break;
112 }
113 }
114 if (ascii_iswhite(*p)) {
115 for (i = 0; i < MENUDEPTH && !ascii_iswhite(*arg); i++) {
116 pri_tab[i] = getdigits_long(&arg, false, 0);
117 if (pri_tab[i] == 0) {
118 pri_tab[i] = 500;
119 }
120 if (*arg == '.') {
121 arg++;
122 }
123 }
124 arg = skipwhite(arg);
125 } else if (eap->addr_count && eap->line2 != 0) {
126 pri_tab[0] = eap->line2;
127 i = 1;
128 } else
129 i = 0;
130 while (i < MENUDEPTH)
131 pri_tab[i++] = 500;
132 pri_tab[MENUDEPTH] = -1; /* mark end of the table */
133
134 /*
135 * Check for "disable" or "enable" argument.
136 */
137 if (STRNCMP(arg, "enable", 6) == 0 && ascii_iswhite(arg[6])) {
138 enable = kTrue;
139 arg = skipwhite(arg + 6);
140 } else if (STRNCMP(arg, "disable", 7) == 0 && ascii_iswhite(arg[7])) {
141 enable = kFalse;
142 arg = skipwhite(arg + 7);
143 }
144
145 /*
146 * If there is no argument, display all menus.
147 */
148 if (*arg == NUL) {
149 show_menus(arg, modes);
150 return;
151 }
152
153
154 menu_path = arg;
155 if (*menu_path == '.') {
156 EMSG2(_(e_invarg2), menu_path);
157 goto theend;
158 }
159
160 map_to = menu_translate_tab_and_shift(arg);
161
162 /*
163 * If there is only a menu name, display menus with that name.
164 */
165 if (*map_to == NUL && !unmenu && enable == kNone) {
166 show_menus(menu_path, modes);
167 goto theend;
168 } else if (*map_to != NUL && (unmenu || enable != kNone)) {
169 EMSG(_(e_trailing));
170 goto theend;
171 }
172
173 if (enable != kNone) {
174 // Change sensitivity of the menu.
175 // For the PopUp menu, remove a menu for each mode separately.
176 // Careful: menu_enable_recurse() changes menu_path.
177 if (STRCMP(menu_path, "*") == 0) { // meaning: do all menus
178 menu_path = (char_u *)"";
179 }
180
181 if (menu_is_popup(menu_path)) {
182 for (i = 0; i < MENU_INDEX_TIP; ++i)
183 if (modes & (1 << i)) {
184 p = popup_mode_name(menu_path, i);
185 menu_enable_recurse(root_menu, p, MENU_ALL_MODES, enable);
186 xfree(p);
187 }
188 }
189 menu_enable_recurse(root_menu, menu_path, modes, enable);
190 } else if (unmenu) {
191 /*
192 * Delete menu(s).
193 */
194 if (STRCMP(menu_path, "*") == 0) /* meaning: remove all menus */
195 menu_path = (char_u *)"";
196
197 /*
198 * For the PopUp menu, remove a menu for each mode separately.
199 */
200 if (menu_is_popup(menu_path)) {
201 for (i = 0; i < MENU_INDEX_TIP; ++i)
202 if (modes & (1 << i)) {
203 p = popup_mode_name(menu_path, i);
204 remove_menu(&root_menu, p, MENU_ALL_MODES, TRUE);
205 xfree(p);
206 }
207 }
208
209 /* Careful: remove_menu() changes menu_path */
210 remove_menu(&root_menu, menu_path, modes, FALSE);
211 } else {
212 /*
213 * Add menu(s).
214 * Replace special key codes.
215 */
216 if (STRICMP(map_to, "<nop>") == 0) { /* "<Nop>" means nothing */
217 map_to = (char_u *)"";
218 map_buf = NULL;
219 } else if (modes & MENU_TIP_MODE) {
220 map_buf = NULL; // Menu tips are plain text.
221 } else {
222 map_to = replace_termcodes(map_to, STRLEN(map_to), &map_buf, false, true,
223 true, CPO_TO_CPO_FLAGS);
224 }
225 menuarg.modes = modes;
226 menuarg.noremap[0] = noremap;
227 menuarg.silent[0] = silent;
228 add_menu_path(menu_path, &menuarg, pri_tab, map_to);
229
230 /*
231 * For the PopUp menu, add a menu for each mode separately.
232 */
233 if (menu_is_popup(menu_path)) {
234 for (i = 0; i < MENU_INDEX_TIP; ++i)
235 if (modes & (1 << i)) {
236 p = popup_mode_name(menu_path, i);
237 // Include all modes, to make ":amenu" work
238 menuarg.modes = modes;
239 add_menu_path(p, &menuarg, pri_tab, map_to);
240 xfree(p);
241 }
242 }
243
244 xfree(map_buf);
245 }
246
247 ui_call_update_menu();
248
249theend:
250 ;
251}
252
253
254/// Add the menu with the given name to the menu hierarchy
255///
256/// @param[out] menuarg menu entry
257/// @param[] pri_tab priority table
258/// @param[in] call_data Right hand side command
259static int
260add_menu_path(
261 const char_u *const menu_path,
262 vimmenu_T *menuarg,
263 const long *const pri_tab,
264 const char_u *const call_data
265)
266{
267 char_u *path_name;
268 int modes = menuarg->modes;
269 vimmenu_T **menup;
270 vimmenu_T *menu = NULL;
271 vimmenu_T *parent;
272 vimmenu_T **lower_pri;
273 char_u *p;
274 char_u *name;
275 char_u *dname;
276 char_u *next_name;
277 char_u c;
278 char_u d;
279 int i;
280 int pri_idx = 0;
281 int old_modes = 0;
282 int amenu;
283 char_u *en_name;
284 char_u *map_to = NULL;
285
286 /* Make a copy so we can stuff around with it, since it could be const */
287 path_name = vim_strsave(menu_path);
288 menup = &root_menu;
289 parent = NULL;
290 name = path_name;
291 while (*name) {
292 /* Get name of this element in the menu hierarchy, and the simplified
293 * name (without mnemonic and accelerator text). */
294 next_name = menu_name_skip(name);
295 map_to = menutrans_lookup(name, (int)STRLEN(name));
296 if (map_to != NULL) {
297 en_name = name;
298 name = map_to;
299 } else {
300 en_name = NULL;
301 }
302 dname = menu_text(name, NULL, NULL);
303 if (*dname == NUL) {
304 /* Only a mnemonic or accelerator is not valid. */
305 EMSG(_("E792: Empty menu name"));
306 goto erret;
307 }
308
309 /* See if it's already there */
310 lower_pri = menup;
311 menu = *menup;
312 while (menu != NULL) {
313 if (menu_name_equal(name, menu) || menu_name_equal(dname, menu)) {
314 if (*next_name == NUL && menu->children != NULL) {
315 if (!sys_menu) {
316 EMSG(_("E330: Menu path must not lead to a sub-menu"));
317 }
318 goto erret;
319 }
320 if (*next_name != NUL && menu->children == NULL) {
321 if (!sys_menu) {
322 EMSG(_(e_notsubmenu));
323 }
324 goto erret;
325 }
326 break;
327 }
328 menup = &menu->next;
329
330 /* Count menus, to find where this one needs to be inserted.
331 * Ignore menus that are not in the menubar (PopUp and Toolbar) */
332 if (parent != NULL || menu_is_menubar(menu->name)) {
333 if (menu->priority <= pri_tab[pri_idx]) {
334 lower_pri = menup;
335 }
336 }
337 menu = menu->next;
338 }
339
340 if (menu == NULL) {
341 if (*next_name == NUL && parent == NULL) {
342 EMSG(_("E331: Must not add menu items directly to menu bar"));
343 goto erret;
344 }
345
346 if (menu_is_separator(dname) && *next_name != NUL) {
347 EMSG(_("E332: Separator cannot be part of a menu path"));
348 goto erret;
349 }
350
351 /* Not already there, so lets add it */
352 menu = xcalloc(1, sizeof(vimmenu_T));
353
354 menu->modes = modes;
355 menu->enabled = MENU_ALL_MODES;
356 menu->name = vim_strsave(name);
357 // separate mnemonic and accelerator text from actual menu name
358 menu->dname = menu_text(name, &menu->mnemonic, &menu->actext);
359 if (en_name != NULL) {
360 menu->en_name = vim_strsave(en_name);
361 menu->en_dname = menu_text(en_name, NULL, NULL);
362 } else {
363 menu->en_name = NULL;
364 menu->en_dname = NULL;
365 }
366 menu->priority = pri_tab[pri_idx];
367 menu->parent = parent;
368
369 // Add after menu that has lower priority.
370 menu->next = *lower_pri;
371 *lower_pri = menu;
372
373 old_modes = 0;
374
375 } else {
376 old_modes = menu->modes;
377
378 /*
379 * If this menu option was previously only available in other
380 * modes, then make sure it's available for this one now
381 * Also enable a menu when it's created or changed.
382 */
383 {
384 menu->modes |= modes;
385 menu->enabled |= modes;
386 }
387 }
388
389
390 menup = &menu->children;
391 parent = menu;
392 name = next_name;
393 XFREE_CLEAR(dname);
394 if (pri_tab[pri_idx + 1] != -1) {
395 pri_idx++;
396 }
397 }
398 xfree(path_name);
399
400 /*
401 * Only add system menu items which have not been defined yet.
402 * First check if this was an ":amenu".
403 */
404 amenu = ((modes & (MENU_NORMAL_MODE | MENU_INSERT_MODE)) ==
405 (MENU_NORMAL_MODE | MENU_INSERT_MODE));
406 if (sys_menu)
407 modes &= ~old_modes;
408
409 if (menu != NULL && modes) {
410 p = (call_data == NULL) ? NULL : vim_strsave(call_data);
411
412 /* loop over all modes, may add more than one */
413 for (i = 0; i < MENU_MODES; ++i) {
414 if (modes & (1 << i)) {
415 /* free any old menu */
416 free_menu_string(menu, i);
417
418 // For "amenu", may insert an extra character.
419 // Don't do this for "<Nop>".
420 c = 0;
421 d = 0;
422 if (amenu && call_data != NULL && *call_data != NUL) {
423 switch (1 << i) {
424 case MENU_VISUAL_MODE:
425 case MENU_SELECT_MODE:
426 case MENU_OP_PENDING_MODE:
427 case MENU_CMDLINE_MODE:
428 c = Ctrl_C;
429 break;
430 case MENU_INSERT_MODE:
431 c = Ctrl_BSL;
432 d = Ctrl_O;
433 break;
434 }
435 }
436
437 if (c != 0) {
438 menu->strings[i] = xmalloc(STRLEN(call_data) + 5 );
439 menu->strings[i][0] = c;
440 if (d == 0) {
441 STRCPY(menu->strings[i] + 1, call_data);
442 } else {
443 menu->strings[i][1] = d;
444 STRCPY(menu->strings[i] + 2, call_data);
445 }
446 if (c == Ctrl_C) {
447 int len = (int)STRLEN(menu->strings[i]);
448
449 /* Append CTRL-\ CTRL-G to obey 'insertmode'. */
450 menu->strings[i][len] = Ctrl_BSL;
451 menu->strings[i][len + 1] = Ctrl_G;
452 menu->strings[i][len + 2] = NUL;
453 }
454 } else {
455 menu->strings[i] = p;
456 }
457 menu->noremap[i] = menuarg->noremap[0];
458 menu->silent[i] = menuarg->silent[0];
459 }
460 }
461 }
462 return OK;
463
464erret:
465 xfree(path_name);
466 xfree(dname);
467
468 /* Delete any empty submenu we added before discovering the error. Repeat
469 * for higher levels. */
470 while (parent != NULL && parent->children == NULL) {
471 if (parent->parent == NULL)
472 menup = &root_menu;
473 else
474 menup = &parent->parent->children;
475 for (; *menup != NULL && *menup != parent; menup = &((*menup)->next))
476 ;
477 if (*menup == NULL) /* safety check */
478 break;
479 parent = parent->parent;
480 free_menu(menup);
481 }
482 return FAIL;
483}
484
485/*
486 * Set the (sub)menu with the given name to enabled or disabled.
487 * Called recursively.
488 */
489static int menu_enable_recurse(vimmenu_T *menu,
490 char_u *name,
491 int modes,
492 int enable)
493{
494 char_u *p;
495
496 if (menu == NULL)
497 return OK; /* Got to bottom of hierarchy */
498
499 /* Get name of this element in the menu hierarchy */
500 p = menu_name_skip(name);
501
502 /* Find the menu */
503 while (menu != NULL) {
504 if (*name == NUL || *name == '*' || menu_name_equal(name, menu)) {
505 if (*p != NUL) {
506 if (menu->children == NULL) {
507 EMSG(_(e_notsubmenu));
508 return FAIL;
509 }
510 if (menu_enable_recurse(menu->children, p, modes, enable) == FAIL) {
511 return FAIL;
512 }
513 } else if (enable) {
514 menu->enabled |= modes;
515 } else {
516 menu->enabled &= ~modes;
517 }
518
519 /*
520 * When name is empty, we are doing all menu items for the given
521 * modes, so keep looping, otherwise we are just doing the named
522 * menu item (which has been found) so break here.
523 */
524 if (*name != NUL && *name != '*')
525 break;
526 }
527 menu = menu->next;
528 }
529 if (*name != NUL && *name != '*' && menu == NULL) {
530 EMSG2(_(e_nomenu), name);
531 return FAIL;
532 }
533
534
535 return OK;
536}
537
538/*
539 * Remove the (sub)menu with the given name from the menu hierarchy
540 * Called recursively.
541 */
542static int
543remove_menu (
544 vimmenu_T **menup,
545 char_u *name,
546 int modes,
547 bool silent /* don't give error messages */
548)
549{
550 vimmenu_T *menu;
551 vimmenu_T *child;
552 char_u *p;
553
554 if (*menup == NULL)
555 return OK; /* Got to bottom of hierarchy */
556
557 /* Get name of this element in the menu hierarchy */
558 p = menu_name_skip(name);
559
560 /* Find the menu */
561 while ((menu = *menup) != NULL) {
562 if (*name == NUL || menu_name_equal(name, menu)) {
563 if (*p != NUL && menu->children == NULL) {
564 if (!silent)
565 EMSG(_(e_notsubmenu));
566 return FAIL;
567 }
568 if ((menu->modes & modes) != 0x0) {
569 if (remove_menu(&menu->children, p, modes, silent) == FAIL)
570 return FAIL;
571 } else if (*name != NUL) {
572 if (!silent)
573 EMSG(_(e_othermode));
574 return FAIL;
575 }
576
577 /*
578 * When name is empty, we are removing all menu items for the given
579 * modes, so keep looping, otherwise we are just removing the named
580 * menu item (which has been found) so break here.
581 */
582 if (*name != NUL)
583 break;
584
585 /* Remove the menu item for the given mode[s]. If the menu item
586 * is no longer valid in ANY mode, delete it */
587 menu->modes &= ~modes;
588 if (modes & MENU_TIP_MODE)
589 free_menu_string(menu, MENU_INDEX_TIP);
590 if ((menu->modes & MENU_ALL_MODES) == 0)
591 free_menu(menup);
592 else
593 menup = &menu->next;
594 } else
595 menup = &menu->next;
596 }
597 if (*name != NUL) {
598 if (menu == NULL) {
599 if (!silent)
600 EMSG2(_(e_nomenu), name);
601 return FAIL;
602 }
603
604
605 /* Recalculate modes for menu based on the new updated children */
606 menu->modes &= ~modes;
607 child = menu->children;
608 for (; child != NULL; child = child->next)
609 menu->modes |= child->modes;
610 if (modes & MENU_TIP_MODE) {
611 free_menu_string(menu, MENU_INDEX_TIP);
612 }
613 if ((menu->modes & MENU_ALL_MODES) == 0) {
614 /* The menu item is no longer valid in ANY mode, so delete it */
615 *menup = menu;
616 free_menu(menup);
617 }
618 }
619
620 return OK;
621}
622
623/*
624 * Free the given menu structure and remove it from the linked list.
625 */
626static void free_menu(vimmenu_T **menup)
627{
628 int i;
629 vimmenu_T *menu;
630
631 menu = *menup;
632
633
634 /* Don't change *menup until after calling gui_mch_destroy_menu(). The
635 * MacOS code needs the original structure to properly delete the menu. */
636 *menup = menu->next;
637 xfree(menu->name);
638 xfree(menu->dname);
639 xfree(menu->en_name);
640 xfree(menu->en_dname);
641 xfree(menu->actext);
642 for (i = 0; i < MENU_MODES; i++)
643 free_menu_string(menu, i);
644 xfree(menu);
645
646}
647
648/*
649 * Free the menu->string with the given index.
650 */
651static void free_menu_string(vimmenu_T *menu, int idx)
652{
653 int count = 0;
654 int i;
655
656 for (i = 0; i < MENU_MODES; i++)
657 if (menu->strings[i] == menu->strings[idx])
658 count++;
659 if (count == 1)
660 xfree(menu->strings[idx]);
661 menu->strings[idx] = NULL;
662}
663
664/// Export menus
665///
666/// @param[in] menu if null, starts from root_menu
667/// @param modes, a choice of \ref MENU_MODES
668/// @return dict with name/commands
669/// @see show_menus_recursive
670/// @see menu_get
671static dict_T *menu_get_recursive(const vimmenu_T *menu, int modes)
672{
673 dict_T *dict;
674
675 if (!menu || (menu->modes & modes) == 0x0) {
676 return NULL;
677 }
678
679 dict = tv_dict_alloc();
680 tv_dict_add_str(dict, S_LEN("name"), (char *)menu->dname);
681 tv_dict_add_nr(dict, S_LEN("priority"), (int)menu->priority);
682 tv_dict_add_nr(dict, S_LEN("hidden"), menu_is_hidden(menu->dname));
683
684 if (menu->mnemonic) {
685 char buf[MB_MAXCHAR + 1] = { 0 }; // > max value of utf8_char2bytes
686 utf_char2bytes(menu->mnemonic, (char_u *)buf);
687 tv_dict_add_str(dict, S_LEN("shortcut"), buf);
688 }
689
690 if (menu->actext) {
691 tv_dict_add_str(dict, S_LEN("actext"), (char *)menu->actext);
692 }
693
694 if (menu->modes & MENU_TIP_MODE && menu->strings[MENU_INDEX_TIP]) {
695 tv_dict_add_str(dict, S_LEN("tooltip"),
696 (char *)menu->strings[MENU_INDEX_TIP]);
697 }
698
699 if (!menu->children) {
700 // leaf menu
701 dict_T *commands = tv_dict_alloc();
702 tv_dict_add_dict(dict, S_LEN("mappings"), commands);
703
704 for (int bit = 0; bit < MENU_MODES; bit++) {
705 if ((menu->modes & modes & (1 << bit)) != 0) {
706 dict_T *impl = tv_dict_alloc();
707 tv_dict_add_allocated_str(impl, S_LEN("rhs"),
708 str2special_save((char *)menu->strings[bit],
709 false, false));
710 tv_dict_add_nr(impl, S_LEN("silent"), menu->silent[bit]);
711 tv_dict_add_nr(impl, S_LEN("enabled"),
712 (menu->enabled & (1 << bit)) ? 1 : 0);
713 tv_dict_add_nr(impl, S_LEN("noremap"),
714 (menu->noremap[bit] & REMAP_NONE) ? 1 : 0);
715 tv_dict_add_nr(impl, S_LEN("sid"),
716 (menu->noremap[bit] & REMAP_SCRIPT) ? 1 : 0);
717 tv_dict_add_dict(commands, (char *)&menu_mode_chars[bit], 1, impl);
718 }
719 }
720 } else {
721 // visit recursively all children
722 list_T *const children_list = tv_list_alloc(kListLenMayKnow);
723 for (menu = menu->children; menu != NULL; menu = menu->next) {
724 dict_T *d = menu_get_recursive(menu, modes);
725 if (tv_dict_len(d) > 0) {
726 tv_list_append_dict(children_list, d);
727 }
728 }
729 tv_dict_add_list(dict, S_LEN("submenus"), children_list);
730 }
731 return dict;
732}
733
734
735/// Export menus matching path \p path_name
736///
737/// @param path_name
738/// @param modes supported modes, see \ref MENU_MODES
739/// @param[in,out] list must be allocated
740/// @return false if could not find path_name
741bool menu_get(char_u *const path_name, int modes, list_T *list)
742{
743 vimmenu_T *menu = find_menu(root_menu, path_name, modes);
744 if (!menu) {
745 return false;
746 }
747 for (; menu != NULL; menu = menu->next) {
748 dict_T *d = menu_get_recursive(menu, modes);
749 if (d && tv_dict_len(d) > 0) {
750 tv_list_append_dict(list, d);
751 }
752 if (*path_name != NUL) {
753 // If a (non-empty) path query was given, only the first node in the
754 // find_menu() result is relevant. Else we want all nodes.
755 break;
756 }
757 }
758 return true;
759}
760
761
762/// Find menu matching `name` and `modes`.
763///
764/// @param menu top menu to start looking from
765/// @param name path towards the menu
766/// @return menu if \p name is null, found menu or NULL
767static vimmenu_T *find_menu(vimmenu_T *menu, char_u *name, int modes)
768{
769 char_u *p;
770
771 while (*name) {
772 // find the end of one dot-separated name and put a NUL at the dot
773 p = menu_name_skip(name);
774 while (menu != NULL) {
775 if (menu_name_equal(name, menu)) {
776 // Found menu
777 if (*p != NUL && menu->children == NULL) {
778 EMSG(_(e_notsubmenu));
779 return NULL;
780 } else if ((menu->modes & modes) == 0x0) {
781 EMSG(_(e_othermode));
782 return NULL;
783 } else if (*p == NUL) { // found a full match
784 return menu;
785 }
786 break;
787 }
788 menu = menu->next;
789 }
790
791 if (menu == NULL) {
792 EMSG2(_(e_nomenu), name);
793 return NULL;
794 }
795 // Found a match, search the sub-menu.
796 name = p;
797 menu = menu->children;
798 }
799 return menu;
800}
801
802/// Show the mapping associated with a menu item or hierarchy in a sub-menu.
803static int show_menus(char_u *const path_name, int modes)
804{
805 vimmenu_T *menu;
806
807 // First, find the (sub)menu with the given name
808 menu = find_menu(root_menu, path_name, modes);
809 if (!menu) {
810 return FAIL;
811 }
812
813 /* Now we have found the matching menu, and we list the mappings */
814 /* Highlight title */
815 MSG_PUTS_TITLE(_("\n--- Menus ---"));
816
817 show_menus_recursive(menu->parent, modes, 0);
818 return OK;
819}
820
821/// Recursively show the mappings associated with the menus under the given one
822static void show_menus_recursive(vimmenu_T *menu, int modes, int depth)
823{
824 int i;
825 int bit;
826
827 if (menu != NULL && (menu->modes & modes) == 0x0)
828 return;
829
830 if (menu != NULL) {
831 msg_putchar('\n');
832 if (got_int) /* "q" hit for "--more--" */
833 return;
834 for (i = 0; i < depth; i++)
835 MSG_PUTS(" ");
836 if (menu->priority) {
837 msg_outnum((long)menu->priority);
838 MSG_PUTS(" ");
839 }
840 // Same highlighting as for directories!?
841 msg_outtrans_attr(menu->name, HL_ATTR(HLF_D));
842 }
843
844 if (menu != NULL && menu->children == NULL) {
845 for (bit = 0; bit < MENU_MODES; bit++)
846 if ((menu->modes & modes & (1 << bit)) != 0) {
847 msg_putchar('\n');
848 if (got_int) /* "q" hit for "--more--" */
849 return;
850 for (i = 0; i < depth + 2; i++)
851 MSG_PUTS(" ");
852 msg_putchar(menu_mode_chars[bit]);
853 if (menu->noremap[bit] == REMAP_NONE)
854 msg_putchar('*');
855 else if (menu->noremap[bit] == REMAP_SCRIPT)
856 msg_putchar('&');
857 else
858 msg_putchar(' ');
859 if (menu->silent[bit])
860 msg_putchar('s');
861 else
862 msg_putchar(' ');
863 if ((menu->modes & menu->enabled & (1 << bit)) == 0)
864 msg_putchar('-');
865 else
866 msg_putchar(' ');
867 MSG_PUTS(" ");
868 if (*menu->strings[bit] == NUL) {
869 msg_puts_attr("<Nop>", HL_ATTR(HLF_8));
870 } else {
871 msg_outtrans_special(menu->strings[bit], false);
872 }
873 }
874 } else {
875 if (menu == NULL) {
876 menu = root_menu;
877 depth--;
878 } else
879 menu = menu->children;
880
881 /* recursively show all children. Skip PopUp[nvoci]. */
882 for (; menu != NULL && !got_int; menu = menu->next)
883 if (!menu_is_hidden(menu->dname))
884 show_menus_recursive(menu, modes, depth + 1);
885 }
886}
887
888
889/*
890 * Used when expanding menu names.
891 */
892static vimmenu_T *expand_menu = NULL;
893static int expand_modes = 0x0;
894static int expand_emenu; /* TRUE for ":emenu" command */
895
896/*
897 * Work out what to complete when doing command line completion of menu names.
898 */
899char_u *set_context_in_menu_cmd(expand_T *xp, char_u *cmd, char_u *arg, int forceit)
900{
901 char_u *after_dot;
902 char_u *p;
903 char_u *path_name = NULL;
904 char_u *name;
905 int unmenu;
906 vimmenu_T *menu;
907 int expand_menus;
908
909 xp->xp_context = EXPAND_UNSUCCESSFUL;
910
911
912 /* Check for priority numbers, enable and disable */
913 for (p = arg; *p; ++p)
914 if (!ascii_isdigit(*p) && *p != '.')
915 break;
916
917 if (!ascii_iswhite(*p)) {
918 if (STRNCMP(arg, "enable", 6) == 0
919 && (arg[6] == NUL || ascii_iswhite(arg[6])))
920 p = arg + 6;
921 else if (STRNCMP(arg, "disable", 7) == 0
922 && (arg[7] == NUL || ascii_iswhite(arg[7])))
923 p = arg + 7;
924 else
925 p = arg;
926 }
927
928 while (*p != NUL && ascii_iswhite(*p))
929 ++p;
930
931 arg = after_dot = p;
932
933 for (; *p && !ascii_iswhite(*p); ++p) {
934 if ((*p == '\\' || *p == Ctrl_V) && p[1] != NUL)
935 p++;
936 else if (*p == '.')
937 after_dot = p + 1;
938 }
939
940 // ":popup" only uses menues, not entries
941 expand_menus = !((*cmd == 't' && cmd[1] == 'e') || *cmd == 'p');
942 expand_emenu = (*cmd == 'e');
943 if (expand_menus && ascii_iswhite(*p))
944 return NULL; /* TODO: check for next command? */
945 if (*p == NUL) { /* Complete the menu name */
946 /*
947 * With :unmenu, you only want to match menus for the appropriate mode.
948 * With :menu though you might want to add a menu with the same name as
949 * one in another mode, so match menus from other modes too.
950 */
951 expand_modes = get_menu_cmd_modes(cmd, forceit, NULL, &unmenu);
952 if (!unmenu)
953 expand_modes = MENU_ALL_MODES;
954
955 menu = root_menu;
956 if (after_dot > arg) {
957 size_t path_len = (size_t) (after_dot - arg);
958 path_name = xmalloc(path_len);
959 STRLCPY(path_name, arg, path_len);
960 }
961 name = path_name;
962 while (name != NULL && *name) {
963 p = menu_name_skip(name);
964 while (menu != NULL) {
965 if (menu_name_equal(name, menu)) {
966 /* Found menu */
967 if ((*p != NUL && menu->children == NULL)
968 || ((menu->modes & expand_modes) == 0x0)) {
969 /*
970 * Menu path continues, but we have reached a leaf.
971 * Or menu exists only in another mode.
972 */
973 xfree(path_name);
974 return NULL;
975 }
976 break;
977 }
978 menu = menu->next;
979 }
980 if (menu == NULL) {
981 /* No menu found with the name we were looking for */
982 xfree(path_name);
983 return NULL;
984 }
985 name = p;
986 menu = menu->children;
987 }
988 xfree(path_name);
989
990 xp->xp_context = expand_menus ? EXPAND_MENUNAMES : EXPAND_MENUS;
991 xp->xp_pattern = after_dot;
992 expand_menu = menu;
993 } else /* We're in the mapping part */
994 xp->xp_context = EXPAND_NOTHING;
995 return NULL;
996}
997
998/*
999 * Function given to ExpandGeneric() to obtain the list of (sub)menus (not
1000 * entries).
1001 */
1002char_u *get_menu_name(expand_T *xp, int idx)
1003{
1004 static vimmenu_T *menu = NULL;
1005 char_u *str;
1006 static int should_advance = FALSE;
1007
1008 if (idx == 0) { /* first call: start at first item */
1009 menu = expand_menu;
1010 should_advance = FALSE;
1011 }
1012
1013 /* Skip PopUp[nvoci]. */
1014 while (menu != NULL && (menu_is_hidden(menu->dname)
1015 || menu_is_separator(menu->dname)
1016 || menu->children == NULL))
1017 menu = menu->next;
1018
1019 if (menu == NULL) /* at end of linked list */
1020 return NULL;
1021
1022 if (menu->modes & expand_modes)
1023 if (should_advance)
1024 str = menu->en_dname;
1025 else {
1026 str = menu->dname;
1027 if (menu->en_dname == NULL)
1028 should_advance = TRUE;
1029 }
1030 else
1031 str = (char_u *)"";
1032
1033 if (should_advance)
1034 /* Advance to next menu entry. */
1035 menu = menu->next;
1036
1037 should_advance = !should_advance;
1038
1039 return str;
1040}
1041
1042/*
1043 * Function given to ExpandGeneric() to obtain the list of menus and menu
1044 * entries.
1045 */
1046char_u *get_menu_names(expand_T *xp, int idx)
1047{
1048 static vimmenu_T *menu = NULL;
1049#define TBUFFER_LEN 256
1050 static char_u tbuffer[TBUFFER_LEN]; /*hack*/
1051 char_u *str;
1052 static int should_advance = FALSE;
1053
1054 if (idx == 0) { /* first call: start at first item */
1055 menu = expand_menu;
1056 should_advance = FALSE;
1057 }
1058
1059 /* Skip Browse-style entries, popup menus and separators. */
1060 while (menu != NULL
1061 && ( menu_is_hidden(menu->dname)
1062 || (expand_emenu && menu_is_separator(menu->dname))
1063 || menu->dname[STRLEN(menu->dname) - 1] == '.'
1064 ))
1065 menu = menu->next;
1066
1067 if (menu == NULL) /* at end of linked list */
1068 return NULL;
1069
1070 if (menu->modes & expand_modes) {
1071 if (menu->children != NULL) {
1072 if (should_advance)
1073 STRLCPY(tbuffer, menu->en_dname, TBUFFER_LEN - 1);
1074 else {
1075 STRLCPY(tbuffer, menu->dname, TBUFFER_LEN - 1);
1076 if (menu->en_dname == NULL)
1077 should_advance = TRUE;
1078 }
1079 /* hack on menu separators: use a 'magic' char for the separator
1080 * so that '.' in names gets escaped properly */
1081 STRCAT(tbuffer, "\001");
1082 str = tbuffer;
1083 } else {
1084 if (should_advance)
1085 str = menu->en_dname;
1086 else {
1087 str = menu->dname;
1088 if (menu->en_dname == NULL)
1089 should_advance = TRUE;
1090 }
1091 }
1092 } else
1093 str = (char_u *)"";
1094
1095 if (should_advance)
1096 /* Advance to next menu entry. */
1097 menu = menu->next;
1098
1099 should_advance = !should_advance;
1100
1101 return str;
1102}
1103
1104
1105/// Skip over this element of the menu path and return the start of the next
1106/// element. Any \ and ^Vs are removed from the current element.
1107///
1108/// @param name may be modified.
1109/// @return start of the next element
1110char_u *menu_name_skip(char_u *const name)
1111{
1112 char_u *p;
1113
1114 for (p = name; *p && *p != '.'; MB_PTR_ADV(p)) {
1115 if (*p == '\\' || *p == Ctrl_V) {
1116 STRMOVE(p, p + 1);
1117 if (*p == NUL)
1118 break;
1119 }
1120 }
1121 if (*p)
1122 *p++ = NUL;
1123 return p;
1124}
1125
1126/*
1127 * Return TRUE when "name" matches with menu "menu". The name is compared in
1128 * two ways: raw menu name and menu name without '&'. ignore part after a TAB.
1129 */
1130static bool menu_name_equal(const char_u *const name, vimmenu_T *const menu)
1131{
1132 if (menu->en_name != NULL
1133 && (menu_namecmp(name, menu->en_name)
1134 || menu_namecmp(name, menu->en_dname)))
1135 return true;
1136 return menu_namecmp(name, menu->name) || menu_namecmp(name, menu->dname);
1137}
1138
1139static bool menu_namecmp(const char_u *const name, const char_u *const mname)
1140{
1141 int i;
1142
1143 for (i = 0; name[i] != NUL && name[i] != TAB; ++i)
1144 if (name[i] != mname[i])
1145 break;
1146 return (name[i] == NUL || name[i] == TAB)
1147 && (mname[i] == NUL || mname[i] == TAB);
1148}
1149
1150
1151/// Returns the \ref MENU_MODES specified by menu command `cmd`.
1152/// (eg :menu! returns MENU_CMDLINE_MODE | MENU_INSERT_MODE)
1153///
1154/// @param[in] cmd string like "nmenu", "vmenu", etc.
1155/// @param[in] forceit bang (!) was given after the command
1156/// @param[out] noremap If not NULL, the flag it points to is set according
1157/// to whether the command is a "nore" command.
1158/// @param[out] unmenu If not NULL, the flag it points to is set according
1159/// to whether the command is an "unmenu" command.
1160int
1161get_menu_cmd_modes(
1162 const char_u * cmd,
1163 bool forceit,
1164 int *noremap,
1165 int *unmenu
1166)
1167{
1168 int modes;
1169
1170 switch (*cmd++) {
1171 case 'v': /* vmenu, vunmenu, vnoremenu */
1172 modes = MENU_VISUAL_MODE | MENU_SELECT_MODE;
1173 break;
1174 case 'x': /* xmenu, xunmenu, xnoremenu */
1175 modes = MENU_VISUAL_MODE;
1176 break;
1177 case 's': /* smenu, sunmenu, snoremenu */
1178 modes = MENU_SELECT_MODE;
1179 break;
1180 case 'o': /* omenu */
1181 modes = MENU_OP_PENDING_MODE;
1182 break;
1183 case 'i': /* imenu */
1184 modes = MENU_INSERT_MODE;
1185 break;
1186 case 't':
1187 modes = MENU_TIP_MODE; /* tmenu */
1188 break;
1189 case 'c': /* cmenu */
1190 modes = MENU_CMDLINE_MODE;
1191 break;
1192 case 'a': /* amenu */
1193 modes = MENU_INSERT_MODE | MENU_CMDLINE_MODE | MENU_NORMAL_MODE
1194 | MENU_VISUAL_MODE | MENU_SELECT_MODE
1195 | MENU_OP_PENDING_MODE;
1196 break;
1197 case 'n':
1198 if (*cmd != 'o') { /* nmenu, not noremenu */
1199 modes = MENU_NORMAL_MODE;
1200 break;
1201 }
1202 FALLTHROUGH;
1203 default:
1204 cmd--;
1205 if (forceit) {
1206 // menu!!
1207 modes = MENU_INSERT_MODE | MENU_CMDLINE_MODE;
1208 } else {
1209 // menu
1210 modes = MENU_NORMAL_MODE | MENU_VISUAL_MODE | MENU_SELECT_MODE
1211 | MENU_OP_PENDING_MODE;
1212 }
1213 }
1214
1215 if (noremap != NULL)
1216 *noremap = (*cmd == 'n' ? REMAP_NONE : REMAP_YES);
1217 if (unmenu != NULL)
1218 *unmenu = (*cmd == 'u');
1219 return modes;
1220}
1221
1222/*
1223 * Modify a menu name starting with "PopUp" to include the mode character.
1224 * Returns the name in allocated memory.
1225 */
1226static char_u *popup_mode_name(char_u *name, int idx)
1227{
1228 size_t len = STRLEN(name);
1229 assert(len >= 4);
1230
1231 char_u *p = vim_strnsave(name, len + 1);
1232 memmove(p + 6, p + 5, len - 4);
1233 p[5] = menu_mode_chars[idx];
1234
1235 return p;
1236}
1237
1238
1239/// Duplicate the menu item text and then process to see if a mnemonic key
1240/// and/or accelerator text has been identified.
1241///
1242/// @param str The menu item text.
1243/// @param[out] mnemonic If non-NULL, *mnemonic is set to the character after
1244/// the first '&'.
1245/// @param[out] actext If non-NULL, *actext is set to the text after the first
1246/// TAB, but only if a TAB was found. Memory pointed to is newly
1247/// allocated.
1248///
1249/// @return a pointer to allocated memory.
1250static char_u *menu_text(const char_u *str, int *mnemonic, char_u **actext)
1251 FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT
1252 FUNC_ATTR_NONNULL_ARG(1)
1253{
1254 char_u *p;
1255 char_u *text;
1256
1257 /* Locate accelerator text, after the first TAB */
1258 p = vim_strchr(str, TAB);
1259 if (p != NULL) {
1260 if (actext != NULL)
1261 *actext = vim_strsave(p + 1);
1262 assert(p >= str);
1263 text = vim_strnsave(str, (size_t)(p - str));
1264 } else
1265 text = vim_strsave(str);
1266
1267 /* Find mnemonic characters "&a" and reduce "&&" to "&". */
1268 for (p = text; p != NULL; ) {
1269 p = vim_strchr(p, '&');
1270 if (p != NULL) {
1271 if (p[1] == NUL) /* trailing "&" */
1272 break;
1273 if (mnemonic != NULL && p[1] != '&')
1274 *mnemonic = p[1];
1275 STRMOVE(p, p + 1);
1276 p = p + 1;
1277 }
1278 }
1279 return text;
1280}
1281
1282/*
1283 * Return TRUE if "name" can be a menu in the MenuBar.
1284 */
1285int menu_is_menubar(char_u *name)
1286{
1287 return !menu_is_popup(name)
1288 && !menu_is_toolbar(name)
1289 && *name != MNU_HIDDEN_CHAR;
1290}
1291
1292/*
1293 * Return TRUE if "name" is a popup menu name.
1294 */
1295int menu_is_popup(char_u *name)
1296{
1297 return STRNCMP(name, "PopUp", 5) == 0;
1298}
1299
1300
1301/*
1302 * Return TRUE if "name" is a toolbar menu name.
1303 */
1304int menu_is_toolbar(char_u *name)
1305{
1306 return STRNCMP(name, "ToolBar", 7) == 0;
1307}
1308
1309/*
1310 * Return TRUE if the name is a menu separator identifier: Starts and ends
1311 * with '-'
1312 */
1313int menu_is_separator(char_u *name)
1314{
1315 return name[0] == '-' && name[STRLEN(name) - 1] == '-';
1316}
1317
1318
1319/// True if a popup menu or starts with \ref MNU_HIDDEN_CHAR
1320///
1321/// @return true if the menu is hidden
1322static int menu_is_hidden(char_u *name)
1323{
1324 return (name[0] == MNU_HIDDEN_CHAR)
1325 || (menu_is_popup(name) && name[5] != NUL);
1326}
1327
1328/*
1329 * Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy and
1330 * execute it.
1331 */
1332void ex_emenu(exarg_T *eap)
1333{
1334 vimmenu_T *menu;
1335 char_u *name;
1336 char_u *saved_name;
1337 char_u *p;
1338 int idx;
1339 char_u *mode;
1340
1341 saved_name = vim_strsave(eap->arg);
1342
1343 menu = root_menu;
1344 name = saved_name;
1345 while (*name) {
1346 /* Find in the menu hierarchy */
1347 p = menu_name_skip(name);
1348
1349 while (menu != NULL) {
1350 if (menu_name_equal(name, menu)) {
1351 if (*p == NUL && menu->children != NULL) {
1352 EMSG(_("E333: Menu path must lead to a menu item"));
1353 menu = NULL;
1354 } else if (*p != NUL && menu->children == NULL) {
1355 EMSG(_(e_notsubmenu));
1356 menu = NULL;
1357 }
1358 break;
1359 }
1360 menu = menu->next;
1361 }
1362 if (menu == NULL || *p == NUL)
1363 break;
1364 menu = menu->children;
1365 name = p;
1366 }
1367 xfree(saved_name);
1368 if (menu == NULL) {
1369 EMSG2(_("E334: Menu not found: %s"), eap->arg);
1370 return;
1371 }
1372
1373 /* Found the menu, so execute.
1374 * Use the Insert mode entry when returning to Insert mode. */
1375 if (((State & INSERT) || restart_edit) && !current_sctx.sc_sid) {
1376 mode = (char_u *)"Insert";
1377 idx = MENU_INDEX_INSERT;
1378 } else if (State & CMDLINE) {
1379 mode = (char_u *)"Command";
1380 idx = MENU_INDEX_CMDLINE;
1381 } else if (get_real_state() & VISUAL) {
1382 /* Detect real visual mode -- if we are really in visual mode we
1383 * don't need to do any guesswork to figure out what the selection
1384 * is. Just execute the visual binding for the menu. */
1385 mode = (char_u *)"Visual";
1386 idx = MENU_INDEX_VISUAL;
1387 } else if (eap->addr_count) {
1388 pos_T tpos;
1389
1390 mode = (char_u *)"Visual";
1391 idx = MENU_INDEX_VISUAL;
1392
1393 /* GEDDES: This is not perfect - but it is a
1394 * quick way of detecting whether we are doing this from a
1395 * selection - see if the range matches up with the visual
1396 * select start and end. */
1397 if ((curbuf->b_visual.vi_start.lnum == eap->line1)
1398 && (curbuf->b_visual.vi_end.lnum) == eap->line2) {
1399 /* Set it up for visual mode - equivalent to gv. */
1400 VIsual_mode = curbuf->b_visual.vi_mode;
1401 tpos = curbuf->b_visual.vi_end;
1402 curwin->w_cursor = curbuf->b_visual.vi_start;
1403 curwin->w_curswant = curbuf->b_visual.vi_curswant;
1404 } else {
1405 /* Set it up for line-wise visual mode */
1406 VIsual_mode = 'V';
1407 curwin->w_cursor.lnum = eap->line1;
1408 curwin->w_cursor.col = 1;
1409 tpos.lnum = eap->line2;
1410 tpos.col = MAXCOL;
1411 tpos.coladd = 0;
1412 }
1413
1414 /* Activate visual mode */
1415 VIsual_active = TRUE;
1416 VIsual_reselect = TRUE;
1417 check_cursor();
1418 VIsual = curwin->w_cursor;
1419 curwin->w_cursor = tpos;
1420
1421 check_cursor();
1422
1423 /* Adjust the cursor to make sure it is in the correct pos
1424 * for exclusive mode */
1425 if (*p_sel == 'e' && gchar_cursor() != NUL)
1426 ++curwin->w_cursor.col;
1427 } else {
1428 mode = (char_u *)"Normal";
1429 idx = MENU_INDEX_NORMAL;
1430 }
1431
1432 assert(idx != MENU_INDEX_INVALID);
1433 if (menu->strings[idx] != NULL) {
1434 // When executing a script or function execute the commands right now.
1435 // Otherwise put them in the typeahead buffer.
1436 if (current_sctx.sc_sid != 0) {
1437 exec_normal_cmd(menu->strings[idx], menu->noremap[idx],
1438 menu->silent[idx]);
1439 } else {
1440 ins_typebuf(menu->strings[idx], menu->noremap[idx], 0, true,
1441 menu->silent[idx]);
1442 }
1443 } else {
1444 EMSG2(_("E335: Menu not defined for %s mode"), mode);
1445 }
1446}
1447
1448/*
1449 * Translation of menu names. Just a simple lookup table.
1450 */
1451
1452typedef struct {
1453 char_u *from; /* English name */
1454 char_u *from_noamp; /* same, without '&' */
1455 char_u *to; /* translated name */
1456} menutrans_T;
1457
1458static garray_T menutrans_ga = GA_EMPTY_INIT_VALUE;
1459
1460#define FREE_MENUTRANS(mt) \
1461 menutrans_T* _mt = (mt); \
1462 xfree(_mt->from); \
1463 xfree(_mt->from_noamp); \
1464 xfree(_mt->to)
1465
1466/*
1467 * ":menutrans".
1468 * This function is also defined without the +multi_lang feature, in which
1469 * case the commands are ignored.
1470 */
1471void ex_menutranslate(exarg_T *eap)
1472{
1473 char_u *arg = eap->arg;
1474 char_u *from, *from_noamp, *to;
1475
1476 if (menutrans_ga.ga_itemsize == 0)
1477 ga_init(&menutrans_ga, (int)sizeof(menutrans_T), 5);
1478
1479 /*
1480 * ":menutrans clear": clear all translations.
1481 */
1482 if (STRNCMP(arg, "clear", 5) == 0 && ends_excmd(*skipwhite(arg + 5))) {
1483 GA_DEEP_CLEAR(&menutrans_ga, menutrans_T, FREE_MENUTRANS);
1484
1485 /* Delete all "menutrans_" global variables. */
1486 del_menutrans_vars();
1487 } else {
1488 /* ":menutrans from to": add translation */
1489 from = arg;
1490 arg = menu_skip_part(arg);
1491 to = skipwhite(arg);
1492 *arg = NUL;
1493 arg = menu_skip_part(to);
1494 if (arg == to)
1495 EMSG(_(e_invarg));
1496 else {
1497 from = vim_strsave(from);
1498 from_noamp = menu_text(from, NULL, NULL);
1499 assert(arg >= to);
1500 to = vim_strnsave(to, (size_t)(arg - to));
1501 menu_translate_tab_and_shift(from);
1502 menu_translate_tab_and_shift(to);
1503 menu_unescape_name(from);
1504 menu_unescape_name(to);
1505 menutrans_T* tp = GA_APPEND_VIA_PTR(menutrans_T, &menutrans_ga);
1506 tp->from = from;
1507 tp->from_noamp = from_noamp;
1508 tp->to = to;
1509 }
1510 }
1511}
1512
1513/*
1514 * Find the character just after one part of a menu name.
1515 */
1516static char_u *menu_skip_part(char_u *p)
1517{
1518 while (*p != NUL && *p != '.' && !ascii_iswhite(*p)) {
1519 if ((*p == '\\' || *p == Ctrl_V) && p[1] != NUL)
1520 ++p;
1521 ++p;
1522 }
1523 return p;
1524}
1525
1526/*
1527 * Lookup part of a menu name in the translations.
1528 * Return a pointer to the translation or NULL if not found.
1529 */
1530static char_u *menutrans_lookup(char_u *name, int len)
1531{
1532 menutrans_T *tp = (menutrans_T *)menutrans_ga.ga_data;
1533 char_u *dname;
1534
1535 for (int i = 0; i < menutrans_ga.ga_len; i++) {
1536 if (STRNICMP(name, tp[i].from, len) == 0 && tp[i].from[len] == NUL) {
1537 return tp[i].to;
1538 }
1539 }
1540
1541 /* Now try again while ignoring '&' characters. */
1542 char_u c = name[len];
1543 name[len] = NUL;
1544 dname = menu_text(name, NULL, NULL);
1545 name[len] = c;
1546 for (int i = 0; i < menutrans_ga.ga_len; i++) {
1547 if (STRICMP(dname, tp[i].from_noamp) == 0) {
1548 xfree(dname);
1549 return tp[i].to;
1550 }
1551 }
1552 xfree(dname);
1553
1554 return NULL;
1555}
1556
1557/*
1558 * Unescape the name in the translate dictionary table.
1559 */
1560static void menu_unescape_name(char_u *name)
1561{
1562 char_u *p;
1563
1564 for (p = name; *p && *p != '.'; MB_PTR_ADV(p)) {
1565 if (*p == '\\') {
1566 STRMOVE(p, p + 1);
1567 }
1568 }
1569}
1570
1571/*
1572 * Isolate the menu name.
1573 * Skip the menu name, and translate <Tab> into a real TAB.
1574 */
1575static char_u *menu_translate_tab_and_shift(char_u *arg_start)
1576{
1577 char_u *arg = arg_start;
1578
1579 while (*arg && !ascii_iswhite(*arg)) {
1580 if ((*arg == '\\' || *arg == Ctrl_V) && arg[1] != NUL)
1581 arg++;
1582 else if (STRNICMP(arg, "<TAB>", 5) == 0) {
1583 *arg = TAB;
1584 STRMOVE(arg + 1, arg + 5);
1585 }
1586 arg++;
1587 }
1588 if (*arg != NUL)
1589 *arg++ = NUL;
1590 arg = skipwhite(arg);
1591
1592 return arg;
1593}
1594
1595