blob: e55cab675842c11d3e3c81ee7698814f6986f9bc [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
Bram Moolenaar4f974752019-02-17 17:44:42 +010021#ifdef FEAT_GUI_MSWIN
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);
Bram Moolenaar5843f5f2019-08-20 20:13:45 +020032static char_u *menu_name_skip(char_u *name);
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010033static int menu_name_equal(char_u *name, vimmenu_T *menu);
34static int menu_namecmp(char_u *name, char_u *mname);
35static int get_menu_cmd_modes(char_u *, int, int *, int *);
36static char_u *popup_mode_name(char_u *name, int idx);
37static char_u *menu_text(char_u *text, int *mnemonic, char_u **actext);
Bram Moolenaar071d4272004-06-13 20:20:40 +000038
Bram Moolenaar4f974752019-02-17 17:44:42 +010039#if defined(FEAT_GUI_MSWIN) && defined(FEAT_TEAROFF)
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010040static void gui_create_tearoffs_recurse(vimmenu_T *menu, const char_u *pname, int *pri_tab, int pri_idx);
41static void gui_add_tearoff(char_u *tearpath, int *pri_tab, int pri_idx);
42static void gui_destroy_tearoffs_recurse(vimmenu_T *menu);
Bram Moolenaar071d4272004-06-13 20:20:40 +000043static int s_tearoffs = FALSE;
44#endif
45
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010046static int menu_is_hidden(char_u *name);
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010047static int menu_is_tearoff(char_u *name);
Bram Moolenaar071d4272004-06-13 20:20:40 +000048
49#if defined(FEAT_MULTI_LANG) || defined(FEAT_TOOLBAR)
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010050static char_u *menu_skip_part(char_u *p);
Bram Moolenaar071d4272004-06-13 20:20:40 +000051#endif
52#ifdef FEAT_MULTI_LANG
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010053static char_u *menutrans_lookup(char_u *name, int len);
54static void menu_unescape_name(char_u *p);
Bram Moolenaar071d4272004-06-13 20:20:40 +000055#endif
56
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010057static char_u *menu_translate_tab_and_shift(char_u *arg_start);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +020058
Bram Moolenaar071d4272004-06-13 20:20:40 +000059/* The character for each menu mode */
Bram Moolenaar4c5d8152018-10-19 22:36:53 +020060static char *menu_mode_chars[] = {"n", "v", "s", "o", "i", "c", "tl", "t"};
Bram Moolenaar071d4272004-06-13 20:20:40 +000061
62static char_u e_notsubmenu[] = N_("E327: Part of menu-item path is not sub-menu");
Bram Moolenaar342337a2005-07-21 21:11:17 +000063static char_u e_nomenu[] = N_("E329: No menu \"%s\"");
Bram Moolenaar071d4272004-06-13 20:20:40 +000064
65#ifdef FEAT_TOOLBAR
66static const char *toolbar_names[] =
67{
68 /* 0 */ "New", "Open", "Save", "Undo", "Redo",
69 /* 5 */ "Cut", "Copy", "Paste", "Print", "Help",
70 /* 10 */ "Find", "SaveAll", "SaveSesn", "NewSesn", "LoadSesn",
71 /* 15 */ "RunScript", "Replace", "WinClose", "WinMax", "WinMin",
72 /* 20 */ "WinSplit", "Shell", "FindPrev", "FindNext", "FindHelp",
73 /* 25 */ "Make", "TagJump", "RunCtags", "WinVSplit", "WinMaxWidth",
74 /* 30 */ "WinMinWidth", "Exit"
75};
76# define TOOLBAR_NAME_COUNT (sizeof(toolbar_names) / sizeof(char *))
77#endif
78
79/*
Bram Moolenaar1b9645d2017-09-17 23:03:31 +020080 * Return TRUE if "name" is a window toolbar menu name.
81 */
82 static int
83menu_is_winbar(char_u *name)
84{
Bram Moolenaar378daf82017-09-23 23:58:28 +020085 return (STRNCMP(name, "WinBar", 6) == 0);
Bram Moolenaar1b9645d2017-09-17 23:03:31 +020086}
87
88 int
89winbar_height(win_T *wp)
90{
91 if (wp->w_winbar != NULL && wp->w_winbar->children != NULL)
92 return 1;
93 return 0;
94}
95
96 static vimmenu_T **
97get_root_menu(char_u *name)
98{
99 if (menu_is_winbar(name))
100 return &curwin->w_winbar;
101 return &root_menu;
102}
103
104/*
Bram Moolenaar071d4272004-06-13 20:20:40 +0000105 * Do the :menu command and relatives.
106 */
107 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100108ex_menu(
109 exarg_T *eap) /* Ex command arguments */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000110{
111 char_u *menu_path;
112 int modes;
113 char_u *map_to;
114 int noremap;
115 int silent = FALSE;
Bram Moolenaar8b2d9c42006-05-03 21:28:47 +0000116 int special = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000117 int unmenu;
118 char_u *map_buf;
119 char_u *arg;
120 char_u *p;
121 int i;
Bram Moolenaar241a8aa2005-12-06 20:04:44 +0000122#if defined(FEAT_GUI) && !defined(FEAT_GUI_GTK)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000123 int old_menu_height;
Bram Moolenaar4f974752019-02-17 17:44:42 +0100124# if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_MSWIN)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000125 int old_toolbar_height;
126# endif
127#endif
128 int pri_tab[MENUDEPTH + 1];
129 int enable = MAYBE; /* TRUE for "menu enable", FALSE for "menu
130 * disable */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000131#ifdef FEAT_TOOLBAR
132 char_u *icon = NULL;
133#endif
134 vimmenu_T menuarg;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200135 vimmenu_T **root_menu_ptr;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000136
137 modes = get_menu_cmd_modes(eap->cmd, eap->forceit, &noremap, &unmenu);
138 arg = eap->arg;
139
140 for (;;)
141 {
142 if (STRNCMP(arg, "<script>", 8) == 0)
143 {
144 noremap = REMAP_SCRIPT;
145 arg = skipwhite(arg + 8);
146 continue;
147 }
148 if (STRNCMP(arg, "<silent>", 8) == 0)
149 {
150 silent = TRUE;
151 arg = skipwhite(arg + 8);
152 continue;
153 }
Bram Moolenaar8b2d9c42006-05-03 21:28:47 +0000154 if (STRNCMP(arg, "<special>", 9) == 0)
155 {
156 special = TRUE;
157 arg = skipwhite(arg + 9);
158 continue;
159 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000160 break;
161 }
162
163
164 /* Locate an optional "icon=filename" argument. */
165 if (STRNCMP(arg, "icon=", 5) == 0)
166 {
167 arg += 5;
168#ifdef FEAT_TOOLBAR
169 icon = arg;
170#endif
171 while (*arg != NUL && *arg != ' ')
172 {
173 if (*arg == '\\')
Bram Moolenaar8c8de832008-06-24 22:58:06 +0000174 STRMOVE(arg, arg + 1);
Bram Moolenaar91acfff2017-03-12 19:22:36 +0100175 MB_PTR_ADV(arg);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000176 }
177 if (*arg != NUL)
178 {
179 *arg++ = NUL;
180 arg = skipwhite(arg);
181 }
182 }
183
184 /*
185 * Fill in the priority table.
186 */
187 for (p = arg; *p; ++p)
188 if (!VIM_ISDIGIT(*p) && *p != '.')
189 break;
Bram Moolenaar1c465442017-03-12 20:10:05 +0100190 if (VIM_ISWHITE(*p))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000191 {
Bram Moolenaar1c465442017-03-12 20:10:05 +0100192 for (i = 0; i < MENUDEPTH && !VIM_ISWHITE(*arg); ++i)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000193 {
194 pri_tab[i] = getdigits(&arg);
195 if (pri_tab[i] == 0)
196 pri_tab[i] = 500;
197 if (*arg == '.')
198 ++arg;
199 }
200 arg = skipwhite(arg);
201 }
202 else if (eap->addr_count && eap->line2 != 0)
203 {
204 pri_tab[0] = eap->line2;
205 i = 1;
206 }
207 else
208 i = 0;
209 while (i < MENUDEPTH)
210 pri_tab[i++] = 500;
211 pri_tab[MENUDEPTH] = -1; /* mark end of the table */
212
213 /*
214 * Check for "disable" or "enable" argument.
215 */
Bram Moolenaar1c465442017-03-12 20:10:05 +0100216 if (STRNCMP(arg, "enable", 6) == 0 && VIM_ISWHITE(arg[6]))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000217 {
218 enable = TRUE;
219 arg = skipwhite(arg + 6);
220 }
Bram Moolenaar1c465442017-03-12 20:10:05 +0100221 else if (STRNCMP(arg, "disable", 7) == 0 && VIM_ISWHITE(arg[7]))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000222 {
223 enable = FALSE;
224 arg = skipwhite(arg + 7);
225 }
226
227 /*
228 * If there is no argument, display all menus.
229 */
230 if (*arg == NUL)
231 {
232 show_menus(arg, modes);
233 return;
234 }
235
236#ifdef FEAT_TOOLBAR
237 /*
238 * Need to get the toolbar icon index before doing the translation.
239 */
240 menuarg.iconidx = -1;
241 menuarg.icon_builtin = FALSE;
242 if (menu_is_toolbar(arg))
243 {
244 menu_path = menu_skip_part(arg);
245 if (*menu_path == '.')
246 {
247 p = menu_skip_part(++menu_path);
248 if (STRNCMP(menu_path, "BuiltIn", 7) == 0)
249 {
250 if (skipdigits(menu_path + 7) == p)
251 {
252 menuarg.iconidx = atoi((char *)menu_path + 7);
Bram Moolenaaraf0167f2009-05-16 15:31:32 +0000253 if (menuarg.iconidx >= (int)TOOLBAR_NAME_COUNT)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000254 menuarg.iconidx = -1;
255 else
256 menuarg.icon_builtin = TRUE;
257 }
258 }
259 else
260 {
Bram Moolenaaraf0167f2009-05-16 15:31:32 +0000261 for (i = 0; i < (int)TOOLBAR_NAME_COUNT; ++i)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000262 if (STRNCMP(toolbar_names[i], menu_path, p - menu_path)
263 == 0)
264 {
265 menuarg.iconidx = i;
266 break;
267 }
268 }
269 }
270 }
271#endif
272
Bram Moolenaar071d4272004-06-13 20:20:40 +0000273 menu_path = arg;
274 if (*menu_path == '.')
275 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100276 semsg(_(e_invarg2), menu_path);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000277 goto theend;
278 }
279
Bram Moolenaar70b11cd2010-05-14 22:24:40 +0200280 map_to = menu_translate_tab_and_shift(arg);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000281
282 /*
283 * If there is only a menu name, display menus with that name.
284 */
285 if (*map_to == NUL && !unmenu && enable == MAYBE)
286 {
287 show_menus(menu_path, modes);
288 goto theend;
289 }
290 else if (*map_to != NUL && (unmenu || enable != MAYBE))
291 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100292 emsg(_(e_trailing));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000293 goto theend;
294 }
Bram Moolenaar241a8aa2005-12-06 20:04:44 +0000295#if defined(FEAT_GUI) && !(defined(FEAT_GUI_GTK) || defined(FEAT_GUI_PHOTON))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000296 old_menu_height = gui.menu_height;
Bram Moolenaar4f974752019-02-17 17:44:42 +0100297# if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_MSWIN)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000298 old_toolbar_height = gui.toolbar_height;
299# endif
300#endif
301
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200302 root_menu_ptr = get_root_menu(menu_path);
303 if (root_menu_ptr == &curwin->w_winbar)
304 /* Assume the window toolbar menu will change. */
305 redraw_later(NOT_VALID);
306
Bram Moolenaar071d4272004-06-13 20:20:40 +0000307 if (enable != MAYBE)
308 {
309 /*
310 * Change sensitivity of the menu.
311 * For the PopUp menu, remove a menu for each mode separately.
312 * Careful: menu_nable_recurse() changes menu_path.
313 */
314 if (STRCMP(menu_path, "*") == 0) /* meaning: do all menus */
315 menu_path = (char_u *)"";
316
317 if (menu_is_popup(menu_path))
318 {
319 for (i = 0; i < MENU_INDEX_TIP; ++i)
320 if (modes & (1 << i))
321 {
322 p = popup_mode_name(menu_path, i);
323 if (p != NULL)
324 {
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200325 menu_nable_recurse(*root_menu_ptr, p, MENU_ALL_MODES,
Bram Moolenaar071d4272004-06-13 20:20:40 +0000326 enable);
327 vim_free(p);
328 }
329 }
330 }
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200331 menu_nable_recurse(*root_menu_ptr, menu_path, modes, enable);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000332 }
333 else if (unmenu)
334 {
335 /*
336 * Delete menu(s).
337 */
338 if (STRCMP(menu_path, "*") == 0) /* meaning: remove all menus */
339 menu_path = (char_u *)"";
340
341 /*
342 * For the PopUp menu, remove a menu for each mode separately.
343 */
344 if (menu_is_popup(menu_path))
345 {
346 for (i = 0; i < MENU_INDEX_TIP; ++i)
347 if (modes & (1 << i))
348 {
349 p = popup_mode_name(menu_path, i);
350 if (p != NULL)
351 {
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200352 remove_menu(root_menu_ptr, p, MENU_ALL_MODES, TRUE);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000353 vim_free(p);
354 }
355 }
356 }
357
358 /* Careful: remove_menu() changes menu_path */
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200359 remove_menu(root_menu_ptr, menu_path, modes, FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000360 }
361 else
362 {
363 /*
364 * Add menu(s).
365 * Replace special key codes.
366 */
367 if (STRICMP(map_to, "<nop>") == 0) /* "<Nop>" means nothing */
368 {
369 map_to = (char_u *)"";
370 map_buf = NULL;
371 }
Bram Moolenaar3fdfa4a2004-10-07 21:02:47 +0000372 else if (modes & MENU_TIP_MODE)
373 map_buf = NULL; /* Menu tips are plain text. */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000374 else
Bram Moolenaar459fd782019-10-13 16:43:39 +0200375 map_to = replace_termcodes(map_to, &map_buf,
376 REPTERM_DO_LT | (special ? REPTERM_SPECIAL : 0), NULL);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000377 menuarg.modes = modes;
378#ifdef FEAT_TOOLBAR
379 menuarg.iconfile = icon;
380#endif
381 menuarg.noremap[0] = noremap;
382 menuarg.silent[0] = silent;
383 add_menu_path(menu_path, &menuarg, pri_tab, map_to
Bram Moolenaar4f974752019-02-17 17:44:42 +0100384#ifdef FEAT_GUI_MSWIN
Bram Moolenaar071d4272004-06-13 20:20:40 +0000385 , TRUE
386#endif
387 );
388
389 /*
390 * For the PopUp menu, add a menu for each mode separately.
391 */
392 if (menu_is_popup(menu_path))
393 {
394 for (i = 0; i < MENU_INDEX_TIP; ++i)
395 if (modes & (1 << i))
396 {
397 p = popup_mode_name(menu_path, i);
398 if (p != NULL)
399 {
400 /* Include all modes, to make ":amenu" work */
401 menuarg.modes = modes;
402#ifdef FEAT_TOOLBAR
403 menuarg.iconfile = NULL;
404 menuarg.iconidx = -1;
405 menuarg.icon_builtin = FALSE;
406#endif
407 add_menu_path(p, &menuarg, pri_tab, map_to
Bram Moolenaar4f974752019-02-17 17:44:42 +0100408#ifdef FEAT_GUI_MSWIN
Bram Moolenaar071d4272004-06-13 20:20:40 +0000409 , TRUE
410#endif
411 );
412 vim_free(p);
413 }
414 }
415 }
416
417 vim_free(map_buf);
418 }
419
Bram Moolenaar241a8aa2005-12-06 20:04:44 +0000420#if defined(FEAT_GUI) && !(defined(FEAT_GUI_GTK))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000421 /* If the menubar height changed, resize the window */
422 if (gui.in_use
423 && (gui.menu_height != old_menu_height
Bram Moolenaar4f974752019-02-17 17:44:42 +0100424# if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_MSWIN)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000425 || gui.toolbar_height != old_toolbar_height
426# endif
427 ))
Bram Moolenaar04a9d452006-03-27 21:03:26 +0000428 gui_set_shellsize(FALSE, FALSE, RESIZE_VERT);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000429#endif
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200430 if (root_menu_ptr == &curwin->w_winbar)
431 {
432 int h = winbar_height(curwin);
433
434 if (h != curwin->w_winbar_height)
435 {
436 if (h == 0)
437 ++curwin->w_height;
438 else if (curwin->w_height > 0)
439 --curwin->w_height;
440 curwin->w_winbar_height = h;
441 }
442 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000443
444theend:
Bram Moolenaar071d4272004-06-13 20:20:40 +0000445 ;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000446}
447
448/*
449 * Add the menu with the given name to the menu hierarchy
450 */
451 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100452add_menu_path(
453 char_u *menu_path,
454 vimmenu_T *menuarg, /* passes modes, iconfile, iconidx,
Bram Moolenaar071d4272004-06-13 20:20:40 +0000455 icon_builtin, silent[0], noremap[0] */
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100456 int *pri_tab,
457 char_u *call_data
Bram Moolenaar4f974752019-02-17 17:44:42 +0100458#ifdef FEAT_GUI_MSWIN
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100459 , int addtearoff /* may add tearoff item */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000460#endif
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100461 )
Bram Moolenaar071d4272004-06-13 20:20:40 +0000462{
463 char_u *path_name;
464 int modes = menuarg->modes;
465 vimmenu_T **menup;
466 vimmenu_T *menu = NULL;
467 vimmenu_T *parent;
468 vimmenu_T **lower_pri;
469 char_u *p;
470 char_u *name;
471 char_u *dname;
472 char_u *next_name;
473 int i;
474 int c;
Bram Moolenaar7871a502010-05-14 21:19:23 +0200475 int d;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000476#ifdef FEAT_GUI
477 int idx;
478 int new_idx;
479#endif
480 int pri_idx = 0;
481 int old_modes = 0;
482 int amenu;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +0200483#ifdef FEAT_MULTI_LANG
484 char_u *en_name;
485 char_u *map_to = NULL;
486#endif
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200487 vimmenu_T **root_menu_ptr;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000488
489 /* Make a copy so we can stuff around with it, since it could be const */
490 path_name = vim_strsave(menu_path);
491 if (path_name == NULL)
492 return FAIL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200493 root_menu_ptr = get_root_menu(menu_path);
494 menup = root_menu_ptr;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000495 parent = NULL;
496 name = path_name;
497 while (*name)
498 {
499 /* Get name of this element in the menu hierarchy, and the simplified
500 * name (without mnemonic and accelerator text). */
501 next_name = menu_name_skip(name);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +0200502#ifdef FEAT_MULTI_LANG
Bram Moolenaar442b4222010-05-24 21:34:22 +0200503 map_to = menutrans_lookup(name, (int)STRLEN(name));
Bram Moolenaar70b11cd2010-05-14 22:24:40 +0200504 if (map_to != NULL)
505 {
506 en_name = name;
507 name = map_to;
508 }
509 else
510 en_name = NULL;
511#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000512 dname = menu_text(name, NULL, NULL);
Bram Moolenaar18a0b122006-08-16 13:55:16 +0000513 if (dname == NULL)
514 goto erret;
515 if (*dname == NUL)
516 {
517 /* Only a mnemonic or accelerator is not valid. */
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100518 emsg(_("E792: Empty menu name"));
Bram Moolenaar18a0b122006-08-16 13:55:16 +0000519 goto erret;
520 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000521
522 /* See if it's already there */
523 lower_pri = menup;
524#ifdef FEAT_GUI
525 idx = 0;
526 new_idx = 0;
527#endif
528 menu = *menup;
529 while (menu != NULL)
530 {
531 if (menu_name_equal(name, menu) || menu_name_equal(dname, menu))
532 {
533 if (*next_name == NUL && menu->children != NULL)
534 {
535 if (!sys_menu)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100536 emsg(_("E330: Menu path must not lead to a sub-menu"));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000537 goto erret;
538 }
539 if (*next_name != NUL && menu->children == NULL
Bram Moolenaar4f974752019-02-17 17:44:42 +0100540#ifdef FEAT_GUI_MSWIN
Bram Moolenaar071d4272004-06-13 20:20:40 +0000541 && addtearoff
542#endif
543 )
544 {
545 if (!sys_menu)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100546 emsg(_(e_notsubmenu));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000547 goto erret;
548 }
549 break;
550 }
551 menup = &menu->next;
552
553 /* Count menus, to find where this one needs to be inserted.
554 * Ignore menus that are not in the menubar (PopUp and Toolbar) */
555 if (parent != NULL || menu_is_menubar(menu->name))
556 {
557#ifdef FEAT_GUI
558 ++idx;
559#endif
560 if (menu->priority <= pri_tab[pri_idx])
561 {
562 lower_pri = menup;
563#ifdef FEAT_GUI
564 new_idx = idx;
565#endif
566 }
567 }
568 menu = menu->next;
569 }
570
571 if (menu == NULL)
572 {
573 if (*next_name == NUL && parent == NULL)
574 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100575 emsg(_("E331: Must not add menu items directly to menu bar"));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000576 goto erret;
577 }
578
579 if (menu_is_separator(dname) && *next_name != NUL)
580 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100581 emsg(_("E332: Separator cannot be part of a menu path"));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000582 goto erret;
583 }
584
585 /* Not already there, so lets add it */
Bram Moolenaare809a4e2019-07-04 17:35:05 +0200586 menu = ALLOC_CLEAR_ONE(vimmenu_T);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000587 if (menu == NULL)
588 goto erret;
589
590 menu->modes = modes;
591 menu->enabled = MENU_ALL_MODES;
592 menu->name = vim_strsave(name);
593 /* separate mnemonic and accelerator text from actual menu name */
594 menu->dname = menu_text(name, &menu->mnemonic, &menu->actext);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +0200595#ifdef FEAT_MULTI_LANG
596 if (en_name != NULL)
597 {
598 menu->en_name = vim_strsave(en_name);
599 menu->en_dname = menu_text(en_name, NULL, NULL);
600 }
601 else
602 {
603 menu->en_name = NULL;
604 menu->en_dname = NULL;
605 }
606#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000607 menu->priority = pri_tab[pri_idx];
608 menu->parent = parent;
609#ifdef FEAT_GUI_MOTIF
610 menu->sensitive = TRUE; /* the default */
611#endif
612#ifdef FEAT_BEVAL_TIP
613 menu->tip = NULL;
614#endif
615#ifdef FEAT_GUI_ATHENA
616 menu->image = None; /* X-Windows definition for NULL*/
617#endif
618
619 /*
620 * Add after menu that has lower priority.
621 */
622 menu->next = *lower_pri;
623 *lower_pri = menu;
624
625 old_modes = 0;
626
627#ifdef FEAT_TOOLBAR
628 menu->iconidx = menuarg->iconidx;
629 menu->icon_builtin = menuarg->icon_builtin;
630 if (*next_name == NUL && menuarg->iconfile != NULL)
631 menu->iconfile = vim_strsave(menuarg->iconfile);
632#endif
Bram Moolenaar4f974752019-02-17 17:44:42 +0100633#if defined(FEAT_GUI_MSWIN) && defined(FEAT_TEAROFF)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000634 /* the tearoff item must be present in the modes of each item. */
635 if (parent != NULL && menu_is_tearoff(parent->children->dname))
636 parent->children->modes |= modes;
637#endif
638 }
639 else
640 {
641 old_modes = menu->modes;
642
643 /*
644 * If this menu option was previously only available in other
645 * modes, then make sure it's available for this one now
646 * Also enable a menu when it's created or changed.
647 */
Bram Moolenaar4f974752019-02-17 17:44:42 +0100648#ifdef FEAT_GUI_MSWIN
Bram Moolenaar071d4272004-06-13 20:20:40 +0000649 /* If adding a tearbar (addtearoff == FALSE) don't update modes */
650 if (addtearoff)
651#endif
652 {
653 menu->modes |= modes;
654 menu->enabled |= modes;
655 }
656 }
657
658#ifdef FEAT_GUI
659 /*
660 * Add the menu item when it's used in one of the modes, but not when
661 * only a tooltip is defined.
662 */
663 if ((old_modes & MENU_ALL_MODES) == 0
664 && (menu->modes & MENU_ALL_MODES) != 0)
665 {
666 if (gui.in_use) /* Otherwise it will be added when GUI starts */
667 {
668 if (*next_name == NUL)
669 {
670 /* Real menu item, not sub-menu */
671 gui_mch_add_menu_item(menu, new_idx);
672
673 /* Want to update menus now even if mode not changed */
674 force_menu_update = TRUE;
675 }
676 else
677 {
678 /* Sub-menu (not at end of path yet) */
679 gui_mch_add_menu(menu, new_idx);
680 }
681 }
682
Bram Moolenaar4f974752019-02-17 17:44:42 +0100683# if defined(FEAT_GUI_MSWIN) & defined(FEAT_TEAROFF)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000684 /* When adding a new submenu, may add a tearoff item */
685 if ( addtearoff
686 && *next_name
687 && vim_strchr(p_go, GO_TEAROFF) != NULL
688 && menu_is_menubar(name))
689 {
690 char_u *tearpath;
691
692 /*
693 * The pointers next_name & path_name refer to a string with
694 * \'s and ^V's stripped out. But menu_path is a "raw"
695 * string, so we must correct for special characters.
696 */
Bram Moolenaar964b3742019-05-24 18:54:09 +0200697 tearpath = alloc(STRLEN(menu_path) + TEAR_LEN + 2);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000698 if (tearpath != NULL)
699 {
700 char_u *s;
701 int idx;
702
703 STRCPY(tearpath, menu_path);
704 idx = (int)(next_name - path_name - 1);
Bram Moolenaar91acfff2017-03-12 19:22:36 +0100705 for (s = tearpath; *s && s < tearpath + idx; MB_PTR_ADV(s))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000706 {
707 if ((*s == '\\' || *s == Ctrl_V) && s[1])
708 {
709 ++idx;
710 ++s;
711 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000712 }
713 tearpath[idx] = NUL;
714 gui_add_tearoff(tearpath, pri_tab, pri_idx);
715 vim_free(tearpath);
716 }
717 }
718# endif
719 }
720#endif /* FEAT_GUI */
721
722 menup = &menu->children;
723 parent = menu;
724 name = next_name;
Bram Moolenaard23a8232018-02-10 18:45:26 +0100725 VIM_CLEAR(dname);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000726 if (pri_tab[pri_idx + 1] != -1)
727 ++pri_idx;
728 }
729 vim_free(path_name);
730
731 /*
732 * Only add system menu items which have not been defined yet.
733 * First check if this was an ":amenu".
734 */
735 amenu = ((modes & (MENU_NORMAL_MODE | MENU_INSERT_MODE)) ==
736 (MENU_NORMAL_MODE | MENU_INSERT_MODE));
737 if (sys_menu)
738 modes &= ~old_modes;
739
740 if (menu != NULL && modes)
741 {
742#ifdef FEAT_GUI
743 menu->cb = gui_menu_cb;
744#endif
745 p = (call_data == NULL) ? NULL : vim_strsave(call_data);
746
747 /* loop over all modes, may add more than one */
748 for (i = 0; i < MENU_MODES; ++i)
749 {
750 if (modes & (1 << i))
751 {
752 /* free any old menu */
753 free_menu_string(menu, i);
754
755 /* For "amenu", may insert an extra character.
756 * Don't do this if adding a tearbar (addtearoff == FALSE).
757 * Don't do this for "<Nop>". */
758 c = 0;
Bram Moolenaar7871a502010-05-14 21:19:23 +0200759 d = 0;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000760 if (amenu && call_data != NULL && *call_data != NUL
Bram Moolenaar4f974752019-02-17 17:44:42 +0100761#ifdef FEAT_GUI_MSWIN
Bram Moolenaar071d4272004-06-13 20:20:40 +0000762 && addtearoff
763#endif
764 )
765 {
766 switch (1 << i)
767 {
768 case MENU_VISUAL_MODE:
Bram Moolenaarb3656ed2006-03-20 21:59:49 +0000769 case MENU_SELECT_MODE:
Bram Moolenaar071d4272004-06-13 20:20:40 +0000770 case MENU_OP_PENDING_MODE:
771 case MENU_CMDLINE_MODE:
772 c = Ctrl_C;
773 break;
774 case MENU_INSERT_MODE:
Bram Moolenaar7871a502010-05-14 21:19:23 +0200775 c = Ctrl_BSL;
776 d = Ctrl_O;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000777 break;
778 }
779 }
780
Bram Moolenaar7871a502010-05-14 21:19:23 +0200781 if (c != 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000782 {
Bram Moolenaar964b3742019-05-24 18:54:09 +0200783 menu->strings[i] = alloc(STRLEN(call_data) + 5);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000784 if (menu->strings[i] != NULL)
785 {
786 menu->strings[i][0] = c;
Bram Moolenaar7871a502010-05-14 21:19:23 +0200787 if (d == 0)
788 STRCPY(menu->strings[i] + 1, call_data);
789 else
790 {
791 menu->strings[i][1] = d;
792 STRCPY(menu->strings[i] + 2, call_data);
793 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000794 if (c == Ctrl_C)
795 {
Bram Moolenaara93fa7e2006-04-17 22:14:47 +0000796 int len = (int)STRLEN(menu->strings[i]);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000797
798 /* Append CTRL-\ CTRL-G to obey 'insertmode'. */
799 menu->strings[i][len] = Ctrl_BSL;
800 menu->strings[i][len + 1] = Ctrl_G;
801 menu->strings[i][len + 2] = NUL;
802 }
803 }
804 }
805 else
806 menu->strings[i] = p;
807 menu->noremap[i] = menuarg->noremap[0];
808 menu->silent[i] = menuarg->silent[0];
809 }
810 }
Bram Moolenaar4f974752019-02-17 17:44:42 +0100811#if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_MSWIN) \
Bram Moolenaarc3719bd2017-11-18 22:13:31 +0100812 && (defined(FEAT_BEVAL_GUI) || defined(FEAT_GUI_GTK))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000813 /* Need to update the menu tip. */
814 if (modes & MENU_TIP_MODE)
815 gui_mch_menu_set_tip(menu);
816#endif
817 }
818 return OK;
819
820erret:
821 vim_free(path_name);
822 vim_free(dname);
Bram Moolenaar18a0b122006-08-16 13:55:16 +0000823
824 /* Delete any empty submenu we added before discovering the error. Repeat
825 * for higher levels. */
826 while (parent != NULL && parent->children == NULL)
827 {
828 if (parent->parent == NULL)
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200829 menup = root_menu_ptr;
Bram Moolenaar18a0b122006-08-16 13:55:16 +0000830 else
831 menup = &parent->parent->children;
832 for ( ; *menup != NULL && *menup != parent; menup = &((*menup)->next))
833 ;
834 if (*menup == NULL) /* safety check */
835 break;
836 parent = parent->parent;
837 free_menu(menup);
838 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000839 return FAIL;
840}
841
842/*
843 * Set the (sub)menu with the given name to enabled or disabled.
844 * Called recursively.
845 */
846 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100847menu_nable_recurse(
848 vimmenu_T *menu,
849 char_u *name,
850 int modes,
851 int enable)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000852{
853 char_u *p;
854
855 if (menu == NULL)
856 return OK; /* Got to bottom of hierarchy */
857
858 /* Get name of this element in the menu hierarchy */
859 p = menu_name_skip(name);
860
861 /* Find the menu */
862 while (menu != NULL)
863 {
864 if (*name == NUL || *name == '*' || menu_name_equal(name, menu))
865 {
866 if (*p != NUL)
867 {
868 if (menu->children == NULL)
869 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100870 emsg(_(e_notsubmenu));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000871 return FAIL;
872 }
873 if (menu_nable_recurse(menu->children, p, modes, enable)
874 == FAIL)
875 return FAIL;
876 }
877 else
878 if (enable)
879 menu->enabled |= modes;
880 else
881 menu->enabled &= ~modes;
882
883 /*
884 * When name is empty, we are doing all menu items for the given
885 * modes, so keep looping, otherwise we are just doing the named
886 * menu item (which has been found) so break here.
887 */
888 if (*name != NUL && *name != '*')
889 break;
890 }
891 menu = menu->next;
892 }
893 if (*name != NUL && *name != '*' && menu == NULL)
894 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100895 semsg(_(e_nomenu), name);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000896 return FAIL;
897 }
898
899#ifdef FEAT_GUI
900 /* Want to update menus now even if mode not changed */
901 force_menu_update = TRUE;
902#endif
903
904 return OK;
905}
906
907/*
908 * Remove the (sub)menu with the given name from the menu hierarchy
909 * Called recursively.
910 */
911 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100912remove_menu(
913 vimmenu_T **menup,
914 char_u *name,
915 int modes,
916 int silent) /* don't give error messages */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000917{
918 vimmenu_T *menu;
919 vimmenu_T *child;
920 char_u *p;
921
922 if (*menup == NULL)
923 return OK; /* Got to bottom of hierarchy */
924
925 /* Get name of this element in the menu hierarchy */
926 p = menu_name_skip(name);
927
928 /* Find the menu */
929 while ((menu = *menup) != NULL)
930 {
931 if (*name == NUL || menu_name_equal(name, menu))
932 {
933 if (*p != NUL && menu->children == NULL)
934 {
935 if (!silent)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100936 emsg(_(e_notsubmenu));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000937 return FAIL;
938 }
939 if ((menu->modes & modes) != 0x0)
940 {
Bram Moolenaar4f974752019-02-17 17:44:42 +0100941#if defined(FEAT_GUI_MSWIN) & defined(FEAT_TEAROFF)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000942 /*
943 * If we are removing all entries for this menu,MENU_ALL_MODES,
944 * Then kill any tearoff before we start
945 */
946 if (*p == NUL && modes == MENU_ALL_MODES)
947 {
948 if (IsWindow(menu->tearoff_handle))
949 DestroyWindow(menu->tearoff_handle);
950 }
951#endif
952 if (remove_menu(&menu->children, p, modes, silent) == FAIL)
953 return FAIL;
954 }
955 else if (*name != NUL)
956 {
957 if (!silent)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100958 emsg(_(e_menuothermode));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000959 return FAIL;
960 }
961
962 /*
963 * When name is empty, we are removing all menu items for the given
964 * modes, so keep looping, otherwise we are just removing the named
965 * menu item (which has been found) so break here.
966 */
967 if (*name != NUL)
968 break;
969
970 /* Remove the menu item for the given mode[s]. If the menu item
971 * is no longer valid in ANY mode, delete it */
972 menu->modes &= ~modes;
973 if (modes & MENU_TIP_MODE)
974 free_menu_string(menu, MENU_INDEX_TIP);
975 if ((menu->modes & MENU_ALL_MODES) == 0)
976 free_menu(menup);
977 else
978 menup = &menu->next;
979 }
980 else
981 menup = &menu->next;
982 }
983 if (*name != NUL)
984 {
985 if (menu == NULL)
986 {
987 if (!silent)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100988 semsg(_(e_nomenu), name);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000989 return FAIL;
990 }
991
992
993 /* Recalculate modes for menu based on the new updated children */
994 menu->modes &= ~modes;
Bram Moolenaar4f974752019-02-17 17:44:42 +0100995#if defined(FEAT_GUI_MSWIN) & defined(FEAT_TEAROFF)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000996 if ((s_tearoffs) && (menu->children != NULL)) /* there's a tear bar.. */
997 child = menu->children->next; /* don't count tearoff bar */
998 else
999#endif
1000 child = menu->children;
1001 for ( ; child != NULL; child = child->next)
1002 menu->modes |= child->modes;
1003 if (modes & MENU_TIP_MODE)
1004 {
1005 free_menu_string(menu, MENU_INDEX_TIP);
Bram Moolenaar4f974752019-02-17 17:44:42 +01001006#if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_MSWIN) \
Bram Moolenaarc3719bd2017-11-18 22:13:31 +01001007 && (defined(FEAT_BEVAL_GUI) || defined(FEAT_GUI_GTK))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001008 /* Need to update the menu tip. */
1009 if (gui.in_use)
1010 gui_mch_menu_set_tip(menu);
1011#endif
1012 }
1013 if ((menu->modes & MENU_ALL_MODES) == 0)
1014 {
1015 /* The menu item is no longer valid in ANY mode, so delete it */
Bram Moolenaar4f974752019-02-17 17:44:42 +01001016#if defined(FEAT_GUI_MSWIN) & defined(FEAT_TEAROFF)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001017 if (s_tearoffs && menu->children != NULL) /* there's a tear bar.. */
1018 free_menu(&menu->children);
1019#endif
1020 *menup = menu;
1021 free_menu(menup);
1022 }
1023 }
1024
1025 return OK;
1026}
1027
1028/*
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001029 * Remove the WinBar menu from window "wp".
1030 */
1031 void
1032remove_winbar(win_T *wp)
1033{
1034 remove_menu(&wp->w_winbar, (char_u *)"", MENU_ALL_MODES, TRUE);
1035 vim_free(wp->w_winbar_items);
1036}
1037
1038/*
Bram Moolenaar071d4272004-06-13 20:20:40 +00001039 * Free the given menu structure and remove it from the linked list.
1040 */
1041 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001042free_menu(vimmenu_T **menup)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001043{
1044 int i;
1045 vimmenu_T *menu;
1046
1047 menu = *menup;
1048
1049#ifdef FEAT_GUI
1050 /* Free machine specific menu structures (only when already created) */
1051 /* Also may rebuild a tearoff'ed menu */
1052 if (gui.in_use)
1053 gui_mch_destroy_menu(menu);
1054#endif
1055
1056 /* Don't change *menup until after calling gui_mch_destroy_menu(). The
1057 * MacOS code needs the original structure to properly delete the menu. */
1058 *menup = menu->next;
1059 vim_free(menu->name);
1060 vim_free(menu->dname);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001061#ifdef FEAT_MULTI_LANG
1062 vim_free(menu->en_name);
1063 vim_free(menu->en_dname);
1064#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001065 vim_free(menu->actext);
1066#ifdef FEAT_TOOLBAR
1067 vim_free(menu->iconfile);
Bram Moolenaarbee0c5b2005-02-07 22:03:36 +00001068# ifdef FEAT_GUI_MOTIF
1069 vim_free(menu->xpm_fname);
1070# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001071#endif
1072 for (i = 0; i < MENU_MODES; i++)
1073 free_menu_string(menu, i);
1074 vim_free(menu);
1075
1076#ifdef FEAT_GUI
1077 /* Want to update menus now even if mode not changed */
1078 force_menu_update = TRUE;
1079#endif
1080}
1081
1082/*
1083 * Free the menu->string with the given index.
1084 */
1085 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001086free_menu_string(vimmenu_T *menu, int idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001087{
1088 int count = 0;
1089 int i;
1090
1091 for (i = 0; i < MENU_MODES; i++)
1092 if (menu->strings[i] == menu->strings[idx])
1093 count++;
1094 if (count == 1)
1095 vim_free(menu->strings[idx]);
1096 menu->strings[idx] = NULL;
1097}
1098
1099/*
1100 * Show the mapping associated with a menu item or hierarchy in a sub-menu.
1101 */
1102 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001103show_menus(char_u *path_name, int modes)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001104{
1105 char_u *p;
1106 char_u *name;
1107 vimmenu_T *menu;
1108 vimmenu_T *parent = NULL;
1109
Bram Moolenaar071d4272004-06-13 20:20:40 +00001110 name = path_name = vim_strsave(path_name);
1111 if (path_name == NULL)
1112 return FAIL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001113 menu = *get_root_menu(path_name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001114
1115 /* First, find the (sub)menu with the given name */
1116 while (*name)
1117 {
1118 p = menu_name_skip(name);
1119 while (menu != NULL)
1120 {
1121 if (menu_name_equal(name, menu))
1122 {
1123 /* Found menu */
1124 if (*p != NUL && menu->children == NULL)
1125 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01001126 emsg(_(e_notsubmenu));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001127 vim_free(path_name);
1128 return FAIL;
1129 }
1130 else if ((menu->modes & modes) == 0x0)
1131 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01001132 emsg(_(e_menuothermode));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001133 vim_free(path_name);
1134 return FAIL;
1135 }
1136 break;
1137 }
1138 menu = menu->next;
1139 }
1140 if (menu == NULL)
1141 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01001142 semsg(_(e_nomenu), name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001143 vim_free(path_name);
1144 return FAIL;
1145 }
1146 name = p;
1147 parent = menu;
1148 menu = menu->children;
1149 }
Bram Moolenaaracbd4422008-08-17 21:44:45 +00001150 vim_free(path_name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001151
1152 /* Now we have found the matching menu, and we list the mappings */
1153 /* Highlight title */
Bram Moolenaar32526b32019-01-19 17:43:09 +01001154 msg_puts_title(_("\n--- Menus ---"));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001155
1156 show_menus_recursive(parent, modes, 0);
1157 return OK;
1158}
1159
1160/*
1161 * Recursively show the mappings associated with the menus under the given one
1162 */
1163 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001164show_menus_recursive(vimmenu_T *menu, int modes, int depth)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001165{
1166 int i;
1167 int bit;
1168
1169 if (menu != NULL && (menu->modes & modes) == 0x0)
1170 return;
1171
1172 if (menu != NULL)
1173 {
1174 msg_putchar('\n');
1175 if (got_int) /* "q" hit for "--more--" */
1176 return;
1177 for (i = 0; i < depth; i++)
Bram Moolenaar32526b32019-01-19 17:43:09 +01001178 msg_puts(" ");
Bram Moolenaar071d4272004-06-13 20:20:40 +00001179 if (menu->priority)
1180 {
1181 msg_outnum((long)menu->priority);
Bram Moolenaar32526b32019-01-19 17:43:09 +01001182 msg_puts(" ");
Bram Moolenaar071d4272004-06-13 20:20:40 +00001183 }
1184 /* Same highlighting as for directories!? */
Bram Moolenaar8820b482017-03-16 17:23:31 +01001185 msg_outtrans_attr(menu->name, HL_ATTR(HLF_D));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001186 }
1187
1188 if (menu != NULL && menu->children == NULL)
1189 {
1190 for (bit = 0; bit < MENU_MODES; bit++)
1191 if ((menu->modes & modes & (1 << bit)) != 0)
1192 {
1193 msg_putchar('\n');
1194 if (got_int) /* "q" hit for "--more--" */
1195 return;
1196 for (i = 0; i < depth + 2; i++)
Bram Moolenaar32526b32019-01-19 17:43:09 +01001197 msg_puts(" ");
1198 msg_puts(menu_mode_chars[bit]);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001199 if (menu->noremap[bit] == REMAP_NONE)
1200 msg_putchar('*');
1201 else if (menu->noremap[bit] == REMAP_SCRIPT)
1202 msg_putchar('&');
1203 else
1204 msg_putchar(' ');
1205 if (menu->silent[bit])
1206 msg_putchar('s');
1207 else
1208 msg_putchar(' ');
1209 if ((menu->modes & menu->enabled & (1 << bit)) == 0)
1210 msg_putchar('-');
1211 else
1212 msg_putchar(' ');
Bram Moolenaar32526b32019-01-19 17:43:09 +01001213 msg_puts(" ");
Bram Moolenaar071d4272004-06-13 20:20:40 +00001214 if (*menu->strings[bit] == NUL)
Bram Moolenaar32526b32019-01-19 17:43:09 +01001215 msg_puts_attr("<Nop>", HL_ATTR(HLF_8));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001216 else
Bram Moolenaar725310d2019-04-24 23:08:23 +02001217 msg_outtrans_special(menu->strings[bit], FALSE, 0);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001218 }
1219 }
1220 else
1221 {
1222 if (menu == NULL)
1223 {
1224 menu = root_menu;
1225 depth--;
1226 }
1227 else
1228 menu = menu->children;
1229
1230 /* recursively show all children. Skip PopUp[nvoci]. */
1231 for (; menu != NULL && !got_int; menu = menu->next)
1232 if (!menu_is_hidden(menu->dname))
1233 show_menus_recursive(menu, modes, depth + 1);
1234 }
1235}
1236
Bram Moolenaar071d4272004-06-13 20:20:40 +00001237/*
1238 * Used when expanding menu names.
1239 */
1240static vimmenu_T *expand_menu = NULL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001241static vimmenu_T *expand_menu_alt = NULL;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001242static int expand_modes = 0x0;
1243static int expand_emenu; /* TRUE for ":emenu" command */
1244
1245/*
1246 * Work out what to complete when doing command line completion of menu names.
1247 */
1248 char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001249set_context_in_menu_cmd(
1250 expand_T *xp,
1251 char_u *cmd,
1252 char_u *arg,
1253 int forceit)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001254{
1255 char_u *after_dot;
1256 char_u *p;
1257 char_u *path_name = NULL;
1258 char_u *name;
1259 int unmenu;
1260 vimmenu_T *menu;
1261 int expand_menus;
1262
1263 xp->xp_context = EXPAND_UNSUCCESSFUL;
1264
1265
1266 /* Check for priority numbers, enable and disable */
1267 for (p = arg; *p; ++p)
1268 if (!VIM_ISDIGIT(*p) && *p != '.')
1269 break;
1270
Bram Moolenaar1c465442017-03-12 20:10:05 +01001271 if (!VIM_ISWHITE(*p))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001272 {
1273 if (STRNCMP(arg, "enable", 6) == 0
Bram Moolenaar1c465442017-03-12 20:10:05 +01001274 && (arg[6] == NUL || VIM_ISWHITE(arg[6])))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001275 p = arg + 6;
1276 else if (STRNCMP(arg, "disable", 7) == 0
Bram Moolenaar1c465442017-03-12 20:10:05 +01001277 && (arg[7] == NUL || VIM_ISWHITE(arg[7])))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001278 p = arg + 7;
1279 else
1280 p = arg;
1281 }
1282
Bram Moolenaar1c465442017-03-12 20:10:05 +01001283 while (*p != NUL && VIM_ISWHITE(*p))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001284 ++p;
1285
1286 arg = after_dot = p;
1287
Bram Moolenaar1c465442017-03-12 20:10:05 +01001288 for (; *p && !VIM_ISWHITE(*p); ++p)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001289 {
1290 if ((*p == '\\' || *p == Ctrl_V) && p[1] != NUL)
1291 p++;
1292 else if (*p == '.')
1293 after_dot = p + 1;
1294 }
1295
1296 /* ":tearoff" and ":popup" only use menus, not entries */
1297 expand_menus = !((*cmd == 't' && cmd[1] == 'e') || *cmd == 'p');
1298 expand_emenu = (*cmd == 'e');
Bram Moolenaar1c465442017-03-12 20:10:05 +01001299 if (expand_menus && VIM_ISWHITE(*p))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001300 return NULL; /* TODO: check for next command? */
1301 if (*p == NUL) /* Complete the menu name */
1302 {
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001303 int try_alt_menu = TRUE;
1304
Bram Moolenaar071d4272004-06-13 20:20:40 +00001305 /*
1306 * With :unmenu, you only want to match menus for the appropriate mode.
1307 * With :menu though you might want to add a menu with the same name as
1308 * one in another mode, so match menus from other modes too.
1309 */
1310 expand_modes = get_menu_cmd_modes(cmd, forceit, NULL, &unmenu);
1311 if (!unmenu)
1312 expand_modes = MENU_ALL_MODES;
1313
1314 menu = root_menu;
1315 if (after_dot != arg)
1316 {
Bram Moolenaar964b3742019-05-24 18:54:09 +02001317 path_name = alloc(after_dot - arg);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001318 if (path_name == NULL)
1319 return NULL;
Bram Moolenaarce0842a2005-07-18 21:58:11 +00001320 vim_strncpy(path_name, arg, after_dot - arg - 1);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001321 }
1322 name = path_name;
1323 while (name != NULL && *name)
1324 {
1325 p = menu_name_skip(name);
1326 while (menu != NULL)
1327 {
1328 if (menu_name_equal(name, menu))
1329 {
1330 /* Found menu */
1331 if ((*p != NUL && menu->children == NULL)
1332 || ((menu->modes & expand_modes) == 0x0))
1333 {
1334 /*
1335 * Menu path continues, but we have reached a leaf.
1336 * Or menu exists only in another mode.
1337 */
1338 vim_free(path_name);
1339 return NULL;
1340 }
1341 break;
1342 }
1343 menu = menu->next;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001344 if (menu == NULL && try_alt_menu)
1345 {
1346 menu = curwin->w_winbar;
1347 try_alt_menu = FALSE;
1348 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001349 }
1350 if (menu == NULL)
1351 {
1352 /* No menu found with the name we were looking for */
1353 vim_free(path_name);
1354 return NULL;
1355 }
1356 name = p;
1357 menu = menu->children;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001358 try_alt_menu = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001359 }
Bram Moolenaareb3593b2006-04-22 22:33:57 +00001360 vim_free(path_name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001361
1362 xp->xp_context = expand_menus ? EXPAND_MENUNAMES : EXPAND_MENUS;
1363 xp->xp_pattern = after_dot;
1364 expand_menu = menu;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001365 if (expand_menu == root_menu)
1366 expand_menu_alt = curwin->w_winbar;
1367 else
1368 expand_menu_alt = NULL;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001369 }
1370 else /* We're in the mapping part */
1371 xp->xp_context = EXPAND_NOTHING;
1372 return NULL;
1373}
1374
1375/*
1376 * Function given to ExpandGeneric() to obtain the list of (sub)menus (not
1377 * entries).
1378 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001379 char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001380get_menu_name(expand_T *xp UNUSED, int idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001381{
1382 static vimmenu_T *menu = NULL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001383 static int did_alt_menu = FALSE;
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 Moolenaar1b9645d2017-09-17 23:03:31 +02001392 did_alt_menu = FALSE;
Bram Moolenaar41375642010-05-16 12:49:27 +02001393#ifdef FEAT_MULTI_LANG
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001394 should_advance = FALSE;
Bram Moolenaar41375642010-05-16 12:49:27 +02001395#endif
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001396 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001397
1398 /* Skip PopUp[nvoci]. */
1399 while (menu != NULL && (menu_is_hidden(menu->dname)
1400 || menu_is_separator(menu->dname)
1401 || menu_is_tearoff(menu->dname)
1402 || menu->children == NULL))
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001403 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001404 menu = menu->next;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001405 if (menu == NULL && !did_alt_menu)
1406 {
1407 menu = expand_menu_alt;
1408 did_alt_menu = TRUE;
1409 }
1410 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001411
1412 if (menu == NULL) /* at end of linked list */
1413 return NULL;
1414
1415 if (menu->modes & expand_modes)
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001416#ifdef FEAT_MULTI_LANG
1417 if (should_advance)
1418 str = menu->en_dname;
1419 else
1420 {
1421#endif
1422 str = menu->dname;
1423#ifdef FEAT_MULTI_LANG
1424 if (menu->en_dname == NULL)
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001425 should_advance = TRUE;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001426 }
1427#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001428 else
1429 str = (char_u *)"";
1430
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001431#ifdef FEAT_MULTI_LANG
1432 if (should_advance)
1433#endif
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001434 {
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001435 /* Advance to next menu entry. */
1436 menu = menu->next;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001437 if (menu == NULL && !did_alt_menu)
1438 {
1439 menu = expand_menu_alt;
1440 did_alt_menu = TRUE;
1441 }
1442 }
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001443
1444#ifdef FEAT_MULTI_LANG
1445 should_advance = !should_advance;
1446#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001447
1448 return str;
1449}
1450
1451/*
1452 * Function given to ExpandGeneric() to obtain the list of menus and menu
1453 * entries.
1454 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001455 char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001456get_menu_names(expand_T *xp UNUSED, int idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001457{
1458 static vimmenu_T *menu = NULL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001459 static int did_alt_menu = FALSE;
Bram Moolenaaref9d6aa2011-04-11 16:56:35 +02001460#define TBUFFER_LEN 256
1461 static char_u tbuffer[TBUFFER_LEN]; /*hack*/
Bram Moolenaar071d4272004-06-13 20:20:40 +00001462 char_u *str;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001463#ifdef FEAT_MULTI_LANG
1464 static int should_advance = FALSE;
1465#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001466
1467 if (idx == 0) /* first call: start at first item */
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001468 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001469 menu = expand_menu;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001470 did_alt_menu = FALSE;
Bram Moolenaar41375642010-05-16 12:49:27 +02001471#ifdef FEAT_MULTI_LANG
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001472 should_advance = FALSE;
Bram Moolenaar41375642010-05-16 12:49:27 +02001473#endif
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001474 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001475
1476 /* Skip Browse-style entries, popup menus and separators. */
1477 while (menu != NULL
1478 && ( menu_is_hidden(menu->dname)
1479 || (expand_emenu && menu_is_separator(menu->dname))
1480 || menu_is_tearoff(menu->dname)
1481#ifndef FEAT_BROWSE
1482 || menu->dname[STRLEN(menu->dname) - 1] == '.'
1483#endif
1484 ))
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001485 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001486 menu = menu->next;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001487 if (menu == NULL && !did_alt_menu)
1488 {
1489 menu = expand_menu_alt;
1490 did_alt_menu = TRUE;
1491 }
1492 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001493
1494 if (menu == NULL) /* at end of linked list */
1495 return NULL;
1496
1497 if (menu->modes & expand_modes)
1498 {
1499 if (menu->children != NULL)
1500 {
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001501#ifdef FEAT_MULTI_LANG
1502 if (should_advance)
Bram Moolenaaref9d6aa2011-04-11 16:56:35 +02001503 vim_strncpy(tbuffer, menu->en_dname, TBUFFER_LEN - 2);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001504 else
1505 {
1506#endif
Bram Moolenaaref9d6aa2011-04-11 16:56:35 +02001507 vim_strncpy(tbuffer, menu->dname, TBUFFER_LEN - 2);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001508#ifdef FEAT_MULTI_LANG
1509 if (menu->en_dname == NULL)
1510 should_advance = TRUE;
1511 }
1512#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001513 /* hack on menu separators: use a 'magic' char for the separator
1514 * so that '.' in names gets escaped properly */
1515 STRCAT(tbuffer, "\001");
1516 str = tbuffer;
1517 }
1518 else
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001519#ifdef FEAT_MULTI_LANG
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001520 {
1521 if (should_advance)
1522 str = menu->en_dname;
1523 else
1524 {
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001525#endif
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001526 str = menu->dname;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001527#ifdef FEAT_MULTI_LANG
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001528 if (menu->en_dname == NULL)
1529 should_advance = TRUE;
1530 }
1531 }
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001532#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001533 }
1534 else
1535 str = (char_u *)"";
1536
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001537#ifdef FEAT_MULTI_LANG
1538 if (should_advance)
1539#endif
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001540 {
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001541 /* Advance to next menu entry. */
1542 menu = menu->next;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001543 if (menu == NULL && !did_alt_menu)
1544 {
1545 menu = expand_menu_alt;
1546 did_alt_menu = TRUE;
1547 }
1548 }
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001549
1550#ifdef FEAT_MULTI_LANG
1551 should_advance = !should_advance;
1552#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001553
1554 return str;
1555}
Bram Moolenaar071d4272004-06-13 20:20:40 +00001556
1557/*
1558 * Skip over this element of the menu path and return the start of the next
1559 * element. Any \ and ^Vs are removed from the current element.
Bram Moolenaar342337a2005-07-21 21:11:17 +00001560 * "name" may be modified.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001561 */
Bram Moolenaar5843f5f2019-08-20 20:13:45 +02001562 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001563menu_name_skip(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001564{
1565 char_u *p;
1566
Bram Moolenaar91acfff2017-03-12 19:22:36 +01001567 for (p = name; *p && *p != '.'; MB_PTR_ADV(p))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001568 {
1569 if (*p == '\\' || *p == Ctrl_V)
1570 {
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001571 STRMOVE(p, p + 1);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001572 if (*p == NUL)
1573 break;
1574 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001575 }
1576 if (*p)
1577 *p++ = NUL;
1578 return p;
1579}
1580
1581/*
1582 * Return TRUE when "name" matches with menu "menu". The name is compared in
1583 * two ways: raw menu name and menu name without '&'. ignore part after a TAB.
1584 */
1585 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001586menu_name_equal(char_u *name, vimmenu_T *menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001587{
Bram Moolenaar41375642010-05-16 12:49:27 +02001588#ifdef FEAT_MULTI_LANG
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001589 if (menu->en_name != NULL
Bram Moolenaard91f7042011-01-04 17:49:32 +01001590 && (menu_namecmp(name, menu->en_name)
1591 || menu_namecmp(name, menu->en_dname)))
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001592 return TRUE;
Bram Moolenaar41375642010-05-16 12:49:27 +02001593#endif
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001594 return menu_namecmp(name, menu->name) || menu_namecmp(name, menu->dname);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001595}
1596
1597 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001598menu_namecmp(char_u *name, char_u *mname)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001599{
1600 int i;
1601
1602 for (i = 0; name[i] != NUL && name[i] != TAB; ++i)
1603 if (name[i] != mname[i])
1604 break;
1605 return ((name[i] == NUL || name[i] == TAB)
1606 && (mname[i] == NUL || mname[i] == TAB));
1607}
1608
1609/*
1610 * Return the modes specified by the given menu command (eg :menu! returns
1611 * MENU_CMDLINE_MODE | MENU_INSERT_MODE).
1612 * If "noremap" is not NULL, then the flag it points to is set according to
1613 * whether the command is a "nore" command.
1614 * If "unmenu" is not NULL, then the flag it points to is set according to
1615 * whether the command is an "unmenu" command.
1616 */
1617 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001618get_menu_cmd_modes(
1619 char_u *cmd,
1620 int forceit, /* Was there a "!" after the command? */
1621 int *noremap,
1622 int *unmenu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001623{
1624 int modes;
1625
1626 switch (*cmd++)
1627 {
1628 case 'v': /* vmenu, vunmenu, vnoremenu */
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001629 modes = MENU_VISUAL_MODE | MENU_SELECT_MODE;
1630 break;
1631 case 'x': /* xmenu, xunmenu, xnoremenu */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001632 modes = MENU_VISUAL_MODE;
1633 break;
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001634 case 's': /* smenu, sunmenu, snoremenu */
1635 modes = MENU_SELECT_MODE;
1636 break;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001637 case 'o': /* omenu */
1638 modes = MENU_OP_PENDING_MODE;
1639 break;
1640 case 'i': /* imenu */
1641 modes = MENU_INSERT_MODE;
1642 break;
1643 case 't':
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001644 if (*cmd == 'l') /* tlmenu, tlunmenu, tlnoremenu */
1645 {
1646 modes = MENU_TERMINAL_MODE;
1647 ++cmd;
1648 break;
1649 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001650 modes = MENU_TIP_MODE; /* tmenu */
1651 break;
1652 case 'c': /* cmenu */
1653 modes = MENU_CMDLINE_MODE;
1654 break;
1655 case 'a': /* amenu */
1656 modes = MENU_INSERT_MODE | MENU_CMDLINE_MODE | MENU_NORMAL_MODE
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001657 | MENU_VISUAL_MODE | MENU_SELECT_MODE
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00001658 | MENU_OP_PENDING_MODE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001659 break;
1660 case 'n':
1661 if (*cmd != 'o') /* nmenu, not noremenu */
1662 {
1663 modes = MENU_NORMAL_MODE;
1664 break;
1665 }
1666 /* FALLTHROUGH */
1667 default:
1668 --cmd;
1669 if (forceit) /* menu!! */
1670 modes = MENU_INSERT_MODE | MENU_CMDLINE_MODE;
1671 else /* menu */
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001672 modes = MENU_NORMAL_MODE | MENU_VISUAL_MODE | MENU_SELECT_MODE
Bram Moolenaar071d4272004-06-13 20:20:40 +00001673 | MENU_OP_PENDING_MODE;
1674 }
1675
1676 if (noremap != NULL)
1677 *noremap = (*cmd == 'n' ? REMAP_NONE : REMAP_YES);
1678 if (unmenu != NULL)
1679 *unmenu = (*cmd == 'u');
1680 return modes;
1681}
1682
1683/*
1684 * Modify a menu name starting with "PopUp" to include the mode character.
1685 * Returns the name in allocated memory (NULL for failure).
1686 */
1687 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001688popup_mode_name(char_u *name, int idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001689{
1690 char_u *p;
1691 int len = (int)STRLEN(name);
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001692 char *mode_chars = menu_mode_chars[idx];
1693 int mode_chars_len = (int)strlen(mode_chars);
1694 int i;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001695
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001696 p = vim_strnsave(name, len + mode_chars_len);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001697 if (p != NULL)
1698 {
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001699 mch_memmove(p + 5 + mode_chars_len, p + 5, (size_t)(len - 4));
1700 for (i = 0; i < mode_chars_len; ++i)
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001701 p[5 + i] = menu_mode_chars[idx][i];
Bram Moolenaar071d4272004-06-13 20:20:40 +00001702 }
1703 return p;
1704}
1705
1706#if defined(FEAT_GUI) || defined(PROTO)
1707/*
1708 * Return the index into the menu->strings or menu->noremap arrays for the
1709 * current state. Returns MENU_INDEX_INVALID if there is no mapping for the
1710 * given menu in the current mode.
1711 */
1712 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001713get_menu_index(vimmenu_T *menu, int state)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001714{
1715 int idx;
1716
1717 if ((state & INSERT))
1718 idx = MENU_INDEX_INSERT;
1719 else if (state & CMDLINE)
1720 idx = MENU_INDEX_CMDLINE;
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001721#ifdef FEAT_TERMINAL
1722 else if (term_use_loop())
1723 idx = MENU_INDEX_TERMINAL;
1724#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001725 else if (VIsual_active)
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001726 {
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00001727 if (VIsual_select)
1728 idx = MENU_INDEX_SELECT;
1729 else
1730 idx = MENU_INDEX_VISUAL;
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001731 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001732 else if (state == HITRETURN || state == ASKMORE)
1733 idx = MENU_INDEX_CMDLINE;
1734 else if (finish_op)
1735 idx = MENU_INDEX_OP_PENDING;
1736 else if ((state & NORMAL))
1737 idx = MENU_INDEX_NORMAL;
1738 else
1739 idx = MENU_INDEX_INVALID;
1740
1741 if (idx != MENU_INDEX_INVALID && menu->strings[idx] == NULL)
1742 idx = MENU_INDEX_INVALID;
1743 return idx;
1744}
1745#endif
1746
1747/*
1748 * Duplicate the menu item text and then process to see if a mnemonic key
1749 * and/or accelerator text has been identified.
1750 * Returns a pointer to allocated memory, or NULL for failure.
1751 * If mnemonic != NULL, *mnemonic is set to the character after the first '&'.
1752 * If actext != NULL, *actext is set to the text after the first TAB.
1753 */
1754 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001755menu_text(char_u *str, int *mnemonic, char_u **actext)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001756{
1757 char_u *p;
1758 char_u *text;
1759
1760 /* Locate accelerator text, after the first TAB */
1761 p = vim_strchr(str, TAB);
1762 if (p != NULL)
1763 {
1764 if (actext != NULL)
1765 *actext = vim_strsave(p + 1);
1766 text = vim_strnsave(str, (int)(p - str));
1767 }
1768 else
1769 text = vim_strsave(str);
1770
1771 /* Find mnemonic characters "&a" and reduce "&&" to "&". */
1772 for (p = text; p != NULL; )
1773 {
1774 p = vim_strchr(p, '&');
1775 if (p != NULL)
1776 {
1777 if (p[1] == NUL) /* trailing "&" */
1778 break;
1779 if (mnemonic != NULL && p[1] != '&')
1780#if !defined(__MVS__) || defined(MOTIF390_MNEMONIC_FIXED)
1781 *mnemonic = p[1];
1782#else
1783 {
1784 /*
1785 * Well there is a bug in the Motif libraries on OS390 Unix.
1786 * The mnemonic keys needs to be converted to ASCII values
1787 * first.
1788 * This behavior has been seen in 2.8 and 2.9.
1789 */
1790 char c = p[1];
1791 __etoa_l(&c, 1);
1792 *mnemonic = c;
1793 }
1794#endif
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001795 STRMOVE(p, p + 1);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001796 p = p + 1;
1797 }
1798 }
1799 return text;
1800}
1801
1802/*
1803 * Return TRUE if "name" can be a menu in the MenuBar.
1804 */
1805 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001806menu_is_menubar(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001807{
1808 return (!menu_is_popup(name)
1809 && !menu_is_toolbar(name)
Bram Moolenaar378daf82017-09-23 23:58:28 +02001810 && !menu_is_winbar(name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001811 && *name != MNU_HIDDEN_CHAR);
1812}
1813
1814/*
1815 * Return TRUE if "name" is a popup menu name.
1816 */
1817 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001818menu_is_popup(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001819{
1820 return (STRNCMP(name, "PopUp", 5) == 0);
1821}
1822
1823#if (defined(FEAT_GUI_MOTIF) && (XmVersion <= 1002)) || defined(PROTO)
1824/*
1825 * Return TRUE if "name" is part of a popup menu.
1826 */
1827 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001828menu_is_child_of_popup(vimmenu_T *menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001829{
1830 while (menu->parent != NULL)
1831 menu = menu->parent;
1832 return menu_is_popup(menu->name);
1833}
1834#endif
1835
1836/*
1837 * Return TRUE if "name" is a toolbar menu name.
1838 */
1839 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001840menu_is_toolbar(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001841{
1842 return (STRNCMP(name, "ToolBar", 7) == 0);
1843}
1844
1845/*
1846 * Return TRUE if the name is a menu separator identifier: Starts and ends
1847 * with '-'
1848 */
1849 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001850menu_is_separator(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001851{
1852 return (name[0] == '-' && name[STRLEN(name) - 1] == '-');
1853}
1854
1855/*
1856 * Return TRUE if the menu is hidden: Starts with ']'
1857 */
1858 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001859menu_is_hidden(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001860{
1861 return (name[0] == ']') || (menu_is_popup(name) && name[5] != NUL);
1862}
1863
Bram Moolenaar071d4272004-06-13 20:20:40 +00001864/*
1865 * Return TRUE if the menu is the tearoff menu.
1866 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001867 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001868menu_is_tearoff(char_u *name UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001869{
1870#ifdef FEAT_GUI
1871 return (STRCMP(name, TEAR_STRING) == 0);
1872#else
1873 return FALSE;
1874#endif
1875}
Bram Moolenaar071d4272004-06-13 20:20:40 +00001876
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001877#if defined(FEAT_GUI) || defined(FEAT_TERM_POPUP_MENU) || defined(PROTO)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001878
1879 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001880get_menu_mode(void)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001881{
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001882#ifdef FEAT_TERMINAL
1883 if (term_use_loop())
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001884 return MENU_INDEX_TERMINAL;
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001885#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001886 if (VIsual_active)
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001887 {
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00001888 if (VIsual_select)
1889 return MENU_INDEX_SELECT;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001890 return MENU_INDEX_VISUAL;
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001891 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001892 if (State & INSERT)
1893 return MENU_INDEX_INSERT;
1894 if ((State & CMDLINE) || State == ASKMORE || State == HITRETURN)
1895 return MENU_INDEX_CMDLINE;
1896 if (finish_op)
1897 return MENU_INDEX_OP_PENDING;
1898 if (State & NORMAL)
1899 return MENU_INDEX_NORMAL;
1900 if (State & LANGMAP) /* must be a "r" command, like Insert mode */
1901 return MENU_INDEX_INSERT;
1902 return MENU_INDEX_INVALID;
1903}
1904
Bram Moolenaar29a2c082018-03-05 21:06:23 +01001905 int
1906get_menu_mode_flag(void)
1907{
1908 int mode = get_menu_mode();
1909
1910 if (mode == MENU_INDEX_INVALID)
1911 return 0;
1912 return 1 << mode;
1913}
1914
Bram Moolenaar071d4272004-06-13 20:20:40 +00001915/*
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001916 * Display the Special "PopUp" menu as a pop-up at the current mouse
1917 * position. The "PopUpn" menu is for Normal mode, "PopUpi" for Insert mode,
1918 * etc.
1919 */
1920 void
1921show_popupmenu(void)
1922{
1923 vimmenu_T *menu;
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001924 int menu_mode;
1925 char* mode;
1926 int mode_len;
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001927
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001928 menu_mode = get_menu_mode();
1929 if (menu_mode == MENU_INDEX_INVALID)
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001930 return;
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001931 mode = menu_mode_chars[menu_mode];
1932 mode_len = (int)strlen(mode);
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001933
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001934 apply_autocmds(EVENT_MENUPOPUP, (char_u*)mode, NULL, FALSE, curbuf);
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001935
1936 for (menu = root_menu; menu != NULL; menu = menu->next)
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001937 if (STRNCMP("PopUp", menu->name, 5) == 0 && STRNCMP(menu->name + 5, mode, mode_len) == 0)
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001938 break;
1939
1940 /* Only show a popup when it is defined and has entries */
1941 if (menu != NULL && menu->children != NULL)
1942 {
1943# if defined(FEAT_GUI)
1944 if (gui.in_use)
1945 {
1946 /* Update the menus now, in case the MenuPopup autocommand did
1947 * anything. */
1948 gui_update_menus(0);
1949 gui_mch_show_popupmenu(menu);
1950 }
1951# endif
1952# if defined(FEAT_GUI) && defined(FEAT_TERM_POPUP_MENU)
1953 else
1954# endif
1955# if defined(FEAT_TERM_POPUP_MENU)
1956 pum_show_popupmenu(menu);
1957# endif
1958 }
1959}
1960#endif
1961
1962#if defined(FEAT_GUI) || defined(PROTO)
1963
1964/*
Bram Moolenaar968bbbe2006-08-16 19:41:08 +00001965 * Check that a pointer appears in the menu tree. Used to protect from using
1966 * a menu that was deleted after it was selected but before the event was
1967 * handled.
1968 * Return OK or FAIL. Used recursively.
1969 */
1970 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001971check_menu_pointer(vimmenu_T *root, vimmenu_T *menu_to_check)
Bram Moolenaar968bbbe2006-08-16 19:41:08 +00001972{
1973 vimmenu_T *p;
1974
1975 for (p = root; p != NULL; p = p->next)
1976 if (p == menu_to_check
1977 || (p->children != NULL
1978 && check_menu_pointer(p->children, menu_to_check) == OK))
1979 return OK;
1980 return FAIL;
1981}
1982
1983/*
Bram Moolenaar071d4272004-06-13 20:20:40 +00001984 * After we have started the GUI, then we can create any menus that have been
1985 * defined. This is done once here. add_menu_path() may have already been
1986 * called to define these menus, and may be called again. This function calls
1987 * itself recursively. Should be called at the top level with:
Bram Moolenaara06ecab2016-07-16 14:47:36 +02001988 * gui_create_initial_menus(root_menu);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001989 */
1990 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001991gui_create_initial_menus(vimmenu_T *menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001992{
1993 int idx = 0;
1994
1995 while (menu != NULL)
1996 {
1997 /* Don't add a menu when only a tip was defined. */
1998 if (menu->modes & MENU_ALL_MODES)
1999 {
2000 if (menu->children != NULL)
2001 {
2002 gui_mch_add_menu(menu, idx);
2003 gui_create_initial_menus(menu->children);
2004 }
2005 else
2006 gui_mch_add_menu_item(menu, idx);
2007 }
2008 menu = menu->next;
2009 ++idx;
2010 }
2011}
2012
2013/*
2014 * Used recursively by gui_update_menus (see below)
2015 */
2016 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002017gui_update_menus_recurse(vimmenu_T *menu, int mode)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002018{
2019 int grey;
2020
2021 while (menu)
2022 {
2023 if ((menu->modes & menu->enabled & mode)
Bram Moolenaar4f974752019-02-17 17:44:42 +01002024# if defined(FEAT_GUI_MSWIN) && defined(FEAT_TEAROFF)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002025 || menu_is_tearoff(menu->dname)
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002026# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002027 )
2028 grey = FALSE;
2029 else
2030 grey = TRUE;
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002031# ifdef FEAT_GUI_ATHENA
Bram Moolenaar071d4272004-06-13 20:20:40 +00002032 /* Hiding menus doesn't work for Athena, it can cause a crash. */
2033 gui_mch_menu_grey(menu, grey);
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002034# else
Bram Moolenaar071d4272004-06-13 20:20:40 +00002035 /* Never hide a toplevel menu, it may make the menubar resize or
2036 * disappear. Same problem for ToolBar items. */
2037 if (vim_strchr(p_go, GO_GREY) != NULL || menu->parent == NULL
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002038# ifdef FEAT_TOOLBAR
Bram Moolenaar071d4272004-06-13 20:20:40 +00002039 || menu_is_toolbar(menu->parent->name)
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002040# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002041 )
2042 gui_mch_menu_grey(menu, grey);
2043 else
2044 gui_mch_menu_hidden(menu, grey);
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002045# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002046 gui_update_menus_recurse(menu->children, mode);
2047 menu = menu->next;
2048 }
2049}
2050
2051/*
2052 * Make sure only the valid menu items appear for this mode. If
2053 * force_menu_update is not TRUE, then we only do this if the mode has changed
2054 * since last time. If "modes" is not 0, then we use these modes instead.
2055 */
2056 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002057gui_update_menus(int modes)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002058{
2059 static int prev_mode = -1;
2060 int mode = 0;
2061
2062 if (modes != 0x0)
2063 mode = modes;
2064 else
Bram Moolenaar29a2c082018-03-05 21:06:23 +01002065 mode = get_menu_mode_flag();
Bram Moolenaar071d4272004-06-13 20:20:40 +00002066
2067 if (force_menu_update || mode != prev_mode)
2068 {
2069 gui_update_menus_recurse(root_menu, mode);
2070 gui_mch_draw_menubar();
2071 prev_mode = mode;
2072 force_menu_update = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002073 }
2074}
2075
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002076# if defined(FEAT_GUI_MSWIN) || defined(FEAT_GUI_MOTIF) \
Bram Moolenaar241a8aa2005-12-06 20:04:44 +00002077 || defined(FEAT_GUI_GTK) || defined(FEAT_GUI_PHOTON) || defined(PROTO)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002078/*
2079 * Check if a key is used as a mnemonic for a toplevel menu.
2080 * Case of the key is ignored.
2081 */
2082 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002083gui_is_menu_shortcut(int key)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002084{
2085 vimmenu_T *menu;
2086
2087 if (key < 256)
2088 key = TOLOWER_LOC(key);
2089 for (menu = root_menu; menu != NULL; menu = menu->next)
2090 if (menu->mnemonic == key
2091 || (menu->mnemonic < 256 && TOLOWER_LOC(menu->mnemonic) == key))
2092 return TRUE;
2093 return FALSE;
2094}
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002095# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002096#endif /* FEAT_GUI */
2097
Bram Moolenaar4f974752019-02-17 17:44:42 +01002098#if (defined(FEAT_GUI_MSWIN) && defined(FEAT_TEAROFF)) || defined(PROTO)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002099
2100/*
2101 * Deal with tearoff items that are added like a menu item.
2102 * Currently only for Win32 GUI. Others may follow later.
2103 */
2104
2105 void
2106gui_mch_toggle_tearoffs(int enable)
2107{
2108 int pri_tab[MENUDEPTH + 1];
2109 int i;
2110
2111 if (enable)
2112 {
2113 for (i = 0; i < MENUDEPTH; ++i)
2114 pri_tab[i] = 500;
2115 pri_tab[MENUDEPTH] = -1;
2116 gui_create_tearoffs_recurse(root_menu, (char_u *)"", pri_tab, 0);
2117 }
2118 else
2119 gui_destroy_tearoffs_recurse(root_menu);
2120 s_tearoffs = enable;
2121}
2122
2123/*
2124 * Recursively add tearoff items
2125 */
2126 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002127gui_create_tearoffs_recurse(
2128 vimmenu_T *menu,
2129 const char_u *pname,
2130 int *pri_tab,
2131 int pri_idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002132{
2133 char_u *newpname = NULL;
2134 int len;
2135 char_u *s;
2136 char_u *d;
2137
2138 if (pri_tab[pri_idx + 1] != -1)
2139 ++pri_idx;
2140 while (menu != NULL)
2141 {
2142 if (menu->children != NULL && menu_is_menubar(menu->name))
2143 {
2144 /* Add the menu name to the menu path. Insert a backslash before
2145 * dots (it's used to separate menu names). */
2146 len = (int)STRLEN(pname) + (int)STRLEN(menu->name);
2147 for (s = menu->name; *s; ++s)
2148 if (*s == '.' || *s == '\\')
2149 ++len;
2150 newpname = alloc(len + TEAR_LEN + 2);
2151 if (newpname != NULL)
2152 {
2153 STRCPY(newpname, pname);
2154 d = newpname + STRLEN(newpname);
2155 for (s = menu->name; *s; ++s)
2156 {
2157 if (*s == '.' || *s == '\\')
2158 *d++ = '\\';
2159 *d++ = *s;
2160 }
2161 *d = NUL;
2162
2163 /* check if tearoff already exists */
2164 if (STRCMP(menu->children->name, TEAR_STRING) != 0)
2165 {
2166 gui_add_tearoff(newpname, pri_tab, pri_idx - 1);
2167 *d = NUL; /* remove TEAR_STRING */
2168 }
2169
2170 STRCAT(newpname, ".");
2171 gui_create_tearoffs_recurse(menu->children, newpname,
2172 pri_tab, pri_idx);
2173 vim_free(newpname);
2174 }
2175 }
2176 menu = menu->next;
2177 }
2178}
2179
2180/*
2181 * Add tear-off menu item for a submenu.
2182 * "tearpath" is the menu path, and must have room to add TEAR_STRING.
2183 */
2184 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002185gui_add_tearoff(char_u *tearpath, int *pri_tab, int pri_idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002186{
2187 char_u *tbuf;
2188 int t;
2189 vimmenu_T menuarg;
2190
2191 tbuf = alloc(5 + (unsigned int)STRLEN(tearpath));
2192 if (tbuf != NULL)
2193 {
2194 tbuf[0] = K_SPECIAL;
2195 tbuf[1] = K_SECOND(K_TEAROFF);
2196 tbuf[2] = K_THIRD(K_TEAROFF);
2197 STRCPY(tbuf + 3, tearpath);
2198 STRCAT(tbuf + 3, "\r");
2199
2200 STRCAT(tearpath, ".");
2201 STRCAT(tearpath, TEAR_STRING);
2202
2203 /* Priority of tear-off is always 1 */
2204 t = pri_tab[pri_idx + 1];
2205 pri_tab[pri_idx + 1] = 1;
2206
2207#ifdef FEAT_TOOLBAR
2208 menuarg.iconfile = NULL;
2209 menuarg.iconidx = -1;
2210 menuarg.icon_builtin = FALSE;
2211#endif
2212 menuarg.noremap[0] = REMAP_NONE;
2213 menuarg.silent[0] = TRUE;
2214
2215 menuarg.modes = MENU_ALL_MODES;
2216 add_menu_path(tearpath, &menuarg, pri_tab, tbuf, FALSE);
2217
2218 menuarg.modes = MENU_TIP_MODE;
2219 add_menu_path(tearpath, &menuarg, pri_tab,
2220 (char_u *)_("Tear off this menu"), FALSE);
2221
2222 pri_tab[pri_idx + 1] = t;
2223 vim_free(tbuf);
2224 }
2225}
2226
2227/*
2228 * Recursively destroy tearoff items
2229 */
2230 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002231gui_destroy_tearoffs_recurse(vimmenu_T *menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002232{
2233 while (menu)
2234 {
2235 if (menu->children)
2236 {
2237 /* check if tearoff exists */
2238 if (STRCMP(menu->children->name, TEAR_STRING) == 0)
2239 {
2240 /* Disconnect the item and free the memory */
2241 free_menu(&menu->children);
2242 }
2243 if (menu->children != NULL) /* if not the last one */
2244 gui_destroy_tearoffs_recurse(menu->children);
2245 }
2246 menu = menu->next;
2247 }
2248}
2249
Bram Moolenaar4f974752019-02-17 17:44:42 +01002250#endif /* FEAT_GUI_MSWIN && FEAT_TEAROFF */
Bram Moolenaar071d4272004-06-13 20:20:40 +00002251
2252/*
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002253 * Execute "menu". Use by ":emenu" and the window toolbar.
2254 * "eap" is NULL for the window toolbar.
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002255 * "mode_idx" specifies a MENU_INDEX_ value, use -1 to depend on the current
2256 * state.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002257 */
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002258 void
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002259execute_menu(exarg_T *eap, vimmenu_T *menu, int mode_idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002260{
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002261 int idx = mode_idx;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002262
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002263 if (idx < 0)
2264 {
2265 /* Use the Insert mode entry when returning to Insert mode. */
2266 if (restart_edit
Bram Moolenaar4463f292005-09-25 22:20:24 +00002267#ifdef FEAT_EVAL
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002268 && !current_sctx.sc_sid
Bram Moolenaar4463f292005-09-25 22:20:24 +00002269#endif
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002270 )
Bram Moolenaar071d4272004-06-13 20:20:40 +00002271 {
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002272 idx = MENU_INDEX_INSERT;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002273 }
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002274#ifdef FEAT_TERMINAL
2275 else if (term_use_loop())
Bram Moolenaar071d4272004-06-13 20:20:40 +00002276 {
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002277 idx = MENU_INDEX_TERMINAL;
2278 }
2279#endif
2280 else if (VIsual_active)
2281 {
2282 idx = MENU_INDEX_VISUAL;
2283 }
2284 else if (eap != NULL && eap->addr_count)
2285 {
2286 pos_T tpos;
2287
2288 idx = MENU_INDEX_VISUAL;
2289
2290 /* GEDDES: This is not perfect - but it is a
2291 * quick way of detecting whether we are doing this from a
2292 * selection - see if the range matches up with the visual
2293 * select start and end. */
2294 if ((curbuf->b_visual.vi_start.lnum == eap->line1)
2295 && (curbuf->b_visual.vi_end.lnum) == eap->line2)
2296 {
2297 /* Set it up for visual mode - equivalent to gv. */
2298 VIsual_mode = curbuf->b_visual.vi_mode;
2299 tpos = curbuf->b_visual.vi_end;
2300 curwin->w_cursor = curbuf->b_visual.vi_start;
2301 curwin->w_curswant = curbuf->b_visual.vi_curswant;
2302 }
2303 else
2304 {
2305 /* Set it up for line-wise visual mode */
2306 VIsual_mode = 'V';
2307 curwin->w_cursor.lnum = eap->line1;
2308 curwin->w_cursor.col = 1;
2309 tpos.lnum = eap->line2;
2310 tpos.col = MAXCOL;
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002311 tpos.coladd = 0;
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002312 }
2313
2314 /* Activate visual mode */
2315 VIsual_active = TRUE;
2316 VIsual_reselect = TRUE;
2317 check_cursor();
2318 VIsual = curwin->w_cursor;
2319 curwin->w_cursor = tpos;
2320
2321 check_cursor();
2322
2323 /* Adjust the cursor to make sure it is in the correct pos
2324 * for exclusive mode */
2325 if (*p_sel == 'e' && gchar_cursor() != NUL)
2326 ++curwin->w_cursor.col;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002327 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00002328 }
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002329
2330 /* For the WinBar menu always use the Normal mode menu. */
2331 if (idx == -1 || eap == NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002332 idx = MENU_INDEX_NORMAL;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002333
Bram Moolenaarce793532019-05-05 14:19:20 +02002334 if (idx != MENU_INDEX_INVALID && menu->strings[idx] != NULL
2335 && (menu->modes & (1 << idx)))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002336 {
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002337 /* When executing a script or function execute the commands right now.
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002338 * Also for the window toolbar.
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002339 * Otherwise put them in the typeahead buffer. */
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002340 if (eap == NULL
Bram Moolenaar9c4b4ab2006-12-05 20:29:56 +00002341#ifdef FEAT_EVAL
Bram Moolenaarf29c1c62018-09-10 21:05:02 +02002342 || current_sctx.sc_sid != 0
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002343#endif
2344 )
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002345 {
2346 save_state_T save_state;
2347
2348 ++ex_normal_busy;
2349 if (save_current_state(&save_state))
2350 exec_normal_cmd(menu->strings[idx], menu->noremap[idx],
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002351 menu->silent[idx]);
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002352 restore_current_state(&save_state);
2353 --ex_normal_busy;
2354 }
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002355 else
2356 ins_typebuf(menu->strings[idx], menu->noremap[idx], 0,
Bram Moolenaar071d4272004-06-13 20:20:40 +00002357 TRUE, menu->silent[idx]);
2358 }
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002359 else if (eap != NULL)
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002360 {
2361 char_u *mode;
2362
2363 switch (idx)
2364 {
2365 case MENU_INDEX_VISUAL:
2366 mode = (char_u *)"Visual";
2367 break;
2368 case MENU_INDEX_SELECT:
2369 mode = (char_u *)"Select";
2370 break;
2371 case MENU_INDEX_OP_PENDING:
2372 mode = (char_u *)"Op-pending";
2373 break;
2374 case MENU_INDEX_TERMINAL:
2375 mode = (char_u *)"Terminal";
2376 break;
2377 case MENU_INDEX_INSERT:
2378 mode = (char_u *)"Insert";
2379 break;
2380 case MENU_INDEX_CMDLINE:
2381 mode = (char_u *)"Cmdline";
2382 break;
2383 // case MENU_INDEX_TIP: cannot happen
2384 default:
2385 mode = (char_u *)"Normal";
2386 }
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01002387 semsg(_("E335: Menu not defined for %s mode"), mode);
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002388 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00002389}
2390
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002391/*
2392 * Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy and
2393 * execute it.
2394 */
2395 void
2396ex_emenu(exarg_T *eap)
2397{
2398 vimmenu_T *menu;
2399 char_u *name;
2400 char_u *saved_name;
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002401 char_u *arg = eap->arg;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002402 char_u *p;
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002403 int gave_emsg = FALSE;
2404 int mode_idx = -1;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002405
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002406 if (arg[0] && VIM_ISWHITE(arg[1]))
2407 {
2408 switch (arg[0])
2409 {
2410 case 'n': mode_idx = MENU_INDEX_NORMAL; break;
2411 case 'v': mode_idx = MENU_INDEX_VISUAL; break;
2412 case 's': mode_idx = MENU_INDEX_SELECT; break;
2413 case 'o': mode_idx = MENU_INDEX_OP_PENDING; break;
2414 case 't': mode_idx = MENU_INDEX_TERMINAL; break;
2415 case 'i': mode_idx = MENU_INDEX_INSERT; break;
2416 case 'c': mode_idx = MENU_INDEX_CMDLINE; break;
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01002417 default: semsg(_(e_invarg2), arg);
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002418 return;
2419 }
2420 arg = skipwhite(arg + 2);
2421 }
2422
2423 saved_name = vim_strsave(arg);
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002424 if (saved_name == NULL)
2425 return;
2426
2427 menu = *get_root_menu(saved_name);
2428 name = saved_name;
2429 while (*name)
2430 {
2431 /* Find in the menu hierarchy */
2432 p = menu_name_skip(name);
2433
2434 while (menu != NULL)
2435 {
2436 if (menu_name_equal(name, menu))
2437 {
2438 if (*p == NUL && menu->children != NULL)
2439 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01002440 emsg(_("E333: Menu path must lead to a menu item"));
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002441 gave_emsg = TRUE;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002442 menu = NULL;
2443 }
2444 else if (*p != NUL && menu->children == NULL)
2445 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01002446 emsg(_(e_notsubmenu));
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002447 menu = NULL;
2448 }
2449 break;
2450 }
2451 menu = menu->next;
2452 }
2453 if (menu == NULL || *p == NUL)
2454 break;
2455 menu = menu->children;
2456 name = p;
2457 }
2458 vim_free(saved_name);
2459 if (menu == NULL)
2460 {
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002461 if (!gave_emsg)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01002462 semsg(_("E334: Menu not found: %s"), arg);
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002463 return;
2464 }
2465
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002466 // Found the menu, so execute.
2467 execute_menu(eap, menu, mode_idx);
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002468}
2469
2470/*
2471 * Handle a click in the window toolbar of "wp" at column "col".
2472 */
2473 void
2474winbar_click(win_T *wp, int col)
2475{
2476 int idx;
2477
2478 if (wp->w_winbar_items == NULL)
2479 return;
2480 for (idx = 0; wp->w_winbar_items[idx].wb_menu != NULL; ++idx)
2481 {
2482 winbar_item_T *item = &wp->w_winbar_items[idx];
2483
2484 if (col >= item->wb_startcol && col <= item->wb_endcol)
2485 {
Bram Moolenaard2fad672019-05-04 16:55:25 +02002486 win_T *save_curwin = NULL;
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002487 pos_T save_visual = VIsual;
2488 int save_visual_active = VIsual_active;
2489 int save_visual_select = VIsual_select;
2490 int save_visual_reselect = VIsual_reselect;
2491 int save_visual_mode = VIsual_mode;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002492
2493 if (wp != curwin)
2494 {
2495 /* Clicking in the window toolbar of a not-current window.
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002496 * Make that window the current one and save Visual mode. */
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002497 save_curwin = curwin;
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002498 VIsual_active = FALSE;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002499 curwin = wp;
2500 curbuf = curwin->w_buffer;
2501 check_cursor();
2502 }
2503
Bram Moolenaard2fad672019-05-04 16:55:25 +02002504 // Note: the command might close the current window.
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002505 execute_menu(NULL, item->wb_menu, -1);
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002506
Bram Moolenaard2fad672019-05-04 16:55:25 +02002507 if (save_curwin != NULL && win_valid(save_curwin))
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002508 {
2509 curwin = save_curwin;
2510 curbuf = curwin->w_buffer;
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002511 VIsual = save_visual;
2512 VIsual_active = save_visual_active;
2513 VIsual_select = save_visual_select;
2514 VIsual_reselect = save_visual_reselect;
2515 VIsual_mode = save_visual_mode;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002516 }
Bram Moolenaard2fad672019-05-04 16:55:25 +02002517 if (!win_valid(wp))
2518 break;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002519 }
2520 }
2521}
2522
2523#if defined(FEAT_GUI_MSWIN) || defined(FEAT_GUI_GTK) \
Bram Moolenaar40d77b02018-03-05 21:32:27 +01002524 || defined(FEAT_TERM_POPUP_MENU) \
Bram Moolenaar071d4272004-06-13 20:20:40 +00002525 || defined(FEAT_BEVAL_TIP) || defined(PROTO)
2526/*
2527 * Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy.
2528 */
2529 vimmenu_T *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002530gui_find_menu(char_u *path_name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002531{
2532 vimmenu_T *menu = NULL;
2533 char_u *name;
2534 char_u *saved_name;
2535 char_u *p;
2536
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002537 menu = *get_root_menu(path_name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002538
2539 saved_name = vim_strsave(path_name);
2540 if (saved_name == NULL)
2541 return NULL;
2542
2543 name = saved_name;
2544 while (*name)
2545 {
2546 /* find the end of one dot-separated name and put a NUL at the dot */
2547 p = menu_name_skip(name);
2548
2549 while (menu != NULL)
2550 {
Bram Moolenaard91f7042011-01-04 17:49:32 +01002551 if (menu_name_equal(name, menu))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002552 {
2553 if (menu->children == NULL)
2554 {
2555 /* found a menu item instead of a sub-menu */
2556 if (*p == NUL)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01002557 emsg(_("E336: Menu path must lead to a sub-menu"));
Bram Moolenaar071d4272004-06-13 20:20:40 +00002558 else
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01002559 emsg(_(e_notsubmenu));
Bram Moolenaar071d4272004-06-13 20:20:40 +00002560 menu = NULL;
2561 goto theend;
2562 }
2563 if (*p == NUL) /* found a full match */
2564 goto theend;
2565 break;
2566 }
2567 menu = menu->next;
2568 }
2569 if (menu == NULL) /* didn't find it */
2570 break;
2571
2572 /* Found a match, search the sub-menu. */
2573 menu = menu->children;
2574 name = p;
2575 }
2576
2577 if (menu == NULL)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01002578 emsg(_("E337: Menu not found - check menu names"));
Bram Moolenaar071d4272004-06-13 20:20:40 +00002579theend:
2580 vim_free(saved_name);
2581 return menu;
2582}
2583#endif
2584
2585#ifdef FEAT_MULTI_LANG
2586/*
2587 * Translation of menu names. Just a simple lookup table.
2588 */
2589
2590typedef struct
2591{
2592 char_u *from; /* English name */
2593 char_u *from_noamp; /* same, without '&' */
2594 char_u *to; /* translated name */
2595} menutrans_T;
2596
2597static garray_T menutrans_ga = {0, 0, 0, 0, NULL};
2598#endif
2599
2600/*
2601 * ":menutrans".
2602 * This function is also defined without the +multi_lang feature, in which
2603 * case the commands are ignored.
2604 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00002605 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002606ex_menutranslate(exarg_T *eap UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002607{
2608#ifdef FEAT_MULTI_LANG
2609 char_u *arg = eap->arg;
2610 menutrans_T *tp;
2611 int i;
2612 char_u *from, *from_noamp, *to;
2613
2614 if (menutrans_ga.ga_itemsize == 0)
2615 ga_init2(&menutrans_ga, (int)sizeof(menutrans_T), 5);
2616
2617 /*
2618 * ":menutrans clear": clear all translations.
2619 */
2620 if (STRNCMP(arg, "clear", 5) == 0 && ends_excmd(*skipwhite(arg + 5)))
2621 {
2622 tp = (menutrans_T *)menutrans_ga.ga_data;
2623 for (i = 0; i < menutrans_ga.ga_len; ++i)
2624 {
2625 vim_free(tp[i].from);
2626 vim_free(tp[i].from_noamp);
2627 vim_free(tp[i].to);
2628 }
2629 ga_clear(&menutrans_ga);
2630# ifdef FEAT_EVAL
2631 /* Delete all "menutrans_" global variables. */
2632 del_menutrans_vars();
2633# endif
2634 }
2635 else
2636 {
2637 /* ":menutrans from to": add translation */
2638 from = arg;
2639 arg = menu_skip_part(arg);
2640 to = skipwhite(arg);
2641 *arg = NUL;
2642 arg = menu_skip_part(to);
2643 if (arg == to)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01002644 emsg(_(e_invarg));
Bram Moolenaar071d4272004-06-13 20:20:40 +00002645 else
2646 {
2647 if (ga_grow(&menutrans_ga, 1) == OK)
2648 {
2649 tp = (menutrans_T *)menutrans_ga.ga_data;
2650 from = vim_strsave(from);
Bram Moolenaarfc1421e2006-04-20 22:17:20 +00002651 if (from != NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002652 {
Bram Moolenaarfc1421e2006-04-20 22:17:20 +00002653 from_noamp = menu_text(from, NULL, NULL);
2654 to = vim_strnsave(to, (int)(arg - to));
2655 if (from_noamp != NULL && to != NULL)
2656 {
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002657 menu_translate_tab_and_shift(from);
2658 menu_translate_tab_and_shift(to);
2659 menu_unescape_name(from);
2660 menu_unescape_name(to);
Bram Moolenaarfc1421e2006-04-20 22:17:20 +00002661 tp[menutrans_ga.ga_len].from = from;
2662 tp[menutrans_ga.ga_len].from_noamp = from_noamp;
2663 tp[menutrans_ga.ga_len].to = to;
2664 ++menutrans_ga.ga_len;
2665 }
2666 else
2667 {
2668 vim_free(from);
2669 vim_free(from_noamp);
2670 vim_free(to);
2671 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00002672 }
2673 }
2674 }
2675 }
2676#endif
2677}
2678
2679#if defined(FEAT_MULTI_LANG) || defined(FEAT_TOOLBAR)
2680/*
2681 * Find the character just after one part of a menu name.
2682 */
2683 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002684menu_skip_part(char_u *p)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002685{
Bram Moolenaar1c465442017-03-12 20:10:05 +01002686 while (*p != NUL && *p != '.' && !VIM_ISWHITE(*p))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002687 {
2688 if ((*p == '\\' || *p == Ctrl_V) && p[1] != NUL)
2689 ++p;
2690 ++p;
2691 }
2692 return p;
2693}
2694#endif
2695
2696#ifdef FEAT_MULTI_LANG
2697/*
2698 * Lookup part of a menu name in the translations.
2699 * Return a pointer to the translation or NULL if not found.
2700 */
2701 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002702menutrans_lookup(char_u *name, int len)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002703{
2704 menutrans_T *tp = (menutrans_T *)menutrans_ga.ga_data;
2705 int i;
2706 char_u *dname;
2707
2708 for (i = 0; i < menutrans_ga.ga_len; ++i)
Bram Moolenaar11dd8c12017-03-04 20:41:34 +01002709 if (STRNICMP(name, tp[i].from, len) == 0 && tp[i].from[len] == NUL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002710 return tp[i].to;
2711
2712 /* Now try again while ignoring '&' characters. */
2713 i = name[len];
2714 name[len] = NUL;
2715 dname = menu_text(name, NULL, NULL);
2716 name[len] = i;
2717 if (dname != NULL)
2718 {
2719 for (i = 0; i < menutrans_ga.ga_len; ++i)
Bram Moolenaar11dd8c12017-03-04 20:41:34 +01002720 if (STRICMP(dname, tp[i].from_noamp) == 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002721 {
2722 vim_free(dname);
2723 return tp[i].to;
2724 }
2725 vim_free(dname);
2726 }
2727
2728 return NULL;
2729}
Bram Moolenaar071d4272004-06-13 20:20:40 +00002730
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002731/*
2732 * Unescape the name in the translate dictionary table.
2733 */
2734 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002735menu_unescape_name(char_u *name)
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002736{
2737 char_u *p;
2738
Bram Moolenaar91acfff2017-03-12 19:22:36 +01002739 for (p = name; *p && *p != '.'; MB_PTR_ADV(p))
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002740 if (*p == '\\')
2741 STRMOVE(p, p + 1);
2742}
Bram Moolenaar56be9502010-06-06 14:20:26 +02002743#endif /* FEAT_MULTI_LANG */
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002744
2745/*
2746 * Isolate the menu name.
2747 * Skip the menu name, and translate <Tab> into a real TAB.
2748 */
2749 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002750menu_translate_tab_and_shift(char_u *arg_start)
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002751{
2752 char_u *arg = arg_start;
2753
Bram Moolenaar1c465442017-03-12 20:10:05 +01002754 while (*arg && !VIM_ISWHITE(*arg))
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002755 {
2756 if ((*arg == '\\' || *arg == Ctrl_V) && arg[1] != NUL)
2757 arg++;
2758 else if (STRNICMP(arg, "<TAB>", 5) == 0)
2759 {
2760 *arg = TAB;
2761 STRMOVE(arg + 1, arg + 5);
2762 }
2763 arg++;
2764 }
2765 if (*arg != NUL)
2766 *arg++ = NUL;
2767 arg = skipwhite(arg);
2768
2769 return arg;
2770}
2771
Bram Moolenaar071d4272004-06-13 20:20:40 +00002772#endif /* FEAT_MENU */