blob: 07cf57ec4cd4f64a8ab93960df68747b7d0ecfc5 [file] [log] [blame]
Bram Moolenaaredf3f972016-08-29 22:49:24 +02001/* vi:set ts=8 sts=4 sw=4 noet:
Bram Moolenaar071d4272004-06-13 20:20:40 +00002 *
3 * VIM - Vi IMproved by Bram Moolenaar
4 * GUI/Motif support by Robert Webb
5 *
6 * Do ":help uganda" in Vim to read copying and usage conditions.
7 * Do ":help credits" in Vim to see a list of people who contributed.
8 * See README.txt for an overview of the Vim source code.
9 */
10
11/*
12 * Code for menus. Used for the GUI and 'wildmenu'.
13 */
14
15#include "vim.h"
16
17#if defined(FEAT_MENU) || defined(PROTO)
18
19#define MENUDEPTH 10 /* maximum depth of menus */
20
21#ifdef FEAT_GUI_W32
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010022static int add_menu_path(char_u *, vimmenu_T *, int *, char_u *, int);
Bram Moolenaar071d4272004-06-13 20:20:40 +000023#else
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010024static int add_menu_path(char_u *, vimmenu_T *, int *, char_u *);
Bram Moolenaar071d4272004-06-13 20:20:40 +000025#endif
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010026static int menu_nable_recurse(vimmenu_T *menu, char_u *name, int modes, int enable);
27static int remove_menu(vimmenu_T **, char_u *, int, int silent);
28static void free_menu(vimmenu_T **menup);
29static void free_menu_string(vimmenu_T *, int);
30static int show_menus(char_u *, int);
31static void show_menus_recursive(vimmenu_T *, int, int);
32static int menu_name_equal(char_u *name, vimmenu_T *menu);
33static int menu_namecmp(char_u *name, char_u *mname);
34static int get_menu_cmd_modes(char_u *, int, int *, int *);
35static char_u *popup_mode_name(char_u *name, int idx);
36static char_u *menu_text(char_u *text, int *mnemonic, char_u **actext);
Bram Moolenaar071d4272004-06-13 20:20:40 +000037#ifdef FEAT_GUI
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010038static int get_menu_mode(void);
39static void gui_update_menus_recurse(vimmenu_T *, int);
Bram Moolenaar071d4272004-06-13 20:20:40 +000040#endif
41
42#if defined(FEAT_GUI_W32) && defined(FEAT_TEAROFF)
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010043static void gui_create_tearoffs_recurse(vimmenu_T *menu, const char_u *pname, int *pri_tab, int pri_idx);
44static void gui_add_tearoff(char_u *tearpath, int *pri_tab, int pri_idx);
45static void gui_destroy_tearoffs_recurse(vimmenu_T *menu);
Bram Moolenaar071d4272004-06-13 20:20:40 +000046static int s_tearoffs = FALSE;
47#endif
48
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010049static int menu_is_hidden(char_u *name);
Bram Moolenaar071d4272004-06-13 20:20:40 +000050#if defined(FEAT_CMDL_COMPL) || (defined(FEAT_GUI_W32) && defined(FEAT_TEAROFF))
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010051static int menu_is_tearoff(char_u *name);
Bram Moolenaar071d4272004-06-13 20:20:40 +000052#endif
53
54#if defined(FEAT_MULTI_LANG) || defined(FEAT_TOOLBAR)
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010055static char_u *menu_skip_part(char_u *p);
Bram Moolenaar071d4272004-06-13 20:20:40 +000056#endif
57#ifdef FEAT_MULTI_LANG
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010058static char_u *menutrans_lookup(char_u *name, int len);
59static void menu_unescape_name(char_u *p);
Bram Moolenaar071d4272004-06-13 20:20:40 +000060#endif
61
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010062static char_u *menu_translate_tab_and_shift(char_u *arg_start);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +020063
Bram Moolenaar071d4272004-06-13 20:20:40 +000064/* The character for each menu mode */
Bram Moolenaareb3593b2006-04-22 22:33:57 +000065static char_u menu_mode_chars[] = {'n', 'v', 's', 'o', 'i', 'c', 't'};
Bram Moolenaar071d4272004-06-13 20:20:40 +000066
67static char_u e_notsubmenu[] = N_("E327: Part of menu-item path is not sub-menu");
68static char_u e_othermode[] = N_("E328: Menu only exists in another mode");
Bram Moolenaar342337a2005-07-21 21:11:17 +000069static char_u e_nomenu[] = N_("E329: No menu \"%s\"");
Bram Moolenaar071d4272004-06-13 20:20:40 +000070
71#ifdef FEAT_TOOLBAR
72static const char *toolbar_names[] =
73{
74 /* 0 */ "New", "Open", "Save", "Undo", "Redo",
75 /* 5 */ "Cut", "Copy", "Paste", "Print", "Help",
76 /* 10 */ "Find", "SaveAll", "SaveSesn", "NewSesn", "LoadSesn",
77 /* 15 */ "RunScript", "Replace", "WinClose", "WinMax", "WinMin",
78 /* 20 */ "WinSplit", "Shell", "FindPrev", "FindNext", "FindHelp",
79 /* 25 */ "Make", "TagJump", "RunCtags", "WinVSplit", "WinMaxWidth",
80 /* 30 */ "WinMinWidth", "Exit"
81};
82# define TOOLBAR_NAME_COUNT (sizeof(toolbar_names) / sizeof(char *))
83#endif
84
85/*
86 * Do the :menu command and relatives.
87 */
88 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +010089ex_menu(
90 exarg_T *eap) /* Ex command arguments */
Bram Moolenaar071d4272004-06-13 20:20:40 +000091{
92 char_u *menu_path;
93 int modes;
94 char_u *map_to;
95 int noremap;
96 int silent = FALSE;
Bram Moolenaar8b2d9c42006-05-03 21:28:47 +000097 int special = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +000098 int unmenu;
99 char_u *map_buf;
100 char_u *arg;
101 char_u *p;
102 int i;
Bram Moolenaar241a8aa2005-12-06 20:04:44 +0000103#if defined(FEAT_GUI) && !defined(FEAT_GUI_GTK)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000104 int old_menu_height;
Bram Moolenaare89ff042016-02-20 22:17:05 +0100105# if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_W32)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000106 int old_toolbar_height;
107# endif
108#endif
109 int pri_tab[MENUDEPTH + 1];
110 int enable = MAYBE; /* TRUE for "menu enable", FALSE for "menu
111 * disable */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000112#ifdef FEAT_TOOLBAR
113 char_u *icon = NULL;
114#endif
115 vimmenu_T menuarg;
116
117 modes = get_menu_cmd_modes(eap->cmd, eap->forceit, &noremap, &unmenu);
118 arg = eap->arg;
119
120 for (;;)
121 {
122 if (STRNCMP(arg, "<script>", 8) == 0)
123 {
124 noremap = REMAP_SCRIPT;
125 arg = skipwhite(arg + 8);
126 continue;
127 }
128 if (STRNCMP(arg, "<silent>", 8) == 0)
129 {
130 silent = TRUE;
131 arg = skipwhite(arg + 8);
132 continue;
133 }
Bram Moolenaar8b2d9c42006-05-03 21:28:47 +0000134 if (STRNCMP(arg, "<special>", 9) == 0)
135 {
136 special = TRUE;
137 arg = skipwhite(arg + 9);
138 continue;
139 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000140 break;
141 }
142
143
144 /* Locate an optional "icon=filename" argument. */
145 if (STRNCMP(arg, "icon=", 5) == 0)
146 {
147 arg += 5;
148#ifdef FEAT_TOOLBAR
149 icon = arg;
150#endif
151 while (*arg != NUL && *arg != ' ')
152 {
153 if (*arg == '\\')
Bram Moolenaar8c8de832008-06-24 22:58:06 +0000154 STRMOVE(arg, arg + 1);
Bram Moolenaar91acfff2017-03-12 19:22:36 +0100155 MB_PTR_ADV(arg);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000156 }
157 if (*arg != NUL)
158 {
159 *arg++ = NUL;
160 arg = skipwhite(arg);
161 }
162 }
163
164 /*
165 * Fill in the priority table.
166 */
167 for (p = arg; *p; ++p)
168 if (!VIM_ISDIGIT(*p) && *p != '.')
169 break;
Bram Moolenaar1c465442017-03-12 20:10:05 +0100170 if (VIM_ISWHITE(*p))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000171 {
Bram Moolenaar1c465442017-03-12 20:10:05 +0100172 for (i = 0; i < MENUDEPTH && !VIM_ISWHITE(*arg); ++i)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000173 {
174 pri_tab[i] = getdigits(&arg);
175 if (pri_tab[i] == 0)
176 pri_tab[i] = 500;
177 if (*arg == '.')
178 ++arg;
179 }
180 arg = skipwhite(arg);
181 }
182 else if (eap->addr_count && eap->line2 != 0)
183 {
184 pri_tab[0] = eap->line2;
185 i = 1;
186 }
187 else
188 i = 0;
189 while (i < MENUDEPTH)
190 pri_tab[i++] = 500;
191 pri_tab[MENUDEPTH] = -1; /* mark end of the table */
192
193 /*
194 * Check for "disable" or "enable" argument.
195 */
Bram Moolenaar1c465442017-03-12 20:10:05 +0100196 if (STRNCMP(arg, "enable", 6) == 0 && VIM_ISWHITE(arg[6]))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000197 {
198 enable = TRUE;
199 arg = skipwhite(arg + 6);
200 }
Bram Moolenaar1c465442017-03-12 20:10:05 +0100201 else if (STRNCMP(arg, "disable", 7) == 0 && VIM_ISWHITE(arg[7]))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000202 {
203 enable = FALSE;
204 arg = skipwhite(arg + 7);
205 }
206
207 /*
208 * If there is no argument, display all menus.
209 */
210 if (*arg == NUL)
211 {
212 show_menus(arg, modes);
213 return;
214 }
215
216#ifdef FEAT_TOOLBAR
217 /*
218 * Need to get the toolbar icon index before doing the translation.
219 */
220 menuarg.iconidx = -1;
221 menuarg.icon_builtin = FALSE;
222 if (menu_is_toolbar(arg))
223 {
224 menu_path = menu_skip_part(arg);
225 if (*menu_path == '.')
226 {
227 p = menu_skip_part(++menu_path);
228 if (STRNCMP(menu_path, "BuiltIn", 7) == 0)
229 {
230 if (skipdigits(menu_path + 7) == p)
231 {
232 menuarg.iconidx = atoi((char *)menu_path + 7);
Bram Moolenaaraf0167f2009-05-16 15:31:32 +0000233 if (menuarg.iconidx >= (int)TOOLBAR_NAME_COUNT)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000234 menuarg.iconidx = -1;
235 else
236 menuarg.icon_builtin = TRUE;
237 }
238 }
239 else
240 {
Bram Moolenaaraf0167f2009-05-16 15:31:32 +0000241 for (i = 0; i < (int)TOOLBAR_NAME_COUNT; ++i)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000242 if (STRNCMP(toolbar_names[i], menu_path, p - menu_path)
243 == 0)
244 {
245 menuarg.iconidx = i;
246 break;
247 }
248 }
249 }
250 }
251#endif
252
Bram Moolenaar071d4272004-06-13 20:20:40 +0000253 menu_path = arg;
254 if (*menu_path == '.')
255 {
256 EMSG2(_(e_invarg2), menu_path);
257 goto theend;
258 }
259
Bram Moolenaar70b11cd2010-05-14 22:24:40 +0200260 map_to = menu_translate_tab_and_shift(arg);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000261
262 /*
263 * If there is only a menu name, display menus with that name.
264 */
265 if (*map_to == NUL && !unmenu && enable == MAYBE)
266 {
267 show_menus(menu_path, modes);
268 goto theend;
269 }
270 else if (*map_to != NUL && (unmenu || enable != MAYBE))
271 {
272 EMSG(_(e_trailing));
273 goto theend;
274 }
Bram Moolenaar241a8aa2005-12-06 20:04:44 +0000275#if defined(FEAT_GUI) && !(defined(FEAT_GUI_GTK) || defined(FEAT_GUI_PHOTON))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000276 old_menu_height = gui.menu_height;
Bram Moolenaare89ff042016-02-20 22:17:05 +0100277# if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_W32)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000278 old_toolbar_height = gui.toolbar_height;
279# endif
280#endif
281
282 if (enable != MAYBE)
283 {
284 /*
285 * Change sensitivity of the menu.
286 * For the PopUp menu, remove a menu for each mode separately.
287 * Careful: menu_nable_recurse() changes menu_path.
288 */
289 if (STRCMP(menu_path, "*") == 0) /* meaning: do all menus */
290 menu_path = (char_u *)"";
291
292 if (menu_is_popup(menu_path))
293 {
294 for (i = 0; i < MENU_INDEX_TIP; ++i)
295 if (modes & (1 << i))
296 {
297 p = popup_mode_name(menu_path, i);
298 if (p != NULL)
299 {
300 menu_nable_recurse(root_menu, p, MENU_ALL_MODES,
301 enable);
302 vim_free(p);
303 }
304 }
305 }
306 menu_nable_recurse(root_menu, menu_path, modes, enable);
307 }
308 else if (unmenu)
309 {
310 /*
311 * Delete menu(s).
312 */
313 if (STRCMP(menu_path, "*") == 0) /* meaning: remove all menus */
314 menu_path = (char_u *)"";
315
316 /*
317 * For the PopUp menu, remove a menu for each mode separately.
318 */
319 if (menu_is_popup(menu_path))
320 {
321 for (i = 0; i < MENU_INDEX_TIP; ++i)
322 if (modes & (1 << i))
323 {
324 p = popup_mode_name(menu_path, i);
325 if (p != NULL)
326 {
327 remove_menu(&root_menu, p, MENU_ALL_MODES, TRUE);
328 vim_free(p);
329 }
330 }
331 }
332
333 /* Careful: remove_menu() changes menu_path */
334 remove_menu(&root_menu, menu_path, modes, FALSE);
335 }
336 else
337 {
338 /*
339 * Add menu(s).
340 * Replace special key codes.
341 */
342 if (STRICMP(map_to, "<nop>") == 0) /* "<Nop>" means nothing */
343 {
344 map_to = (char_u *)"";
345 map_buf = NULL;
346 }
Bram Moolenaar3fdfa4a2004-10-07 21:02:47 +0000347 else if (modes & MENU_TIP_MODE)
348 map_buf = NULL; /* Menu tips are plain text. */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000349 else
Bram Moolenaar8b2d9c42006-05-03 21:28:47 +0000350 map_to = replace_termcodes(map_to, &map_buf, FALSE, TRUE, special);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000351 menuarg.modes = modes;
352#ifdef FEAT_TOOLBAR
353 menuarg.iconfile = icon;
354#endif
355 menuarg.noremap[0] = noremap;
356 menuarg.silent[0] = silent;
357 add_menu_path(menu_path, &menuarg, pri_tab, map_to
358#ifdef FEAT_GUI_W32
359 , TRUE
360#endif
361 );
362
363 /*
364 * For the PopUp menu, add a menu for each mode separately.
365 */
366 if (menu_is_popup(menu_path))
367 {
368 for (i = 0; i < MENU_INDEX_TIP; ++i)
369 if (modes & (1 << i))
370 {
371 p = popup_mode_name(menu_path, i);
372 if (p != NULL)
373 {
374 /* Include all modes, to make ":amenu" work */
375 menuarg.modes = modes;
376#ifdef FEAT_TOOLBAR
377 menuarg.iconfile = NULL;
378 menuarg.iconidx = -1;
379 menuarg.icon_builtin = FALSE;
380#endif
381 add_menu_path(p, &menuarg, pri_tab, map_to
382#ifdef FEAT_GUI_W32
383 , TRUE
384#endif
385 );
386 vim_free(p);
387 }
388 }
389 }
390
391 vim_free(map_buf);
392 }
393
Bram Moolenaar241a8aa2005-12-06 20:04:44 +0000394#if defined(FEAT_GUI) && !(defined(FEAT_GUI_GTK))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000395 /* If the menubar height changed, resize the window */
396 if (gui.in_use
397 && (gui.menu_height != old_menu_height
Bram Moolenaare89ff042016-02-20 22:17:05 +0100398# if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_W32)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000399 || gui.toolbar_height != old_toolbar_height
400# endif
401 ))
Bram Moolenaar04a9d452006-03-27 21:03:26 +0000402 gui_set_shellsize(FALSE, FALSE, RESIZE_VERT);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000403#endif
404
405theend:
Bram Moolenaar071d4272004-06-13 20:20:40 +0000406 ;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000407}
408
409/*
410 * Add the menu with the given name to the menu hierarchy
411 */
412 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100413add_menu_path(
414 char_u *menu_path,
415 vimmenu_T *menuarg, /* passes modes, iconfile, iconidx,
Bram Moolenaar071d4272004-06-13 20:20:40 +0000416 icon_builtin, silent[0], noremap[0] */
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100417 int *pri_tab,
418 char_u *call_data
Bram Moolenaar071d4272004-06-13 20:20:40 +0000419#ifdef FEAT_GUI_W32
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100420 , int addtearoff /* may add tearoff item */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000421#endif
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100422 )
Bram Moolenaar071d4272004-06-13 20:20:40 +0000423{
424 char_u *path_name;
425 int modes = menuarg->modes;
426 vimmenu_T **menup;
427 vimmenu_T *menu = NULL;
428 vimmenu_T *parent;
429 vimmenu_T **lower_pri;
430 char_u *p;
431 char_u *name;
432 char_u *dname;
433 char_u *next_name;
434 int i;
435 int c;
Bram Moolenaar7871a502010-05-14 21:19:23 +0200436 int d;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000437#ifdef FEAT_GUI
438 int idx;
439 int new_idx;
440#endif
441 int pri_idx = 0;
442 int old_modes = 0;
443 int amenu;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +0200444#ifdef FEAT_MULTI_LANG
445 char_u *en_name;
446 char_u *map_to = NULL;
447#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000448
449 /* Make a copy so we can stuff around with it, since it could be const */
450 path_name = vim_strsave(menu_path);
451 if (path_name == NULL)
452 return FAIL;
453 menup = &root_menu;
454 parent = NULL;
455 name = path_name;
456 while (*name)
457 {
458 /* Get name of this element in the menu hierarchy, and the simplified
459 * name (without mnemonic and accelerator text). */
460 next_name = menu_name_skip(name);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +0200461#ifdef FEAT_MULTI_LANG
Bram Moolenaar442b4222010-05-24 21:34:22 +0200462 map_to = menutrans_lookup(name, (int)STRLEN(name));
Bram Moolenaar70b11cd2010-05-14 22:24:40 +0200463 if (map_to != NULL)
464 {
465 en_name = name;
466 name = map_to;
467 }
468 else
469 en_name = NULL;
470#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000471 dname = menu_text(name, NULL, NULL);
Bram Moolenaar18a0b122006-08-16 13:55:16 +0000472 if (dname == NULL)
473 goto erret;
474 if (*dname == NUL)
475 {
476 /* Only a mnemonic or accelerator is not valid. */
477 EMSG(_("E792: Empty menu name"));
478 goto erret;
479 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000480
481 /* See if it's already there */
482 lower_pri = menup;
483#ifdef FEAT_GUI
484 idx = 0;
485 new_idx = 0;
486#endif
487 menu = *menup;
488 while (menu != NULL)
489 {
490 if (menu_name_equal(name, menu) || menu_name_equal(dname, menu))
491 {
492 if (*next_name == NUL && menu->children != NULL)
493 {
494 if (!sys_menu)
495 EMSG(_("E330: Menu path must not lead to a sub-menu"));
496 goto erret;
497 }
498 if (*next_name != NUL && menu->children == NULL
499#ifdef FEAT_GUI_W32
500 && addtearoff
501#endif
502 )
503 {
504 if (!sys_menu)
505 EMSG(_(e_notsubmenu));
506 goto erret;
507 }
508 break;
509 }
510 menup = &menu->next;
511
512 /* Count menus, to find where this one needs to be inserted.
513 * Ignore menus that are not in the menubar (PopUp and Toolbar) */
514 if (parent != NULL || menu_is_menubar(menu->name))
515 {
516#ifdef FEAT_GUI
517 ++idx;
518#endif
519 if (menu->priority <= pri_tab[pri_idx])
520 {
521 lower_pri = menup;
522#ifdef FEAT_GUI
523 new_idx = idx;
524#endif
525 }
526 }
527 menu = menu->next;
528 }
529
530 if (menu == NULL)
531 {
532 if (*next_name == NUL && parent == NULL)
533 {
534 EMSG(_("E331: Must not add menu items directly to menu bar"));
535 goto erret;
536 }
537
538 if (menu_is_separator(dname) && *next_name != NUL)
539 {
540 EMSG(_("E332: Separator cannot be part of a menu path"));
541 goto erret;
542 }
543
544 /* Not already there, so lets add it */
545 menu = (vimmenu_T *)alloc_clear((unsigned)sizeof(vimmenu_T));
546 if (menu == NULL)
547 goto erret;
548
549 menu->modes = modes;
550 menu->enabled = MENU_ALL_MODES;
551 menu->name = vim_strsave(name);
552 /* separate mnemonic and accelerator text from actual menu name */
553 menu->dname = menu_text(name, &menu->mnemonic, &menu->actext);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +0200554#ifdef FEAT_MULTI_LANG
555 if (en_name != NULL)
556 {
557 menu->en_name = vim_strsave(en_name);
558 menu->en_dname = menu_text(en_name, NULL, NULL);
559 }
560 else
561 {
562 menu->en_name = NULL;
563 menu->en_dname = NULL;
564 }
565#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000566 menu->priority = pri_tab[pri_idx];
567 menu->parent = parent;
568#ifdef FEAT_GUI_MOTIF
569 menu->sensitive = TRUE; /* the default */
570#endif
571#ifdef FEAT_BEVAL_TIP
572 menu->tip = NULL;
573#endif
574#ifdef FEAT_GUI_ATHENA
575 menu->image = None; /* X-Windows definition for NULL*/
576#endif
577
578 /*
579 * Add after menu that has lower priority.
580 */
581 menu->next = *lower_pri;
582 *lower_pri = menu;
583
584 old_modes = 0;
585
586#ifdef FEAT_TOOLBAR
587 menu->iconidx = menuarg->iconidx;
588 menu->icon_builtin = menuarg->icon_builtin;
589 if (*next_name == NUL && menuarg->iconfile != NULL)
590 menu->iconfile = vim_strsave(menuarg->iconfile);
591#endif
592#if defined(FEAT_GUI_W32) && defined(FEAT_TEAROFF)
593 /* the tearoff item must be present in the modes of each item. */
594 if (parent != NULL && menu_is_tearoff(parent->children->dname))
595 parent->children->modes |= modes;
596#endif
597 }
598 else
599 {
600 old_modes = menu->modes;
601
602 /*
603 * If this menu option was previously only available in other
604 * modes, then make sure it's available for this one now
605 * Also enable a menu when it's created or changed.
606 */
607#ifdef FEAT_GUI_W32
608 /* If adding a tearbar (addtearoff == FALSE) don't update modes */
609 if (addtearoff)
610#endif
611 {
612 menu->modes |= modes;
613 menu->enabled |= modes;
614 }
615 }
616
617#ifdef FEAT_GUI
618 /*
619 * Add the menu item when it's used in one of the modes, but not when
620 * only a tooltip is defined.
621 */
622 if ((old_modes & MENU_ALL_MODES) == 0
623 && (menu->modes & MENU_ALL_MODES) != 0)
624 {
625 if (gui.in_use) /* Otherwise it will be added when GUI starts */
626 {
627 if (*next_name == NUL)
628 {
629 /* Real menu item, not sub-menu */
630 gui_mch_add_menu_item(menu, new_idx);
631
632 /* Want to update menus now even if mode not changed */
633 force_menu_update = TRUE;
634 }
635 else
636 {
637 /* Sub-menu (not at end of path yet) */
638 gui_mch_add_menu(menu, new_idx);
639 }
640 }
641
642# if defined(FEAT_GUI_W32) & defined(FEAT_TEAROFF)
643 /* When adding a new submenu, may add a tearoff item */
644 if ( addtearoff
645 && *next_name
646 && vim_strchr(p_go, GO_TEAROFF) != NULL
647 && menu_is_menubar(name))
648 {
649 char_u *tearpath;
650
651 /*
652 * The pointers next_name & path_name refer to a string with
653 * \'s and ^V's stripped out. But menu_path is a "raw"
654 * string, so we must correct for special characters.
655 */
656 tearpath = alloc((unsigned int)STRLEN(menu_path) + TEAR_LEN + 2);
657 if (tearpath != NULL)
658 {
659 char_u *s;
660 int idx;
661
662 STRCPY(tearpath, menu_path);
663 idx = (int)(next_name - path_name - 1);
Bram Moolenaar91acfff2017-03-12 19:22:36 +0100664 for (s = tearpath; *s && s < tearpath + idx; MB_PTR_ADV(s))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000665 {
666 if ((*s == '\\' || *s == Ctrl_V) && s[1])
667 {
668 ++idx;
669 ++s;
670 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000671 }
672 tearpath[idx] = NUL;
673 gui_add_tearoff(tearpath, pri_tab, pri_idx);
674 vim_free(tearpath);
675 }
676 }
677# endif
678 }
679#endif /* FEAT_GUI */
680
681 menup = &menu->children;
682 parent = menu;
683 name = next_name;
684 vim_free(dname);
Bram Moolenaar18a0b122006-08-16 13:55:16 +0000685 dname = NULL;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000686 if (pri_tab[pri_idx + 1] != -1)
687 ++pri_idx;
688 }
689 vim_free(path_name);
690
691 /*
692 * Only add system menu items which have not been defined yet.
693 * First check if this was an ":amenu".
694 */
695 amenu = ((modes & (MENU_NORMAL_MODE | MENU_INSERT_MODE)) ==
696 (MENU_NORMAL_MODE | MENU_INSERT_MODE));
697 if (sys_menu)
698 modes &= ~old_modes;
699
700 if (menu != NULL && modes)
701 {
702#ifdef FEAT_GUI
703 menu->cb = gui_menu_cb;
704#endif
705 p = (call_data == NULL) ? NULL : vim_strsave(call_data);
706
707 /* loop over all modes, may add more than one */
708 for (i = 0; i < MENU_MODES; ++i)
709 {
710 if (modes & (1 << i))
711 {
712 /* free any old menu */
713 free_menu_string(menu, i);
714
715 /* For "amenu", may insert an extra character.
716 * Don't do this if adding a tearbar (addtearoff == FALSE).
717 * Don't do this for "<Nop>". */
718 c = 0;
Bram Moolenaar7871a502010-05-14 21:19:23 +0200719 d = 0;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000720 if (amenu && call_data != NULL && *call_data != NUL
721#ifdef FEAT_GUI_W32
722 && addtearoff
723#endif
724 )
725 {
726 switch (1 << i)
727 {
728 case MENU_VISUAL_MODE:
Bram Moolenaarb3656ed2006-03-20 21:59:49 +0000729 case MENU_SELECT_MODE:
Bram Moolenaar071d4272004-06-13 20:20:40 +0000730 case MENU_OP_PENDING_MODE:
731 case MENU_CMDLINE_MODE:
732 c = Ctrl_C;
733 break;
734 case MENU_INSERT_MODE:
Bram Moolenaar7871a502010-05-14 21:19:23 +0200735 c = Ctrl_BSL;
736 d = Ctrl_O;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000737 break;
738 }
739 }
740
Bram Moolenaar7871a502010-05-14 21:19:23 +0200741 if (c != 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000742 {
Bram Moolenaar7871a502010-05-14 21:19:23 +0200743 menu->strings[i] = alloc((unsigned)(STRLEN(call_data) + 5 ));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000744 if (menu->strings[i] != NULL)
745 {
746 menu->strings[i][0] = c;
Bram Moolenaar7871a502010-05-14 21:19:23 +0200747 if (d == 0)
748 STRCPY(menu->strings[i] + 1, call_data);
749 else
750 {
751 menu->strings[i][1] = d;
752 STRCPY(menu->strings[i] + 2, call_data);
753 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000754 if (c == Ctrl_C)
755 {
Bram Moolenaara93fa7e2006-04-17 22:14:47 +0000756 int len = (int)STRLEN(menu->strings[i]);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000757
758 /* Append CTRL-\ CTRL-G to obey 'insertmode'. */
759 menu->strings[i][len] = Ctrl_BSL;
760 menu->strings[i][len + 1] = Ctrl_G;
761 menu->strings[i][len + 2] = NUL;
762 }
763 }
764 }
765 else
766 menu->strings[i] = p;
767 menu->noremap[i] = menuarg->noremap[0];
768 menu->silent[i] = menuarg->silent[0];
769 }
770 }
771#if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_W32) \
772 && (defined(FEAT_BEVAL) || defined(FEAT_GUI_GTK))
773 /* Need to update the menu tip. */
774 if (modes & MENU_TIP_MODE)
775 gui_mch_menu_set_tip(menu);
776#endif
777 }
778 return OK;
779
780erret:
781 vim_free(path_name);
782 vim_free(dname);
Bram Moolenaar18a0b122006-08-16 13:55:16 +0000783
784 /* Delete any empty submenu we added before discovering the error. Repeat
785 * for higher levels. */
786 while (parent != NULL && parent->children == NULL)
787 {
788 if (parent->parent == NULL)
789 menup = &root_menu;
790 else
791 menup = &parent->parent->children;
792 for ( ; *menup != NULL && *menup != parent; menup = &((*menup)->next))
793 ;
794 if (*menup == NULL) /* safety check */
795 break;
796 parent = parent->parent;
797 free_menu(menup);
798 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000799 return FAIL;
800}
801
802/*
803 * Set the (sub)menu with the given name to enabled or disabled.
804 * Called recursively.
805 */
806 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100807menu_nable_recurse(
808 vimmenu_T *menu,
809 char_u *name,
810 int modes,
811 int enable)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000812{
813 char_u *p;
814
815 if (menu == NULL)
816 return OK; /* Got to bottom of hierarchy */
817
818 /* Get name of this element in the menu hierarchy */
819 p = menu_name_skip(name);
820
821 /* Find the menu */
822 while (menu != NULL)
823 {
824 if (*name == NUL || *name == '*' || menu_name_equal(name, menu))
825 {
826 if (*p != NUL)
827 {
828 if (menu->children == NULL)
829 {
830 EMSG(_(e_notsubmenu));
831 return FAIL;
832 }
833 if (menu_nable_recurse(menu->children, p, modes, enable)
834 == FAIL)
835 return FAIL;
836 }
837 else
838 if (enable)
839 menu->enabled |= modes;
840 else
841 menu->enabled &= ~modes;
842
843 /*
844 * When name is empty, we are doing all menu items for the given
845 * modes, so keep looping, otherwise we are just doing the named
846 * menu item (which has been found) so break here.
847 */
848 if (*name != NUL && *name != '*')
849 break;
850 }
851 menu = menu->next;
852 }
853 if (*name != NUL && *name != '*' && menu == NULL)
854 {
Bram Moolenaar342337a2005-07-21 21:11:17 +0000855 EMSG2(_(e_nomenu), name);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000856 return FAIL;
857 }
858
859#ifdef FEAT_GUI
860 /* Want to update menus now even if mode not changed */
861 force_menu_update = TRUE;
862#endif
863
864 return OK;
865}
866
867/*
868 * Remove the (sub)menu with the given name from the menu hierarchy
869 * Called recursively.
870 */
871 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100872remove_menu(
873 vimmenu_T **menup,
874 char_u *name,
875 int modes,
876 int silent) /* don't give error messages */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000877{
878 vimmenu_T *menu;
879 vimmenu_T *child;
880 char_u *p;
881
882 if (*menup == NULL)
883 return OK; /* Got to bottom of hierarchy */
884
885 /* Get name of this element in the menu hierarchy */
886 p = menu_name_skip(name);
887
888 /* Find the menu */
889 while ((menu = *menup) != NULL)
890 {
891 if (*name == NUL || menu_name_equal(name, menu))
892 {
893 if (*p != NUL && menu->children == NULL)
894 {
895 if (!silent)
896 EMSG(_(e_notsubmenu));
897 return FAIL;
898 }
899 if ((menu->modes & modes) != 0x0)
900 {
901#if defined(FEAT_GUI_W32) & defined(FEAT_TEAROFF)
902 /*
903 * If we are removing all entries for this menu,MENU_ALL_MODES,
904 * Then kill any tearoff before we start
905 */
906 if (*p == NUL && modes == MENU_ALL_MODES)
907 {
908 if (IsWindow(menu->tearoff_handle))
909 DestroyWindow(menu->tearoff_handle);
910 }
911#endif
912 if (remove_menu(&menu->children, p, modes, silent) == FAIL)
913 return FAIL;
914 }
915 else if (*name != NUL)
916 {
917 if (!silent)
918 EMSG(_(e_othermode));
919 return FAIL;
920 }
921
922 /*
923 * When name is empty, we are removing all menu items for the given
924 * modes, so keep looping, otherwise we are just removing the named
925 * menu item (which has been found) so break here.
926 */
927 if (*name != NUL)
928 break;
929
930 /* Remove the menu item for the given mode[s]. If the menu item
931 * is no longer valid in ANY mode, delete it */
932 menu->modes &= ~modes;
933 if (modes & MENU_TIP_MODE)
934 free_menu_string(menu, MENU_INDEX_TIP);
935 if ((menu->modes & MENU_ALL_MODES) == 0)
936 free_menu(menup);
937 else
938 menup = &menu->next;
939 }
940 else
941 menup = &menu->next;
942 }
943 if (*name != NUL)
944 {
945 if (menu == NULL)
946 {
947 if (!silent)
Bram Moolenaar342337a2005-07-21 21:11:17 +0000948 EMSG2(_(e_nomenu), name);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000949 return FAIL;
950 }
951
952
953 /* Recalculate modes for menu based on the new updated children */
954 menu->modes &= ~modes;
955#if defined(FEAT_GUI_W32) & defined(FEAT_TEAROFF)
956 if ((s_tearoffs) && (menu->children != NULL)) /* there's a tear bar.. */
957 child = menu->children->next; /* don't count tearoff bar */
958 else
959#endif
960 child = menu->children;
961 for ( ; child != NULL; child = child->next)
962 menu->modes |= child->modes;
963 if (modes & MENU_TIP_MODE)
964 {
965 free_menu_string(menu, MENU_INDEX_TIP);
966#if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_W32) \
967 && (defined(FEAT_BEVAL) || defined(FEAT_GUI_GTK))
968 /* Need to update the menu tip. */
969 if (gui.in_use)
970 gui_mch_menu_set_tip(menu);
971#endif
972 }
973 if ((menu->modes & MENU_ALL_MODES) == 0)
974 {
975 /* The menu item is no longer valid in ANY mode, so delete it */
976#if defined(FEAT_GUI_W32) & defined(FEAT_TEAROFF)
977 if (s_tearoffs && menu->children != NULL) /* there's a tear bar.. */
978 free_menu(&menu->children);
979#endif
980 *menup = menu;
981 free_menu(menup);
982 }
983 }
984
985 return OK;
986}
987
988/*
989 * Free the given menu structure and remove it from the linked list.
990 */
991 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100992free_menu(vimmenu_T **menup)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000993{
994 int i;
995 vimmenu_T *menu;
996
997 menu = *menup;
998
999#ifdef FEAT_GUI
1000 /* Free machine specific menu structures (only when already created) */
1001 /* Also may rebuild a tearoff'ed menu */
1002 if (gui.in_use)
1003 gui_mch_destroy_menu(menu);
1004#endif
1005
1006 /* Don't change *menup until after calling gui_mch_destroy_menu(). The
1007 * MacOS code needs the original structure to properly delete the menu. */
1008 *menup = menu->next;
1009 vim_free(menu->name);
1010 vim_free(menu->dname);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001011#ifdef FEAT_MULTI_LANG
1012 vim_free(menu->en_name);
1013 vim_free(menu->en_dname);
1014#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001015 vim_free(menu->actext);
1016#ifdef FEAT_TOOLBAR
1017 vim_free(menu->iconfile);
Bram Moolenaarbee0c5b2005-02-07 22:03:36 +00001018# ifdef FEAT_GUI_MOTIF
1019 vim_free(menu->xpm_fname);
1020# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001021#endif
1022 for (i = 0; i < MENU_MODES; i++)
1023 free_menu_string(menu, i);
1024 vim_free(menu);
1025
1026#ifdef FEAT_GUI
1027 /* Want to update menus now even if mode not changed */
1028 force_menu_update = TRUE;
1029#endif
1030}
1031
1032/*
1033 * Free the menu->string with the given index.
1034 */
1035 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001036free_menu_string(vimmenu_T *menu, int idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001037{
1038 int count = 0;
1039 int i;
1040
1041 for (i = 0; i < MENU_MODES; i++)
1042 if (menu->strings[i] == menu->strings[idx])
1043 count++;
1044 if (count == 1)
1045 vim_free(menu->strings[idx]);
1046 menu->strings[idx] = NULL;
1047}
1048
1049/*
1050 * Show the mapping associated with a menu item or hierarchy in a sub-menu.
1051 */
1052 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001053show_menus(char_u *path_name, int modes)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001054{
1055 char_u *p;
1056 char_u *name;
1057 vimmenu_T *menu;
1058 vimmenu_T *parent = NULL;
1059
1060 menu = root_menu;
1061 name = path_name = vim_strsave(path_name);
1062 if (path_name == NULL)
1063 return FAIL;
1064
1065 /* First, find the (sub)menu with the given name */
1066 while (*name)
1067 {
1068 p = menu_name_skip(name);
1069 while (menu != NULL)
1070 {
1071 if (menu_name_equal(name, menu))
1072 {
1073 /* Found menu */
1074 if (*p != NUL && menu->children == NULL)
1075 {
1076 EMSG(_(e_notsubmenu));
1077 vim_free(path_name);
1078 return FAIL;
1079 }
1080 else if ((menu->modes & modes) == 0x0)
1081 {
1082 EMSG(_(e_othermode));
1083 vim_free(path_name);
1084 return FAIL;
1085 }
1086 break;
1087 }
1088 menu = menu->next;
1089 }
1090 if (menu == NULL)
1091 {
Bram Moolenaar342337a2005-07-21 21:11:17 +00001092 EMSG2(_(e_nomenu), name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001093 vim_free(path_name);
1094 return FAIL;
1095 }
1096 name = p;
1097 parent = menu;
1098 menu = menu->children;
1099 }
Bram Moolenaaracbd4422008-08-17 21:44:45 +00001100 vim_free(path_name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001101
1102 /* Now we have found the matching menu, and we list the mappings */
1103 /* Highlight title */
1104 MSG_PUTS_TITLE(_("\n--- Menus ---"));
1105
1106 show_menus_recursive(parent, modes, 0);
1107 return OK;
1108}
1109
1110/*
1111 * Recursively show the mappings associated with the menus under the given one
1112 */
1113 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001114show_menus_recursive(vimmenu_T *menu, int modes, int depth)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001115{
1116 int i;
1117 int bit;
1118
1119 if (menu != NULL && (menu->modes & modes) == 0x0)
1120 return;
1121
1122 if (menu != NULL)
1123 {
1124 msg_putchar('\n');
1125 if (got_int) /* "q" hit for "--more--" */
1126 return;
1127 for (i = 0; i < depth; i++)
1128 MSG_PUTS(" ");
1129 if (menu->priority)
1130 {
1131 msg_outnum((long)menu->priority);
1132 MSG_PUTS(" ");
1133 }
1134 /* Same highlighting as for directories!? */
1135 msg_outtrans_attr(menu->name, hl_attr(HLF_D));
1136 }
1137
1138 if (menu != NULL && menu->children == NULL)
1139 {
1140 for (bit = 0; bit < MENU_MODES; bit++)
1141 if ((menu->modes & modes & (1 << bit)) != 0)
1142 {
1143 msg_putchar('\n');
1144 if (got_int) /* "q" hit for "--more--" */
1145 return;
1146 for (i = 0; i < depth + 2; i++)
1147 MSG_PUTS(" ");
1148 msg_putchar(menu_mode_chars[bit]);
1149 if (menu->noremap[bit] == REMAP_NONE)
1150 msg_putchar('*');
1151 else if (menu->noremap[bit] == REMAP_SCRIPT)
1152 msg_putchar('&');
1153 else
1154 msg_putchar(' ');
1155 if (menu->silent[bit])
1156 msg_putchar('s');
1157 else
1158 msg_putchar(' ');
1159 if ((menu->modes & menu->enabled & (1 << bit)) == 0)
1160 msg_putchar('-');
1161 else
1162 msg_putchar(' ');
1163 MSG_PUTS(" ");
1164 if (*menu->strings[bit] == NUL)
1165 msg_puts_attr((char_u *)"<Nop>", hl_attr(HLF_8));
1166 else
1167 msg_outtrans_special(menu->strings[bit], FALSE);
1168 }
1169 }
1170 else
1171 {
1172 if (menu == NULL)
1173 {
1174 menu = root_menu;
1175 depth--;
1176 }
1177 else
1178 menu = menu->children;
1179
1180 /* recursively show all children. Skip PopUp[nvoci]. */
1181 for (; menu != NULL && !got_int; menu = menu->next)
1182 if (!menu_is_hidden(menu->dname))
1183 show_menus_recursive(menu, modes, depth + 1);
1184 }
1185}
1186
1187#ifdef FEAT_CMDL_COMPL
1188
1189/*
1190 * Used when expanding menu names.
1191 */
1192static vimmenu_T *expand_menu = NULL;
1193static int expand_modes = 0x0;
1194static int expand_emenu; /* TRUE for ":emenu" command */
1195
1196/*
1197 * Work out what to complete when doing command line completion of menu names.
1198 */
1199 char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001200set_context_in_menu_cmd(
1201 expand_T *xp,
1202 char_u *cmd,
1203 char_u *arg,
1204 int forceit)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001205{
1206 char_u *after_dot;
1207 char_u *p;
1208 char_u *path_name = NULL;
1209 char_u *name;
1210 int unmenu;
1211 vimmenu_T *menu;
1212 int expand_menus;
1213
1214 xp->xp_context = EXPAND_UNSUCCESSFUL;
1215
1216
1217 /* Check for priority numbers, enable and disable */
1218 for (p = arg; *p; ++p)
1219 if (!VIM_ISDIGIT(*p) && *p != '.')
1220 break;
1221
Bram Moolenaar1c465442017-03-12 20:10:05 +01001222 if (!VIM_ISWHITE(*p))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001223 {
1224 if (STRNCMP(arg, "enable", 6) == 0
Bram Moolenaar1c465442017-03-12 20:10:05 +01001225 && (arg[6] == NUL || VIM_ISWHITE(arg[6])))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001226 p = arg + 6;
1227 else if (STRNCMP(arg, "disable", 7) == 0
Bram Moolenaar1c465442017-03-12 20:10:05 +01001228 && (arg[7] == NUL || VIM_ISWHITE(arg[7])))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001229 p = arg + 7;
1230 else
1231 p = arg;
1232 }
1233
Bram Moolenaar1c465442017-03-12 20:10:05 +01001234 while (*p != NUL && VIM_ISWHITE(*p))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001235 ++p;
1236
1237 arg = after_dot = p;
1238
Bram Moolenaar1c465442017-03-12 20:10:05 +01001239 for (; *p && !VIM_ISWHITE(*p); ++p)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001240 {
1241 if ((*p == '\\' || *p == Ctrl_V) && p[1] != NUL)
1242 p++;
1243 else if (*p == '.')
1244 after_dot = p + 1;
1245 }
1246
1247 /* ":tearoff" and ":popup" only use menus, not entries */
1248 expand_menus = !((*cmd == 't' && cmd[1] == 'e') || *cmd == 'p');
1249 expand_emenu = (*cmd == 'e');
Bram Moolenaar1c465442017-03-12 20:10:05 +01001250 if (expand_menus && VIM_ISWHITE(*p))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001251 return NULL; /* TODO: check for next command? */
1252 if (*p == NUL) /* Complete the menu name */
1253 {
1254 /*
1255 * With :unmenu, you only want to match menus for the appropriate mode.
1256 * With :menu though you might want to add a menu with the same name as
1257 * one in another mode, so match menus from other modes too.
1258 */
1259 expand_modes = get_menu_cmd_modes(cmd, forceit, NULL, &unmenu);
1260 if (!unmenu)
1261 expand_modes = MENU_ALL_MODES;
1262
1263 menu = root_menu;
1264 if (after_dot != arg)
1265 {
1266 path_name = alloc((unsigned)(after_dot - arg));
1267 if (path_name == NULL)
1268 return NULL;
Bram Moolenaarce0842a2005-07-18 21:58:11 +00001269 vim_strncpy(path_name, arg, after_dot - arg - 1);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001270 }
1271 name = path_name;
1272 while (name != NULL && *name)
1273 {
1274 p = menu_name_skip(name);
1275 while (menu != NULL)
1276 {
1277 if (menu_name_equal(name, menu))
1278 {
1279 /* Found menu */
1280 if ((*p != NUL && menu->children == NULL)
1281 || ((menu->modes & expand_modes) == 0x0))
1282 {
1283 /*
1284 * Menu path continues, but we have reached a leaf.
1285 * Or menu exists only in another mode.
1286 */
1287 vim_free(path_name);
1288 return NULL;
1289 }
1290 break;
1291 }
1292 menu = menu->next;
1293 }
1294 if (menu == NULL)
1295 {
1296 /* No menu found with the name we were looking for */
1297 vim_free(path_name);
1298 return NULL;
1299 }
1300 name = p;
1301 menu = menu->children;
1302 }
Bram Moolenaareb3593b2006-04-22 22:33:57 +00001303 vim_free(path_name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001304
1305 xp->xp_context = expand_menus ? EXPAND_MENUNAMES : EXPAND_MENUS;
1306 xp->xp_pattern = after_dot;
1307 expand_menu = menu;
1308 }
1309 else /* We're in the mapping part */
1310 xp->xp_context = EXPAND_NOTHING;
1311 return NULL;
1312}
1313
1314/*
1315 * Function given to ExpandGeneric() to obtain the list of (sub)menus (not
1316 * entries).
1317 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001318 char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001319get_menu_name(expand_T *xp UNUSED, int idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001320{
1321 static vimmenu_T *menu = NULL;
1322 char_u *str;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001323#ifdef FEAT_MULTI_LANG
1324 static int should_advance = FALSE;
1325#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001326
1327 if (idx == 0) /* first call: start at first item */
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001328 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001329 menu = expand_menu;
Bram Moolenaar41375642010-05-16 12:49:27 +02001330#ifdef FEAT_MULTI_LANG
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001331 should_advance = FALSE;
Bram Moolenaar41375642010-05-16 12:49:27 +02001332#endif
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001333 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001334
1335 /* Skip PopUp[nvoci]. */
1336 while (menu != NULL && (menu_is_hidden(menu->dname)
1337 || menu_is_separator(menu->dname)
1338 || menu_is_tearoff(menu->dname)
1339 || menu->children == NULL))
1340 menu = menu->next;
1341
1342 if (menu == NULL) /* at end of linked list */
1343 return NULL;
1344
1345 if (menu->modes & expand_modes)
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001346#ifdef FEAT_MULTI_LANG
1347 if (should_advance)
1348 str = menu->en_dname;
1349 else
1350 {
1351#endif
1352 str = menu->dname;
1353#ifdef FEAT_MULTI_LANG
1354 if (menu->en_dname == NULL)
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001355 should_advance = TRUE;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001356 }
1357#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001358 else
1359 str = (char_u *)"";
1360
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001361#ifdef FEAT_MULTI_LANG
1362 if (should_advance)
1363#endif
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001364 /* Advance to next menu entry. */
1365 menu = menu->next;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001366
1367#ifdef FEAT_MULTI_LANG
1368 should_advance = !should_advance;
1369#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001370
1371 return str;
1372}
1373
1374/*
1375 * Function given to ExpandGeneric() to obtain the list of menus and menu
1376 * entries.
1377 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001378 char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001379get_menu_names(expand_T *xp UNUSED, int idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001380{
1381 static vimmenu_T *menu = NULL;
Bram Moolenaaref9d6aa2011-04-11 16:56:35 +02001382#define TBUFFER_LEN 256
1383 static char_u tbuffer[TBUFFER_LEN]; /*hack*/
Bram Moolenaar071d4272004-06-13 20:20:40 +00001384 char_u *str;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001385#ifdef FEAT_MULTI_LANG
1386 static int should_advance = FALSE;
1387#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001388
1389 if (idx == 0) /* first call: start at first item */
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001390 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001391 menu = expand_menu;
Bram Moolenaar41375642010-05-16 12:49:27 +02001392#ifdef FEAT_MULTI_LANG
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001393 should_advance = FALSE;
Bram Moolenaar41375642010-05-16 12:49:27 +02001394#endif
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001395 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001396
1397 /* Skip Browse-style entries, popup menus and separators. */
1398 while (menu != NULL
1399 && ( menu_is_hidden(menu->dname)
1400 || (expand_emenu && menu_is_separator(menu->dname))
1401 || menu_is_tearoff(menu->dname)
1402#ifndef FEAT_BROWSE
1403 || menu->dname[STRLEN(menu->dname) - 1] == '.'
1404#endif
1405 ))
1406 menu = menu->next;
1407
1408 if (menu == NULL) /* at end of linked list */
1409 return NULL;
1410
1411 if (menu->modes & expand_modes)
1412 {
1413 if (menu->children != NULL)
1414 {
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001415#ifdef FEAT_MULTI_LANG
1416 if (should_advance)
Bram Moolenaaref9d6aa2011-04-11 16:56:35 +02001417 vim_strncpy(tbuffer, menu->en_dname, TBUFFER_LEN - 2);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001418 else
1419 {
1420#endif
Bram Moolenaaref9d6aa2011-04-11 16:56:35 +02001421 vim_strncpy(tbuffer, menu->dname, TBUFFER_LEN - 2);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001422#ifdef FEAT_MULTI_LANG
1423 if (menu->en_dname == NULL)
1424 should_advance = TRUE;
1425 }
1426#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001427 /* hack on menu separators: use a 'magic' char for the separator
1428 * so that '.' in names gets escaped properly */
1429 STRCAT(tbuffer, "\001");
1430 str = tbuffer;
1431 }
1432 else
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001433#ifdef FEAT_MULTI_LANG
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001434 {
1435 if (should_advance)
1436 str = menu->en_dname;
1437 else
1438 {
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001439#endif
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001440 str = menu->dname;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001441#ifdef FEAT_MULTI_LANG
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001442 if (menu->en_dname == NULL)
1443 should_advance = TRUE;
1444 }
1445 }
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001446#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001447 }
1448 else
1449 str = (char_u *)"";
1450
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001451#ifdef FEAT_MULTI_LANG
1452 if (should_advance)
1453#endif
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001454 /* Advance to next menu entry. */
1455 menu = menu->next;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001456
1457#ifdef FEAT_MULTI_LANG
1458 should_advance = !should_advance;
1459#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001460
1461 return str;
1462}
1463#endif /* FEAT_CMDL_COMPL */
1464
1465/*
1466 * Skip over this element of the menu path and return the start of the next
1467 * element. Any \ and ^Vs are removed from the current element.
Bram Moolenaar342337a2005-07-21 21:11:17 +00001468 * "name" may be modified.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001469 */
1470 char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001471menu_name_skip(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001472{
1473 char_u *p;
1474
Bram Moolenaar91acfff2017-03-12 19:22:36 +01001475 for (p = name; *p && *p != '.'; MB_PTR_ADV(p))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001476 {
1477 if (*p == '\\' || *p == Ctrl_V)
1478 {
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001479 STRMOVE(p, p + 1);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001480 if (*p == NUL)
1481 break;
1482 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001483 }
1484 if (*p)
1485 *p++ = NUL;
1486 return p;
1487}
1488
1489/*
1490 * Return TRUE when "name" matches with menu "menu". The name is compared in
1491 * two ways: raw menu name and menu name without '&'. ignore part after a TAB.
1492 */
1493 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001494menu_name_equal(char_u *name, vimmenu_T *menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001495{
Bram Moolenaar41375642010-05-16 12:49:27 +02001496#ifdef FEAT_MULTI_LANG
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001497 if (menu->en_name != NULL
Bram Moolenaard91f7042011-01-04 17:49:32 +01001498 && (menu_namecmp(name, menu->en_name)
1499 || menu_namecmp(name, menu->en_dname)))
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001500 return TRUE;
Bram Moolenaar41375642010-05-16 12:49:27 +02001501#endif
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001502 return menu_namecmp(name, menu->name) || menu_namecmp(name, menu->dname);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001503}
1504
1505 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001506menu_namecmp(char_u *name, char_u *mname)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001507{
1508 int i;
1509
1510 for (i = 0; name[i] != NUL && name[i] != TAB; ++i)
1511 if (name[i] != mname[i])
1512 break;
1513 return ((name[i] == NUL || name[i] == TAB)
1514 && (mname[i] == NUL || mname[i] == TAB));
1515}
1516
1517/*
1518 * Return the modes specified by the given menu command (eg :menu! returns
1519 * MENU_CMDLINE_MODE | MENU_INSERT_MODE).
1520 * If "noremap" is not NULL, then the flag it points to is set according to
1521 * whether the command is a "nore" command.
1522 * If "unmenu" is not NULL, then the flag it points to is set according to
1523 * whether the command is an "unmenu" command.
1524 */
1525 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001526get_menu_cmd_modes(
1527 char_u *cmd,
1528 int forceit, /* Was there a "!" after the command? */
1529 int *noremap,
1530 int *unmenu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001531{
1532 int modes;
1533
1534 switch (*cmd++)
1535 {
1536 case 'v': /* vmenu, vunmenu, vnoremenu */
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001537 modes = MENU_VISUAL_MODE | MENU_SELECT_MODE;
1538 break;
1539 case 'x': /* xmenu, xunmenu, xnoremenu */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001540 modes = MENU_VISUAL_MODE;
1541 break;
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001542 case 's': /* smenu, sunmenu, snoremenu */
1543 modes = MENU_SELECT_MODE;
1544 break;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001545 case 'o': /* omenu */
1546 modes = MENU_OP_PENDING_MODE;
1547 break;
1548 case 'i': /* imenu */
1549 modes = MENU_INSERT_MODE;
1550 break;
1551 case 't':
1552 modes = MENU_TIP_MODE; /* tmenu */
1553 break;
1554 case 'c': /* cmenu */
1555 modes = MENU_CMDLINE_MODE;
1556 break;
1557 case 'a': /* amenu */
1558 modes = MENU_INSERT_MODE | MENU_CMDLINE_MODE | MENU_NORMAL_MODE
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001559 | MENU_VISUAL_MODE | MENU_SELECT_MODE
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00001560 | MENU_OP_PENDING_MODE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001561 break;
1562 case 'n':
1563 if (*cmd != 'o') /* nmenu, not noremenu */
1564 {
1565 modes = MENU_NORMAL_MODE;
1566 break;
1567 }
1568 /* FALLTHROUGH */
1569 default:
1570 --cmd;
1571 if (forceit) /* menu!! */
1572 modes = MENU_INSERT_MODE | MENU_CMDLINE_MODE;
1573 else /* menu */
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001574 modes = MENU_NORMAL_MODE | MENU_VISUAL_MODE | MENU_SELECT_MODE
Bram Moolenaar071d4272004-06-13 20:20:40 +00001575 | MENU_OP_PENDING_MODE;
1576 }
1577
1578 if (noremap != NULL)
1579 *noremap = (*cmd == 'n' ? REMAP_NONE : REMAP_YES);
1580 if (unmenu != NULL)
1581 *unmenu = (*cmd == 'u');
1582 return modes;
1583}
1584
1585/*
1586 * Modify a menu name starting with "PopUp" to include the mode character.
1587 * Returns the name in allocated memory (NULL for failure).
1588 */
1589 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001590popup_mode_name(char_u *name, int idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001591{
1592 char_u *p;
1593 int len = (int)STRLEN(name);
1594
1595 p = vim_strnsave(name, len + 1);
1596 if (p != NULL)
1597 {
1598 mch_memmove(p + 6, p + 5, (size_t)(len - 4));
1599 p[5] = menu_mode_chars[idx];
1600 }
1601 return p;
1602}
1603
1604#if defined(FEAT_GUI) || defined(PROTO)
1605/*
1606 * Return the index into the menu->strings or menu->noremap arrays for the
1607 * current state. Returns MENU_INDEX_INVALID if there is no mapping for the
1608 * given menu in the current mode.
1609 */
1610 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001611get_menu_index(vimmenu_T *menu, int state)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001612{
1613 int idx;
1614
1615 if ((state & INSERT))
1616 idx = MENU_INDEX_INSERT;
1617 else if (state & CMDLINE)
1618 idx = MENU_INDEX_CMDLINE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001619 else if (VIsual_active)
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001620 {
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00001621 if (VIsual_select)
1622 idx = MENU_INDEX_SELECT;
1623 else
1624 idx = MENU_INDEX_VISUAL;
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001625 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001626 else if (state == HITRETURN || state == ASKMORE)
1627 idx = MENU_INDEX_CMDLINE;
1628 else if (finish_op)
1629 idx = MENU_INDEX_OP_PENDING;
1630 else if ((state & NORMAL))
1631 idx = MENU_INDEX_NORMAL;
1632 else
1633 idx = MENU_INDEX_INVALID;
1634
1635 if (idx != MENU_INDEX_INVALID && menu->strings[idx] == NULL)
1636 idx = MENU_INDEX_INVALID;
1637 return idx;
1638}
1639#endif
1640
1641/*
1642 * Duplicate the menu item text and then process to see if a mnemonic key
1643 * and/or accelerator text has been identified.
1644 * Returns a pointer to allocated memory, or NULL for failure.
1645 * If mnemonic != NULL, *mnemonic is set to the character after the first '&'.
1646 * If actext != NULL, *actext is set to the text after the first TAB.
1647 */
1648 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001649menu_text(char_u *str, int *mnemonic, char_u **actext)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001650{
1651 char_u *p;
1652 char_u *text;
1653
1654 /* Locate accelerator text, after the first TAB */
1655 p = vim_strchr(str, TAB);
1656 if (p != NULL)
1657 {
1658 if (actext != NULL)
1659 *actext = vim_strsave(p + 1);
1660 text = vim_strnsave(str, (int)(p - str));
1661 }
1662 else
1663 text = vim_strsave(str);
1664
1665 /* Find mnemonic characters "&a" and reduce "&&" to "&". */
1666 for (p = text; p != NULL; )
1667 {
1668 p = vim_strchr(p, '&');
1669 if (p != NULL)
1670 {
1671 if (p[1] == NUL) /* trailing "&" */
1672 break;
1673 if (mnemonic != NULL && p[1] != '&')
1674#if !defined(__MVS__) || defined(MOTIF390_MNEMONIC_FIXED)
1675 *mnemonic = p[1];
1676#else
1677 {
1678 /*
1679 * Well there is a bug in the Motif libraries on OS390 Unix.
1680 * The mnemonic keys needs to be converted to ASCII values
1681 * first.
1682 * This behavior has been seen in 2.8 and 2.9.
1683 */
1684 char c = p[1];
1685 __etoa_l(&c, 1);
1686 *mnemonic = c;
1687 }
1688#endif
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001689 STRMOVE(p, p + 1);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001690 p = p + 1;
1691 }
1692 }
1693 return text;
1694}
1695
1696/*
1697 * Return TRUE if "name" can be a menu in the MenuBar.
1698 */
1699 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001700menu_is_menubar(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001701{
1702 return (!menu_is_popup(name)
1703 && !menu_is_toolbar(name)
1704 && *name != MNU_HIDDEN_CHAR);
1705}
1706
1707/*
1708 * Return TRUE if "name" is a popup menu name.
1709 */
1710 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001711menu_is_popup(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001712{
1713 return (STRNCMP(name, "PopUp", 5) == 0);
1714}
1715
1716#if (defined(FEAT_GUI_MOTIF) && (XmVersion <= 1002)) || defined(PROTO)
1717/*
1718 * Return TRUE if "name" is part of a popup menu.
1719 */
1720 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001721menu_is_child_of_popup(vimmenu_T *menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001722{
1723 while (menu->parent != NULL)
1724 menu = menu->parent;
1725 return menu_is_popup(menu->name);
1726}
1727#endif
1728
1729/*
1730 * Return TRUE if "name" is a toolbar menu name.
1731 */
1732 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001733menu_is_toolbar(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001734{
1735 return (STRNCMP(name, "ToolBar", 7) == 0);
1736}
1737
1738/*
1739 * Return TRUE if the name is a menu separator identifier: Starts and ends
1740 * with '-'
1741 */
1742 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001743menu_is_separator(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001744{
1745 return (name[0] == '-' && name[STRLEN(name) - 1] == '-');
1746}
1747
1748/*
1749 * Return TRUE if the menu is hidden: Starts with ']'
1750 */
1751 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001752menu_is_hidden(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001753{
1754 return (name[0] == ']') || (menu_is_popup(name) && name[5] != NUL);
1755}
1756
1757#if defined(FEAT_CMDL_COMPL) \
1758 || (defined(FEAT_GUI_W32) && defined(FEAT_TEAROFF))
1759/*
1760 * Return TRUE if the menu is the tearoff menu.
1761 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001762 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001763menu_is_tearoff(char_u *name UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001764{
1765#ifdef FEAT_GUI
1766 return (STRCMP(name, TEAR_STRING) == 0);
1767#else
1768 return FALSE;
1769#endif
1770}
1771#endif
1772
1773#ifdef FEAT_GUI
1774
1775 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001776get_menu_mode(void)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001777{
Bram Moolenaar071d4272004-06-13 20:20:40 +00001778 if (VIsual_active)
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001779 {
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00001780 if (VIsual_select)
1781 return MENU_INDEX_SELECT;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001782 return MENU_INDEX_VISUAL;
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001783 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001784 if (State & INSERT)
1785 return MENU_INDEX_INSERT;
1786 if ((State & CMDLINE) || State == ASKMORE || State == HITRETURN)
1787 return MENU_INDEX_CMDLINE;
1788 if (finish_op)
1789 return MENU_INDEX_OP_PENDING;
1790 if (State & NORMAL)
1791 return MENU_INDEX_NORMAL;
1792 if (State & LANGMAP) /* must be a "r" command, like Insert mode */
1793 return MENU_INDEX_INSERT;
1794 return MENU_INDEX_INVALID;
1795}
1796
1797/*
Bram Moolenaar968bbbe2006-08-16 19:41:08 +00001798 * Check that a pointer appears in the menu tree. Used to protect from using
1799 * a menu that was deleted after it was selected but before the event was
1800 * handled.
1801 * Return OK or FAIL. Used recursively.
1802 */
1803 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001804check_menu_pointer(vimmenu_T *root, vimmenu_T *menu_to_check)
Bram Moolenaar968bbbe2006-08-16 19:41:08 +00001805{
1806 vimmenu_T *p;
1807
1808 for (p = root; p != NULL; p = p->next)
1809 if (p == menu_to_check
1810 || (p->children != NULL
1811 && check_menu_pointer(p->children, menu_to_check) == OK))
1812 return OK;
1813 return FAIL;
1814}
1815
1816/*
Bram Moolenaar071d4272004-06-13 20:20:40 +00001817 * After we have started the GUI, then we can create any menus that have been
1818 * defined. This is done once here. add_menu_path() may have already been
1819 * called to define these menus, and may be called again. This function calls
1820 * itself recursively. Should be called at the top level with:
Bram Moolenaara06ecab2016-07-16 14:47:36 +02001821 * gui_create_initial_menus(root_menu);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001822 */
1823 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001824gui_create_initial_menus(vimmenu_T *menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001825{
1826 int idx = 0;
1827
1828 while (menu != NULL)
1829 {
1830 /* Don't add a menu when only a tip was defined. */
1831 if (menu->modes & MENU_ALL_MODES)
1832 {
1833 if (menu->children != NULL)
1834 {
1835 gui_mch_add_menu(menu, idx);
1836 gui_create_initial_menus(menu->children);
1837 }
1838 else
1839 gui_mch_add_menu_item(menu, idx);
1840 }
1841 menu = menu->next;
1842 ++idx;
1843 }
1844}
1845
1846/*
1847 * Used recursively by gui_update_menus (see below)
1848 */
1849 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001850gui_update_menus_recurse(vimmenu_T *menu, int mode)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001851{
1852 int grey;
1853
1854 while (menu)
1855 {
1856 if ((menu->modes & menu->enabled & mode)
1857#if defined(FEAT_GUI_W32) && defined(FEAT_TEAROFF)
1858 || menu_is_tearoff(menu->dname)
1859#endif
1860 )
1861 grey = FALSE;
1862 else
1863 grey = TRUE;
1864#ifdef FEAT_GUI_ATHENA
1865 /* Hiding menus doesn't work for Athena, it can cause a crash. */
1866 gui_mch_menu_grey(menu, grey);
1867#else
1868 /* Never hide a toplevel menu, it may make the menubar resize or
1869 * disappear. Same problem for ToolBar items. */
1870 if (vim_strchr(p_go, GO_GREY) != NULL || menu->parent == NULL
1871# ifdef FEAT_TOOLBAR
1872 || menu_is_toolbar(menu->parent->name)
1873# endif
1874 )
1875 gui_mch_menu_grey(menu, grey);
1876 else
1877 gui_mch_menu_hidden(menu, grey);
1878#endif
1879 gui_update_menus_recurse(menu->children, mode);
1880 menu = menu->next;
1881 }
1882}
1883
1884/*
1885 * Make sure only the valid menu items appear for this mode. If
1886 * force_menu_update is not TRUE, then we only do this if the mode has changed
1887 * since last time. If "modes" is not 0, then we use these modes instead.
1888 */
1889 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001890gui_update_menus(int modes)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001891{
1892 static int prev_mode = -1;
1893 int mode = 0;
1894
1895 if (modes != 0x0)
1896 mode = modes;
1897 else
1898 {
1899 mode = get_menu_mode();
1900 if (mode == MENU_INDEX_INVALID)
1901 mode = 0;
1902 else
1903 mode = (1 << mode);
1904 }
1905
1906 if (force_menu_update || mode != prev_mode)
1907 {
1908 gui_update_menus_recurse(root_menu, mode);
1909 gui_mch_draw_menubar();
1910 prev_mode = mode;
1911 force_menu_update = FALSE;
1912#ifdef FEAT_GUI_W32
1913 /* This can leave a tearoff as active window - make sure we
1914 * have the focus <negri>*/
1915 gui_mch_activate_window();
1916#endif
1917 }
1918}
1919
Bram Moolenaar241a8aa2005-12-06 20:04:44 +00001920#if defined(FEAT_GUI_MSWIN) || defined(FEAT_GUI_MOTIF) \
1921 || defined(FEAT_GUI_GTK) || defined(FEAT_GUI_PHOTON) || defined(PROTO)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001922/*
1923 * Check if a key is used as a mnemonic for a toplevel menu.
1924 * Case of the key is ignored.
1925 */
1926 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001927gui_is_menu_shortcut(int key)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001928{
1929 vimmenu_T *menu;
1930
1931 if (key < 256)
1932 key = TOLOWER_LOC(key);
1933 for (menu = root_menu; menu != NULL; menu = menu->next)
1934 if (menu->mnemonic == key
1935 || (menu->mnemonic < 256 && TOLOWER_LOC(menu->mnemonic) == key))
1936 return TRUE;
1937 return FALSE;
1938}
1939#endif
1940
1941/*
1942 * Display the Special "PopUp" menu as a pop-up at the current mouse
1943 * position. The "PopUpn" menu is for Normal mode, "PopUpi" for Insert mode,
1944 * etc.
1945 */
1946 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001947gui_show_popupmenu(void)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001948{
1949 vimmenu_T *menu;
1950 int mode;
1951
1952 mode = get_menu_mode();
1953 if (mode == MENU_INDEX_INVALID)
1954 return;
1955 mode = menu_mode_chars[mode];
1956
Bram Moolenaar342337a2005-07-21 21:11:17 +00001957#ifdef FEAT_AUTOCMD
1958 {
1959 char_u ename[2];
1960
1961 ename[0] = mode;
1962 ename[1] = NUL;
1963 apply_autocmds(EVENT_MENUPOPUP, ename, NULL, FALSE, curbuf);
1964 }
1965#endif
1966
Bram Moolenaar071d4272004-06-13 20:20:40 +00001967 for (menu = root_menu; menu != NULL; menu = menu->next)
1968 if (STRNCMP("PopUp", menu->name, 5) == 0 && menu->name[5] == mode)
1969 break;
1970
1971 /* Only show a popup when it is defined and has entries */
1972 if (menu != NULL && menu->children != NULL)
Bram Moolenaar2a67ed82016-06-10 21:52:42 +02001973 {
1974 /* Update the menus now, in case the MenuPopup autocommand did
1975 * anything. */
1976 gui_update_menus(0);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001977 gui_mch_show_popupmenu(menu);
Bram Moolenaar2a67ed82016-06-10 21:52:42 +02001978 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001979}
1980#endif /* FEAT_GUI */
1981
1982#if (defined(FEAT_GUI_W32) && defined(FEAT_TEAROFF)) || defined(PROTO)
1983
1984/*
1985 * Deal with tearoff items that are added like a menu item.
1986 * Currently only for Win32 GUI. Others may follow later.
1987 */
1988
1989 void
1990gui_mch_toggle_tearoffs(int enable)
1991{
1992 int pri_tab[MENUDEPTH + 1];
1993 int i;
1994
1995 if (enable)
1996 {
1997 for (i = 0; i < MENUDEPTH; ++i)
1998 pri_tab[i] = 500;
1999 pri_tab[MENUDEPTH] = -1;
2000 gui_create_tearoffs_recurse(root_menu, (char_u *)"", pri_tab, 0);
2001 }
2002 else
2003 gui_destroy_tearoffs_recurse(root_menu);
2004 s_tearoffs = enable;
2005}
2006
2007/*
2008 * Recursively add tearoff items
2009 */
2010 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002011gui_create_tearoffs_recurse(
2012 vimmenu_T *menu,
2013 const char_u *pname,
2014 int *pri_tab,
2015 int pri_idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002016{
2017 char_u *newpname = NULL;
2018 int len;
2019 char_u *s;
2020 char_u *d;
2021
2022 if (pri_tab[pri_idx + 1] != -1)
2023 ++pri_idx;
2024 while (menu != NULL)
2025 {
2026 if (menu->children != NULL && menu_is_menubar(menu->name))
2027 {
2028 /* Add the menu name to the menu path. Insert a backslash before
2029 * dots (it's used to separate menu names). */
2030 len = (int)STRLEN(pname) + (int)STRLEN(menu->name);
2031 for (s = menu->name; *s; ++s)
2032 if (*s == '.' || *s == '\\')
2033 ++len;
2034 newpname = alloc(len + TEAR_LEN + 2);
2035 if (newpname != NULL)
2036 {
2037 STRCPY(newpname, pname);
2038 d = newpname + STRLEN(newpname);
2039 for (s = menu->name; *s; ++s)
2040 {
2041 if (*s == '.' || *s == '\\')
2042 *d++ = '\\';
2043 *d++ = *s;
2044 }
2045 *d = NUL;
2046
2047 /* check if tearoff already exists */
2048 if (STRCMP(menu->children->name, TEAR_STRING) != 0)
2049 {
2050 gui_add_tearoff(newpname, pri_tab, pri_idx - 1);
2051 *d = NUL; /* remove TEAR_STRING */
2052 }
2053
2054 STRCAT(newpname, ".");
2055 gui_create_tearoffs_recurse(menu->children, newpname,
2056 pri_tab, pri_idx);
2057 vim_free(newpname);
2058 }
2059 }
2060 menu = menu->next;
2061 }
2062}
2063
2064/*
2065 * Add tear-off menu item for a submenu.
2066 * "tearpath" is the menu path, and must have room to add TEAR_STRING.
2067 */
2068 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002069gui_add_tearoff(char_u *tearpath, int *pri_tab, int pri_idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002070{
2071 char_u *tbuf;
2072 int t;
2073 vimmenu_T menuarg;
2074
2075 tbuf = alloc(5 + (unsigned int)STRLEN(tearpath));
2076 if (tbuf != NULL)
2077 {
2078 tbuf[0] = K_SPECIAL;
2079 tbuf[1] = K_SECOND(K_TEAROFF);
2080 tbuf[2] = K_THIRD(K_TEAROFF);
2081 STRCPY(tbuf + 3, tearpath);
2082 STRCAT(tbuf + 3, "\r");
2083
2084 STRCAT(tearpath, ".");
2085 STRCAT(tearpath, TEAR_STRING);
2086
2087 /* Priority of tear-off is always 1 */
2088 t = pri_tab[pri_idx + 1];
2089 pri_tab[pri_idx + 1] = 1;
2090
2091#ifdef FEAT_TOOLBAR
2092 menuarg.iconfile = NULL;
2093 menuarg.iconidx = -1;
2094 menuarg.icon_builtin = FALSE;
2095#endif
2096 menuarg.noremap[0] = REMAP_NONE;
2097 menuarg.silent[0] = TRUE;
2098
2099 menuarg.modes = MENU_ALL_MODES;
2100 add_menu_path(tearpath, &menuarg, pri_tab, tbuf, FALSE);
2101
2102 menuarg.modes = MENU_TIP_MODE;
2103 add_menu_path(tearpath, &menuarg, pri_tab,
2104 (char_u *)_("Tear off this menu"), FALSE);
2105
2106 pri_tab[pri_idx + 1] = t;
2107 vim_free(tbuf);
2108 }
2109}
2110
2111/*
2112 * Recursively destroy tearoff items
2113 */
2114 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002115gui_destroy_tearoffs_recurse(vimmenu_T *menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002116{
2117 while (menu)
2118 {
2119 if (menu->children)
2120 {
2121 /* check if tearoff exists */
2122 if (STRCMP(menu->children->name, TEAR_STRING) == 0)
2123 {
2124 /* Disconnect the item and free the memory */
2125 free_menu(&menu->children);
2126 }
2127 if (menu->children != NULL) /* if not the last one */
2128 gui_destroy_tearoffs_recurse(menu->children);
2129 }
2130 menu = menu->next;
2131 }
2132}
2133
2134#endif /* FEAT_GUI_W32 && FEAT_TEAROFF */
2135
2136/*
2137 * Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy and
2138 * execute it.
2139 */
2140 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002141ex_emenu(exarg_T *eap)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002142{
2143 vimmenu_T *menu;
2144 char_u *name;
2145 char_u *saved_name;
2146 char_u *p;
2147 int idx;
2148 char_u *mode;
2149
2150 saved_name = vim_strsave(eap->arg);
2151 if (saved_name == NULL)
2152 return;
2153
2154 menu = root_menu;
2155 name = saved_name;
2156 while (*name)
2157 {
2158 /* Find in the menu hierarchy */
2159 p = menu_name_skip(name);
2160
2161 while (menu != NULL)
2162 {
2163 if (menu_name_equal(name, menu))
2164 {
2165 if (*p == NUL && menu->children != NULL)
2166 {
2167 EMSG(_("E333: Menu path must lead to a menu item"));
2168 menu = NULL;
2169 }
2170 else if (*p != NUL && menu->children == NULL)
2171 {
2172 EMSG(_(e_notsubmenu));
2173 menu = NULL;
2174 }
2175 break;
2176 }
2177 menu = menu->next;
2178 }
2179 if (menu == NULL || *p == NUL)
2180 break;
2181 menu = menu->children;
2182 name = p;
2183 }
2184 vim_free(saved_name);
2185 if (menu == NULL)
2186 {
2187 EMSG2(_("E334: Menu not found: %s"), eap->arg);
2188 return;
2189 }
2190
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002191 /* Found the menu, so execute.
2192 * Use the Insert mode entry when returning to Insert mode. */
Bram Moolenaar4463f292005-09-25 22:20:24 +00002193 if (restart_edit
2194#ifdef FEAT_EVAL
2195 && !current_SID
2196#endif
2197 )
Bram Moolenaar071d4272004-06-13 20:20:40 +00002198 {
2199 mode = (char_u *)"Insert";
2200 idx = MENU_INDEX_INSERT;
2201 }
2202 else if (eap->addr_count)
2203 {
2204 pos_T tpos;
2205
2206 mode = (char_u *)"Visual";
2207 idx = MENU_INDEX_VISUAL;
2208
2209 /* GEDDES: This is not perfect - but it is a
2210 * quick way of detecting whether we are doing this from a
2211 * selection - see if the range matches up with the visual
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002212 * select start and end. */
Bram Moolenaareddf53b2006-02-27 00:11:10 +00002213 if ((curbuf->b_visual.vi_start.lnum == eap->line1)
2214 && (curbuf->b_visual.vi_end.lnum) == eap->line2)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002215 {
2216 /* Set it up for visual mode - equivalent to gv. */
Bram Moolenaareddf53b2006-02-27 00:11:10 +00002217 VIsual_mode = curbuf->b_visual.vi_mode;
2218 tpos = curbuf->b_visual.vi_end;
2219 curwin->w_cursor = curbuf->b_visual.vi_start;
2220 curwin->w_curswant = curbuf->b_visual.vi_curswant;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002221 }
2222 else
2223 {
2224 /* Set it up for line-wise visual mode */
2225 VIsual_mode = 'V';
2226 curwin->w_cursor.lnum = eap->line1;
2227 curwin->w_cursor.col = 1;
2228 tpos.lnum = eap->line2;
2229 tpos.col = MAXCOL;
Bram Moolenaar261bfea2006-03-01 22:12:31 +00002230#ifdef FEAT_VIRTUALEDIT
2231 tpos.coladd = 0;
2232#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002233 }
2234
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002235 /* Activate visual mode */
Bram Moolenaar071d4272004-06-13 20:20:40 +00002236 VIsual_active = TRUE;
2237 VIsual_reselect = TRUE;
2238 check_cursor();
2239 VIsual = curwin->w_cursor;
2240 curwin->w_cursor = tpos;
2241
2242 check_cursor();
2243
2244 /* Adjust the cursor to make sure it is in the correct pos
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002245 * for exclusive mode */
Bram Moolenaar071d4272004-06-13 20:20:40 +00002246 if (*p_sel == 'e' && gchar_cursor() != NUL)
2247 ++curwin->w_cursor.col;
2248 }
2249 else
2250 {
2251 mode = (char_u *)"Normal";
2252 idx = MENU_INDEX_NORMAL;
2253 }
2254
2255 if (idx != MENU_INDEX_INVALID && menu->strings[idx] != NULL)
2256 {
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002257 /* When executing a script or function execute the commands right now.
2258 * Otherwise put them in the typeahead buffer. */
Bram Moolenaar9c4b4ab2006-12-05 20:29:56 +00002259#ifdef FEAT_EVAL
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002260 if (current_SID != 0)
2261 exec_normal_cmd(menu->strings[idx], menu->noremap[idx],
2262 menu->silent[idx]);
2263 else
Bram Moolenaar4463f292005-09-25 22:20:24 +00002264#endif
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002265 ins_typebuf(menu->strings[idx], menu->noremap[idx], 0,
Bram Moolenaar071d4272004-06-13 20:20:40 +00002266 TRUE, menu->silent[idx]);
2267 }
2268 else
2269 EMSG2(_("E335: Menu not defined for %s mode"), mode);
2270}
2271
2272#if defined(FEAT_GUI_MSWIN) \
Bram Moolenaar071d4272004-06-13 20:20:40 +00002273 || (defined(FEAT_GUI_GTK) && defined(FEAT_MENU)) \
2274 || defined(FEAT_BEVAL_TIP) || defined(PROTO)
2275/*
2276 * Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy.
2277 */
2278 vimmenu_T *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002279gui_find_menu(char_u *path_name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002280{
2281 vimmenu_T *menu = NULL;
2282 char_u *name;
2283 char_u *saved_name;
2284 char_u *p;
2285
2286 menu = root_menu;
2287
2288 saved_name = vim_strsave(path_name);
2289 if (saved_name == NULL)
2290 return NULL;
2291
2292 name = saved_name;
2293 while (*name)
2294 {
2295 /* find the end of one dot-separated name and put a NUL at the dot */
2296 p = menu_name_skip(name);
2297
2298 while (menu != NULL)
2299 {
Bram Moolenaard91f7042011-01-04 17:49:32 +01002300 if (menu_name_equal(name, menu))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002301 {
2302 if (menu->children == NULL)
2303 {
2304 /* found a menu item instead of a sub-menu */
2305 if (*p == NUL)
2306 EMSG(_("E336: Menu path must lead to a sub-menu"));
2307 else
2308 EMSG(_(e_notsubmenu));
2309 menu = NULL;
2310 goto theend;
2311 }
2312 if (*p == NUL) /* found a full match */
2313 goto theend;
2314 break;
2315 }
2316 menu = menu->next;
2317 }
2318 if (menu == NULL) /* didn't find it */
2319 break;
2320
2321 /* Found a match, search the sub-menu. */
2322 menu = menu->children;
2323 name = p;
2324 }
2325
2326 if (menu == NULL)
2327 EMSG(_("E337: Menu not found - check menu names"));
2328theend:
2329 vim_free(saved_name);
2330 return menu;
2331}
2332#endif
2333
2334#ifdef FEAT_MULTI_LANG
2335/*
2336 * Translation of menu names. Just a simple lookup table.
2337 */
2338
2339typedef struct
2340{
2341 char_u *from; /* English name */
2342 char_u *from_noamp; /* same, without '&' */
2343 char_u *to; /* translated name */
2344} menutrans_T;
2345
2346static garray_T menutrans_ga = {0, 0, 0, 0, NULL};
2347#endif
2348
2349/*
2350 * ":menutrans".
2351 * This function is also defined without the +multi_lang feature, in which
2352 * case the commands are ignored.
2353 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00002354 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002355ex_menutranslate(exarg_T *eap UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002356{
2357#ifdef FEAT_MULTI_LANG
2358 char_u *arg = eap->arg;
2359 menutrans_T *tp;
2360 int i;
2361 char_u *from, *from_noamp, *to;
2362
2363 if (menutrans_ga.ga_itemsize == 0)
2364 ga_init2(&menutrans_ga, (int)sizeof(menutrans_T), 5);
2365
2366 /*
2367 * ":menutrans clear": clear all translations.
2368 */
2369 if (STRNCMP(arg, "clear", 5) == 0 && ends_excmd(*skipwhite(arg + 5)))
2370 {
2371 tp = (menutrans_T *)menutrans_ga.ga_data;
2372 for (i = 0; i < menutrans_ga.ga_len; ++i)
2373 {
2374 vim_free(tp[i].from);
2375 vim_free(tp[i].from_noamp);
2376 vim_free(tp[i].to);
2377 }
2378 ga_clear(&menutrans_ga);
2379# ifdef FEAT_EVAL
2380 /* Delete all "menutrans_" global variables. */
2381 del_menutrans_vars();
2382# endif
2383 }
2384 else
2385 {
2386 /* ":menutrans from to": add translation */
2387 from = arg;
2388 arg = menu_skip_part(arg);
2389 to = skipwhite(arg);
2390 *arg = NUL;
2391 arg = menu_skip_part(to);
2392 if (arg == to)
2393 EMSG(_(e_invarg));
2394 else
2395 {
2396 if (ga_grow(&menutrans_ga, 1) == OK)
2397 {
2398 tp = (menutrans_T *)menutrans_ga.ga_data;
2399 from = vim_strsave(from);
Bram Moolenaarfc1421e2006-04-20 22:17:20 +00002400 if (from != NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002401 {
Bram Moolenaarfc1421e2006-04-20 22:17:20 +00002402 from_noamp = menu_text(from, NULL, NULL);
2403 to = vim_strnsave(to, (int)(arg - to));
2404 if (from_noamp != NULL && to != NULL)
2405 {
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002406 menu_translate_tab_and_shift(from);
2407 menu_translate_tab_and_shift(to);
2408 menu_unescape_name(from);
2409 menu_unescape_name(to);
Bram Moolenaarfc1421e2006-04-20 22:17:20 +00002410 tp[menutrans_ga.ga_len].from = from;
2411 tp[menutrans_ga.ga_len].from_noamp = from_noamp;
2412 tp[menutrans_ga.ga_len].to = to;
2413 ++menutrans_ga.ga_len;
2414 }
2415 else
2416 {
2417 vim_free(from);
2418 vim_free(from_noamp);
2419 vim_free(to);
2420 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00002421 }
2422 }
2423 }
2424 }
2425#endif
2426}
2427
2428#if defined(FEAT_MULTI_LANG) || defined(FEAT_TOOLBAR)
2429/*
2430 * Find the character just after one part of a menu name.
2431 */
2432 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002433menu_skip_part(char_u *p)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002434{
Bram Moolenaar1c465442017-03-12 20:10:05 +01002435 while (*p != NUL && *p != '.' && !VIM_ISWHITE(*p))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002436 {
2437 if ((*p == '\\' || *p == Ctrl_V) && p[1] != NUL)
2438 ++p;
2439 ++p;
2440 }
2441 return p;
2442}
2443#endif
2444
2445#ifdef FEAT_MULTI_LANG
2446/*
2447 * Lookup part of a menu name in the translations.
2448 * Return a pointer to the translation or NULL if not found.
2449 */
2450 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002451menutrans_lookup(char_u *name, int len)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002452{
2453 menutrans_T *tp = (menutrans_T *)menutrans_ga.ga_data;
2454 int i;
2455 char_u *dname;
2456
2457 for (i = 0; i < menutrans_ga.ga_len; ++i)
Bram Moolenaar11dd8c12017-03-04 20:41:34 +01002458 if (STRNICMP(name, tp[i].from, len) == 0 && tp[i].from[len] == NUL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002459 return tp[i].to;
2460
2461 /* Now try again while ignoring '&' characters. */
2462 i = name[len];
2463 name[len] = NUL;
2464 dname = menu_text(name, NULL, NULL);
2465 name[len] = i;
2466 if (dname != NULL)
2467 {
2468 for (i = 0; i < menutrans_ga.ga_len; ++i)
Bram Moolenaar11dd8c12017-03-04 20:41:34 +01002469 if (STRICMP(dname, tp[i].from_noamp) == 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002470 {
2471 vim_free(dname);
2472 return tp[i].to;
2473 }
2474 vim_free(dname);
2475 }
2476
2477 return NULL;
2478}
Bram Moolenaar071d4272004-06-13 20:20:40 +00002479
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002480/*
2481 * Unescape the name in the translate dictionary table.
2482 */
2483 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002484menu_unescape_name(char_u *name)
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002485{
2486 char_u *p;
2487
Bram Moolenaar91acfff2017-03-12 19:22:36 +01002488 for (p = name; *p && *p != '.'; MB_PTR_ADV(p))
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002489 if (*p == '\\')
2490 STRMOVE(p, p + 1);
2491}
Bram Moolenaar56be9502010-06-06 14:20:26 +02002492#endif /* FEAT_MULTI_LANG */
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002493
2494/*
2495 * Isolate the menu name.
2496 * Skip the menu name, and translate <Tab> into a real TAB.
2497 */
2498 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002499menu_translate_tab_and_shift(char_u *arg_start)
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002500{
2501 char_u *arg = arg_start;
2502
Bram Moolenaar1c465442017-03-12 20:10:05 +01002503 while (*arg && !VIM_ISWHITE(*arg))
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002504 {
2505 if ((*arg == '\\' || *arg == Ctrl_V) && arg[1] != NUL)
2506 arg++;
2507 else if (STRNICMP(arg, "<TAB>", 5) == 0)
2508 {
2509 *arg = TAB;
2510 STRMOVE(arg + 1, arg + 5);
2511 }
2512 arg++;
2513 }
2514 if (*arg != NUL)
2515 *arg++ = NUL;
2516 arg = skipwhite(arg);
2517
2518 return arg;
2519}
2520
Bram Moolenaar071d4272004-06-13 20:20:40 +00002521#endif /* FEAT_MENU */