blob: b01792594b06d4bcc4dce8ef789fa17c6c01541c [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
Bram Moolenaar4ba37b52019-12-04 21:57:43 +010019#define MENUDEPTH 10 // maximum depth of menus
Bram Moolenaar071d4272004-06-13 20:20:40 +000020
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 Moolenaar4ba37b52019-12-04 21:57:43 +010059// 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(
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100109 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];
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100129 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
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100164 // Locate an optional "icon=filename" argument.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000165 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;
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100211 pri_tab[MENUDEPTH] = -1; // mark end of the table
Bram Moolenaar071d4272004-06-13 20:20:40 +0000212
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)
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100304 // Assume the window toolbar menu will change.
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200305 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 */
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100314 if (STRCMP(menu_path, "*") == 0) // meaning: do all menus
Bram Moolenaar071d4272004-06-13 20:20:40 +0000315 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 */
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100338 if (STRCMP(menu_path, "*") == 0) // meaning: remove all menus
Bram Moolenaar071d4272004-06-13 20:20:40 +0000339 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
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100358 // 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 */
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100367 if (STRICMP(map_to, "<nop>") == 0) // "<Nop>" means nothing
Bram Moolenaar071d4272004-06-13 20:20:40 +0000368 {
369 map_to = (char_u *)"";
370 map_buf = NULL;
371 }
Bram Moolenaar3fdfa4a2004-10-07 21:02:47 +0000372 else if (modes & MENU_TIP_MODE)
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100373 map_buf = NULL; // Menu tips are plain text.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000374 else
Bram Moolenaar459fd782019-10-13 16:43:39 +0200375 map_to = replace_termcodes(map_to, &map_buf,
376 REPTERM_DO_LT | (special ? REPTERM_SPECIAL : 0), NULL);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000377 menuarg.modes = modes;
378#ifdef FEAT_TOOLBAR
379 menuarg.iconfile = icon;
380#endif
381 menuarg.noremap[0] = noremap;
382 menuarg.silent[0] = silent;
383 add_menu_path(menu_path, &menuarg, pri_tab, map_to
Bram Moolenaar4f974752019-02-17 17:44:42 +0100384#ifdef FEAT_GUI_MSWIN
Bram Moolenaar071d4272004-06-13 20:20:40 +0000385 , TRUE
386#endif
387 );
388
389 /*
390 * For the PopUp menu, add a menu for each mode separately.
391 */
392 if (menu_is_popup(menu_path))
393 {
394 for (i = 0; i < MENU_INDEX_TIP; ++i)
395 if (modes & (1 << i))
396 {
397 p = popup_mode_name(menu_path, i);
398 if (p != NULL)
399 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100400 // Include all modes, to make ":amenu" work
Bram Moolenaar071d4272004-06-13 20:20:40 +0000401 menuarg.modes = modes;
402#ifdef FEAT_TOOLBAR
403 menuarg.iconfile = NULL;
404 menuarg.iconidx = -1;
405 menuarg.icon_builtin = FALSE;
406#endif
407 add_menu_path(p, &menuarg, pri_tab, map_to
Bram Moolenaar4f974752019-02-17 17:44:42 +0100408#ifdef FEAT_GUI_MSWIN
Bram Moolenaar071d4272004-06-13 20:20:40 +0000409 , TRUE
410#endif
411 );
412 vim_free(p);
413 }
414 }
415 }
416
417 vim_free(map_buf);
418 }
419
Bram Moolenaar241a8aa2005-12-06 20:04:44 +0000420#if defined(FEAT_GUI) && !(defined(FEAT_GUI_GTK))
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100421 // If the menubar height changed, resize the window
Bram Moolenaar071d4272004-06-13 20:20:40 +0000422 if (gui.in_use
423 && (gui.menu_height != old_menu_height
Bram Moolenaar4f974752019-02-17 17:44:42 +0100424# if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_MSWIN)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000425 || gui.toolbar_height != old_toolbar_height
426# endif
427 ))
Bram Moolenaar04a9d452006-03-27 21:03:26 +0000428 gui_set_shellsize(FALSE, FALSE, RESIZE_VERT);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000429#endif
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200430 if (root_menu_ptr == &curwin->w_winbar)
431 {
432 int h = winbar_height(curwin);
433
434 if (h != curwin->w_winbar_height)
435 {
436 if (h == 0)
437 ++curwin->w_height;
438 else if (curwin->w_height > 0)
439 --curwin->w_height;
440 curwin->w_winbar_height = h;
441 }
442 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000443
444theend:
Bram Moolenaar071d4272004-06-13 20:20:40 +0000445 ;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000446}
447
448/*
449 * Add the menu with the given name to the menu hierarchy
450 */
451 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100452add_menu_path(
453 char_u *menu_path,
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100454 vimmenu_T *menuarg, // passes modes, iconfile, iconidx,
455 // icon_builtin, silent[0], noremap[0]
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100456 int *pri_tab,
457 char_u *call_data
Bram Moolenaar4f974752019-02-17 17:44:42 +0100458#ifdef FEAT_GUI_MSWIN
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100459 , int addtearoff // may add tearoff item
Bram Moolenaar071d4272004-06-13 20:20:40 +0000460#endif
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100461 )
Bram Moolenaar071d4272004-06-13 20:20:40 +0000462{
463 char_u *path_name;
464 int modes = menuarg->modes;
465 vimmenu_T **menup;
466 vimmenu_T *menu = NULL;
467 vimmenu_T *parent;
468 vimmenu_T **lower_pri;
469 char_u *p;
470 char_u *name;
471 char_u *dname;
472 char_u *next_name;
473 int i;
474 int c;
Bram Moolenaar7871a502010-05-14 21:19:23 +0200475 int d;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000476#ifdef FEAT_GUI
477 int idx;
478 int new_idx;
479#endif
480 int pri_idx = 0;
481 int old_modes = 0;
482 int amenu;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +0200483#ifdef FEAT_MULTI_LANG
484 char_u *en_name;
485 char_u *map_to = NULL;
486#endif
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200487 vimmenu_T **root_menu_ptr;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000488
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100489 // Make a copy so we can stuff around with it, since it could be const
Bram Moolenaar071d4272004-06-13 20:20:40 +0000490 path_name = vim_strsave(menu_path);
491 if (path_name == NULL)
492 return FAIL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200493 root_menu_ptr = get_root_menu(menu_path);
494 menup = root_menu_ptr;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000495 parent = NULL;
496 name = path_name;
497 while (*name)
498 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100499 // Get name of this element in the menu hierarchy, and the simplified
500 // name (without mnemonic and accelerator text).
Bram Moolenaar071d4272004-06-13 20:20:40 +0000501 next_name = menu_name_skip(name);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +0200502#ifdef FEAT_MULTI_LANG
Bram Moolenaar442b4222010-05-24 21:34:22 +0200503 map_to = menutrans_lookup(name, (int)STRLEN(name));
Bram Moolenaar70b11cd2010-05-14 22:24:40 +0200504 if (map_to != NULL)
505 {
506 en_name = name;
507 name = map_to;
508 }
509 else
510 en_name = NULL;
511#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000512 dname = menu_text(name, NULL, NULL);
Bram Moolenaar18a0b122006-08-16 13:55:16 +0000513 if (dname == NULL)
514 goto erret;
515 if (*dname == NUL)
516 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100517 // Only a mnemonic or accelerator is not valid.
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100518 emsg(_("E792: Empty menu name"));
Bram Moolenaar18a0b122006-08-16 13:55:16 +0000519 goto erret;
520 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000521
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100522 // See if it's already there
Bram Moolenaar071d4272004-06-13 20:20:40 +0000523 lower_pri = menup;
524#ifdef FEAT_GUI
525 idx = 0;
526 new_idx = 0;
527#endif
528 menu = *menup;
529 while (menu != NULL)
530 {
531 if (menu_name_equal(name, menu) || menu_name_equal(dname, menu))
532 {
533 if (*next_name == NUL && menu->children != NULL)
534 {
535 if (!sys_menu)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100536 emsg(_("E330: Menu path must not lead to a sub-menu"));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000537 goto erret;
538 }
539 if (*next_name != NUL && menu->children == NULL
Bram Moolenaar4f974752019-02-17 17:44:42 +0100540#ifdef FEAT_GUI_MSWIN
Bram Moolenaar071d4272004-06-13 20:20:40 +0000541 && addtearoff
542#endif
543 )
544 {
545 if (!sys_menu)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100546 emsg(_(e_notsubmenu));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000547 goto erret;
548 }
549 break;
550 }
551 menup = &menu->next;
552
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100553 // Count menus, to find where this one needs to be inserted.
554 // Ignore menus that are not in the menubar (PopUp and Toolbar)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000555 if (parent != NULL || menu_is_menubar(menu->name))
556 {
557#ifdef FEAT_GUI
558 ++idx;
559#endif
560 if (menu->priority <= pri_tab[pri_idx])
561 {
562 lower_pri = menup;
563#ifdef FEAT_GUI
564 new_idx = idx;
565#endif
566 }
567 }
568 menu = menu->next;
569 }
570
571 if (menu == NULL)
572 {
573 if (*next_name == NUL && parent == NULL)
574 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100575 emsg(_("E331: Must not add menu items directly to menu bar"));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000576 goto erret;
577 }
578
579 if (menu_is_separator(dname) && *next_name != NUL)
580 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100581 emsg(_("E332: Separator cannot be part of a menu path"));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000582 goto erret;
583 }
584
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100585 // Not already there, so lets add it
Bram Moolenaare809a4e2019-07-04 17:35:05 +0200586 menu = ALLOC_CLEAR_ONE(vimmenu_T);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000587 if (menu == NULL)
588 goto erret;
589
590 menu->modes = modes;
591 menu->enabled = MENU_ALL_MODES;
592 menu->name = vim_strsave(name);
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100593 // separate mnemonic and accelerator text from actual menu name
Bram Moolenaar071d4272004-06-13 20:20:40 +0000594 menu->dname = menu_text(name, &menu->mnemonic, &menu->actext);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +0200595#ifdef FEAT_MULTI_LANG
596 if (en_name != NULL)
597 {
598 menu->en_name = vim_strsave(en_name);
599 menu->en_dname = menu_text(en_name, NULL, NULL);
600 }
601 else
602 {
603 menu->en_name = NULL;
604 menu->en_dname = NULL;
605 }
606#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000607 menu->priority = pri_tab[pri_idx];
608 menu->parent = parent;
609#ifdef FEAT_GUI_MOTIF
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100610 menu->sensitive = TRUE; // the default
Bram Moolenaar071d4272004-06-13 20:20:40 +0000611#endif
612#ifdef FEAT_BEVAL_TIP
613 menu->tip = NULL;
614#endif
615#ifdef FEAT_GUI_ATHENA
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100616 menu->image = None; // X-Windows definition for NULL
Bram Moolenaar071d4272004-06-13 20:20:40 +0000617#endif
618
619 /*
620 * Add after menu that has lower priority.
621 */
622 menu->next = *lower_pri;
623 *lower_pri = menu;
624
625 old_modes = 0;
626
627#ifdef FEAT_TOOLBAR
628 menu->iconidx = menuarg->iconidx;
629 menu->icon_builtin = menuarg->icon_builtin;
630 if (*next_name == NUL && menuarg->iconfile != NULL)
631 menu->iconfile = vim_strsave(menuarg->iconfile);
632#endif
Bram Moolenaar4f974752019-02-17 17:44:42 +0100633#if defined(FEAT_GUI_MSWIN) && defined(FEAT_TEAROFF)
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100634 // the tearoff item must be present in the modes of each item.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000635 if (parent != NULL && menu_is_tearoff(parent->children->dname))
636 parent->children->modes |= modes;
637#endif
638 }
639 else
640 {
641 old_modes = menu->modes;
642
643 /*
644 * If this menu option was previously only available in other
645 * modes, then make sure it's available for this one now
646 * Also enable a menu when it's created or changed.
647 */
Bram Moolenaar4f974752019-02-17 17:44:42 +0100648#ifdef FEAT_GUI_MSWIN
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100649 // If adding a tearbar (addtearoff == FALSE) don't update modes
Bram Moolenaar071d4272004-06-13 20:20:40 +0000650 if (addtearoff)
651#endif
652 {
653 menu->modes |= modes;
654 menu->enabled |= modes;
655 }
656 }
657
658#ifdef FEAT_GUI
659 /*
660 * Add the menu item when it's used in one of the modes, but not when
661 * only a tooltip is defined.
662 */
663 if ((old_modes & MENU_ALL_MODES) == 0
664 && (menu->modes & MENU_ALL_MODES) != 0)
665 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100666 if (gui.in_use) // Otherwise it will be added when GUI starts
Bram Moolenaar071d4272004-06-13 20:20:40 +0000667 {
668 if (*next_name == NUL)
669 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100670 // Real menu item, not sub-menu
Bram Moolenaar071d4272004-06-13 20:20:40 +0000671 gui_mch_add_menu_item(menu, new_idx);
672
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100673 // Want to update menus now even if mode not changed
Bram Moolenaar071d4272004-06-13 20:20:40 +0000674 force_menu_update = TRUE;
675 }
676 else
677 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100678 // Sub-menu (not at end of path yet)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000679 gui_mch_add_menu(menu, new_idx);
680 }
681 }
682
Bram Moolenaar4f974752019-02-17 17:44:42 +0100683# if defined(FEAT_GUI_MSWIN) & defined(FEAT_TEAROFF)
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100684 // When adding a new submenu, may add a tearoff item
Bram Moolenaar071d4272004-06-13 20:20:40 +0000685 if ( addtearoff
686 && *next_name
687 && vim_strchr(p_go, GO_TEAROFF) != NULL
Bram Moolenaar310c32e2019-11-29 23:15:25 +0100688 && menu_is_menubar(name)
689# ifdef VIMDLL
690 && (gui.in_use || gui.starting)
691# endif
692 )
Bram Moolenaar071d4272004-06-13 20:20:40 +0000693 {
694 char_u *tearpath;
695
696 /*
697 * The pointers next_name & path_name refer to a string with
698 * \'s and ^V's stripped out. But menu_path is a "raw"
699 * string, so we must correct for special characters.
700 */
Bram Moolenaar964b3742019-05-24 18:54:09 +0200701 tearpath = alloc(STRLEN(menu_path) + TEAR_LEN + 2);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000702 if (tearpath != NULL)
703 {
704 char_u *s;
705 int idx;
706
707 STRCPY(tearpath, menu_path);
708 idx = (int)(next_name - path_name - 1);
Bram Moolenaar91acfff2017-03-12 19:22:36 +0100709 for (s = tearpath; *s && s < tearpath + idx; MB_PTR_ADV(s))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000710 {
711 if ((*s == '\\' || *s == Ctrl_V) && s[1])
712 {
713 ++idx;
714 ++s;
715 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000716 }
717 tearpath[idx] = NUL;
718 gui_add_tearoff(tearpath, pri_tab, pri_idx);
719 vim_free(tearpath);
720 }
721 }
722# endif
723 }
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100724#endif // FEAT_GUI
Bram Moolenaar071d4272004-06-13 20:20:40 +0000725
726 menup = &menu->children;
727 parent = menu;
728 name = next_name;
Bram Moolenaard23a8232018-02-10 18:45:26 +0100729 VIM_CLEAR(dname);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000730 if (pri_tab[pri_idx + 1] != -1)
731 ++pri_idx;
732 }
733 vim_free(path_name);
734
735 /*
736 * Only add system menu items which have not been defined yet.
737 * First check if this was an ":amenu".
738 */
739 amenu = ((modes & (MENU_NORMAL_MODE | MENU_INSERT_MODE)) ==
740 (MENU_NORMAL_MODE | MENU_INSERT_MODE));
741 if (sys_menu)
742 modes &= ~old_modes;
743
744 if (menu != NULL && modes)
745 {
746#ifdef FEAT_GUI
747 menu->cb = gui_menu_cb;
748#endif
749 p = (call_data == NULL) ? NULL : vim_strsave(call_data);
750
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100751 // loop over all modes, may add more than one
Bram Moolenaar071d4272004-06-13 20:20:40 +0000752 for (i = 0; i < MENU_MODES; ++i)
753 {
754 if (modes & (1 << i))
755 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100756 // free any old menu
Bram Moolenaar071d4272004-06-13 20:20:40 +0000757 free_menu_string(menu, i);
758
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100759 // For "amenu", may insert an extra character.
760 // Don't do this if adding a tearbar (addtearoff == FALSE).
761 // Don't do this for "<Nop>".
Bram Moolenaar071d4272004-06-13 20:20:40 +0000762 c = 0;
Bram Moolenaar7871a502010-05-14 21:19:23 +0200763 d = 0;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000764 if (amenu && call_data != NULL && *call_data != NUL
Bram Moolenaar4f974752019-02-17 17:44:42 +0100765#ifdef FEAT_GUI_MSWIN
Bram Moolenaar071d4272004-06-13 20:20:40 +0000766 && addtearoff
767#endif
768 )
769 {
770 switch (1 << i)
771 {
772 case MENU_VISUAL_MODE:
Bram Moolenaarb3656ed2006-03-20 21:59:49 +0000773 case MENU_SELECT_MODE:
Bram Moolenaar071d4272004-06-13 20:20:40 +0000774 case MENU_OP_PENDING_MODE:
775 case MENU_CMDLINE_MODE:
776 c = Ctrl_C;
777 break;
778 case MENU_INSERT_MODE:
Bram Moolenaar7871a502010-05-14 21:19:23 +0200779 c = Ctrl_BSL;
780 d = Ctrl_O;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000781 break;
782 }
783 }
784
Bram Moolenaar7871a502010-05-14 21:19:23 +0200785 if (c != 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000786 {
Bram Moolenaar964b3742019-05-24 18:54:09 +0200787 menu->strings[i] = alloc(STRLEN(call_data) + 5);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000788 if (menu->strings[i] != NULL)
789 {
790 menu->strings[i][0] = c;
Bram Moolenaar7871a502010-05-14 21:19:23 +0200791 if (d == 0)
792 STRCPY(menu->strings[i] + 1, call_data);
793 else
794 {
795 menu->strings[i][1] = d;
796 STRCPY(menu->strings[i] + 2, call_data);
797 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000798 if (c == Ctrl_C)
799 {
Bram Moolenaara93fa7e2006-04-17 22:14:47 +0000800 int len = (int)STRLEN(menu->strings[i]);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000801
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100802 // Append CTRL-\ CTRL-G to obey 'insertmode'.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000803 menu->strings[i][len] = Ctrl_BSL;
804 menu->strings[i][len + 1] = Ctrl_G;
805 menu->strings[i][len + 2] = NUL;
806 }
807 }
808 }
809 else
810 menu->strings[i] = p;
811 menu->noremap[i] = menuarg->noremap[0];
812 menu->silent[i] = menuarg->silent[0];
813 }
814 }
Bram Moolenaar4f974752019-02-17 17:44:42 +0100815#if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_MSWIN) \
Bram Moolenaarc3719bd2017-11-18 22:13:31 +0100816 && (defined(FEAT_BEVAL_GUI) || defined(FEAT_GUI_GTK))
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100817 // Need to update the menu tip.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000818 if (modes & MENU_TIP_MODE)
819 gui_mch_menu_set_tip(menu);
820#endif
821 }
822 return OK;
823
824erret:
825 vim_free(path_name);
826 vim_free(dname);
Bram Moolenaar18a0b122006-08-16 13:55:16 +0000827
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100828 // Delete any empty submenu we added before discovering the error. Repeat
829 // for higher levels.
Bram Moolenaar18a0b122006-08-16 13:55:16 +0000830 while (parent != NULL && parent->children == NULL)
831 {
832 if (parent->parent == NULL)
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200833 menup = root_menu_ptr;
Bram Moolenaar18a0b122006-08-16 13:55:16 +0000834 else
835 menup = &parent->parent->children;
836 for ( ; *menup != NULL && *menup != parent; menup = &((*menup)->next))
837 ;
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100838 if (*menup == NULL) // safety check
Bram Moolenaar18a0b122006-08-16 13:55:16 +0000839 break;
840 parent = parent->parent;
841 free_menu(menup);
842 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000843 return FAIL;
844}
845
846/*
847 * Set the (sub)menu with the given name to enabled or disabled.
848 * Called recursively.
849 */
850 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100851menu_nable_recurse(
852 vimmenu_T *menu,
853 char_u *name,
854 int modes,
855 int enable)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000856{
857 char_u *p;
858
859 if (menu == NULL)
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100860 return OK; // Got to bottom of hierarchy
Bram Moolenaar071d4272004-06-13 20:20:40 +0000861
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100862 // Get name of this element in the menu hierarchy
Bram Moolenaar071d4272004-06-13 20:20:40 +0000863 p = menu_name_skip(name);
864
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100865 // Find the menu
Bram Moolenaar071d4272004-06-13 20:20:40 +0000866 while (menu != NULL)
867 {
868 if (*name == NUL || *name == '*' || menu_name_equal(name, menu))
869 {
870 if (*p != NUL)
871 {
872 if (menu->children == NULL)
873 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100874 emsg(_(e_notsubmenu));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000875 return FAIL;
876 }
877 if (menu_nable_recurse(menu->children, p, modes, enable)
878 == FAIL)
879 return FAIL;
880 }
881 else
882 if (enable)
883 menu->enabled |= modes;
884 else
885 menu->enabled &= ~modes;
886
887 /*
888 * When name is empty, we are doing all menu items for the given
889 * modes, so keep looping, otherwise we are just doing the named
890 * menu item (which has been found) so break here.
891 */
892 if (*name != NUL && *name != '*')
893 break;
894 }
895 menu = menu->next;
896 }
897 if (*name != NUL && *name != '*' && menu == NULL)
898 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100899 semsg(_(e_nomenu), name);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000900 return FAIL;
901 }
902
903#ifdef FEAT_GUI
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100904 // Want to update menus now even if mode not changed
Bram Moolenaar071d4272004-06-13 20:20:40 +0000905 force_menu_update = TRUE;
906#endif
907
908 return OK;
909}
910
911/*
912 * Remove the (sub)menu with the given name from the menu hierarchy
913 * Called recursively.
914 */
915 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100916remove_menu(
917 vimmenu_T **menup,
918 char_u *name,
919 int modes,
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100920 int silent) // don't give error messages
Bram Moolenaar071d4272004-06-13 20:20:40 +0000921{
922 vimmenu_T *menu;
923 vimmenu_T *child;
924 char_u *p;
925
926 if (*menup == NULL)
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100927 return OK; // Got to bottom of hierarchy
Bram Moolenaar071d4272004-06-13 20:20:40 +0000928
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100929 // Get name of this element in the menu hierarchy
Bram Moolenaar071d4272004-06-13 20:20:40 +0000930 p = menu_name_skip(name);
931
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100932 // Find the menu
Bram Moolenaar071d4272004-06-13 20:20:40 +0000933 while ((menu = *menup) != NULL)
934 {
935 if (*name == NUL || menu_name_equal(name, menu))
936 {
937 if (*p != NUL && menu->children == NULL)
938 {
939 if (!silent)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100940 emsg(_(e_notsubmenu));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000941 return FAIL;
942 }
943 if ((menu->modes & modes) != 0x0)
944 {
Bram Moolenaar4f974752019-02-17 17:44:42 +0100945#if defined(FEAT_GUI_MSWIN) & defined(FEAT_TEAROFF)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000946 /*
947 * If we are removing all entries for this menu,MENU_ALL_MODES,
948 * Then kill any tearoff before we start
949 */
950 if (*p == NUL && modes == MENU_ALL_MODES)
951 {
952 if (IsWindow(menu->tearoff_handle))
953 DestroyWindow(menu->tearoff_handle);
954 }
955#endif
956 if (remove_menu(&menu->children, p, modes, silent) == FAIL)
957 return FAIL;
958 }
959 else if (*name != NUL)
960 {
961 if (!silent)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100962 emsg(_(e_menuothermode));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000963 return FAIL;
964 }
965
966 /*
967 * When name is empty, we are removing all menu items for the given
968 * modes, so keep looping, otherwise we are just removing the named
969 * menu item (which has been found) so break here.
970 */
971 if (*name != NUL)
972 break;
973
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100974 // Remove the menu item for the given mode[s]. If the menu item
975 // is no longer valid in ANY mode, delete it
Bram Moolenaar071d4272004-06-13 20:20:40 +0000976 menu->modes &= ~modes;
977 if (modes & MENU_TIP_MODE)
978 free_menu_string(menu, MENU_INDEX_TIP);
979 if ((menu->modes & MENU_ALL_MODES) == 0)
980 free_menu(menup);
981 else
982 menup = &menu->next;
983 }
984 else
985 menup = &menu->next;
986 }
987 if (*name != NUL)
988 {
989 if (menu == NULL)
990 {
991 if (!silent)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100992 semsg(_(e_nomenu), name);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000993 return FAIL;
994 }
995
996
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100997 // Recalculate modes for menu based on the new updated children
Bram Moolenaar071d4272004-06-13 20:20:40 +0000998 menu->modes &= ~modes;
Bram Moolenaar4f974752019-02-17 17:44:42 +0100999#if defined(FEAT_GUI_MSWIN) & defined(FEAT_TEAROFF)
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001000 if ((s_tearoffs) && (menu->children != NULL)) // there's a tear bar..
1001 child = menu->children->next; // don't count tearoff bar
Bram Moolenaar071d4272004-06-13 20:20:40 +00001002 else
1003#endif
1004 child = menu->children;
1005 for ( ; child != NULL; child = child->next)
1006 menu->modes |= child->modes;
1007 if (modes & MENU_TIP_MODE)
1008 {
1009 free_menu_string(menu, MENU_INDEX_TIP);
Bram Moolenaar4f974752019-02-17 17:44:42 +01001010#if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_MSWIN) \
Bram Moolenaarc3719bd2017-11-18 22:13:31 +01001011 && (defined(FEAT_BEVAL_GUI) || defined(FEAT_GUI_GTK))
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001012 // Need to update the menu tip.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001013 if (gui.in_use)
1014 gui_mch_menu_set_tip(menu);
1015#endif
1016 }
1017 if ((menu->modes & MENU_ALL_MODES) == 0)
1018 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001019 // The menu item is no longer valid in ANY mode, so delete it
Bram Moolenaar4f974752019-02-17 17:44:42 +01001020#if defined(FEAT_GUI_MSWIN) & defined(FEAT_TEAROFF)
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001021 if (s_tearoffs && menu->children != NULL) // there's a tear bar..
Bram Moolenaar071d4272004-06-13 20:20:40 +00001022 free_menu(&menu->children);
1023#endif
1024 *menup = menu;
1025 free_menu(menup);
1026 }
1027 }
1028
1029 return OK;
1030}
1031
1032/*
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001033 * Remove the WinBar menu from window "wp".
1034 */
1035 void
1036remove_winbar(win_T *wp)
1037{
1038 remove_menu(&wp->w_winbar, (char_u *)"", MENU_ALL_MODES, TRUE);
1039 vim_free(wp->w_winbar_items);
1040}
1041
1042/*
Bram Moolenaar071d4272004-06-13 20:20:40 +00001043 * Free the given menu structure and remove it from the linked list.
1044 */
1045 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001046free_menu(vimmenu_T **menup)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001047{
1048 int i;
1049 vimmenu_T *menu;
1050
1051 menu = *menup;
1052
1053#ifdef FEAT_GUI
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001054 // Free machine specific menu structures (only when already created)
1055 // Also may rebuild a tearoff'ed menu
Bram Moolenaar071d4272004-06-13 20:20:40 +00001056 if (gui.in_use)
1057 gui_mch_destroy_menu(menu);
1058#endif
1059
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001060 // Don't change *menup until after calling gui_mch_destroy_menu(). The
1061 // MacOS code needs the original structure to properly delete the menu.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001062 *menup = menu->next;
1063 vim_free(menu->name);
1064 vim_free(menu->dname);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001065#ifdef FEAT_MULTI_LANG
1066 vim_free(menu->en_name);
1067 vim_free(menu->en_dname);
1068#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001069 vim_free(menu->actext);
1070#ifdef FEAT_TOOLBAR
1071 vim_free(menu->iconfile);
Bram Moolenaarbee0c5b2005-02-07 22:03:36 +00001072# ifdef FEAT_GUI_MOTIF
1073 vim_free(menu->xpm_fname);
1074# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001075#endif
1076 for (i = 0; i < MENU_MODES; i++)
1077 free_menu_string(menu, i);
1078 vim_free(menu);
1079
1080#ifdef FEAT_GUI
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001081 // Want to update menus now even if mode not changed
Bram Moolenaar071d4272004-06-13 20:20:40 +00001082 force_menu_update = TRUE;
1083#endif
1084}
1085
1086/*
1087 * Free the menu->string with the given index.
1088 */
1089 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001090free_menu_string(vimmenu_T *menu, int idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001091{
1092 int count = 0;
1093 int i;
1094
1095 for (i = 0; i < MENU_MODES; i++)
1096 if (menu->strings[i] == menu->strings[idx])
1097 count++;
1098 if (count == 1)
1099 vim_free(menu->strings[idx]);
1100 menu->strings[idx] = NULL;
1101}
1102
1103/*
1104 * Show the mapping associated with a menu item or hierarchy in a sub-menu.
1105 */
1106 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001107show_menus(char_u *path_name, int modes)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001108{
1109 char_u *p;
1110 char_u *name;
1111 vimmenu_T *menu;
1112 vimmenu_T *parent = NULL;
1113
Bram Moolenaar071d4272004-06-13 20:20:40 +00001114 name = path_name = vim_strsave(path_name);
1115 if (path_name == NULL)
1116 return FAIL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001117 menu = *get_root_menu(path_name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001118
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001119 // First, find the (sub)menu with the given name
Bram Moolenaar071d4272004-06-13 20:20:40 +00001120 while (*name)
1121 {
1122 p = menu_name_skip(name);
1123 while (menu != NULL)
1124 {
1125 if (menu_name_equal(name, menu))
1126 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001127 // Found menu
Bram Moolenaar071d4272004-06-13 20:20:40 +00001128 if (*p != NUL && menu->children == NULL)
1129 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01001130 emsg(_(e_notsubmenu));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001131 vim_free(path_name);
1132 return FAIL;
1133 }
1134 else if ((menu->modes & modes) == 0x0)
1135 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01001136 emsg(_(e_menuothermode));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001137 vim_free(path_name);
1138 return FAIL;
1139 }
1140 break;
1141 }
1142 menu = menu->next;
1143 }
1144 if (menu == NULL)
1145 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01001146 semsg(_(e_nomenu), name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001147 vim_free(path_name);
1148 return FAIL;
1149 }
1150 name = p;
1151 parent = menu;
1152 menu = menu->children;
1153 }
Bram Moolenaaracbd4422008-08-17 21:44:45 +00001154 vim_free(path_name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001155
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001156 // Now we have found the matching menu, and we list the mappings
1157 // Highlight title
Bram Moolenaar32526b32019-01-19 17:43:09 +01001158 msg_puts_title(_("\n--- Menus ---"));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001159
1160 show_menus_recursive(parent, modes, 0);
1161 return OK;
1162}
1163
1164/*
1165 * Recursively show the mappings associated with the menus under the given one
1166 */
1167 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001168show_menus_recursive(vimmenu_T *menu, int modes, int depth)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001169{
1170 int i;
1171 int bit;
1172
1173 if (menu != NULL && (menu->modes & modes) == 0x0)
1174 return;
1175
1176 if (menu != NULL)
1177 {
1178 msg_putchar('\n');
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001179 if (got_int) // "q" hit for "--more--"
Bram Moolenaar071d4272004-06-13 20:20:40 +00001180 return;
1181 for (i = 0; i < depth; i++)
Bram Moolenaar32526b32019-01-19 17:43:09 +01001182 msg_puts(" ");
Bram Moolenaar071d4272004-06-13 20:20:40 +00001183 if (menu->priority)
1184 {
1185 msg_outnum((long)menu->priority);
Bram Moolenaar32526b32019-01-19 17:43:09 +01001186 msg_puts(" ");
Bram Moolenaar071d4272004-06-13 20:20:40 +00001187 }
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001188 // Same highlighting as for directories!?
Bram Moolenaar8820b482017-03-16 17:23:31 +01001189 msg_outtrans_attr(menu->name, HL_ATTR(HLF_D));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001190 }
1191
1192 if (menu != NULL && menu->children == NULL)
1193 {
1194 for (bit = 0; bit < MENU_MODES; bit++)
1195 if ((menu->modes & modes & (1 << bit)) != 0)
1196 {
1197 msg_putchar('\n');
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001198 if (got_int) // "q" hit for "--more--"
Bram Moolenaar071d4272004-06-13 20:20:40 +00001199 return;
1200 for (i = 0; i < depth + 2; i++)
Bram Moolenaar32526b32019-01-19 17:43:09 +01001201 msg_puts(" ");
1202 msg_puts(menu_mode_chars[bit]);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001203 if (menu->noremap[bit] == REMAP_NONE)
1204 msg_putchar('*');
1205 else if (menu->noremap[bit] == REMAP_SCRIPT)
1206 msg_putchar('&');
1207 else
1208 msg_putchar(' ');
1209 if (menu->silent[bit])
1210 msg_putchar('s');
1211 else
1212 msg_putchar(' ');
1213 if ((menu->modes & menu->enabled & (1 << bit)) == 0)
1214 msg_putchar('-');
1215 else
1216 msg_putchar(' ');
Bram Moolenaar32526b32019-01-19 17:43:09 +01001217 msg_puts(" ");
Bram Moolenaar071d4272004-06-13 20:20:40 +00001218 if (*menu->strings[bit] == NUL)
Bram Moolenaar32526b32019-01-19 17:43:09 +01001219 msg_puts_attr("<Nop>", HL_ATTR(HLF_8));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001220 else
Bram Moolenaar725310d2019-04-24 23:08:23 +02001221 msg_outtrans_special(menu->strings[bit], FALSE, 0);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001222 }
1223 }
1224 else
1225 {
1226 if (menu == NULL)
1227 {
1228 menu = root_menu;
1229 depth--;
1230 }
1231 else
1232 menu = menu->children;
1233
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001234 // recursively show all children. Skip PopUp[nvoci].
Bram Moolenaar071d4272004-06-13 20:20:40 +00001235 for (; menu != NULL && !got_int; menu = menu->next)
1236 if (!menu_is_hidden(menu->dname))
1237 show_menus_recursive(menu, modes, depth + 1);
1238 }
1239}
1240
Bram Moolenaar071d4272004-06-13 20:20:40 +00001241/*
1242 * Used when expanding menu names.
1243 */
1244static vimmenu_T *expand_menu = NULL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001245static vimmenu_T *expand_menu_alt = NULL;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001246static int expand_modes = 0x0;
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001247static int expand_emenu; // TRUE for ":emenu" command
Bram Moolenaar071d4272004-06-13 20:20:40 +00001248
1249/*
1250 * Work out what to complete when doing command line completion of menu names.
1251 */
1252 char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001253set_context_in_menu_cmd(
1254 expand_T *xp,
1255 char_u *cmd,
1256 char_u *arg,
1257 int forceit)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001258{
1259 char_u *after_dot;
1260 char_u *p;
1261 char_u *path_name = NULL;
1262 char_u *name;
1263 int unmenu;
1264 vimmenu_T *menu;
1265 int expand_menus;
1266
1267 xp->xp_context = EXPAND_UNSUCCESSFUL;
1268
1269
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001270 // Check for priority numbers, enable and disable
Bram Moolenaar071d4272004-06-13 20:20:40 +00001271 for (p = arg; *p; ++p)
1272 if (!VIM_ISDIGIT(*p) && *p != '.')
1273 break;
1274
Bram Moolenaar1c465442017-03-12 20:10:05 +01001275 if (!VIM_ISWHITE(*p))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001276 {
1277 if (STRNCMP(arg, "enable", 6) == 0
Bram Moolenaar1c465442017-03-12 20:10:05 +01001278 && (arg[6] == NUL || VIM_ISWHITE(arg[6])))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001279 p = arg + 6;
1280 else if (STRNCMP(arg, "disable", 7) == 0
Bram Moolenaar1c465442017-03-12 20:10:05 +01001281 && (arg[7] == NUL || VIM_ISWHITE(arg[7])))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001282 p = arg + 7;
1283 else
1284 p = arg;
1285 }
1286
Bram Moolenaar1c465442017-03-12 20:10:05 +01001287 while (*p != NUL && VIM_ISWHITE(*p))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001288 ++p;
1289
1290 arg = after_dot = p;
1291
Bram Moolenaar1c465442017-03-12 20:10:05 +01001292 for (; *p && !VIM_ISWHITE(*p); ++p)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001293 {
1294 if ((*p == '\\' || *p == Ctrl_V) && p[1] != NUL)
1295 p++;
1296 else if (*p == '.')
1297 after_dot = p + 1;
1298 }
1299
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001300 // ":tearoff" and ":popup" only use menus, not entries
Bram Moolenaar071d4272004-06-13 20:20:40 +00001301 expand_menus = !((*cmd == 't' && cmd[1] == 'e') || *cmd == 'p');
1302 expand_emenu = (*cmd == 'e');
Bram Moolenaar1c465442017-03-12 20:10:05 +01001303 if (expand_menus && VIM_ISWHITE(*p))
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001304 return NULL; // TODO: check for next command?
1305 if (*p == NUL) // Complete the menu name
Bram Moolenaar071d4272004-06-13 20:20:40 +00001306 {
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001307 int try_alt_menu = TRUE;
1308
Bram Moolenaar071d4272004-06-13 20:20:40 +00001309 /*
1310 * With :unmenu, you only want to match menus for the appropriate mode.
1311 * With :menu though you might want to add a menu with the same name as
1312 * one in another mode, so match menus from other modes too.
1313 */
1314 expand_modes = get_menu_cmd_modes(cmd, forceit, NULL, &unmenu);
1315 if (!unmenu)
1316 expand_modes = MENU_ALL_MODES;
1317
1318 menu = root_menu;
1319 if (after_dot != arg)
1320 {
Bram Moolenaar964b3742019-05-24 18:54:09 +02001321 path_name = alloc(after_dot - arg);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001322 if (path_name == NULL)
1323 return NULL;
Bram Moolenaarce0842a2005-07-18 21:58:11 +00001324 vim_strncpy(path_name, arg, after_dot - arg - 1);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001325 }
1326 name = path_name;
1327 while (name != NULL && *name)
1328 {
1329 p = menu_name_skip(name);
1330 while (menu != NULL)
1331 {
1332 if (menu_name_equal(name, menu))
1333 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001334 // Found menu
Bram Moolenaar071d4272004-06-13 20:20:40 +00001335 if ((*p != NUL && menu->children == NULL)
1336 || ((menu->modes & expand_modes) == 0x0))
1337 {
1338 /*
1339 * Menu path continues, but we have reached a leaf.
1340 * Or menu exists only in another mode.
1341 */
1342 vim_free(path_name);
1343 return NULL;
1344 }
1345 break;
1346 }
1347 menu = menu->next;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001348 if (menu == NULL && try_alt_menu)
1349 {
1350 menu = curwin->w_winbar;
1351 try_alt_menu = FALSE;
1352 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001353 }
1354 if (menu == NULL)
1355 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001356 // No menu found with the name we were looking for
Bram Moolenaar071d4272004-06-13 20:20:40 +00001357 vim_free(path_name);
1358 return NULL;
1359 }
1360 name = p;
1361 menu = menu->children;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001362 try_alt_menu = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001363 }
Bram Moolenaareb3593b2006-04-22 22:33:57 +00001364 vim_free(path_name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001365
1366 xp->xp_context = expand_menus ? EXPAND_MENUNAMES : EXPAND_MENUS;
1367 xp->xp_pattern = after_dot;
1368 expand_menu = menu;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001369 if (expand_menu == root_menu)
1370 expand_menu_alt = curwin->w_winbar;
1371 else
1372 expand_menu_alt = NULL;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001373 }
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001374 else // We're in the mapping part
Bram Moolenaar071d4272004-06-13 20:20:40 +00001375 xp->xp_context = EXPAND_NOTHING;
1376 return NULL;
1377}
1378
1379/*
1380 * Function given to ExpandGeneric() to obtain the list of (sub)menus (not
1381 * entries).
1382 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001383 char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001384get_menu_name(expand_T *xp UNUSED, int idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001385{
1386 static vimmenu_T *menu = NULL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001387 static int did_alt_menu = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001388 char_u *str;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001389#ifdef FEAT_MULTI_LANG
1390 static int should_advance = FALSE;
1391#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001392
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001393 if (idx == 0) // first call: start at first item
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001394 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001395 menu = expand_menu;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001396 did_alt_menu = FALSE;
Bram Moolenaar41375642010-05-16 12:49:27 +02001397#ifdef FEAT_MULTI_LANG
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001398 should_advance = FALSE;
Bram Moolenaar41375642010-05-16 12:49:27 +02001399#endif
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001400 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001401
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001402 // Skip PopUp[nvoci].
Bram Moolenaar071d4272004-06-13 20:20:40 +00001403 while (menu != NULL && (menu_is_hidden(menu->dname)
1404 || menu_is_separator(menu->dname)
1405 || menu_is_tearoff(menu->dname)
1406 || menu->children == NULL))
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001407 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001408 menu = menu->next;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001409 if (menu == NULL && !did_alt_menu)
1410 {
1411 menu = expand_menu_alt;
1412 did_alt_menu = TRUE;
1413 }
1414 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001415
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001416 if (menu == NULL) // at end of linked list
Bram Moolenaar071d4272004-06-13 20:20:40 +00001417 return NULL;
1418
1419 if (menu->modes & expand_modes)
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001420#ifdef FEAT_MULTI_LANG
1421 if (should_advance)
1422 str = menu->en_dname;
1423 else
1424 {
1425#endif
1426 str = menu->dname;
1427#ifdef FEAT_MULTI_LANG
1428 if (menu->en_dname == NULL)
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001429 should_advance = TRUE;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001430 }
1431#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001432 else
1433 str = (char_u *)"";
1434
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001435#ifdef FEAT_MULTI_LANG
1436 if (should_advance)
1437#endif
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001438 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001439 // Advance to next menu entry.
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001440 menu = menu->next;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001441 if (menu == NULL && !did_alt_menu)
1442 {
1443 menu = expand_menu_alt;
1444 did_alt_menu = TRUE;
1445 }
1446 }
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001447
1448#ifdef FEAT_MULTI_LANG
1449 should_advance = !should_advance;
1450#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001451
1452 return str;
1453}
1454
1455/*
1456 * Function given to ExpandGeneric() to obtain the list of menus and menu
1457 * entries.
1458 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001459 char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001460get_menu_names(expand_T *xp UNUSED, int idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001461{
1462 static vimmenu_T *menu = NULL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001463 static int did_alt_menu = FALSE;
Bram Moolenaaref9d6aa2011-04-11 16:56:35 +02001464#define TBUFFER_LEN 256
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001465 static char_u tbuffer[TBUFFER_LEN]; //hack
Bram Moolenaar071d4272004-06-13 20:20:40 +00001466 char_u *str;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001467#ifdef FEAT_MULTI_LANG
1468 static int should_advance = FALSE;
1469#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001470
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001471 if (idx == 0) // first call: start at first item
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001472 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001473 menu = expand_menu;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001474 did_alt_menu = FALSE;
Bram Moolenaar41375642010-05-16 12:49:27 +02001475#ifdef FEAT_MULTI_LANG
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001476 should_advance = FALSE;
Bram Moolenaar41375642010-05-16 12:49:27 +02001477#endif
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001478 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001479
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001480 // Skip Browse-style entries, popup menus and separators.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001481 while (menu != NULL
1482 && ( menu_is_hidden(menu->dname)
1483 || (expand_emenu && menu_is_separator(menu->dname))
1484 || menu_is_tearoff(menu->dname)
1485#ifndef FEAT_BROWSE
1486 || menu->dname[STRLEN(menu->dname) - 1] == '.'
1487#endif
1488 ))
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001489 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001490 menu = menu->next;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001491 if (menu == NULL && !did_alt_menu)
1492 {
1493 menu = expand_menu_alt;
1494 did_alt_menu = TRUE;
1495 }
1496 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001497
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001498 if (menu == NULL) // at end of linked list
Bram Moolenaar071d4272004-06-13 20:20:40 +00001499 return NULL;
1500
1501 if (menu->modes & expand_modes)
1502 {
1503 if (menu->children != NULL)
1504 {
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001505#ifdef FEAT_MULTI_LANG
1506 if (should_advance)
Bram Moolenaaref9d6aa2011-04-11 16:56:35 +02001507 vim_strncpy(tbuffer, menu->en_dname, TBUFFER_LEN - 2);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001508 else
1509 {
1510#endif
Bram Moolenaaref9d6aa2011-04-11 16:56:35 +02001511 vim_strncpy(tbuffer, menu->dname, TBUFFER_LEN - 2);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001512#ifdef FEAT_MULTI_LANG
1513 if (menu->en_dname == NULL)
1514 should_advance = TRUE;
1515 }
1516#endif
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001517 // hack on menu separators: use a 'magic' char for the separator
1518 // so that '.' in names gets escaped properly
Bram Moolenaar071d4272004-06-13 20:20:40 +00001519 STRCAT(tbuffer, "\001");
1520 str = tbuffer;
1521 }
1522 else
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001523#ifdef FEAT_MULTI_LANG
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001524 {
1525 if (should_advance)
1526 str = menu->en_dname;
1527 else
1528 {
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001529#endif
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001530 str = menu->dname;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001531#ifdef FEAT_MULTI_LANG
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001532 if (menu->en_dname == NULL)
1533 should_advance = TRUE;
1534 }
1535 }
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001536#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001537 }
1538 else
1539 str = (char_u *)"";
1540
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001541#ifdef FEAT_MULTI_LANG
1542 if (should_advance)
1543#endif
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001544 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001545 // Advance to next menu entry.
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001546 menu = menu->next;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001547 if (menu == NULL && !did_alt_menu)
1548 {
1549 menu = expand_menu_alt;
1550 did_alt_menu = TRUE;
1551 }
1552 }
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001553
1554#ifdef FEAT_MULTI_LANG
1555 should_advance = !should_advance;
1556#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001557
1558 return str;
1559}
Bram Moolenaar071d4272004-06-13 20:20:40 +00001560
1561/*
1562 * Skip over this element of the menu path and return the start of the next
1563 * element. Any \ and ^Vs are removed from the current element.
Bram Moolenaar342337a2005-07-21 21:11:17 +00001564 * "name" may be modified.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001565 */
Bram Moolenaar5843f5f2019-08-20 20:13:45 +02001566 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001567menu_name_skip(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001568{
1569 char_u *p;
1570
Bram Moolenaar91acfff2017-03-12 19:22:36 +01001571 for (p = name; *p && *p != '.'; MB_PTR_ADV(p))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001572 {
1573 if (*p == '\\' || *p == Ctrl_V)
1574 {
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001575 STRMOVE(p, p + 1);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001576 if (*p == NUL)
1577 break;
1578 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001579 }
1580 if (*p)
1581 *p++ = NUL;
1582 return p;
1583}
1584
1585/*
1586 * Return TRUE when "name" matches with menu "menu". The name is compared in
1587 * two ways: raw menu name and menu name without '&'. ignore part after a TAB.
1588 */
1589 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001590menu_name_equal(char_u *name, vimmenu_T *menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001591{
Bram Moolenaar41375642010-05-16 12:49:27 +02001592#ifdef FEAT_MULTI_LANG
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001593 if (menu->en_name != NULL
Bram Moolenaard91f7042011-01-04 17:49:32 +01001594 && (menu_namecmp(name, menu->en_name)
1595 || menu_namecmp(name, menu->en_dname)))
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001596 return TRUE;
Bram Moolenaar41375642010-05-16 12:49:27 +02001597#endif
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001598 return menu_namecmp(name, menu->name) || menu_namecmp(name, menu->dname);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001599}
1600
1601 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001602menu_namecmp(char_u *name, char_u *mname)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001603{
1604 int i;
1605
1606 for (i = 0; name[i] != NUL && name[i] != TAB; ++i)
1607 if (name[i] != mname[i])
1608 break;
1609 return ((name[i] == NUL || name[i] == TAB)
1610 && (mname[i] == NUL || mname[i] == TAB));
1611}
1612
1613/*
1614 * Return the modes specified by the given menu command (eg :menu! returns
1615 * MENU_CMDLINE_MODE | MENU_INSERT_MODE).
1616 * If "noremap" is not NULL, then the flag it points to is set according to
1617 * whether the command is a "nore" command.
1618 * If "unmenu" is not NULL, then the flag it points to is set according to
1619 * whether the command is an "unmenu" command.
1620 */
1621 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001622get_menu_cmd_modes(
1623 char_u *cmd,
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001624 int forceit, // Was there a "!" after the command?
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001625 int *noremap,
1626 int *unmenu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001627{
1628 int modes;
1629
1630 switch (*cmd++)
1631 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001632 case 'v': // vmenu, vunmenu, vnoremenu
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001633 modes = MENU_VISUAL_MODE | MENU_SELECT_MODE;
1634 break;
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001635 case 'x': // xmenu, xunmenu, xnoremenu
Bram Moolenaar071d4272004-06-13 20:20:40 +00001636 modes = MENU_VISUAL_MODE;
1637 break;
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001638 case 's': // smenu, sunmenu, snoremenu
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001639 modes = MENU_SELECT_MODE;
1640 break;
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001641 case 'o': // omenu
Bram Moolenaar071d4272004-06-13 20:20:40 +00001642 modes = MENU_OP_PENDING_MODE;
1643 break;
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001644 case 'i': // imenu
Bram Moolenaar071d4272004-06-13 20:20:40 +00001645 modes = MENU_INSERT_MODE;
1646 break;
1647 case 't':
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001648 if (*cmd == 'l') // tlmenu, tlunmenu, tlnoremenu
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001649 {
1650 modes = MENU_TERMINAL_MODE;
1651 ++cmd;
1652 break;
1653 }
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001654 modes = MENU_TIP_MODE; // tmenu
Bram Moolenaar071d4272004-06-13 20:20:40 +00001655 break;
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001656 case 'c': // cmenu
Bram Moolenaar071d4272004-06-13 20:20:40 +00001657 modes = MENU_CMDLINE_MODE;
1658 break;
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001659 case 'a': // amenu
Bram Moolenaar071d4272004-06-13 20:20:40 +00001660 modes = MENU_INSERT_MODE | MENU_CMDLINE_MODE | MENU_NORMAL_MODE
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001661 | MENU_VISUAL_MODE | MENU_SELECT_MODE
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00001662 | MENU_OP_PENDING_MODE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001663 break;
1664 case 'n':
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001665 if (*cmd != 'o') // nmenu, not noremenu
Bram Moolenaar071d4272004-06-13 20:20:40 +00001666 {
1667 modes = MENU_NORMAL_MODE;
1668 break;
1669 }
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001670 // FALLTHROUGH
Bram Moolenaar071d4272004-06-13 20:20:40 +00001671 default:
1672 --cmd;
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001673 if (forceit) // menu!!
Bram Moolenaar071d4272004-06-13 20:20:40 +00001674 modes = MENU_INSERT_MODE | MENU_CMDLINE_MODE;
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001675 else // menu
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001676 modes = MENU_NORMAL_MODE | MENU_VISUAL_MODE | MENU_SELECT_MODE
Bram Moolenaar071d4272004-06-13 20:20:40 +00001677 | MENU_OP_PENDING_MODE;
1678 }
1679
1680 if (noremap != NULL)
1681 *noremap = (*cmd == 'n' ? REMAP_NONE : REMAP_YES);
1682 if (unmenu != NULL)
1683 *unmenu = (*cmd == 'u');
1684 return modes;
1685}
1686
1687/*
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01001688 * Return the string representation of the menu modes. Does the opposite
1689 * of get_menu_cmd_modes().
1690 */
1691 static char_u *
1692get_menu_mode_str(int modes)
1693{
1694 if ((modes & (MENU_INSERT_MODE | MENU_CMDLINE_MODE | MENU_NORMAL_MODE |
1695 MENU_VISUAL_MODE | MENU_SELECT_MODE | MENU_OP_PENDING_MODE))
1696 == (MENU_INSERT_MODE | MENU_CMDLINE_MODE | MENU_NORMAL_MODE |
1697 MENU_VISUAL_MODE | MENU_SELECT_MODE | MENU_OP_PENDING_MODE))
1698 return (char_u *)"a";
1699 if ((modes & (MENU_NORMAL_MODE | MENU_VISUAL_MODE | MENU_SELECT_MODE |
1700 MENU_OP_PENDING_MODE))
1701 == (MENU_NORMAL_MODE | MENU_VISUAL_MODE | MENU_SELECT_MODE |
1702 MENU_OP_PENDING_MODE))
1703 return (char_u *)" ";
1704 if ((modes & (MENU_INSERT_MODE | MENU_CMDLINE_MODE))
1705 == (MENU_INSERT_MODE | MENU_CMDLINE_MODE))
1706 return (char_u *)"!";
1707 if ((modes & (MENU_VISUAL_MODE | MENU_SELECT_MODE))
1708 == (MENU_VISUAL_MODE | MENU_SELECT_MODE))
1709 return (char_u *)"v";
1710 if (modes & MENU_VISUAL_MODE)
1711 return (char_u *)"x";
1712 if (modes & MENU_SELECT_MODE)
1713 return (char_u *)"s";
1714 if (modes & MENU_OP_PENDING_MODE)
1715 return (char_u *)"o";
1716 if (modes & MENU_INSERT_MODE)
1717 return (char_u *)"i";
1718 if (modes & MENU_TERMINAL_MODE)
1719 return (char_u *)"tl";
1720 if (modes & MENU_CMDLINE_MODE)
1721 return (char_u *)"c";
1722 if (modes & MENU_NORMAL_MODE)
1723 return (char_u *)"n";
1724 if (modes & MENU_TIP_MODE)
1725 return (char_u *)"t";
1726
1727 return (char_u *)"";
1728}
1729
1730/*
Bram Moolenaar071d4272004-06-13 20:20:40 +00001731 * Modify a menu name starting with "PopUp" to include the mode character.
1732 * Returns the name in allocated memory (NULL for failure).
1733 */
1734 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001735popup_mode_name(char_u *name, int idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001736{
1737 char_u *p;
1738 int len = (int)STRLEN(name);
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001739 char *mode_chars = menu_mode_chars[idx];
1740 int mode_chars_len = (int)strlen(mode_chars);
1741 int i;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001742
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001743 p = vim_strnsave(name, len + mode_chars_len);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001744 if (p != NULL)
1745 {
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001746 mch_memmove(p + 5 + mode_chars_len, p + 5, (size_t)(len - 4));
1747 for (i = 0; i < mode_chars_len; ++i)
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001748 p[5 + i] = menu_mode_chars[idx][i];
Bram Moolenaar071d4272004-06-13 20:20:40 +00001749 }
1750 return p;
1751}
1752
1753#if defined(FEAT_GUI) || defined(PROTO)
1754/*
1755 * Return the index into the menu->strings or menu->noremap arrays for the
1756 * current state. Returns MENU_INDEX_INVALID if there is no mapping for the
1757 * given menu in the current mode.
1758 */
1759 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001760get_menu_index(vimmenu_T *menu, int state)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001761{
1762 int idx;
1763
1764 if ((state & INSERT))
1765 idx = MENU_INDEX_INSERT;
1766 else if (state & CMDLINE)
1767 idx = MENU_INDEX_CMDLINE;
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001768#ifdef FEAT_TERMINAL
1769 else if (term_use_loop())
1770 idx = MENU_INDEX_TERMINAL;
1771#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001772 else if (VIsual_active)
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001773 {
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00001774 if (VIsual_select)
1775 idx = MENU_INDEX_SELECT;
1776 else
1777 idx = MENU_INDEX_VISUAL;
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001778 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001779 else if (state == HITRETURN || state == ASKMORE)
1780 idx = MENU_INDEX_CMDLINE;
1781 else if (finish_op)
1782 idx = MENU_INDEX_OP_PENDING;
1783 else if ((state & NORMAL))
1784 idx = MENU_INDEX_NORMAL;
1785 else
1786 idx = MENU_INDEX_INVALID;
1787
1788 if (idx != MENU_INDEX_INVALID && menu->strings[idx] == NULL)
1789 idx = MENU_INDEX_INVALID;
1790 return idx;
1791}
1792#endif
1793
1794/*
1795 * Duplicate the menu item text and then process to see if a mnemonic key
1796 * and/or accelerator text has been identified.
1797 * Returns a pointer to allocated memory, or NULL for failure.
1798 * If mnemonic != NULL, *mnemonic is set to the character after the first '&'.
1799 * If actext != NULL, *actext is set to the text after the first TAB.
1800 */
1801 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001802menu_text(char_u *str, int *mnemonic, char_u **actext)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001803{
1804 char_u *p;
1805 char_u *text;
1806
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001807 // Locate accelerator text, after the first TAB
Bram Moolenaar071d4272004-06-13 20:20:40 +00001808 p = vim_strchr(str, TAB);
1809 if (p != NULL)
1810 {
1811 if (actext != NULL)
1812 *actext = vim_strsave(p + 1);
1813 text = vim_strnsave(str, (int)(p - str));
1814 }
1815 else
1816 text = vim_strsave(str);
1817
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001818 // Find mnemonic characters "&a" and reduce "&&" to "&".
Bram Moolenaar071d4272004-06-13 20:20:40 +00001819 for (p = text; p != NULL; )
1820 {
1821 p = vim_strchr(p, '&');
1822 if (p != NULL)
1823 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001824 if (p[1] == NUL) // trailing "&"
Bram Moolenaar071d4272004-06-13 20:20:40 +00001825 break;
1826 if (mnemonic != NULL && p[1] != '&')
1827#if !defined(__MVS__) || defined(MOTIF390_MNEMONIC_FIXED)
1828 *mnemonic = p[1];
1829#else
1830 {
1831 /*
1832 * Well there is a bug in the Motif libraries on OS390 Unix.
1833 * The mnemonic keys needs to be converted to ASCII values
1834 * first.
1835 * This behavior has been seen in 2.8 and 2.9.
1836 */
1837 char c = p[1];
1838 __etoa_l(&c, 1);
1839 *mnemonic = c;
1840 }
1841#endif
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001842 STRMOVE(p, p + 1);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001843 p = p + 1;
1844 }
1845 }
1846 return text;
1847}
1848
1849/*
1850 * Return TRUE if "name" can be a menu in the MenuBar.
1851 */
1852 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001853menu_is_menubar(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001854{
1855 return (!menu_is_popup(name)
1856 && !menu_is_toolbar(name)
Bram Moolenaar378daf82017-09-23 23:58:28 +02001857 && !menu_is_winbar(name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001858 && *name != MNU_HIDDEN_CHAR);
1859}
1860
1861/*
1862 * Return TRUE if "name" is a popup menu name.
1863 */
1864 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001865menu_is_popup(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001866{
1867 return (STRNCMP(name, "PopUp", 5) == 0);
1868}
1869
1870#if (defined(FEAT_GUI_MOTIF) && (XmVersion <= 1002)) || defined(PROTO)
1871/*
1872 * Return TRUE if "name" is part of a popup menu.
1873 */
1874 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001875menu_is_child_of_popup(vimmenu_T *menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001876{
1877 while (menu->parent != NULL)
1878 menu = menu->parent;
1879 return menu_is_popup(menu->name);
1880}
1881#endif
1882
1883/*
1884 * Return TRUE if "name" is a toolbar menu name.
1885 */
1886 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001887menu_is_toolbar(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001888{
1889 return (STRNCMP(name, "ToolBar", 7) == 0);
1890}
1891
1892/*
1893 * Return TRUE if the name is a menu separator identifier: Starts and ends
1894 * with '-'
1895 */
1896 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001897menu_is_separator(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001898{
1899 return (name[0] == '-' && name[STRLEN(name) - 1] == '-');
1900}
1901
1902/*
1903 * Return TRUE if the menu is hidden: Starts with ']'
1904 */
1905 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001906menu_is_hidden(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001907{
1908 return (name[0] == ']') || (menu_is_popup(name) && name[5] != NUL);
1909}
1910
Bram Moolenaar071d4272004-06-13 20:20:40 +00001911/*
1912 * Return TRUE if the menu is the tearoff menu.
1913 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001914 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001915menu_is_tearoff(char_u *name UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001916{
1917#ifdef FEAT_GUI
1918 return (STRCMP(name, TEAR_STRING) == 0);
1919#else
1920 return FALSE;
1921#endif
1922}
Bram Moolenaar071d4272004-06-13 20:20:40 +00001923
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001924#if defined(FEAT_GUI) || defined(FEAT_TERM_POPUP_MENU) || defined(PROTO)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001925
1926 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001927get_menu_mode(void)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001928{
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001929#ifdef FEAT_TERMINAL
1930 if (term_use_loop())
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001931 return MENU_INDEX_TERMINAL;
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001932#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001933 if (VIsual_active)
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001934 {
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00001935 if (VIsual_select)
1936 return MENU_INDEX_SELECT;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001937 return MENU_INDEX_VISUAL;
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001938 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001939 if (State & INSERT)
1940 return MENU_INDEX_INSERT;
1941 if ((State & CMDLINE) || State == ASKMORE || State == HITRETURN)
1942 return MENU_INDEX_CMDLINE;
1943 if (finish_op)
1944 return MENU_INDEX_OP_PENDING;
1945 if (State & NORMAL)
1946 return MENU_INDEX_NORMAL;
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001947 if (State & LANGMAP) // must be a "r" command, like Insert mode
Bram Moolenaar071d4272004-06-13 20:20:40 +00001948 return MENU_INDEX_INSERT;
1949 return MENU_INDEX_INVALID;
1950}
1951
Bram Moolenaar29a2c082018-03-05 21:06:23 +01001952 int
1953get_menu_mode_flag(void)
1954{
1955 int mode = get_menu_mode();
1956
1957 if (mode == MENU_INDEX_INVALID)
1958 return 0;
1959 return 1 << mode;
1960}
1961
Bram Moolenaar071d4272004-06-13 20:20:40 +00001962/*
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001963 * Display the Special "PopUp" menu as a pop-up at the current mouse
1964 * position. The "PopUpn" menu is for Normal mode, "PopUpi" for Insert mode,
1965 * etc.
1966 */
1967 void
1968show_popupmenu(void)
1969{
1970 vimmenu_T *menu;
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001971 int menu_mode;
1972 char* mode;
1973 int mode_len;
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001974
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001975 menu_mode = get_menu_mode();
1976 if (menu_mode == MENU_INDEX_INVALID)
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001977 return;
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001978 mode = menu_mode_chars[menu_mode];
1979 mode_len = (int)strlen(mode);
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001980
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001981 apply_autocmds(EVENT_MENUPOPUP, (char_u*)mode, NULL, FALSE, curbuf);
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001982
Bram Moolenaar00d253e2020-04-06 22:13:01 +02001983 FOR_ALL_MENUS(menu)
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001984 if (STRNCMP("PopUp", menu->name, 5) == 0 && STRNCMP(menu->name + 5, mode, mode_len) == 0)
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001985 break;
1986
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001987 // Only show a popup when it is defined and has entries
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001988 if (menu != NULL && menu->children != NULL)
1989 {
1990# if defined(FEAT_GUI)
1991 if (gui.in_use)
1992 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001993 // Update the menus now, in case the MenuPopup autocommand did
1994 // anything.
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001995 gui_update_menus(0);
1996 gui_mch_show_popupmenu(menu);
1997 }
1998# endif
1999# if defined(FEAT_GUI) && defined(FEAT_TERM_POPUP_MENU)
2000 else
2001# endif
2002# if defined(FEAT_TERM_POPUP_MENU)
2003 pum_show_popupmenu(menu);
2004# endif
2005 }
2006}
2007#endif
2008
2009#if defined(FEAT_GUI) || defined(PROTO)
2010
2011/*
Bram Moolenaar968bbbe2006-08-16 19:41:08 +00002012 * Check that a pointer appears in the menu tree. Used to protect from using
2013 * a menu that was deleted after it was selected but before the event was
2014 * handled.
2015 * Return OK or FAIL. Used recursively.
2016 */
2017 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002018check_menu_pointer(vimmenu_T *root, vimmenu_T *menu_to_check)
Bram Moolenaar968bbbe2006-08-16 19:41:08 +00002019{
2020 vimmenu_T *p;
2021
2022 for (p = root; p != NULL; p = p->next)
2023 if (p == menu_to_check
2024 || (p->children != NULL
2025 && check_menu_pointer(p->children, menu_to_check) == OK))
2026 return OK;
2027 return FAIL;
2028}
2029
2030/*
Bram Moolenaar071d4272004-06-13 20:20:40 +00002031 * After we have started the GUI, then we can create any menus that have been
2032 * defined. This is done once here. add_menu_path() may have already been
2033 * called to define these menus, and may be called again. This function calls
2034 * itself recursively. Should be called at the top level with:
Bram Moolenaara06ecab2016-07-16 14:47:36 +02002035 * gui_create_initial_menus(root_menu);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002036 */
2037 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002038gui_create_initial_menus(vimmenu_T *menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002039{
2040 int idx = 0;
2041
2042 while (menu != NULL)
2043 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002044 // Don't add a menu when only a tip was defined.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002045 if (menu->modes & MENU_ALL_MODES)
2046 {
2047 if (menu->children != NULL)
2048 {
2049 gui_mch_add_menu(menu, idx);
2050 gui_create_initial_menus(menu->children);
2051 }
2052 else
2053 gui_mch_add_menu_item(menu, idx);
2054 }
2055 menu = menu->next;
2056 ++idx;
2057 }
2058}
2059
2060/*
2061 * Used recursively by gui_update_menus (see below)
2062 */
2063 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002064gui_update_menus_recurse(vimmenu_T *menu, int mode)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002065{
2066 int grey;
2067
2068 while (menu)
2069 {
2070 if ((menu->modes & menu->enabled & mode)
Bram Moolenaar4f974752019-02-17 17:44:42 +01002071# if defined(FEAT_GUI_MSWIN) && defined(FEAT_TEAROFF)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002072 || menu_is_tearoff(menu->dname)
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002073# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002074 )
2075 grey = FALSE;
2076 else
2077 grey = TRUE;
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002078# ifdef FEAT_GUI_ATHENA
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002079 // Hiding menus doesn't work for Athena, it can cause a crash.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002080 gui_mch_menu_grey(menu, grey);
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002081# else
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002082 // Never hide a toplevel menu, it may make the menubar resize or
2083 // disappear. Same problem for ToolBar items.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002084 if (vim_strchr(p_go, GO_GREY) != NULL || menu->parent == NULL
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002085# ifdef FEAT_TOOLBAR
Bram Moolenaar071d4272004-06-13 20:20:40 +00002086 || menu_is_toolbar(menu->parent->name)
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002087# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002088 )
2089 gui_mch_menu_grey(menu, grey);
2090 else
2091 gui_mch_menu_hidden(menu, grey);
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002092# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002093 gui_update_menus_recurse(menu->children, mode);
2094 menu = menu->next;
2095 }
2096}
2097
2098/*
2099 * Make sure only the valid menu items appear for this mode. If
2100 * force_menu_update is not TRUE, then we only do this if the mode has changed
2101 * since last time. If "modes" is not 0, then we use these modes instead.
2102 */
2103 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002104gui_update_menus(int modes)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002105{
2106 static int prev_mode = -1;
2107 int mode = 0;
2108
2109 if (modes != 0x0)
2110 mode = modes;
2111 else
Bram Moolenaar29a2c082018-03-05 21:06:23 +01002112 mode = get_menu_mode_flag();
Bram Moolenaar071d4272004-06-13 20:20:40 +00002113
2114 if (force_menu_update || mode != prev_mode)
2115 {
2116 gui_update_menus_recurse(root_menu, mode);
2117 gui_mch_draw_menubar();
2118 prev_mode = mode;
2119 force_menu_update = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002120 }
2121}
2122
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002123# if defined(FEAT_GUI_MSWIN) || defined(FEAT_GUI_MOTIF) \
Bram Moolenaar241a8aa2005-12-06 20:04:44 +00002124 || defined(FEAT_GUI_GTK) || defined(FEAT_GUI_PHOTON) || defined(PROTO)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002125/*
2126 * Check if a key is used as a mnemonic for a toplevel menu.
2127 * Case of the key is ignored.
2128 */
2129 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002130gui_is_menu_shortcut(int key)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002131{
2132 vimmenu_T *menu;
2133
2134 if (key < 256)
2135 key = TOLOWER_LOC(key);
Bram Moolenaar00d253e2020-04-06 22:13:01 +02002136 FOR_ALL_MENUS(menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002137 if (menu->mnemonic == key
2138 || (menu->mnemonic < 256 && TOLOWER_LOC(menu->mnemonic) == key))
2139 return TRUE;
2140 return FALSE;
2141}
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002142# endif
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002143#endif // FEAT_GUI
Bram Moolenaar071d4272004-06-13 20:20:40 +00002144
Bram Moolenaar4f974752019-02-17 17:44:42 +01002145#if (defined(FEAT_GUI_MSWIN) && defined(FEAT_TEAROFF)) || defined(PROTO)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002146
2147/*
2148 * Deal with tearoff items that are added like a menu item.
2149 * Currently only for Win32 GUI. Others may follow later.
2150 */
2151
2152 void
2153gui_mch_toggle_tearoffs(int enable)
2154{
2155 int pri_tab[MENUDEPTH + 1];
2156 int i;
2157
2158 if (enable)
2159 {
2160 for (i = 0; i < MENUDEPTH; ++i)
2161 pri_tab[i] = 500;
2162 pri_tab[MENUDEPTH] = -1;
2163 gui_create_tearoffs_recurse(root_menu, (char_u *)"", pri_tab, 0);
2164 }
2165 else
2166 gui_destroy_tearoffs_recurse(root_menu);
2167 s_tearoffs = enable;
2168}
2169
2170/*
2171 * Recursively add tearoff items
2172 */
2173 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002174gui_create_tearoffs_recurse(
2175 vimmenu_T *menu,
2176 const char_u *pname,
2177 int *pri_tab,
2178 int pri_idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002179{
2180 char_u *newpname = NULL;
2181 int len;
2182 char_u *s;
2183 char_u *d;
2184
2185 if (pri_tab[pri_idx + 1] != -1)
2186 ++pri_idx;
2187 while (menu != NULL)
2188 {
2189 if (menu->children != NULL && menu_is_menubar(menu->name))
2190 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002191 // Add the menu name to the menu path. Insert a backslash before
2192 // dots (it's used to separate menu names).
Bram Moolenaar071d4272004-06-13 20:20:40 +00002193 len = (int)STRLEN(pname) + (int)STRLEN(menu->name);
2194 for (s = menu->name; *s; ++s)
2195 if (*s == '.' || *s == '\\')
2196 ++len;
2197 newpname = alloc(len + TEAR_LEN + 2);
2198 if (newpname != NULL)
2199 {
2200 STRCPY(newpname, pname);
2201 d = newpname + STRLEN(newpname);
2202 for (s = menu->name; *s; ++s)
2203 {
2204 if (*s == '.' || *s == '\\')
2205 *d++ = '\\';
2206 *d++ = *s;
2207 }
2208 *d = NUL;
2209
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002210 // check if tearoff already exists
Bram Moolenaar071d4272004-06-13 20:20:40 +00002211 if (STRCMP(menu->children->name, TEAR_STRING) != 0)
2212 {
2213 gui_add_tearoff(newpname, pri_tab, pri_idx - 1);
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002214 *d = NUL; // remove TEAR_STRING
Bram Moolenaar071d4272004-06-13 20:20:40 +00002215 }
2216
2217 STRCAT(newpname, ".");
2218 gui_create_tearoffs_recurse(menu->children, newpname,
2219 pri_tab, pri_idx);
2220 vim_free(newpname);
2221 }
2222 }
2223 menu = menu->next;
2224 }
2225}
2226
2227/*
2228 * Add tear-off menu item for a submenu.
2229 * "tearpath" is the menu path, and must have room to add TEAR_STRING.
2230 */
2231 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002232gui_add_tearoff(char_u *tearpath, int *pri_tab, int pri_idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002233{
2234 char_u *tbuf;
2235 int t;
2236 vimmenu_T menuarg;
2237
2238 tbuf = alloc(5 + (unsigned int)STRLEN(tearpath));
2239 if (tbuf != NULL)
2240 {
2241 tbuf[0] = K_SPECIAL;
2242 tbuf[1] = K_SECOND(K_TEAROFF);
2243 tbuf[2] = K_THIRD(K_TEAROFF);
2244 STRCPY(tbuf + 3, tearpath);
2245 STRCAT(tbuf + 3, "\r");
2246
2247 STRCAT(tearpath, ".");
2248 STRCAT(tearpath, TEAR_STRING);
2249
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002250 // Priority of tear-off is always 1
Bram Moolenaar071d4272004-06-13 20:20:40 +00002251 t = pri_tab[pri_idx + 1];
2252 pri_tab[pri_idx + 1] = 1;
2253
2254#ifdef FEAT_TOOLBAR
2255 menuarg.iconfile = NULL;
2256 menuarg.iconidx = -1;
2257 menuarg.icon_builtin = FALSE;
2258#endif
2259 menuarg.noremap[0] = REMAP_NONE;
2260 menuarg.silent[0] = TRUE;
2261
2262 menuarg.modes = MENU_ALL_MODES;
2263 add_menu_path(tearpath, &menuarg, pri_tab, tbuf, FALSE);
2264
2265 menuarg.modes = MENU_TIP_MODE;
2266 add_menu_path(tearpath, &menuarg, pri_tab,
2267 (char_u *)_("Tear off this menu"), FALSE);
2268
2269 pri_tab[pri_idx + 1] = t;
2270 vim_free(tbuf);
2271 }
2272}
2273
2274/*
2275 * Recursively destroy tearoff items
2276 */
2277 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002278gui_destroy_tearoffs_recurse(vimmenu_T *menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002279{
2280 while (menu)
2281 {
2282 if (menu->children)
2283 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002284 // check if tearoff exists
Bram Moolenaar071d4272004-06-13 20:20:40 +00002285 if (STRCMP(menu->children->name, TEAR_STRING) == 0)
2286 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002287 // Disconnect the item and free the memory
Bram Moolenaar071d4272004-06-13 20:20:40 +00002288 free_menu(&menu->children);
2289 }
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002290 if (menu->children != NULL) // if not the last one
Bram Moolenaar071d4272004-06-13 20:20:40 +00002291 gui_destroy_tearoffs_recurse(menu->children);
2292 }
2293 menu = menu->next;
2294 }
2295}
2296
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002297#endif // FEAT_GUI_MSWIN && FEAT_TEAROFF
Bram Moolenaar071d4272004-06-13 20:20:40 +00002298
2299/*
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002300 * Execute "menu". Use by ":emenu" and the window toolbar.
2301 * "eap" is NULL for the window toolbar.
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002302 * "mode_idx" specifies a MENU_INDEX_ value, use -1 to depend on the current
2303 * state.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002304 */
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002305 void
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002306execute_menu(exarg_T *eap, vimmenu_T *menu, int mode_idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002307{
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002308 int idx = mode_idx;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002309
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002310 if (idx < 0)
2311 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002312 // Use the Insert mode entry when returning to Insert mode.
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002313 if (restart_edit
Bram Moolenaar4463f292005-09-25 22:20:24 +00002314#ifdef FEAT_EVAL
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002315 && !current_sctx.sc_sid
Bram Moolenaar4463f292005-09-25 22:20:24 +00002316#endif
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002317 )
Bram Moolenaar071d4272004-06-13 20:20:40 +00002318 {
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002319 idx = MENU_INDEX_INSERT;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002320 }
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002321#ifdef FEAT_TERMINAL
2322 else if (term_use_loop())
Bram Moolenaar071d4272004-06-13 20:20:40 +00002323 {
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002324 idx = MENU_INDEX_TERMINAL;
2325 }
2326#endif
2327 else if (VIsual_active)
2328 {
2329 idx = MENU_INDEX_VISUAL;
2330 }
2331 else if (eap != NULL && eap->addr_count)
2332 {
2333 pos_T tpos;
2334
2335 idx = MENU_INDEX_VISUAL;
2336
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002337 // GEDDES: This is not perfect - but it is a
2338 // quick way of detecting whether we are doing this from a
2339 // selection - see if the range matches up with the visual
2340 // select start and end.
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002341 if ((curbuf->b_visual.vi_start.lnum == eap->line1)
2342 && (curbuf->b_visual.vi_end.lnum) == eap->line2)
2343 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002344 // Set it up for visual mode - equivalent to gv.
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002345 VIsual_mode = curbuf->b_visual.vi_mode;
2346 tpos = curbuf->b_visual.vi_end;
2347 curwin->w_cursor = curbuf->b_visual.vi_start;
2348 curwin->w_curswant = curbuf->b_visual.vi_curswant;
2349 }
2350 else
2351 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002352 // Set it up for line-wise visual mode
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002353 VIsual_mode = 'V';
2354 curwin->w_cursor.lnum = eap->line1;
2355 curwin->w_cursor.col = 1;
2356 tpos.lnum = eap->line2;
2357 tpos.col = MAXCOL;
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002358 tpos.coladd = 0;
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002359 }
2360
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002361 // Activate visual mode
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002362 VIsual_active = TRUE;
2363 VIsual_reselect = TRUE;
2364 check_cursor();
2365 VIsual = curwin->w_cursor;
2366 curwin->w_cursor = tpos;
2367
2368 check_cursor();
2369
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002370 // Adjust the cursor to make sure it is in the correct pos
2371 // for exclusive mode
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002372 if (*p_sel == 'e' && gchar_cursor() != NUL)
2373 ++curwin->w_cursor.col;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002374 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00002375 }
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002376
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002377 // For the WinBar menu always use the Normal mode menu.
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002378 if (idx == -1 || eap == NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002379 idx = MENU_INDEX_NORMAL;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002380
Bram Moolenaarce793532019-05-05 14:19:20 +02002381 if (idx != MENU_INDEX_INVALID && menu->strings[idx] != NULL
2382 && (menu->modes & (1 << idx)))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002383 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002384 // When executing a script or function execute the commands right now.
2385 // Also for the window toolbar.
2386 // Otherwise put them in the typeahead buffer.
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002387 if (eap == NULL
Bram Moolenaar9c4b4ab2006-12-05 20:29:56 +00002388#ifdef FEAT_EVAL
Bram Moolenaarf29c1c62018-09-10 21:05:02 +02002389 || current_sctx.sc_sid != 0
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002390#endif
2391 )
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002392 {
2393 save_state_T save_state;
2394
2395 ++ex_normal_busy;
2396 if (save_current_state(&save_state))
2397 exec_normal_cmd(menu->strings[idx], menu->noremap[idx],
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002398 menu->silent[idx]);
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002399 restore_current_state(&save_state);
2400 --ex_normal_busy;
2401 }
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002402 else
2403 ins_typebuf(menu->strings[idx], menu->noremap[idx], 0,
Bram Moolenaar071d4272004-06-13 20:20:40 +00002404 TRUE, menu->silent[idx]);
2405 }
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002406 else if (eap != NULL)
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002407 {
2408 char_u *mode;
2409
2410 switch (idx)
2411 {
2412 case MENU_INDEX_VISUAL:
2413 mode = (char_u *)"Visual";
2414 break;
2415 case MENU_INDEX_SELECT:
2416 mode = (char_u *)"Select";
2417 break;
2418 case MENU_INDEX_OP_PENDING:
2419 mode = (char_u *)"Op-pending";
2420 break;
2421 case MENU_INDEX_TERMINAL:
2422 mode = (char_u *)"Terminal";
2423 break;
2424 case MENU_INDEX_INSERT:
2425 mode = (char_u *)"Insert";
2426 break;
2427 case MENU_INDEX_CMDLINE:
2428 mode = (char_u *)"Cmdline";
2429 break;
2430 // case MENU_INDEX_TIP: cannot happen
2431 default:
2432 mode = (char_u *)"Normal";
2433 }
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01002434 semsg(_("E335: Menu not defined for %s mode"), mode);
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002435 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00002436}
2437
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002438/*
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002439 * Lookup a menu by the descriptor name e.g. "File.New"
2440 * Returns NULL if the menu is not found
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002441 */
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002442 static vimmenu_T *
2443menu_getbyname(char_u *name_arg)
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002444{
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002445 char_u *name;
2446 char_u *saved_name;
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002447 vimmenu_T *menu;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002448 char_u *p;
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002449 int gave_emsg = FALSE;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002450
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002451 saved_name = vim_strsave(name_arg);
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002452 if (saved_name == NULL)
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002453 return NULL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002454
2455 menu = *get_root_menu(saved_name);
2456 name = saved_name;
2457 while (*name)
2458 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002459 // Find in the menu hierarchy
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002460 p = menu_name_skip(name);
2461
2462 while (menu != NULL)
2463 {
2464 if (menu_name_equal(name, menu))
2465 {
2466 if (*p == NUL && menu->children != NULL)
2467 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01002468 emsg(_("E333: Menu path must lead to a menu item"));
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002469 gave_emsg = TRUE;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002470 menu = NULL;
2471 }
2472 else if (*p != NUL && menu->children == NULL)
2473 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01002474 emsg(_(e_notsubmenu));
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002475 menu = NULL;
2476 }
2477 break;
2478 }
2479 menu = menu->next;
2480 }
2481 if (menu == NULL || *p == NUL)
2482 break;
2483 menu = menu->children;
2484 name = p;
2485 }
2486 vim_free(saved_name);
2487 if (menu == NULL)
2488 {
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002489 if (!gave_emsg)
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002490 semsg(_("E334: Menu not found: %s"), name_arg);
2491 return NULL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002492 }
2493
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002494 return menu;
2495}
2496
2497/*
2498 * Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy and
2499 * execute it.
2500 */
2501 void
2502ex_emenu(exarg_T *eap)
2503{
2504 vimmenu_T *menu;
2505 char_u *arg = eap->arg;
2506 int mode_idx = -1;
2507
2508 if (arg[0] && VIM_ISWHITE(arg[1]))
2509 {
2510 switch (arg[0])
2511 {
2512 case 'n': mode_idx = MENU_INDEX_NORMAL; break;
2513 case 'v': mode_idx = MENU_INDEX_VISUAL; break;
2514 case 's': mode_idx = MENU_INDEX_SELECT; break;
2515 case 'o': mode_idx = MENU_INDEX_OP_PENDING; break;
2516 case 't': mode_idx = MENU_INDEX_TERMINAL; break;
2517 case 'i': mode_idx = MENU_INDEX_INSERT; break;
2518 case 'c': mode_idx = MENU_INDEX_CMDLINE; break;
2519 default: semsg(_(e_invarg2), arg);
2520 return;
2521 }
2522 arg = skipwhite(arg + 2);
2523 }
2524
2525 menu = menu_getbyname(arg);
2526 if (menu == NULL)
2527 return;
2528
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002529 // Found the menu, so execute.
2530 execute_menu(eap, menu, mode_idx);
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002531}
2532
2533/*
2534 * Handle a click in the window toolbar of "wp" at column "col".
2535 */
2536 void
2537winbar_click(win_T *wp, int col)
2538{
2539 int idx;
2540
2541 if (wp->w_winbar_items == NULL)
2542 return;
2543 for (idx = 0; wp->w_winbar_items[idx].wb_menu != NULL; ++idx)
2544 {
2545 winbar_item_T *item = &wp->w_winbar_items[idx];
2546
2547 if (col >= item->wb_startcol && col <= item->wb_endcol)
2548 {
Bram Moolenaard2fad672019-05-04 16:55:25 +02002549 win_T *save_curwin = NULL;
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002550 pos_T save_visual = VIsual;
2551 int save_visual_active = VIsual_active;
2552 int save_visual_select = VIsual_select;
2553 int save_visual_reselect = VIsual_reselect;
2554 int save_visual_mode = VIsual_mode;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002555
2556 if (wp != curwin)
2557 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002558 // Clicking in the window toolbar of a not-current window.
2559 // Make that window the current one and save Visual mode.
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002560 save_curwin = curwin;
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002561 VIsual_active = FALSE;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002562 curwin = wp;
2563 curbuf = curwin->w_buffer;
2564 check_cursor();
2565 }
2566
Bram Moolenaard2fad672019-05-04 16:55:25 +02002567 // Note: the command might close the current window.
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002568 execute_menu(NULL, item->wb_menu, -1);
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002569
Bram Moolenaard2fad672019-05-04 16:55:25 +02002570 if (save_curwin != NULL && win_valid(save_curwin))
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002571 {
2572 curwin = save_curwin;
2573 curbuf = curwin->w_buffer;
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002574 VIsual = save_visual;
2575 VIsual_active = save_visual_active;
2576 VIsual_select = save_visual_select;
2577 VIsual_reselect = save_visual_reselect;
2578 VIsual_mode = save_visual_mode;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002579 }
Bram Moolenaard2fad672019-05-04 16:55:25 +02002580 if (!win_valid(wp))
2581 break;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002582 }
2583 }
2584}
2585
2586#if defined(FEAT_GUI_MSWIN) || defined(FEAT_GUI_GTK) \
Bram Moolenaarb3f74062020-02-26 16:16:53 +01002587 || defined(FEAT_TERM_POPUP_MENU) || defined(FEAT_GUI_HAIKU) \
Bram Moolenaar071d4272004-06-13 20:20:40 +00002588 || defined(FEAT_BEVAL_TIP) || defined(PROTO)
2589/*
2590 * Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy.
2591 */
2592 vimmenu_T *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002593gui_find_menu(char_u *path_name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002594{
2595 vimmenu_T *menu = NULL;
2596 char_u *name;
2597 char_u *saved_name;
2598 char_u *p;
2599
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002600 menu = *get_root_menu(path_name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002601
2602 saved_name = vim_strsave(path_name);
2603 if (saved_name == NULL)
2604 return NULL;
2605
2606 name = saved_name;
2607 while (*name)
2608 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002609 // find the end of one dot-separated name and put a NUL at the dot
Bram Moolenaar071d4272004-06-13 20:20:40 +00002610 p = menu_name_skip(name);
2611
2612 while (menu != NULL)
2613 {
Bram Moolenaard91f7042011-01-04 17:49:32 +01002614 if (menu_name_equal(name, menu))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002615 {
2616 if (menu->children == NULL)
2617 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002618 // found a menu item instead of a sub-menu
Bram Moolenaar071d4272004-06-13 20:20:40 +00002619 if (*p == NUL)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01002620 emsg(_("E336: Menu path must lead to a sub-menu"));
Bram Moolenaar071d4272004-06-13 20:20:40 +00002621 else
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01002622 emsg(_(e_notsubmenu));
Bram Moolenaar071d4272004-06-13 20:20:40 +00002623 menu = NULL;
2624 goto theend;
2625 }
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002626 if (*p == NUL) // found a full match
Bram Moolenaar071d4272004-06-13 20:20:40 +00002627 goto theend;
2628 break;
2629 }
2630 menu = menu->next;
2631 }
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002632 if (menu == NULL) // didn't find it
Bram Moolenaar071d4272004-06-13 20:20:40 +00002633 break;
2634
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002635 // Found a match, search the sub-menu.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002636 menu = menu->children;
2637 name = p;
2638 }
2639
2640 if (menu == NULL)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01002641 emsg(_("E337: Menu not found - check menu names"));
Bram Moolenaar071d4272004-06-13 20:20:40 +00002642theend:
2643 vim_free(saved_name);
2644 return menu;
2645}
2646#endif
2647
2648#ifdef FEAT_MULTI_LANG
2649/*
2650 * Translation of menu names. Just a simple lookup table.
2651 */
2652
2653typedef struct
2654{
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002655 char_u *from; // English name
2656 char_u *from_noamp; // same, without '&'
2657 char_u *to; // translated name
Bram Moolenaar071d4272004-06-13 20:20:40 +00002658} menutrans_T;
2659
2660static garray_T menutrans_ga = {0, 0, 0, 0, NULL};
2661#endif
2662
2663/*
2664 * ":menutrans".
2665 * This function is also defined without the +multi_lang feature, in which
2666 * case the commands are ignored.
2667 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00002668 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002669ex_menutranslate(exarg_T *eap UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002670{
2671#ifdef FEAT_MULTI_LANG
2672 char_u *arg = eap->arg;
2673 menutrans_T *tp;
2674 int i;
2675 char_u *from, *from_noamp, *to;
2676
2677 if (menutrans_ga.ga_itemsize == 0)
2678 ga_init2(&menutrans_ga, (int)sizeof(menutrans_T), 5);
2679
2680 /*
2681 * ":menutrans clear": clear all translations.
2682 */
Bram Moolenaar1966c242020-04-20 22:42:32 +02002683 if (STRNCMP(arg, "clear", 5) == 0 && ends_excmd2(arg, skipwhite(arg + 5)))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002684 {
2685 tp = (menutrans_T *)menutrans_ga.ga_data;
2686 for (i = 0; i < menutrans_ga.ga_len; ++i)
2687 {
2688 vim_free(tp[i].from);
2689 vim_free(tp[i].from_noamp);
2690 vim_free(tp[i].to);
2691 }
2692 ga_clear(&menutrans_ga);
2693# ifdef FEAT_EVAL
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002694 // Delete all "menutrans_" global variables.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002695 del_menutrans_vars();
2696# endif
2697 }
2698 else
2699 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002700 // ":menutrans from to": add translation
Bram Moolenaar071d4272004-06-13 20:20:40 +00002701 from = arg;
2702 arg = menu_skip_part(arg);
2703 to = skipwhite(arg);
2704 *arg = NUL;
2705 arg = menu_skip_part(to);
Bram Moolenaar1966c242020-04-20 22:42:32 +02002706 if (arg == to || ends_excmd2(eap->arg, from)
2707 || ends_excmd2(eap->arg, to)
2708 || !ends_excmd2(eap->arg, skipwhite(arg)))
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01002709 emsg(_(e_invarg));
Bram Moolenaar071d4272004-06-13 20:20:40 +00002710 else
2711 {
2712 if (ga_grow(&menutrans_ga, 1) == OK)
2713 {
2714 tp = (menutrans_T *)menutrans_ga.ga_data;
2715 from = vim_strsave(from);
Bram Moolenaarfc1421e2006-04-20 22:17:20 +00002716 if (from != NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002717 {
Bram Moolenaarfc1421e2006-04-20 22:17:20 +00002718 from_noamp = menu_text(from, NULL, NULL);
2719 to = vim_strnsave(to, (int)(arg - to));
2720 if (from_noamp != NULL && to != NULL)
2721 {
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002722 menu_translate_tab_and_shift(from);
2723 menu_translate_tab_and_shift(to);
2724 menu_unescape_name(from);
2725 menu_unescape_name(to);
Bram Moolenaarfc1421e2006-04-20 22:17:20 +00002726 tp[menutrans_ga.ga_len].from = from;
2727 tp[menutrans_ga.ga_len].from_noamp = from_noamp;
2728 tp[menutrans_ga.ga_len].to = to;
2729 ++menutrans_ga.ga_len;
2730 }
2731 else
2732 {
2733 vim_free(from);
2734 vim_free(from_noamp);
2735 vim_free(to);
2736 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00002737 }
2738 }
2739 }
2740 }
2741#endif
2742}
2743
2744#if defined(FEAT_MULTI_LANG) || defined(FEAT_TOOLBAR)
2745/*
2746 * Find the character just after one part of a menu name.
2747 */
2748 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002749menu_skip_part(char_u *p)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002750{
Bram Moolenaar1c465442017-03-12 20:10:05 +01002751 while (*p != NUL && *p != '.' && !VIM_ISWHITE(*p))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002752 {
2753 if ((*p == '\\' || *p == Ctrl_V) && p[1] != NUL)
2754 ++p;
2755 ++p;
2756 }
2757 return p;
2758}
2759#endif
2760
2761#ifdef FEAT_MULTI_LANG
2762/*
2763 * Lookup part of a menu name in the translations.
2764 * Return a pointer to the translation or NULL if not found.
2765 */
2766 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002767menutrans_lookup(char_u *name, int len)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002768{
2769 menutrans_T *tp = (menutrans_T *)menutrans_ga.ga_data;
2770 int i;
2771 char_u *dname;
2772
2773 for (i = 0; i < menutrans_ga.ga_len; ++i)
Bram Moolenaar11dd8c12017-03-04 20:41:34 +01002774 if (STRNICMP(name, tp[i].from, len) == 0 && tp[i].from[len] == NUL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002775 return tp[i].to;
2776
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002777 // Now try again while ignoring '&' characters.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002778 i = name[len];
2779 name[len] = NUL;
2780 dname = menu_text(name, NULL, NULL);
2781 name[len] = i;
2782 if (dname != NULL)
2783 {
2784 for (i = 0; i < menutrans_ga.ga_len; ++i)
Bram Moolenaar11dd8c12017-03-04 20:41:34 +01002785 if (STRICMP(dname, tp[i].from_noamp) == 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002786 {
2787 vim_free(dname);
2788 return tp[i].to;
2789 }
2790 vim_free(dname);
2791 }
2792
2793 return NULL;
2794}
Bram Moolenaar071d4272004-06-13 20:20:40 +00002795
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002796/*
2797 * Unescape the name in the translate dictionary table.
2798 */
2799 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002800menu_unescape_name(char_u *name)
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002801{
2802 char_u *p;
2803
Bram Moolenaar91acfff2017-03-12 19:22:36 +01002804 for (p = name; *p && *p != '.'; MB_PTR_ADV(p))
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002805 if (*p == '\\')
2806 STRMOVE(p, p + 1);
2807}
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002808#endif // FEAT_MULTI_LANG
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002809
2810/*
2811 * Isolate the menu name.
2812 * Skip the menu name, and translate <Tab> into a real TAB.
2813 */
2814 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002815menu_translate_tab_and_shift(char_u *arg_start)
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002816{
2817 char_u *arg = arg_start;
2818
Bram Moolenaar1c465442017-03-12 20:10:05 +01002819 while (*arg && !VIM_ISWHITE(*arg))
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002820 {
2821 if ((*arg == '\\' || *arg == Ctrl_V) && arg[1] != NUL)
2822 arg++;
2823 else if (STRNICMP(arg, "<TAB>", 5) == 0)
2824 {
2825 *arg = TAB;
2826 STRMOVE(arg + 1, arg + 5);
2827 }
2828 arg++;
2829 }
2830 if (*arg != NUL)
2831 *arg++ = NUL;
2832 arg = skipwhite(arg);
2833
2834 return arg;
2835}
2836
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002837/*
2838 * Get the information about a menu item in mode 'which'
2839 */
2840 static int
2841menuitem_getinfo(vimmenu_T *menu, int modes, dict_T *dict)
2842{
2843 int status;
2844
2845 if (menu_is_tearoff(menu->dname)) // skip tearoff menu item
2846 return OK;
2847
2848 status = dict_add_string(dict, "name", menu->name);
2849 if (status == OK)
2850 status = dict_add_string(dict, "display", menu->dname);
2851 if (status == OK && menu->actext != NULL)
2852 status = dict_add_string(dict, "accel", menu->actext);
2853 if (status == OK)
2854 status = dict_add_number(dict, "priority", menu->priority);
2855 if (status == OK)
2856 status = dict_add_string(dict, "modes",
2857 get_menu_mode_str(menu->modes));
2858#ifdef FEAT_TOOLBAR
2859 if (status == OK && menu->iconfile != NULL)
2860 status = dict_add_string(dict, "icon", menu->iconfile);
2861 if (status == OK && menu->iconidx >= 0)
2862 status = dict_add_number(dict, "iconidx", menu->iconidx);
2863#endif
2864 if (status == OK)
2865 {
2866 char_u buf[NUMBUFLEN];
2867
2868 if (has_mbyte)
2869 buf[utf_char2bytes(menu->mnemonic, buf)] = NUL;
2870 else
2871 {
2872 buf[0] = (char_u)menu->mnemonic;
2873 buf[1] = NUL;
2874 }
2875 status = dict_add_string(dict, "shortcut", buf);
2876 }
2877 if (status == OK && menu->children == NULL)
2878 {
2879 int bit;
2880
2881 // Get the first mode in which the menu is available
Bram Moolenaar56cb3372020-03-16 20:04:41 +01002882 for (bit = 0; bit < MENU_MODES && !((1 << bit) & modes); bit++)
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002883 ;
Bram Moolenaar56cb3372020-03-16 20:04:41 +01002884 if (bit < MENU_MODES) // just in case, avoid Coverity warning
2885 {
2886 if (menu->strings[bit] != NULL)
Bram Moolenaar292b90d2020-03-18 15:23:16 +01002887 {
2888 char_u *tofree = NULL;
2889
Bram Moolenaar56cb3372020-03-16 20:04:41 +01002890 status = dict_add_string(dict, "rhs",
2891 *menu->strings[bit] == NUL
Bram Moolenaar292b90d2020-03-18 15:23:16 +01002892 ? (char_u *)"<Nop>"
2893 : (tofree = str2special_save(
2894 menu->strings[bit], FALSE)));
2895 vim_free(tofree);
2896 }
Bram Moolenaar56cb3372020-03-16 20:04:41 +01002897 if (status == OK)
2898 status = dict_add_bool(dict, "noremenu",
2899 menu->noremap[bit] == REMAP_NONE);
2900 if (status == OK)
2901 status = dict_add_bool(dict, "script",
2902 menu->noremap[bit] == REMAP_SCRIPT);
2903 if (status == OK)
2904 status = dict_add_bool(dict, "silent", menu->silent[bit]);
2905 if (status == OK)
2906 status = dict_add_bool(dict, "enabled",
2907 ((menu->enabled & (1 << bit)) != 0));
2908 }
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002909 }
Bram Moolenaar56cb3372020-03-16 20:04:41 +01002910
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002911 // If there are submenus, add all the submenu display names
2912 if (status == OK && menu->children != NULL)
2913 {
2914 list_T *l = list_alloc();
2915 vimmenu_T *child;
2916
2917 if (l == NULL)
2918 return FAIL;
2919
2920 dict_add_list(dict, "submenus", l);
2921 child = menu->children;
2922 while (child)
2923 {
2924 if (!menu_is_tearoff(child->dname)) // skip tearoff menu
2925 list_append_string(l, child->dname, -1);
2926 child = child->next;
2927 }
2928 }
2929
2930 return status;
2931}
2932
2933/*
2934 * "menu_info()" function
2935 * Return information about a menu (including all the child menus)
2936 */
2937 void
2938f_menu_info(typval_T *argvars, typval_T *rettv)
2939{
2940 char_u *menu_name;
2941 char_u *which;
2942 int modes;
2943 char_u *saved_name;
2944 char_u *name;
2945 vimmenu_T *menu;
2946 dict_T *retdict;
2947
2948 if (rettv_dict_alloc(rettv) != OK)
2949 return;
2950 retdict = rettv->vval.v_dict;
2951
2952 menu_name = tv_get_string_chk(&argvars[0]);
2953 if (menu_name == NULL)
2954 return;
2955
2956 // menu mode
2957 if (argvars[1].v_type != VAR_UNKNOWN)
2958 which = tv_get_string_chk(&argvars[1]);
2959 else
2960 which = (char_u *)""; // Default is modes for "menu"
2961 if (which == NULL)
2962 return;
2963
2964 modes = get_menu_cmd_modes(which, *which == '!', NULL, NULL);
2965
2966 // Locate the specified menu or menu item
2967 menu = *get_root_menu(menu_name);
2968 saved_name = vim_strsave(menu_name);
2969 if (saved_name == NULL)
2970 return;
2971 if (*saved_name != NUL)
2972 {
2973 char_u *p;
2974
2975 name = saved_name;
2976 while (*name)
2977 {
2978 // Find in the menu hierarchy
2979 p = menu_name_skip(name);
2980 while (menu != NULL)
2981 {
2982 if (menu_name_equal(name, menu))
2983 break;
2984 menu = menu->next;
2985 }
2986 if (menu == NULL || *p == NUL)
2987 break;
2988 menu = menu->children;
2989 name = p;
2990 }
2991 }
2992 vim_free(saved_name);
2993
2994 if (menu == NULL) // specified menu not found
2995 return;
2996
2997 if (menu->modes & modes)
2998 menuitem_getinfo(menu, modes, retdict);
2999}
3000
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01003001#endif // FEAT_MENU