blob: e4008baab43d293cd132e15d460ed52f40c326d3 [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
Bram Moolenaar920d3112022-11-13 21:10:02 +000049// When non-zero no menu must be added or cleared. Prevents the list of menus
50// changing while listing them.
51static int menus_locked = 0;
52
Bram Moolenaar071d4272004-06-13 20:20:40 +000053#if defined(FEAT_MULTI_LANG) || defined(FEAT_TOOLBAR)
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010054static char_u *menu_skip_part(char_u *p);
Bram Moolenaar071d4272004-06-13 20:20:40 +000055#endif
56#ifdef FEAT_MULTI_LANG
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010057static char_u *menutrans_lookup(char_u *name, int len);
58static void menu_unescape_name(char_u *p);
Bram Moolenaar071d4272004-06-13 20:20:40 +000059#endif
60
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010061static char_u *menu_translate_tab_and_shift(char_u *arg_start);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +020062
Bram Moolenaar4ba37b52019-12-04 21:57:43 +010063// The character for each menu mode
Bram Moolenaar4c5d8152018-10-19 22:36:53 +020064static char *menu_mode_chars[] = {"n", "v", "s", "o", "i", "c", "tl", "t"};
Bram Moolenaar071d4272004-06-13 20:20:40 +000065
Bram Moolenaar071d4272004-06-13 20:20:40 +000066#ifdef FEAT_TOOLBAR
67static const char *toolbar_names[] =
68{
69 /* 0 */ "New", "Open", "Save", "Undo", "Redo",
70 /* 5 */ "Cut", "Copy", "Paste", "Print", "Help",
71 /* 10 */ "Find", "SaveAll", "SaveSesn", "NewSesn", "LoadSesn",
72 /* 15 */ "RunScript", "Replace", "WinClose", "WinMax", "WinMin",
73 /* 20 */ "WinSplit", "Shell", "FindPrev", "FindNext", "FindHelp",
74 /* 25 */ "Make", "TagJump", "RunCtags", "WinVSplit", "WinMaxWidth",
75 /* 30 */ "WinMinWidth", "Exit"
76};
K.Takataeeec2542021-06-02 13:28:16 +020077# define TOOLBAR_NAME_COUNT ARRAY_LENGTH(toolbar_names)
Bram Moolenaar071d4272004-06-13 20:20:40 +000078#endif
79
80/*
Bram Moolenaar1b9645d2017-09-17 23:03:31 +020081 * Return TRUE if "name" is a window toolbar menu name.
82 */
83 static int
84menu_is_winbar(char_u *name)
85{
Bram Moolenaar378daf82017-09-23 23:58:28 +020086 return (STRNCMP(name, "WinBar", 6) == 0);
Bram Moolenaar1b9645d2017-09-17 23:03:31 +020087}
88
89 int
90winbar_height(win_T *wp)
91{
92 if (wp->w_winbar != NULL && wp->w_winbar->children != NULL)
93 return 1;
94 return 0;
95}
96
97 static vimmenu_T **
98get_root_menu(char_u *name)
99{
100 if (menu_is_winbar(name))
101 return &curwin->w_winbar;
102 return &root_menu;
103}
104
105/*
Bram Moolenaar920d3112022-11-13 21:10:02 +0000106 * If "menus_locked" is set then give an error and return TRUE.
107 * Otherwise return FALSE.
108 */
109 static int
110is_menus_locked(void)
111{
112 if (menus_locked > 0)
113 {
114 emsg(_(e_cannot_change_menus_while_listing));
115 return TRUE;
116 }
117 return FALSE;
118}
119
120/*
Bram Moolenaar071d4272004-06-13 20:20:40 +0000121 * Do the :menu command and relatives.
122 */
123 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100124ex_menu(
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100125 exarg_T *eap) // Ex command arguments
Bram Moolenaar071d4272004-06-13 20:20:40 +0000126{
127 char_u *menu_path;
128 int modes;
129 char_u *map_to;
130 int noremap;
131 int silent = FALSE;
Bram Moolenaar8b2d9c42006-05-03 21:28:47 +0000132 int special = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000133 int unmenu;
134 char_u *map_buf;
135 char_u *arg;
136 char_u *p;
137 int i;
Bram Moolenaar241a8aa2005-12-06 20:04:44 +0000138#if defined(FEAT_GUI) && !defined(FEAT_GUI_GTK)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000139 int old_menu_height;
Bram Moolenaar4f974752019-02-17 17:44:42 +0100140# if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_MSWIN)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000141 int old_toolbar_height;
142# endif
143#endif
144 int pri_tab[MENUDEPTH + 1];
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100145 int enable = MAYBE; // TRUE for "menu enable", FALSE for "menu
146 // disable
Bram Moolenaar071d4272004-06-13 20:20:40 +0000147#ifdef FEAT_TOOLBAR
148 char_u *icon = NULL;
149#endif
150 vimmenu_T menuarg;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200151 vimmenu_T **root_menu_ptr;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000152
153 modes = get_menu_cmd_modes(eap->cmd, eap->forceit, &noremap, &unmenu);
154 arg = eap->arg;
155
156 for (;;)
157 {
158 if (STRNCMP(arg, "<script>", 8) == 0)
159 {
160 noremap = REMAP_SCRIPT;
161 arg = skipwhite(arg + 8);
162 continue;
163 }
164 if (STRNCMP(arg, "<silent>", 8) == 0)
165 {
166 silent = TRUE;
167 arg = skipwhite(arg + 8);
168 continue;
169 }
Bram Moolenaar8b2d9c42006-05-03 21:28:47 +0000170 if (STRNCMP(arg, "<special>", 9) == 0)
171 {
172 special = TRUE;
173 arg = skipwhite(arg + 9);
174 continue;
175 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000176 break;
177 }
178
179
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100180 // Locate an optional "icon=filename" argument.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000181 if (STRNCMP(arg, "icon=", 5) == 0)
182 {
183 arg += 5;
184#ifdef FEAT_TOOLBAR
185 icon = arg;
186#endif
187 while (*arg != NUL && *arg != ' ')
188 {
189 if (*arg == '\\')
Bram Moolenaar8c8de832008-06-24 22:58:06 +0000190 STRMOVE(arg, arg + 1);
Bram Moolenaar91acfff2017-03-12 19:22:36 +0100191 MB_PTR_ADV(arg);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000192 }
193 if (*arg != NUL)
194 {
195 *arg++ = NUL;
196 arg = skipwhite(arg);
197 }
198 }
199
200 /*
201 * Fill in the priority table.
202 */
203 for (p = arg; *p; ++p)
204 if (!VIM_ISDIGIT(*p) && *p != '.')
205 break;
Bram Moolenaar1c465442017-03-12 20:10:05 +0100206 if (VIM_ISWHITE(*p))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000207 {
Bram Moolenaar1c465442017-03-12 20:10:05 +0100208 for (i = 0; i < MENUDEPTH && !VIM_ISWHITE(*arg); ++i)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000209 {
210 pri_tab[i] = getdigits(&arg);
211 if (pri_tab[i] == 0)
212 pri_tab[i] = 500;
213 if (*arg == '.')
214 ++arg;
215 }
216 arg = skipwhite(arg);
217 }
218 else if (eap->addr_count && eap->line2 != 0)
219 {
220 pri_tab[0] = eap->line2;
221 i = 1;
222 }
223 else
224 i = 0;
225 while (i < MENUDEPTH)
226 pri_tab[i++] = 500;
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100227 pri_tab[MENUDEPTH] = -1; // mark end of the table
Bram Moolenaar071d4272004-06-13 20:20:40 +0000228
229 /*
230 * Check for "disable" or "enable" argument.
231 */
Bram Moolenaar1c465442017-03-12 20:10:05 +0100232 if (STRNCMP(arg, "enable", 6) == 0 && VIM_ISWHITE(arg[6]))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000233 {
234 enable = TRUE;
235 arg = skipwhite(arg + 6);
236 }
Bram Moolenaar1c465442017-03-12 20:10:05 +0100237 else if (STRNCMP(arg, "disable", 7) == 0 && VIM_ISWHITE(arg[7]))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000238 {
239 enable = FALSE;
240 arg = skipwhite(arg + 7);
241 }
242
243 /*
244 * If there is no argument, display all menus.
245 */
246 if (*arg == NUL)
247 {
248 show_menus(arg, modes);
249 return;
250 }
251
252#ifdef FEAT_TOOLBAR
253 /*
254 * Need to get the toolbar icon index before doing the translation.
255 */
256 menuarg.iconidx = -1;
257 menuarg.icon_builtin = FALSE;
258 if (menu_is_toolbar(arg))
259 {
260 menu_path = menu_skip_part(arg);
261 if (*menu_path == '.')
262 {
263 p = menu_skip_part(++menu_path);
264 if (STRNCMP(menu_path, "BuiltIn", 7) == 0)
265 {
266 if (skipdigits(menu_path + 7) == p)
267 {
268 menuarg.iconidx = atoi((char *)menu_path + 7);
Bram Moolenaaraf0167f2009-05-16 15:31:32 +0000269 if (menuarg.iconidx >= (int)TOOLBAR_NAME_COUNT)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000270 menuarg.iconidx = -1;
271 else
272 menuarg.icon_builtin = TRUE;
273 }
274 }
275 else
276 {
Bram Moolenaaraf0167f2009-05-16 15:31:32 +0000277 for (i = 0; i < (int)TOOLBAR_NAME_COUNT; ++i)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000278 if (STRNCMP(toolbar_names[i], menu_path, p - menu_path)
279 == 0)
280 {
281 menuarg.iconidx = i;
282 break;
283 }
284 }
285 }
286 }
287#endif
288
Bram Moolenaar071d4272004-06-13 20:20:40 +0000289 menu_path = arg;
290 if (*menu_path == '.')
291 {
Bram Moolenaar436b5ad2021-12-31 22:49:24 +0000292 semsg(_(e_invalid_argument_str), menu_path);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000293 goto theend;
294 }
295
Bram Moolenaar70b11cd2010-05-14 22:24:40 +0200296 map_to = menu_translate_tab_and_shift(arg);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000297
298 /*
299 * If there is only a menu name, display menus with that name.
300 */
301 if (*map_to == NUL && !unmenu && enable == MAYBE)
302 {
303 show_menus(menu_path, modes);
304 goto theend;
305 }
306 else if (*map_to != NUL && (unmenu || enable != MAYBE))
307 {
Bram Moolenaar74409f62022-01-01 15:58:22 +0000308 semsg(_(e_trailing_characters_str), map_to);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000309 goto theend;
310 }
Bram Moolenaar241a8aa2005-12-06 20:04:44 +0000311#if defined(FEAT_GUI) && !(defined(FEAT_GUI_GTK) || defined(FEAT_GUI_PHOTON))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000312 old_menu_height = gui.menu_height;
Bram Moolenaar4f974752019-02-17 17:44:42 +0100313# if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_MSWIN)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000314 old_toolbar_height = gui.toolbar_height;
315# endif
316#endif
317
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200318 root_menu_ptr = get_root_menu(menu_path);
319 if (root_menu_ptr == &curwin->w_winbar)
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100320 // Assume the window toolbar menu will change.
Bram Moolenaara4d158b2022-08-14 14:17:45 +0100321 redraw_later(UPD_NOT_VALID);
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200322
Bram Moolenaar071d4272004-06-13 20:20:40 +0000323 if (enable != MAYBE)
324 {
325 /*
326 * Change sensitivity of the menu.
327 * For the PopUp menu, remove a menu for each mode separately.
328 * Careful: menu_nable_recurse() changes menu_path.
329 */
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100330 if (STRCMP(menu_path, "*") == 0) // meaning: do all menus
Bram Moolenaar071d4272004-06-13 20:20:40 +0000331 menu_path = (char_u *)"";
332
333 if (menu_is_popup(menu_path))
334 {
335 for (i = 0; i < MENU_INDEX_TIP; ++i)
336 if (modes & (1 << i))
337 {
338 p = popup_mode_name(menu_path, i);
339 if (p != NULL)
340 {
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200341 menu_nable_recurse(*root_menu_ptr, p, MENU_ALL_MODES,
Bram Moolenaar071d4272004-06-13 20:20:40 +0000342 enable);
343 vim_free(p);
344 }
345 }
346 }
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200347 menu_nable_recurse(*root_menu_ptr, menu_path, modes, enable);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000348 }
349 else if (unmenu)
350 {
Bram Moolenaar920d3112022-11-13 21:10:02 +0000351 if (is_menus_locked())
352 goto theend;
353
Bram Moolenaar071d4272004-06-13 20:20:40 +0000354 /*
355 * Delete menu(s).
356 */
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100357 if (STRCMP(menu_path, "*") == 0) // meaning: remove all menus
Bram Moolenaar071d4272004-06-13 20:20:40 +0000358 menu_path = (char_u *)"";
359
360 /*
361 * For the PopUp menu, remove a menu for each mode separately.
362 */
363 if (menu_is_popup(menu_path))
364 {
365 for (i = 0; i < MENU_INDEX_TIP; ++i)
366 if (modes & (1 << i))
367 {
368 p = popup_mode_name(menu_path, i);
369 if (p != NULL)
370 {
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200371 remove_menu(root_menu_ptr, p, MENU_ALL_MODES, TRUE);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000372 vim_free(p);
373 }
374 }
375 }
376
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100377 // Careful: remove_menu() changes menu_path
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200378 remove_menu(root_menu_ptr, menu_path, modes, FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000379 }
380 else
381 {
Bram Moolenaar920d3112022-11-13 21:10:02 +0000382 if (is_menus_locked())
383 goto theend;
384
Bram Moolenaar071d4272004-06-13 20:20:40 +0000385 /*
386 * Add menu(s).
387 * Replace special key codes.
388 */
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100389 if (STRICMP(map_to, "<nop>") == 0) // "<Nop>" means nothing
Bram Moolenaar071d4272004-06-13 20:20:40 +0000390 {
391 map_to = (char_u *)"";
392 map_buf = NULL;
393 }
Bram Moolenaar3fdfa4a2004-10-07 21:02:47 +0000394 else if (modes & MENU_TIP_MODE)
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100395 map_buf = NULL; // Menu tips are plain text.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000396 else
zeertzjq7e0bae02023-08-11 23:15:38 +0200397 map_to = replace_termcodes(map_to, &map_buf, 0,
Bram Moolenaar459fd782019-10-13 16:43:39 +0200398 REPTERM_DO_LT | (special ? REPTERM_SPECIAL : 0), NULL);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000399 menuarg.modes = modes;
400#ifdef FEAT_TOOLBAR
401 menuarg.iconfile = icon;
402#endif
403 menuarg.noremap[0] = noremap;
404 menuarg.silent[0] = silent;
405 add_menu_path(menu_path, &menuarg, pri_tab, map_to
Bram Moolenaar4f974752019-02-17 17:44:42 +0100406#ifdef FEAT_GUI_MSWIN
Bram Moolenaar071d4272004-06-13 20:20:40 +0000407 , TRUE
408#endif
409 );
410
411 /*
412 * For the PopUp menu, add a menu for each mode separately.
413 */
414 if (menu_is_popup(menu_path))
415 {
416 for (i = 0; i < MENU_INDEX_TIP; ++i)
417 if (modes & (1 << i))
418 {
419 p = popup_mode_name(menu_path, i);
420 if (p != NULL)
421 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100422 // Include all modes, to make ":amenu" work
Bram Moolenaar071d4272004-06-13 20:20:40 +0000423 menuarg.modes = modes;
424#ifdef FEAT_TOOLBAR
425 menuarg.iconfile = NULL;
426 menuarg.iconidx = -1;
427 menuarg.icon_builtin = FALSE;
428#endif
429 add_menu_path(p, &menuarg, pri_tab, map_to
Bram Moolenaar4f974752019-02-17 17:44:42 +0100430#ifdef FEAT_GUI_MSWIN
Bram Moolenaar071d4272004-06-13 20:20:40 +0000431 , TRUE
432#endif
433 );
434 vim_free(p);
435 }
436 }
437 }
438
439 vim_free(map_buf);
440 }
441
Bram Moolenaar241a8aa2005-12-06 20:04:44 +0000442#if defined(FEAT_GUI) && !(defined(FEAT_GUI_GTK))
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100443 // If the menubar height changed, resize the window
Bram Moolenaar071d4272004-06-13 20:20:40 +0000444 if (gui.in_use
445 && (gui.menu_height != old_menu_height
Bram Moolenaar4f974752019-02-17 17:44:42 +0100446# if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_MSWIN)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000447 || gui.toolbar_height != old_toolbar_height
448# endif
449 ))
Bram Moolenaar04a9d452006-03-27 21:03:26 +0000450 gui_set_shellsize(FALSE, FALSE, RESIZE_VERT);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000451#endif
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200452 if (root_menu_ptr == &curwin->w_winbar)
453 {
454 int h = winbar_height(curwin);
455
456 if (h != curwin->w_winbar_height)
457 {
458 if (h == 0)
459 ++curwin->w_height;
460 else if (curwin->w_height > 0)
461 --curwin->w_height;
462 curwin->w_winbar_height = h;
463 }
Luuk van Baal5ed39172022-09-13 11:55:10 +0100464 curwin->w_prev_height = curwin->w_height;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200465 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000466
467theend:
Bram Moolenaar071d4272004-06-13 20:20:40 +0000468 ;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000469}
470
471/*
472 * Add the menu with the given name to the menu hierarchy
473 */
474 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100475add_menu_path(
476 char_u *menu_path,
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100477 vimmenu_T *menuarg, // passes modes, iconfile, iconidx,
478 // icon_builtin, silent[0], noremap[0]
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100479 int *pri_tab,
480 char_u *call_data
Bram Moolenaar4f974752019-02-17 17:44:42 +0100481#ifdef FEAT_GUI_MSWIN
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100482 , int addtearoff // may add tearoff item
Bram Moolenaar071d4272004-06-13 20:20:40 +0000483#endif
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100484 )
Bram Moolenaar071d4272004-06-13 20:20:40 +0000485{
486 char_u *path_name;
487 int modes = menuarg->modes;
488 vimmenu_T **menup;
489 vimmenu_T *menu = NULL;
490 vimmenu_T *parent;
491 vimmenu_T **lower_pri;
492 char_u *p;
493 char_u *name;
494 char_u *dname;
495 char_u *next_name;
496 int i;
497 int c;
Bram Moolenaar7871a502010-05-14 21:19:23 +0200498 int d;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000499#ifdef FEAT_GUI
500 int idx;
501 int new_idx;
502#endif
503 int pri_idx = 0;
504 int old_modes = 0;
505 int amenu;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +0200506#ifdef FEAT_MULTI_LANG
507 char_u *en_name;
508 char_u *map_to = NULL;
509#endif
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200510 vimmenu_T **root_menu_ptr;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000511
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100512 // Make a copy so we can stuff around with it, since it could be const
Bram Moolenaar071d4272004-06-13 20:20:40 +0000513 path_name = vim_strsave(menu_path);
514 if (path_name == NULL)
515 return FAIL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200516 root_menu_ptr = get_root_menu(menu_path);
517 menup = root_menu_ptr;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000518 parent = NULL;
519 name = path_name;
520 while (*name)
521 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100522 // Get name of this element in the menu hierarchy, and the simplified
523 // name (without mnemonic and accelerator text).
Bram Moolenaar071d4272004-06-13 20:20:40 +0000524 next_name = menu_name_skip(name);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +0200525#ifdef FEAT_MULTI_LANG
Bram Moolenaar442b4222010-05-24 21:34:22 +0200526 map_to = menutrans_lookup(name, (int)STRLEN(name));
Bram Moolenaar70b11cd2010-05-14 22:24:40 +0200527 if (map_to != NULL)
528 {
529 en_name = name;
530 name = map_to;
531 }
532 else
533 en_name = NULL;
534#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000535 dname = menu_text(name, NULL, NULL);
Bram Moolenaar18a0b122006-08-16 13:55:16 +0000536 if (dname == NULL)
537 goto erret;
538 if (*dname == NUL)
539 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100540 // Only a mnemonic or accelerator is not valid.
Bram Moolenaar677658a2022-01-05 16:09:06 +0000541 emsg(_(e_empty_menu_name));
Bram Moolenaar18a0b122006-08-16 13:55:16 +0000542 goto erret;
543 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000544
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100545 // See if it's already there
Bram Moolenaar071d4272004-06-13 20:20:40 +0000546 lower_pri = menup;
547#ifdef FEAT_GUI
548 idx = 0;
549 new_idx = 0;
550#endif
551 menu = *menup;
552 while (menu != NULL)
553 {
554 if (menu_name_equal(name, menu) || menu_name_equal(dname, menu))
555 {
556 if (*next_name == NUL && menu->children != NULL)
557 {
558 if (!sys_menu)
Bram Moolenaard88be5b2022-01-04 19:57:55 +0000559 emsg(_(e_menu_path_must_not_lead_to_sub_menu));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000560 goto erret;
561 }
562 if (*next_name != NUL && menu->children == NULL
Bram Moolenaar4f974752019-02-17 17:44:42 +0100563#ifdef FEAT_GUI_MSWIN
Bram Moolenaar071d4272004-06-13 20:20:40 +0000564 && addtearoff
565#endif
566 )
567 {
568 if (!sys_menu)
Bram Moolenaareaaac012022-01-02 17:00:40 +0000569 emsg(_(e_part_of_menu_item_path_is_not_sub_menu));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000570 goto erret;
571 }
572 break;
573 }
574 menup = &menu->next;
575
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100576 // Count menus, to find where this one needs to be inserted.
577 // Ignore menus that are not in the menubar (PopUp and Toolbar)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000578 if (parent != NULL || menu_is_menubar(menu->name))
579 {
580#ifdef FEAT_GUI
581 ++idx;
582#endif
583 if (menu->priority <= pri_tab[pri_idx])
584 {
585 lower_pri = menup;
586#ifdef FEAT_GUI
587 new_idx = idx;
588#endif
589 }
590 }
591 menu = menu->next;
592 }
593
594 if (menu == NULL)
595 {
596 if (*next_name == NUL && parent == NULL)
597 {
Bram Moolenaareaaac012022-01-02 17:00:40 +0000598 emsg(_(e_must_not_add_menu_items_directly_to_menu_bar));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000599 goto erret;
600 }
601
602 if (menu_is_separator(dname) && *next_name != NUL)
603 {
Bram Moolenaareaaac012022-01-02 17:00:40 +0000604 emsg(_(e_separator_cannot_be_part_of_menu_path));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000605 goto erret;
606 }
607
Dominique Pelleaf4a61a2021-12-27 17:21:41 +0000608 // Not already there, so let's add it
Bram Moolenaare809a4e2019-07-04 17:35:05 +0200609 menu = ALLOC_CLEAR_ONE(vimmenu_T);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000610 if (menu == NULL)
611 goto erret;
612
613 menu->modes = modes;
614 menu->enabled = MENU_ALL_MODES;
615 menu->name = vim_strsave(name);
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100616 // separate mnemonic and accelerator text from actual menu name
Bram Moolenaar071d4272004-06-13 20:20:40 +0000617 menu->dname = menu_text(name, &menu->mnemonic, &menu->actext);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +0200618#ifdef FEAT_MULTI_LANG
619 if (en_name != NULL)
620 {
621 menu->en_name = vim_strsave(en_name);
622 menu->en_dname = menu_text(en_name, NULL, NULL);
623 }
624 else
625 {
626 menu->en_name = NULL;
627 menu->en_dname = NULL;
628 }
629#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000630 menu->priority = pri_tab[pri_idx];
631 menu->parent = parent;
632#ifdef FEAT_GUI_MOTIF
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100633 menu->sensitive = TRUE; // the default
Bram Moolenaar071d4272004-06-13 20:20:40 +0000634#endif
635#ifdef FEAT_BEVAL_TIP
636 menu->tip = NULL;
637#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000638 /*
639 * Add after menu that has lower priority.
640 */
641 menu->next = *lower_pri;
642 *lower_pri = menu;
643
644 old_modes = 0;
645
646#ifdef FEAT_TOOLBAR
647 menu->iconidx = menuarg->iconidx;
648 menu->icon_builtin = menuarg->icon_builtin;
649 if (*next_name == NUL && menuarg->iconfile != NULL)
650 menu->iconfile = vim_strsave(menuarg->iconfile);
651#endif
Bram Moolenaar4f974752019-02-17 17:44:42 +0100652#if defined(FEAT_GUI_MSWIN) && defined(FEAT_TEAROFF)
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100653 // the tearoff item must be present in the modes of each item.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000654 if (parent != NULL && menu_is_tearoff(parent->children->dname))
655 parent->children->modes |= modes;
656#endif
657 }
658 else
659 {
660 old_modes = menu->modes;
661
662 /*
663 * If this menu option was previously only available in other
664 * modes, then make sure it's available for this one now
665 * Also enable a menu when it's created or changed.
666 */
Bram Moolenaar4f974752019-02-17 17:44:42 +0100667#ifdef FEAT_GUI_MSWIN
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100668 // If adding a tearbar (addtearoff == FALSE) don't update modes
Bram Moolenaar071d4272004-06-13 20:20:40 +0000669 if (addtearoff)
670#endif
671 {
672 menu->modes |= modes;
673 menu->enabled |= modes;
674 }
675 }
676
677#ifdef FEAT_GUI
678 /*
679 * Add the menu item when it's used in one of the modes, but not when
680 * only a tooltip is defined.
681 */
682 if ((old_modes & MENU_ALL_MODES) == 0
683 && (menu->modes & MENU_ALL_MODES) != 0)
684 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100685 if (gui.in_use) // Otherwise it will be added when GUI starts
Bram Moolenaar071d4272004-06-13 20:20:40 +0000686 {
687 if (*next_name == NUL)
688 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100689 // Real menu item, not sub-menu
Bram Moolenaar071d4272004-06-13 20:20:40 +0000690 gui_mch_add_menu_item(menu, new_idx);
691
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100692 // Want to update menus now even if mode not changed
Bram Moolenaar071d4272004-06-13 20:20:40 +0000693 force_menu_update = TRUE;
694 }
695 else
696 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100697 // Sub-menu (not at end of path yet)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000698 gui_mch_add_menu(menu, new_idx);
699 }
700 }
701
K.Takata54119102022-02-03 13:33:03 +0000702# if defined(FEAT_GUI_MSWIN) && defined(FEAT_TEAROFF)
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100703 // When adding a new submenu, may add a tearoff item
Bram Moolenaar071d4272004-06-13 20:20:40 +0000704 if ( addtearoff
705 && *next_name
706 && vim_strchr(p_go, GO_TEAROFF) != NULL
Bram Moolenaar310c32e2019-11-29 23:15:25 +0100707 && menu_is_menubar(name)
708# ifdef VIMDLL
709 && (gui.in_use || gui.starting)
710# endif
711 )
Bram Moolenaar071d4272004-06-13 20:20:40 +0000712 {
713 char_u *tearpath;
714
715 /*
716 * The pointers next_name & path_name refer to a string with
717 * \'s and ^V's stripped out. But menu_path is a "raw"
718 * string, so we must correct for special characters.
719 */
Bram Moolenaar964b3742019-05-24 18:54:09 +0200720 tearpath = alloc(STRLEN(menu_path) + TEAR_LEN + 2);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000721 if (tearpath != NULL)
722 {
723 char_u *s;
724 int idx;
725
726 STRCPY(tearpath, menu_path);
727 idx = (int)(next_name - path_name - 1);
Bram Moolenaar91acfff2017-03-12 19:22:36 +0100728 for (s = tearpath; *s && s < tearpath + idx; MB_PTR_ADV(s))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000729 {
730 if ((*s == '\\' || *s == Ctrl_V) && s[1])
731 {
732 ++idx;
733 ++s;
734 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000735 }
736 tearpath[idx] = NUL;
737 gui_add_tearoff(tearpath, pri_tab, pri_idx);
738 vim_free(tearpath);
739 }
740 }
741# endif
742 }
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100743#endif // FEAT_GUI
Bram Moolenaar071d4272004-06-13 20:20:40 +0000744
745 menup = &menu->children;
746 parent = menu;
747 name = next_name;
Bram Moolenaard23a8232018-02-10 18:45:26 +0100748 VIM_CLEAR(dname);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000749 if (pri_tab[pri_idx + 1] != -1)
750 ++pri_idx;
751 }
752 vim_free(path_name);
753
754 /*
755 * Only add system menu items which have not been defined yet.
756 * First check if this was an ":amenu".
757 */
758 amenu = ((modes & (MENU_NORMAL_MODE | MENU_INSERT_MODE)) ==
759 (MENU_NORMAL_MODE | MENU_INSERT_MODE));
760 if (sys_menu)
761 modes &= ~old_modes;
762
763 if (menu != NULL && modes)
764 {
765#ifdef FEAT_GUI
766 menu->cb = gui_menu_cb;
767#endif
768 p = (call_data == NULL) ? NULL : vim_strsave(call_data);
769
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100770 // loop over all modes, may add more than one
Bram Moolenaar071d4272004-06-13 20:20:40 +0000771 for (i = 0; i < MENU_MODES; ++i)
772 {
773 if (modes & (1 << i))
774 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100775 // free any old menu
Bram Moolenaar071d4272004-06-13 20:20:40 +0000776 free_menu_string(menu, i);
777
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100778 // For "amenu", may insert an extra character.
779 // Don't do this if adding a tearbar (addtearoff == FALSE).
780 // Don't do this for "<Nop>".
Bram Moolenaar071d4272004-06-13 20:20:40 +0000781 c = 0;
Bram Moolenaar7871a502010-05-14 21:19:23 +0200782 d = 0;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000783 if (amenu && call_data != NULL && *call_data != NUL
Bram Moolenaar4f974752019-02-17 17:44:42 +0100784#ifdef FEAT_GUI_MSWIN
Bram Moolenaar071d4272004-06-13 20:20:40 +0000785 && addtearoff
786#endif
787 )
788 {
789 switch (1 << i)
790 {
791 case MENU_VISUAL_MODE:
Bram Moolenaarb3656ed2006-03-20 21:59:49 +0000792 case MENU_SELECT_MODE:
Bram Moolenaar071d4272004-06-13 20:20:40 +0000793 case MENU_OP_PENDING_MODE:
794 case MENU_CMDLINE_MODE:
795 c = Ctrl_C;
796 break;
797 case MENU_INSERT_MODE:
Bram Moolenaar7871a502010-05-14 21:19:23 +0200798 c = Ctrl_BSL;
799 d = Ctrl_O;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000800 break;
801 }
802 }
803
Bram Moolenaar7871a502010-05-14 21:19:23 +0200804 if (c != 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000805 {
Bram Moolenaar964b3742019-05-24 18:54:09 +0200806 menu->strings[i] = alloc(STRLEN(call_data) + 5);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000807 if (menu->strings[i] != NULL)
808 {
809 menu->strings[i][0] = c;
Bram Moolenaar7871a502010-05-14 21:19:23 +0200810 if (d == 0)
811 STRCPY(menu->strings[i] + 1, call_data);
812 else
813 {
814 menu->strings[i][1] = d;
815 STRCPY(menu->strings[i] + 2, call_data);
816 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000817 if (c == Ctrl_C)
818 {
Bram Moolenaara93fa7e2006-04-17 22:14:47 +0000819 int len = (int)STRLEN(menu->strings[i]);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000820
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100821 // Append CTRL-\ CTRL-G to obey 'insertmode'.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000822 menu->strings[i][len] = Ctrl_BSL;
823 menu->strings[i][len + 1] = Ctrl_G;
824 menu->strings[i][len + 2] = NUL;
825 }
826 }
827 }
828 else
829 menu->strings[i] = p;
830 menu->noremap[i] = menuarg->noremap[0];
831 menu->silent[i] = menuarg->silent[0];
832 }
833 }
Bram Moolenaar4f974752019-02-17 17:44:42 +0100834#if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_MSWIN) \
Bram Moolenaarc3719bd2017-11-18 22:13:31 +0100835 && (defined(FEAT_BEVAL_GUI) || defined(FEAT_GUI_GTK))
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100836 // Need to update the menu tip.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000837 if (modes & MENU_TIP_MODE)
838 gui_mch_menu_set_tip(menu);
839#endif
840 }
841 return OK;
842
843erret:
844 vim_free(path_name);
845 vim_free(dname);
Bram Moolenaar18a0b122006-08-16 13:55:16 +0000846
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100847 // Delete any empty submenu we added before discovering the error. Repeat
848 // for higher levels.
Bram Moolenaar18a0b122006-08-16 13:55:16 +0000849 while (parent != NULL && parent->children == NULL)
850 {
851 if (parent->parent == NULL)
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200852 menup = root_menu_ptr;
Bram Moolenaar18a0b122006-08-16 13:55:16 +0000853 else
854 menup = &parent->parent->children;
855 for ( ; *menup != NULL && *menup != parent; menup = &((*menup)->next))
856 ;
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100857 if (*menup == NULL) // safety check
Bram Moolenaar18a0b122006-08-16 13:55:16 +0000858 break;
859 parent = parent->parent;
860 free_menu(menup);
861 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000862 return FAIL;
863}
864
865/*
866 * Set the (sub)menu with the given name to enabled or disabled.
867 * Called recursively.
868 */
869 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100870menu_nable_recurse(
871 vimmenu_T *menu,
872 char_u *name,
873 int modes,
874 int enable)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000875{
876 char_u *p;
877
878 if (menu == NULL)
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100879 return OK; // Got to bottom of hierarchy
Bram Moolenaar071d4272004-06-13 20:20:40 +0000880
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100881 // Get name of this element in the menu hierarchy
Bram Moolenaar071d4272004-06-13 20:20:40 +0000882 p = menu_name_skip(name);
883
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100884 // Find the menu
Bram Moolenaar071d4272004-06-13 20:20:40 +0000885 while (menu != NULL)
886 {
887 if (*name == NUL || *name == '*' || menu_name_equal(name, menu))
888 {
889 if (*p != NUL)
890 {
891 if (menu->children == NULL)
892 {
Bram Moolenaareaaac012022-01-02 17:00:40 +0000893 emsg(_(e_part_of_menu_item_path_is_not_sub_menu));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000894 return FAIL;
895 }
896 if (menu_nable_recurse(menu->children, p, modes, enable)
897 == FAIL)
898 return FAIL;
899 }
900 else
901 if (enable)
902 menu->enabled |= modes;
903 else
904 menu->enabled &= ~modes;
905
906 /*
907 * When name is empty, we are doing all menu items for the given
908 * modes, so keep looping, otherwise we are just doing the named
909 * menu item (which has been found) so break here.
910 */
911 if (*name != NUL && *name != '*')
912 break;
913 }
914 menu = menu->next;
915 }
916 if (*name != NUL && *name != '*' && menu == NULL)
917 {
Bram Moolenaareaaac012022-01-02 17:00:40 +0000918 semsg(_(e_no_menu_str), name);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000919 return FAIL;
920 }
921
922#ifdef FEAT_GUI
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100923 // Want to update menus now even if mode not changed
Bram Moolenaar071d4272004-06-13 20:20:40 +0000924 force_menu_update = TRUE;
925#endif
926
927 return OK;
928}
929
930/*
931 * Remove the (sub)menu with the given name from the menu hierarchy
932 * Called recursively.
933 */
934 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100935remove_menu(
936 vimmenu_T **menup,
937 char_u *name,
938 int modes,
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100939 int silent) // don't give error messages
Bram Moolenaar071d4272004-06-13 20:20:40 +0000940{
941 vimmenu_T *menu;
942 vimmenu_T *child;
943 char_u *p;
944
945 if (*menup == NULL)
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100946 return OK; // Got to bottom of hierarchy
Bram Moolenaar071d4272004-06-13 20:20:40 +0000947
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100948 // Get name of this element in the menu hierarchy
Bram Moolenaar071d4272004-06-13 20:20:40 +0000949 p = menu_name_skip(name);
950
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100951 // Find the menu
Bram Moolenaar071d4272004-06-13 20:20:40 +0000952 while ((menu = *menup) != NULL)
953 {
954 if (*name == NUL || menu_name_equal(name, menu))
955 {
956 if (*p != NUL && menu->children == NULL)
957 {
958 if (!silent)
Bram Moolenaareaaac012022-01-02 17:00:40 +0000959 emsg(_(e_part_of_menu_item_path_is_not_sub_menu));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000960 return FAIL;
961 }
962 if ((menu->modes & modes) != 0x0)
963 {
Bram Moolenaar4f974752019-02-17 17:44:42 +0100964#if defined(FEAT_GUI_MSWIN) & defined(FEAT_TEAROFF)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000965 /*
966 * If we are removing all entries for this menu,MENU_ALL_MODES,
967 * Then kill any tearoff before we start
968 */
969 if (*p == NUL && modes == MENU_ALL_MODES)
970 {
971 if (IsWindow(menu->tearoff_handle))
972 DestroyWindow(menu->tearoff_handle);
973 }
974#endif
975 if (remove_menu(&menu->children, p, modes, silent) == FAIL)
976 return FAIL;
977 }
978 else if (*name != NUL)
979 {
980 if (!silent)
Bram Moolenaar3a846e62022-01-01 16:21:00 +0000981 emsg(_(e_menu_only_exists_in_another_mode));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000982 return FAIL;
983 }
984
985 /*
986 * When name is empty, we are removing all menu items for the given
987 * modes, so keep looping, otherwise we are just removing the named
988 * menu item (which has been found) so break here.
989 */
990 if (*name != NUL)
991 break;
992
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100993 // Remove the menu item for the given mode[s]. If the menu item
994 // is no longer valid in ANY mode, delete it
Bram Moolenaar071d4272004-06-13 20:20:40 +0000995 menu->modes &= ~modes;
996 if (modes & MENU_TIP_MODE)
997 free_menu_string(menu, MENU_INDEX_TIP);
998 if ((menu->modes & MENU_ALL_MODES) == 0)
999 free_menu(menup);
1000 else
1001 menup = &menu->next;
1002 }
1003 else
1004 menup = &menu->next;
1005 }
1006 if (*name != NUL)
1007 {
1008 if (menu == NULL)
1009 {
1010 if (!silent)
Bram Moolenaareaaac012022-01-02 17:00:40 +00001011 semsg(_(e_no_menu_str), name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001012 return FAIL;
1013 }
1014
1015
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001016 // Recalculate modes for menu based on the new updated children
Bram Moolenaar071d4272004-06-13 20:20:40 +00001017 menu->modes &= ~modes;
Bram Moolenaar4f974752019-02-17 17:44:42 +01001018#if defined(FEAT_GUI_MSWIN) & defined(FEAT_TEAROFF)
Dominique Pelleaf4a61a2021-12-27 17:21:41 +00001019 if ((s_tearoffs) && (menu->children != NULL)) // there's a tear bar.
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001020 child = menu->children->next; // don't count tearoff bar
Bram Moolenaar071d4272004-06-13 20:20:40 +00001021 else
1022#endif
1023 child = menu->children;
1024 for ( ; child != NULL; child = child->next)
1025 menu->modes |= child->modes;
1026 if (modes & MENU_TIP_MODE)
1027 {
1028 free_menu_string(menu, MENU_INDEX_TIP);
Bram Moolenaar4f974752019-02-17 17:44:42 +01001029#if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_MSWIN) \
Bram Moolenaarc3719bd2017-11-18 22:13:31 +01001030 && (defined(FEAT_BEVAL_GUI) || defined(FEAT_GUI_GTK))
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001031 // Need to update the menu tip.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001032 if (gui.in_use)
1033 gui_mch_menu_set_tip(menu);
1034#endif
1035 }
1036 if ((menu->modes & MENU_ALL_MODES) == 0)
1037 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001038 // The menu item is no longer valid in ANY mode, so delete it
Bram Moolenaar4f974752019-02-17 17:44:42 +01001039#if defined(FEAT_GUI_MSWIN) & defined(FEAT_TEAROFF)
Dominique Pelleaf4a61a2021-12-27 17:21:41 +00001040 if (s_tearoffs && menu->children != NULL) // there's a tear bar.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001041 free_menu(&menu->children);
1042#endif
1043 *menup = menu;
1044 free_menu(menup);
1045 }
1046 }
1047
1048 return OK;
1049}
1050
1051/*
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001052 * Remove the WinBar menu from window "wp".
1053 */
1054 void
1055remove_winbar(win_T *wp)
1056{
1057 remove_menu(&wp->w_winbar, (char_u *)"", MENU_ALL_MODES, TRUE);
1058 vim_free(wp->w_winbar_items);
1059}
1060
1061/*
Bram Moolenaar071d4272004-06-13 20:20:40 +00001062 * Free the given menu structure and remove it from the linked list.
1063 */
1064 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001065free_menu(vimmenu_T **menup)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001066{
1067 int i;
1068 vimmenu_T *menu;
1069
1070 menu = *menup;
1071
1072#ifdef FEAT_GUI
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001073 // Free machine specific menu structures (only when already created)
1074 // Also may rebuild a tearoff'ed menu
Bram Moolenaar071d4272004-06-13 20:20:40 +00001075 if (gui.in_use)
1076 gui_mch_destroy_menu(menu);
1077#endif
1078
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001079 // Don't change *menup until after calling gui_mch_destroy_menu(). The
1080 // MacOS code needs the original structure to properly delete the menu.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001081 *menup = menu->next;
1082 vim_free(menu->name);
1083 vim_free(menu->dname);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001084#ifdef FEAT_MULTI_LANG
1085 vim_free(menu->en_name);
1086 vim_free(menu->en_dname);
1087#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001088 vim_free(menu->actext);
1089#ifdef FEAT_TOOLBAR
1090 vim_free(menu->iconfile);
Bram Moolenaarbee0c5b2005-02-07 22:03:36 +00001091# ifdef FEAT_GUI_MOTIF
1092 vim_free(menu->xpm_fname);
1093# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001094#endif
1095 for (i = 0; i < MENU_MODES; i++)
1096 free_menu_string(menu, i);
1097 vim_free(menu);
1098
1099#ifdef FEAT_GUI
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001100 // Want to update menus now even if mode not changed
Bram Moolenaar071d4272004-06-13 20:20:40 +00001101 force_menu_update = TRUE;
1102#endif
1103}
1104
1105/*
1106 * Free the menu->string with the given index.
1107 */
1108 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001109free_menu_string(vimmenu_T *menu, int idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001110{
1111 int count = 0;
1112 int i;
1113
1114 for (i = 0; i < MENU_MODES; i++)
1115 if (menu->strings[i] == menu->strings[idx])
1116 count++;
1117 if (count == 1)
1118 vim_free(menu->strings[idx]);
1119 menu->strings[idx] = NULL;
1120}
1121
1122/*
1123 * Show the mapping associated with a menu item or hierarchy in a sub-menu.
1124 */
1125 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001126show_menus(char_u *path_name, int modes)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001127{
1128 char_u *p;
1129 char_u *name;
1130 vimmenu_T *menu;
1131 vimmenu_T *parent = NULL;
1132
Bram Moolenaar071d4272004-06-13 20:20:40 +00001133 name = path_name = vim_strsave(path_name);
1134 if (path_name == NULL)
1135 return FAIL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001136 menu = *get_root_menu(path_name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001137
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001138 // First, find the (sub)menu with the given name
Bram Moolenaar071d4272004-06-13 20:20:40 +00001139 while (*name)
1140 {
1141 p = menu_name_skip(name);
1142 while (menu != NULL)
1143 {
1144 if (menu_name_equal(name, menu))
1145 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001146 // Found menu
Bram Moolenaar071d4272004-06-13 20:20:40 +00001147 if (*p != NUL && menu->children == NULL)
1148 {
Bram Moolenaareaaac012022-01-02 17:00:40 +00001149 emsg(_(e_part_of_menu_item_path_is_not_sub_menu));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001150 vim_free(path_name);
1151 return FAIL;
1152 }
1153 else if ((menu->modes & modes) == 0x0)
1154 {
Bram Moolenaar3a846e62022-01-01 16:21:00 +00001155 emsg(_(e_menu_only_exists_in_another_mode));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001156 vim_free(path_name);
1157 return FAIL;
1158 }
1159 break;
1160 }
1161 menu = menu->next;
1162 }
1163 if (menu == NULL)
1164 {
Bram Moolenaareaaac012022-01-02 17:00:40 +00001165 semsg(_(e_no_menu_str), name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001166 vim_free(path_name);
1167 return FAIL;
1168 }
1169 name = p;
1170 parent = menu;
1171 menu = menu->children;
1172 }
Bram Moolenaaracbd4422008-08-17 21:44:45 +00001173 vim_free(path_name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001174
Bram Moolenaar920d3112022-11-13 21:10:02 +00001175 // make sure the list of menus doesn't change while listing them
1176 ++menus_locked;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001177
Bram Moolenaar920d3112022-11-13 21:10:02 +00001178 // list the matching menu mappings
1179 msg_puts_title(_("\n--- Menus ---"));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001180 show_menus_recursive(parent, modes, 0);
Bram Moolenaar920d3112022-11-13 21:10:02 +00001181
1182 --menus_locked;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001183 return OK;
1184}
1185
1186/*
1187 * Recursively show the mappings associated with the menus under the given one
1188 */
1189 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001190show_menus_recursive(vimmenu_T *menu, int modes, int depth)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001191{
1192 int i;
1193 int bit;
1194
1195 if (menu != NULL && (menu->modes & modes) == 0x0)
1196 return;
1197
1198 if (menu != NULL)
1199 {
1200 msg_putchar('\n');
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001201 if (got_int) // "q" hit for "--more--"
Bram Moolenaar071d4272004-06-13 20:20:40 +00001202 return;
1203 for (i = 0; i < depth; i++)
Bram Moolenaar32526b32019-01-19 17:43:09 +01001204 msg_puts(" ");
Bram Moolenaar071d4272004-06-13 20:20:40 +00001205 if (menu->priority)
1206 {
1207 msg_outnum((long)menu->priority);
Bram Moolenaar32526b32019-01-19 17:43:09 +01001208 msg_puts(" ");
Bram Moolenaar071d4272004-06-13 20:20:40 +00001209 }
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001210 // Same highlighting as for directories!?
Bram Moolenaar8820b482017-03-16 17:23:31 +01001211 msg_outtrans_attr(menu->name, HL_ATTR(HLF_D));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001212 }
1213
1214 if (menu != NULL && menu->children == NULL)
1215 {
1216 for (bit = 0; bit < MENU_MODES; bit++)
1217 if ((menu->modes & modes & (1 << bit)) != 0)
1218 {
1219 msg_putchar('\n');
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001220 if (got_int) // "q" hit for "--more--"
Bram Moolenaar071d4272004-06-13 20:20:40 +00001221 return;
1222 for (i = 0; i < depth + 2; i++)
Bram Moolenaar32526b32019-01-19 17:43:09 +01001223 msg_puts(" ");
1224 msg_puts(menu_mode_chars[bit]);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001225 if (menu->noremap[bit] == REMAP_NONE)
1226 msg_putchar('*');
1227 else if (menu->noremap[bit] == REMAP_SCRIPT)
1228 msg_putchar('&');
1229 else
1230 msg_putchar(' ');
1231 if (menu->silent[bit])
1232 msg_putchar('s');
1233 else
1234 msg_putchar(' ');
1235 if ((menu->modes & menu->enabled & (1 << bit)) == 0)
1236 msg_putchar('-');
1237 else
1238 msg_putchar(' ');
Bram Moolenaar32526b32019-01-19 17:43:09 +01001239 msg_puts(" ");
Bram Moolenaar071d4272004-06-13 20:20:40 +00001240 if (*menu->strings[bit] == NUL)
Bram Moolenaar32526b32019-01-19 17:43:09 +01001241 msg_puts_attr("<Nop>", HL_ATTR(HLF_8));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001242 else
Bram Moolenaar725310d2019-04-24 23:08:23 +02001243 msg_outtrans_special(menu->strings[bit], FALSE, 0);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001244 }
1245 }
1246 else
1247 {
1248 if (menu == NULL)
1249 {
1250 menu = root_menu;
1251 depth--;
1252 }
1253 else
1254 menu = menu->children;
1255
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001256 // recursively show all children. Skip PopUp[nvoci].
Bram Moolenaar071d4272004-06-13 20:20:40 +00001257 for (; menu != NULL && !got_int; menu = menu->next)
1258 if (!menu_is_hidden(menu->dname))
1259 show_menus_recursive(menu, modes, depth + 1);
1260 }
1261}
1262
Bram Moolenaar071d4272004-06-13 20:20:40 +00001263/*
1264 * Used when expanding menu names.
1265 */
1266static vimmenu_T *expand_menu = NULL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001267static vimmenu_T *expand_menu_alt = NULL;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001268static int expand_modes = 0x0;
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001269static int expand_emenu; // TRUE for ":emenu" command
Bram Moolenaar071d4272004-06-13 20:20:40 +00001270
1271/*
1272 * Work out what to complete when doing command line completion of menu names.
1273 */
1274 char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001275set_context_in_menu_cmd(
1276 expand_T *xp,
1277 char_u *cmd,
1278 char_u *arg,
1279 int forceit)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001280{
1281 char_u *after_dot;
1282 char_u *p;
1283 char_u *path_name = NULL;
1284 char_u *name;
1285 int unmenu;
1286 vimmenu_T *menu;
1287 int expand_menus;
1288
1289 xp->xp_context = EXPAND_UNSUCCESSFUL;
1290
1291
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001292 // Check for priority numbers, enable and disable
Bram Moolenaar071d4272004-06-13 20:20:40 +00001293 for (p = arg; *p; ++p)
1294 if (!VIM_ISDIGIT(*p) && *p != '.')
1295 break;
1296
Bram Moolenaar1c465442017-03-12 20:10:05 +01001297 if (!VIM_ISWHITE(*p))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001298 {
1299 if (STRNCMP(arg, "enable", 6) == 0
Bram Moolenaar1c465442017-03-12 20:10:05 +01001300 && (arg[6] == NUL || VIM_ISWHITE(arg[6])))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001301 p = arg + 6;
1302 else if (STRNCMP(arg, "disable", 7) == 0
Bram Moolenaar1c465442017-03-12 20:10:05 +01001303 && (arg[7] == NUL || VIM_ISWHITE(arg[7])))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001304 p = arg + 7;
1305 else
1306 p = arg;
1307 }
1308
Bram Moolenaar1c465442017-03-12 20:10:05 +01001309 while (*p != NUL && VIM_ISWHITE(*p))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001310 ++p;
1311
1312 arg = after_dot = p;
1313
Bram Moolenaar1c465442017-03-12 20:10:05 +01001314 for (; *p && !VIM_ISWHITE(*p); ++p)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001315 {
1316 if ((*p == '\\' || *p == Ctrl_V) && p[1] != NUL)
1317 p++;
1318 else if (*p == '.')
1319 after_dot = p + 1;
1320 }
1321
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001322 // ":tearoff" and ":popup" only use menus, not entries
Bram Moolenaar071d4272004-06-13 20:20:40 +00001323 expand_menus = !((*cmd == 't' && cmd[1] == 'e') || *cmd == 'p');
1324 expand_emenu = (*cmd == 'e');
Bram Moolenaar1c465442017-03-12 20:10:05 +01001325 if (expand_menus && VIM_ISWHITE(*p))
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001326 return NULL; // TODO: check for next command?
1327 if (*p == NUL) // Complete the menu name
Bram Moolenaar071d4272004-06-13 20:20:40 +00001328 {
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001329 int try_alt_menu = TRUE;
1330
Bram Moolenaar071d4272004-06-13 20:20:40 +00001331 /*
1332 * With :unmenu, you only want to match menus for the appropriate mode.
1333 * With :menu though you might want to add a menu with the same name as
1334 * one in another mode, so match menus from other modes too.
1335 */
1336 expand_modes = get_menu_cmd_modes(cmd, forceit, NULL, &unmenu);
1337 if (!unmenu)
1338 expand_modes = MENU_ALL_MODES;
1339
1340 menu = root_menu;
1341 if (after_dot != arg)
1342 {
Bram Moolenaar964b3742019-05-24 18:54:09 +02001343 path_name = alloc(after_dot - arg);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001344 if (path_name == NULL)
1345 return NULL;
Bram Moolenaarce0842a2005-07-18 21:58:11 +00001346 vim_strncpy(path_name, arg, after_dot - arg - 1);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001347 }
1348 name = path_name;
1349 while (name != NULL && *name)
1350 {
1351 p = menu_name_skip(name);
1352 while (menu != NULL)
1353 {
1354 if (menu_name_equal(name, menu))
1355 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001356 // Found menu
Bram Moolenaar071d4272004-06-13 20:20:40 +00001357 if ((*p != NUL && menu->children == NULL)
1358 || ((menu->modes & expand_modes) == 0x0))
1359 {
1360 /*
1361 * Menu path continues, but we have reached a leaf.
1362 * Or menu exists only in another mode.
1363 */
1364 vim_free(path_name);
1365 return NULL;
1366 }
1367 break;
1368 }
1369 menu = menu->next;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001370 if (menu == NULL && try_alt_menu)
1371 {
1372 menu = curwin->w_winbar;
1373 try_alt_menu = FALSE;
1374 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001375 }
1376 if (menu == NULL)
1377 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001378 // No menu found with the name we were looking for
Bram Moolenaar071d4272004-06-13 20:20:40 +00001379 vim_free(path_name);
1380 return NULL;
1381 }
1382 name = p;
1383 menu = menu->children;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001384 try_alt_menu = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001385 }
Bram Moolenaareb3593b2006-04-22 22:33:57 +00001386 vim_free(path_name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001387
1388 xp->xp_context = expand_menus ? EXPAND_MENUNAMES : EXPAND_MENUS;
1389 xp->xp_pattern = after_dot;
1390 expand_menu = menu;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001391 if (expand_menu == root_menu)
1392 expand_menu_alt = curwin->w_winbar;
1393 else
1394 expand_menu_alt = NULL;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001395 }
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001396 else // We're in the mapping part
Bram Moolenaar071d4272004-06-13 20:20:40 +00001397 xp->xp_context = EXPAND_NOTHING;
1398 return NULL;
1399}
1400
1401/*
1402 * Function given to ExpandGeneric() to obtain the list of (sub)menus (not
1403 * entries).
1404 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001405 char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001406get_menu_name(expand_T *xp UNUSED, int idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001407{
1408 static vimmenu_T *menu = NULL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001409 static int did_alt_menu = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001410 char_u *str;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001411#ifdef FEAT_MULTI_LANG
1412 static int should_advance = FALSE;
1413#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001414
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001415 if (idx == 0) // first call: start at first item
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001416 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001417 menu = expand_menu;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001418 did_alt_menu = FALSE;
Bram Moolenaar41375642010-05-16 12:49:27 +02001419#ifdef FEAT_MULTI_LANG
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001420 should_advance = FALSE;
Bram Moolenaar41375642010-05-16 12:49:27 +02001421#endif
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001422 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001423
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001424 // Skip PopUp[nvoci].
Bram Moolenaar071d4272004-06-13 20:20:40 +00001425 while (menu != NULL && (menu_is_hidden(menu->dname)
1426 || menu_is_separator(menu->dname)
1427 || menu_is_tearoff(menu->dname)
1428 || menu->children == NULL))
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001429 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001430 menu = menu->next;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001431 if (menu == NULL && !did_alt_menu)
1432 {
1433 menu = expand_menu_alt;
1434 did_alt_menu = TRUE;
1435 }
1436 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001437
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001438 if (menu == NULL) // at end of linked list
Bram Moolenaar071d4272004-06-13 20:20:40 +00001439 return NULL;
1440
1441 if (menu->modes & expand_modes)
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001442#ifdef FEAT_MULTI_LANG
1443 if (should_advance)
1444 str = menu->en_dname;
1445 else
1446 {
1447#endif
1448 str = menu->dname;
1449#ifdef FEAT_MULTI_LANG
1450 if (menu->en_dname == NULL)
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001451 should_advance = TRUE;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001452 }
1453#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001454 else
1455 str = (char_u *)"";
1456
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001457#ifdef FEAT_MULTI_LANG
1458 if (should_advance)
1459#endif
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001460 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001461 // Advance to next menu entry.
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001462 menu = menu->next;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001463 if (menu == NULL && !did_alt_menu)
1464 {
1465 menu = expand_menu_alt;
1466 did_alt_menu = TRUE;
1467 }
1468 }
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001469
1470#ifdef FEAT_MULTI_LANG
1471 should_advance = !should_advance;
1472#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001473
1474 return str;
1475}
1476
1477/*
1478 * Function given to ExpandGeneric() to obtain the list of menus and menu
1479 * entries.
1480 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001481 char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001482get_menu_names(expand_T *xp UNUSED, int idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001483{
1484 static vimmenu_T *menu = NULL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001485 static int did_alt_menu = FALSE;
Bram Moolenaaref9d6aa2011-04-11 16:56:35 +02001486#define TBUFFER_LEN 256
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001487 static char_u tbuffer[TBUFFER_LEN]; //hack
Bram Moolenaar071d4272004-06-13 20:20:40 +00001488 char_u *str;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001489#ifdef FEAT_MULTI_LANG
1490 static int should_advance = FALSE;
1491#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001492
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001493 if (idx == 0) // first call: start at first item
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001494 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001495 menu = expand_menu;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001496 did_alt_menu = FALSE;
Bram Moolenaar41375642010-05-16 12:49:27 +02001497#ifdef FEAT_MULTI_LANG
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001498 should_advance = FALSE;
Bram Moolenaar41375642010-05-16 12:49:27 +02001499#endif
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001500 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001501
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001502 // Skip Browse-style entries, popup menus and separators.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001503 while (menu != NULL
1504 && ( menu_is_hidden(menu->dname)
1505 || (expand_emenu && menu_is_separator(menu->dname))
1506 || menu_is_tearoff(menu->dname)
1507#ifndef FEAT_BROWSE
1508 || menu->dname[STRLEN(menu->dname) - 1] == '.'
1509#endif
1510 ))
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001511 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001512 menu = menu->next;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001513 if (menu == NULL && !did_alt_menu)
1514 {
1515 menu = expand_menu_alt;
1516 did_alt_menu = TRUE;
1517 }
1518 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001519
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001520 if (menu == NULL) // at end of linked list
Bram Moolenaar071d4272004-06-13 20:20:40 +00001521 return NULL;
1522
1523 if (menu->modes & expand_modes)
1524 {
1525 if (menu->children != NULL)
1526 {
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001527#ifdef FEAT_MULTI_LANG
1528 if (should_advance)
Bram Moolenaaref9d6aa2011-04-11 16:56:35 +02001529 vim_strncpy(tbuffer, menu->en_dname, TBUFFER_LEN - 2);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001530 else
1531 {
1532#endif
Bram Moolenaaref9d6aa2011-04-11 16:56:35 +02001533 vim_strncpy(tbuffer, menu->dname, TBUFFER_LEN - 2);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001534#ifdef FEAT_MULTI_LANG
1535 if (menu->en_dname == NULL)
1536 should_advance = TRUE;
1537 }
1538#endif
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001539 // hack on menu separators: use a 'magic' char for the separator
1540 // so that '.' in names gets escaped properly
Bram Moolenaar071d4272004-06-13 20:20:40 +00001541 STRCAT(tbuffer, "\001");
1542 str = tbuffer;
1543 }
1544 else
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001545#ifdef FEAT_MULTI_LANG
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001546 {
1547 if (should_advance)
1548 str = menu->en_dname;
1549 else
1550 {
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001551#endif
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001552 str = menu->dname;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001553#ifdef FEAT_MULTI_LANG
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001554 if (menu->en_dname == NULL)
1555 should_advance = TRUE;
1556 }
1557 }
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001558#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001559 }
1560 else
1561 str = (char_u *)"";
1562
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001563#ifdef FEAT_MULTI_LANG
1564 if (should_advance)
1565#endif
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001566 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001567 // Advance to next menu entry.
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001568 menu = menu->next;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001569 if (menu == NULL && !did_alt_menu)
1570 {
1571 menu = expand_menu_alt;
1572 did_alt_menu = TRUE;
1573 }
1574 }
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001575
1576#ifdef FEAT_MULTI_LANG
1577 should_advance = !should_advance;
1578#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001579
1580 return str;
1581}
Bram Moolenaar071d4272004-06-13 20:20:40 +00001582
1583/*
1584 * Skip over this element of the menu path and return the start of the next
1585 * element. Any \ and ^Vs are removed from the current element.
Bram Moolenaar342337a2005-07-21 21:11:17 +00001586 * "name" may be modified.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001587 */
Bram Moolenaar5843f5f2019-08-20 20:13:45 +02001588 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001589menu_name_skip(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001590{
1591 char_u *p;
1592
Bram Moolenaar91acfff2017-03-12 19:22:36 +01001593 for (p = name; *p && *p != '.'; MB_PTR_ADV(p))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001594 {
1595 if (*p == '\\' || *p == Ctrl_V)
1596 {
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001597 STRMOVE(p, p + 1);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001598 if (*p == NUL)
1599 break;
1600 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001601 }
1602 if (*p)
1603 *p++ = NUL;
1604 return p;
1605}
1606
1607/*
1608 * Return TRUE when "name" matches with menu "menu". The name is compared in
1609 * two ways: raw menu name and menu name without '&'. ignore part after a TAB.
1610 */
1611 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001612menu_name_equal(char_u *name, vimmenu_T *menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001613{
Bram Moolenaar41375642010-05-16 12:49:27 +02001614#ifdef FEAT_MULTI_LANG
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001615 if (menu->en_name != NULL
Bram Moolenaard91f7042011-01-04 17:49:32 +01001616 && (menu_namecmp(name, menu->en_name)
1617 || menu_namecmp(name, menu->en_dname)))
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001618 return TRUE;
Bram Moolenaar41375642010-05-16 12:49:27 +02001619#endif
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001620 return menu_namecmp(name, menu->name) || menu_namecmp(name, menu->dname);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001621}
1622
1623 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001624menu_namecmp(char_u *name, char_u *mname)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001625{
1626 int i;
1627
1628 for (i = 0; name[i] != NUL && name[i] != TAB; ++i)
1629 if (name[i] != mname[i])
1630 break;
1631 return ((name[i] == NUL || name[i] == TAB)
1632 && (mname[i] == NUL || mname[i] == TAB));
1633}
1634
1635/*
1636 * Return the modes specified by the given menu command (eg :menu! returns
1637 * MENU_CMDLINE_MODE | MENU_INSERT_MODE).
1638 * If "noremap" is not NULL, then the flag it points to is set according to
1639 * whether the command is a "nore" command.
1640 * If "unmenu" is not NULL, then the flag it points to is set according to
1641 * whether the command is an "unmenu" command.
1642 */
1643 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001644get_menu_cmd_modes(
1645 char_u *cmd,
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001646 int forceit, // Was there a "!" after the command?
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001647 int *noremap,
1648 int *unmenu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001649{
1650 int modes;
1651
1652 switch (*cmd++)
1653 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001654 case 'v': // vmenu, vunmenu, vnoremenu
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001655 modes = MENU_VISUAL_MODE | MENU_SELECT_MODE;
1656 break;
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001657 case 'x': // xmenu, xunmenu, xnoremenu
Bram Moolenaar071d4272004-06-13 20:20:40 +00001658 modes = MENU_VISUAL_MODE;
1659 break;
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001660 case 's': // smenu, sunmenu, snoremenu
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001661 modes = MENU_SELECT_MODE;
1662 break;
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001663 case 'o': // omenu
Bram Moolenaar071d4272004-06-13 20:20:40 +00001664 modes = MENU_OP_PENDING_MODE;
1665 break;
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001666 case 'i': // imenu
Bram Moolenaar071d4272004-06-13 20:20:40 +00001667 modes = MENU_INSERT_MODE;
1668 break;
1669 case 't':
Bram Moolenaar6ed545e2022-05-09 20:09:23 +01001670 if (*cmd == 'l') // tlmenu, tlunmenu, tlnoremenu
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001671 {
1672 modes = MENU_TERMINAL_MODE;
1673 ++cmd;
1674 break;
1675 }
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001676 modes = MENU_TIP_MODE; // tmenu
Bram Moolenaar071d4272004-06-13 20:20:40 +00001677 break;
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001678 case 'c': // cmenu
Bram Moolenaar071d4272004-06-13 20:20:40 +00001679 modes = MENU_CMDLINE_MODE;
1680 break;
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001681 case 'a': // amenu
Bram Moolenaar071d4272004-06-13 20:20:40 +00001682 modes = MENU_INSERT_MODE | MENU_CMDLINE_MODE | MENU_NORMAL_MODE
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001683 | MENU_VISUAL_MODE | MENU_SELECT_MODE
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00001684 | MENU_OP_PENDING_MODE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001685 break;
1686 case 'n':
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001687 if (*cmd != 'o') // nmenu, not noremenu
Bram Moolenaar071d4272004-06-13 20:20:40 +00001688 {
1689 modes = MENU_NORMAL_MODE;
1690 break;
1691 }
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001692 // FALLTHROUGH
Bram Moolenaar071d4272004-06-13 20:20:40 +00001693 default:
1694 --cmd;
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001695 if (forceit) // menu!!
Bram Moolenaar071d4272004-06-13 20:20:40 +00001696 modes = MENU_INSERT_MODE | MENU_CMDLINE_MODE;
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001697 else // menu
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001698 modes = MENU_NORMAL_MODE | MENU_VISUAL_MODE | MENU_SELECT_MODE
Bram Moolenaar071d4272004-06-13 20:20:40 +00001699 | MENU_OP_PENDING_MODE;
1700 }
1701
1702 if (noremap != NULL)
1703 *noremap = (*cmd == 'n' ? REMAP_NONE : REMAP_YES);
1704 if (unmenu != NULL)
1705 *unmenu = (*cmd == 'u');
1706 return modes;
1707}
1708
1709/*
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01001710 * Return the string representation of the menu modes. Does the opposite
1711 * of get_menu_cmd_modes().
1712 */
1713 static char_u *
1714get_menu_mode_str(int modes)
1715{
1716 if ((modes & (MENU_INSERT_MODE | MENU_CMDLINE_MODE | MENU_NORMAL_MODE |
1717 MENU_VISUAL_MODE | MENU_SELECT_MODE | MENU_OP_PENDING_MODE))
1718 == (MENU_INSERT_MODE | MENU_CMDLINE_MODE | MENU_NORMAL_MODE |
1719 MENU_VISUAL_MODE | MENU_SELECT_MODE | MENU_OP_PENDING_MODE))
1720 return (char_u *)"a";
1721 if ((modes & (MENU_NORMAL_MODE | MENU_VISUAL_MODE | MENU_SELECT_MODE |
1722 MENU_OP_PENDING_MODE))
1723 == (MENU_NORMAL_MODE | MENU_VISUAL_MODE | MENU_SELECT_MODE |
1724 MENU_OP_PENDING_MODE))
1725 return (char_u *)" ";
1726 if ((modes & (MENU_INSERT_MODE | MENU_CMDLINE_MODE))
1727 == (MENU_INSERT_MODE | MENU_CMDLINE_MODE))
1728 return (char_u *)"!";
1729 if ((modes & (MENU_VISUAL_MODE | MENU_SELECT_MODE))
1730 == (MENU_VISUAL_MODE | MENU_SELECT_MODE))
1731 return (char_u *)"v";
1732 if (modes & MENU_VISUAL_MODE)
1733 return (char_u *)"x";
1734 if (modes & MENU_SELECT_MODE)
1735 return (char_u *)"s";
1736 if (modes & MENU_OP_PENDING_MODE)
1737 return (char_u *)"o";
1738 if (modes & MENU_INSERT_MODE)
1739 return (char_u *)"i";
1740 if (modes & MENU_TERMINAL_MODE)
1741 return (char_u *)"tl";
1742 if (modes & MENU_CMDLINE_MODE)
1743 return (char_u *)"c";
1744 if (modes & MENU_NORMAL_MODE)
1745 return (char_u *)"n";
1746 if (modes & MENU_TIP_MODE)
1747 return (char_u *)"t";
1748
1749 return (char_u *)"";
1750}
1751
1752/*
Bram Moolenaar071d4272004-06-13 20:20:40 +00001753 * Modify a menu name starting with "PopUp" to include the mode character.
1754 * Returns the name in allocated memory (NULL for failure).
1755 */
1756 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001757popup_mode_name(char_u *name, int idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001758{
1759 char_u *p;
1760 int len = (int)STRLEN(name);
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001761 char *mode_chars = menu_mode_chars[idx];
1762 int mode_chars_len = (int)strlen(mode_chars);
1763 int i;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001764
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001765 p = vim_strnsave(name, len + mode_chars_len);
Yegappan Lakshmanane8575982023-01-14 12:32:28 +00001766 if (p == NULL)
1767 return NULL;
1768
1769 mch_memmove(p + 5 + mode_chars_len, p + 5, (size_t)(len - 4));
1770 for (i = 0; i < mode_chars_len; ++i)
1771 p[5 + i] = menu_mode_chars[idx][i];
Bram Moolenaar071d4272004-06-13 20:20:40 +00001772 return p;
1773}
1774
1775#if defined(FEAT_GUI) || defined(PROTO)
1776/*
1777 * Return the index into the menu->strings or menu->noremap arrays for the
1778 * current state. Returns MENU_INDEX_INVALID if there is no mapping for the
1779 * given menu in the current mode.
1780 */
1781 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001782get_menu_index(vimmenu_T *menu, int state)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001783{
1784 int idx;
1785
Bram Moolenaar24959102022-05-07 20:01:16 +01001786 if ((state & MODE_INSERT))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001787 idx = MENU_INDEX_INSERT;
Bram Moolenaar24959102022-05-07 20:01:16 +01001788 else if (state & MODE_CMDLINE)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001789 idx = MENU_INDEX_CMDLINE;
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001790#ifdef FEAT_TERMINAL
1791 else if (term_use_loop())
1792 idx = MENU_INDEX_TERMINAL;
1793#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001794 else if (VIsual_active)
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001795 {
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00001796 if (VIsual_select)
1797 idx = MENU_INDEX_SELECT;
1798 else
1799 idx = MENU_INDEX_VISUAL;
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001800 }
Bram Moolenaar24959102022-05-07 20:01:16 +01001801 else if (state == MODE_HITRETURN || state == MODE_ASKMORE)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001802 idx = MENU_INDEX_CMDLINE;
1803 else if (finish_op)
1804 idx = MENU_INDEX_OP_PENDING;
Bram Moolenaar24959102022-05-07 20:01:16 +01001805 else if ((state & MODE_NORMAL))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001806 idx = MENU_INDEX_NORMAL;
1807 else
1808 idx = MENU_INDEX_INVALID;
1809
1810 if (idx != MENU_INDEX_INVALID && menu->strings[idx] == NULL)
1811 idx = MENU_INDEX_INVALID;
1812 return idx;
1813}
1814#endif
1815
1816/*
1817 * Duplicate the menu item text and then process to see if a mnemonic key
1818 * and/or accelerator text has been identified.
1819 * Returns a pointer to allocated memory, or NULL for failure.
1820 * If mnemonic != NULL, *mnemonic is set to the character after the first '&'.
1821 * If actext != NULL, *actext is set to the text after the first TAB.
1822 */
1823 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001824menu_text(char_u *str, int *mnemonic, char_u **actext)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001825{
1826 char_u *p;
1827 char_u *text;
1828
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001829 // Locate accelerator text, after the first TAB
Bram Moolenaar071d4272004-06-13 20:20:40 +00001830 p = vim_strchr(str, TAB);
1831 if (p != NULL)
1832 {
1833 if (actext != NULL)
1834 *actext = vim_strsave(p + 1);
Bram Moolenaar71ccd032020-06-12 22:59:11 +02001835 text = vim_strnsave(str, p - str);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001836 }
1837 else
1838 text = vim_strsave(str);
1839
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001840 // Find mnemonic characters "&a" and reduce "&&" to "&".
Bram Moolenaar071d4272004-06-13 20:20:40 +00001841 for (p = text; p != NULL; )
1842 {
1843 p = vim_strchr(p, '&');
1844 if (p != NULL)
1845 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001846 if (p[1] == NUL) // trailing "&"
Bram Moolenaar071d4272004-06-13 20:20:40 +00001847 break;
1848 if (mnemonic != NULL && p[1] != '&')
1849#if !defined(__MVS__) || defined(MOTIF390_MNEMONIC_FIXED)
1850 *mnemonic = p[1];
1851#else
1852 {
1853 /*
1854 * Well there is a bug in the Motif libraries on OS390 Unix.
1855 * The mnemonic keys needs to be converted to ASCII values
1856 * first.
1857 * This behavior has been seen in 2.8 and 2.9.
1858 */
1859 char c = p[1];
1860 __etoa_l(&c, 1);
1861 *mnemonic = c;
1862 }
1863#endif
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001864 STRMOVE(p, p + 1);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001865 p = p + 1;
1866 }
1867 }
1868 return text;
1869}
1870
1871/*
1872 * Return TRUE if "name" can be a menu in the MenuBar.
1873 */
1874 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001875menu_is_menubar(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001876{
1877 return (!menu_is_popup(name)
1878 && !menu_is_toolbar(name)
Bram Moolenaar378daf82017-09-23 23:58:28 +02001879 && !menu_is_winbar(name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001880 && *name != MNU_HIDDEN_CHAR);
1881}
1882
1883/*
1884 * Return TRUE if "name" is a popup menu name.
1885 */
1886 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001887menu_is_popup(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001888{
1889 return (STRNCMP(name, "PopUp", 5) == 0);
1890}
1891
1892#if (defined(FEAT_GUI_MOTIF) && (XmVersion <= 1002)) || defined(PROTO)
1893/*
1894 * Return TRUE if "name" is part of a popup menu.
1895 */
1896 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001897menu_is_child_of_popup(vimmenu_T *menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001898{
1899 while (menu->parent != NULL)
1900 menu = menu->parent;
1901 return menu_is_popup(menu->name);
1902}
1903#endif
1904
1905/*
1906 * Return TRUE if "name" is a toolbar menu name.
1907 */
1908 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001909menu_is_toolbar(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001910{
1911 return (STRNCMP(name, "ToolBar", 7) == 0);
1912}
1913
1914/*
1915 * Return TRUE if the name is a menu separator identifier: Starts and ends
1916 * with '-'
1917 */
1918 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001919menu_is_separator(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001920{
1921 return (name[0] == '-' && name[STRLEN(name) - 1] == '-');
1922}
1923
1924/*
1925 * Return TRUE if the menu is hidden: Starts with ']'
1926 */
1927 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001928menu_is_hidden(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001929{
1930 return (name[0] == ']') || (menu_is_popup(name) && name[5] != NUL);
1931}
1932
Bram Moolenaar071d4272004-06-13 20:20:40 +00001933/*
1934 * Return TRUE if the menu is the tearoff menu.
1935 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001936 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001937menu_is_tearoff(char_u *name UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001938{
1939#ifdef FEAT_GUI
1940 return (STRCMP(name, TEAR_STRING) == 0);
1941#else
1942 return FALSE;
1943#endif
1944}
Bram Moolenaar071d4272004-06-13 20:20:40 +00001945
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001946#if defined(FEAT_GUI) || defined(FEAT_TERM_POPUP_MENU) || defined(PROTO)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001947
1948 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001949get_menu_mode(void)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001950{
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001951#ifdef FEAT_TERMINAL
1952 if (term_use_loop())
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001953 return MENU_INDEX_TERMINAL;
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001954#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001955 if (VIsual_active)
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001956 {
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00001957 if (VIsual_select)
1958 return MENU_INDEX_SELECT;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001959 return MENU_INDEX_VISUAL;
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001960 }
Bram Moolenaar24959102022-05-07 20:01:16 +01001961 if (State & MODE_INSERT)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001962 return MENU_INDEX_INSERT;
Bram Moolenaar24959102022-05-07 20:01:16 +01001963 if ((State & MODE_CMDLINE) || State == MODE_ASKMORE
1964 || State == MODE_HITRETURN)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001965 return MENU_INDEX_CMDLINE;
1966 if (finish_op)
1967 return MENU_INDEX_OP_PENDING;
Bram Moolenaar24959102022-05-07 20:01:16 +01001968 if (State & MODE_NORMAL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001969 return MENU_INDEX_NORMAL;
Bram Moolenaar24959102022-05-07 20:01:16 +01001970 if (State & MODE_LANGMAP) // must be a "r" command, like Insert mode
Bram Moolenaar071d4272004-06-13 20:20:40 +00001971 return MENU_INDEX_INSERT;
1972 return MENU_INDEX_INVALID;
1973}
1974
Bram Moolenaar29a2c082018-03-05 21:06:23 +01001975 int
1976get_menu_mode_flag(void)
1977{
1978 int mode = get_menu_mode();
1979
1980 if (mode == MENU_INDEX_INVALID)
1981 return 0;
1982 return 1 << mode;
1983}
1984
Bram Moolenaar071d4272004-06-13 20:20:40 +00001985/*
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001986 * Display the Special "PopUp" menu as a pop-up at the current mouse
1987 * position. The "PopUpn" menu is for Normal mode, "PopUpi" for Insert mode,
1988 * etc.
1989 */
1990 void
1991show_popupmenu(void)
1992{
1993 vimmenu_T *menu;
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001994 int menu_mode;
1995 char* mode;
1996 int mode_len;
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001997
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001998 menu_mode = get_menu_mode();
1999 if (menu_mode == MENU_INDEX_INVALID)
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002000 return;
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002001 mode = menu_mode_chars[menu_mode];
2002 mode_len = (int)strlen(mode);
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002003
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002004 apply_autocmds(EVENT_MENUPOPUP, (char_u*)mode, NULL, FALSE, curbuf);
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002005
Bram Moolenaar00d253e2020-04-06 22:13:01 +02002006 FOR_ALL_MENUS(menu)
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002007 if (STRNCMP("PopUp", menu->name, 5) == 0 && STRNCMP(menu->name + 5, mode, mode_len) == 0)
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002008 break;
2009
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002010 // Only show a popup when it is defined and has entries
Yegappan Lakshmanane8575982023-01-14 12:32:28 +00002011 if (menu == NULL || menu->children == NULL)
2012 return;
2013
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002014# if defined(FEAT_GUI)
Yegappan Lakshmanane8575982023-01-14 12:32:28 +00002015 if (gui.in_use)
2016 {
2017 // Update the menus now, in case the MenuPopup autocommand did
2018 // anything.
2019 gui_update_menus(0);
2020 gui_mch_show_popupmenu(menu);
2021 }
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002022# endif
2023# if defined(FEAT_GUI) && defined(FEAT_TERM_POPUP_MENU)
Yegappan Lakshmanane8575982023-01-14 12:32:28 +00002024 else
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002025# endif
2026# if defined(FEAT_TERM_POPUP_MENU)
Yegappan Lakshmanane8575982023-01-14 12:32:28 +00002027 pum_show_popupmenu(menu);
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002028# endif
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002029}
2030#endif
2031
2032#if defined(FEAT_GUI) || defined(PROTO)
2033
2034/*
Bram Moolenaar968bbbe2006-08-16 19:41:08 +00002035 * Check that a pointer appears in the menu tree. Used to protect from using
2036 * a menu that was deleted after it was selected but before the event was
2037 * handled.
2038 * Return OK or FAIL. Used recursively.
2039 */
2040 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002041check_menu_pointer(vimmenu_T *root, vimmenu_T *menu_to_check)
Bram Moolenaar968bbbe2006-08-16 19:41:08 +00002042{
2043 vimmenu_T *p;
2044
2045 for (p = root; p != NULL; p = p->next)
2046 if (p == menu_to_check
2047 || (p->children != NULL
2048 && check_menu_pointer(p->children, menu_to_check) == OK))
2049 return OK;
2050 return FAIL;
2051}
2052
2053/*
Bram Moolenaar071d4272004-06-13 20:20:40 +00002054 * After we have started the GUI, then we can create any menus that have been
2055 * defined. This is done once here. add_menu_path() may have already been
2056 * called to define these menus, and may be called again. This function calls
2057 * itself recursively. Should be called at the top level with:
Bram Moolenaara06ecab2016-07-16 14:47:36 +02002058 * gui_create_initial_menus(root_menu);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002059 */
2060 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002061gui_create_initial_menus(vimmenu_T *menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002062{
2063 int idx = 0;
2064
2065 while (menu != NULL)
2066 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002067 // Don't add a menu when only a tip was defined.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002068 if (menu->modes & MENU_ALL_MODES)
2069 {
2070 if (menu->children != NULL)
2071 {
2072 gui_mch_add_menu(menu, idx);
2073 gui_create_initial_menus(menu->children);
2074 }
2075 else
2076 gui_mch_add_menu_item(menu, idx);
2077 }
2078 menu = menu->next;
2079 ++idx;
2080 }
2081}
2082
2083/*
2084 * Used recursively by gui_update_menus (see below)
2085 */
2086 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002087gui_update_menus_recurse(vimmenu_T *menu, int mode)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002088{
2089 int grey;
2090
2091 while (menu)
2092 {
2093 if ((menu->modes & menu->enabled & mode)
Bram Moolenaar4f974752019-02-17 17:44:42 +01002094# if defined(FEAT_GUI_MSWIN) && defined(FEAT_TEAROFF)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002095 || menu_is_tearoff(menu->dname)
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002096# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002097 )
2098 grey = FALSE;
2099 else
2100 grey = TRUE;
Bram Moolenaar0b962e52022-04-03 18:02:37 +01002101
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002102 // Never hide a toplevel menu, it may make the menubar resize or
2103 // disappear. Same problem for ToolBar items.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002104 if (vim_strchr(p_go, GO_GREY) != NULL || menu->parent == NULL
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002105# ifdef FEAT_TOOLBAR
Bram Moolenaar071d4272004-06-13 20:20:40 +00002106 || menu_is_toolbar(menu->parent->name)
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002107# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002108 )
2109 gui_mch_menu_grey(menu, grey);
2110 else
2111 gui_mch_menu_hidden(menu, grey);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002112 gui_update_menus_recurse(menu->children, mode);
2113 menu = menu->next;
2114 }
2115}
2116
2117/*
2118 * Make sure only the valid menu items appear for this mode. If
2119 * force_menu_update is not TRUE, then we only do this if the mode has changed
2120 * since last time. If "modes" is not 0, then we use these modes instead.
2121 */
2122 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002123gui_update_menus(int modes)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002124{
2125 static int prev_mode = -1;
2126 int mode = 0;
2127
2128 if (modes != 0x0)
2129 mode = modes;
2130 else
Bram Moolenaar29a2c082018-03-05 21:06:23 +01002131 mode = get_menu_mode_flag();
Bram Moolenaar071d4272004-06-13 20:20:40 +00002132
2133 if (force_menu_update || mode != prev_mode)
2134 {
2135 gui_update_menus_recurse(root_menu, mode);
2136 gui_mch_draw_menubar();
2137 prev_mode = mode;
2138 force_menu_update = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002139 }
2140}
2141
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002142# if defined(FEAT_GUI_MSWIN) || defined(FEAT_GUI_MOTIF) \
Bram Moolenaar241a8aa2005-12-06 20:04:44 +00002143 || defined(FEAT_GUI_GTK) || defined(FEAT_GUI_PHOTON) || defined(PROTO)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002144/*
2145 * Check if a key is used as a mnemonic for a toplevel menu.
2146 * Case of the key is ignored.
2147 */
2148 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002149gui_is_menu_shortcut(int key)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002150{
2151 vimmenu_T *menu;
2152
2153 if (key < 256)
2154 key = TOLOWER_LOC(key);
Bram Moolenaar00d253e2020-04-06 22:13:01 +02002155 FOR_ALL_MENUS(menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002156 if (menu->mnemonic == key
2157 || (menu->mnemonic < 256 && TOLOWER_LOC(menu->mnemonic) == key))
2158 return TRUE;
2159 return FALSE;
2160}
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002161# endif
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002162#endif // FEAT_GUI
Bram Moolenaar071d4272004-06-13 20:20:40 +00002163
Bram Moolenaar4f974752019-02-17 17:44:42 +01002164#if (defined(FEAT_GUI_MSWIN) && defined(FEAT_TEAROFF)) || defined(PROTO)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002165
2166/*
2167 * Deal with tearoff items that are added like a menu item.
2168 * Currently only for Win32 GUI. Others may follow later.
2169 */
2170
2171 void
2172gui_mch_toggle_tearoffs(int enable)
2173{
2174 int pri_tab[MENUDEPTH + 1];
2175 int i;
2176
2177 if (enable)
2178 {
2179 for (i = 0; i < MENUDEPTH; ++i)
2180 pri_tab[i] = 500;
2181 pri_tab[MENUDEPTH] = -1;
2182 gui_create_tearoffs_recurse(root_menu, (char_u *)"", pri_tab, 0);
2183 }
2184 else
2185 gui_destroy_tearoffs_recurse(root_menu);
2186 s_tearoffs = enable;
2187}
2188
2189/*
2190 * Recursively add tearoff items
2191 */
2192 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002193gui_create_tearoffs_recurse(
2194 vimmenu_T *menu,
2195 const char_u *pname,
2196 int *pri_tab,
2197 int pri_idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002198{
2199 char_u *newpname = NULL;
2200 int len;
2201 char_u *s;
2202 char_u *d;
2203
2204 if (pri_tab[pri_idx + 1] != -1)
2205 ++pri_idx;
2206 while (menu != NULL)
2207 {
2208 if (menu->children != NULL && menu_is_menubar(menu->name))
2209 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002210 // Add the menu name to the menu path. Insert a backslash before
2211 // dots (it's used to separate menu names).
Bram Moolenaar071d4272004-06-13 20:20:40 +00002212 len = (int)STRLEN(pname) + (int)STRLEN(menu->name);
2213 for (s = menu->name; *s; ++s)
2214 if (*s == '.' || *s == '\\')
2215 ++len;
2216 newpname = alloc(len + TEAR_LEN + 2);
2217 if (newpname != NULL)
2218 {
2219 STRCPY(newpname, pname);
2220 d = newpname + STRLEN(newpname);
2221 for (s = menu->name; *s; ++s)
2222 {
2223 if (*s == '.' || *s == '\\')
2224 *d++ = '\\';
2225 *d++ = *s;
2226 }
2227 *d = NUL;
2228
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002229 // check if tearoff already exists
Bram Moolenaar071d4272004-06-13 20:20:40 +00002230 if (STRCMP(menu->children->name, TEAR_STRING) != 0)
2231 {
2232 gui_add_tearoff(newpname, pri_tab, pri_idx - 1);
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002233 *d = NUL; // remove TEAR_STRING
Bram Moolenaar071d4272004-06-13 20:20:40 +00002234 }
2235
2236 STRCAT(newpname, ".");
2237 gui_create_tearoffs_recurse(menu->children, newpname,
2238 pri_tab, pri_idx);
2239 vim_free(newpname);
2240 }
2241 }
2242 menu = menu->next;
2243 }
2244}
2245
2246/*
2247 * Add tear-off menu item for a submenu.
2248 * "tearpath" is the menu path, and must have room to add TEAR_STRING.
2249 */
2250 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002251gui_add_tearoff(char_u *tearpath, int *pri_tab, int pri_idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002252{
2253 char_u *tbuf;
2254 int t;
2255 vimmenu_T menuarg;
2256
2257 tbuf = alloc(5 + (unsigned int)STRLEN(tearpath));
Yegappan Lakshmanane8575982023-01-14 12:32:28 +00002258 if (tbuf == NULL)
2259 return;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002260
Yegappan Lakshmanane8575982023-01-14 12:32:28 +00002261 tbuf[0] = K_SPECIAL;
2262 tbuf[1] = K_SECOND(K_TEAROFF);
2263 tbuf[2] = K_THIRD(K_TEAROFF);
2264 STRCPY(tbuf + 3, tearpath);
2265 STRCAT(tbuf + 3, "\r");
Bram Moolenaar071d4272004-06-13 20:20:40 +00002266
Yegappan Lakshmanane8575982023-01-14 12:32:28 +00002267 STRCAT(tearpath, ".");
2268 STRCAT(tearpath, TEAR_STRING);
2269
2270 // Priority of tear-off is always 1
2271 t = pri_tab[pri_idx + 1];
2272 pri_tab[pri_idx + 1] = 1;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002273
2274#ifdef FEAT_TOOLBAR
Yegappan Lakshmanane8575982023-01-14 12:32:28 +00002275 menuarg.iconfile = NULL;
2276 menuarg.iconidx = -1;
2277 menuarg.icon_builtin = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002278#endif
Yegappan Lakshmanane8575982023-01-14 12:32:28 +00002279 menuarg.noremap[0] = REMAP_NONE;
2280 menuarg.silent[0] = TRUE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002281
Yegappan Lakshmanane8575982023-01-14 12:32:28 +00002282 menuarg.modes = MENU_ALL_MODES;
2283 add_menu_path(tearpath, &menuarg, pri_tab, tbuf, FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002284
Yegappan Lakshmanane8575982023-01-14 12:32:28 +00002285 menuarg.modes = MENU_TIP_MODE;
2286 add_menu_path(tearpath, &menuarg, pri_tab,
2287 (char_u *)_("Tear off this menu"), FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002288
Yegappan Lakshmanane8575982023-01-14 12:32:28 +00002289 pri_tab[pri_idx + 1] = t;
2290 vim_free(tbuf);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002291}
2292
2293/*
2294 * Recursively destroy tearoff items
2295 */
2296 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002297gui_destroy_tearoffs_recurse(vimmenu_T *menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002298{
2299 while (menu)
2300 {
2301 if (menu->children)
2302 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002303 // check if tearoff exists
Bram Moolenaar071d4272004-06-13 20:20:40 +00002304 if (STRCMP(menu->children->name, TEAR_STRING) == 0)
2305 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002306 // Disconnect the item and free the memory
Bram Moolenaar071d4272004-06-13 20:20:40 +00002307 free_menu(&menu->children);
2308 }
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002309 if (menu->children != NULL) // if not the last one
Bram Moolenaar071d4272004-06-13 20:20:40 +00002310 gui_destroy_tearoffs_recurse(menu->children);
2311 }
2312 menu = menu->next;
2313 }
2314}
2315
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002316#endif // FEAT_GUI_MSWIN && FEAT_TEAROFF
Bram Moolenaar071d4272004-06-13 20:20:40 +00002317
2318/*
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002319 * Execute "menu". Use by ":emenu" and the window toolbar.
2320 * "eap" is NULL for the window toolbar.
Bram Moolenaarf39d9e92023-04-22 22:54:40 +01002321 * "mode_idx" specifies a MENU_INDEX_ value, use MENU_INDEX_INVALID to depend
2322 * on the current state.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002323 */
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002324 void
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002325execute_menu(exarg_T *eap, vimmenu_T *menu, int mode_idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002326{
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002327 int idx = mode_idx;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002328
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002329 if (idx < 0)
2330 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002331 // Use the Insert mode entry when returning to Insert mode.
Bram Moolenaarf39d9e92023-04-22 22:54:40 +01002332 if (restart_edit && current_sctx.sc_sid == 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002333 {
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002334 idx = MENU_INDEX_INSERT;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002335 }
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002336#ifdef FEAT_TERMINAL
2337 else if (term_use_loop())
Bram Moolenaar071d4272004-06-13 20:20:40 +00002338 {
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002339 idx = MENU_INDEX_TERMINAL;
2340 }
2341#endif
2342 else if (VIsual_active)
2343 {
2344 idx = MENU_INDEX_VISUAL;
2345 }
2346 else if (eap != NULL && eap->addr_count)
2347 {
2348 pos_T tpos;
2349
2350 idx = MENU_INDEX_VISUAL;
2351
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002352 // GEDDES: This is not perfect - but it is a
2353 // quick way of detecting whether we are doing this from a
2354 // selection - see if the range matches up with the visual
2355 // select start and end.
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002356 if ((curbuf->b_visual.vi_start.lnum == eap->line1)
2357 && (curbuf->b_visual.vi_end.lnum) == eap->line2)
2358 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002359 // Set it up for visual mode - equivalent to gv.
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002360 VIsual_mode = curbuf->b_visual.vi_mode;
2361 tpos = curbuf->b_visual.vi_end;
2362 curwin->w_cursor = curbuf->b_visual.vi_start;
2363 curwin->w_curswant = curbuf->b_visual.vi_curswant;
2364 }
2365 else
2366 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002367 // Set it up for line-wise visual mode
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002368 VIsual_mode = 'V';
2369 curwin->w_cursor.lnum = eap->line1;
2370 curwin->w_cursor.col = 1;
2371 tpos.lnum = eap->line2;
2372 tpos.col = MAXCOL;
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002373 tpos.coladd = 0;
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002374 }
2375
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002376 // Activate visual mode
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002377 VIsual_active = TRUE;
2378 VIsual_reselect = TRUE;
2379 check_cursor();
2380 VIsual = curwin->w_cursor;
2381 curwin->w_cursor = tpos;
2382
2383 check_cursor();
2384
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002385 // Adjust the cursor to make sure it is in the correct pos
2386 // for exclusive mode
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002387 if (*p_sel == 'e' && gchar_cursor() != NUL)
2388 ++curwin->w_cursor.col;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002389 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00002390 }
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002391
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002392 // For the WinBar menu always use the Normal mode menu.
zeertzjq79ae1522022-07-01 12:13:15 +01002393 if (idx == MENU_INDEX_INVALID || eap == NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002394 idx = MENU_INDEX_NORMAL;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002395
zeertzjq79ae1522022-07-01 12:13:15 +01002396 if (menu->strings[idx] != NULL && (menu->modes & (1 << idx)))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002397 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002398 // When executing a script or function execute the commands right now.
2399 // Also for the window toolbar.
2400 // Otherwise put them in the typeahead buffer.
Bram Moolenaar9b8d6222020-12-28 18:26:00 +01002401 if (eap == NULL || current_sctx.sc_sid != 0)
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002402 {
2403 save_state_T save_state;
2404
2405 ++ex_normal_busy;
2406 if (save_current_state(&save_state))
2407 exec_normal_cmd(menu->strings[idx], menu->noremap[idx],
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002408 menu->silent[idx]);
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002409 restore_current_state(&save_state);
2410 --ex_normal_busy;
2411 }
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002412 else
2413 ins_typebuf(menu->strings[idx], menu->noremap[idx], 0,
Bram Moolenaar071d4272004-06-13 20:20:40 +00002414 TRUE, menu->silent[idx]);
2415 }
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002416 else if (eap != NULL)
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002417 {
2418 char_u *mode;
2419
2420 switch (idx)
2421 {
2422 case MENU_INDEX_VISUAL:
2423 mode = (char_u *)"Visual";
2424 break;
2425 case MENU_INDEX_SELECT:
2426 mode = (char_u *)"Select";
2427 break;
2428 case MENU_INDEX_OP_PENDING:
2429 mode = (char_u *)"Op-pending";
2430 break;
2431 case MENU_INDEX_TERMINAL:
2432 mode = (char_u *)"Terminal";
2433 break;
2434 case MENU_INDEX_INSERT:
2435 mode = (char_u *)"Insert";
2436 break;
2437 case MENU_INDEX_CMDLINE:
2438 mode = (char_u *)"Cmdline";
2439 break;
2440 // case MENU_INDEX_TIP: cannot happen
2441 default:
2442 mode = (char_u *)"Normal";
2443 }
Bram Moolenaareaaac012022-01-02 17:00:40 +00002444 semsg(_(e_menu_not_defined_for_str_mode), mode);
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002445 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00002446}
2447
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002448/*
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002449 * Lookup a menu by the descriptor name e.g. "File.New"
2450 * Returns NULL if the menu is not found
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002451 */
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002452 static vimmenu_T *
2453menu_getbyname(char_u *name_arg)
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002454{
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002455 char_u *name;
2456 char_u *saved_name;
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002457 vimmenu_T *menu;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002458 char_u *p;
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002459 int gave_emsg = FALSE;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002460
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002461 saved_name = vim_strsave(name_arg);
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002462 if (saved_name == NULL)
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002463 return NULL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002464
2465 menu = *get_root_menu(saved_name);
2466 name = saved_name;
2467 while (*name)
2468 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002469 // Find in the menu hierarchy
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002470 p = menu_name_skip(name);
2471
2472 while (menu != NULL)
2473 {
2474 if (menu_name_equal(name, menu))
2475 {
2476 if (*p == NUL && menu->children != NULL)
2477 {
Bram Moolenaareaaac012022-01-02 17:00:40 +00002478 emsg(_(e_menu_path_must_lead_to_menu_item));
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002479 gave_emsg = TRUE;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002480 menu = NULL;
2481 }
2482 else if (*p != NUL && menu->children == NULL)
2483 {
Bram Moolenaareaaac012022-01-02 17:00:40 +00002484 emsg(_(e_part_of_menu_item_path_is_not_sub_menu));
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002485 menu = NULL;
2486 }
2487 break;
2488 }
2489 menu = menu->next;
2490 }
2491 if (menu == NULL || *p == NUL)
2492 break;
2493 menu = menu->children;
2494 name = p;
2495 }
2496 vim_free(saved_name);
2497 if (menu == NULL)
2498 {
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002499 if (!gave_emsg)
Bram Moolenaareaaac012022-01-02 17:00:40 +00002500 semsg(_(e_menu_not_found_str), name_arg);
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002501 return NULL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002502 }
2503
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002504 return menu;
2505}
2506
2507/*
2508 * Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy and
2509 * execute it.
2510 */
2511 void
2512ex_emenu(exarg_T *eap)
2513{
2514 vimmenu_T *menu;
2515 char_u *arg = eap->arg;
Bram Moolenaarf39d9e92023-04-22 22:54:40 +01002516 int mode_idx = MENU_INDEX_INVALID;
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002517
2518 if (arg[0] && VIM_ISWHITE(arg[1]))
2519 {
2520 switch (arg[0])
2521 {
2522 case 'n': mode_idx = MENU_INDEX_NORMAL; break;
2523 case 'v': mode_idx = MENU_INDEX_VISUAL; break;
2524 case 's': mode_idx = MENU_INDEX_SELECT; break;
2525 case 'o': mode_idx = MENU_INDEX_OP_PENDING; break;
2526 case 't': mode_idx = MENU_INDEX_TERMINAL; break;
2527 case 'i': mode_idx = MENU_INDEX_INSERT; break;
2528 case 'c': mode_idx = MENU_INDEX_CMDLINE; break;
Bram Moolenaar436b5ad2021-12-31 22:49:24 +00002529 default: semsg(_(e_invalid_argument_str), arg);
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002530 return;
2531 }
2532 arg = skipwhite(arg + 2);
2533 }
2534
2535 menu = menu_getbyname(arg);
2536 if (menu == NULL)
2537 return;
2538
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002539 // Found the menu, so execute.
2540 execute_menu(eap, menu, mode_idx);
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002541}
2542
2543/*
2544 * Handle a click in the window toolbar of "wp" at column "col".
2545 */
2546 void
2547winbar_click(win_T *wp, int col)
2548{
2549 int idx;
2550
2551 if (wp->w_winbar_items == NULL)
2552 return;
2553 for (idx = 0; wp->w_winbar_items[idx].wb_menu != NULL; ++idx)
2554 {
2555 winbar_item_T *item = &wp->w_winbar_items[idx];
2556
2557 if (col >= item->wb_startcol && col <= item->wb_endcol)
2558 {
Bram Moolenaard2fad672019-05-04 16:55:25 +02002559 win_T *save_curwin = NULL;
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002560 pos_T save_visual = VIsual;
2561 int save_visual_active = VIsual_active;
2562 int save_visual_select = VIsual_select;
2563 int save_visual_reselect = VIsual_reselect;
2564 int save_visual_mode = VIsual_mode;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002565
2566 if (wp != curwin)
2567 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002568 // Clicking in the window toolbar of a not-current window.
2569 // Make that window the current one and save Visual mode.
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002570 save_curwin = curwin;
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002571 VIsual_active = FALSE;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002572 curwin = wp;
2573 curbuf = curwin->w_buffer;
2574 check_cursor();
2575 }
2576
Bram Moolenaard2fad672019-05-04 16:55:25 +02002577 // Note: the command might close the current window.
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002578 execute_menu(NULL, item->wb_menu, -1);
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002579
Bram Moolenaard2fad672019-05-04 16:55:25 +02002580 if (save_curwin != NULL && win_valid(save_curwin))
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002581 {
2582 curwin = save_curwin;
2583 curbuf = curwin->w_buffer;
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002584 VIsual = save_visual;
2585 VIsual_active = save_visual_active;
2586 VIsual_select = save_visual_select;
2587 VIsual_reselect = save_visual_reselect;
2588 VIsual_mode = save_visual_mode;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002589 }
Bram Moolenaard2fad672019-05-04 16:55:25 +02002590 if (!win_valid(wp))
2591 break;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002592 }
2593 }
2594}
2595
2596#if defined(FEAT_GUI_MSWIN) || defined(FEAT_GUI_GTK) \
Bram Moolenaarb3f74062020-02-26 16:16:53 +01002597 || defined(FEAT_TERM_POPUP_MENU) || defined(FEAT_GUI_HAIKU) \
Bram Moolenaar071d4272004-06-13 20:20:40 +00002598 || defined(FEAT_BEVAL_TIP) || defined(PROTO)
2599/*
2600 * Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy.
2601 */
2602 vimmenu_T *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002603gui_find_menu(char_u *path_name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002604{
2605 vimmenu_T *menu = NULL;
2606 char_u *name;
2607 char_u *saved_name;
2608 char_u *p;
2609
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002610 menu = *get_root_menu(path_name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002611
2612 saved_name = vim_strsave(path_name);
2613 if (saved_name == NULL)
2614 return NULL;
2615
2616 name = saved_name;
2617 while (*name)
2618 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002619 // find the end of one dot-separated name and put a NUL at the dot
Bram Moolenaar071d4272004-06-13 20:20:40 +00002620 p = menu_name_skip(name);
2621
2622 while (menu != NULL)
2623 {
Bram Moolenaard91f7042011-01-04 17:49:32 +01002624 if (menu_name_equal(name, menu))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002625 {
2626 if (menu->children == NULL)
2627 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002628 // found a menu item instead of a sub-menu
Bram Moolenaar071d4272004-06-13 20:20:40 +00002629 if (*p == NUL)
Bram Moolenaareaaac012022-01-02 17:00:40 +00002630 emsg(_(e_menu_path_must_lead_to_sub_menu));
Bram Moolenaar071d4272004-06-13 20:20:40 +00002631 else
Bram Moolenaareaaac012022-01-02 17:00:40 +00002632 emsg(_(e_part_of_menu_item_path_is_not_sub_menu));
Bram Moolenaar071d4272004-06-13 20:20:40 +00002633 menu = NULL;
2634 goto theend;
2635 }
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002636 if (*p == NUL) // found a full match
Bram Moolenaar071d4272004-06-13 20:20:40 +00002637 goto theend;
2638 break;
2639 }
2640 menu = menu->next;
2641 }
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002642 if (menu == NULL) // didn't find it
Bram Moolenaar071d4272004-06-13 20:20:40 +00002643 break;
2644
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002645 // Found a match, search the sub-menu.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002646 menu = menu->children;
2647 name = p;
2648 }
2649
2650 if (menu == NULL)
Bram Moolenaareaaac012022-01-02 17:00:40 +00002651 emsg(_(e_menu_not_found_check_menu_names));
Bram Moolenaar071d4272004-06-13 20:20:40 +00002652theend:
2653 vim_free(saved_name);
2654 return menu;
2655}
2656#endif
2657
2658#ifdef FEAT_MULTI_LANG
2659/*
2660 * Translation of menu names. Just a simple lookup table.
2661 */
2662
2663typedef struct
2664{
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002665 char_u *from; // English name
2666 char_u *from_noamp; // same, without '&'
2667 char_u *to; // translated name
Bram Moolenaar071d4272004-06-13 20:20:40 +00002668} menutrans_T;
2669
2670static garray_T menutrans_ga = {0, 0, 0, 0, NULL};
2671#endif
2672
2673/*
2674 * ":menutrans".
2675 * This function is also defined without the +multi_lang feature, in which
2676 * case the commands are ignored.
2677 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00002678 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002679ex_menutranslate(exarg_T *eap UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002680{
2681#ifdef FEAT_MULTI_LANG
2682 char_u *arg = eap->arg;
2683 menutrans_T *tp;
2684 int i;
2685 char_u *from, *from_noamp, *to;
2686
2687 if (menutrans_ga.ga_itemsize == 0)
Bram Moolenaar04935fb2022-01-08 16:19:22 +00002688 ga_init2(&menutrans_ga, sizeof(menutrans_T), 5);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002689
2690 /*
2691 * ":menutrans clear": clear all translations.
2692 */
Bram Moolenaar1966c242020-04-20 22:42:32 +02002693 if (STRNCMP(arg, "clear", 5) == 0 && ends_excmd2(arg, skipwhite(arg + 5)))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002694 {
2695 tp = (menutrans_T *)menutrans_ga.ga_data;
2696 for (i = 0; i < menutrans_ga.ga_len; ++i)
2697 {
2698 vim_free(tp[i].from);
2699 vim_free(tp[i].from_noamp);
2700 vim_free(tp[i].to);
2701 }
2702 ga_clear(&menutrans_ga);
2703# ifdef FEAT_EVAL
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002704 // Delete all "menutrans_" global variables.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002705 del_menutrans_vars();
2706# endif
2707 }
2708 else
2709 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002710 // ":menutrans from to": add translation
Bram Moolenaar071d4272004-06-13 20:20:40 +00002711 from = arg;
2712 arg = menu_skip_part(arg);
2713 to = skipwhite(arg);
2714 *arg = NUL;
2715 arg = menu_skip_part(to);
Bram Moolenaar1966c242020-04-20 22:42:32 +02002716 if (arg == to || ends_excmd2(eap->arg, from)
2717 || ends_excmd2(eap->arg, to)
2718 || !ends_excmd2(eap->arg, skipwhite(arg)))
Bram Moolenaar436b5ad2021-12-31 22:49:24 +00002719 emsg(_(e_invalid_argument));
Bram Moolenaar071d4272004-06-13 20:20:40 +00002720 else
2721 {
2722 if (ga_grow(&menutrans_ga, 1) == OK)
2723 {
2724 tp = (menutrans_T *)menutrans_ga.ga_data;
2725 from = vim_strsave(from);
Bram Moolenaarfc1421e2006-04-20 22:17:20 +00002726 if (from != NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002727 {
Bram Moolenaarfc1421e2006-04-20 22:17:20 +00002728 from_noamp = menu_text(from, NULL, NULL);
Bram Moolenaar71ccd032020-06-12 22:59:11 +02002729 to = vim_strnsave(to, arg - to);
Bram Moolenaarfc1421e2006-04-20 22:17:20 +00002730 if (from_noamp != NULL && to != NULL)
2731 {
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002732 menu_translate_tab_and_shift(from);
2733 menu_translate_tab_and_shift(to);
2734 menu_unescape_name(from);
2735 menu_unescape_name(to);
Bram Moolenaarfc1421e2006-04-20 22:17:20 +00002736 tp[menutrans_ga.ga_len].from = from;
2737 tp[menutrans_ga.ga_len].from_noamp = from_noamp;
2738 tp[menutrans_ga.ga_len].to = to;
2739 ++menutrans_ga.ga_len;
2740 }
2741 else
2742 {
2743 vim_free(from);
2744 vim_free(from_noamp);
2745 vim_free(to);
2746 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00002747 }
2748 }
2749 }
2750 }
2751#endif
2752}
2753
2754#if defined(FEAT_MULTI_LANG) || defined(FEAT_TOOLBAR)
2755/*
2756 * Find the character just after one part of a menu name.
2757 */
2758 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002759menu_skip_part(char_u *p)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002760{
Bram Moolenaar1c465442017-03-12 20:10:05 +01002761 while (*p != NUL && *p != '.' && !VIM_ISWHITE(*p))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002762 {
2763 if ((*p == '\\' || *p == Ctrl_V) && p[1] != NUL)
2764 ++p;
2765 ++p;
2766 }
2767 return p;
2768}
2769#endif
2770
2771#ifdef FEAT_MULTI_LANG
2772/*
2773 * Lookup part of a menu name in the translations.
2774 * Return a pointer to the translation or NULL if not found.
2775 */
2776 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002777menutrans_lookup(char_u *name, int len)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002778{
2779 menutrans_T *tp = (menutrans_T *)menutrans_ga.ga_data;
2780 int i;
2781 char_u *dname;
2782
2783 for (i = 0; i < menutrans_ga.ga_len; ++i)
Bram Moolenaar11dd8c12017-03-04 20:41:34 +01002784 if (STRNICMP(name, tp[i].from, len) == 0 && tp[i].from[len] == NUL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002785 return tp[i].to;
2786
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002787 // Now try again while ignoring '&' characters.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002788 i = name[len];
2789 name[len] = NUL;
2790 dname = menu_text(name, NULL, NULL);
2791 name[len] = i;
Yegappan Lakshmanane8575982023-01-14 12:32:28 +00002792 if (dname == NULL)
2793 return NULL;
2794
2795 for (i = 0; i < menutrans_ga.ga_len; ++i)
2796 if (STRICMP(dname, tp[i].from_noamp) == 0)
2797 {
2798 vim_free(dname);
2799 return tp[i].to;
2800 }
2801 vim_free(dname);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002802
2803 return NULL;
2804}
Bram Moolenaar071d4272004-06-13 20:20:40 +00002805
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002806/*
2807 * Unescape the name in the translate dictionary table.
2808 */
2809 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002810menu_unescape_name(char_u *name)
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002811{
2812 char_u *p;
2813
Bram Moolenaar91acfff2017-03-12 19:22:36 +01002814 for (p = name; *p && *p != '.'; MB_PTR_ADV(p))
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002815 if (*p == '\\')
2816 STRMOVE(p, p + 1);
2817}
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002818#endif // FEAT_MULTI_LANG
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002819
2820/*
2821 * Isolate the menu name.
2822 * Skip the menu name, and translate <Tab> into a real TAB.
2823 */
2824 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002825menu_translate_tab_and_shift(char_u *arg_start)
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002826{
2827 char_u *arg = arg_start;
2828
Bram Moolenaar1c465442017-03-12 20:10:05 +01002829 while (*arg && !VIM_ISWHITE(*arg))
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002830 {
2831 if ((*arg == '\\' || *arg == Ctrl_V) && arg[1] != NUL)
2832 arg++;
2833 else if (STRNICMP(arg, "<TAB>", 5) == 0)
2834 {
2835 *arg = TAB;
2836 STRMOVE(arg + 1, arg + 5);
2837 }
2838 arg++;
2839 }
2840 if (*arg != NUL)
2841 *arg++ = NUL;
2842 arg = skipwhite(arg);
2843
2844 return arg;
2845}
2846
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002847/*
2848 * Get the information about a menu item in mode 'which'
2849 */
2850 static int
Yegappan Lakshmanan51491ad2021-09-30 19:00:00 +01002851menuitem_getinfo(char_u *menu_name, vimmenu_T *menu, int modes, dict_T *dict)
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002852{
2853 int status;
Yegappan Lakshmanan51491ad2021-09-30 19:00:00 +01002854 list_T *l;
2855
2856 if (*menu_name == NUL)
2857 {
2858 // Return all the top-level menus
2859 vimmenu_T *topmenu;
2860
2861 l = list_alloc();
2862 if (l == NULL)
2863 return FAIL;
2864
2865 dict_add_list(dict, "submenus", l);
2866 // get all the children. Skip PopUp[nvoci].
2867 for (topmenu = menu; topmenu != NULL; topmenu = topmenu->next)
2868 if (!menu_is_hidden(topmenu->dname))
2869 list_append_string(l, topmenu->dname, -1);
2870 return OK;
2871 }
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002872
2873 if (menu_is_tearoff(menu->dname)) // skip tearoff menu item
2874 return OK;
2875
2876 status = dict_add_string(dict, "name", menu->name);
2877 if (status == OK)
2878 status = dict_add_string(dict, "display", menu->dname);
2879 if (status == OK && menu->actext != NULL)
2880 status = dict_add_string(dict, "accel", menu->actext);
2881 if (status == OK)
2882 status = dict_add_number(dict, "priority", menu->priority);
2883 if (status == OK)
2884 status = dict_add_string(dict, "modes",
2885 get_menu_mode_str(menu->modes));
2886#ifdef FEAT_TOOLBAR
2887 if (status == OK && menu->iconfile != NULL)
2888 status = dict_add_string(dict, "icon", menu->iconfile);
2889 if (status == OK && menu->iconidx >= 0)
2890 status = dict_add_number(dict, "iconidx", menu->iconidx);
2891#endif
2892 if (status == OK)
2893 {
2894 char_u buf[NUMBUFLEN];
2895
2896 if (has_mbyte)
2897 buf[utf_char2bytes(menu->mnemonic, buf)] = NUL;
2898 else
2899 {
2900 buf[0] = (char_u)menu->mnemonic;
2901 buf[1] = NUL;
2902 }
2903 status = dict_add_string(dict, "shortcut", buf);
2904 }
2905 if (status == OK && menu->children == NULL)
2906 {
2907 int bit;
2908
2909 // Get the first mode in which the menu is available
Bram Moolenaar56cb3372020-03-16 20:04:41 +01002910 for (bit = 0; bit < MENU_MODES && !((1 << bit) & modes); bit++)
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002911 ;
Bram Moolenaar56cb3372020-03-16 20:04:41 +01002912 if (bit < MENU_MODES) // just in case, avoid Coverity warning
2913 {
2914 if (menu->strings[bit] != NULL)
Bram Moolenaar292b90d2020-03-18 15:23:16 +01002915 {
2916 char_u *tofree = NULL;
2917
Bram Moolenaar56cb3372020-03-16 20:04:41 +01002918 status = dict_add_string(dict, "rhs",
2919 *menu->strings[bit] == NUL
Bram Moolenaar292b90d2020-03-18 15:23:16 +01002920 ? (char_u *)"<Nop>"
2921 : (tofree = str2special_save(
zeertzjqcdc83932022-09-12 13:38:41 +01002922 menu->strings[bit], FALSE, FALSE)));
Bram Moolenaar292b90d2020-03-18 15:23:16 +01002923 vim_free(tofree);
2924 }
Bram Moolenaar56cb3372020-03-16 20:04:41 +01002925 if (status == OK)
2926 status = dict_add_bool(dict, "noremenu",
2927 menu->noremap[bit] == REMAP_NONE);
2928 if (status == OK)
2929 status = dict_add_bool(dict, "script",
2930 menu->noremap[bit] == REMAP_SCRIPT);
2931 if (status == OK)
2932 status = dict_add_bool(dict, "silent", menu->silent[bit]);
2933 if (status == OK)
2934 status = dict_add_bool(dict, "enabled",
2935 ((menu->enabled & (1 << bit)) != 0));
2936 }
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002937 }
Bram Moolenaar56cb3372020-03-16 20:04:41 +01002938
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002939 // If there are submenus, add all the submenu display names
2940 if (status == OK && menu->children != NULL)
2941 {
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002942 vimmenu_T *child;
2943
Yegappan Lakshmanan51491ad2021-09-30 19:00:00 +01002944 l = list_alloc();
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002945 if (l == NULL)
2946 return FAIL;
2947
2948 dict_add_list(dict, "submenus", l);
2949 child = menu->children;
2950 while (child)
2951 {
2952 if (!menu_is_tearoff(child->dname)) // skip tearoff menu
2953 list_append_string(l, child->dname, -1);
2954 child = child->next;
2955 }
2956 }
2957
2958 return status;
2959}
2960
2961/*
2962 * "menu_info()" function
2963 * Return information about a menu (including all the child menus)
2964 */
2965 void
2966f_menu_info(typval_T *argvars, typval_T *rettv)
2967{
2968 char_u *menu_name;
2969 char_u *which;
2970 int modes;
2971 char_u *saved_name;
2972 char_u *name;
2973 vimmenu_T *menu;
2974 dict_T *retdict;
2975
Bram Moolenaar93a10962022-06-16 11:42:09 +01002976 if (rettv_dict_alloc(rettv) == FAIL)
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002977 return;
2978 retdict = rettv->vval.v_dict;
2979
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02002980 if (in_vim9script()
2981 && (check_for_string_arg(argvars, 0) == FAIL
2982 || check_for_opt_string_arg(argvars, 1) == FAIL))
2983 return;
2984
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002985 menu_name = tv_get_string_chk(&argvars[0]);
2986 if (menu_name == NULL)
2987 return;
2988
2989 // menu mode
2990 if (argvars[1].v_type != VAR_UNKNOWN)
2991 which = tv_get_string_chk(&argvars[1]);
2992 else
2993 which = (char_u *)""; // Default is modes for "menu"
2994 if (which == NULL)
2995 return;
2996
2997 modes = get_menu_cmd_modes(which, *which == '!', NULL, NULL);
2998
2999 // Locate the specified menu or menu item
3000 menu = *get_root_menu(menu_name);
3001 saved_name = vim_strsave(menu_name);
3002 if (saved_name == NULL)
3003 return;
3004 if (*saved_name != NUL)
3005 {
3006 char_u *p;
3007
3008 name = saved_name;
3009 while (*name)
3010 {
3011 // Find in the menu hierarchy
3012 p = menu_name_skip(name);
3013 while (menu != NULL)
3014 {
3015 if (menu_name_equal(name, menu))
3016 break;
3017 menu = menu->next;
3018 }
3019 if (menu == NULL || *p == NUL)
3020 break;
3021 menu = menu->children;
3022 name = p;
3023 }
3024 }
3025 vim_free(saved_name);
3026
3027 if (menu == NULL) // specified menu not found
3028 return;
3029
3030 if (menu->modes & modes)
Yegappan Lakshmanan51491ad2021-09-30 19:00:00 +01003031 menuitem_getinfo(menu_name, menu, modes, retdict);
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01003032}
3033
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01003034#endif // FEAT_MENU