mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	viml: introduce menu_get() function #6322
menu_get({path}, {modes}). See :h menu_get.
			
			
This commit is contained in:
		 Matthieu Coudron
					Matthieu Coudron
				
			
				
					committed by
					
						 Justin M. Keyes
						Justin M. Keyes
					
				
			
			
				
	
			
			
			 Justin M. Keyes
						Justin M. Keyes
					
				
			
						parent
						
							e6d54407ba
						
					
				
				
					commit
					dc685387a3
				
			| @@ -5508,6 +5508,46 @@ max({expr})	Return the maximum value of all items in {expr}. | ||||
| 		items in {expr} cannot be used as a Number this results in | ||||
|                 an error.  An empty |List| or |Dictionary| results in zero. | ||||
|  | ||||
| menu_get({path}, {modes})				*menu_get()* | ||||
| 		Returns a |Dictionary| with all the submenu of {path} (set to  | ||||
| 		an empty string to match all menus). Only the commands matching {modes} are  | ||||
| 		returned ('a' for all, 'i' for insert see |creating-menus|). | ||||
|  | ||||
|     For instance, executing: | ||||
| > | ||||
| 			nnoremenu &Test.Test inormal | ||||
| 			inoremenu Test.Test insert | ||||
| 			vnoremenu Test.Test x | ||||
| 			echo menu_get("") | ||||
| < | ||||
| should produce an output with a similar structure: | ||||
| > | ||||
| 	[ { | ||||
| 		"hidden": 0, | ||||
| 		"name": "Test", | ||||
| 		"priority": 500, | ||||
| 		"shortcut": 84, | ||||
| 		"submenus": [ { | ||||
| 			"hidden": 0, | ||||
| 			"mappings": { | ||||
| 				i": { | ||||
| 					"enabled": 1, | ||||
| 					"noremap": 1, | ||||
| 					"rhs": "insert", | ||||
| 					"sid": 1, | ||||
| 					"silent": 0 | ||||
| 				}, | ||||
| 				n": { ... }, | ||||
| 				s": { ... }, | ||||
| 				v": { ... } | ||||
| 			}, | ||||
| 			"name": "Test", | ||||
| 			"priority": 500, | ||||
| 			"shortcut": 0 | ||||
| 		} ] | ||||
| 	} ] | ||||
| < | ||||
|  | ||||
| 							*min()* | ||||
| min({expr})	Return the minimum value of all items in {expr}. | ||||
| 		{expr} can be a list or a dictionary.  For a dictionary, | ||||
|   | ||||
| @@ -47,6 +47,7 @@ | ||||
| #include "nvim/mbyte.h" | ||||
| #include "nvim/memline.h" | ||||
| #include "nvim/memory.h" | ||||
| #include "nvim/menu.h" | ||||
| #include "nvim/message.h" | ||||
| #include "nvim/misc1.h" | ||||
| #include "nvim/keymap.h" | ||||
| @@ -8173,6 +8174,19 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) | ||||
|   } | ||||
| } | ||||
|  | ||||
|  | ||||
| /// "menu_get(path [, modes])" function | ||||
| static void f_menu_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) | ||||
| { | ||||
|   tv_list_alloc_ret(rettv); | ||||
|   int modes = MENU_ALL_MODES; | ||||
|   if (argvars[1].v_type == VAR_STRING) { | ||||
|     const char_u *const strmodes = (char_u *)tv_get_string(&argvars[1]); | ||||
|     modes = get_menu_cmd_modes(strmodes, false, NULL, NULL); | ||||
|   } | ||||
|   menu_get((char_u *)tv_get_string(&argvars[0]), modes, rettv->vval.v_list); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * "extend(list, list [, idx])" function | ||||
|  * "extend(dict, dict [, action])" function | ||||
|   | ||||
| @@ -2,10 +2,10 @@ | ||||
| -- | ||||
| -- Keys: | ||||
| -- | ||||
| -- args  Number of arguments, list with maximum and minimum number of arguments  | ||||
| --       or list with a minimum number of arguments only. Defaults to zero  | ||||
| -- args  Number of arguments, list with maximum and minimum number of arguments | ||||
| --       or list with a minimum number of arguments only. Defaults to zero | ||||
| --       arguments. | ||||
| -- func  Name of the C function which implements the VimL function. Defaults to  | ||||
| -- func  Name of the C function which implements the VimL function. Defaults to | ||||
| --       `f_{funcname}`. | ||||
|  | ||||
| local varargs = function(nr) | ||||
| @@ -208,6 +208,7 @@ return { | ||||
|     matchstr={args={2, 4}}, | ||||
|     matchstrpos={args={2,4}}, | ||||
|     max={args=1}, | ||||
|     menu_get={args={1, 2}}, | ||||
|     min={args=1}, | ||||
|     mkdir={args={1, 3}}, | ||||
|     mode={args={0, 1}}, | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| bit = require 'bit' | ||||
| local bit = require 'bit' | ||||
|  | ||||
| -- Description of the values below is contained in ex_cmds_defs.h file. | ||||
| local RANGE      =    0x001 | ||||
|   | ||||
| @@ -5,12 +5,15 @@ | ||||
| #include "nvim/buffer_defs.h" | ||||
| #include "nvim/ex_cmds_defs.h" | ||||
|  | ||||
| /* Values for "noremap" argument of ins_typebuf().  Also used for | ||||
|  * map->m_noremap and menu->noremap[]. */ | ||||
| #define REMAP_YES       0       /* allow remapping */ | ||||
| #define REMAP_NONE      -1      /* no remapping */ | ||||
| #define REMAP_SCRIPT    -2      /* remap script-local mappings only */ | ||||
| #define REMAP_SKIP      -3      /* no remapping for first char */ | ||||
| /// Values for "noremap" argument of ins_typebuf().  Also used for | ||||
| /// map->m_noremap and menu->noremap[]. | ||||
| /// @addtogroup REMAP_VALUES | ||||
| /// @{ | ||||
| #define REMAP_YES       0       ///< allow remapping | ||||
| #define REMAP_NONE      -1      ///< no remapping | ||||
| #define REMAP_SCRIPT    -2      ///< remap script-local mappings only | ||||
| #define REMAP_SKIP      -3      ///< no remapping for first char | ||||
| /// @} | ||||
|  | ||||
| #define KEYLEN_PART_KEY -1      /* keylen value for incomplete key-code */ | ||||
| #define KEYLEN_PART_MAP -2      /* keylen value for incomplete mapping */ | ||||
|   | ||||
| @@ -932,12 +932,13 @@ int utf_char2len(int c) | ||||
|   return 6; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Convert Unicode character "c" to UTF-8 string in "buf[]". | ||||
|  * Returns the number of bytes. | ||||
|  * This does not include composing characters. | ||||
|  */ | ||||
| int utf_char2bytes(int c, char_u *buf) | ||||
| /// Convert Unicode character to UTF-8 string | ||||
| /// | ||||
| /// @param c character to convert to \p buf | ||||
| /// @param[out] buf UTF-8 string generated from \p c, does not add \0 | ||||
| /// @return the number of bytes (between 1 and 6) | ||||
| /// @note This does not include composing characters. | ||||
| int utf_char2bytes(int c, char_u *const buf) | ||||
| { | ||||
|   if (c < 0x80) {               /* 7 bits */ | ||||
|     buf[0] = c; | ||||
|   | ||||
							
								
								
									
										296
									
								
								src/nvim/menu.c
									
									
									
									
									
								
							
							
						
						
									
										296
									
								
								src/nvim/menu.c
									
									
									
									
									
								
							| @@ -26,7 +26,7 @@ | ||||
| #include "nvim/state.h" | ||||
| #include "nvim/strings.h" | ||||
| #include "nvim/ui.h" | ||||
|  | ||||
| #include "nvim/eval/typval.h" | ||||
|  | ||||
| #define MENUDEPTH   10          /* maximum depth of menus */ | ||||
|  | ||||
| @@ -38,8 +38,8 @@ | ||||
|  | ||||
|  | ||||
|  | ||||
| /* The character for each menu mode */ | ||||
| static char_u menu_mode_chars[] = {'n', 'v', 's', 'o', 'i', 'c', 't'}; | ||||
| /// The character for each menu mode | ||||
| static char_u menu_mode_chars[] = { 'n', 'v', 's', 'o', 'i', 'c', 't' }; | ||||
|  | ||||
| static char_u e_notsubmenu[] = N_( | ||||
|     "E327: Part of menu-item path is not sub-menu"); | ||||
| @@ -47,17 +47,14 @@ static char_u e_othermode[] = N_("E328: Menu only exists in another mode"); | ||||
| static char_u e_nomenu[] = N_("E329: No menu \"%s\""); | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * Do the :menu command and relatives. | ||||
|  */ | ||||
| void  | ||||
| ex_menu ( | ||||
|     exarg_T *eap                   /* Ex command arguments */ | ||||
| ) | ||||
| /// Do the :menu command and relatives. | ||||
| /// @param eap Ex command arguments | ||||
| void | ||||
| ex_menu(exarg_T *eap) | ||||
| { | ||||
|   char_u      *menu_path; | ||||
|   int modes; | ||||
|   char_u      *map_to; | ||||
|   char_u      *map_to;            // command mapped to the menu entry | ||||
|   int noremap; | ||||
|   bool silent = false; | ||||
|   int unmenu; | ||||
| @@ -93,9 +90,12 @@ ex_menu ( | ||||
|   } | ||||
|  | ||||
|  | ||||
|   /* Locate an optional "icon=filename" argument. */ | ||||
|   // Locate an optional "icon=filename" argument | ||||
|   // Kept just the command parsing  from vim for compativility but no further | ||||
|   // processing is done | ||||
|   if (STRNCMP(arg, "icon=", 5) == 0) { | ||||
|     arg += 5; | ||||
|     // icon = arg; | ||||
|     while (*arg != NUL && *arg != ' ') { | ||||
|       if (*arg == '\\') | ||||
|         STRMOVE(arg, arg + 1); | ||||
| @@ -107,12 +107,12 @@ ex_menu ( | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /* | ||||
|    * Fill in the priority table. | ||||
|    */ | ||||
|   for (p = arg; *p; ++p) | ||||
|     if (!ascii_isdigit(*p) && *p != '.') | ||||
|   // Fill in the priority table. | ||||
|   for (p = arg; *p; p++) { | ||||
|     if (!ascii_isdigit(*p) && *p != '.') { | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
|   if (ascii_iswhite(*p)) { | ||||
|     for (i = 0; i < MENUDEPTH && !ascii_iswhite(*arg); ++i) { | ||||
|       pri_tab[i] = getdigits_long(&arg); | ||||
| @@ -226,8 +226,7 @@ ex_menu ( | ||||
|     menuarg.modes = modes; | ||||
|     menuarg.noremap[0] = noremap; | ||||
|     menuarg.silent[0] = silent; | ||||
|     add_menu_path(menu_path, &menuarg, pri_tab, map_to | ||||
|         ); | ||||
|     add_menu_path(menu_path, &menuarg, pri_tab, map_to); | ||||
|  | ||||
|     /* | ||||
|      * For the PopUp menu, add a menu for each mode separately. | ||||
| @@ -252,16 +251,18 @@ theend: | ||||
|   ; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Add the menu with the given name to the menu hierarchy | ||||
|  */ | ||||
| static int  | ||||
| add_menu_path ( | ||||
|     char_u *menu_path, | ||||
|     vimmenu_T *menuarg,           /* passes modes, iconfile, iconidx, | ||||
|                                    icon_builtin, silent[0], noremap[0] */ | ||||
|     long *pri_tab, | ||||
|     char_u *call_data | ||||
|  | ||||
| /// Add the menu with the given name to the menu hierarchy | ||||
| /// | ||||
| /// @param[out]  menuarg menu entry | ||||
| /// @param[] pri_tab priority table | ||||
| /// @param[in] call_data Right hand side command | ||||
| static int | ||||
| add_menu_path( | ||||
|     const char_u *const menu_path, | ||||
|     vimmenu_T *menuarg, | ||||
|     const long *const pri_tab, | ||||
|     const char_u *const call_data | ||||
| ) | ||||
| { | ||||
|   char_u      *path_name; | ||||
| @@ -296,8 +297,9 @@ add_menu_path ( | ||||
|     if (map_to != NULL) { | ||||
|       en_name = name; | ||||
|       name = map_to; | ||||
|     } else | ||||
|     } else { | ||||
|       en_name = NULL; | ||||
|     } | ||||
|     dname = menu_text(name, NULL, NULL); | ||||
|     if (*dname == NUL) { | ||||
|       /* Only a mnemonic or accelerator is not valid. */ | ||||
| @@ -311,14 +313,15 @@ add_menu_path ( | ||||
|     while (menu != NULL) { | ||||
|       if (menu_name_equal(name, menu) || menu_name_equal(dname, menu)) { | ||||
|         if (*next_name == NUL && menu->children != NULL) { | ||||
|           if (!sys_menu) | ||||
|           if (!sys_menu) { | ||||
|             EMSG(_("E330: Menu path must not lead to a sub-menu")); | ||||
|           } | ||||
|           goto erret; | ||||
|         } | ||||
|         if (*next_name != NUL && menu->children == NULL | ||||
|             ) { | ||||
|           if (!sys_menu) | ||||
|         if (*next_name != NUL && menu->children == NULL) { | ||||
|           if (!sys_menu) { | ||||
|             EMSG(_(e_notsubmenu)); | ||||
|           } | ||||
|           goto erret; | ||||
|         } | ||||
|         break; | ||||
| @@ -352,7 +355,7 @@ add_menu_path ( | ||||
|       menu->modes = modes; | ||||
|       menu->enabled = MENU_ALL_MODES; | ||||
|       menu->name = vim_strsave(name); | ||||
|       /* separate mnemonic and accelerator text from actual menu name */ | ||||
|       // separate mnemonic and accelerator text from actual menu name | ||||
|       menu->dname = menu_text(name, &menu->mnemonic, &menu->actext); | ||||
|       if (en_name != NULL) { | ||||
|         menu->en_name = vim_strsave(en_name); | ||||
| @@ -364,9 +367,7 @@ add_menu_path ( | ||||
|       menu->priority = pri_tab[pri_idx]; | ||||
|       menu->parent = parent; | ||||
|  | ||||
|       /* | ||||
|        * Add after menu that has lower priority. | ||||
|        */ | ||||
|       // Add after menu that has lower priority. | ||||
|       menu->next = *lower_pri; | ||||
|       *lower_pri = menu; | ||||
|  | ||||
| @@ -392,8 +393,9 @@ add_menu_path ( | ||||
|     name = next_name; | ||||
|     xfree(dname); | ||||
|     dname = NULL; | ||||
|     if (pri_tab[pri_idx + 1] != -1) | ||||
|       ++pri_idx; | ||||
|     if (pri_tab[pri_idx + 1] != -1) { | ||||
|       pri_idx++; | ||||
|     } | ||||
|   } | ||||
|   xfree(path_name); | ||||
|  | ||||
| @@ -419,8 +421,7 @@ add_menu_path ( | ||||
|         // Don't do this for "<Nop>". | ||||
|         c = 0; | ||||
|         d = 0; | ||||
|         if (amenu && call_data != NULL && *call_data != NUL | ||||
|             ) { | ||||
|         if (amenu && call_data != NULL && *call_data != NUL) { | ||||
|           switch (1 << i) { | ||||
|           case MENU_VISUAL_MODE: | ||||
|           case MENU_SELECT_MODE: | ||||
| @@ -438,9 +439,9 @@ add_menu_path ( | ||||
|         if (c != 0) { | ||||
|           menu->strings[i] = xmalloc(STRLEN(call_data) + 5 ); | ||||
|           menu->strings[i][0] = c; | ||||
|           if (d == 0) | ||||
|           if (d == 0) { | ||||
|             STRCPY(menu->strings[i] + 1, call_data); | ||||
|           else { | ||||
|           } else { | ||||
|             menu->strings[i][1] = d; | ||||
|             STRCPY(menu->strings[i] + 2, call_data); | ||||
|           } | ||||
| @@ -452,8 +453,9 @@ add_menu_path ( | ||||
|             menu->strings[i][len + 1] = Ctrl_G; | ||||
|             menu->strings[i][len + 2] = NUL; | ||||
|           } | ||||
|         } else | ||||
|         } else { | ||||
|           menu->strings[i] = p; | ||||
|         } | ||||
|         menu->noremap[i] = menuarg->noremap[0]; | ||||
|         menu->silent[i] = menuarg->silent[0]; | ||||
|       } | ||||
| @@ -657,20 +659,109 @@ static void free_menu_string(vimmenu_T *menu, int idx) | ||||
|   menu->strings[idx] = NULL; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Show the mapping associated with a menu item or hierarchy in a sub-menu. | ||||
|  */ | ||||
| static int show_menus(char_u *path_name, int modes) | ||||
| /// Export menus | ||||
| /// | ||||
| /// @param[in] menu if null, starts from root_menu | ||||
| /// @param modes, a choice of \ref MENU_MODES | ||||
| /// @return a dict with name/commands | ||||
| /// @see menu_get | ||||
| static dict_T *menu_get_recursive(const vimmenu_T *menu, int modes) | ||||
| { | ||||
|   dict_T *dict; | ||||
|   char buf[sizeof(menu->mnemonic)]; | ||||
|   int mnemonic_len; | ||||
|  | ||||
|   if (!menu || (menu->modes & modes) == 0x0) { | ||||
|     return NULL; | ||||
|   } | ||||
|  | ||||
|   dict = tv_dict_alloc(); | ||||
|   tv_dict_add_str(dict, S_LEN("name"), (char *)menu->dname); | ||||
|   tv_dict_add_nr(dict, S_LEN("priority"), (int)menu->priority); | ||||
|   tv_dict_add_nr(dict, S_LEN("hidden"), menu_is_hidden(menu->dname)); | ||||
|  | ||||
|   if (menu->mnemonic) { | ||||
|     mnemonic_len = utf_char2bytes(menu->mnemonic, (u_char *)buf); | ||||
|     buf[mnemonic_len] = '\0'; | ||||
|     tv_dict_add_str(dict, S_LEN("shortcut"), buf); | ||||
|   } | ||||
|  | ||||
|   if (menu->modes & MENU_TIP_MODE && menu->strings[MENU_INDEX_TIP]) { | ||||
|     tv_dict_add_str(dict, S_LEN("tooltip"), | ||||
|                     (char *)menu->strings[MENU_INDEX_TIP]); | ||||
|   } | ||||
|  | ||||
|   if (!menu->children) { | ||||
|     // leaf menu | ||||
|     dict_T *commands = tv_dict_alloc(); | ||||
|     tv_dict_add_dict(dict, S_LEN("mappings"), commands); | ||||
|  | ||||
|     for (int bit = 0; bit < MENU_MODES; bit++) { | ||||
|       if ((menu->modes & modes & (1 << bit)) != 0) { | ||||
|         dict_T *impl = tv_dict_alloc(); | ||||
|         if (*menu->strings[bit] == NUL) { | ||||
|           tv_dict_add_str(impl, S_LEN("rhs"), (char *)"<Nop>"); | ||||
|         } else { | ||||
|           tv_dict_add_str(impl, S_LEN("rhs"), (char *)menu->strings[bit]); | ||||
|         } | ||||
|         tv_dict_add_nr(impl, S_LEN("silent"), menu->silent[bit]); | ||||
|         tv_dict_add_nr(impl, S_LEN("enabled"), | ||||
|                        (menu->enabled & (1 << bit)) ? 1 : 0); | ||||
|         tv_dict_add_nr(impl, S_LEN("noremap"), | ||||
|                        (menu->noremap[bit] & REMAP_NONE) ? 1 : 0); | ||||
|         tv_dict_add_nr(impl, S_LEN("sid"), | ||||
|                        (menu->noremap[bit] & REMAP_SCRIPT) ? 1 : 0); | ||||
|         tv_dict_add_dict(commands, (char *)&menu_mode_chars[bit], 1, impl); | ||||
|       } | ||||
|     } | ||||
|   } else { | ||||
|     // visit recursively all children | ||||
|     list_T *children_list = tv_list_alloc(); | ||||
|     for (menu = menu->children; menu != NULL; menu = menu->next) { | ||||
|         dict_T *dic = menu_get_recursive(menu, modes); | ||||
|         if (dict && tv_dict_len(dict) > 0) { | ||||
|           tv_list_append_dict(children_list, dic); | ||||
|         } | ||||
|     } | ||||
|     tv_dict_add_list(dict, S_LEN("submenus"), children_list); | ||||
|   } | ||||
|   return dict; | ||||
| } | ||||
|  | ||||
|  | ||||
| /// Export menus matching path \p path_name | ||||
| /// | ||||
| /// @param path_name | ||||
| /// @param modes supported modes, see \ref MENU_MODES | ||||
| /// @param[in,out] list must be allocated | ||||
| /// @return false if could not find path_name | ||||
| bool menu_get(char_u *const path_name, int modes, list_T *list) | ||||
| { | ||||
|   char_u      *p; | ||||
|   char_u      *name; | ||||
|   vimmenu_T   *menu; | ||||
|   vimmenu_T   *parent = NULL; | ||||
|   menu = find_menu(root_menu, path_name, modes); | ||||
|   if (!menu) { | ||||
|     return false; | ||||
|   } | ||||
|   for (; menu != NULL; menu = menu->next) { | ||||
|     dict_T *dict = menu_get_recursive(menu, modes); | ||||
|     if (dict && tv_dict_len(dict) > 0) { | ||||
|       tv_list_append_dict(list, dict); | ||||
|     } | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
|  | ||||
|   menu = root_menu; | ||||
|   name = path_name = vim_strsave(path_name); | ||||
|  | ||||
|   /* First, find the (sub)menu with the given name */ | ||||
| /// Find menu matching required name and modes | ||||
| /// | ||||
| /// @param menu top menu to start looking from | ||||
| /// @param name path towards the menu | ||||
| /// @return menu if \p name is null, found menu or NULL | ||||
| vimmenu_T * | ||||
| find_menu(vimmenu_T *menu, char_u * name, int modes) | ||||
| { | ||||
|   char_u *p; | ||||
|  | ||||
|   while (*name) { | ||||
|     p = menu_name_skip(name); | ||||
|     while (menu != NULL) { | ||||
| @@ -678,39 +769,46 @@ static int show_menus(char_u *path_name, int modes) | ||||
|         /* Found menu */ | ||||
|         if (*p != NUL && menu->children == NULL) { | ||||
|           EMSG(_(e_notsubmenu)); | ||||
|           xfree(path_name); | ||||
|           return FAIL; | ||||
|           return NULL; | ||||
|         } else if ((menu->modes & modes) == 0x0) { | ||||
|           EMSG(_(e_othermode)); | ||||
|           xfree(path_name); | ||||
|           return FAIL; | ||||
|           return NULL; | ||||
|         } | ||||
|         break; | ||||
|       } | ||||
|       menu = menu->next; | ||||
|     } | ||||
|  | ||||
|     if (menu == NULL) { | ||||
|       EMSG2(_(e_nomenu), name); | ||||
|       xfree(path_name); | ||||
|       return FAIL; | ||||
|       return NULL; | ||||
|     } | ||||
|     name = p; | ||||
|     parent = menu; | ||||
|     menu = menu->children; | ||||
|   } | ||||
|   xfree(path_name); | ||||
|   return menu; | ||||
| } | ||||
|  | ||||
| /// Show the mapping associated with a menu item or hierarchy in a sub-menu. | ||||
| static int show_menus(char_u *const path_name, int modes) | ||||
| { | ||||
|   vimmenu_T   *menu; | ||||
|  | ||||
|   // First, find the (sub)menu with the given name | ||||
|   menu = find_menu(root_menu, path_name, modes); | ||||
|   if (!menu) { | ||||
|     return FAIL; | ||||
|   } | ||||
|  | ||||
|   /* Now we have found the matching menu, and we list the mappings */ | ||||
|   /* Highlight title */ | ||||
|   MSG_PUTS_TITLE(_("\n--- Menus ---")); | ||||
|  | ||||
|   show_menus_recursive(parent, modes, 0); | ||||
|   show_menus_recursive(menu->parent, modes, 0); | ||||
|   return OK; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Recursively show the mappings associated with the menus under the given one | ||||
|  */ | ||||
| /// Recursively show the mappings associated with the menus under the given one | ||||
| static void show_menus_recursive(vimmenu_T *menu, int modes, int depth) | ||||
| { | ||||
|   int i; | ||||
| @@ -993,12 +1091,13 @@ char_u *get_menu_names(expand_T *xp, int idx) | ||||
|   return str; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Skip over this element of the menu path and return the start of the next | ||||
|  * element.  Any \ and ^Vs are removed from the current element. | ||||
|  * "name" may be modified. | ||||
|  */ | ||||
| char_u *menu_name_skip(char_u *name) | ||||
|  | ||||
| /// Skip over this element of the menu path and return the start of the next | ||||
| /// element.  Any \ and ^Vs are removed from the current element. | ||||
| /// | ||||
| /// @param name may be modified. | ||||
| /// @return start of the next element | ||||
| char_u *menu_name_skip(char_u *const name) | ||||
| { | ||||
|   char_u  *p; | ||||
|  | ||||
| @@ -1018,16 +1117,16 @@ char_u *menu_name_skip(char_u *name) | ||||
|  * Return TRUE when "name" matches with menu "menu".  The name is compared in | ||||
|  * two ways: raw menu name and menu name without '&'.  ignore part after a TAB. | ||||
|  */ | ||||
| static int menu_name_equal(char_u *name, vimmenu_T *menu) | ||||
| static bool menu_name_equal(const char_u *const name, vimmenu_T *const menu) | ||||
| { | ||||
|   if (menu->en_name != NULL | ||||
|       && (menu_namecmp(name, menu->en_name) | ||||
|           || menu_namecmp(name, menu->en_dname))) | ||||
|     return TRUE; | ||||
|     return true; | ||||
|   return menu_namecmp(name, menu->name) || menu_namecmp(name, menu->dname); | ||||
| } | ||||
|  | ||||
| static int menu_namecmp(char_u *name, char_u *mname) | ||||
| static bool menu_namecmp(const char_u *const name, const char_u *const mname) | ||||
| { | ||||
|   int i; | ||||
|  | ||||
| @@ -1038,18 +1137,20 @@ static int menu_namecmp(char_u *name, char_u *mname) | ||||
|          && (mname[i] == NUL || mname[i] == TAB); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Return the modes specified by the given menu command (eg :menu! returns | ||||
|  * MENU_CMDLINE_MODE | MENU_INSERT_MODE). | ||||
|  * If "noremap" is not NULL, then the flag it points to is set according to | ||||
|  * whether the command is a "nore" command. | ||||
|  * If "unmenu" is not NULL, then the flag it points to is set according to | ||||
|  * whether the command is an "unmenu" command. | ||||
|  */ | ||||
| static int  | ||||
| get_menu_cmd_modes ( | ||||
|     char_u *cmd, | ||||
|     int forceit,                /* Was there a "!" after the command? */ | ||||
|  | ||||
| /// converts a string into a combination of \ref MENU_MODES | ||||
| ///  (eg :menu! returns MENU_CMDLINE_MODE | MENU_INSERT_MODE) | ||||
| /// | ||||
| /// @param[in] cmd a string like 'n' (normal) or 'a' (all) | ||||
| /// @param[in] forceit Was there a "!" after the command? | ||||
| /// @param[out] If "noremap" is not NULL, then the flag it points to is set | ||||
| /// according to whether the command is a "nore" command. | ||||
| /// @param[out] unmenu is not NULL, then the flag it points to is set according | ||||
| /// to whether the command is an "unmenu" command. | ||||
| int | ||||
| get_menu_cmd_modes( | ||||
|     const char_u * cmd, | ||||
|     bool forceit, | ||||
|     int *noremap, | ||||
|     int *unmenu | ||||
| ) | ||||
| @@ -1090,12 +1191,15 @@ get_menu_cmd_modes ( | ||||
|     } | ||||
|   /* FALLTHROUGH */ | ||||
|   default: | ||||
|     --cmd; | ||||
|     if (forceit)                        /* menu!! */ | ||||
|     cmd--; | ||||
|     if (forceit) { | ||||
|       // menu!! | ||||
|       modes = MENU_INSERT_MODE | MENU_CMDLINE_MODE; | ||||
|     else                                /* menu */ | ||||
|     } else { | ||||
|       // menu | ||||
|       modes = MENU_NORMAL_MODE | MENU_VISUAL_MODE | MENU_SELECT_MODE | ||||
|               | MENU_OP_PENDING_MODE; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (noremap != NULL) | ||||
| @@ -1201,12 +1305,14 @@ int menu_is_separator(char_u *name) | ||||
|   return name[0] == '-' && name[STRLEN(name) - 1] == '-'; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Return TRUE if the menu is hidden:  Starts with ']' | ||||
|  */ | ||||
|  | ||||
| /// True if a popup menu or starts with \ref MNU_HIDDEN_CHAR | ||||
| /// | ||||
| /// @return true if the menu is hidden | ||||
| static int menu_is_hidden(char_u *name) | ||||
| { | ||||
|   return (name[0] == ']') || (menu_is_popup(name) && name[5] != NUL); | ||||
|   return (name[0] == MNU_HIDDEN_CHAR) | ||||
|           || (menu_is_popup(name) && name[5] != NUL); | ||||
| } | ||||
|  | ||||
| /* | ||||
|   | ||||
| @@ -6,7 +6,9 @@ | ||||
| #include "nvim/types.h" // for char_u and expand_T | ||||
| #include "nvim/ex_cmds_defs.h" // for exarg_T | ||||
|  | ||||
| /* Indices into vimmenu_T->strings[] and vimmenu_T->noremap[] for each mode */ | ||||
| /// Indices into vimmenu_T->strings[] and vimmenu_T->noremap[] for each mode | ||||
| /// \addtogroup MENU_INDEX | ||||
| /// @{ | ||||
| #define MENU_INDEX_INVALID      -1 | ||||
| #define MENU_INDEX_NORMAL       0 | ||||
| #define MENU_INDEX_VISUAL       1 | ||||
| @@ -16,8 +18,12 @@ | ||||
| #define MENU_INDEX_CMDLINE      5 | ||||
| #define MENU_INDEX_TIP          6 | ||||
| #define MENU_MODES              7 | ||||
| /// @} | ||||
| /// note MENU_INDEX_TIP is not a 'real' mode | ||||
|  | ||||
| /* Menu modes */ | ||||
| /// Menu modes | ||||
| /// \addtogroup MENU_MODES | ||||
| /// @{ | ||||
| #define MENU_NORMAL_MODE        (1 << MENU_INDEX_NORMAL) | ||||
| #define MENU_VISUAL_MODE        (1 << MENU_INDEX_VISUAL) | ||||
| #define MENU_SELECT_MODE        (1 << MENU_INDEX_SELECT) | ||||
| @@ -26,31 +32,30 @@ | ||||
| #define MENU_CMDLINE_MODE       (1 << MENU_INDEX_CMDLINE) | ||||
| #define MENU_TIP_MODE           (1 << MENU_INDEX_TIP) | ||||
| #define MENU_ALL_MODES          ((1 << MENU_INDEX_TIP) - 1) | ||||
| /*note MENU_INDEX_TIP is not a 'real' mode*/ | ||||
| /// @} | ||||
|  | ||||
| /* Start a menu name with this to not include it on the main menu bar */ | ||||
| /// Start a menu name with this to not include it on the main menu bar | ||||
| #define MNU_HIDDEN_CHAR         ']' | ||||
|  | ||||
| typedef struct VimMenu vimmenu_T; | ||||
|  | ||||
| struct VimMenu { | ||||
|   int modes;                        /* Which modes is this menu visible for? */ | ||||
|   int enabled;                      /* for which modes the menu is enabled */ | ||||
|   char_u      *name;                /* Name of menu, possibly translated */ | ||||
|   char_u      *dname;               /* Displayed Name ("name" without '&') */ | ||||
|   char_u      *en_name;             /* "name" untranslated, NULL when "name" | ||||
|                                      * was not translated */ | ||||
|   char_u      *en_dname;            /* "dname" untranslated, NULL when "dname" | ||||
|                                      * was not translated */ | ||||
|   int mnemonic;                     /* mnemonic key (after '&') */ | ||||
|   char_u      *actext;              /* accelerator text (after TAB) */ | ||||
|   long priority;                     /* Menu order priority */ | ||||
|   char_u      *strings[MENU_MODES];   /* Mapped string for each mode */ | ||||
|   int noremap[MENU_MODES];           /* A REMAP_ flag for each mode */ | ||||
|   bool silent[MENU_MODES];          /* A silent flag for each mode */ | ||||
|   vimmenu_T   *children;            /* Children of sub-menu */ | ||||
|   vimmenu_T   *parent;              /* Parent of menu */ | ||||
|   vimmenu_T   *next;                /* Next item in menu */ | ||||
|   int modes;                         ///< Which modes is this menu visible for | ||||
|   int enabled;                       ///< for which modes the menu is enabled | ||||
|   char_u      *name;                 ///< Name of menu, possibly translated | ||||
|   char_u      *dname;                ///< Displayed Name ("name" without '&') | ||||
|   char_u      *en_name;              ///< "name" untranslated, NULL when | ||||
|                                      ///< was not translated | ||||
|   char_u      *en_dname;             ///< NULL when "dname" untranslated | ||||
|   int mnemonic;                      ///< mnemonic key (after '&') | ||||
|   char_u      *actext;               ///< accelerator text (after TAB) | ||||
|   long priority;                     ///< Menu order priority | ||||
|   char_u      *strings[MENU_MODES];  ///< Mapped string for each mode | ||||
|   int noremap[MENU_MODES];           ///< A \ref REMAP_VALUES flag for each mode | ||||
|   bool silent[MENU_MODES];           ///< A silent flag for each mode | ||||
|   vimmenu_T   *children;             ///< Children of sub-menu | ||||
|   vimmenu_T   *parent;               ///< Parent of menu | ||||
|   vimmenu_T   *next;                 ///< Next item in menu | ||||
| }; | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -2,6 +2,8 @@ local helpers = require('test.functional.helpers')(after_each) | ||||
| local clear, command, nvim = helpers.clear, helpers.command, helpers.nvim | ||||
| local expect, feed = helpers.expect, helpers.feed | ||||
| local eq, eval = helpers.eq, helpers.eval | ||||
| local funcs = helpers.funcs | ||||
|  | ||||
|  | ||||
| describe(':emenu', function() | ||||
|  | ||||
| @@ -56,3 +58,328 @@ describe(':emenu', function() | ||||
|       eq('thiscmdmode', eval('getcmdline()')) | ||||
|   end) | ||||
| end) | ||||
|  | ||||
| describe('menu_get', function() | ||||
|  | ||||
|   before_each(function() | ||||
|     clear() | ||||
|     command('nnoremenu &Test.Test inormal<ESC>') | ||||
|     command('inoremenu Test.Test insert') | ||||
|     command('vnoremenu Test.Test x') | ||||
|     command('cnoremenu Test.Test cmdmode') | ||||
|     command('menu Test.Nested.test level1') | ||||
|     command('menu Test.Nested.Nested2 level2') | ||||
|  | ||||
|     command('nnoremenu <script> Export.Script p') | ||||
|     command('tmenu Export.Script This is the tooltip') | ||||
|     command('menu ]Export.hidden thisoneshouldbehidden') | ||||
|  | ||||
|     command('nnoremenu Edit.Paste p') | ||||
|     command('cnoremenu Edit.Paste <C-R>"') | ||||
|   end) | ||||
|  | ||||
|   it('no path, all modes', function() | ||||
|     local m = funcs.menu_get("","a"); | ||||
|     -- You can use the following to print the expected table | ||||
|     -- and regenerate the tests: | ||||
|     -- local pretty = require('pl.pretty'); | ||||
|     -- print(pretty.dump(m)) | ||||
|     local expected = { | ||||
|       { | ||||
|         shortcut = "T", | ||||
|         hidden = 0, | ||||
|         submenus = { | ||||
|           { | ||||
|             mappings = { | ||||
|               i = { | ||||
|                 sid = 1, | ||||
|                 noremap = 1, | ||||
|                 enabled = 1, | ||||
|                 rhs = "insert", | ||||
|                 silent = 0 | ||||
|               }, | ||||
|               s = { | ||||
|                 sid = 1, | ||||
|                 noremap = 1, | ||||
|                 enabled = 1, | ||||
|                 rhs = "x", | ||||
|                 silent = 0 | ||||
|               }, | ||||
|               n = { | ||||
|                 sid = 1, | ||||
|                 noremap = 1, | ||||
|                 enabled = 1, | ||||
|                 rhs = "inormal\27", | ||||
|                 silent = 0 | ||||
|               }, | ||||
|               v = { | ||||
|                 sid = 1, | ||||
|                 noremap = 1, | ||||
|                 enabled = 1, | ||||
|                 rhs = "x", | ||||
|                 silent = 0 | ||||
|               }, | ||||
|               c = { | ||||
|                 sid = 1, | ||||
|                 noremap = 1, | ||||
|                 enabled = 1, | ||||
|                 rhs = "cmdmode", | ||||
|                 silent = 0 | ||||
|               } | ||||
|             }, | ||||
|             priority = 500, | ||||
|             name = "Test", | ||||
|             hidden = 0 | ||||
|           }, | ||||
|           { | ||||
|             priority = 500, | ||||
|             name = "Nested", | ||||
|             submenus = { | ||||
|               { | ||||
|                 mappings = { | ||||
|                   o = { | ||||
|                     sid = 0, | ||||
|                     noremap = 0, | ||||
|                     enabled = 1, | ||||
|                     rhs = "level1", | ||||
|                     silent = 0 | ||||
|                   }, | ||||
|                   v = { | ||||
|                     sid = 0, | ||||
|                     noremap = 0, | ||||
|                     enabled = 1, | ||||
|                     rhs = "level1", | ||||
|                     silent = 0 | ||||
|                   }, | ||||
|                   s = { | ||||
|                     sid = 0, | ||||
|                     noremap = 0, | ||||
|                     enabled = 1, | ||||
|                     rhs = "level1", | ||||
|                     silent = 0 | ||||
|                   }, | ||||
|                   n = { | ||||
|                     sid = 0, | ||||
|                     noremap = 0, | ||||
|                     enabled = 1, | ||||
|                     rhs = "level1", | ||||
|                     silent = 0 | ||||
|                   } | ||||
|                 }, | ||||
|                 priority = 500, | ||||
|                 name = "test", | ||||
|                 hidden = 0 | ||||
|               }, | ||||
|               { | ||||
|                 mappings = { | ||||
|                   o = { | ||||
|                     sid = 0, | ||||
|                     noremap = 0, | ||||
|                     enabled = 1, | ||||
|                     rhs = "level2", | ||||
|                     silent = 0 | ||||
|                   }, | ||||
|                   v = { | ||||
|                     sid = 0, | ||||
|                     noremap = 0, | ||||
|                     enabled = 1, | ||||
|                     rhs = "level2", | ||||
|                     silent = 0 | ||||
|                   }, | ||||
|                   s = { | ||||
|                     sid = 0, | ||||
|                     noremap = 0, | ||||
|                     enabled = 1, | ||||
|                     rhs = "level2", | ||||
|                     silent = 0 | ||||
|                   }, | ||||
|                   n = { | ||||
|                     sid = 0, | ||||
|                     noremap = 0, | ||||
|                     enabled = 1, | ||||
|                     rhs = "level2", | ||||
|                     silent = 0 | ||||
|                   } | ||||
|                 }, | ||||
|                 priority = 500, | ||||
|                 name = "Nested2", | ||||
|                 hidden = 0 | ||||
|               } | ||||
|             }, | ||||
|             hidden = 0 | ||||
|           } | ||||
|         }, | ||||
|         priority = 500, | ||||
|         name = "Test" | ||||
|       }, | ||||
|       { | ||||
|         priority = 500, | ||||
|         name = "Export", | ||||
|         submenus = { | ||||
|           { | ||||
|             tooltip = "This is the tooltip", | ||||
|             hidden = 0, | ||||
|             name = "Script", | ||||
|             priority = 500, | ||||
|             mappings = { | ||||
|               n = { | ||||
|                 sid = 1, | ||||
|                 noremap = 1, | ||||
|                 enabled = 1, | ||||
|                 rhs = "p", | ||||
|                 silent = 0 | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|         }, | ||||
|         hidden = 0 | ||||
|       }, | ||||
|       { | ||||
|         priority = 500, | ||||
|         name = "Edit", | ||||
|         submenus = { | ||||
|           { | ||||
|             mappings = { | ||||
|               c = { | ||||
|                 sid = 1, | ||||
|                 noremap = 1, | ||||
|                 enabled = 1, | ||||
|                 rhs = "\18\"", | ||||
|                 silent = 0 | ||||
|               }, | ||||
|               n = { | ||||
|                 sid = 1, | ||||
|                 noremap = 1, | ||||
|                 enabled = 1, | ||||
|                 rhs = "p", | ||||
|                 silent = 0 | ||||
|               } | ||||
|             }, | ||||
|             priority = 500, | ||||
|             name = "Paste", | ||||
|             hidden = 0 | ||||
|           } | ||||
|         }, | ||||
|         hidden = 0 | ||||
|       }, | ||||
|       { | ||||
|         priority = 500, | ||||
|         name = "]Export", | ||||
|         submenus = { | ||||
|           { | ||||
|             mappings = { | ||||
|               o = { | ||||
|                 sid = 0, | ||||
|                 noremap = 0, | ||||
|                 enabled = 1, | ||||
|                 rhs = "thisoneshouldbehidden", | ||||
|                 silent = 0 | ||||
|               }, | ||||
|               v = { | ||||
|                 sid = 0, | ||||
|                 noremap = 0, | ||||
|                 enabled = 1, | ||||
|                 rhs = "thisoneshouldbehidden", | ||||
|                 silent = 0 | ||||
|               }, | ||||
|               s = { | ||||
|                 sid = 0, | ||||
|                 noremap = 0, | ||||
|                 enabled = 1, | ||||
|                 rhs = "thisoneshouldbehidden", | ||||
|                 silent = 0 | ||||
|               }, | ||||
|               n = { | ||||
|                 sid = 0, | ||||
|                 noremap = 0, | ||||
|                 enabled = 1, | ||||
|                 rhs = "thisoneshouldbehidden", | ||||
|                 silent = 0 | ||||
|               } | ||||
|             }, | ||||
|             priority = 500, | ||||
|             name = "hidden", | ||||
|             hidden = 0 | ||||
|           } | ||||
|         }, | ||||
|         hidden = 1 | ||||
|       } | ||||
|     } | ||||
|     eq(expected, m) | ||||
|   end) | ||||
|  | ||||
|   it('matching path, default modes', function() | ||||
|     local m = funcs.menu_get("Export", "a") | ||||
|     local expected = { | ||||
|       { | ||||
|         tooltip = "This is the tooltip", | ||||
|         hidden = 0, | ||||
|         name = "Script", | ||||
|         priority = 500, | ||||
|         mappings = { | ||||
|           n = { | ||||
|             sid = 1, | ||||
|             noremap = 1, | ||||
|             enabled = 1, | ||||
|             rhs = "p", | ||||
|             silent = 0 | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     eq(expected, m) | ||||
|   end) | ||||
|  | ||||
|   it('no path, matching modes', function() | ||||
|     local m = funcs.menu_get("","i") | ||||
|     local expected = { | ||||
|       { | ||||
|         shortcut = "T", | ||||
|         hidden = 0, | ||||
|         submenus = { | ||||
|           { | ||||
|             mappings = { | ||||
|               i = { | ||||
|                 sid = 1, | ||||
|                 noremap = 1, | ||||
|                 enabled = 1, | ||||
|                 rhs = "insert", | ||||
|                 silent = 0 | ||||
|               } | ||||
|             }, | ||||
|             priority = 500, | ||||
|             name = "Test", | ||||
|             hidden = 0 | ||||
|           }, | ||||
|           { | ||||
|           } | ||||
|         }, | ||||
|         priority = 500, | ||||
|         name = "Test" | ||||
|       } | ||||
|     } | ||||
|     eq(expected, m) | ||||
|   end) | ||||
|  | ||||
|   it('matching path and modes', function() | ||||
|     local m = funcs.menu_get("Test","i") | ||||
|     local expected = { | ||||
|       { | ||||
|         mappings = { | ||||
|           i = { | ||||
|             sid = 1, | ||||
|             noremap = 1, | ||||
|             enabled = 1, | ||||
|             rhs = "insert", | ||||
|             silent = 0 | ||||
|           } | ||||
|         }, | ||||
|         priority = 500, | ||||
|         name = "Test", | ||||
|         hidden = 0 | ||||
|       } | ||||
|     } | ||||
|     eq(expected, m) | ||||
|   end) | ||||
|  | ||||
| end) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user