blob: 88b8b84594ede2820e1d64a938d25141f4eb61c4 [file] [log] [blame]
Bram Moolenaaredf3f972016-08-29 22:49:24 +02001/* vi:set ts=8 sts=4 sw=4 noet:
Bram Moolenaar071d4272004-06-13 20:20:40 +00002 *
3 * VIM - Vi IMproved by Bram Moolenaar
4 * GUI/Motif support by Robert Webb
5 *
6 * Do ":help uganda" in Vim to read copying and usage conditions.
7 * Do ":help credits" in Vim to see a list of people who contributed.
8 * See README.txt for an overview of the Vim source code.
9 */
10
11/*
12 * Code for menus. Used for the GUI and 'wildmenu'.
13 */
14
15#include "vim.h"
16
17#if defined(FEAT_MENU) || defined(PROTO)
18
Bram Moolenaar4ba37b52019-12-04 21:57:43 +010019#define MENUDEPTH 10 // maximum depth of menus
Bram Moolenaar071d4272004-06-13 20:20:40 +000020
Bram Moolenaar4f974752019-02-17 17:44:42 +010021#ifdef FEAT_GUI_MSWIN
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010022static int add_menu_path(char_u *, vimmenu_T *, int *, char_u *, int);
Bram Moolenaar071d4272004-06-13 20:20:40 +000023#else
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010024static int add_menu_path(char_u *, vimmenu_T *, int *, char_u *);
Bram Moolenaar071d4272004-06-13 20:20:40 +000025#endif
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010026static int menu_nable_recurse(vimmenu_T *menu, char_u *name, int modes, int enable);
27static int remove_menu(vimmenu_T **, char_u *, int, int silent);
28static void free_menu(vimmenu_T **menup);
29static void free_menu_string(vimmenu_T *, int);
30static int show_menus(char_u *, int);
31static void show_menus_recursive(vimmenu_T *, int, int);
Bram Moolenaar5843f5f2019-08-20 20:13:45 +020032static char_u *menu_name_skip(char_u *name);
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010033static int menu_name_equal(char_u *name, vimmenu_T *menu);
34static int menu_namecmp(char_u *name, char_u *mname);
35static int get_menu_cmd_modes(char_u *, int, int *, int *);
36static char_u *popup_mode_name(char_u *name, int idx);
37static char_u *menu_text(char_u *text, int *mnemonic, char_u **actext);
Bram Moolenaar071d4272004-06-13 20:20:40 +000038
Bram Moolenaar4f974752019-02-17 17:44:42 +010039#if defined(FEAT_GUI_MSWIN) && defined(FEAT_TEAROFF)
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010040static void gui_create_tearoffs_recurse(vimmenu_T *menu, const char_u *pname, int *pri_tab, int pri_idx);
41static void gui_add_tearoff(char_u *tearpath, int *pri_tab, int pri_idx);
42static void gui_destroy_tearoffs_recurse(vimmenu_T *menu);
Bram Moolenaar071d4272004-06-13 20:20:40 +000043static int s_tearoffs = FALSE;
44#endif
45
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010046static int menu_is_hidden(char_u *name);
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010047static int menu_is_tearoff(char_u *name);
Bram Moolenaar071d4272004-06-13 20:20:40 +000048
49#if defined(FEAT_MULTI_LANG) || defined(FEAT_TOOLBAR)
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010050static char_u *menu_skip_part(char_u *p);
Bram Moolenaar071d4272004-06-13 20:20:40 +000051#endif
52#ifdef FEAT_MULTI_LANG
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010053static char_u *menutrans_lookup(char_u *name, int len);
54static void menu_unescape_name(char_u *p);
Bram Moolenaar071d4272004-06-13 20:20:40 +000055#endif
56
Bram Moolenaar92b8b2d2016-01-29 22:36:45 +010057static char_u *menu_translate_tab_and_shift(char_u *arg_start);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +020058
Bram Moolenaar4ba37b52019-12-04 21:57:43 +010059// The character for each menu mode
Bram Moolenaar4c5d8152018-10-19 22:36:53 +020060static char *menu_mode_chars[] = {"n", "v", "s", "o", "i", "c", "tl", "t"};
Bram Moolenaar071d4272004-06-13 20:20:40 +000061
Bram Moolenaar071d4272004-06-13 20:20:40 +000062#ifdef FEAT_TOOLBAR
63static const char *toolbar_names[] =
64{
65 /* 0 */ "New", "Open", "Save", "Undo", "Redo",
66 /* 5 */ "Cut", "Copy", "Paste", "Print", "Help",
67 /* 10 */ "Find", "SaveAll", "SaveSesn", "NewSesn", "LoadSesn",
68 /* 15 */ "RunScript", "Replace", "WinClose", "WinMax", "WinMin",
69 /* 20 */ "WinSplit", "Shell", "FindPrev", "FindNext", "FindHelp",
70 /* 25 */ "Make", "TagJump", "RunCtags", "WinVSplit", "WinMaxWidth",
71 /* 30 */ "WinMinWidth", "Exit"
72};
K.Takataeeec2542021-06-02 13:28:16 +020073# define TOOLBAR_NAME_COUNT ARRAY_LENGTH(toolbar_names)
Bram Moolenaar071d4272004-06-13 20:20:40 +000074#endif
75
76/*
Bram Moolenaar1b9645d2017-09-17 23:03:31 +020077 * Return TRUE if "name" is a window toolbar menu name.
78 */
79 static int
80menu_is_winbar(char_u *name)
81{
Bram Moolenaar378daf82017-09-23 23:58:28 +020082 return (STRNCMP(name, "WinBar", 6) == 0);
Bram Moolenaar1b9645d2017-09-17 23:03:31 +020083}
84
85 int
86winbar_height(win_T *wp)
87{
88 if (wp->w_winbar != NULL && wp->w_winbar->children != NULL)
89 return 1;
90 return 0;
91}
92
93 static vimmenu_T **
94get_root_menu(char_u *name)
95{
96 if (menu_is_winbar(name))
97 return &curwin->w_winbar;
98 return &root_menu;
99}
100
101/*
Bram Moolenaar071d4272004-06-13 20:20:40 +0000102 * Do the :menu command and relatives.
103 */
104 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100105ex_menu(
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100106 exarg_T *eap) // Ex command arguments
Bram Moolenaar071d4272004-06-13 20:20:40 +0000107{
108 char_u *menu_path;
109 int modes;
110 char_u *map_to;
111 int noremap;
112 int silent = FALSE;
Bram Moolenaar8b2d9c42006-05-03 21:28:47 +0000113 int special = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000114 int unmenu;
115 char_u *map_buf;
116 char_u *arg;
117 char_u *p;
118 int i;
Bram Moolenaar241a8aa2005-12-06 20:04:44 +0000119#if defined(FEAT_GUI) && !defined(FEAT_GUI_GTK)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000120 int old_menu_height;
Bram Moolenaar4f974752019-02-17 17:44:42 +0100121# if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_MSWIN)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000122 int old_toolbar_height;
123# endif
124#endif
125 int pri_tab[MENUDEPTH + 1];
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100126 int enable = MAYBE; // TRUE for "menu enable", FALSE for "menu
127 // disable
Bram Moolenaar071d4272004-06-13 20:20:40 +0000128#ifdef FEAT_TOOLBAR
129 char_u *icon = NULL;
130#endif
131 vimmenu_T menuarg;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200132 vimmenu_T **root_menu_ptr;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000133
134 modes = get_menu_cmd_modes(eap->cmd, eap->forceit, &noremap, &unmenu);
135 arg = eap->arg;
136
137 for (;;)
138 {
139 if (STRNCMP(arg, "<script>", 8) == 0)
140 {
141 noremap = REMAP_SCRIPT;
142 arg = skipwhite(arg + 8);
143 continue;
144 }
145 if (STRNCMP(arg, "<silent>", 8) == 0)
146 {
147 silent = TRUE;
148 arg = skipwhite(arg + 8);
149 continue;
150 }
Bram Moolenaar8b2d9c42006-05-03 21:28:47 +0000151 if (STRNCMP(arg, "<special>", 9) == 0)
152 {
153 special = TRUE;
154 arg = skipwhite(arg + 9);
155 continue;
156 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000157 break;
158 }
159
160
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100161 // Locate an optional "icon=filename" argument.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000162 if (STRNCMP(arg, "icon=", 5) == 0)
163 {
164 arg += 5;
165#ifdef FEAT_TOOLBAR
166 icon = arg;
167#endif
168 while (*arg != NUL && *arg != ' ')
169 {
170 if (*arg == '\\')
Bram Moolenaar8c8de832008-06-24 22:58:06 +0000171 STRMOVE(arg, arg + 1);
Bram Moolenaar91acfff2017-03-12 19:22:36 +0100172 MB_PTR_ADV(arg);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000173 }
174 if (*arg != NUL)
175 {
176 *arg++ = NUL;
177 arg = skipwhite(arg);
178 }
179 }
180
181 /*
182 * Fill in the priority table.
183 */
184 for (p = arg; *p; ++p)
185 if (!VIM_ISDIGIT(*p) && *p != '.')
186 break;
Bram Moolenaar1c465442017-03-12 20:10:05 +0100187 if (VIM_ISWHITE(*p))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000188 {
Bram Moolenaar1c465442017-03-12 20:10:05 +0100189 for (i = 0; i < MENUDEPTH && !VIM_ISWHITE(*arg); ++i)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000190 {
191 pri_tab[i] = getdigits(&arg);
192 if (pri_tab[i] == 0)
193 pri_tab[i] = 500;
194 if (*arg == '.')
195 ++arg;
196 }
197 arg = skipwhite(arg);
198 }
199 else if (eap->addr_count && eap->line2 != 0)
200 {
201 pri_tab[0] = eap->line2;
202 i = 1;
203 }
204 else
205 i = 0;
206 while (i < MENUDEPTH)
207 pri_tab[i++] = 500;
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100208 pri_tab[MENUDEPTH] = -1; // mark end of the table
Bram Moolenaar071d4272004-06-13 20:20:40 +0000209
210 /*
211 * Check for "disable" or "enable" argument.
212 */
Bram Moolenaar1c465442017-03-12 20:10:05 +0100213 if (STRNCMP(arg, "enable", 6) == 0 && VIM_ISWHITE(arg[6]))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000214 {
215 enable = TRUE;
216 arg = skipwhite(arg + 6);
217 }
Bram Moolenaar1c465442017-03-12 20:10:05 +0100218 else if (STRNCMP(arg, "disable", 7) == 0 && VIM_ISWHITE(arg[7]))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000219 {
220 enable = FALSE;
221 arg = skipwhite(arg + 7);
222 }
223
224 /*
225 * If there is no argument, display all menus.
226 */
227 if (*arg == NUL)
228 {
229 show_menus(arg, modes);
230 return;
231 }
232
233#ifdef FEAT_TOOLBAR
234 /*
235 * Need to get the toolbar icon index before doing the translation.
236 */
237 menuarg.iconidx = -1;
238 menuarg.icon_builtin = FALSE;
239 if (menu_is_toolbar(arg))
240 {
241 menu_path = menu_skip_part(arg);
242 if (*menu_path == '.')
243 {
244 p = menu_skip_part(++menu_path);
245 if (STRNCMP(menu_path, "BuiltIn", 7) == 0)
246 {
247 if (skipdigits(menu_path + 7) == p)
248 {
249 menuarg.iconidx = atoi((char *)menu_path + 7);
Bram Moolenaaraf0167f2009-05-16 15:31:32 +0000250 if (menuarg.iconidx >= (int)TOOLBAR_NAME_COUNT)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000251 menuarg.iconidx = -1;
252 else
253 menuarg.icon_builtin = TRUE;
254 }
255 }
256 else
257 {
Bram Moolenaaraf0167f2009-05-16 15:31:32 +0000258 for (i = 0; i < (int)TOOLBAR_NAME_COUNT; ++i)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000259 if (STRNCMP(toolbar_names[i], menu_path, p - menu_path)
260 == 0)
261 {
262 menuarg.iconidx = i;
263 break;
264 }
265 }
266 }
267 }
268#endif
269
Bram Moolenaar071d4272004-06-13 20:20:40 +0000270 menu_path = arg;
271 if (*menu_path == '.')
272 {
Bram Moolenaar436b5ad2021-12-31 22:49:24 +0000273 semsg(_(e_invalid_argument_str), menu_path);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000274 goto theend;
275 }
276
Bram Moolenaar70b11cd2010-05-14 22:24:40 +0200277 map_to = menu_translate_tab_and_shift(arg);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000278
279 /*
280 * If there is only a menu name, display menus with that name.
281 */
282 if (*map_to == NUL && !unmenu && enable == MAYBE)
283 {
284 show_menus(menu_path, modes);
285 goto theend;
286 }
287 else if (*map_to != NUL && (unmenu || enable != MAYBE))
288 {
Bram Moolenaar74409f62022-01-01 15:58:22 +0000289 semsg(_(e_trailing_characters_str), map_to);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000290 goto theend;
291 }
Bram Moolenaar241a8aa2005-12-06 20:04:44 +0000292#if defined(FEAT_GUI) && !(defined(FEAT_GUI_GTK) || defined(FEAT_GUI_PHOTON))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000293 old_menu_height = gui.menu_height;
Bram Moolenaar4f974752019-02-17 17:44:42 +0100294# if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_MSWIN)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000295 old_toolbar_height = gui.toolbar_height;
296# endif
297#endif
298
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200299 root_menu_ptr = get_root_menu(menu_path);
300 if (root_menu_ptr == &curwin->w_winbar)
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100301 // Assume the window toolbar menu will change.
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200302 redraw_later(NOT_VALID);
303
Bram Moolenaar071d4272004-06-13 20:20:40 +0000304 if (enable != MAYBE)
305 {
306 /*
307 * Change sensitivity of the menu.
308 * For the PopUp menu, remove a menu for each mode separately.
309 * Careful: menu_nable_recurse() changes menu_path.
310 */
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100311 if (STRCMP(menu_path, "*") == 0) // meaning: do all menus
Bram Moolenaar071d4272004-06-13 20:20:40 +0000312 menu_path = (char_u *)"";
313
314 if (menu_is_popup(menu_path))
315 {
316 for (i = 0; i < MENU_INDEX_TIP; ++i)
317 if (modes & (1 << i))
318 {
319 p = popup_mode_name(menu_path, i);
320 if (p != NULL)
321 {
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200322 menu_nable_recurse(*root_menu_ptr, p, MENU_ALL_MODES,
Bram Moolenaar071d4272004-06-13 20:20:40 +0000323 enable);
324 vim_free(p);
325 }
326 }
327 }
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200328 menu_nable_recurse(*root_menu_ptr, menu_path, modes, enable);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000329 }
330 else if (unmenu)
331 {
332 /*
333 * Delete menu(s).
334 */
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100335 if (STRCMP(menu_path, "*") == 0) // meaning: remove all menus
Bram Moolenaar071d4272004-06-13 20:20:40 +0000336 menu_path = (char_u *)"";
337
338 /*
339 * For the PopUp menu, remove a menu for each mode separately.
340 */
341 if (menu_is_popup(menu_path))
342 {
343 for (i = 0; i < MENU_INDEX_TIP; ++i)
344 if (modes & (1 << i))
345 {
346 p = popup_mode_name(menu_path, i);
347 if (p != NULL)
348 {
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200349 remove_menu(root_menu_ptr, p, MENU_ALL_MODES, TRUE);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000350 vim_free(p);
351 }
352 }
353 }
354
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100355 // Careful: remove_menu() changes menu_path
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200356 remove_menu(root_menu_ptr, menu_path, modes, FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000357 }
358 else
359 {
360 /*
361 * Add menu(s).
362 * Replace special key codes.
363 */
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100364 if (STRICMP(map_to, "<nop>") == 0) // "<Nop>" means nothing
Bram Moolenaar071d4272004-06-13 20:20:40 +0000365 {
366 map_to = (char_u *)"";
367 map_buf = NULL;
368 }
Bram Moolenaar3fdfa4a2004-10-07 21:02:47 +0000369 else if (modes & MENU_TIP_MODE)
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100370 map_buf = NULL; // Menu tips are plain text.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000371 else
Bram Moolenaar459fd782019-10-13 16:43:39 +0200372 map_to = replace_termcodes(map_to, &map_buf,
373 REPTERM_DO_LT | (special ? REPTERM_SPECIAL : 0), NULL);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000374 menuarg.modes = modes;
375#ifdef FEAT_TOOLBAR
376 menuarg.iconfile = icon;
377#endif
378 menuarg.noremap[0] = noremap;
379 menuarg.silent[0] = silent;
380 add_menu_path(menu_path, &menuarg, pri_tab, map_to
Bram Moolenaar4f974752019-02-17 17:44:42 +0100381#ifdef FEAT_GUI_MSWIN
Bram Moolenaar071d4272004-06-13 20:20:40 +0000382 , TRUE
383#endif
384 );
385
386 /*
387 * For the PopUp menu, add a menu for each mode separately.
388 */
389 if (menu_is_popup(menu_path))
390 {
391 for (i = 0; i < MENU_INDEX_TIP; ++i)
392 if (modes & (1 << i))
393 {
394 p = popup_mode_name(menu_path, i);
395 if (p != NULL)
396 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100397 // Include all modes, to make ":amenu" work
Bram Moolenaar071d4272004-06-13 20:20:40 +0000398 menuarg.modes = modes;
399#ifdef FEAT_TOOLBAR
400 menuarg.iconfile = NULL;
401 menuarg.iconidx = -1;
402 menuarg.icon_builtin = FALSE;
403#endif
404 add_menu_path(p, &menuarg, pri_tab, map_to
Bram Moolenaar4f974752019-02-17 17:44:42 +0100405#ifdef FEAT_GUI_MSWIN
Bram Moolenaar071d4272004-06-13 20:20:40 +0000406 , TRUE
407#endif
408 );
409 vim_free(p);
410 }
411 }
412 }
413
414 vim_free(map_buf);
415 }
416
Bram Moolenaar241a8aa2005-12-06 20:04:44 +0000417#if defined(FEAT_GUI) && !(defined(FEAT_GUI_GTK))
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100418 // If the menubar height changed, resize the window
Bram Moolenaar071d4272004-06-13 20:20:40 +0000419 if (gui.in_use
420 && (gui.menu_height != old_menu_height
Bram Moolenaar4f974752019-02-17 17:44:42 +0100421# if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_MSWIN)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000422 || gui.toolbar_height != old_toolbar_height
423# endif
424 ))
Bram Moolenaar04a9d452006-03-27 21:03:26 +0000425 gui_set_shellsize(FALSE, FALSE, RESIZE_VERT);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000426#endif
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200427 if (root_menu_ptr == &curwin->w_winbar)
428 {
429 int h = winbar_height(curwin);
430
431 if (h != curwin->w_winbar_height)
432 {
433 if (h == 0)
434 ++curwin->w_height;
435 else if (curwin->w_height > 0)
436 --curwin->w_height;
437 curwin->w_winbar_height = h;
438 }
439 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000440
441theend:
Bram Moolenaar071d4272004-06-13 20:20:40 +0000442 ;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000443}
444
445/*
446 * Add the menu with the given name to the menu hierarchy
447 */
448 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100449add_menu_path(
450 char_u *menu_path,
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100451 vimmenu_T *menuarg, // passes modes, iconfile, iconidx,
452 // icon_builtin, silent[0], noremap[0]
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100453 int *pri_tab,
454 char_u *call_data
Bram Moolenaar4f974752019-02-17 17:44:42 +0100455#ifdef FEAT_GUI_MSWIN
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100456 , int addtearoff // may add tearoff item
Bram Moolenaar071d4272004-06-13 20:20:40 +0000457#endif
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100458 )
Bram Moolenaar071d4272004-06-13 20:20:40 +0000459{
460 char_u *path_name;
461 int modes = menuarg->modes;
462 vimmenu_T **menup;
463 vimmenu_T *menu = NULL;
464 vimmenu_T *parent;
465 vimmenu_T **lower_pri;
466 char_u *p;
467 char_u *name;
468 char_u *dname;
469 char_u *next_name;
470 int i;
471 int c;
Bram Moolenaar7871a502010-05-14 21:19:23 +0200472 int d;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000473#ifdef FEAT_GUI
474 int idx;
475 int new_idx;
476#endif
477 int pri_idx = 0;
478 int old_modes = 0;
479 int amenu;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +0200480#ifdef FEAT_MULTI_LANG
481 char_u *en_name;
482 char_u *map_to = NULL;
483#endif
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200484 vimmenu_T **root_menu_ptr;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000485
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100486 // Make a copy so we can stuff around with it, since it could be const
Bram Moolenaar071d4272004-06-13 20:20:40 +0000487 path_name = vim_strsave(menu_path);
488 if (path_name == NULL)
489 return FAIL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200490 root_menu_ptr = get_root_menu(menu_path);
491 menup = root_menu_ptr;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000492 parent = NULL;
493 name = path_name;
494 while (*name)
495 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100496 // Get name of this element in the menu hierarchy, and the simplified
497 // name (without mnemonic and accelerator text).
Bram Moolenaar071d4272004-06-13 20:20:40 +0000498 next_name = menu_name_skip(name);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +0200499#ifdef FEAT_MULTI_LANG
Bram Moolenaar442b4222010-05-24 21:34:22 +0200500 map_to = menutrans_lookup(name, (int)STRLEN(name));
Bram Moolenaar70b11cd2010-05-14 22:24:40 +0200501 if (map_to != NULL)
502 {
503 en_name = name;
504 name = map_to;
505 }
506 else
507 en_name = NULL;
508#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000509 dname = menu_text(name, NULL, NULL);
Bram Moolenaar18a0b122006-08-16 13:55:16 +0000510 if (dname == NULL)
511 goto erret;
512 if (*dname == NUL)
513 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100514 // Only a mnemonic or accelerator is not valid.
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100515 emsg(_("E792: Empty menu name"));
Bram Moolenaar18a0b122006-08-16 13:55:16 +0000516 goto erret;
517 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000518
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100519 // See if it's already there
Bram Moolenaar071d4272004-06-13 20:20:40 +0000520 lower_pri = menup;
521#ifdef FEAT_GUI
522 idx = 0;
523 new_idx = 0;
524#endif
525 menu = *menup;
526 while (menu != NULL)
527 {
528 if (menu_name_equal(name, menu) || menu_name_equal(dname, menu))
529 {
530 if (*next_name == NUL && menu->children != NULL)
531 {
532 if (!sys_menu)
Bram Moolenaard88be5b2022-01-04 19:57:55 +0000533 emsg(_(e_menu_path_must_not_lead_to_sub_menu));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000534 goto erret;
535 }
536 if (*next_name != NUL && menu->children == NULL
Bram Moolenaar4f974752019-02-17 17:44:42 +0100537#ifdef FEAT_GUI_MSWIN
Bram Moolenaar071d4272004-06-13 20:20:40 +0000538 && addtearoff
539#endif
540 )
541 {
542 if (!sys_menu)
Bram Moolenaareaaac012022-01-02 17:00:40 +0000543 emsg(_(e_part_of_menu_item_path_is_not_sub_menu));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000544 goto erret;
545 }
546 break;
547 }
548 menup = &menu->next;
549
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100550 // Count menus, to find where this one needs to be inserted.
551 // Ignore menus that are not in the menubar (PopUp and Toolbar)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000552 if (parent != NULL || menu_is_menubar(menu->name))
553 {
554#ifdef FEAT_GUI
555 ++idx;
556#endif
557 if (menu->priority <= pri_tab[pri_idx])
558 {
559 lower_pri = menup;
560#ifdef FEAT_GUI
561 new_idx = idx;
562#endif
563 }
564 }
565 menu = menu->next;
566 }
567
568 if (menu == NULL)
569 {
570 if (*next_name == NUL && parent == NULL)
571 {
Bram Moolenaareaaac012022-01-02 17:00:40 +0000572 emsg(_(e_must_not_add_menu_items_directly_to_menu_bar));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000573 goto erret;
574 }
575
576 if (menu_is_separator(dname) && *next_name != NUL)
577 {
Bram Moolenaareaaac012022-01-02 17:00:40 +0000578 emsg(_(e_separator_cannot_be_part_of_menu_path));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000579 goto erret;
580 }
581
Dominique Pelleaf4a61a2021-12-27 17:21:41 +0000582 // Not already there, so let's add it
Bram Moolenaare809a4e2019-07-04 17:35:05 +0200583 menu = ALLOC_CLEAR_ONE(vimmenu_T);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000584 if (menu == NULL)
585 goto erret;
586
587 menu->modes = modes;
588 menu->enabled = MENU_ALL_MODES;
589 menu->name = vim_strsave(name);
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100590 // separate mnemonic and accelerator text from actual menu name
Bram Moolenaar071d4272004-06-13 20:20:40 +0000591 menu->dname = menu_text(name, &menu->mnemonic, &menu->actext);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +0200592#ifdef FEAT_MULTI_LANG
593 if (en_name != NULL)
594 {
595 menu->en_name = vim_strsave(en_name);
596 menu->en_dname = menu_text(en_name, NULL, NULL);
597 }
598 else
599 {
600 menu->en_name = NULL;
601 menu->en_dname = NULL;
602 }
603#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000604 menu->priority = pri_tab[pri_idx];
605 menu->parent = parent;
606#ifdef FEAT_GUI_MOTIF
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100607 menu->sensitive = TRUE; // the default
Bram Moolenaar071d4272004-06-13 20:20:40 +0000608#endif
609#ifdef FEAT_BEVAL_TIP
610 menu->tip = NULL;
611#endif
612#ifdef FEAT_GUI_ATHENA
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100613 menu->image = None; // X-Windows definition for NULL
Bram Moolenaar071d4272004-06-13 20:20:40 +0000614#endif
615
616 /*
617 * Add after menu that has lower priority.
618 */
619 menu->next = *lower_pri;
620 *lower_pri = menu;
621
622 old_modes = 0;
623
624#ifdef FEAT_TOOLBAR
625 menu->iconidx = menuarg->iconidx;
626 menu->icon_builtin = menuarg->icon_builtin;
627 if (*next_name == NUL && menuarg->iconfile != NULL)
628 menu->iconfile = vim_strsave(menuarg->iconfile);
629#endif
Bram Moolenaar4f974752019-02-17 17:44:42 +0100630#if defined(FEAT_GUI_MSWIN) && defined(FEAT_TEAROFF)
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100631 // the tearoff item must be present in the modes of each item.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000632 if (parent != NULL && menu_is_tearoff(parent->children->dname))
633 parent->children->modes |= modes;
634#endif
635 }
636 else
637 {
638 old_modes = menu->modes;
639
640 /*
641 * If this menu option was previously only available in other
642 * modes, then make sure it's available for this one now
643 * Also enable a menu when it's created or changed.
644 */
Bram Moolenaar4f974752019-02-17 17:44:42 +0100645#ifdef FEAT_GUI_MSWIN
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100646 // If adding a tearbar (addtearoff == FALSE) don't update modes
Bram Moolenaar071d4272004-06-13 20:20:40 +0000647 if (addtearoff)
648#endif
649 {
650 menu->modes |= modes;
651 menu->enabled |= modes;
652 }
653 }
654
655#ifdef FEAT_GUI
656 /*
657 * Add the menu item when it's used in one of the modes, but not when
658 * only a tooltip is defined.
659 */
660 if ((old_modes & MENU_ALL_MODES) == 0
661 && (menu->modes & MENU_ALL_MODES) != 0)
662 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100663 if (gui.in_use) // Otherwise it will be added when GUI starts
Bram Moolenaar071d4272004-06-13 20:20:40 +0000664 {
665 if (*next_name == NUL)
666 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100667 // Real menu item, not sub-menu
Bram Moolenaar071d4272004-06-13 20:20:40 +0000668 gui_mch_add_menu_item(menu, new_idx);
669
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100670 // Want to update menus now even if mode not changed
Bram Moolenaar071d4272004-06-13 20:20:40 +0000671 force_menu_update = TRUE;
672 }
673 else
674 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100675 // Sub-menu (not at end of path yet)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000676 gui_mch_add_menu(menu, new_idx);
677 }
678 }
679
Bram Moolenaar4f974752019-02-17 17:44:42 +0100680# if defined(FEAT_GUI_MSWIN) & defined(FEAT_TEAROFF)
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100681 // When adding a new submenu, may add a tearoff item
Bram Moolenaar071d4272004-06-13 20:20:40 +0000682 if ( addtearoff
683 && *next_name
684 && vim_strchr(p_go, GO_TEAROFF) != NULL
Bram Moolenaar310c32e2019-11-29 23:15:25 +0100685 && menu_is_menubar(name)
686# ifdef VIMDLL
687 && (gui.in_use || gui.starting)
688# endif
689 )
Bram Moolenaar071d4272004-06-13 20:20:40 +0000690 {
691 char_u *tearpath;
692
693 /*
694 * The pointers next_name & path_name refer to a string with
695 * \'s and ^V's stripped out. But menu_path is a "raw"
696 * string, so we must correct for special characters.
697 */
Bram Moolenaar964b3742019-05-24 18:54:09 +0200698 tearpath = alloc(STRLEN(menu_path) + TEAR_LEN + 2);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000699 if (tearpath != NULL)
700 {
701 char_u *s;
702 int idx;
703
704 STRCPY(tearpath, menu_path);
705 idx = (int)(next_name - path_name - 1);
Bram Moolenaar91acfff2017-03-12 19:22:36 +0100706 for (s = tearpath; *s && s < tearpath + idx; MB_PTR_ADV(s))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000707 {
708 if ((*s == '\\' || *s == Ctrl_V) && s[1])
709 {
710 ++idx;
711 ++s;
712 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000713 }
714 tearpath[idx] = NUL;
715 gui_add_tearoff(tearpath, pri_tab, pri_idx);
716 vim_free(tearpath);
717 }
718 }
719# endif
720 }
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100721#endif // FEAT_GUI
Bram Moolenaar071d4272004-06-13 20:20:40 +0000722
723 menup = &menu->children;
724 parent = menu;
725 name = next_name;
Bram Moolenaard23a8232018-02-10 18:45:26 +0100726 VIM_CLEAR(dname);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000727 if (pri_tab[pri_idx + 1] != -1)
728 ++pri_idx;
729 }
730 vim_free(path_name);
731
732 /*
733 * Only add system menu items which have not been defined yet.
734 * First check if this was an ":amenu".
735 */
736 amenu = ((modes & (MENU_NORMAL_MODE | MENU_INSERT_MODE)) ==
737 (MENU_NORMAL_MODE | MENU_INSERT_MODE));
738 if (sys_menu)
739 modes &= ~old_modes;
740
741 if (menu != NULL && modes)
742 {
743#ifdef FEAT_GUI
744 menu->cb = gui_menu_cb;
745#endif
746 p = (call_data == NULL) ? NULL : vim_strsave(call_data);
747
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100748 // loop over all modes, may add more than one
Bram Moolenaar071d4272004-06-13 20:20:40 +0000749 for (i = 0; i < MENU_MODES; ++i)
750 {
751 if (modes & (1 << i))
752 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100753 // free any old menu
Bram Moolenaar071d4272004-06-13 20:20:40 +0000754 free_menu_string(menu, i);
755
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100756 // For "amenu", may insert an extra character.
757 // Don't do this if adding a tearbar (addtearoff == FALSE).
758 // Don't do this for "<Nop>".
Bram Moolenaar071d4272004-06-13 20:20:40 +0000759 c = 0;
Bram Moolenaar7871a502010-05-14 21:19:23 +0200760 d = 0;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000761 if (amenu && call_data != NULL && *call_data != NUL
Bram Moolenaar4f974752019-02-17 17:44:42 +0100762#ifdef FEAT_GUI_MSWIN
Bram Moolenaar071d4272004-06-13 20:20:40 +0000763 && addtearoff
764#endif
765 )
766 {
767 switch (1 << i)
768 {
769 case MENU_VISUAL_MODE:
Bram Moolenaarb3656ed2006-03-20 21:59:49 +0000770 case MENU_SELECT_MODE:
Bram Moolenaar071d4272004-06-13 20:20:40 +0000771 case MENU_OP_PENDING_MODE:
772 case MENU_CMDLINE_MODE:
773 c = Ctrl_C;
774 break;
775 case MENU_INSERT_MODE:
Bram Moolenaar7871a502010-05-14 21:19:23 +0200776 c = Ctrl_BSL;
777 d = Ctrl_O;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000778 break;
779 }
780 }
781
Bram Moolenaar7871a502010-05-14 21:19:23 +0200782 if (c != 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000783 {
Bram Moolenaar964b3742019-05-24 18:54:09 +0200784 menu->strings[i] = alloc(STRLEN(call_data) + 5);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000785 if (menu->strings[i] != NULL)
786 {
787 menu->strings[i][0] = c;
Bram Moolenaar7871a502010-05-14 21:19:23 +0200788 if (d == 0)
789 STRCPY(menu->strings[i] + 1, call_data);
790 else
791 {
792 menu->strings[i][1] = d;
793 STRCPY(menu->strings[i] + 2, call_data);
794 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000795 if (c == Ctrl_C)
796 {
Bram Moolenaara93fa7e2006-04-17 22:14:47 +0000797 int len = (int)STRLEN(menu->strings[i]);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000798
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100799 // Append CTRL-\ CTRL-G to obey 'insertmode'.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000800 menu->strings[i][len] = Ctrl_BSL;
801 menu->strings[i][len + 1] = Ctrl_G;
802 menu->strings[i][len + 2] = NUL;
803 }
804 }
805 }
806 else
807 menu->strings[i] = p;
808 menu->noremap[i] = menuarg->noremap[0];
809 menu->silent[i] = menuarg->silent[0];
810 }
811 }
Bram Moolenaar4f974752019-02-17 17:44:42 +0100812#if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_MSWIN) \
Bram Moolenaarc3719bd2017-11-18 22:13:31 +0100813 && (defined(FEAT_BEVAL_GUI) || defined(FEAT_GUI_GTK))
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100814 // Need to update the menu tip.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000815 if (modes & MENU_TIP_MODE)
816 gui_mch_menu_set_tip(menu);
817#endif
818 }
819 return OK;
820
821erret:
822 vim_free(path_name);
823 vim_free(dname);
Bram Moolenaar18a0b122006-08-16 13:55:16 +0000824
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100825 // Delete any empty submenu we added before discovering the error. Repeat
826 // for higher levels.
Bram Moolenaar18a0b122006-08-16 13:55:16 +0000827 while (parent != NULL && parent->children == NULL)
828 {
829 if (parent->parent == NULL)
Bram Moolenaar1b9645d2017-09-17 23:03:31 +0200830 menup = root_menu_ptr;
Bram Moolenaar18a0b122006-08-16 13:55:16 +0000831 else
832 menup = &parent->parent->children;
833 for ( ; *menup != NULL && *menup != parent; menup = &((*menup)->next))
834 ;
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100835 if (*menup == NULL) // safety check
Bram Moolenaar18a0b122006-08-16 13:55:16 +0000836 break;
837 parent = parent->parent;
838 free_menu(menup);
839 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000840 return FAIL;
841}
842
843/*
844 * Set the (sub)menu with the given name to enabled or disabled.
845 * Called recursively.
846 */
847 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100848menu_nable_recurse(
849 vimmenu_T *menu,
850 char_u *name,
851 int modes,
852 int enable)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000853{
854 char_u *p;
855
856 if (menu == NULL)
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100857 return OK; // Got to bottom of hierarchy
Bram Moolenaar071d4272004-06-13 20:20:40 +0000858
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100859 // Get name of this element in the menu hierarchy
Bram Moolenaar071d4272004-06-13 20:20:40 +0000860 p = menu_name_skip(name);
861
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100862 // Find the menu
Bram Moolenaar071d4272004-06-13 20:20:40 +0000863 while (menu != NULL)
864 {
865 if (*name == NUL || *name == '*' || menu_name_equal(name, menu))
866 {
867 if (*p != NUL)
868 {
869 if (menu->children == NULL)
870 {
Bram Moolenaareaaac012022-01-02 17:00:40 +0000871 emsg(_(e_part_of_menu_item_path_is_not_sub_menu));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000872 return FAIL;
873 }
874 if (menu_nable_recurse(menu->children, p, modes, enable)
875 == FAIL)
876 return FAIL;
877 }
878 else
879 if (enable)
880 menu->enabled |= modes;
881 else
882 menu->enabled &= ~modes;
883
884 /*
885 * When name is empty, we are doing all menu items for the given
886 * modes, so keep looping, otherwise we are just doing the named
887 * menu item (which has been found) so break here.
888 */
889 if (*name != NUL && *name != '*')
890 break;
891 }
892 menu = menu->next;
893 }
894 if (*name != NUL && *name != '*' && menu == NULL)
895 {
Bram Moolenaareaaac012022-01-02 17:00:40 +0000896 semsg(_(e_no_menu_str), name);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000897 return FAIL;
898 }
899
900#ifdef FEAT_GUI
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100901 // Want to update menus now even if mode not changed
Bram Moolenaar071d4272004-06-13 20:20:40 +0000902 force_menu_update = TRUE;
903#endif
904
905 return OK;
906}
907
908/*
909 * Remove the (sub)menu with the given name from the menu hierarchy
910 * Called recursively.
911 */
912 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +0100913remove_menu(
914 vimmenu_T **menup,
915 char_u *name,
916 int modes,
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100917 int silent) // don't give error messages
Bram Moolenaar071d4272004-06-13 20:20:40 +0000918{
919 vimmenu_T *menu;
920 vimmenu_T *child;
921 char_u *p;
922
923 if (*menup == NULL)
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100924 return OK; // Got to bottom of hierarchy
Bram Moolenaar071d4272004-06-13 20:20:40 +0000925
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100926 // Get name of this element in the menu hierarchy
Bram Moolenaar071d4272004-06-13 20:20:40 +0000927 p = menu_name_skip(name);
928
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100929 // Find the menu
Bram Moolenaar071d4272004-06-13 20:20:40 +0000930 while ((menu = *menup) != NULL)
931 {
932 if (*name == NUL || menu_name_equal(name, menu))
933 {
934 if (*p != NUL && menu->children == NULL)
935 {
936 if (!silent)
Bram Moolenaareaaac012022-01-02 17:00:40 +0000937 emsg(_(e_part_of_menu_item_path_is_not_sub_menu));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000938 return FAIL;
939 }
940 if ((menu->modes & modes) != 0x0)
941 {
Bram Moolenaar4f974752019-02-17 17:44:42 +0100942#if defined(FEAT_GUI_MSWIN) & defined(FEAT_TEAROFF)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000943 /*
944 * If we are removing all entries for this menu,MENU_ALL_MODES,
945 * Then kill any tearoff before we start
946 */
947 if (*p == NUL && modes == MENU_ALL_MODES)
948 {
949 if (IsWindow(menu->tearoff_handle))
950 DestroyWindow(menu->tearoff_handle);
951 }
952#endif
953 if (remove_menu(&menu->children, p, modes, silent) == FAIL)
954 return FAIL;
955 }
956 else if (*name != NUL)
957 {
958 if (!silent)
Bram Moolenaar3a846e62022-01-01 16:21:00 +0000959 emsg(_(e_menu_only_exists_in_another_mode));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000960 return FAIL;
961 }
962
963 /*
964 * When name is empty, we are removing all menu items for the given
965 * modes, so keep looping, otherwise we are just removing the named
966 * menu item (which has been found) so break here.
967 */
968 if (*name != NUL)
969 break;
970
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100971 // Remove the menu item for the given mode[s]. If the menu item
972 // is no longer valid in ANY mode, delete it
Bram Moolenaar071d4272004-06-13 20:20:40 +0000973 menu->modes &= ~modes;
974 if (modes & MENU_TIP_MODE)
975 free_menu_string(menu, MENU_INDEX_TIP);
976 if ((menu->modes & MENU_ALL_MODES) == 0)
977 free_menu(menup);
978 else
979 menup = &menu->next;
980 }
981 else
982 menup = &menu->next;
983 }
984 if (*name != NUL)
985 {
986 if (menu == NULL)
987 {
988 if (!silent)
Bram Moolenaareaaac012022-01-02 17:00:40 +0000989 semsg(_(e_no_menu_str), name);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000990 return FAIL;
991 }
992
993
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100994 // Recalculate modes for menu based on the new updated children
Bram Moolenaar071d4272004-06-13 20:20:40 +0000995 menu->modes &= ~modes;
Bram Moolenaar4f974752019-02-17 17:44:42 +0100996#if defined(FEAT_GUI_MSWIN) & defined(FEAT_TEAROFF)
Dominique Pelleaf4a61a2021-12-27 17:21:41 +0000997 if ((s_tearoffs) && (menu->children != NULL)) // there's a tear bar.
Bram Moolenaar4ba37b52019-12-04 21:57:43 +0100998 child = menu->children->next; // don't count tearoff bar
Bram Moolenaar071d4272004-06-13 20:20:40 +0000999 else
1000#endif
1001 child = menu->children;
1002 for ( ; child != NULL; child = child->next)
1003 menu->modes |= child->modes;
1004 if (modes & MENU_TIP_MODE)
1005 {
1006 free_menu_string(menu, MENU_INDEX_TIP);
Bram Moolenaar4f974752019-02-17 17:44:42 +01001007#if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_MSWIN) \
Bram Moolenaarc3719bd2017-11-18 22:13:31 +01001008 && (defined(FEAT_BEVAL_GUI) || defined(FEAT_GUI_GTK))
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001009 // Need to update the menu tip.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001010 if (gui.in_use)
1011 gui_mch_menu_set_tip(menu);
1012#endif
1013 }
1014 if ((menu->modes & MENU_ALL_MODES) == 0)
1015 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001016 // The menu item is no longer valid in ANY mode, so delete it
Bram Moolenaar4f974752019-02-17 17:44:42 +01001017#if defined(FEAT_GUI_MSWIN) & defined(FEAT_TEAROFF)
Dominique Pelleaf4a61a2021-12-27 17:21:41 +00001018 if (s_tearoffs && menu->children != NULL) // there's a tear bar.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001019 free_menu(&menu->children);
1020#endif
1021 *menup = menu;
1022 free_menu(menup);
1023 }
1024 }
1025
1026 return OK;
1027}
1028
1029/*
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001030 * Remove the WinBar menu from window "wp".
1031 */
1032 void
1033remove_winbar(win_T *wp)
1034{
1035 remove_menu(&wp->w_winbar, (char_u *)"", MENU_ALL_MODES, TRUE);
1036 vim_free(wp->w_winbar_items);
1037}
1038
1039/*
Bram Moolenaar071d4272004-06-13 20:20:40 +00001040 * Free the given menu structure and remove it from the linked list.
1041 */
1042 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001043free_menu(vimmenu_T **menup)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001044{
1045 int i;
1046 vimmenu_T *menu;
1047
1048 menu = *menup;
1049
1050#ifdef FEAT_GUI
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001051 // Free machine specific menu structures (only when already created)
1052 // Also may rebuild a tearoff'ed menu
Bram Moolenaar071d4272004-06-13 20:20:40 +00001053 if (gui.in_use)
1054 gui_mch_destroy_menu(menu);
1055#endif
1056
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001057 // Don't change *menup until after calling gui_mch_destroy_menu(). The
1058 // MacOS code needs the original structure to properly delete the menu.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001059 *menup = menu->next;
1060 vim_free(menu->name);
1061 vim_free(menu->dname);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001062#ifdef FEAT_MULTI_LANG
1063 vim_free(menu->en_name);
1064 vim_free(menu->en_dname);
1065#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001066 vim_free(menu->actext);
1067#ifdef FEAT_TOOLBAR
1068 vim_free(menu->iconfile);
Bram Moolenaarbee0c5b2005-02-07 22:03:36 +00001069# ifdef FEAT_GUI_MOTIF
1070 vim_free(menu->xpm_fname);
1071# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001072#endif
1073 for (i = 0; i < MENU_MODES; i++)
1074 free_menu_string(menu, i);
1075 vim_free(menu);
1076
1077#ifdef FEAT_GUI
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001078 // Want to update menus now even if mode not changed
Bram Moolenaar071d4272004-06-13 20:20:40 +00001079 force_menu_update = TRUE;
1080#endif
1081}
1082
1083/*
1084 * Free the menu->string with the given index.
1085 */
1086 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001087free_menu_string(vimmenu_T *menu, int idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001088{
1089 int count = 0;
1090 int i;
1091
1092 for (i = 0; i < MENU_MODES; i++)
1093 if (menu->strings[i] == menu->strings[idx])
1094 count++;
1095 if (count == 1)
1096 vim_free(menu->strings[idx]);
1097 menu->strings[idx] = NULL;
1098}
1099
1100/*
1101 * Show the mapping associated with a menu item or hierarchy in a sub-menu.
1102 */
1103 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001104show_menus(char_u *path_name, int modes)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001105{
1106 char_u *p;
1107 char_u *name;
1108 vimmenu_T *menu;
1109 vimmenu_T *parent = NULL;
1110
Bram Moolenaar071d4272004-06-13 20:20:40 +00001111 name = path_name = vim_strsave(path_name);
1112 if (path_name == NULL)
1113 return FAIL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001114 menu = *get_root_menu(path_name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001115
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001116 // First, find the (sub)menu with the given name
Bram Moolenaar071d4272004-06-13 20:20:40 +00001117 while (*name)
1118 {
1119 p = menu_name_skip(name);
1120 while (menu != NULL)
1121 {
1122 if (menu_name_equal(name, menu))
1123 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001124 // Found menu
Bram Moolenaar071d4272004-06-13 20:20:40 +00001125 if (*p != NUL && menu->children == NULL)
1126 {
Bram Moolenaareaaac012022-01-02 17:00:40 +00001127 emsg(_(e_part_of_menu_item_path_is_not_sub_menu));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001128 vim_free(path_name);
1129 return FAIL;
1130 }
1131 else if ((menu->modes & modes) == 0x0)
1132 {
Bram Moolenaar3a846e62022-01-01 16:21:00 +00001133 emsg(_(e_menu_only_exists_in_another_mode));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001134 vim_free(path_name);
1135 return FAIL;
1136 }
1137 break;
1138 }
1139 menu = menu->next;
1140 }
1141 if (menu == NULL)
1142 {
Bram Moolenaareaaac012022-01-02 17:00:40 +00001143 semsg(_(e_no_menu_str), name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001144 vim_free(path_name);
1145 return FAIL;
1146 }
1147 name = p;
1148 parent = menu;
1149 menu = menu->children;
1150 }
Bram Moolenaaracbd4422008-08-17 21:44:45 +00001151 vim_free(path_name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001152
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001153 // Now we have found the matching menu, and we list the mappings
1154 // Highlight title
Bram Moolenaar32526b32019-01-19 17:43:09 +01001155 msg_puts_title(_("\n--- Menus ---"));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001156
1157 show_menus_recursive(parent, modes, 0);
1158 return OK;
1159}
1160
1161/*
1162 * Recursively show the mappings associated with the menus under the given one
1163 */
1164 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001165show_menus_recursive(vimmenu_T *menu, int modes, int depth)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001166{
1167 int i;
1168 int bit;
1169
1170 if (menu != NULL && (menu->modes & modes) == 0x0)
1171 return;
1172
1173 if (menu != NULL)
1174 {
1175 msg_putchar('\n');
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001176 if (got_int) // "q" hit for "--more--"
Bram Moolenaar071d4272004-06-13 20:20:40 +00001177 return;
1178 for (i = 0; i < depth; i++)
Bram Moolenaar32526b32019-01-19 17:43:09 +01001179 msg_puts(" ");
Bram Moolenaar071d4272004-06-13 20:20:40 +00001180 if (menu->priority)
1181 {
1182 msg_outnum((long)menu->priority);
Bram Moolenaar32526b32019-01-19 17:43:09 +01001183 msg_puts(" ");
Bram Moolenaar071d4272004-06-13 20:20:40 +00001184 }
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001185 // Same highlighting as for directories!?
Bram Moolenaar8820b482017-03-16 17:23:31 +01001186 msg_outtrans_attr(menu->name, HL_ATTR(HLF_D));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001187 }
1188
1189 if (menu != NULL && menu->children == NULL)
1190 {
1191 for (bit = 0; bit < MENU_MODES; bit++)
1192 if ((menu->modes & modes & (1 << bit)) != 0)
1193 {
1194 msg_putchar('\n');
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001195 if (got_int) // "q" hit for "--more--"
Bram Moolenaar071d4272004-06-13 20:20:40 +00001196 return;
1197 for (i = 0; i < depth + 2; i++)
Bram Moolenaar32526b32019-01-19 17:43:09 +01001198 msg_puts(" ");
1199 msg_puts(menu_mode_chars[bit]);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001200 if (menu->noremap[bit] == REMAP_NONE)
1201 msg_putchar('*');
1202 else if (menu->noremap[bit] == REMAP_SCRIPT)
1203 msg_putchar('&');
1204 else
1205 msg_putchar(' ');
1206 if (menu->silent[bit])
1207 msg_putchar('s');
1208 else
1209 msg_putchar(' ');
1210 if ((menu->modes & menu->enabled & (1 << bit)) == 0)
1211 msg_putchar('-');
1212 else
1213 msg_putchar(' ');
Bram Moolenaar32526b32019-01-19 17:43:09 +01001214 msg_puts(" ");
Bram Moolenaar071d4272004-06-13 20:20:40 +00001215 if (*menu->strings[bit] == NUL)
Bram Moolenaar32526b32019-01-19 17:43:09 +01001216 msg_puts_attr("<Nop>", HL_ATTR(HLF_8));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001217 else
Bram Moolenaar725310d2019-04-24 23:08:23 +02001218 msg_outtrans_special(menu->strings[bit], FALSE, 0);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001219 }
1220 }
1221 else
1222 {
1223 if (menu == NULL)
1224 {
1225 menu = root_menu;
1226 depth--;
1227 }
1228 else
1229 menu = menu->children;
1230
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001231 // recursively show all children. Skip PopUp[nvoci].
Bram Moolenaar071d4272004-06-13 20:20:40 +00001232 for (; menu != NULL && !got_int; menu = menu->next)
1233 if (!menu_is_hidden(menu->dname))
1234 show_menus_recursive(menu, modes, depth + 1);
1235 }
1236}
1237
Bram Moolenaar071d4272004-06-13 20:20:40 +00001238/*
1239 * Used when expanding menu names.
1240 */
1241static vimmenu_T *expand_menu = NULL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001242static vimmenu_T *expand_menu_alt = NULL;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001243static int expand_modes = 0x0;
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001244static int expand_emenu; // TRUE for ":emenu" command
Bram Moolenaar071d4272004-06-13 20:20:40 +00001245
1246/*
1247 * Work out what to complete when doing command line completion of menu names.
1248 */
1249 char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001250set_context_in_menu_cmd(
1251 expand_T *xp,
1252 char_u *cmd,
1253 char_u *arg,
1254 int forceit)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001255{
1256 char_u *after_dot;
1257 char_u *p;
1258 char_u *path_name = NULL;
1259 char_u *name;
1260 int unmenu;
1261 vimmenu_T *menu;
1262 int expand_menus;
1263
1264 xp->xp_context = EXPAND_UNSUCCESSFUL;
1265
1266
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001267 // Check for priority numbers, enable and disable
Bram Moolenaar071d4272004-06-13 20:20:40 +00001268 for (p = arg; *p; ++p)
1269 if (!VIM_ISDIGIT(*p) && *p != '.')
1270 break;
1271
Bram Moolenaar1c465442017-03-12 20:10:05 +01001272 if (!VIM_ISWHITE(*p))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001273 {
1274 if (STRNCMP(arg, "enable", 6) == 0
Bram Moolenaar1c465442017-03-12 20:10:05 +01001275 && (arg[6] == NUL || VIM_ISWHITE(arg[6])))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001276 p = arg + 6;
1277 else if (STRNCMP(arg, "disable", 7) == 0
Bram Moolenaar1c465442017-03-12 20:10:05 +01001278 && (arg[7] == NUL || VIM_ISWHITE(arg[7])))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001279 p = arg + 7;
1280 else
1281 p = arg;
1282 }
1283
Bram Moolenaar1c465442017-03-12 20:10:05 +01001284 while (*p != NUL && VIM_ISWHITE(*p))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001285 ++p;
1286
1287 arg = after_dot = p;
1288
Bram Moolenaar1c465442017-03-12 20:10:05 +01001289 for (; *p && !VIM_ISWHITE(*p); ++p)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001290 {
1291 if ((*p == '\\' || *p == Ctrl_V) && p[1] != NUL)
1292 p++;
1293 else if (*p == '.')
1294 after_dot = p + 1;
1295 }
1296
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001297 // ":tearoff" and ":popup" only use menus, not entries
Bram Moolenaar071d4272004-06-13 20:20:40 +00001298 expand_menus = !((*cmd == 't' && cmd[1] == 'e') || *cmd == 'p');
1299 expand_emenu = (*cmd == 'e');
Bram Moolenaar1c465442017-03-12 20:10:05 +01001300 if (expand_menus && VIM_ISWHITE(*p))
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001301 return NULL; // TODO: check for next command?
1302 if (*p == NUL) // Complete the menu name
Bram Moolenaar071d4272004-06-13 20:20:40 +00001303 {
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001304 int try_alt_menu = TRUE;
1305
Bram Moolenaar071d4272004-06-13 20:20:40 +00001306 /*
1307 * With :unmenu, you only want to match menus for the appropriate mode.
1308 * With :menu though you might want to add a menu with the same name as
1309 * one in another mode, so match menus from other modes too.
1310 */
1311 expand_modes = get_menu_cmd_modes(cmd, forceit, NULL, &unmenu);
1312 if (!unmenu)
1313 expand_modes = MENU_ALL_MODES;
1314
1315 menu = root_menu;
1316 if (after_dot != arg)
1317 {
Bram Moolenaar964b3742019-05-24 18:54:09 +02001318 path_name = alloc(after_dot - arg);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001319 if (path_name == NULL)
1320 return NULL;
Bram Moolenaarce0842a2005-07-18 21:58:11 +00001321 vim_strncpy(path_name, arg, after_dot - arg - 1);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001322 }
1323 name = path_name;
1324 while (name != NULL && *name)
1325 {
1326 p = menu_name_skip(name);
1327 while (menu != NULL)
1328 {
1329 if (menu_name_equal(name, menu))
1330 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001331 // Found menu
Bram Moolenaar071d4272004-06-13 20:20:40 +00001332 if ((*p != NUL && menu->children == NULL)
1333 || ((menu->modes & expand_modes) == 0x0))
1334 {
1335 /*
1336 * Menu path continues, but we have reached a leaf.
1337 * Or menu exists only in another mode.
1338 */
1339 vim_free(path_name);
1340 return NULL;
1341 }
1342 break;
1343 }
1344 menu = menu->next;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001345 if (menu == NULL && try_alt_menu)
1346 {
1347 menu = curwin->w_winbar;
1348 try_alt_menu = FALSE;
1349 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001350 }
1351 if (menu == NULL)
1352 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001353 // No menu found with the name we were looking for
Bram Moolenaar071d4272004-06-13 20:20:40 +00001354 vim_free(path_name);
1355 return NULL;
1356 }
1357 name = p;
1358 menu = menu->children;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001359 try_alt_menu = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001360 }
Bram Moolenaareb3593b2006-04-22 22:33:57 +00001361 vim_free(path_name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001362
1363 xp->xp_context = expand_menus ? EXPAND_MENUNAMES : EXPAND_MENUS;
1364 xp->xp_pattern = after_dot;
1365 expand_menu = menu;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001366 if (expand_menu == root_menu)
1367 expand_menu_alt = curwin->w_winbar;
1368 else
1369 expand_menu_alt = NULL;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001370 }
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001371 else // We're in the mapping part
Bram Moolenaar071d4272004-06-13 20:20:40 +00001372 xp->xp_context = EXPAND_NOTHING;
1373 return NULL;
1374}
1375
1376/*
1377 * Function given to ExpandGeneric() to obtain the list of (sub)menus (not
1378 * entries).
1379 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001380 char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001381get_menu_name(expand_T *xp UNUSED, int idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001382{
1383 static vimmenu_T *menu = NULL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001384 static int did_alt_menu = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001385 char_u *str;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001386#ifdef FEAT_MULTI_LANG
1387 static int should_advance = FALSE;
1388#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001389
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001390 if (idx == 0) // first call: start at first item
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001391 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001392 menu = expand_menu;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001393 did_alt_menu = FALSE;
Bram Moolenaar41375642010-05-16 12:49:27 +02001394#ifdef FEAT_MULTI_LANG
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001395 should_advance = FALSE;
Bram Moolenaar41375642010-05-16 12:49:27 +02001396#endif
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001397 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001398
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001399 // Skip PopUp[nvoci].
Bram Moolenaar071d4272004-06-13 20:20:40 +00001400 while (menu != NULL && (menu_is_hidden(menu->dname)
1401 || menu_is_separator(menu->dname)
1402 || menu_is_tearoff(menu->dname)
1403 || menu->children == NULL))
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001404 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001405 menu = menu->next;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001406 if (menu == NULL && !did_alt_menu)
1407 {
1408 menu = expand_menu_alt;
1409 did_alt_menu = TRUE;
1410 }
1411 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001412
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001413 if (menu == NULL) // at end of linked list
Bram Moolenaar071d4272004-06-13 20:20:40 +00001414 return NULL;
1415
1416 if (menu->modes & expand_modes)
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001417#ifdef FEAT_MULTI_LANG
1418 if (should_advance)
1419 str = menu->en_dname;
1420 else
1421 {
1422#endif
1423 str = menu->dname;
1424#ifdef FEAT_MULTI_LANG
1425 if (menu->en_dname == NULL)
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001426 should_advance = TRUE;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001427 }
1428#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001429 else
1430 str = (char_u *)"";
1431
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001432#ifdef FEAT_MULTI_LANG
1433 if (should_advance)
1434#endif
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001435 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001436 // Advance to next menu entry.
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001437 menu = menu->next;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001438 if (menu == NULL && !did_alt_menu)
1439 {
1440 menu = expand_menu_alt;
1441 did_alt_menu = TRUE;
1442 }
1443 }
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001444
1445#ifdef FEAT_MULTI_LANG
1446 should_advance = !should_advance;
1447#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001448
1449 return str;
1450}
1451
1452/*
1453 * Function given to ExpandGeneric() to obtain the list of menus and menu
1454 * entries.
1455 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001456 char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001457get_menu_names(expand_T *xp UNUSED, int idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001458{
1459 static vimmenu_T *menu = NULL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001460 static int did_alt_menu = FALSE;
Bram Moolenaaref9d6aa2011-04-11 16:56:35 +02001461#define TBUFFER_LEN 256
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001462 static char_u tbuffer[TBUFFER_LEN]; //hack
Bram Moolenaar071d4272004-06-13 20:20:40 +00001463 char_u *str;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001464#ifdef FEAT_MULTI_LANG
1465 static int should_advance = FALSE;
1466#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001467
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001468 if (idx == 0) // first call: start at first item
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001469 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001470 menu = expand_menu;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001471 did_alt_menu = FALSE;
Bram Moolenaar41375642010-05-16 12:49:27 +02001472#ifdef FEAT_MULTI_LANG
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001473 should_advance = FALSE;
Bram Moolenaar41375642010-05-16 12:49:27 +02001474#endif
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001475 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001476
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001477 // Skip Browse-style entries, popup menus and separators.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001478 while (menu != NULL
1479 && ( menu_is_hidden(menu->dname)
1480 || (expand_emenu && menu_is_separator(menu->dname))
1481 || menu_is_tearoff(menu->dname)
1482#ifndef FEAT_BROWSE
1483 || menu->dname[STRLEN(menu->dname) - 1] == '.'
1484#endif
1485 ))
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001486 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001487 menu = menu->next;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001488 if (menu == NULL && !did_alt_menu)
1489 {
1490 menu = expand_menu_alt;
1491 did_alt_menu = TRUE;
1492 }
1493 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001494
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001495 if (menu == NULL) // at end of linked list
Bram Moolenaar071d4272004-06-13 20:20:40 +00001496 return NULL;
1497
1498 if (menu->modes & expand_modes)
1499 {
1500 if (menu->children != NULL)
1501 {
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001502#ifdef FEAT_MULTI_LANG
1503 if (should_advance)
Bram Moolenaaref9d6aa2011-04-11 16:56:35 +02001504 vim_strncpy(tbuffer, menu->en_dname, TBUFFER_LEN - 2);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001505 else
1506 {
1507#endif
Bram Moolenaaref9d6aa2011-04-11 16:56:35 +02001508 vim_strncpy(tbuffer, menu->dname, TBUFFER_LEN - 2);
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001509#ifdef FEAT_MULTI_LANG
1510 if (menu->en_dname == NULL)
1511 should_advance = TRUE;
1512 }
1513#endif
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001514 // hack on menu separators: use a 'magic' char for the separator
1515 // so that '.' in names gets escaped properly
Bram Moolenaar071d4272004-06-13 20:20:40 +00001516 STRCAT(tbuffer, "\001");
1517 str = tbuffer;
1518 }
1519 else
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001520#ifdef FEAT_MULTI_LANG
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001521 {
1522 if (should_advance)
1523 str = menu->en_dname;
1524 else
1525 {
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001526#endif
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001527 str = menu->dname;
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001528#ifdef FEAT_MULTI_LANG
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001529 if (menu->en_dname == NULL)
1530 should_advance = TRUE;
1531 }
1532 }
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001533#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001534 }
1535 else
1536 str = (char_u *)"";
1537
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001538#ifdef FEAT_MULTI_LANG
1539 if (should_advance)
1540#endif
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001541 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001542 // Advance to next menu entry.
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001543 menu = menu->next;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02001544 if (menu == NULL && !did_alt_menu)
1545 {
1546 menu = expand_menu_alt;
1547 did_alt_menu = TRUE;
1548 }
1549 }
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001550
1551#ifdef FEAT_MULTI_LANG
1552 should_advance = !should_advance;
1553#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001554
1555 return str;
1556}
Bram Moolenaar071d4272004-06-13 20:20:40 +00001557
1558/*
1559 * Skip over this element of the menu path and return the start of the next
1560 * element. Any \ and ^Vs are removed from the current element.
Bram Moolenaar342337a2005-07-21 21:11:17 +00001561 * "name" may be modified.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001562 */
Bram Moolenaar5843f5f2019-08-20 20:13:45 +02001563 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001564menu_name_skip(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001565{
1566 char_u *p;
1567
Bram Moolenaar91acfff2017-03-12 19:22:36 +01001568 for (p = name; *p && *p != '.'; MB_PTR_ADV(p))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001569 {
1570 if (*p == '\\' || *p == Ctrl_V)
1571 {
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001572 STRMOVE(p, p + 1);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001573 if (*p == NUL)
1574 break;
1575 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001576 }
1577 if (*p)
1578 *p++ = NUL;
1579 return p;
1580}
1581
1582/*
1583 * Return TRUE when "name" matches with menu "menu". The name is compared in
1584 * two ways: raw menu name and menu name without '&'. ignore part after a TAB.
1585 */
1586 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001587menu_name_equal(char_u *name, vimmenu_T *menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001588{
Bram Moolenaar41375642010-05-16 12:49:27 +02001589#ifdef FEAT_MULTI_LANG
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001590 if (menu->en_name != NULL
Bram Moolenaard91f7042011-01-04 17:49:32 +01001591 && (menu_namecmp(name, menu->en_name)
1592 || menu_namecmp(name, menu->en_dname)))
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001593 return TRUE;
Bram Moolenaar41375642010-05-16 12:49:27 +02001594#endif
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02001595 return menu_namecmp(name, menu->name) || menu_namecmp(name, menu->dname);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001596}
1597
1598 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001599menu_namecmp(char_u *name, char_u *mname)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001600{
1601 int i;
1602
1603 for (i = 0; name[i] != NUL && name[i] != TAB; ++i)
1604 if (name[i] != mname[i])
1605 break;
1606 return ((name[i] == NUL || name[i] == TAB)
1607 && (mname[i] == NUL || mname[i] == TAB));
1608}
1609
1610/*
1611 * Return the modes specified by the given menu command (eg :menu! returns
1612 * MENU_CMDLINE_MODE | MENU_INSERT_MODE).
1613 * If "noremap" is not NULL, then the flag it points to is set according to
1614 * whether the command is a "nore" command.
1615 * If "unmenu" is not NULL, then the flag it points to is set according to
1616 * whether the command is an "unmenu" command.
1617 */
1618 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001619get_menu_cmd_modes(
1620 char_u *cmd,
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001621 int forceit, // Was there a "!" after the command?
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001622 int *noremap,
1623 int *unmenu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001624{
1625 int modes;
1626
1627 switch (*cmd++)
1628 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001629 case 'v': // vmenu, vunmenu, vnoremenu
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001630 modes = MENU_VISUAL_MODE | MENU_SELECT_MODE;
1631 break;
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001632 case 'x': // xmenu, xunmenu, xnoremenu
Bram Moolenaar071d4272004-06-13 20:20:40 +00001633 modes = MENU_VISUAL_MODE;
1634 break;
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001635 case 's': // smenu, sunmenu, snoremenu
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001636 modes = MENU_SELECT_MODE;
1637 break;
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001638 case 'o': // omenu
Bram Moolenaar071d4272004-06-13 20:20:40 +00001639 modes = MENU_OP_PENDING_MODE;
1640 break;
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001641 case 'i': // imenu
Bram Moolenaar071d4272004-06-13 20:20:40 +00001642 modes = MENU_INSERT_MODE;
1643 break;
1644 case 't':
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001645 if (*cmd == 'l') // tlmenu, tlunmenu, tlnoremenu
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001646 {
1647 modes = MENU_TERMINAL_MODE;
1648 ++cmd;
1649 break;
1650 }
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001651 modes = MENU_TIP_MODE; // tmenu
Bram Moolenaar071d4272004-06-13 20:20:40 +00001652 break;
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001653 case 'c': // cmenu
Bram Moolenaar071d4272004-06-13 20:20:40 +00001654 modes = MENU_CMDLINE_MODE;
1655 break;
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001656 case 'a': // amenu
Bram Moolenaar071d4272004-06-13 20:20:40 +00001657 modes = MENU_INSERT_MODE | MENU_CMDLINE_MODE | MENU_NORMAL_MODE
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001658 | MENU_VISUAL_MODE | MENU_SELECT_MODE
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00001659 | MENU_OP_PENDING_MODE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001660 break;
1661 case 'n':
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001662 if (*cmd != 'o') // nmenu, not noremenu
Bram Moolenaar071d4272004-06-13 20:20:40 +00001663 {
1664 modes = MENU_NORMAL_MODE;
1665 break;
1666 }
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001667 // FALLTHROUGH
Bram Moolenaar071d4272004-06-13 20:20:40 +00001668 default:
1669 --cmd;
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001670 if (forceit) // menu!!
Bram Moolenaar071d4272004-06-13 20:20:40 +00001671 modes = MENU_INSERT_MODE | MENU_CMDLINE_MODE;
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001672 else // menu
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001673 modes = MENU_NORMAL_MODE | MENU_VISUAL_MODE | MENU_SELECT_MODE
Bram Moolenaar071d4272004-06-13 20:20:40 +00001674 | MENU_OP_PENDING_MODE;
1675 }
1676
1677 if (noremap != NULL)
1678 *noremap = (*cmd == 'n' ? REMAP_NONE : REMAP_YES);
1679 if (unmenu != NULL)
1680 *unmenu = (*cmd == 'u');
1681 return modes;
1682}
1683
1684/*
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01001685 * Return the string representation of the menu modes. Does the opposite
1686 * of get_menu_cmd_modes().
1687 */
1688 static char_u *
1689get_menu_mode_str(int modes)
1690{
1691 if ((modes & (MENU_INSERT_MODE | MENU_CMDLINE_MODE | MENU_NORMAL_MODE |
1692 MENU_VISUAL_MODE | MENU_SELECT_MODE | MENU_OP_PENDING_MODE))
1693 == (MENU_INSERT_MODE | MENU_CMDLINE_MODE | MENU_NORMAL_MODE |
1694 MENU_VISUAL_MODE | MENU_SELECT_MODE | MENU_OP_PENDING_MODE))
1695 return (char_u *)"a";
1696 if ((modes & (MENU_NORMAL_MODE | MENU_VISUAL_MODE | MENU_SELECT_MODE |
1697 MENU_OP_PENDING_MODE))
1698 == (MENU_NORMAL_MODE | MENU_VISUAL_MODE | MENU_SELECT_MODE |
1699 MENU_OP_PENDING_MODE))
1700 return (char_u *)" ";
1701 if ((modes & (MENU_INSERT_MODE | MENU_CMDLINE_MODE))
1702 == (MENU_INSERT_MODE | MENU_CMDLINE_MODE))
1703 return (char_u *)"!";
1704 if ((modes & (MENU_VISUAL_MODE | MENU_SELECT_MODE))
1705 == (MENU_VISUAL_MODE | MENU_SELECT_MODE))
1706 return (char_u *)"v";
1707 if (modes & MENU_VISUAL_MODE)
1708 return (char_u *)"x";
1709 if (modes & MENU_SELECT_MODE)
1710 return (char_u *)"s";
1711 if (modes & MENU_OP_PENDING_MODE)
1712 return (char_u *)"o";
1713 if (modes & MENU_INSERT_MODE)
1714 return (char_u *)"i";
1715 if (modes & MENU_TERMINAL_MODE)
1716 return (char_u *)"tl";
1717 if (modes & MENU_CMDLINE_MODE)
1718 return (char_u *)"c";
1719 if (modes & MENU_NORMAL_MODE)
1720 return (char_u *)"n";
1721 if (modes & MENU_TIP_MODE)
1722 return (char_u *)"t";
1723
1724 return (char_u *)"";
1725}
1726
1727/*
Bram Moolenaar071d4272004-06-13 20:20:40 +00001728 * Modify a menu name starting with "PopUp" to include the mode character.
1729 * Returns the name in allocated memory (NULL for failure).
1730 */
1731 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001732popup_mode_name(char_u *name, int idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001733{
1734 char_u *p;
1735 int len = (int)STRLEN(name);
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001736 char *mode_chars = menu_mode_chars[idx];
1737 int mode_chars_len = (int)strlen(mode_chars);
1738 int i;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001739
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001740 p = vim_strnsave(name, len + mode_chars_len);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001741 if (p != NULL)
1742 {
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001743 mch_memmove(p + 5 + mode_chars_len, p + 5, (size_t)(len - 4));
1744 for (i = 0; i < mode_chars_len; ++i)
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001745 p[5 + i] = menu_mode_chars[idx][i];
Bram Moolenaar071d4272004-06-13 20:20:40 +00001746 }
1747 return p;
1748}
1749
1750#if defined(FEAT_GUI) || defined(PROTO)
1751/*
1752 * Return the index into the menu->strings or menu->noremap arrays for the
1753 * current state. Returns MENU_INDEX_INVALID if there is no mapping for the
1754 * given menu in the current mode.
1755 */
1756 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001757get_menu_index(vimmenu_T *menu, int state)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001758{
1759 int idx;
1760
1761 if ((state & INSERT))
1762 idx = MENU_INDEX_INSERT;
1763 else if (state & CMDLINE)
1764 idx = MENU_INDEX_CMDLINE;
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001765#ifdef FEAT_TERMINAL
1766 else if (term_use_loop())
1767 idx = MENU_INDEX_TERMINAL;
1768#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001769 else if (VIsual_active)
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001770 {
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00001771 if (VIsual_select)
1772 idx = MENU_INDEX_SELECT;
1773 else
1774 idx = MENU_INDEX_VISUAL;
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001775 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001776 else if (state == HITRETURN || state == ASKMORE)
1777 idx = MENU_INDEX_CMDLINE;
1778 else if (finish_op)
1779 idx = MENU_INDEX_OP_PENDING;
1780 else if ((state & NORMAL))
1781 idx = MENU_INDEX_NORMAL;
1782 else
1783 idx = MENU_INDEX_INVALID;
1784
1785 if (idx != MENU_INDEX_INVALID && menu->strings[idx] == NULL)
1786 idx = MENU_INDEX_INVALID;
1787 return idx;
1788}
1789#endif
1790
1791/*
1792 * Duplicate the menu item text and then process to see if a mnemonic key
1793 * and/or accelerator text has been identified.
1794 * Returns a pointer to allocated memory, or NULL for failure.
1795 * If mnemonic != NULL, *mnemonic is set to the character after the first '&'.
1796 * If actext != NULL, *actext is set to the text after the first TAB.
1797 */
1798 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001799menu_text(char_u *str, int *mnemonic, char_u **actext)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001800{
1801 char_u *p;
1802 char_u *text;
1803
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001804 // Locate accelerator text, after the first TAB
Bram Moolenaar071d4272004-06-13 20:20:40 +00001805 p = vim_strchr(str, TAB);
1806 if (p != NULL)
1807 {
1808 if (actext != NULL)
1809 *actext = vim_strsave(p + 1);
Bram Moolenaar71ccd032020-06-12 22:59:11 +02001810 text = vim_strnsave(str, p - str);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001811 }
1812 else
1813 text = vim_strsave(str);
1814
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001815 // Find mnemonic characters "&a" and reduce "&&" to "&".
Bram Moolenaar071d4272004-06-13 20:20:40 +00001816 for (p = text; p != NULL; )
1817 {
1818 p = vim_strchr(p, '&');
1819 if (p != NULL)
1820 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001821 if (p[1] == NUL) // trailing "&"
Bram Moolenaar071d4272004-06-13 20:20:40 +00001822 break;
1823 if (mnemonic != NULL && p[1] != '&')
1824#if !defined(__MVS__) || defined(MOTIF390_MNEMONIC_FIXED)
1825 *mnemonic = p[1];
1826#else
1827 {
1828 /*
1829 * Well there is a bug in the Motif libraries on OS390 Unix.
1830 * The mnemonic keys needs to be converted to ASCII values
1831 * first.
1832 * This behavior has been seen in 2.8 and 2.9.
1833 */
1834 char c = p[1];
1835 __etoa_l(&c, 1);
1836 *mnemonic = c;
1837 }
1838#endif
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001839 STRMOVE(p, p + 1);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001840 p = p + 1;
1841 }
1842 }
1843 return text;
1844}
1845
1846/*
1847 * Return TRUE if "name" can be a menu in the MenuBar.
1848 */
1849 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001850menu_is_menubar(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001851{
1852 return (!menu_is_popup(name)
1853 && !menu_is_toolbar(name)
Bram Moolenaar378daf82017-09-23 23:58:28 +02001854 && !menu_is_winbar(name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001855 && *name != MNU_HIDDEN_CHAR);
1856}
1857
1858/*
1859 * Return TRUE if "name" is a popup menu name.
1860 */
1861 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001862menu_is_popup(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001863{
1864 return (STRNCMP(name, "PopUp", 5) == 0);
1865}
1866
1867#if (defined(FEAT_GUI_MOTIF) && (XmVersion <= 1002)) || defined(PROTO)
1868/*
1869 * Return TRUE if "name" is part of a popup menu.
1870 */
1871 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001872menu_is_child_of_popup(vimmenu_T *menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001873{
1874 while (menu->parent != NULL)
1875 menu = menu->parent;
1876 return menu_is_popup(menu->name);
1877}
1878#endif
1879
1880/*
1881 * Return TRUE if "name" is a toolbar menu name.
1882 */
1883 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001884menu_is_toolbar(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001885{
1886 return (STRNCMP(name, "ToolBar", 7) == 0);
1887}
1888
1889/*
1890 * Return TRUE if the name is a menu separator identifier: Starts and ends
1891 * with '-'
1892 */
1893 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001894menu_is_separator(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001895{
1896 return (name[0] == '-' && name[STRLEN(name) - 1] == '-');
1897}
1898
1899/*
1900 * Return TRUE if the menu is hidden: Starts with ']'
1901 */
1902 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001903menu_is_hidden(char_u *name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001904{
1905 return (name[0] == ']') || (menu_is_popup(name) && name[5] != NUL);
1906}
1907
Bram Moolenaar071d4272004-06-13 20:20:40 +00001908/*
1909 * Return TRUE if the menu is the tearoff menu.
1910 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001911 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001912menu_is_tearoff(char_u *name UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001913{
1914#ifdef FEAT_GUI
1915 return (STRCMP(name, TEAR_STRING) == 0);
1916#else
1917 return FALSE;
1918#endif
1919}
Bram Moolenaar071d4272004-06-13 20:20:40 +00001920
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001921#if defined(FEAT_GUI) || defined(FEAT_TERM_POPUP_MENU) || defined(PROTO)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001922
1923 static int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01001924get_menu_mode(void)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001925{
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001926#ifdef FEAT_TERMINAL
1927 if (term_use_loop())
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001928 return MENU_INDEX_TERMINAL;
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001929#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001930 if (VIsual_active)
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001931 {
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00001932 if (VIsual_select)
1933 return MENU_INDEX_SELECT;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001934 return MENU_INDEX_VISUAL;
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001935 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001936 if (State & INSERT)
1937 return MENU_INDEX_INSERT;
1938 if ((State & CMDLINE) || State == ASKMORE || State == HITRETURN)
1939 return MENU_INDEX_CMDLINE;
1940 if (finish_op)
1941 return MENU_INDEX_OP_PENDING;
1942 if (State & NORMAL)
1943 return MENU_INDEX_NORMAL;
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001944 if (State & LANGMAP) // must be a "r" command, like Insert mode
Bram Moolenaar071d4272004-06-13 20:20:40 +00001945 return MENU_INDEX_INSERT;
1946 return MENU_INDEX_INVALID;
1947}
1948
Bram Moolenaar29a2c082018-03-05 21:06:23 +01001949 int
1950get_menu_mode_flag(void)
1951{
1952 int mode = get_menu_mode();
1953
1954 if (mode == MENU_INDEX_INVALID)
1955 return 0;
1956 return 1 << mode;
1957}
1958
Bram Moolenaar071d4272004-06-13 20:20:40 +00001959/*
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001960 * Display the Special "PopUp" menu as a pop-up at the current mouse
1961 * position. The "PopUpn" menu is for Normal mode, "PopUpi" for Insert mode,
1962 * etc.
1963 */
1964 void
1965show_popupmenu(void)
1966{
1967 vimmenu_T *menu;
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001968 int menu_mode;
1969 char* mode;
1970 int mode_len;
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001971
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001972 menu_mode = get_menu_mode();
1973 if (menu_mode == MENU_INDEX_INVALID)
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001974 return;
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001975 mode = menu_mode_chars[menu_mode];
1976 mode_len = (int)strlen(mode);
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001977
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001978 apply_autocmds(EVENT_MENUPOPUP, (char_u*)mode, NULL, FALSE, curbuf);
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001979
Bram Moolenaar00d253e2020-04-06 22:13:01 +02001980 FOR_ALL_MENUS(menu)
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02001981 if (STRNCMP("PopUp", menu->name, 5) == 0 && STRNCMP(menu->name + 5, mode, mode_len) == 0)
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001982 break;
1983
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001984 // Only show a popup when it is defined and has entries
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001985 if (menu != NULL && menu->children != NULL)
1986 {
1987# if defined(FEAT_GUI)
1988 if (gui.in_use)
1989 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01001990 // Update the menus now, in case the MenuPopup autocommand did
1991 // anything.
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01001992 gui_update_menus(0);
1993 gui_mch_show_popupmenu(menu);
1994 }
1995# endif
1996# if defined(FEAT_GUI) && defined(FEAT_TERM_POPUP_MENU)
1997 else
1998# endif
1999# if defined(FEAT_TERM_POPUP_MENU)
2000 pum_show_popupmenu(menu);
2001# endif
2002 }
2003}
2004#endif
2005
2006#if defined(FEAT_GUI) || defined(PROTO)
2007
2008/*
Bram Moolenaar968bbbe2006-08-16 19:41:08 +00002009 * Check that a pointer appears in the menu tree. Used to protect from using
2010 * a menu that was deleted after it was selected but before the event was
2011 * handled.
2012 * Return OK or FAIL. Used recursively.
2013 */
2014 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002015check_menu_pointer(vimmenu_T *root, vimmenu_T *menu_to_check)
Bram Moolenaar968bbbe2006-08-16 19:41:08 +00002016{
2017 vimmenu_T *p;
2018
2019 for (p = root; p != NULL; p = p->next)
2020 if (p == menu_to_check
2021 || (p->children != NULL
2022 && check_menu_pointer(p->children, menu_to_check) == OK))
2023 return OK;
2024 return FAIL;
2025}
2026
2027/*
Bram Moolenaar071d4272004-06-13 20:20:40 +00002028 * After we have started the GUI, then we can create any menus that have been
2029 * defined. This is done once here. add_menu_path() may have already been
2030 * called to define these menus, and may be called again. This function calls
2031 * itself recursively. Should be called at the top level with:
Bram Moolenaara06ecab2016-07-16 14:47:36 +02002032 * gui_create_initial_menus(root_menu);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002033 */
2034 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002035gui_create_initial_menus(vimmenu_T *menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002036{
2037 int idx = 0;
2038
2039 while (menu != NULL)
2040 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002041 // Don't add a menu when only a tip was defined.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002042 if (menu->modes & MENU_ALL_MODES)
2043 {
2044 if (menu->children != NULL)
2045 {
2046 gui_mch_add_menu(menu, idx);
2047 gui_create_initial_menus(menu->children);
2048 }
2049 else
2050 gui_mch_add_menu_item(menu, idx);
2051 }
2052 menu = menu->next;
2053 ++idx;
2054 }
2055}
2056
2057/*
2058 * Used recursively by gui_update_menus (see below)
2059 */
2060 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002061gui_update_menus_recurse(vimmenu_T *menu, int mode)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002062{
2063 int grey;
2064
2065 while (menu)
2066 {
2067 if ((menu->modes & menu->enabled & mode)
Bram Moolenaar4f974752019-02-17 17:44:42 +01002068# if defined(FEAT_GUI_MSWIN) && defined(FEAT_TEAROFF)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002069 || menu_is_tearoff(menu->dname)
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002070# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002071 )
2072 grey = FALSE;
2073 else
2074 grey = TRUE;
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002075# ifdef FEAT_GUI_ATHENA
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002076 // Hiding menus doesn't work for Athena, it can cause a crash.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002077 gui_mch_menu_grey(menu, grey);
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002078# else
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002079 // Never hide a toplevel menu, it may make the menubar resize or
2080 // disappear. Same problem for ToolBar items.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002081 if (vim_strchr(p_go, GO_GREY) != NULL || menu->parent == NULL
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002082# ifdef FEAT_TOOLBAR
Bram Moolenaar071d4272004-06-13 20:20:40 +00002083 || menu_is_toolbar(menu->parent->name)
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002084# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002085 )
2086 gui_mch_menu_grey(menu, grey);
2087 else
2088 gui_mch_menu_hidden(menu, grey);
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002089# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002090 gui_update_menus_recurse(menu->children, mode);
2091 menu = menu->next;
2092 }
2093}
2094
2095/*
2096 * Make sure only the valid menu items appear for this mode. If
2097 * force_menu_update is not TRUE, then we only do this if the mode has changed
2098 * since last time. If "modes" is not 0, then we use these modes instead.
2099 */
2100 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002101gui_update_menus(int modes)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002102{
2103 static int prev_mode = -1;
2104 int mode = 0;
2105
2106 if (modes != 0x0)
2107 mode = modes;
2108 else
Bram Moolenaar29a2c082018-03-05 21:06:23 +01002109 mode = get_menu_mode_flag();
Bram Moolenaar071d4272004-06-13 20:20:40 +00002110
2111 if (force_menu_update || mode != prev_mode)
2112 {
2113 gui_update_menus_recurse(root_menu, mode);
2114 gui_mch_draw_menubar();
2115 prev_mode = mode;
2116 force_menu_update = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002117 }
2118}
2119
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002120# if defined(FEAT_GUI_MSWIN) || defined(FEAT_GUI_MOTIF) \
Bram Moolenaar241a8aa2005-12-06 20:04:44 +00002121 || defined(FEAT_GUI_GTK) || defined(FEAT_GUI_PHOTON) || defined(PROTO)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002122/*
2123 * Check if a key is used as a mnemonic for a toplevel menu.
2124 * Case of the key is ignored.
2125 */
2126 int
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002127gui_is_menu_shortcut(int key)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002128{
2129 vimmenu_T *menu;
2130
2131 if (key < 256)
2132 key = TOLOWER_LOC(key);
Bram Moolenaar00d253e2020-04-06 22:13:01 +02002133 FOR_ALL_MENUS(menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002134 if (menu->mnemonic == key
2135 || (menu->mnemonic < 256 && TOLOWER_LOC(menu->mnemonic) == key))
2136 return TRUE;
2137 return FALSE;
2138}
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002139# endif
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002140#endif // FEAT_GUI
Bram Moolenaar071d4272004-06-13 20:20:40 +00002141
Bram Moolenaar4f974752019-02-17 17:44:42 +01002142#if (defined(FEAT_GUI_MSWIN) && defined(FEAT_TEAROFF)) || defined(PROTO)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002143
2144/*
2145 * Deal with tearoff items that are added like a menu item.
2146 * Currently only for Win32 GUI. Others may follow later.
2147 */
2148
2149 void
2150gui_mch_toggle_tearoffs(int enable)
2151{
2152 int pri_tab[MENUDEPTH + 1];
2153 int i;
2154
2155 if (enable)
2156 {
2157 for (i = 0; i < MENUDEPTH; ++i)
2158 pri_tab[i] = 500;
2159 pri_tab[MENUDEPTH] = -1;
2160 gui_create_tearoffs_recurse(root_menu, (char_u *)"", pri_tab, 0);
2161 }
2162 else
2163 gui_destroy_tearoffs_recurse(root_menu);
2164 s_tearoffs = enable;
2165}
2166
2167/*
2168 * Recursively add tearoff items
2169 */
2170 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002171gui_create_tearoffs_recurse(
2172 vimmenu_T *menu,
2173 const char_u *pname,
2174 int *pri_tab,
2175 int pri_idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002176{
2177 char_u *newpname = NULL;
2178 int len;
2179 char_u *s;
2180 char_u *d;
2181
2182 if (pri_tab[pri_idx + 1] != -1)
2183 ++pri_idx;
2184 while (menu != NULL)
2185 {
2186 if (menu->children != NULL && menu_is_menubar(menu->name))
2187 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002188 // Add the menu name to the menu path. Insert a backslash before
2189 // dots (it's used to separate menu names).
Bram Moolenaar071d4272004-06-13 20:20:40 +00002190 len = (int)STRLEN(pname) + (int)STRLEN(menu->name);
2191 for (s = menu->name; *s; ++s)
2192 if (*s == '.' || *s == '\\')
2193 ++len;
2194 newpname = alloc(len + TEAR_LEN + 2);
2195 if (newpname != NULL)
2196 {
2197 STRCPY(newpname, pname);
2198 d = newpname + STRLEN(newpname);
2199 for (s = menu->name; *s; ++s)
2200 {
2201 if (*s == '.' || *s == '\\')
2202 *d++ = '\\';
2203 *d++ = *s;
2204 }
2205 *d = NUL;
2206
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002207 // check if tearoff already exists
Bram Moolenaar071d4272004-06-13 20:20:40 +00002208 if (STRCMP(menu->children->name, TEAR_STRING) != 0)
2209 {
2210 gui_add_tearoff(newpname, pri_tab, pri_idx - 1);
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002211 *d = NUL; // remove TEAR_STRING
Bram Moolenaar071d4272004-06-13 20:20:40 +00002212 }
2213
2214 STRCAT(newpname, ".");
2215 gui_create_tearoffs_recurse(menu->children, newpname,
2216 pri_tab, pri_idx);
2217 vim_free(newpname);
2218 }
2219 }
2220 menu = menu->next;
2221 }
2222}
2223
2224/*
2225 * Add tear-off menu item for a submenu.
2226 * "tearpath" is the menu path, and must have room to add TEAR_STRING.
2227 */
2228 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002229gui_add_tearoff(char_u *tearpath, int *pri_tab, int pri_idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002230{
2231 char_u *tbuf;
2232 int t;
2233 vimmenu_T menuarg;
2234
2235 tbuf = alloc(5 + (unsigned int)STRLEN(tearpath));
2236 if (tbuf != NULL)
2237 {
2238 tbuf[0] = K_SPECIAL;
2239 tbuf[1] = K_SECOND(K_TEAROFF);
2240 tbuf[2] = K_THIRD(K_TEAROFF);
2241 STRCPY(tbuf + 3, tearpath);
2242 STRCAT(tbuf + 3, "\r");
2243
2244 STRCAT(tearpath, ".");
2245 STRCAT(tearpath, TEAR_STRING);
2246
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002247 // Priority of tear-off is always 1
Bram Moolenaar071d4272004-06-13 20:20:40 +00002248 t = pri_tab[pri_idx + 1];
2249 pri_tab[pri_idx + 1] = 1;
2250
2251#ifdef FEAT_TOOLBAR
2252 menuarg.iconfile = NULL;
2253 menuarg.iconidx = -1;
2254 menuarg.icon_builtin = FALSE;
2255#endif
2256 menuarg.noremap[0] = REMAP_NONE;
2257 menuarg.silent[0] = TRUE;
2258
2259 menuarg.modes = MENU_ALL_MODES;
2260 add_menu_path(tearpath, &menuarg, pri_tab, tbuf, FALSE);
2261
2262 menuarg.modes = MENU_TIP_MODE;
2263 add_menu_path(tearpath, &menuarg, pri_tab,
2264 (char_u *)_("Tear off this menu"), FALSE);
2265
2266 pri_tab[pri_idx + 1] = t;
2267 vim_free(tbuf);
2268 }
2269}
2270
2271/*
2272 * Recursively destroy tearoff items
2273 */
2274 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002275gui_destroy_tearoffs_recurse(vimmenu_T *menu)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002276{
2277 while (menu)
2278 {
2279 if (menu->children)
2280 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002281 // check if tearoff exists
Bram Moolenaar071d4272004-06-13 20:20:40 +00002282 if (STRCMP(menu->children->name, TEAR_STRING) == 0)
2283 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002284 // Disconnect the item and free the memory
Bram Moolenaar071d4272004-06-13 20:20:40 +00002285 free_menu(&menu->children);
2286 }
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002287 if (menu->children != NULL) // if not the last one
Bram Moolenaar071d4272004-06-13 20:20:40 +00002288 gui_destroy_tearoffs_recurse(menu->children);
2289 }
2290 menu = menu->next;
2291 }
2292}
2293
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002294#endif // FEAT_GUI_MSWIN && FEAT_TEAROFF
Bram Moolenaar071d4272004-06-13 20:20:40 +00002295
2296/*
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002297 * Execute "menu". Use by ":emenu" and the window toolbar.
2298 * "eap" is NULL for the window toolbar.
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002299 * "mode_idx" specifies a MENU_INDEX_ value, use -1 to depend on the current
2300 * state.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002301 */
Bram Moolenaaraef8c3d2018-03-03 18:59:16 +01002302 void
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002303execute_menu(exarg_T *eap, vimmenu_T *menu, int mode_idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002304{
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002305 int idx = mode_idx;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002306
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002307 if (idx < 0)
2308 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002309 // Use the Insert mode entry when returning to Insert mode.
Bram Moolenaar9b8d6222020-12-28 18:26:00 +01002310 if (restart_edit && !current_sctx.sc_sid)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002311 {
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002312 idx = MENU_INDEX_INSERT;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002313 }
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002314#ifdef FEAT_TERMINAL
2315 else if (term_use_loop())
Bram Moolenaar071d4272004-06-13 20:20:40 +00002316 {
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002317 idx = MENU_INDEX_TERMINAL;
2318 }
2319#endif
2320 else if (VIsual_active)
2321 {
2322 idx = MENU_INDEX_VISUAL;
2323 }
2324 else if (eap != NULL && eap->addr_count)
2325 {
2326 pos_T tpos;
2327
2328 idx = MENU_INDEX_VISUAL;
2329
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002330 // GEDDES: This is not perfect - but it is a
2331 // quick way of detecting whether we are doing this from a
2332 // selection - see if the range matches up with the visual
2333 // select start and end.
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002334 if ((curbuf->b_visual.vi_start.lnum == eap->line1)
2335 && (curbuf->b_visual.vi_end.lnum) == eap->line2)
2336 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002337 // Set it up for visual mode - equivalent to gv.
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002338 VIsual_mode = curbuf->b_visual.vi_mode;
2339 tpos = curbuf->b_visual.vi_end;
2340 curwin->w_cursor = curbuf->b_visual.vi_start;
2341 curwin->w_curswant = curbuf->b_visual.vi_curswant;
2342 }
2343 else
2344 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002345 // Set it up for line-wise visual mode
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002346 VIsual_mode = 'V';
2347 curwin->w_cursor.lnum = eap->line1;
2348 curwin->w_cursor.col = 1;
2349 tpos.lnum = eap->line2;
2350 tpos.col = MAXCOL;
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002351 tpos.coladd = 0;
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002352 }
2353
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002354 // Activate visual mode
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002355 VIsual_active = TRUE;
2356 VIsual_reselect = TRUE;
2357 check_cursor();
2358 VIsual = curwin->w_cursor;
2359 curwin->w_cursor = tpos;
2360
2361 check_cursor();
2362
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002363 // Adjust the cursor to make sure it is in the correct pos
2364 // for exclusive mode
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002365 if (*p_sel == 'e' && gchar_cursor() != NUL)
2366 ++curwin->w_cursor.col;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002367 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00002368 }
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002369
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002370 // For the WinBar menu always use the Normal mode menu.
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002371 if (idx == -1 || eap == NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002372 idx = MENU_INDEX_NORMAL;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002373
Bram Moolenaarce793532019-05-05 14:19:20 +02002374 if (idx != MENU_INDEX_INVALID && menu->strings[idx] != NULL
2375 && (menu->modes & (1 << idx)))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002376 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002377 // When executing a script or function execute the commands right now.
2378 // Also for the window toolbar.
2379 // Otherwise put them in the typeahead buffer.
Bram Moolenaar9b8d6222020-12-28 18:26:00 +01002380 if (eap == NULL || current_sctx.sc_sid != 0)
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002381 {
2382 save_state_T save_state;
2383
2384 ++ex_normal_busy;
2385 if (save_current_state(&save_state))
2386 exec_normal_cmd(menu->strings[idx], menu->noremap[idx],
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002387 menu->silent[idx]);
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002388 restore_current_state(&save_state);
2389 --ex_normal_busy;
2390 }
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00002391 else
2392 ins_typebuf(menu->strings[idx], menu->noremap[idx], 0,
Bram Moolenaar071d4272004-06-13 20:20:40 +00002393 TRUE, menu->silent[idx]);
2394 }
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002395 else if (eap != NULL)
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002396 {
2397 char_u *mode;
2398
2399 switch (idx)
2400 {
2401 case MENU_INDEX_VISUAL:
2402 mode = (char_u *)"Visual";
2403 break;
2404 case MENU_INDEX_SELECT:
2405 mode = (char_u *)"Select";
2406 break;
2407 case MENU_INDEX_OP_PENDING:
2408 mode = (char_u *)"Op-pending";
2409 break;
2410 case MENU_INDEX_TERMINAL:
2411 mode = (char_u *)"Terminal";
2412 break;
2413 case MENU_INDEX_INSERT:
2414 mode = (char_u *)"Insert";
2415 break;
2416 case MENU_INDEX_CMDLINE:
2417 mode = (char_u *)"Cmdline";
2418 break;
2419 // case MENU_INDEX_TIP: cannot happen
2420 default:
2421 mode = (char_u *)"Normal";
2422 }
Bram Moolenaareaaac012022-01-02 17:00:40 +00002423 semsg(_(e_menu_not_defined_for_str_mode), mode);
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002424 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00002425}
2426
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002427/*
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002428 * Lookup a menu by the descriptor name e.g. "File.New"
2429 * Returns NULL if the menu is not found
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002430 */
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002431 static vimmenu_T *
2432menu_getbyname(char_u *name_arg)
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002433{
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002434 char_u *name;
2435 char_u *saved_name;
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002436 vimmenu_T *menu;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002437 char_u *p;
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002438 int gave_emsg = FALSE;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002439
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002440 saved_name = vim_strsave(name_arg);
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002441 if (saved_name == NULL)
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002442 return NULL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002443
2444 menu = *get_root_menu(saved_name);
2445 name = saved_name;
2446 while (*name)
2447 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002448 // Find in the menu hierarchy
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002449 p = menu_name_skip(name);
2450
2451 while (menu != NULL)
2452 {
2453 if (menu_name_equal(name, menu))
2454 {
2455 if (*p == NUL && menu->children != NULL)
2456 {
Bram Moolenaareaaac012022-01-02 17:00:40 +00002457 emsg(_(e_menu_path_must_lead_to_menu_item));
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002458 gave_emsg = TRUE;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002459 menu = NULL;
2460 }
2461 else if (*p != NUL && menu->children == NULL)
2462 {
Bram Moolenaareaaac012022-01-02 17:00:40 +00002463 emsg(_(e_part_of_menu_item_path_is_not_sub_menu));
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002464 menu = NULL;
2465 }
2466 break;
2467 }
2468 menu = menu->next;
2469 }
2470 if (menu == NULL || *p == NUL)
2471 break;
2472 menu = menu->children;
2473 name = p;
2474 }
2475 vim_free(saved_name);
2476 if (menu == NULL)
2477 {
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002478 if (!gave_emsg)
Bram Moolenaareaaac012022-01-02 17:00:40 +00002479 semsg(_(e_menu_not_found_str), name_arg);
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002480 return NULL;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002481 }
2482
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002483 return menu;
2484}
2485
2486/*
2487 * Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy and
2488 * execute it.
2489 */
2490 void
2491ex_emenu(exarg_T *eap)
2492{
2493 vimmenu_T *menu;
2494 char_u *arg = eap->arg;
2495 int mode_idx = -1;
2496
2497 if (arg[0] && VIM_ISWHITE(arg[1]))
2498 {
2499 switch (arg[0])
2500 {
2501 case 'n': mode_idx = MENU_INDEX_NORMAL; break;
2502 case 'v': mode_idx = MENU_INDEX_VISUAL; break;
2503 case 's': mode_idx = MENU_INDEX_SELECT; break;
2504 case 'o': mode_idx = MENU_INDEX_OP_PENDING; break;
2505 case 't': mode_idx = MENU_INDEX_TERMINAL; break;
2506 case 'i': mode_idx = MENU_INDEX_INSERT; break;
2507 case 'c': mode_idx = MENU_INDEX_CMDLINE; break;
Bram Moolenaar436b5ad2021-12-31 22:49:24 +00002508 default: semsg(_(e_invalid_argument_str), arg);
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002509 return;
2510 }
2511 arg = skipwhite(arg + 2);
2512 }
2513
2514 menu = menu_getbyname(arg);
2515 if (menu == NULL)
2516 return;
2517
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002518 // Found the menu, so execute.
2519 execute_menu(eap, menu, mode_idx);
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002520}
2521
2522/*
2523 * Handle a click in the window toolbar of "wp" at column "col".
2524 */
2525 void
2526winbar_click(win_T *wp, int col)
2527{
2528 int idx;
2529
2530 if (wp->w_winbar_items == NULL)
2531 return;
2532 for (idx = 0; wp->w_winbar_items[idx].wb_menu != NULL; ++idx)
2533 {
2534 winbar_item_T *item = &wp->w_winbar_items[idx];
2535
2536 if (col >= item->wb_startcol && col <= item->wb_endcol)
2537 {
Bram Moolenaard2fad672019-05-04 16:55:25 +02002538 win_T *save_curwin = NULL;
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002539 pos_T save_visual = VIsual;
2540 int save_visual_active = VIsual_active;
2541 int save_visual_select = VIsual_select;
2542 int save_visual_reselect = VIsual_reselect;
2543 int save_visual_mode = VIsual_mode;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002544
2545 if (wp != curwin)
2546 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002547 // Clicking in the window toolbar of a not-current window.
2548 // Make that window the current one and save Visual mode.
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002549 save_curwin = curwin;
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002550 VIsual_active = FALSE;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002551 curwin = wp;
2552 curbuf = curwin->w_buffer;
2553 check_cursor();
2554 }
2555
Bram Moolenaard2fad672019-05-04 16:55:25 +02002556 // Note: the command might close the current window.
Bram Moolenaar4c5d8152018-10-19 22:36:53 +02002557 execute_menu(NULL, item->wb_menu, -1);
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002558
Bram Moolenaard2fad672019-05-04 16:55:25 +02002559 if (save_curwin != NULL && win_valid(save_curwin))
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002560 {
2561 curwin = save_curwin;
2562 curbuf = curwin->w_buffer;
Bram Moolenaara21a6a92017-09-23 16:33:50 +02002563 VIsual = save_visual;
2564 VIsual_active = save_visual_active;
2565 VIsual_select = save_visual_select;
2566 VIsual_reselect = save_visual_reselect;
2567 VIsual_mode = save_visual_mode;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002568 }
Bram Moolenaard2fad672019-05-04 16:55:25 +02002569 if (!win_valid(wp))
2570 break;
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002571 }
2572 }
2573}
2574
2575#if defined(FEAT_GUI_MSWIN) || defined(FEAT_GUI_GTK) \
Bram Moolenaarb3f74062020-02-26 16:16:53 +01002576 || defined(FEAT_TERM_POPUP_MENU) || defined(FEAT_GUI_HAIKU) \
Bram Moolenaar071d4272004-06-13 20:20:40 +00002577 || defined(FEAT_BEVAL_TIP) || defined(PROTO)
2578/*
2579 * Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy.
2580 */
2581 vimmenu_T *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002582gui_find_menu(char_u *path_name)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002583{
2584 vimmenu_T *menu = NULL;
2585 char_u *name;
2586 char_u *saved_name;
2587 char_u *p;
2588
Bram Moolenaar1b9645d2017-09-17 23:03:31 +02002589 menu = *get_root_menu(path_name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002590
2591 saved_name = vim_strsave(path_name);
2592 if (saved_name == NULL)
2593 return NULL;
2594
2595 name = saved_name;
2596 while (*name)
2597 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002598 // find the end of one dot-separated name and put a NUL at the dot
Bram Moolenaar071d4272004-06-13 20:20:40 +00002599 p = menu_name_skip(name);
2600
2601 while (menu != NULL)
2602 {
Bram Moolenaard91f7042011-01-04 17:49:32 +01002603 if (menu_name_equal(name, menu))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002604 {
2605 if (menu->children == NULL)
2606 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002607 // found a menu item instead of a sub-menu
Bram Moolenaar071d4272004-06-13 20:20:40 +00002608 if (*p == NUL)
Bram Moolenaareaaac012022-01-02 17:00:40 +00002609 emsg(_(e_menu_path_must_lead_to_sub_menu));
Bram Moolenaar071d4272004-06-13 20:20:40 +00002610 else
Bram Moolenaareaaac012022-01-02 17:00:40 +00002611 emsg(_(e_part_of_menu_item_path_is_not_sub_menu));
Bram Moolenaar071d4272004-06-13 20:20:40 +00002612 menu = NULL;
2613 goto theend;
2614 }
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002615 if (*p == NUL) // found a full match
Bram Moolenaar071d4272004-06-13 20:20:40 +00002616 goto theend;
2617 break;
2618 }
2619 menu = menu->next;
2620 }
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002621 if (menu == NULL) // didn't find it
Bram Moolenaar071d4272004-06-13 20:20:40 +00002622 break;
2623
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002624 // Found a match, search the sub-menu.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002625 menu = menu->children;
2626 name = p;
2627 }
2628
2629 if (menu == NULL)
Bram Moolenaareaaac012022-01-02 17:00:40 +00002630 emsg(_(e_menu_not_found_check_menu_names));
Bram Moolenaar071d4272004-06-13 20:20:40 +00002631theend:
2632 vim_free(saved_name);
2633 return menu;
2634}
2635#endif
2636
2637#ifdef FEAT_MULTI_LANG
2638/*
2639 * Translation of menu names. Just a simple lookup table.
2640 */
2641
2642typedef struct
2643{
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002644 char_u *from; // English name
2645 char_u *from_noamp; // same, without '&'
2646 char_u *to; // translated name
Bram Moolenaar071d4272004-06-13 20:20:40 +00002647} menutrans_T;
2648
2649static garray_T menutrans_ga = {0, 0, 0, 0, NULL};
2650#endif
2651
2652/*
2653 * ":menutrans".
2654 * This function is also defined without the +multi_lang feature, in which
2655 * case the commands are ignored.
2656 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00002657 void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002658ex_menutranslate(exarg_T *eap UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002659{
2660#ifdef FEAT_MULTI_LANG
2661 char_u *arg = eap->arg;
2662 menutrans_T *tp;
2663 int i;
2664 char_u *from, *from_noamp, *to;
2665
2666 if (menutrans_ga.ga_itemsize == 0)
2667 ga_init2(&menutrans_ga, (int)sizeof(menutrans_T), 5);
2668
2669 /*
2670 * ":menutrans clear": clear all translations.
2671 */
Bram Moolenaar1966c242020-04-20 22:42:32 +02002672 if (STRNCMP(arg, "clear", 5) == 0 && ends_excmd2(arg, skipwhite(arg + 5)))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002673 {
2674 tp = (menutrans_T *)menutrans_ga.ga_data;
2675 for (i = 0; i < menutrans_ga.ga_len; ++i)
2676 {
2677 vim_free(tp[i].from);
2678 vim_free(tp[i].from_noamp);
2679 vim_free(tp[i].to);
2680 }
2681 ga_clear(&menutrans_ga);
2682# ifdef FEAT_EVAL
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002683 // Delete all "menutrans_" global variables.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002684 del_menutrans_vars();
2685# endif
2686 }
2687 else
2688 {
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002689 // ":menutrans from to": add translation
Bram Moolenaar071d4272004-06-13 20:20:40 +00002690 from = arg;
2691 arg = menu_skip_part(arg);
2692 to = skipwhite(arg);
2693 *arg = NUL;
2694 arg = menu_skip_part(to);
Bram Moolenaar1966c242020-04-20 22:42:32 +02002695 if (arg == to || ends_excmd2(eap->arg, from)
2696 || ends_excmd2(eap->arg, to)
2697 || !ends_excmd2(eap->arg, skipwhite(arg)))
Bram Moolenaar436b5ad2021-12-31 22:49:24 +00002698 emsg(_(e_invalid_argument));
Bram Moolenaar071d4272004-06-13 20:20:40 +00002699 else
2700 {
2701 if (ga_grow(&menutrans_ga, 1) == OK)
2702 {
2703 tp = (menutrans_T *)menutrans_ga.ga_data;
2704 from = vim_strsave(from);
Bram Moolenaarfc1421e2006-04-20 22:17:20 +00002705 if (from != NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002706 {
Bram Moolenaarfc1421e2006-04-20 22:17:20 +00002707 from_noamp = menu_text(from, NULL, NULL);
Bram Moolenaar71ccd032020-06-12 22:59:11 +02002708 to = vim_strnsave(to, arg - to);
Bram Moolenaarfc1421e2006-04-20 22:17:20 +00002709 if (from_noamp != NULL && to != NULL)
2710 {
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002711 menu_translate_tab_and_shift(from);
2712 menu_translate_tab_and_shift(to);
2713 menu_unescape_name(from);
2714 menu_unescape_name(to);
Bram Moolenaarfc1421e2006-04-20 22:17:20 +00002715 tp[menutrans_ga.ga_len].from = from;
2716 tp[menutrans_ga.ga_len].from_noamp = from_noamp;
2717 tp[menutrans_ga.ga_len].to = to;
2718 ++menutrans_ga.ga_len;
2719 }
2720 else
2721 {
2722 vim_free(from);
2723 vim_free(from_noamp);
2724 vim_free(to);
2725 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00002726 }
2727 }
2728 }
2729 }
2730#endif
2731}
2732
2733#if defined(FEAT_MULTI_LANG) || defined(FEAT_TOOLBAR)
2734/*
2735 * Find the character just after one part of a menu name.
2736 */
2737 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002738menu_skip_part(char_u *p)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002739{
Bram Moolenaar1c465442017-03-12 20:10:05 +01002740 while (*p != NUL && *p != '.' && !VIM_ISWHITE(*p))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002741 {
2742 if ((*p == '\\' || *p == Ctrl_V) && p[1] != NUL)
2743 ++p;
2744 ++p;
2745 }
2746 return p;
2747}
2748#endif
2749
2750#ifdef FEAT_MULTI_LANG
2751/*
2752 * Lookup part of a menu name in the translations.
2753 * Return a pointer to the translation or NULL if not found.
2754 */
2755 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002756menutrans_lookup(char_u *name, int len)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002757{
2758 menutrans_T *tp = (menutrans_T *)menutrans_ga.ga_data;
2759 int i;
2760 char_u *dname;
2761
2762 for (i = 0; i < menutrans_ga.ga_len; ++i)
Bram Moolenaar11dd8c12017-03-04 20:41:34 +01002763 if (STRNICMP(name, tp[i].from, len) == 0 && tp[i].from[len] == NUL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002764 return tp[i].to;
2765
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002766 // Now try again while ignoring '&' characters.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002767 i = name[len];
2768 name[len] = NUL;
2769 dname = menu_text(name, NULL, NULL);
2770 name[len] = i;
2771 if (dname != NULL)
2772 {
2773 for (i = 0; i < menutrans_ga.ga_len; ++i)
Bram Moolenaar11dd8c12017-03-04 20:41:34 +01002774 if (STRICMP(dname, tp[i].from_noamp) == 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002775 {
2776 vim_free(dname);
2777 return tp[i].to;
2778 }
2779 vim_free(dname);
2780 }
2781
2782 return NULL;
2783}
Bram Moolenaar071d4272004-06-13 20:20:40 +00002784
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002785/*
2786 * Unescape the name in the translate dictionary table.
2787 */
2788 static void
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002789menu_unescape_name(char_u *name)
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002790{
2791 char_u *p;
2792
Bram Moolenaar91acfff2017-03-12 19:22:36 +01002793 for (p = name; *p && *p != '.'; MB_PTR_ADV(p))
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002794 if (*p == '\\')
2795 STRMOVE(p, p + 1);
2796}
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01002797#endif // FEAT_MULTI_LANG
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002798
2799/*
2800 * Isolate the menu name.
2801 * Skip the menu name, and translate <Tab> into a real TAB.
2802 */
2803 static char_u *
Bram Moolenaar52ea13d2016-01-30 18:51:09 +01002804menu_translate_tab_and_shift(char_u *arg_start)
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002805{
2806 char_u *arg = arg_start;
2807
Bram Moolenaar1c465442017-03-12 20:10:05 +01002808 while (*arg && !VIM_ISWHITE(*arg))
Bram Moolenaar70b11cd2010-05-14 22:24:40 +02002809 {
2810 if ((*arg == '\\' || *arg == Ctrl_V) && arg[1] != NUL)
2811 arg++;
2812 else if (STRNICMP(arg, "<TAB>", 5) == 0)
2813 {
2814 *arg = TAB;
2815 STRMOVE(arg + 1, arg + 5);
2816 }
2817 arg++;
2818 }
2819 if (*arg != NUL)
2820 *arg++ = NUL;
2821 arg = skipwhite(arg);
2822
2823 return arg;
2824}
2825
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002826/*
2827 * Get the information about a menu item in mode 'which'
2828 */
2829 static int
Yegappan Lakshmanan51491ad2021-09-30 19:00:00 +01002830menuitem_getinfo(char_u *menu_name, vimmenu_T *menu, int modes, dict_T *dict)
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002831{
2832 int status;
Yegappan Lakshmanan51491ad2021-09-30 19:00:00 +01002833 list_T *l;
2834
2835 if (*menu_name == NUL)
2836 {
2837 // Return all the top-level menus
2838 vimmenu_T *topmenu;
2839
2840 l = list_alloc();
2841 if (l == NULL)
2842 return FAIL;
2843
2844 dict_add_list(dict, "submenus", l);
2845 // get all the children. Skip PopUp[nvoci].
2846 for (topmenu = menu; topmenu != NULL; topmenu = topmenu->next)
2847 if (!menu_is_hidden(topmenu->dname))
2848 list_append_string(l, topmenu->dname, -1);
2849 return OK;
2850 }
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002851
2852 if (menu_is_tearoff(menu->dname)) // skip tearoff menu item
2853 return OK;
2854
2855 status = dict_add_string(dict, "name", menu->name);
2856 if (status == OK)
2857 status = dict_add_string(dict, "display", menu->dname);
2858 if (status == OK && menu->actext != NULL)
2859 status = dict_add_string(dict, "accel", menu->actext);
2860 if (status == OK)
2861 status = dict_add_number(dict, "priority", menu->priority);
2862 if (status == OK)
2863 status = dict_add_string(dict, "modes",
2864 get_menu_mode_str(menu->modes));
2865#ifdef FEAT_TOOLBAR
2866 if (status == OK && menu->iconfile != NULL)
2867 status = dict_add_string(dict, "icon", menu->iconfile);
2868 if (status == OK && menu->iconidx >= 0)
2869 status = dict_add_number(dict, "iconidx", menu->iconidx);
2870#endif
2871 if (status == OK)
2872 {
2873 char_u buf[NUMBUFLEN];
2874
2875 if (has_mbyte)
2876 buf[utf_char2bytes(menu->mnemonic, buf)] = NUL;
2877 else
2878 {
2879 buf[0] = (char_u)menu->mnemonic;
2880 buf[1] = NUL;
2881 }
2882 status = dict_add_string(dict, "shortcut", buf);
2883 }
2884 if (status == OK && menu->children == NULL)
2885 {
2886 int bit;
2887
2888 // Get the first mode in which the menu is available
Bram Moolenaar56cb3372020-03-16 20:04:41 +01002889 for (bit = 0; bit < MENU_MODES && !((1 << bit) & modes); bit++)
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002890 ;
Bram Moolenaar56cb3372020-03-16 20:04:41 +01002891 if (bit < MENU_MODES) // just in case, avoid Coverity warning
2892 {
2893 if (menu->strings[bit] != NULL)
Bram Moolenaar292b90d2020-03-18 15:23:16 +01002894 {
2895 char_u *tofree = NULL;
2896
Bram Moolenaar56cb3372020-03-16 20:04:41 +01002897 status = dict_add_string(dict, "rhs",
2898 *menu->strings[bit] == NUL
Bram Moolenaar292b90d2020-03-18 15:23:16 +01002899 ? (char_u *)"<Nop>"
2900 : (tofree = str2special_save(
2901 menu->strings[bit], FALSE)));
2902 vim_free(tofree);
2903 }
Bram Moolenaar56cb3372020-03-16 20:04:41 +01002904 if (status == OK)
2905 status = dict_add_bool(dict, "noremenu",
2906 menu->noremap[bit] == REMAP_NONE);
2907 if (status == OK)
2908 status = dict_add_bool(dict, "script",
2909 menu->noremap[bit] == REMAP_SCRIPT);
2910 if (status == OK)
2911 status = dict_add_bool(dict, "silent", menu->silent[bit]);
2912 if (status == OK)
2913 status = dict_add_bool(dict, "enabled",
2914 ((menu->enabled & (1 << bit)) != 0));
2915 }
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002916 }
Bram Moolenaar56cb3372020-03-16 20:04:41 +01002917
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002918 // If there are submenus, add all the submenu display names
2919 if (status == OK && menu->children != NULL)
2920 {
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002921 vimmenu_T *child;
2922
Yegappan Lakshmanan51491ad2021-09-30 19:00:00 +01002923 l = list_alloc();
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002924 if (l == NULL)
2925 return FAIL;
2926
2927 dict_add_list(dict, "submenus", l);
2928 child = menu->children;
2929 while (child)
2930 {
2931 if (!menu_is_tearoff(child->dname)) // skip tearoff menu
2932 list_append_string(l, child->dname, -1);
2933 child = child->next;
2934 }
2935 }
2936
2937 return status;
2938}
2939
2940/*
2941 * "menu_info()" function
2942 * Return information about a menu (including all the child menus)
2943 */
2944 void
2945f_menu_info(typval_T *argvars, typval_T *rettv)
2946{
2947 char_u *menu_name;
2948 char_u *which;
2949 int modes;
2950 char_u *saved_name;
2951 char_u *name;
2952 vimmenu_T *menu;
2953 dict_T *retdict;
2954
2955 if (rettv_dict_alloc(rettv) != OK)
2956 return;
2957 retdict = rettv->vval.v_dict;
2958
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02002959 if (in_vim9script()
2960 && (check_for_string_arg(argvars, 0) == FAIL
2961 || check_for_opt_string_arg(argvars, 1) == FAIL))
2962 return;
2963
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01002964 menu_name = tv_get_string_chk(&argvars[0]);
2965 if (menu_name == NULL)
2966 return;
2967
2968 // menu mode
2969 if (argvars[1].v_type != VAR_UNKNOWN)
2970 which = tv_get_string_chk(&argvars[1]);
2971 else
2972 which = (char_u *)""; // Default is modes for "menu"
2973 if (which == NULL)
2974 return;
2975
2976 modes = get_menu_cmd_modes(which, *which == '!', NULL, NULL);
2977
2978 // Locate the specified menu or menu item
2979 menu = *get_root_menu(menu_name);
2980 saved_name = vim_strsave(menu_name);
2981 if (saved_name == NULL)
2982 return;
2983 if (*saved_name != NUL)
2984 {
2985 char_u *p;
2986
2987 name = saved_name;
2988 while (*name)
2989 {
2990 // Find in the menu hierarchy
2991 p = menu_name_skip(name);
2992 while (menu != NULL)
2993 {
2994 if (menu_name_equal(name, menu))
2995 break;
2996 menu = menu->next;
2997 }
2998 if (menu == NULL || *p == NUL)
2999 break;
3000 menu = menu->children;
3001 name = p;
3002 }
3003 }
3004 vim_free(saved_name);
3005
3006 if (menu == NULL) // specified menu not found
3007 return;
3008
3009 if (menu->modes & modes)
Yegappan Lakshmanan51491ad2021-09-30 19:00:00 +01003010 menuitem_getinfo(menu_name, menu, modes, retdict);
Bram Moolenaar0eabd4d2020-03-15 16:13:53 +01003011}
3012
Bram Moolenaar4ba37b52019-12-04 21:57:43 +01003013#endif // FEAT_MENU