blob: 4096a0571cfa1ae63084d27ae311e3746cea2371 [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 Moolenaar8b2d9c42006-05-03 21:28:47 +0000375 map_to = replace_termcodes(map_to, &map_buf, FALSE, TRUE, special);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000376 menuarg.modes = modes;
377#ifdef FEAT_TOOLBAR
378 menuarg.iconfile = icon;
379#endif
380 menuarg.noremap[0] = noremap;
381 menuarg.silent[0] = silent;
382 add_menu_path(menu_path, &menuarg, pri_tab, map_to
Bram Moolenaar4f974752019-02-17 17:44:42 +0100383#ifdef FEAT_GUI_MSWIN
Bram Moolenaar071d4272004-06-13 20:20:40 +0000384 , TRUE
385#endif
386 );
387
388 /*
389 * For the PopUp menu, add a menu for each mode separately.
390 */
391 if (menu_is_popup(menu_path))
392 {
393 for (i = 0; i < MENU_INDEX_TIP; ++i)
394 if (modes & (1 << i))
395 {
396 p = popup_mode_name(menu_path, i);
397 if (p != NULL)
398 {
399 /* Include all modes, to make ":amenu" work */
400 menuarg.modes = modes;
401#ifdef FEAT_TOOLBAR
402 menuarg.iconfile = NULL;
403 menuarg.iconidx = -1;
404 menuarg.icon_builtin = FALSE;
405#endif
406 add_menu_path(p, &menuarg, pri_tab, map_to
Bram Moolenaar4f974752019-02-17 17:44:42 +0100407#ifdef FEAT_GUI_MSWIN
Bram Moolenaar071d4272004-06-13 20:20:40 +0000408 , TRUE
409#endif
410 );
411 vim_free(p);
412 }
413 }
414 }
415
416 vim_free(map_buf);
417 }
418
Bram Moolenaar241a8aa2005-12-06 20:04:44 +0000419#if defined(FEAT_GUI) && !(defined(FEAT_GUI_GTK))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000420 /* If the menubar height changed, resize the window */
421 if (gui.in_use
422 && (gui.menu_height != old_menu_height
Bram Moolenaar4f974752019-02-17 17:44:42 +0100423# if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_MSWIN)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000424 || gui.toolbar_height != old_toolbar_height
425# endif
426 ))
Bram Moolenaar04a9d452006-03-27 21:03:26 +0000427 gui_set_shellsize(FALSE, FALSE, RESIZE_VERT);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000428#endif
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200429 if (root_menu_ptr == &curwin->w_winbar)
430 {
431 int h = winbar_height(curwin);
432
433 if (h != curwin->w_winbar_height)
434 {
435 if (h == 0)
436 ++curwin->w_height;
437 else if (curwin->w_height > 0)
438 --curwin->w_height;
439 curwin->w_winbar_height = h;
440 }
441 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000442
443theend:
Bram Moolenaar071d4272004-06-13 20:20:40 +0000444 ;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000445}
446
447/*
448 * Add the menu with the given name to the menu hierarchy
449 */
450 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100451add_menu_path(
452 char_u *menu_path,
453 vimmenu_T *menuarg, /* passes modes, iconfile, iconidx,
Bram Moolenaar071d4272004-06-13 20:20:40 +0000454 icon_builtin, silent[0], noremap[0] */
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100455 int *pri_tab,
456 char_u *call_data
Bram Moolenaar4f974752019-02-17 17:44:42 +0100457#ifdef FEAT_GUI_MSWIN
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100458 , int addtearoff /* may add tearoff item */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000459#endif
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100460 )
Bram Moolenaar071d4272004-06-13 20:20:40 +0000461{
462 char_u *path_name;
463 int modes = menuarg->modes;
464 vimmenu_T **menup;
465 vimmenu_T *menu = NULL;
466 vimmenu_T *parent;
467 vimmenu_T **lower_pri;
468 char_u *p;
469 char_u *name;
470 char_u *dname;
471 char_u *next_name;
472 int i;
473 int c;
Bram Moolenaar7871a502010-05-14 21:19:23 +0200474 int d;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000475#ifdef FEAT_GUI
476 int idx;
477 int new_idx;
478#endif
479 int pri_idx = 0;
480 int old_modes = 0;
481 int amenu;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +0200482#ifdef FEAT_MULTI_LANG
483 char_u *en_name;
484 char_u *map_to = NULL;
485#endif
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200486 vimmenu_T **root_menu_ptr;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000487
488 /* Make a copy so we can stuff around with it, since it could be const */
489 path_name = vim_strsave(menu_path);
490 if (path_name == NULL)
491 return FAIL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200492 root_menu_ptr = get_root_menu(menu_path);
493 menup = root_menu_ptr;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000494 parent = NULL;
495 name = path_name;
496 while (*name)
497 {
498 /* Get name of this element in the menu hierarchy, and the simplified
499 * name (without mnemonic and accelerator text). */
500 next_name = menu_name_skip(name);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +0200501#ifdef FEAT_MULTI_LANG
Bram Moolenaar442b4222010-05-24 21:34:22 +0200502 map_to = menutrans_lookup(name, (int)STRLEN(name));
Bram Moolenaar70b11cd2010-05-14 22:24:40 +0200503 if (map_to != NULL)
504 {
505 en_name = name;
506 name = map_to;
507 }
508 else
509 en_name = NULL;
510#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000511 dname = menu_text(name, NULL, NULL);
Bram Moolenaar18a0b122006-08-16 13:55:16 +0000512 if (dname == NULL)
513 goto erret;
514 if (*dname == NUL)
515 {
516 /* Only a mnemonic or accelerator is not valid. */
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100517 emsg(_("E792: Empty menu name"));
Bram Moolenaar18a0b122006-08-16 13:55:16 +0000518 goto erret;
519 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000520
521 /* See if it's already there */
522 lower_pri = menup;
523#ifdef FEAT_GUI
524 idx = 0;
525 new_idx = 0;
526#endif
527 menu = *menup;
528 while (menu != NULL)
529 {
530 if (menu_name_equal(name, menu) || menu_name_equal(dname, menu))
531 {
532 if (*next_name == NUL && menu->children != NULL)
533 {
534 if (!sys_menu)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100535 emsg(_("E330: Menu path must not lead to a sub-menu"));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000536 goto erret;
537 }
538 if (*next_name != NUL && menu->children == NULL
Bram Moolenaar4f974752019-02-17 17:44:42 +0100539#ifdef FEAT_GUI_MSWIN
Bram Moolenaar071d4272004-06-13 20:20:40 +0000540 && addtearoff
541#endif
542 )
543 {
544 if (!sys_menu)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100545 emsg(_(e_notsubmenu));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000546 goto erret;
547 }
548 break;
549 }
550 menup = &menu->next;
551
552 /* Count menus, to find where this one needs to be inserted.
553 * Ignore menus that are not in the menubar (PopUp and Toolbar) */
554 if (parent != NULL || menu_is_menubar(menu->name))
555 {
556#ifdef FEAT_GUI
557 ++idx;
558#endif
559 if (menu->priority <= pri_tab[pri_idx])
560 {
561 lower_pri = menup;
562#ifdef FEAT_GUI
563 new_idx = idx;
564#endif
565 }
566 }
567 menu = menu->next;
568 }
569
570 if (menu == NULL)
571 {
572 if (*next_name == NUL && parent == NULL)
573 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100574 emsg(_("E331: Must not add menu items directly to menu bar"));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000575 goto erret;
576 }
577
578 if (menu_is_separator(dname) && *next_name != NUL)
579 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100580 emsg(_("E332: Separator cannot be part of a menu path"));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000581 goto erret;
582 }
583
584 /* Not already there, so lets add it */
Bram Moolenaare809a4e2019-07-04 17:35:05 +0200585 menu = ALLOC_CLEAR_ONE(vimmenu_T);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000586 if (menu == NULL)
587 goto erret;
588
589 menu->modes = modes;
590 menu->enabled = MENU_ALL_MODES;
591 menu->name = vim_strsave(name);
592 /* separate mnemonic and accelerator text from actual menu name */
593 menu->dname = menu_text(name, &menu->mnemonic, &menu->actext);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +0200594#ifdef FEAT_MULTI_LANG
595 if (en_name != NULL)
596 {
597 menu->en_name = vim_strsave(en_name);
598 menu->en_dname = menu_text(en_name, NULL, NULL);
599 }
600 else
601 {
602 menu->en_name = NULL;
603 menu->en_dname = NULL;
604 }
605#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000606 menu->priority = pri_tab[pri_idx];
607 menu->parent = parent;
608#ifdef FEAT_GUI_MOTIF
609 menu->sensitive = TRUE; /* the default */
610#endif
611#ifdef FEAT_BEVAL_TIP
612 menu->tip = NULL;
613#endif
614#ifdef FEAT_GUI_ATHENA
615 menu->image = None; /* X-Windows definition for NULL*/
616#endif
617
618 /*
619 * Add after menu that has lower priority.
620 */
621 menu->next = *lower_pri;
622 *lower_pri = menu;
623
624 old_modes = 0;
625
626#ifdef FEAT_TOOLBAR
627 menu->iconidx = menuarg->iconidx;
628 menu->icon_builtin = menuarg->icon_builtin;
629 if (*next_name == NUL && menuarg->iconfile != NULL)
630 menu->iconfile = vim_strsave(menuarg->iconfile);
631#endif
Bram Moolenaar4f974752019-02-17 17:44:42 +0100632#if defined(FEAT_GUI_MSWIN) && defined(FEAT_TEAROFF)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000633 /* the tearoff item must be present in the modes of each item. */
634 if (parent != NULL && menu_is_tearoff(parent->children->dname))
635 parent->children->modes |= modes;
636#endif
637 }
638 else
639 {
640 old_modes = menu->modes;
641
642 /*
643 * If this menu option was previously only available in other
644 * modes, then make sure it's available for this one now
645 * Also enable a menu when it's created or changed.
646 */
Bram Moolenaar4f974752019-02-17 17:44:42 +0100647#ifdef FEAT_GUI_MSWIN
Bram Moolenaar071d4272004-06-13 20:20:40 +0000648 /* If adding a tearbar (addtearoff == FALSE) don't update modes */
649 if (addtearoff)
650#endif
651 {
652 menu->modes |= modes;
653 menu->enabled |= modes;
654 }
655 }
656
657#ifdef FEAT_GUI
658 /*
659 * Add the menu item when it's used in one of the modes, but not when
660 * only a tooltip is defined.
661 */
662 if ((old_modes & MENU_ALL_MODES) == 0
663 && (menu->modes & MENU_ALL_MODES) != 0)
664 {
665 if (gui.in_use) /* Otherwise it will be added when GUI starts */
666 {
667 if (*next_name == NUL)
668 {
669 /* Real menu item, not sub-menu */
670 gui_mch_add_menu_item(menu, new_idx);
671
672 /* Want to update menus now even if mode not changed */
673 force_menu_update = TRUE;
674 }
675 else
676 {
677 /* Sub-menu (not at end of path yet) */
678 gui_mch_add_menu(menu, new_idx);
679 }
680 }
681
Bram Moolenaar4f974752019-02-17 17:44:42 +0100682# if defined(FEAT_GUI_MSWIN) & defined(FEAT_TEAROFF)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000683 /* When adding a new submenu, may add a tearoff item */
684 if ( addtearoff
685 && *next_name
686 && vim_strchr(p_go, GO_TEAROFF) != NULL
687 && menu_is_menubar(name))
688 {
689 char_u *tearpath;
690
691 /*
692 * The pointers next_name & path_name refer to a string with
693 * \'s and ^V's stripped out. But menu_path is a "raw"
694 * string, so we must correct for special characters.
695 */
Bram Moolenaar964b3742019-05-24 18:54:09 +0200696 tearpath = alloc(STRLEN(menu_path) + TEAR_LEN + 2);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000697 if (tearpath != NULL)
698 {
699 char_u *s;
700 int idx;
701
702 STRCPY(tearpath, menu_path);
703 idx = (int)(next_name - path_name - 1);
Bram Moolenaar91acfff2017-03-12 19:22:36 +0100704 for (s = tearpath; *s && s < tearpath + idx; MB_PTR_ADV(s))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000705 {
706 if ((*s == '\\' || *s == Ctrl_V) && s[1])
707 {
708 ++idx;
709 ++s;
710 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000711 }
712 tearpath[idx] = NUL;
713 gui_add_tearoff(tearpath, pri_tab, pri_idx);
714 vim_free(tearpath);
715 }
716 }
717# endif
718 }
719#endif /* FEAT_GUI */
720
721 menup = &menu->children;
722 parent = menu;
723 name = next_name;
Bram Moolenaard23a8232018-02-10 18:45:26 +0100724 VIM_CLEAR(dname);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000725 if (pri_tab[pri_idx + 1] != -1)
726 ++pri_idx;
727 }
728 vim_free(path_name);
729
730 /*
731 * Only add system menu items which have not been defined yet.
732 * First check if this was an ":amenu".
733 */
734 amenu = ((modes & (MENU_NORMAL_MODE | MENU_INSERT_MODE)) ==
735 (MENU_NORMAL_MODE | MENU_INSERT_MODE));
736 if (sys_menu)
737 modes &= ~old_modes;
738
739 if (menu != NULL && modes)
740 {
741#ifdef FEAT_GUI
742 menu->cb = gui_menu_cb;
743#endif
744 p = (call_data == NULL) ? NULL : vim_strsave(call_data);
745
746 /* loop over all modes, may add more than one */
747 for (i = 0; i < MENU_MODES; ++i)
748 {
749 if (modes & (1 << i))
750 {
751 /* free any old menu */
752 free_menu_string(menu, i);
753
754 /* For "amenu", may insert an extra character.
755 * Don't do this if adding a tearbar (addtearoff == FALSE).
756 * Don't do this for "<Nop>". */
757 c = 0;
Bram Moolenaar7871a502010-05-14 21:19:23 +0200758 d = 0;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000759 if (amenu && call_data != NULL && *call_data != NUL
Bram Moolenaar4f974752019-02-17 17:44:42 +0100760#ifdef FEAT_GUI_MSWIN
Bram Moolenaar071d4272004-06-13 20:20:40 +0000761 && addtearoff
762#endif
763 )
764 {
765 switch (1 << i)
766 {
767 case MENU_VISUAL_MODE:
Bram Moolenaarb3656ed2006-03-20 21:59:49 +0000768 case MENU_SELECT_MODE:
Bram Moolenaar071d4272004-06-13 20:20:40 +0000769 case MENU_OP_PENDING_MODE:
770 case MENU_CMDLINE_MODE:
771 c = Ctrl_C;
772 break;
773 case MENU_INSERT_MODE:
Bram Moolenaar7871a502010-05-14 21:19:23 +0200774 c = Ctrl_BSL;
775 d = Ctrl_O;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000776 break;
777 }
778 }
779
Bram Moolenaar7871a502010-05-14 21:19:23 +0200780 if (c != 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000781 {
Bram Moolenaar964b3742019-05-24 18:54:09 +0200782 menu->strings[i] = alloc(STRLEN(call_data) + 5);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000783 if (menu->strings[i] != NULL)
784 {
785 menu->strings[i][0] = c;
Bram Moolenaar7871a502010-05-14 21:19:23 +0200786 if (d == 0)
787 STRCPY(menu->strings[i] + 1, call_data);
788 else
789 {
790 menu->strings[i][1] = d;
791 STRCPY(menu->strings[i] + 2, call_data);
792 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000793 if (c == Ctrl_C)
794 {
Bram Moolenaara93fa7e2006-04-17 22:14:47 +0000795 int len = (int)STRLEN(menu->strings[i]);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000796
797 /* Append CTRL-\ CTRL-G to obey 'insertmode'. */
798 menu->strings[i][len] = Ctrl_BSL;
799 menu->strings[i][len + 1] = Ctrl_G;
800 menu->strings[i][len + 2] = NUL;
801 }
802 }
803 }
804 else
805 menu->strings[i] = p;
806 menu->noremap[i] = menuarg->noremap[0];
807 menu->silent[i] = menuarg->silent[0];
808 }
809 }
Bram Moolenaar4f974752019-02-17 17:44:42 +0100810#if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_MSWIN) \
Bram Moolenaarc3719bd2017-11-18 22:13:31 +0100811 && (defined(FEAT_BEVAL_GUI) || defined(FEAT_GUI_GTK))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000812 /* Need to update the menu tip. */
813 if (modes & MENU_TIP_MODE)
814 gui_mch_menu_set_tip(menu);
815#endif
816 }
817 return OK;
818
819erret:
820 vim_free(path_name);
821 vim_free(dname);
Bram Moolenaar18a0b122006-08-16 13:55:16 +0000822
823 /* Delete any empty submenu we added before discovering the error. Repeat
824 * for higher levels. */
825 while (parent != NULL && parent->children == NULL)
826 {
827 if (parent->parent == NULL)
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200828 menup = root_menu_ptr;
Bram Moolenaar18a0b122006-08-16 13:55:16 +0000829 else
830 menup = &parent->parent->children;
831 for ( ; *menup != NULL && *menup != parent; menup = &((*menup)->next))
832 ;
833 if (*menup == NULL) /* safety check */
834 break;
835 parent = parent->parent;
836 free_menu(menup);
837 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000838 return FAIL;
839}
840
841/*
842 * Set the (sub)menu with the given name to enabled or disabled.
843 * Called recursively.
844 */
845 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100846menu_nable_recurse(
847 vimmenu_T *menu,
848 char_u *name,
849 int modes,
850 int enable)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000851{
852 char_u *p;
853
854 if (menu == NULL)
855 return OK; /* Got to bottom of hierarchy */
856
857 /* Get name of this element in the menu hierarchy */
858 p = menu_name_skip(name);
859
860 /* Find the menu */
861 while (menu != NULL)
862 {
863 if (*name == NUL || *name == '*' || menu_name_equal(name, menu))
864 {
865 if (*p != NUL)
866 {
867 if (menu->children == NULL)
868 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100869 emsg(_(e_notsubmenu));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000870 return FAIL;
871 }
872 if (menu_nable_recurse(menu->children, p, modes, enable)
873 == FAIL)
874 return FAIL;
875 }
876 else
877 if (enable)
878 menu->enabled |= modes;
879 else
880 menu->enabled &= ~modes;
881
882 /*
883 * When name is empty, we are doing all menu items for the given
884 * modes, so keep looping, otherwise we are just doing the named
885 * menu item (which has been found) so break here.
886 */
887 if (*name != NUL && *name != '*')
888 break;
889 }
890 menu = menu->next;
891 }
892 if (*name != NUL && *name != '*' && menu == NULL)
893 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100894 semsg(_(e_nomenu), name);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000895 return FAIL;
896 }
897
898#ifdef FEAT_GUI
899 /* Want to update menus now even if mode not changed */
900 force_menu_update = TRUE;
901#endif
902
903 return OK;
904}
905
906/*
907 * Remove the (sub)menu with the given name from the menu hierarchy
908 * Called recursively.
909 */
910 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100911remove_menu(
912 vimmenu_T **menup,
913 char_u *name,
914 int modes,
915 int silent) /* don't give error messages */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000916{
917 vimmenu_T *menu;
918 vimmenu_T *child;
919 char_u *p;
920
921 if (*menup == NULL)
922 return OK; /* Got to bottom of hierarchy */
923
924 /* Get name of this element in the menu hierarchy */
925 p = menu_name_skip(name);
926
927 /* Find the menu */
928 while ((menu = *menup) != NULL)
929 {
930 if (*name == NUL || menu_name_equal(name, menu))
931 {
932 if (*p != NUL && menu->children == NULL)
933 {
934 if (!silent)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100935 emsg(_(e_notsubmenu));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000936 return FAIL;
937 }
938 if ((menu->modes & modes) != 0x0)
939 {
Bram Moolenaar4f974752019-02-17 17:44:42 +0100940#if defined(FEAT_GUI_MSWIN) & defined(FEAT_TEAROFF)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000941 /*
942 * If we are removing all entries for this menu,MENU_ALL_MODES,
943 * Then kill any tearoff before we start
944 */
945 if (*p == NUL && modes == MENU_ALL_MODES)
946 {
947 if (IsWindow(menu->tearoff_handle))
948 DestroyWindow(menu->tearoff_handle);
949 }
950#endif
951 if (remove_menu(&menu->children, p, modes, silent) == FAIL)
952 return FAIL;
953 }
954 else if (*name != NUL)
955 {
956 if (!silent)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100957 emsg(_(e_menuothermode));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000958 return FAIL;
959 }
960
961 /*
962 * When name is empty, we are removing all menu items for the given
963 * modes, so keep looping, otherwise we are just removing the named
964 * menu item (which has been found) so break here.
965 */
966 if (*name != NUL)
967 break;
968
969 /* Remove the menu item for the given mode[s]. If the menu item
970 * is no longer valid in ANY mode, delete it */
971 menu->modes &= ~modes;
972 if (modes & MENU_TIP_MODE)
973 free_menu_string(menu, MENU_INDEX_TIP);
974 if ((menu->modes & MENU_ALL_MODES) == 0)
975 free_menu(menup);
976 else
977 menup = &menu->next;
978 }
979 else
980 menup = &menu->next;
981 }
982 if (*name != NUL)
983 {
984 if (menu == NULL)
985 {
986 if (!silent)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100987 semsg(_(e_nomenu), name);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000988 return FAIL;
989 }
990
991
992 /* Recalculate modes for menu based on the new updated children */
993 menu->modes &= ~modes;
Bram Moolenaar4f974752019-02-17 17:44:42 +0100994#if defined(FEAT_GUI_MSWIN) & defined(FEAT_TEAROFF)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000995 if ((s_tearoffs) && (menu->children != NULL)) /* there's a tear bar.. */
996 child = menu->children->next; /* don't count tearoff bar */
997 else
998#endif
999 child = menu->children;
1000 for ( ; child != NULL; child = child->next)
1001 menu->modes |= child->modes;
1002 if (modes & MENU_TIP_MODE)
1003 {
1004 free_menu_string(menu, MENU_INDEX_TIP);
Bram Moolenaar4f974752019-02-17 17:44:42 +01001005#if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_MSWIN) \
Bram Moolenaarc3719bd2017-11-18 22:13:31 +01001006 && (defined(FEAT_BEVAL_GUI) || defined(FEAT_GUI_GTK))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001007 /* Need to update the menu tip. */
1008 if (gui.in_use)
1009 gui_mch_menu_set_tip(menu);
1010#endif
1011 }
1012 if ((menu->modes & MENU_ALL_MODES) == 0)
1013 {
1014 /* The menu item is no longer valid in ANY mode, so delete it */
Bram Moolenaar4f974752019-02-17 17:44:42 +01001015#if defined(FEAT_GUI_MSWIN) & defined(FEAT_TEAROFF)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001016 if (s_tearoffs && menu->children != NULL) /* there's a tear bar.. */
1017 free_menu(&menu->children);
1018#endif
1019 *menup = menu;
1020 free_menu(menup);
1021 }
1022 }
1023
1024 return OK;
1025}
1026
1027/*
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001028 * Remove the WinBar menu from window "wp".
1029 */
1030 void
1031remove_winbar(win_T *wp)
1032{
1033 remove_menu(&wp->w_winbar, (char_u *)"", MENU_ALL_MODES, TRUE);
1034 vim_free(wp->w_winbar_items);
1035}
1036
1037/*
Bram Moolenaar071d4272004-06-13 20:20:40 +00001038 * Free the given menu structure and remove it from the linked list.
1039 */
1040 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001041free_menu(vimmenu_T **menup)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001042{
1043 int i;
1044 vimmenu_T *menu;
1045
1046 menu = *menup;
1047
1048#ifdef FEAT_GUI
1049 /* Free machine specific menu structures (only when already created) */
1050 /* Also may rebuild a tearoff'ed menu */
1051 if (gui.in_use)
1052 gui_mch_destroy_menu(menu);
1053#endif
1054
1055 /* Don't change *menup until after calling gui_mch_destroy_menu(). The
1056 * MacOS code needs the original structure to properly delete the menu. */
1057 *menup = menu->next;
1058 vim_free(menu->name);
1059 vim_free(menu->dname);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001060#ifdef FEAT_MULTI_LANG
1061 vim_free(menu->en_name);
1062 vim_free(menu->en_dname);
1063#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001064 vim_free(menu->actext);
1065#ifdef FEAT_TOOLBAR
1066 vim_free(menu->iconfile);
Bram Moolenaarbee0c5b2005-02-07 22:03:36 +00001067# ifdef FEAT_GUI_MOTIF
1068 vim_free(menu->xpm_fname);
1069# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001070#endif
1071 for (i = 0; i < MENU_MODES; i++)
1072 free_menu_string(menu, i);
1073 vim_free(menu);
1074
1075#ifdef FEAT_GUI
1076 /* Want to update menus now even if mode not changed */
1077 force_menu_update = TRUE;
1078#endif
1079}
1080
1081/*
1082 * Free the menu->string with the given index.
1083 */
1084 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001085free_menu_string(vimmenu_T *menu, int idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001086{
1087 int count = 0;
1088 int i;
1089
1090 for (i = 0; i < MENU_MODES; i++)
1091 if (menu->strings[i] == menu->strings[idx])
1092 count++;
1093 if (count == 1)
1094 vim_free(menu->strings[idx]);
1095 menu->strings[idx] = NULL;
1096}
1097
1098/*
1099 * Show the mapping associated with a menu item or hierarchy in a sub-menu.
1100 */
1101 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001102show_menus(char_u *path_name, int modes)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001103{
1104 char_u *p;
1105 char_u *name;
1106 vimmenu_T *menu;
1107 vimmenu_T *parent = NULL;
1108
Bram Moolenaar071d4272004-06-13 20:20:40 +00001109 name = path_name = vim_strsave(path_name);
1110 if (path_name == NULL)
1111 return FAIL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001112 menu = *get_root_menu(path_name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001113
1114 /* First, find the (sub)menu with the given name */
1115 while (*name)
1116 {
1117 p = menu_name_skip(name);
1118 while (menu != NULL)
1119 {
1120 if (menu_name_equal(name, menu))
1121 {
1122 /* Found menu */
1123 if (*p != NUL && menu->children == NULL)
1124 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01001125 emsg(_(e_notsubmenu));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001126 vim_free(path_name);
1127 return FAIL;
1128 }
1129 else if ((menu->modes & modes) == 0x0)
1130 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01001131 emsg(_(e_menuothermode));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001132 vim_free(path_name);
1133 return FAIL;
1134 }
1135 break;
1136 }
1137 menu = menu->next;
1138 }
1139 if (menu == NULL)
1140 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01001141 semsg(_(e_nomenu), name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001142 vim_free(path_name);
1143 return FAIL;
1144 }
1145 name = p;
1146 parent = menu;
1147 menu = menu->children;
1148 }
Bram Moolenaaracbd4422008-08-17 21:44:45 +00001149 vim_free(path_name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001150
1151 /* Now we have found the matching menu, and we list the mappings */
1152 /* Highlight title */
Bram Moolenaar32526b32019-01-19 17:43:09 +01001153 msg_puts_title(_("\n--- Menus ---"));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001154
1155 show_menus_recursive(parent, modes, 0);
1156 return OK;
1157}
1158
1159/*
1160 * Recursively show the mappings associated with the menus under the given one
1161 */
1162 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001163show_menus_recursive(vimmenu_T *menu, int modes, int depth)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001164{
1165 int i;
1166 int bit;
1167
1168 if (menu != NULL && (menu->modes & modes) == 0x0)
1169 return;
1170
1171 if (menu != NULL)
1172 {
1173 msg_putchar('\n');
1174 if (got_int) /* "q" hit for "--more--" */
1175 return;
1176 for (i = 0; i < depth; i++)
Bram Moolenaar32526b32019-01-19 17:43:09 +01001177 msg_puts(" ");
Bram Moolenaar071d4272004-06-13 20:20:40 +00001178 if (menu->priority)
1179 {
1180 msg_outnum((long)menu->priority);
Bram Moolenaar32526b32019-01-19 17:43:09 +01001181 msg_puts(" ");
Bram Moolenaar071d4272004-06-13 20:20:40 +00001182 }
1183 /* Same highlighting as for directories!? */
Bram Moolenaar8820b482017-03-16 17:23:31 +01001184 msg_outtrans_attr(menu->name, HL_ATTR(HLF_D));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001185 }
1186
1187 if (menu != NULL && menu->children == NULL)
1188 {
1189 for (bit = 0; bit < MENU_MODES; bit++)
1190 if ((menu->modes & modes & (1 << bit)) != 0)
1191 {
1192 msg_putchar('\n');
1193 if (got_int) /* "q" hit for "--more--" */
1194 return;
1195 for (i = 0; i < depth + 2; i++)
Bram Moolenaar32526b32019-01-19 17:43:09 +01001196 msg_puts(" ");
1197 msg_puts(menu_mode_chars[bit]);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001198 if (menu->noremap[bit] == REMAP_NONE)
1199 msg_putchar('*');
1200 else if (menu->noremap[bit] == REMAP_SCRIPT)
1201 msg_putchar('&');
1202 else
1203 msg_putchar(' ');
1204 if (menu->silent[bit])
1205 msg_putchar('s');
1206 else
1207 msg_putchar(' ');
1208 if ((menu->modes & menu->enabled & (1 << bit)) == 0)
1209 msg_putchar('-');
1210 else
1211 msg_putchar(' ');
Bram Moolenaar32526b32019-01-19 17:43:09 +01001212 msg_puts(" ");
Bram Moolenaar071d4272004-06-13 20:20:40 +00001213 if (*menu->strings[bit] == NUL)
Bram Moolenaar32526b32019-01-19 17:43:09 +01001214 msg_puts_attr("<Nop>", HL_ATTR(HLF_8));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001215 else
Bram Moolenaar725310d2019-04-24 23:08:23 +02001216 msg_outtrans_special(menu->strings[bit], FALSE, 0);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001217 }
1218 }
1219 else
1220 {
1221 if (menu == NULL)
1222 {
1223 menu = root_menu;
1224 depth--;
1225 }
1226 else
1227 menu = menu->children;
1228
1229 /* recursively show all children. Skip PopUp[nvoci]. */
1230 for (; menu != NULL && !got_int; menu = menu->next)
1231 if (!menu_is_hidden(menu->dname))
1232 show_menus_recursive(menu, modes, depth + 1);
1233 }
1234}
1235
Bram Moolenaar071d4272004-06-13 20:20:40 +00001236/*
1237 * Used when expanding menu names.
1238 */
1239static vimmenu_T *expand_menu = NULL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001240static vimmenu_T *expand_menu_alt = NULL;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001241static int expand_modes = 0x0;
1242static int expand_emenu; /* TRUE for ":emenu" command */
1243
1244/*
1245 * Work out what to complete when doing command line completion of menu names.
1246 */
1247 char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001248set_context_in_menu_cmd(
1249 expand_T *xp,
1250 char_u *cmd,
1251 char_u *arg,
1252 int forceit)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001253{
1254 char_u *after_dot;
1255 char_u *p;
1256 char_u *path_name = NULL;
1257 char_u *name;
1258 int unmenu;
1259 vimmenu_T *menu;
1260 int expand_menus;
1261
1262 xp->xp_context = EXPAND_UNSUCCESSFUL;
1263
1264
1265 /* Check for priority numbers, enable and disable */
1266 for (p = arg; *p; ++p)
1267 if (!VIM_ISDIGIT(*p) && *p != '.')
1268 break;
1269
Bram Moolenaar1c465442017-03-12 20:10:05 +01001270 if (!VIM_ISWHITE(*p))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001271 {
1272 if (STRNCMP(arg, "enable", 6) == 0
Bram Moolenaar1c465442017-03-12 20:10:05 +01001273 && (arg[6] == NUL || VIM_ISWHITE(arg[6])))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001274 p = arg + 6;
1275 else if (STRNCMP(arg, "disable", 7) == 0
Bram Moolenaar1c465442017-03-12 20:10:05 +01001276 && (arg[7] == NUL || VIM_ISWHITE(arg[7])))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001277 p = arg + 7;
1278 else
1279 p = arg;
1280 }
1281
Bram Moolenaar1c465442017-03-12 20:10:05 +01001282 while (*p != NUL && VIM_ISWHITE(*p))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001283 ++p;
1284
1285 arg = after_dot = p;
1286
Bram Moolenaar1c465442017-03-12 20:10:05 +01001287 for (; *p && !VIM_ISWHITE(*p); ++p)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001288 {
1289 if ((*p == '\\' || *p == Ctrl_V) && p[1] != NUL)
1290 p++;
1291 else if (*p == '.')
1292 after_dot = p + 1;
1293 }
1294
1295 /* ":tearoff" and ":popup" only use menus, not entries */
1296 expand_menus = !((*cmd == 't' && cmd[1] == 'e') || *cmd == 'p');
1297 expand_emenu = (*cmd == 'e');
Bram Moolenaar1c465442017-03-12 20:10:05 +01001298 if (expand_menus && VIM_ISWHITE(*p))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001299 return NULL; /* TODO: check for next command? */
1300 if (*p == NUL) /* Complete the menu name */
1301 {
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001302 int try_alt_menu = TRUE;
1303
Bram Moolenaar071d4272004-06-13 20:20:40 +00001304 /*
1305 * With :unmenu, you only want to match menus for the appropriate mode.
1306 * With :menu though you might want to add a menu with the same name as
1307 * one in another mode, so match menus from other modes too.
1308 */
1309 expand_modes = get_menu_cmd_modes(cmd, forceit, NULL, &unmenu);
1310 if (!unmenu)
1311 expand_modes = MENU_ALL_MODES;
1312
1313 menu = root_menu;
1314 if (after_dot != arg)
1315 {
Bram Moolenaar964b3742019-05-24 18:54:09 +02001316 path_name = alloc(after_dot - arg);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001317 if (path_name == NULL)
1318 return NULL;
Bram Moolenaarce0842a2005-07-18 21:58:11 +00001319 vim_strncpy(path_name, arg, after_dot - arg - 1);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001320 }
1321 name = path_name;
1322 while (name != NULL && *name)
1323 {
1324 p = menu_name_skip(name);
1325 while (menu != NULL)
1326 {
1327 if (menu_name_equal(name, menu))
1328 {
1329 /* Found menu */
1330 if ((*p != NUL && menu->children == NULL)
1331 || ((menu->modes & expand_modes) == 0x0))
1332 {
1333 /*
1334 * Menu path continues, but we have reached a leaf.
1335 * Or menu exists only in another mode.
1336 */
1337 vim_free(path_name);
1338 return NULL;
1339 }
1340 break;
1341 }
1342 menu = menu->next;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001343 if (menu == NULL && try_alt_menu)
1344 {
1345 menu = curwin->w_winbar;
1346 try_alt_menu = FALSE;
1347 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001348 }
1349 if (menu == NULL)
1350 {
1351 /* No menu found with the name we were looking for */
1352 vim_free(path_name);
1353 return NULL;
1354 }
1355 name = p;
1356 menu = menu->children;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001357 try_alt_menu = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001358 }
Bram Moolenaareb3593b2006-04-22 22:33:57 +00001359 vim_free(path_name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001360
1361 xp->xp_context = expand_menus ? EXPAND_MENUNAMES : EXPAND_MENUS;
1362 xp->xp_pattern = after_dot;
1363 expand_menu = menu;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001364 if (expand_menu == root_menu)
1365 expand_menu_alt = curwin->w_winbar;
1366 else
1367 expand_menu_alt = NULL;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001368 }
1369 else /* We're in the mapping part */
1370 xp->xp_context = EXPAND_NOTHING;
1371 return NULL;
1372}
1373
1374/*
1375 * Function given to ExpandGeneric() to obtain the list of (sub)menus (not
1376 * entries).
1377 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001378 char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001379get_menu_name(expand_T *xp UNUSED, int idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001380{
1381 static vimmenu_T *menu = NULL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001382 static int did_alt_menu = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001383 char_u *str;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001384#ifdef FEAT_MULTI_LANG
1385 static int should_advance = FALSE;
1386#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001387
1388 if (idx == 0) /* first call: start at first item */
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001389 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001390 menu = expand_menu;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001391 did_alt_menu = FALSE;
Bram Moolenaar41375642010-05-16 12:49:27 +02001392#ifdef FEAT_MULTI_LANG
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001393 should_advance = FALSE;
Bram Moolenaar41375642010-05-16 12:49:27 +02001394#endif
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001395 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001396
1397 /* Skip PopUp[nvoci]. */
1398 while (menu != NULL && (menu_is_hidden(menu->dname)
1399 || menu_is_separator(menu->dname)
1400 || menu_is_tearoff(menu->dname)
1401 || menu->children == NULL))
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001402 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001403 menu = menu->next;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001404 if (menu == NULL && !did_alt_menu)
1405 {
1406 menu = expand_menu_alt;
1407 did_alt_menu = TRUE;
1408 }
1409 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001410
1411 if (menu == NULL) /* at end of linked list */
1412 return NULL;
1413
1414 if (menu->modes & expand_modes)
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001415#ifdef FEAT_MULTI_LANG
1416 if (should_advance)
1417 str = menu->en_dname;
1418 else
1419 {
1420#endif
1421 str = menu->dname;
1422#ifdef FEAT_MULTI_LANG
1423 if (menu->en_dname == NULL)
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001424 should_advance = TRUE;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001425 }
1426#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001427 else
1428 str = (char_u *)"";
1429
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001430#ifdef FEAT_MULTI_LANG
1431 if (should_advance)
1432#endif
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001433 {
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001434 /* Advance to next menu entry. */
1435 menu = menu->next;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001436 if (menu == NULL && !did_alt_menu)
1437 {
1438 menu = expand_menu_alt;
1439 did_alt_menu = TRUE;
1440 }
1441 }
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001442
1443#ifdef FEAT_MULTI_LANG
1444 should_advance = !should_advance;
1445#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001446
1447 return str;
1448}
1449
1450/*
1451 * Function given to ExpandGeneric() to obtain the list of menus and menu
1452 * entries.
1453 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001454 char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001455get_menu_names(expand_T *xp UNUSED, int idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001456{
1457 static vimmenu_T *menu = NULL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001458 static int did_alt_menu = FALSE;
Bram Moolenaaref9d6aa2011-04-11 16:56:35 +02001459#define TBUFFER_LEN 256
1460 static char_u tbuffer[TBUFFER_LEN]; /*hack*/
Bram Moolenaar071d4272004-06-13 20:20:40 +00001461 char_u *str;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001462#ifdef FEAT_MULTI_LANG
1463 static int should_advance = FALSE;
1464#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001465
1466 if (idx == 0) /* first call: start at first item */
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001467 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001468 menu = expand_menu;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001469 did_alt_menu = FALSE;
Bram Moolenaar41375642010-05-16 12:49:27 +02001470#ifdef FEAT_MULTI_LANG
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001471 should_advance = FALSE;
Bram Moolenaar41375642010-05-16 12:49:27 +02001472#endif
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001473 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001474
1475 /* Skip Browse-style entries, popup menus and separators. */
1476 while (menu != NULL
1477 && ( menu_is_hidden(menu->dname)
1478 || (expand_emenu && menu_is_separator(menu->dname))
1479 || menu_is_tearoff(menu->dname)
1480#ifndef FEAT_BROWSE
1481 || menu->dname[STRLEN(menu->dname) - 1] == '.'
1482#endif
1483 ))
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001484 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001485 menu = menu->next;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001486 if (menu == NULL && !did_alt_menu)
1487 {
1488 menu = expand_menu_alt;
1489 did_alt_menu = TRUE;
1490 }
1491 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001492
1493 if (menu == NULL) /* at end of linked list */
1494 return NULL;
1495
1496 if (menu->modes & expand_modes)
1497 {
1498 if (menu->children != NULL)
1499 {
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001500#ifdef FEAT_MULTI_LANG
1501 if (should_advance)
Bram Moolenaaref9d6aa2011-04-11 16:56:35 +02001502 vim_strncpy(tbuffer, menu->en_dname, TBUFFER_LEN - 2);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001503 else
1504 {
1505#endif
Bram Moolenaaref9d6aa2011-04-11 16:56:35 +02001506 vim_strncpy(tbuffer, menu->dname, TBUFFER_LEN - 2);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001507#ifdef FEAT_MULTI_LANG
1508 if (menu->en_dname == NULL)
1509 should_advance = TRUE;
1510 }
1511#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001512 /* hack on menu separators: use a 'magic' char for the separator
1513 * so that '.' in names gets escaped properly */
1514 STRCAT(tbuffer, "\001");
1515 str = tbuffer;
1516 }
1517 else
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001518#ifdef FEAT_MULTI_LANG
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001519 {
1520 if (should_advance)
1521 str = menu->en_dname;
1522 else
1523 {
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001524#endif
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001525 str = menu->dname;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001526#ifdef FEAT_MULTI_LANG
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001527 if (menu->en_dname == NULL)
1528 should_advance = TRUE;
1529 }
1530 }
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001531#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001532 }
1533 else
1534 str = (char_u *)"";
1535
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001536#ifdef FEAT_MULTI_LANG
1537 if (should_advance)
1538#endif
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001539 {
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001540 /* Advance to next menu entry. */
1541 menu = menu->next;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001542 if (menu == NULL && !did_alt_menu)
1543 {
1544 menu = expand_menu_alt;
1545 did_alt_menu = TRUE;
1546 }
1547 }
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001548
1549#ifdef FEAT_MULTI_LANG
1550 should_advance = !should_advance;
1551#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001552
1553 return str;
1554}
Bram Moolenaar071d4272004-06-13 20:20:40 +00001555
1556/*
1557 * Skip over this element of the menu path and return the start of the next
1558 * element. Any \ and ^Vs are removed from the current element.
Bram Moolenaar342337a2005-07-21 21:11:17 +00001559 * "name" may be modified.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001560 */
Bram Moolenaar5843f5f2019-08-20 20:13:45 +02001561 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001562menu_name_skip(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001563{
1564 char_u *p;
1565
Bram Moolenaar91acfff2017-03-12 19:22:36 +01001566 for (p = name; *p && *p != '.'; MB_PTR_ADV(p))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001567 {
1568 if (*p == '\\' || *p == Ctrl_V)
1569 {
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001570 STRMOVE(p, p + 1);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001571 if (*p == NUL)
1572 break;
1573 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001574 }
1575 if (*p)
1576 *p++ = NUL;
1577 return p;
1578}
1579
1580/*
1581 * Return TRUE when "name" matches with menu "menu". The name is compared in
1582 * two ways: raw menu name and menu name without '&'. ignore part after a TAB.
1583 */
1584 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001585menu_name_equal(char_u *name, vimmenu_T *menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001586{
Bram Moolenaar41375642010-05-16 12:49:27 +02001587#ifdef FEAT_MULTI_LANG
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001588 if (menu->en_name != NULL
Bram Moolenaard91f7042011-01-04 17:49:32 +01001589 && (menu_namecmp(name, menu->en_name)
1590 || menu_namecmp(name, menu->en_dname)))
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001591 return TRUE;
Bram Moolenaar41375642010-05-16 12:49:27 +02001592#endif
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001593 return menu_namecmp(name, menu->name) || menu_namecmp(name, menu->dname);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001594}
1595
1596 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001597menu_namecmp(char_u *name, char_u *mname)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001598{
1599 int i;
1600
1601 for (i = 0; name[i] != NUL && name[i] != TAB; ++i)
1602 if (name[i] != mname[i])
1603 break;
1604 return ((name[i] == NUL || name[i] == TAB)
1605 && (mname[i] == NUL || mname[i] == TAB));
1606}
1607
1608/*
1609 * Return the modes specified by the given menu command (eg :menu! returns
1610 * MENU_CMDLINE_MODE | MENU_INSERT_MODE).
1611 * If "noremap" is not NULL, then the flag it points to is set according to
1612 * whether the command is a "nore" command.
1613 * If "unmenu" is not NULL, then the flag it points to is set according to
1614 * whether the command is an "unmenu" command.
1615 */
1616 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001617get_menu_cmd_modes(
1618 char_u *cmd,
1619 int forceit, /* Was there a "!" after the command? */
1620 int *noremap,
1621 int *unmenu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001622{
1623 int modes;
1624
1625 switch (*cmd++)
1626 {
1627 case 'v': /* vmenu, vunmenu, vnoremenu */
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001628 modes = MENU_VISUAL_MODE | MENU_SELECT_MODE;
1629 break;
1630 case 'x': /* xmenu, xunmenu, xnoremenu */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001631 modes = MENU_VISUAL_MODE;
1632 break;
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001633 case 's': /* smenu, sunmenu, snoremenu */
1634 modes = MENU_SELECT_MODE;
1635 break;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001636 case 'o': /* omenu */
1637 modes = MENU_OP_PENDING_MODE;
1638 break;
1639 case 'i': /* imenu */
1640 modes = MENU_INSERT_MODE;
1641 break;
1642 case 't':
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001643 if (*cmd == 'l') /* tlmenu, tlunmenu, tlnoremenu */
1644 {
1645 modes = MENU_TERMINAL_MODE;
1646 ++cmd;
1647 break;
1648 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001649 modes = MENU_TIP_MODE; /* tmenu */
1650 break;
1651 case 'c': /* cmenu */
1652 modes = MENU_CMDLINE_MODE;
1653 break;
1654 case 'a': /* amenu */
1655 modes = MENU_INSERT_MODE | MENU_CMDLINE_MODE | MENU_NORMAL_MODE
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001656 | MENU_VISUAL_MODE | MENU_SELECT_MODE
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00001657 | MENU_OP_PENDING_MODE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001658 break;
1659 case 'n':
1660 if (*cmd != 'o') /* nmenu, not noremenu */
1661 {
1662 modes = MENU_NORMAL_MODE;
1663 break;
1664 }
1665 /* FALLTHROUGH */
1666 default:
1667 --cmd;
1668 if (forceit) /* menu!! */
1669 modes = MENU_INSERT_MODE | MENU_CMDLINE_MODE;
1670 else /* menu */
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001671 modes = MENU_NORMAL_MODE | MENU_VISUAL_MODE | MENU_SELECT_MODE
Bram Moolenaar071d4272004-06-13 20:20:40 +00001672 | MENU_OP_PENDING_MODE;
1673 }
1674
1675 if (noremap != NULL)
1676 *noremap = (*cmd == 'n' ? REMAP_NONE : REMAP_YES);
1677 if (unmenu != NULL)
1678 *unmenu = (*cmd == 'u');
1679 return modes;
1680}
1681
1682/*
1683 * Modify a menu name starting with "PopUp" to include the mode character.
1684 * Returns the name in allocated memory (NULL for failure).
1685 */
1686 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001687popup_mode_name(char_u *name, int idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001688{
1689 char_u *p;
1690 int len = (int)STRLEN(name);
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001691 char *mode_chars = menu_mode_chars[idx];
1692 int mode_chars_len = (int)strlen(mode_chars);
1693 int i;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001694
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001695 p = vim_strnsave(name, len + mode_chars_len);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001696 if (p != NULL)
1697 {
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001698 mch_memmove(p + 5 + mode_chars_len, p + 5, (size_t)(len - 4));
1699 for (i = 0; i < mode_chars_len; ++i)
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001700 p[5 + i] = menu_mode_chars[idx][i];
Bram Moolenaar071d4272004-06-13 20:20:40 +00001701 }
1702 return p;
1703}
1704
1705#if defined(FEAT_GUI) || defined(PROTO)
1706/*
1707 * Return the index into the menu->strings or menu->noremap arrays for the
1708 * current state. Returns MENU_INDEX_INVALID if there is no mapping for the
1709 * given menu in the current mode.
1710 */
1711 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001712get_menu_index(vimmenu_T *menu, int state)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001713{
1714 int idx;
1715
1716 if ((state & INSERT))
1717 idx = MENU_INDEX_INSERT;
1718 else if (state & CMDLINE)
1719 idx = MENU_INDEX_CMDLINE;
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001720#ifdef FEAT_TERMINAL
1721 else if (term_use_loop())
1722 idx = MENU_INDEX_TERMINAL;
1723#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001724 else if (VIsual_active)
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001725 {
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00001726 if (VIsual_select)
1727 idx = MENU_INDEX_SELECT;
1728 else
1729 idx = MENU_INDEX_VISUAL;
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001730 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001731 else if (state == HITRETURN || state == ASKMORE)
1732 idx = MENU_INDEX_CMDLINE;
1733 else if (finish_op)
1734 idx = MENU_INDEX_OP_PENDING;
1735 else if ((state & NORMAL))
1736 idx = MENU_INDEX_NORMAL;
1737 else
1738 idx = MENU_INDEX_INVALID;
1739
1740 if (idx != MENU_INDEX_INVALID && menu->strings[idx] == NULL)
1741 idx = MENU_INDEX_INVALID;
1742 return idx;
1743}
1744#endif
1745
1746/*
1747 * Duplicate the menu item text and then process to see if a mnemonic key
1748 * and/or accelerator text has been identified.
1749 * Returns a pointer to allocated memory, or NULL for failure.
1750 * If mnemonic != NULL, *mnemonic is set to the character after the first '&'.
1751 * If actext != NULL, *actext is set to the text after the first TAB.
1752 */
1753 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001754menu_text(char_u *str, int *mnemonic, char_u **actext)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001755{
1756 char_u *p;
1757 char_u *text;
1758
1759 /* Locate accelerator text, after the first TAB */
1760 p = vim_strchr(str, TAB);
1761 if (p != NULL)
1762 {
1763 if (actext != NULL)
1764 *actext = vim_strsave(p + 1);
1765 text = vim_strnsave(str, (int)(p - str));
1766 }
1767 else
1768 text = vim_strsave(str);
1769
1770 /* Find mnemonic characters "&a" and reduce "&&" to "&". */
1771 for (p = text; p != NULL; )
1772 {
1773 p = vim_strchr(p, '&');
1774 if (p != NULL)
1775 {
1776 if (p[1] == NUL) /* trailing "&" */
1777 break;
1778 if (mnemonic != NULL && p[1] != '&')
1779#if !defined(__MVS__) || defined(MOTIF390_MNEMONIC_FIXED)
1780 *mnemonic = p[1];
1781#else
1782 {
1783 /*
1784 * Well there is a bug in the Motif libraries on OS390 Unix.
1785 * The mnemonic keys needs to be converted to ASCII values
1786 * first.
1787 * This behavior has been seen in 2.8 and 2.9.
1788 */
1789 char c = p[1];
1790 __etoa_l(&c, 1);
1791 *mnemonic = c;
1792 }
1793#endif
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001794 STRMOVE(p, p + 1);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001795 p = p + 1;
1796 }
1797 }
1798 return text;
1799}
1800
1801/*
1802 * Return TRUE if "name" can be a menu in the MenuBar.
1803 */
1804 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001805menu_is_menubar(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001806{
1807 return (!menu_is_popup(name)
1808 && !menu_is_toolbar(name)
Bram Moolenaar378daf82017-09-23 23:58:28 +02001809 && !menu_is_winbar(name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001810 && *name != MNU_HIDDEN_CHAR);
1811}
1812
1813/*
1814 * Return TRUE if "name" is a popup menu name.
1815 */
1816 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001817menu_is_popup(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001818{
1819 return (STRNCMP(name, "PopUp", 5) == 0);
1820}
1821
1822#if (defined(FEAT_GUI_MOTIF) && (XmVersion <= 1002)) || defined(PROTO)
1823/*
1824 * Return TRUE if "name" is part of a popup menu.
1825 */
1826 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001827menu_is_child_of_popup(vimmenu_T *menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001828{
1829 while (menu->parent != NULL)
1830 menu = menu->parent;
1831 return menu_is_popup(menu->name);
1832}
1833#endif
1834
1835/*
1836 * Return TRUE if "name" is a toolbar menu name.
1837 */
1838 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001839menu_is_toolbar(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001840{
1841 return (STRNCMP(name, "ToolBar", 7) == 0);
1842}
1843
1844/*
1845 * Return TRUE if the name is a menu separator identifier: Starts and ends
1846 * with '-'
1847 */
1848 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001849menu_is_separator(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001850{
1851 return (name[0] == '-' && name[STRLEN(name) - 1] == '-');
1852}
1853
1854/*
1855 * Return TRUE if the menu is hidden: Starts with ']'
1856 */
1857 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001858menu_is_hidden(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001859{
1860 return (name[0] == ']') || (menu_is_popup(name) && name[5] != NUL);
1861}
1862
Bram Moolenaar071d4272004-06-13 20:20:40 +00001863/*
1864 * Return TRUE if the menu is the tearoff menu.
1865 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001866 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001867menu_is_tearoff(char_u *name UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001868{
1869#ifdef FEAT_GUI
1870 return (STRCMP(name, TEAR_STRING) == 0);
1871#else
1872 return FALSE;
1873#endif
1874}
Bram Moolenaar071d4272004-06-13 20:20:40 +00001875
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001876#if defined(FEAT_GUI) || defined(FEAT_TERM_POPUP_MENU) || defined(PROTO)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001877
1878 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001879get_menu_mode(void)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001880{
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001881#ifdef FEAT_TERMINAL
1882 if (term_use_loop())
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001883 return MENU_INDEX_TERMINAL;
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001884#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001885 if (VIsual_active)
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001886 {
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00001887 if (VIsual_select)
1888 return MENU_INDEX_SELECT;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001889 return MENU_INDEX_VISUAL;
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001890 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001891 if (State & INSERT)
1892 return MENU_INDEX_INSERT;
1893 if ((State & CMDLINE) || State == ASKMORE || State == HITRETURN)
1894 return MENU_INDEX_CMDLINE;
1895 if (finish_op)
1896 return MENU_INDEX_OP_PENDING;
1897 if (State & NORMAL)
1898 return MENU_INDEX_NORMAL;
1899 if (State & LANGMAP) /* must be a "r" command, like Insert mode */
1900 return MENU_INDEX_INSERT;
1901 return MENU_INDEX_INVALID;
1902}
1903
Bram Moolenaar29a2c082018-03-05 21:06:23 +01001904 int
1905get_menu_mode_flag(void)
1906{
1907 int mode = get_menu_mode();
1908
1909 if (mode == MENU_INDEX_INVALID)
1910 return 0;
1911 return 1 << mode;
1912}
1913
Bram Moolenaar071d4272004-06-13 20:20:40 +00001914/*
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001915 * Display the Special "PopUp" menu as a pop-up at the current mouse
1916 * position. The "PopUpn" menu is for Normal mode, "PopUpi" for Insert mode,
1917 * etc.
1918 */
1919 void
1920show_popupmenu(void)
1921{
1922 vimmenu_T *menu;
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001923 int menu_mode;
1924 char* mode;
1925 int mode_len;
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001926
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001927 menu_mode = get_menu_mode();
1928 if (menu_mode == MENU_INDEX_INVALID)
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001929 return;
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001930 mode = menu_mode_chars[menu_mode];
1931 mode_len = (int)strlen(mode);
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001932
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001933 apply_autocmds(EVENT_MENUPOPUP, (char_u*)mode, NULL, FALSE, curbuf);
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001934
1935 for (menu = root_menu; menu != NULL; menu = menu->next)
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001936 if (STRNCMP("PopUp", menu->name, 5) == 0 && STRNCMP(menu->name + 5, mode, mode_len) == 0)
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001937 break;
1938
1939 /* Only show a popup when it is defined and has entries */
1940 if (menu != NULL && menu->children != NULL)
1941 {
1942# if defined(FEAT_GUI)
1943 if (gui.in_use)
1944 {
1945 /* Update the menus now, in case the MenuPopup autocommand did
1946 * anything. */
1947 gui_update_menus(0);
1948 gui_mch_show_popupmenu(menu);
1949 }
1950# endif
1951# if defined(FEAT_GUI) && defined(FEAT_TERM_POPUP_MENU)
1952 else
1953# endif
1954# if defined(FEAT_TERM_POPUP_MENU)
1955 pum_show_popupmenu(menu);
1956# endif
1957 }
1958}
1959#endif
1960
1961#if defined(FEAT_GUI) || defined(PROTO)
1962
1963/*
Bram Moolenaar968bbbe2006-08-16 19:41:08 +00001964 * Check that a pointer appears in the menu tree. Used to protect from using
1965 * a menu that was deleted after it was selected but before the event was
1966 * handled.
1967 * Return OK or FAIL. Used recursively.
1968 */
1969 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001970check_menu_pointer(vimmenu_T *root, vimmenu_T *menu_to_check)
Bram Moolenaar968bbbe2006-08-16 19:41:08 +00001971{
1972 vimmenu_T *p;
1973
1974 for (p = root; p != NULL; p = p->next)
1975 if (p == menu_to_check
1976 || (p->children != NULL
1977 && check_menu_pointer(p->children, menu_to_check) == OK))
1978 return OK;
1979 return FAIL;
1980}
1981
1982/*
Bram Moolenaar071d4272004-06-13 20:20:40 +00001983 * After we have started the GUI, then we can create any menus that have been
1984 * defined. This is done once here. add_menu_path() may have already been
1985 * called to define these menus, and may be called again. This function calls
1986 * itself recursively. Should be called at the top level with:
Bram Moolenaara06ecab2016-07-16 14:47:36 +02001987 * gui_create_initial_menus(root_menu);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001988 */
1989 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001990gui_create_initial_menus(vimmenu_T *menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001991{
1992 int idx = 0;
1993
1994 while (menu != NULL)
1995 {
1996 /* Don't add a menu when only a tip was defined. */
1997 if (menu->modes & MENU_ALL_MODES)
1998 {
1999 if (menu->children != NULL)
2000 {
2001 gui_mch_add_menu(menu, idx);
2002 gui_create_initial_menus(menu->children);
2003 }
2004 else
2005 gui_mch_add_menu_item(menu, idx);
2006 }
2007 menu = menu->next;
2008 ++idx;
2009 }
2010}
2011
2012/*
2013 * Used recursively by gui_update_menus (see below)
2014 */
2015 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002016gui_update_menus_recurse(vimmenu_T *menu, int mode)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002017{
2018 int grey;
2019
2020 while (menu)
2021 {
2022 if ((menu->modes & menu->enabled & mode)
Bram Moolenaar4f974752019-02-17 17:44:42 +01002023# if defined(FEAT_GUI_MSWIN) && defined(FEAT_TEAROFF)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002024 || menu_is_tearoff(menu->dname)
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002025# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002026 )
2027 grey = FALSE;
2028 else
2029 grey = TRUE;
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002030# ifdef FEAT_GUI_ATHENA
Bram Moolenaar071d4272004-06-13 20:20:40 +00002031 /* Hiding menus doesn't work for Athena, it can cause a crash. */
2032 gui_mch_menu_grey(menu, grey);
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002033# else
Bram Moolenaar071d4272004-06-13 20:20:40 +00002034 /* Never hide a toplevel menu, it may make the menubar resize or
2035 * disappear. Same problem for ToolBar items. */
2036 if (vim_strchr(p_go, GO_GREY) != NULL || menu->parent == NULL
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002037# ifdef FEAT_TOOLBAR
Bram Moolenaar071d4272004-06-13 20:20:40 +00002038 || menu_is_toolbar(menu->parent->name)
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002039# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002040 )
2041 gui_mch_menu_grey(menu, grey);
2042 else
2043 gui_mch_menu_hidden(menu, grey);
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002044# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002045 gui_update_menus_recurse(menu->children, mode);
2046 menu = menu->next;
2047 }
2048}
2049
2050/*
2051 * Make sure only the valid menu items appear for this mode. If
2052 * force_menu_update is not TRUE, then we only do this if the mode has changed
2053 * since last time. If "modes" is not 0, then we use these modes instead.
2054 */
2055 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002056gui_update_menus(int modes)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002057{
2058 static int prev_mode = -1;
2059 int mode = 0;
2060
2061 if (modes != 0x0)
2062 mode = modes;
2063 else
Bram Moolenaar29a2c082018-03-05 21:06:23 +01002064 mode = get_menu_mode_flag();
Bram Moolenaar071d4272004-06-13 20:20:40 +00002065
2066 if (force_menu_update || mode != prev_mode)
2067 {
2068 gui_update_menus_recurse(root_menu, mode);
2069 gui_mch_draw_menubar();
2070 prev_mode = mode;
2071 force_menu_update = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002072 }
2073}
2074
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002075# if defined(FEAT_GUI_MSWIN) || defined(FEAT_GUI_MOTIF) \
Bram Moolenaar241a8aa2005-12-06 20:04:44 +00002076 || defined(FEAT_GUI_GTK) || defined(FEAT_GUI_PHOTON) || defined(PROTO)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002077/*
2078 * Check if a key is used as a mnemonic for a toplevel menu.
2079 * Case of the key is ignored.
2080 */
2081 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002082gui_is_menu_shortcut(int key)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002083{
2084 vimmenu_T *menu;
2085
2086 if (key < 256)
2087 key = TOLOWER_LOC(key);
2088 for (menu = root_menu; menu != NULL; menu = menu->next)
2089 if (menu->mnemonic == key
2090 || (menu->mnemonic < 256 && TOLOWER_LOC(menu->mnemonic) == key))
2091 return TRUE;
2092 return FALSE;
2093}
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002094# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002095#endif /* FEAT_GUI */
2096
Bram Moolenaar4f974752019-02-17 17:44:42 +01002097#if (defined(FEAT_GUI_MSWIN) && defined(FEAT_TEAROFF)) || defined(PROTO)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002098
2099/*
2100 * Deal with tearoff items that are added like a menu item.
2101 * Currently only for Win32 GUI. Others may follow later.
2102 */
2103
2104 void
2105gui_mch_toggle_tearoffs(int enable)
2106{
2107 int pri_tab[MENUDEPTH + 1];
2108 int i;
2109
2110 if (enable)
2111 {
2112 for (i = 0; i < MENUDEPTH; ++i)
2113 pri_tab[i] = 500;
2114 pri_tab[MENUDEPTH] = -1;
2115 gui_create_tearoffs_recurse(root_menu, (char_u *)"", pri_tab, 0);
2116 }
2117 else
2118 gui_destroy_tearoffs_recurse(root_menu);
2119 s_tearoffs = enable;
2120}
2121
2122/*
2123 * Recursively add tearoff items
2124 */
2125 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002126gui_create_tearoffs_recurse(
2127 vimmenu_T *menu,
2128 const char_u *pname,
2129 int *pri_tab,
2130 int pri_idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002131{
2132 char_u *newpname = NULL;
2133 int len;
2134 char_u *s;
2135 char_u *d;
2136
2137 if (pri_tab[pri_idx + 1] != -1)
2138 ++pri_idx;
2139 while (menu != NULL)
2140 {
2141 if (menu->children != NULL && menu_is_menubar(menu->name))
2142 {
2143 /* Add the menu name to the menu path. Insert a backslash before
2144 * dots (it's used to separate menu names). */
2145 len = (int)STRLEN(pname) + (int)STRLEN(menu->name);
2146 for (s = menu->name; *s; ++s)
2147 if (*s == '.' || *s == '\\')
2148 ++len;
2149 newpname = alloc(len + TEAR_LEN + 2);
2150 if (newpname != NULL)
2151 {
2152 STRCPY(newpname, pname);
2153 d = newpname + STRLEN(newpname);
2154 for (s = menu->name; *s; ++s)
2155 {
2156 if (*s == '.' || *s == '\\')
2157 *d++ = '\\';
2158 *d++ = *s;
2159 }
2160 *d = NUL;
2161
2162 /* check if tearoff already exists */
2163 if (STRCMP(menu->children->name, TEAR_STRING) != 0)
2164 {
2165 gui_add_tearoff(newpname, pri_tab, pri_idx - 1);
2166 *d = NUL; /* remove TEAR_STRING */
2167 }
2168
2169 STRCAT(newpname, ".");
2170 gui_create_tearoffs_recurse(menu->children, newpname,
2171 pri_tab, pri_idx);
2172 vim_free(newpname);
2173 }
2174 }
2175 menu = menu->next;
2176 }
2177}
2178
2179/*
2180 * Add tear-off menu item for a submenu.
2181 * "tearpath" is the menu path, and must have room to add TEAR_STRING.
2182 */
2183 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002184gui_add_tearoff(char_u *tearpath, int *pri_tab, int pri_idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002185{
2186 char_u *tbuf;
2187 int t;
2188 vimmenu_T menuarg;
2189
2190 tbuf = alloc(5 + (unsigned int)STRLEN(tearpath));
2191 if (tbuf != NULL)
2192 {
2193 tbuf[0] = K_SPECIAL;
2194 tbuf[1] = K_SECOND(K_TEAROFF);
2195 tbuf[2] = K_THIRD(K_TEAROFF);
2196 STRCPY(tbuf + 3, tearpath);
2197 STRCAT(tbuf + 3, "\r");
2198
2199 STRCAT(tearpath, ".");
2200 STRCAT(tearpath, TEAR_STRING);
2201
2202 /* Priority of tear-off is always 1 */
2203 t = pri_tab[pri_idx + 1];
2204 pri_tab[pri_idx + 1] = 1;
2205
2206#ifdef FEAT_TOOLBAR
2207 menuarg.iconfile = NULL;
2208 menuarg.iconidx = -1;
2209 menuarg.icon_builtin = FALSE;
2210#endif
2211 menuarg.noremap[0] = REMAP_NONE;
2212 menuarg.silent[0] = TRUE;
2213
2214 menuarg.modes = MENU_ALL_MODES;
2215 add_menu_path(tearpath, &menuarg, pri_tab, tbuf, FALSE);
2216
2217 menuarg.modes = MENU_TIP_MODE;
2218 add_menu_path(tearpath, &menuarg, pri_tab,
2219 (char_u *)_("Tear off this menu"), FALSE);
2220
2221 pri_tab[pri_idx + 1] = t;
2222 vim_free(tbuf);
2223 }
2224}
2225
2226/*
2227 * Recursively destroy tearoff items
2228 */
2229 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002230gui_destroy_tearoffs_recurse(vimmenu_T *menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002231{
2232 while (menu)
2233 {
2234 if (menu->children)
2235 {
2236 /* check if tearoff exists */
2237 if (STRCMP(menu->children->name, TEAR_STRING) == 0)
2238 {
2239 /* Disconnect the item and free the memory */
2240 free_menu(&menu->children);
2241 }
2242 if (menu->children != NULL) /* if not the last one */
2243 gui_destroy_tearoffs_recurse(menu->children);
2244 }
2245 menu = menu->next;
2246 }
2247}
2248
Bram Moolenaar4f974752019-02-17 17:44:42 +01002249#endif /* FEAT_GUI_MSWIN && FEAT_TEAROFF */
Bram Moolenaar071d4272004-06-13 20:20:40 +00002250
2251/*
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002252 * Execute "menu". Use by ":emenu" and the window toolbar.
2253 * "eap" is NULL for the window toolbar.
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002254 * "mode_idx" specifies a MENU_INDEX_ value, use -1 to depend on the current
2255 * state.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002256 */
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002257 void
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002258execute_menu(exarg_T *eap, vimmenu_T *menu, int mode_idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002259{
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002260 int idx = mode_idx;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002261
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002262 if (idx < 0)
2263 {
2264 /* Use the Insert mode entry when returning to Insert mode. */
2265 if (restart_edit
Bram Moolenaar4463f292005-09-25 22:20:24 +00002266#ifdef FEAT_EVAL
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002267 && !current_sctx.sc_sid
Bram Moolenaar4463f292005-09-25 22:20:24 +00002268#endif
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002269 )
Bram Moolenaar071d4272004-06-13 20:20:40 +00002270 {
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002271 idx = MENU_INDEX_INSERT;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002272 }
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002273#ifdef FEAT_TERMINAL
2274 else if (term_use_loop())
Bram Moolenaar071d4272004-06-13 20:20:40 +00002275 {
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002276 idx = MENU_INDEX_TERMINAL;
2277 }
2278#endif
2279 else if (VIsual_active)
2280 {
2281 idx = MENU_INDEX_VISUAL;
2282 }
2283 else if (eap != NULL && eap->addr_count)
2284 {
2285 pos_T tpos;
2286
2287 idx = MENU_INDEX_VISUAL;
2288
2289 /* GEDDES: This is not perfect - but it is a
2290 * quick way of detecting whether we are doing this from a
2291 * selection - see if the range matches up with the visual
2292 * select start and end. */
2293 if ((curbuf->b_visual.vi_start.lnum == eap->line1)
2294 && (curbuf->b_visual.vi_end.lnum) == eap->line2)
2295 {
2296 /* Set it up for visual mode - equivalent to gv. */
2297 VIsual_mode = curbuf->b_visual.vi_mode;
2298 tpos = curbuf->b_visual.vi_end;
2299 curwin->w_cursor = curbuf->b_visual.vi_start;
2300 curwin->w_curswant = curbuf->b_visual.vi_curswant;
2301 }
2302 else
2303 {
2304 /* Set it up for line-wise visual mode */
2305 VIsual_mode = 'V';
2306 curwin->w_cursor.lnum = eap->line1;
2307 curwin->w_cursor.col = 1;
2308 tpos.lnum = eap->line2;
2309 tpos.col = MAXCOL;
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002310 tpos.coladd = 0;
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002311 }
2312
2313 /* Activate visual mode */
2314 VIsual_active = TRUE;
2315 VIsual_reselect = TRUE;
2316 check_cursor();
2317 VIsual = curwin->w_cursor;
2318 curwin->w_cursor = tpos;
2319
2320 check_cursor();
2321
2322 /* Adjust the cursor to make sure it is in the correct pos
2323 * for exclusive mode */
2324 if (*p_sel == 'e' && gchar_cursor() != NUL)
2325 ++curwin->w_cursor.col;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002326 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00002327 }
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002328
2329 /* For the WinBar menu always use the Normal mode menu. */
2330 if (idx == -1 || eap == NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002331 idx = MENU_INDEX_NORMAL;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002332
Bram Moolenaarce793532019-05-05 14:19:20 +02002333 if (idx != MENU_INDEX_INVALID && menu->strings[idx] != NULL
2334 && (menu->modes & (1 << idx)))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002335 {
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002336 /* When executing a script or function execute the commands right now.
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002337 * Also for the window toolbar.
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002338 * Otherwise put them in the typeahead buffer. */
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002339 if (eap == NULL
Bram Moolenaar9c4b4ab2006-12-05 20:29:56 +00002340#ifdef FEAT_EVAL
Bram Moolenaarf29c1c62018-09-10 21:05:02 +02002341 || current_sctx.sc_sid != 0
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002342#endif
2343 )
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002344 {
2345 save_state_T save_state;
2346
2347 ++ex_normal_busy;
2348 if (save_current_state(&save_state))
2349 exec_normal_cmd(menu->strings[idx], menu->noremap[idx],
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002350 menu->silent[idx]);
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002351 restore_current_state(&save_state);
2352 --ex_normal_busy;
2353 }
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002354 else
2355 ins_typebuf(menu->strings[idx], menu->noremap[idx], 0,
Bram Moolenaar071d4272004-06-13 20:20:40 +00002356 TRUE, menu->silent[idx]);
2357 }
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002358 else if (eap != NULL)
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002359 {
2360 char_u *mode;
2361
2362 switch (idx)
2363 {
2364 case MENU_INDEX_VISUAL:
2365 mode = (char_u *)"Visual";
2366 break;
2367 case MENU_INDEX_SELECT:
2368 mode = (char_u *)"Select";
2369 break;
2370 case MENU_INDEX_OP_PENDING:
2371 mode = (char_u *)"Op-pending";
2372 break;
2373 case MENU_INDEX_TERMINAL:
2374 mode = (char_u *)"Terminal";
2375 break;
2376 case MENU_INDEX_INSERT:
2377 mode = (char_u *)"Insert";
2378 break;
2379 case MENU_INDEX_CMDLINE:
2380 mode = (char_u *)"Cmdline";
2381 break;
2382 // case MENU_INDEX_TIP: cannot happen
2383 default:
2384 mode = (char_u *)"Normal";
2385 }
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01002386 semsg(_("E335: Menu not defined for %s mode"), mode);
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002387 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00002388}
2389
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002390/*
2391 * Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy and
2392 * execute it.
2393 */
2394 void
2395ex_emenu(exarg_T *eap)
2396{
2397 vimmenu_T *menu;
2398 char_u *name;
2399 char_u *saved_name;
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002400 char_u *arg = eap->arg;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002401 char_u *p;
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002402 int gave_emsg = FALSE;
2403 int mode_idx = -1;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002404
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002405 if (arg[0] && VIM_ISWHITE(arg[1]))
2406 {
2407 switch (arg[0])
2408 {
2409 case 'n': mode_idx = MENU_INDEX_NORMAL; break;
2410 case 'v': mode_idx = MENU_INDEX_VISUAL; break;
2411 case 's': mode_idx = MENU_INDEX_SELECT; break;
2412 case 'o': mode_idx = MENU_INDEX_OP_PENDING; break;
2413 case 't': mode_idx = MENU_INDEX_TERMINAL; break;
2414 case 'i': mode_idx = MENU_INDEX_INSERT; break;
2415 case 'c': mode_idx = MENU_INDEX_CMDLINE; break;
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01002416 default: semsg(_(e_invarg2), arg);
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002417 return;
2418 }
2419 arg = skipwhite(arg + 2);
2420 }
2421
2422 saved_name = vim_strsave(arg);
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002423 if (saved_name == NULL)
2424 return;
2425
2426 menu = *get_root_menu(saved_name);
2427 name = saved_name;
2428 while (*name)
2429 {
2430 /* Find in the menu hierarchy */
2431 p = menu_name_skip(name);
2432
2433 while (menu != NULL)
2434 {
2435 if (menu_name_equal(name, menu))
2436 {
2437 if (*p == NUL && menu->children != NULL)
2438 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01002439 emsg(_("E333: Menu path must lead to a menu item"));
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002440 gave_emsg = TRUE;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002441 menu = NULL;
2442 }
2443 else if (*p != NUL && menu->children == NULL)
2444 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01002445 emsg(_(e_notsubmenu));
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002446 menu = NULL;
2447 }
2448 break;
2449 }
2450 menu = menu->next;
2451 }
2452 if (menu == NULL || *p == NUL)
2453 break;
2454 menu = menu->children;
2455 name = p;
2456 }
2457 vim_free(saved_name);
2458 if (menu == NULL)
2459 {
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002460 if (!gave_emsg)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01002461 semsg(_("E334: Menu not found: %s"), arg);
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002462 return;
2463 }
2464
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002465 // Found the menu, so execute.
2466 execute_menu(eap, menu, mode_idx);
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002467}
2468
2469/*
2470 * Handle a click in the window toolbar of "wp" at column "col".
2471 */
2472 void
2473winbar_click(win_T *wp, int col)
2474{
2475 int idx;
2476
2477 if (wp->w_winbar_items == NULL)
2478 return;
2479 for (idx = 0; wp->w_winbar_items[idx].wb_menu != NULL; ++idx)
2480 {
2481 winbar_item_T *item = &wp->w_winbar_items[idx];
2482
2483 if (col >= item->wb_startcol && col <= item->wb_endcol)
2484 {
Bram Moolenaard2fad672019-05-04 16:55:25 +02002485 win_T *save_curwin = NULL;
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002486 pos_T save_visual = VIsual;
2487 int save_visual_active = VIsual_active;
2488 int save_visual_select = VIsual_select;
2489 int save_visual_reselect = VIsual_reselect;
2490 int save_visual_mode = VIsual_mode;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002491
2492 if (wp != curwin)
2493 {
2494 /* Clicking in the window toolbar of a not-current window.
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002495 * Make that window the current one and save Visual mode. */
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002496 save_curwin = curwin;
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002497 VIsual_active = FALSE;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002498 curwin = wp;
2499 curbuf = curwin->w_buffer;
2500 check_cursor();
2501 }
2502
Bram Moolenaard2fad672019-05-04 16:55:25 +02002503 // Note: the command might close the current window.
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002504 execute_menu(NULL, item->wb_menu, -1);
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002505
Bram Moolenaard2fad672019-05-04 16:55:25 +02002506 if (save_curwin != NULL && win_valid(save_curwin))
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002507 {
2508 curwin = save_curwin;
2509 curbuf = curwin->w_buffer;
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002510 VIsual = save_visual;
2511 VIsual_active = save_visual_active;
2512 VIsual_select = save_visual_select;
2513 VIsual_reselect = save_visual_reselect;
2514 VIsual_mode = save_visual_mode;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002515 }
Bram Moolenaard2fad672019-05-04 16:55:25 +02002516 if (!win_valid(wp))
2517 break;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002518 }
2519 }
2520}
2521
2522#if defined(FEAT_GUI_MSWIN) || defined(FEAT_GUI_GTK) \
Bram Moolenaar40d77b02018-03-05 21:32:27 +01002523 || defined(FEAT_TERM_POPUP_MENU) \
Bram Moolenaar071d4272004-06-13 20:20:40 +00002524 || defined(FEAT_BEVAL_TIP) || defined(PROTO)
2525/*
2526 * Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy.
2527 */
2528 vimmenu_T *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002529gui_find_menu(char_u *path_name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002530{
2531 vimmenu_T *menu = NULL;
2532 char_u *name;
2533 char_u *saved_name;
2534 char_u *p;
2535
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002536 menu = *get_root_menu(path_name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002537
2538 saved_name = vim_strsave(path_name);
2539 if (saved_name == NULL)
2540 return NULL;
2541
2542 name = saved_name;
2543 while (*name)
2544 {
2545 /* find the end of one dot-separated name and put a NUL at the dot */
2546 p = menu_name_skip(name);
2547
2548 while (menu != NULL)
2549 {
Bram Moolenaard91f7042011-01-04 17:49:32 +01002550 if (menu_name_equal(name, menu))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002551 {
2552 if (menu->children == NULL)
2553 {
2554 /* found a menu item instead of a sub-menu */
2555 if (*p == NUL)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01002556 emsg(_("E336: Menu path must lead to a sub-menu"));
Bram Moolenaar071d4272004-06-13 20:20:40 +00002557 else
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01002558 emsg(_(e_notsubmenu));
Bram Moolenaar071d4272004-06-13 20:20:40 +00002559 menu = NULL;
2560 goto theend;
2561 }
2562 if (*p == NUL) /* found a full match */
2563 goto theend;
2564 break;
2565 }
2566 menu = menu->next;
2567 }
2568 if (menu == NULL) /* didn't find it */
2569 break;
2570
2571 /* Found a match, search the sub-menu. */
2572 menu = menu->children;
2573 name = p;
2574 }
2575
2576 if (menu == NULL)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01002577 emsg(_("E337: Menu not found - check menu names"));
Bram Moolenaar071d4272004-06-13 20:20:40 +00002578theend:
2579 vim_free(saved_name);
2580 return menu;
2581}
2582#endif
2583
2584#ifdef FEAT_MULTI_LANG
2585/*
2586 * Translation of menu names. Just a simple lookup table.
2587 */
2588
2589typedef struct
2590{
2591 char_u *from; /* English name */
2592 char_u *from_noamp; /* same, without '&' */
2593 char_u *to; /* translated name */
2594} menutrans_T;
2595
2596static garray_T menutrans_ga = {0, 0, 0, 0, NULL};
2597#endif
2598
2599/*
2600 * ":menutrans".
2601 * This function is also defined without the +multi_lang feature, in which
2602 * case the commands are ignored.
2603 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00002604 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002605ex_menutranslate(exarg_T *eap UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002606{
2607#ifdef FEAT_MULTI_LANG
2608 char_u *arg = eap->arg;
2609 menutrans_T *tp;
2610 int i;
2611 char_u *from, *from_noamp, *to;
2612
2613 if (menutrans_ga.ga_itemsize == 0)
2614 ga_init2(&menutrans_ga, (int)sizeof(menutrans_T), 5);
2615
2616 /*
2617 * ":menutrans clear": clear all translations.
2618 */
2619 if (STRNCMP(arg, "clear", 5) == 0 && ends_excmd(*skipwhite(arg + 5)))
2620 {
2621 tp = (menutrans_T *)menutrans_ga.ga_data;
2622 for (i = 0; i < menutrans_ga.ga_len; ++i)
2623 {
2624 vim_free(tp[i].from);
2625 vim_free(tp[i].from_noamp);
2626 vim_free(tp[i].to);
2627 }
2628 ga_clear(&menutrans_ga);
2629# ifdef FEAT_EVAL
2630 /* Delete all "menutrans_" global variables. */
2631 del_menutrans_vars();
2632# endif
2633 }
2634 else
2635 {
2636 /* ":menutrans from to": add translation */
2637 from = arg;
2638 arg = menu_skip_part(arg);
2639 to = skipwhite(arg);
2640 *arg = NUL;
2641 arg = menu_skip_part(to);
2642 if (arg == to)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01002643 emsg(_(e_invarg));
Bram Moolenaar071d4272004-06-13 20:20:40 +00002644 else
2645 {
2646 if (ga_grow(&menutrans_ga, 1) == OK)
2647 {
2648 tp = (menutrans_T *)menutrans_ga.ga_data;
2649 from = vim_strsave(from);
Bram Moolenaarfc1421e2006-04-20 22:17:20 +00002650 if (from != NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002651 {
Bram Moolenaarfc1421e2006-04-20 22:17:20 +00002652 from_noamp = menu_text(from, NULL, NULL);
2653 to = vim_strnsave(to, (int)(arg - to));
2654 if (from_noamp != NULL && to != NULL)
2655 {
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002656 menu_translate_tab_and_shift(from);
2657 menu_translate_tab_and_shift(to);
2658 menu_unescape_name(from);
2659 menu_unescape_name(to);
Bram Moolenaarfc1421e2006-04-20 22:17:20 +00002660 tp[menutrans_ga.ga_len].from = from;
2661 tp[menutrans_ga.ga_len].from_noamp = from_noamp;
2662 tp[menutrans_ga.ga_len].to = to;
2663 ++menutrans_ga.ga_len;
2664 }
2665 else
2666 {
2667 vim_free(from);
2668 vim_free(from_noamp);
2669 vim_free(to);
2670 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00002671 }
2672 }
2673 }
2674 }
2675#endif
2676}
2677
2678#if defined(FEAT_MULTI_LANG) || defined(FEAT_TOOLBAR)
2679/*
2680 * Find the character just after one part of a menu name.
2681 */
2682 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002683menu_skip_part(char_u *p)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002684{
Bram Moolenaar1c465442017-03-12 20:10:05 +01002685 while (*p != NUL && *p != '.' && !VIM_ISWHITE(*p))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002686 {
2687 if ((*p == '\\' || *p == Ctrl_V) && p[1] != NUL)
2688 ++p;
2689 ++p;
2690 }
2691 return p;
2692}
2693#endif
2694
2695#ifdef FEAT_MULTI_LANG
2696/*
2697 * Lookup part of a menu name in the translations.
2698 * Return a pointer to the translation or NULL if not found.
2699 */
2700 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002701menutrans_lookup(char_u *name, int len)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002702{
2703 menutrans_T *tp = (menutrans_T *)menutrans_ga.ga_data;
2704 int i;
2705 char_u *dname;
2706
2707 for (i = 0; i < menutrans_ga.ga_len; ++i)
Bram Moolenaar11dd8c12017-03-04 20:41:34 +01002708 if (STRNICMP(name, tp[i].from, len) == 0 && tp[i].from[len] == NUL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002709 return tp[i].to;
2710
2711 /* Now try again while ignoring '&' characters. */
2712 i = name[len];
2713 name[len] = NUL;
2714 dname = menu_text(name, NULL, NULL);
2715 name[len] = i;
2716 if (dname != NULL)
2717 {
2718 for (i = 0; i < menutrans_ga.ga_len; ++i)
Bram Moolenaar11dd8c12017-03-04 20:41:34 +01002719 if (STRICMP(dname, tp[i].from_noamp) == 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002720 {
2721 vim_free(dname);
2722 return tp[i].to;
2723 }
2724 vim_free(dname);
2725 }
2726
2727 return NULL;
2728}
Bram Moolenaar071d4272004-06-13 20:20:40 +00002729
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002730/*
2731 * Unescape the name in the translate dictionary table.
2732 */
2733 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002734menu_unescape_name(char_u *name)
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002735{
2736 char_u *p;
2737
Bram Moolenaar91acfff2017-03-12 19:22:36 +01002738 for (p = name; *p && *p != '.'; MB_PTR_ADV(p))
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002739 if (*p == '\\')
2740 STRMOVE(p, p + 1);
2741}
Bram Moolenaar56be9502010-06-06 14:20:26 +02002742#endif /* FEAT_MULTI_LANG */
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002743
2744/*
2745 * Isolate the menu name.
2746 * Skip the menu name, and translate <Tab> into a real TAB.
2747 */
2748 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002749menu_translate_tab_and_shift(char_u *arg_start)
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002750{
2751 char_u *arg = arg_start;
2752
Bram Moolenaar1c465442017-03-12 20:10:05 +01002753 while (*arg && !VIM_ISWHITE(*arg))
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002754 {
2755 if ((*arg == '\\' || *arg == Ctrl_V) && arg[1] != NUL)
2756 arg++;
2757 else if (STRNICMP(arg, "<TAB>", 5) == 0)
2758 {
2759 *arg = TAB;
2760 STRMOVE(arg + 1, arg + 5);
2761 }
2762 arg++;
2763 }
2764 if (*arg != NUL)
2765 *arg++ = NUL;
2766 arg = skipwhite(arg);
2767
2768 return arg;
2769}
2770
Bram Moolenaar071d4272004-06-13 20:20:40 +00002771#endif /* FEAT_MENU */