diff options
author | Matthieu Coudron <mattator@gmail.com> | 2017-03-21 03:21:53 +0100 |
---|---|---|
committer | Justin M. Keyes <justinkz@gmail.com> | 2017-07-28 01:27:58 +0200 |
commit | dc685387a3d60e9ea3d09c80c74d4613b618cf14 (patch) | |
tree | 76dc55b7f9bf077dd0f16c94d01a23e43663390a | |
parent | e6d54407ba8ce580fbd81cb9389eb9ce4483597b (diff) | |
download | rneovim-dc685387a3d60e9ea3d09c80c74d4613b618cf14.tar.gz rneovim-dc685387a3d60e9ea3d09c80c74d4613b618cf14.tar.bz2 rneovim-dc685387a3d60e9ea3d09c80c74d4613b618cf14.zip |
viml: introduce menu_get() function #6322
menu_get({path}, {modes}). See :h menu_get.
-rw-r--r-- | runtime/doc/eval.txt | 40 | ||||
-rw-r--r-- | src/nvim/eval.c | 14 | ||||
-rw-r--r-- | src/nvim/eval.lua | 7 | ||||
-rw-r--r-- | src/nvim/ex_cmds.lua | 2 | ||||
-rw-r--r-- | src/nvim/getchar.h | 15 | ||||
-rw-r--r-- | src/nvim/mbyte.c | 13 | ||||
-rw-r--r-- | src/nvim/menu.c | 296 | ||||
-rw-r--r-- | src/nvim/menu.h | 47 | ||||
-rw-r--r-- | test/functional/ex_cmds/menu_spec.lua | 327 |
9 files changed, 629 insertions, 132 deletions
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 3e75a42a62..3a27b2d7a0 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -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, diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 3ec8deb666..2988225110 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -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 diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 30766a0734..08baae4086 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -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}}, diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index 7203fbd97d..46f7a7bc40 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -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 diff --git a/src/nvim/getchar.h b/src/nvim/getchar.h index 28584e0534..e634273e0d 100644 --- a/src/nvim/getchar.h +++ b/src/nvim/getchar.h @@ -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 */ diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index 2acfb896d8..4440300640 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -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; diff --git a/src/nvim/menu.c b/src/nvim/menu.c index c8e6012e5c..91b615be30 100644 --- a/src/nvim/menu.c +++ b/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); } /* diff --git a/src/nvim/menu.h b/src/nvim/menu.h index a84b7d812e..5ff979f2bf 100644 --- a/src/nvim/menu.h +++ b/src/nvim/menu.h @@ -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 }; diff --git a/test/functional/ex_cmds/menu_spec.lua b/test/functional/ex_cmds/menu_spec.lua index 57198600b9..eca45efcf1 100644 --- a/test/functional/ex_cmds/menu_spec.lua +++ b/test/functional/ex_cmds/menu_spec.lua @@ -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) |