mirror of
https://github.com/neovim/neovim.git
synced 2024-12-19 18:55:14 -07:00
viml: introduce menu_get() function #6322
menu_get({path}, {modes}). See :h menu_get.
This commit is contained in:
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)
|
||||
|
Loading…
Reference in New Issue
Block a user