viml: introduce menu_get() function #6322

menu_get({path}, {modes}). See :h menu_get.
This commit is contained in:
Matthieu Coudron 2017-03-21 03:21:53 +01:00 committed by Justin M. Keyes
parent e6d54407ba
commit dc685387a3
9 changed files with 629 additions and 132 deletions

View File

@ -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 items in {expr} cannot be used as a Number this results in
an error. An empty |List| or |Dictionary| results in zero. 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()*
min({expr}) Return the minimum value of all items in {expr}. min({expr}) Return the minimum value of all items in {expr}.
{expr} can be a list or a dictionary. For a dictionary, {expr} can be a list or a dictionary. For a dictionary,

View File

@ -47,6 +47,7 @@
#include "nvim/mbyte.h" #include "nvim/mbyte.h"
#include "nvim/memline.h" #include "nvim/memline.h"
#include "nvim/memory.h" #include "nvim/memory.h"
#include "nvim/menu.h"
#include "nvim/message.h" #include "nvim/message.h"
#include "nvim/misc1.h" #include "nvim/misc1.h"
#include "nvim/keymap.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(list, list [, idx])" function
* "extend(dict, dict [, action])" function * "extend(dict, dict [, action])" function

View File

@ -208,6 +208,7 @@ return {
matchstr={args={2, 4}}, matchstr={args={2, 4}},
matchstrpos={args={2,4}}, matchstrpos={args={2,4}},
max={args=1}, max={args=1},
menu_get={args={1, 2}},
min={args=1}, min={args=1},
mkdir={args={1, 3}}, mkdir={args={1, 3}},
mode={args={0, 1}}, mode={args={0, 1}},

View File

@ -1,4 +1,4 @@
bit = require 'bit' local bit = require 'bit'
-- Description of the values below is contained in ex_cmds_defs.h file. -- Description of the values below is contained in ex_cmds_defs.h file.
local RANGE = 0x001 local RANGE = 0x001

View File

@ -5,12 +5,15 @@
#include "nvim/buffer_defs.h" #include "nvim/buffer_defs.h"
#include "nvim/ex_cmds_defs.h" #include "nvim/ex_cmds_defs.h"
/* Values for "noremap" argument of ins_typebuf(). Also used for /// Values for "noremap" argument of ins_typebuf(). Also used for
* map->m_noremap and menu->noremap[]. */ /// map->m_noremap and menu->noremap[].
#define REMAP_YES 0 /* allow remapping */ /// @addtogroup REMAP_VALUES
#define REMAP_NONE -1 /* no remapping */ /// @{
#define REMAP_SCRIPT -2 /* remap script-local mappings only */ #define REMAP_YES 0 ///< allow remapping
#define REMAP_SKIP -3 /* no remapping for first char */ #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_KEY -1 /* keylen value for incomplete key-code */
#define KEYLEN_PART_MAP -2 /* keylen value for incomplete mapping */ #define KEYLEN_PART_MAP -2 /* keylen value for incomplete mapping */

View File

@ -932,12 +932,13 @@ int utf_char2len(int c)
return 6; return 6;
} }
/* /// Convert Unicode character to UTF-8 string
* Convert Unicode character "c" to UTF-8 string in "buf[]". ///
* Returns the number of bytes. /// @param c character to convert to \p buf
* This does not include composing characters. /// @param[out] buf UTF-8 string generated from \p c, does not add \0
*/ /// @return the number of bytes (between 1 and 6)
int utf_char2bytes(int c, char_u *buf) /// @note This does not include composing characters.
int utf_char2bytes(int c, char_u *const buf)
{ {
if (c < 0x80) { /* 7 bits */ if (c < 0x80) { /* 7 bits */
buf[0] = c; buf[0] = c;

View File

@ -26,7 +26,7 @@
#include "nvim/state.h" #include "nvim/state.h"
#include "nvim/strings.h" #include "nvim/strings.h"
#include "nvim/ui.h" #include "nvim/ui.h"
#include "nvim/eval/typval.h"
#define MENUDEPTH 10 /* maximum depth of menus */ #define MENUDEPTH 10 /* maximum depth of menus */
@ -38,7 +38,7 @@
/* The character for each menu mode */ /// The character for each menu mode
static char_u menu_mode_chars[] = { 'n', 'v', 's', 'o', 'i', 'c', 't' }; static char_u menu_mode_chars[] = { 'n', 'v', 's', 'o', 'i', 'c', 't' };
static char_u e_notsubmenu[] = N_( static char_u e_notsubmenu[] = N_(
@ -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\""); static char_u e_nomenu[] = N_("E329: No menu \"%s\"");
/* /// Do the :menu command and relatives.
* Do the :menu command and relatives. /// @param eap Ex command arguments
*/
void void
ex_menu ( ex_menu(exarg_T *eap)
exarg_T *eap /* Ex command arguments */
)
{ {
char_u *menu_path; char_u *menu_path;
int modes; int modes;
char_u *map_to; char_u *map_to; // command mapped to the menu entry
int noremap; int noremap;
bool silent = false; bool silent = false;
int unmenu; 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) { if (STRNCMP(arg, "icon=", 5) == 0) {
arg += 5; arg += 5;
// icon = arg;
while (*arg != NUL && *arg != ' ') { while (*arg != NUL && *arg != ' ') {
if (*arg == '\\') if (*arg == '\\')
STRMOVE(arg, arg + 1); STRMOVE(arg, arg + 1);
@ -107,12 +107,12 @@ ex_menu (
} }
} }
/* // Fill in the priority table.
* Fill in the priority table. for (p = arg; *p; p++) {
*/ if (!ascii_isdigit(*p) && *p != '.') {
for (p = arg; *p; ++p)
if (!ascii_isdigit(*p) && *p != '.')
break; break;
}
}
if (ascii_iswhite(*p)) { if (ascii_iswhite(*p)) {
for (i = 0; i < MENUDEPTH && !ascii_iswhite(*arg); ++i) { for (i = 0; i < MENUDEPTH && !ascii_iswhite(*arg); ++i) {
pri_tab[i] = getdigits_long(&arg); pri_tab[i] = getdigits_long(&arg);
@ -226,8 +226,7 @@ ex_menu (
menuarg.modes = modes; menuarg.modes = modes;
menuarg.noremap[0] = noremap; menuarg.noremap[0] = noremap;
menuarg.silent[0] = silent; 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. * 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 /// 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 static int
add_menu_path( add_menu_path(
char_u *menu_path, const char_u *const menu_path,
vimmenu_T *menuarg, /* passes modes, iconfile, iconidx, vimmenu_T *menuarg,
icon_builtin, silent[0], noremap[0] */ const long *const pri_tab,
long *pri_tab, const char_u *const call_data
char_u *call_data
) )
{ {
char_u *path_name; char_u *path_name;
@ -296,8 +297,9 @@ add_menu_path (
if (map_to != NULL) { if (map_to != NULL) {
en_name = name; en_name = name;
name = map_to; name = map_to;
} else } else {
en_name = NULL; en_name = NULL;
}
dname = menu_text(name, NULL, NULL); dname = menu_text(name, NULL, NULL);
if (*dname == NUL) { if (*dname == NUL) {
/* Only a mnemonic or accelerator is not valid. */ /* Only a mnemonic or accelerator is not valid. */
@ -311,14 +313,15 @@ add_menu_path (
while (menu != NULL) { while (menu != NULL) {
if (menu_name_equal(name, menu) || menu_name_equal(dname, menu)) { if (menu_name_equal(name, menu) || menu_name_equal(dname, menu)) {
if (*next_name == NUL && menu->children != NULL) { if (*next_name == NUL && menu->children != NULL) {
if (!sys_menu) if (!sys_menu) {
EMSG(_("E330: Menu path must not lead to a sub-menu")); EMSG(_("E330: Menu path must not lead to a sub-menu"));
}
goto erret; goto erret;
} }
if (*next_name != NUL && menu->children == NULL if (*next_name != NUL && menu->children == NULL) {
) { if (!sys_menu) {
if (!sys_menu)
EMSG(_(e_notsubmenu)); EMSG(_(e_notsubmenu));
}
goto erret; goto erret;
} }
break; break;
@ -352,7 +355,7 @@ add_menu_path (
menu->modes = modes; menu->modes = modes;
menu->enabled = MENU_ALL_MODES; menu->enabled = MENU_ALL_MODES;
menu->name = vim_strsave(name); 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); menu->dname = menu_text(name, &menu->mnemonic, &menu->actext);
if (en_name != NULL) { if (en_name != NULL) {
menu->en_name = vim_strsave(en_name); menu->en_name = vim_strsave(en_name);
@ -364,9 +367,7 @@ add_menu_path (
menu->priority = pri_tab[pri_idx]; menu->priority = pri_tab[pri_idx];
menu->parent = parent; menu->parent = parent;
/* // Add after menu that has lower priority.
* Add after menu that has lower priority.
*/
menu->next = *lower_pri; menu->next = *lower_pri;
*lower_pri = menu; *lower_pri = menu;
@ -392,8 +393,9 @@ add_menu_path (
name = next_name; name = next_name;
xfree(dname); xfree(dname);
dname = NULL; dname = NULL;
if (pri_tab[pri_idx + 1] != -1) if (pri_tab[pri_idx + 1] != -1) {
++pri_idx; pri_idx++;
}
} }
xfree(path_name); xfree(path_name);
@ -419,8 +421,7 @@ add_menu_path (
// Don't do this for "<Nop>". // Don't do this for "<Nop>".
c = 0; c = 0;
d = 0; d = 0;
if (amenu && call_data != NULL && *call_data != NUL if (amenu && call_data != NULL && *call_data != NUL) {
) {
switch (1 << i) { switch (1 << i) {
case MENU_VISUAL_MODE: case MENU_VISUAL_MODE:
case MENU_SELECT_MODE: case MENU_SELECT_MODE:
@ -438,9 +439,9 @@ add_menu_path (
if (c != 0) { if (c != 0) {
menu->strings[i] = xmalloc(STRLEN(call_data) + 5 ); menu->strings[i] = xmalloc(STRLEN(call_data) + 5 );
menu->strings[i][0] = c; menu->strings[i][0] = c;
if (d == 0) if (d == 0) {
STRCPY(menu->strings[i] + 1, call_data); STRCPY(menu->strings[i] + 1, call_data);
else { } else {
menu->strings[i][1] = d; menu->strings[i][1] = d;
STRCPY(menu->strings[i] + 2, call_data); 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 + 1] = Ctrl_G;
menu->strings[i][len + 2] = NUL; menu->strings[i][len + 2] = NUL;
} }
} else } else {
menu->strings[i] = p; menu->strings[i] = p;
}
menu->noremap[i] = menuarg->noremap[0]; menu->noremap[i] = menuarg->noremap[0];
menu->silent[i] = menuarg->silent[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; menu->strings[idx] = NULL;
} }
/* /// Export menus
* Show the mapping associated with a menu item or hierarchy in a sub-menu. ///
*/ /// @param[in] menu if null, starts from root_menu
static int show_menus(char_u *path_name, int modes) /// @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)
{
vimmenu_T *menu;
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;
}
/// 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; char_u *p;
char_u *name;
vimmenu_T *menu;
vimmenu_T *parent = NULL;
menu = root_menu;
name = path_name = vim_strsave(path_name);
/* First, find the (sub)menu with the given name */
while (*name) { while (*name) {
p = menu_name_skip(name); p = menu_name_skip(name);
while (menu != NULL) { while (menu != NULL) {
@ -678,39 +769,46 @@ static int show_menus(char_u *path_name, int modes)
/* Found menu */ /* Found menu */
if (*p != NUL && menu->children == NULL) { if (*p != NUL && menu->children == NULL) {
EMSG(_(e_notsubmenu)); EMSG(_(e_notsubmenu));
xfree(path_name); return NULL;
return FAIL;
} else if ((menu->modes & modes) == 0x0) { } else if ((menu->modes & modes) == 0x0) {
EMSG(_(e_othermode)); EMSG(_(e_othermode));
xfree(path_name); return NULL;
return FAIL;
} }
break; break;
} }
menu = menu->next; menu = menu->next;
} }
if (menu == NULL) { if (menu == NULL) {
EMSG2(_(e_nomenu), name); EMSG2(_(e_nomenu), name);
xfree(path_name); return NULL;
return FAIL;
} }
name = p; name = p;
parent = menu;
menu = menu->children; 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 */ /* Now we have found the matching menu, and we list the mappings */
/* Highlight title */ /* Highlight title */
MSG_PUTS_TITLE(_("\n--- Menus ---")); MSG_PUTS_TITLE(_("\n--- Menus ---"));
show_menus_recursive(parent, modes, 0); show_menus_recursive(menu->parent, modes, 0);
return OK; 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) static void show_menus_recursive(vimmenu_T *menu, int modes, int depth)
{ {
int i; int i;
@ -993,12 +1091,13 @@ char_u *get_menu_names(expand_T *xp, int idx)
return str; return str;
} }
/*
* Skip over this element of the menu path and return the start of the next /// 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. /// element. Any \ and ^Vs are removed from the current element.
* "name" may be modified. ///
*/ /// @param name may be modified.
char_u *menu_name_skip(char_u *name) /// @return start of the next element
char_u *menu_name_skip(char_u *const name)
{ {
char_u *p; 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 * 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. * 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 if (menu->en_name != NULL
&& (menu_namecmp(name, menu->en_name) && (menu_namecmp(name, menu->en_name)
|| menu_namecmp(name, menu->en_dname))) || menu_namecmp(name, menu->en_dname)))
return TRUE; return true;
return menu_namecmp(name, menu->name) || menu_namecmp(name, menu->dname); 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; int i;
@ -1038,18 +1137,20 @@ static int menu_namecmp(char_u *name, char_u *mname)
&& (mname[i] == NUL || mname[i] == TAB); && (mname[i] == NUL || mname[i] == TAB);
} }
/*
* Return the modes specified by the given menu command (eg :menu! returns /// converts a string into a combination of \ref MENU_MODES
* MENU_CMDLINE_MODE | MENU_INSERT_MODE). /// (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. /// @param[in] cmd a string like 'n' (normal) or 'a' (all)
* If "unmenu" is not NULL, then the flag it points to is set according to /// @param[in] forceit Was there a "!" after the command?
* whether the command is an "unmenu" 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.
static int /// @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( get_menu_cmd_modes(
char_u *cmd, const char_u * cmd,
int forceit, /* Was there a "!" after the command? */ bool forceit,
int *noremap, int *noremap,
int *unmenu int *unmenu
) )
@ -1090,13 +1191,16 @@ get_menu_cmd_modes (
} }
/* FALLTHROUGH */ /* FALLTHROUGH */
default: default:
--cmd; cmd--;
if (forceit) /* menu!! */ if (forceit) {
// menu!!
modes = MENU_INSERT_MODE | MENU_CMDLINE_MODE; modes = MENU_INSERT_MODE | MENU_CMDLINE_MODE;
else /* menu */ } else {
// menu
modes = MENU_NORMAL_MODE | MENU_VISUAL_MODE | MENU_SELECT_MODE modes = MENU_NORMAL_MODE | MENU_VISUAL_MODE | MENU_SELECT_MODE
| MENU_OP_PENDING_MODE; | MENU_OP_PENDING_MODE;
} }
}
if (noremap != NULL) if (noremap != NULL)
*noremap = (*cmd == 'n' ? REMAP_NONE : REMAP_YES); *noremap = (*cmd == 'n' ? REMAP_NONE : REMAP_YES);
@ -1201,12 +1305,14 @@ int menu_is_separator(char_u *name)
return name[0] == '-' && name[STRLEN(name) - 1] == '-'; 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) 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);
} }
/* /*

View File

@ -6,7 +6,9 @@
#include "nvim/types.h" // for char_u and expand_T #include "nvim/types.h" // for char_u and expand_T
#include "nvim/ex_cmds_defs.h" // for exarg_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_INVALID -1
#define MENU_INDEX_NORMAL 0 #define MENU_INDEX_NORMAL 0
#define MENU_INDEX_VISUAL 1 #define MENU_INDEX_VISUAL 1
@ -16,8 +18,12 @@
#define MENU_INDEX_CMDLINE 5 #define MENU_INDEX_CMDLINE 5
#define MENU_INDEX_TIP 6 #define MENU_INDEX_TIP 6
#define MENU_MODES 7 #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_NORMAL_MODE (1 << MENU_INDEX_NORMAL)
#define MENU_VISUAL_MODE (1 << MENU_INDEX_VISUAL) #define MENU_VISUAL_MODE (1 << MENU_INDEX_VISUAL)
#define MENU_SELECT_MODE (1 << MENU_INDEX_SELECT) #define MENU_SELECT_MODE (1 << MENU_INDEX_SELECT)
@ -26,31 +32,30 @@
#define MENU_CMDLINE_MODE (1 << MENU_INDEX_CMDLINE) #define MENU_CMDLINE_MODE (1 << MENU_INDEX_CMDLINE)
#define MENU_TIP_MODE (1 << MENU_INDEX_TIP) #define MENU_TIP_MODE (1 << MENU_INDEX_TIP)
#define MENU_ALL_MODES ((1 << MENU_INDEX_TIP) - 1) #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 ']' #define MNU_HIDDEN_CHAR ']'
typedef struct VimMenu vimmenu_T; typedef struct VimMenu vimmenu_T;
struct VimMenu { struct VimMenu {
int modes; /* Which modes is this menu visible for? */ int modes; ///< Which modes is this menu visible for
int enabled; /* for which modes the menu is enabled */ int enabled; ///< for which modes the menu is enabled
char_u *name; /* Name of menu, possibly translated */ char_u *name; ///< Name of menu, possibly translated
char_u *dname; /* Displayed Name ("name" without '&') */ char_u *dname; ///< Displayed Name ("name" without '&')
char_u *en_name; /* "name" untranslated, NULL when "name" char_u *en_name; ///< "name" untranslated, NULL when
* was not translated */ ///< was not translated
char_u *en_dname; /* "dname" untranslated, NULL when "dname" char_u *en_dname; ///< NULL when "dname" untranslated
* was not translated */ int mnemonic; ///< mnemonic key (after '&')
int mnemonic; /* mnemonic key (after '&') */ char_u *actext; ///< accelerator text (after TAB)
char_u *actext; /* accelerator text (after TAB) */ long priority; ///< Menu order priority
long priority; /* Menu order priority */ char_u *strings[MENU_MODES]; ///< Mapped string for each mode
char_u *strings[MENU_MODES]; /* Mapped string for each mode */ int noremap[MENU_MODES]; ///< A \ref REMAP_VALUES flag for each mode
int noremap[MENU_MODES]; /* A REMAP_ flag for each mode */ bool silent[MENU_MODES]; ///< A silent flag for each mode
bool silent[MENU_MODES]; /* A silent flag for each mode */ vimmenu_T *children; ///< Children of sub-menu
vimmenu_T *children; /* Children of sub-menu */ vimmenu_T *parent; ///< Parent of menu
vimmenu_T *parent; /* Parent of menu */ vimmenu_T *next; ///< Next item in menu
vimmenu_T *next; /* Next item in menu */
}; };

View File

@ -2,6 +2,8 @@ local helpers = require('test.functional.helpers')(after_each)
local clear, command, nvim = helpers.clear, helpers.command, helpers.nvim local clear, command, nvim = helpers.clear, helpers.command, helpers.nvim
local expect, feed = helpers.expect, helpers.feed local expect, feed = helpers.expect, helpers.feed
local eq, eval = helpers.eq, helpers.eval local eq, eval = helpers.eq, helpers.eval
local funcs = helpers.funcs
describe(':emenu', function() describe(':emenu', function()
@ -56,3 +58,328 @@ describe(':emenu', function()
eq('thiscmdmode', eval('getcmdline()')) eq('thiscmdmode', eval('getcmdline()'))
end) end)
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)