diff options
-rw-r--r-- | runtime/doc/eval.txt | 62 | ||||
-rw-r--r-- | runtime/doc/gui.txt | 4 | ||||
-rw-r--r-- | runtime/doc/usr_41.txt | 3 | ||||
-rw-r--r-- | src/evalfunc.c | 13 | ||||
-rw-r--r-- | src/menu.c | 269 | ||||
-rw-r--r-- | src/proto/menu.pro | 1 | ||||
-rw-r--r-- | src/testdir/test_menu.vim | 357 | ||||
-rw-r--r-- | src/testdir/test_popup.vim | 6 | ||||
-rw-r--r-- | src/testdir/test_termcodes.vim | 35 | ||||
-rw-r--r-- | src/version.c | 2 |
10 files changed, 721 insertions, 31 deletions
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 5dfc08ea4..e3bb34eb7 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -2601,6 +2601,7 @@ matchstr({expr}, {pat} [, {start} [, {count}]]) matchstrpos({expr}, {pat} [, {start} [, {count}]]) List {count}'th match of {pat} in {expr} max({expr}) Number maximum value of items in {expr} +menu_info({name} [, {mode}]) Dict get menu item information min({expr}) Number minimum value of items in {expr} mkdir({name} [, {path} [, {prot}]]) Number create directory {name} @@ -7124,6 +7125,7 @@ matchstrpos({expr}, {pat} [, {start} [, {count}]]) *matchstrpos()* Can also be used as a |method|: > GetText()->matchstrpos('word') < + *max()* max({expr}) Return the maximum value of all items in {expr}. {expr} can be a List or a Dictionary. For a Dictionary, @@ -7135,6 +7137,66 @@ max({expr}) Return the maximum value of all items in {expr}. Can also be used as a |method|: > mylist->max() + +menu_info({name} [, {mode}]) *menu_info()* + Return information about the specified menu {name} in + mode {mode}. The menu name should be specified without the + shortcut character ('&'). + + {mode} can be one of these strings: + "n" Normal + "v" Visual (including Select) + "o" Operator-pending + "i" Insert + "c" Cmd-line + "s" Select + "x" Visual + "t" Terminal-Job + "" Normal, Visual and Operator-pending + "!" Insert and Cmd-line + When {mode} is omitted, the modes for "" are used. + + Returns a |Dictionary| containing the following items: + accel menu item accelerator text |menu-text| + display display name (name without '&') + enabled v:true if this menu item is enabled + Refer to |:menu-enable| + icon name of the icon file (for toolbar) + |toolbar-icon| + iconidx index of a built-in icon + modes modes for which the menu is defined. In + addition to the modes mentioned above, these + characters will be used: + " " Normal, Visual and Operator-pending + name menu item name. + noremenu v:true if the {rhs} of the menu item is not + remappable else v:false. + priority menu order priority |menu-priority| + rhs right-hand-side of the menu item. The returned + string has special characters translated like + in the output of the ":menu" command listing. + When the {rhs} of a menu item is empty, then + "<Nop>" is returned. + script v:true if script-local remapping of {rhs} is + allowed else v:false. See |:menu-script|. + shortcut shortcut key (character after '&' in + the menu name) |menu-shortcut| + silent v:true if the menu item is created + with <silent> argument |:menu-silent| + submenus |List| containing the names of + all the submenus. Present only if the menu + item has submenus. + + Returns an empty dictionary if the menu item is not found. + + Examples: > + :echo maparg('Edit.Cut') + :echo maparg('File.Save', 'n') +< + Can also be used as a |method|: > + GetMenuName()->maparg('v') + + < *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/runtime/doc/gui.txt b/runtime/doc/gui.txt index 1a3849604..41e20b106 100644 --- a/runtime/doc/gui.txt +++ b/runtime/doc/gui.txt @@ -578,9 +578,11 @@ tooltips for menus. See |terminal-typing|. Special characters in a menu name: + *menu-shortcut* & The next character is the shortcut key. Make sure each shortcut key is only used once in a (sub)menu. If you want to insert a literal "&" in the menu name use "&&". + *menu-text* <Tab> Separates the menu name from right-aligned text. This can be used to show the equivalent typed command. The text "<Tab>" can be used here for convenience. If you are using a real @@ -954,7 +956,7 @@ item for the keyword under the cursor. The register "z" is used. > mappings, or put these lines in your gvimrc; "<C-R>" is CTRL-R, "<CR>" is the <CR> key. |<>|) - + *tooltips* *menu-tips* 5.8 Tooltips & Menu tips See section |42.4| in the user manual. diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt index 1bed01f41..dfd64c85f 100644 --- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -942,10 +942,11 @@ Window size and position: *window-size-functions* winsaveview() get view of current window winrestview() restore saved view of current window -Mappings: *mapping-functions* +Mappings and Menus: *mapping-functions* hasmapto() check if a mapping exists mapcheck() check if a matching mapping exists maparg() get rhs of a mapping + menu_info() get information about a menu item wildmenumode() check if the wildmode is active Testing: *test-functions* diff --git a/src/evalfunc.c b/src/evalfunc.c index 9e8e0fed0..5db6718fb 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -646,6 +646,7 @@ static funcentry_T global_functions[] = {"matchstr", 2, 4, FEARG_1, ret_string, f_matchstr}, {"matchstrpos", 2, 4, FEARG_1, ret_list_any, f_matchstrpos}, {"max", 1, 1, FEARG_1, ret_any, f_max}, + {"menu_info", 1, 2, FEARG_1, ret_dict_any, f_menu_info}, {"min", 1, 1, FEARG_1, ret_any, f_min}, {"mkdir", 1, 3, FEARG_1, ret_number, f_mkdir}, {"mode", 0, 1, FEARG_1, ret_string, f_mode}, @@ -2469,7 +2470,17 @@ f_feedkeys(typval_T *argvars, typval_T *rettv UNUSED) if (lowlevel) { #ifdef USE_INPUT_BUF - add_to_input_buf(keys, (int)STRLEN(keys)); + int idx; + int len = (int)STRLEN(keys); + + for (idx = 0; idx < len; ++idx) + { + // if a CTRL-C was typed, set got_int + if (keys[idx] == 3 && ctrl_c_interrupts) + got_int = TRUE; + else + add_to_input_buf(keys + idx, 1); + } #else emsg(_("E980: lowlevel input not supported")); #endif diff --git a/src/menu.c b/src/menu.c index c57a6c1a6..8e33cff76 100644 --- a/src/menu.c +++ b/src/menu.c @@ -1685,6 +1685,49 @@ get_menu_cmd_modes( } /* + * Return the string representation of the menu modes. Does the opposite + * of get_menu_cmd_modes(). + */ + static char_u * +get_menu_mode_str(int modes) +{ + if ((modes & (MENU_INSERT_MODE | MENU_CMDLINE_MODE | MENU_NORMAL_MODE | + MENU_VISUAL_MODE | MENU_SELECT_MODE | MENU_OP_PENDING_MODE)) + == (MENU_INSERT_MODE | MENU_CMDLINE_MODE | MENU_NORMAL_MODE | + MENU_VISUAL_MODE | MENU_SELECT_MODE | MENU_OP_PENDING_MODE)) + return (char_u *)"a"; + if ((modes & (MENU_NORMAL_MODE | MENU_VISUAL_MODE | MENU_SELECT_MODE | + MENU_OP_PENDING_MODE)) + == (MENU_NORMAL_MODE | MENU_VISUAL_MODE | MENU_SELECT_MODE | + MENU_OP_PENDING_MODE)) + return (char_u *)" "; + if ((modes & (MENU_INSERT_MODE | MENU_CMDLINE_MODE)) + == (MENU_INSERT_MODE | MENU_CMDLINE_MODE)) + return (char_u *)"!"; + if ((modes & (MENU_VISUAL_MODE | MENU_SELECT_MODE)) + == (MENU_VISUAL_MODE | MENU_SELECT_MODE)) + return (char_u *)"v"; + if (modes & MENU_VISUAL_MODE) + return (char_u *)"x"; + if (modes & MENU_SELECT_MODE) + return (char_u *)"s"; + if (modes & MENU_OP_PENDING_MODE) + return (char_u *)"o"; + if (modes & MENU_INSERT_MODE) + return (char_u *)"i"; + if (modes & MENU_TERMINAL_MODE) + return (char_u *)"tl"; + if (modes & MENU_CMDLINE_MODE) + return (char_u *)"c"; + if (modes & MENU_NORMAL_MODE) + return (char_u *)"n"; + if (modes & MENU_TIP_MODE) + return (char_u *)"t"; + + return (char_u *)""; +} + +/* * Modify a menu name starting with "PopUp" to include the mode character. * Returns the name in allocated memory (NULL for failure). */ @@ -2393,40 +2436,21 @@ execute_menu(exarg_T *eap, vimmenu_T *menu, int mode_idx) } /* - * Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy and - * execute it. + * Lookup a menu by the descriptor name e.g. "File.New" + * Returns NULL if the menu is not found */ - void -ex_emenu(exarg_T *eap) + static vimmenu_T * +menu_getbyname(char_u *name_arg) { - vimmenu_T *menu; char_u *name; char_u *saved_name; - char_u *arg = eap->arg; + vimmenu_T *menu; char_u *p; int gave_emsg = FALSE; - int mode_idx = -1; - if (arg[0] && VIM_ISWHITE(arg[1])) - { - switch (arg[0]) - { - case 'n': mode_idx = MENU_INDEX_NORMAL; break; - case 'v': mode_idx = MENU_INDEX_VISUAL; break; - case 's': mode_idx = MENU_INDEX_SELECT; break; - case 'o': mode_idx = MENU_INDEX_OP_PENDING; break; - case 't': mode_idx = MENU_INDEX_TERMINAL; break; - case 'i': mode_idx = MENU_INDEX_INSERT; break; - case 'c': mode_idx = MENU_INDEX_CMDLINE; break; - default: semsg(_(e_invarg2), arg); - return; - } - arg = skipwhite(arg + 2); - } - - saved_name = vim_strsave(arg); + saved_name = vim_strsave(name_arg); if (saved_name == NULL) - return; + return NULL; menu = *get_root_menu(saved_name); name = saved_name; @@ -2463,10 +2487,45 @@ ex_emenu(exarg_T *eap) if (menu == NULL) { if (!gave_emsg) - semsg(_("E334: Menu not found: %s"), arg); - return; + semsg(_("E334: Menu not found: %s"), name_arg); + return NULL; + } + + return menu; +} + +/* + * Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy and + * execute it. + */ + void +ex_emenu(exarg_T *eap) +{ + vimmenu_T *menu; + char_u *arg = eap->arg; + int mode_idx = -1; + + if (arg[0] && VIM_ISWHITE(arg[1])) + { + switch (arg[0]) + { + case 'n': mode_idx = MENU_INDEX_NORMAL; break; + case 'v': mode_idx = MENU_INDEX_VISUAL; break; + case 's': mode_idx = MENU_INDEX_SELECT; break; + case 'o': mode_idx = MENU_INDEX_OP_PENDING; break; + case 't': mode_idx = MENU_INDEX_TERMINAL; break; + case 'i': mode_idx = MENU_INDEX_INSERT; break; + case 'c': mode_idx = MENU_INDEX_CMDLINE; break; + default: semsg(_(e_invarg2), arg); + return; + } + arg = skipwhite(arg + 2); } + menu = menu_getbyname(arg); + if (menu == NULL) + return; + // Found the menu, so execute. execute_menu(eap, menu, mode_idx); } @@ -2773,4 +2832,158 @@ menu_translate_tab_and_shift(char_u *arg_start) return arg; } +/* + * Get the information about a menu item in mode 'which' + */ + static int +menuitem_getinfo(vimmenu_T *menu, int modes, dict_T *dict) +{ + int status; + + if (menu_is_tearoff(menu->dname)) // skip tearoff menu item + return OK; + + status = dict_add_string(dict, "name", menu->name); + if (status == OK) + status = dict_add_string(dict, "display", menu->dname); + if (status == OK && menu->actext != NULL) + status = dict_add_string(dict, "accel", menu->actext); + if (status == OK) + status = dict_add_number(dict, "priority", menu->priority); + if (status == OK) + status = dict_add_string(dict, "modes", + get_menu_mode_str(menu->modes)); +#ifdef FEAT_TOOLBAR + if (status == OK && menu->iconfile != NULL) + status = dict_add_string(dict, "icon", menu->iconfile); + if (status == OK && menu->iconidx >= 0) + status = dict_add_number(dict, "iconidx", menu->iconidx); +#endif + if (status == OK) + { + char_u buf[NUMBUFLEN]; + + if (has_mbyte) + buf[utf_char2bytes(menu->mnemonic, buf)] = NUL; + else + { + buf[0] = (char_u)menu->mnemonic; + buf[1] = NUL; + } + status = dict_add_string(dict, "shortcut", buf); + } + if (status == OK && menu->children == NULL) + { + int bit; + + // Get the first mode in which the menu is available + for (bit = 0; (bit < MENU_MODES) && !((1 << bit) & modes); bit++) + ; + if (menu->strings[bit] != NULL) + status = dict_add_string(dict, "rhs", + *menu->strings[bit] == NUL ? + vim_strsave((char_u *)"<Nop>") : + str2special_save(menu->strings[bit], FALSE)); + if (status == OK) + status = dict_add_bool(dict, "noremenu", + menu->noremap[bit] == REMAP_NONE); + if (status == OK) + status = dict_add_bool(dict, "script", + menu->noremap[bit] == REMAP_SCRIPT); + if (status == OK) + status = dict_add_bool(dict, "silent", menu->silent[bit]); + if (status == OK) + status = dict_add_bool(dict, "enabled", + ((menu->enabled & (1 << bit)) != 0)); + } + // If there are submenus, add all the submenu display names + if (status == OK && menu->children != NULL) + { + list_T *l = list_alloc(); + vimmenu_T *child; + + if (l == NULL) + return FAIL; + + dict_add_list(dict, "submenus", l); + child = menu->children; + while (child) + { + if (!menu_is_tearoff(child->dname)) // skip tearoff menu + list_append_string(l, child->dname, -1); + child = child->next; + } + } + + return status; +} + +/* + * "menu_info()" function + * Return information about a menu (including all the child menus) + */ + void +f_menu_info(typval_T *argvars, typval_T *rettv) +{ + char_u *menu_name; + char_u *which; + int modes; + char_u *saved_name; + char_u *name; + vimmenu_T *menu; + dict_T *retdict; + + if (rettv_dict_alloc(rettv) != OK) + return; + retdict = rettv->vval.v_dict; + + menu_name = tv_get_string_chk(&argvars[0]); + if (menu_name == NULL) + return; + + // menu mode + if (argvars[1].v_type != VAR_UNKNOWN) + which = tv_get_string_chk(&argvars[1]); + else + which = (char_u *)""; // Default is modes for "menu" + if (which == NULL) + return; + + modes = get_menu_cmd_modes(which, *which == '!', NULL, NULL); + + // Locate the specified menu or menu item + menu = *get_root_menu(menu_name); + saved_name = vim_strsave(menu_name); + if (saved_name == NULL) + return; + if (*saved_name != NUL) + { + char_u *p; + + name = saved_name; + while (*name) + { + // Find in the menu hierarchy + p = menu_name_skip(name); + while (menu != NULL) + { + if (menu_name_equal(name, menu)) + break; + menu = menu->next; + } + if (menu == NULL || *p == NUL) + break; + menu = menu->children; + name = p; + } + } + vim_free(saved_name); + + if (menu == NULL) // specified menu not found + return; + + if (menu->modes & modes) + menuitem_getinfo(menu, modes, retdict); +} + #endif // FEAT_MENU diff --git a/src/proto/menu.pro b/src/proto/menu.pro index 95ee48b54..b95f5316d 100644 --- a/src/proto/menu.pro +++ b/src/proto/menu.pro @@ -23,4 +23,5 @@ void ex_emenu(exarg_T *eap); void winbar_click(win_T *wp, int col); vimmenu_T *gui_find_menu(char_u *path_name); void ex_menutranslate(exarg_T *eap); +void f_menu_info(typval_T *argvars, typval_T *rettv); /* vim: set ft=c : */ diff --git a/src/testdir/test_menu.vim b/src/testdir/test_menu.vim index 4af75be51..5e7a77b24 100644 --- a/src/testdir/test_menu.vim +++ b/src/testdir/test_menu.vim @@ -89,6 +89,35 @@ func Test_menu_commands() unlet g:did_menu endfun +" Test various menu related errors +func Test_menu_errors() + menu Test.Foo :version<CR> + + " Error cases + call assert_fails('menu .Test.Foo :ls<CR>', 'E475:') + call assert_fails('menu Test. :ls<CR>', 'E330:') + call assert_fails('menu Foo. :ls<CR>', 'E331:') + call assert_fails('unmenu Test.Foo abc', 'E488:') + call assert_fails('menu <Tab>:ls :ls<CR>', 'E792:') + call assert_fails('menu Test.<Tab>:ls :ls<CR>', 'E792:') + call assert_fails('menu Test.Foo.Bar :ls<CR>', 'E327:') + call assert_fails('menu Test.-Sep-.Baz :ls<CR>', 'E332:') + call assert_fails('menu Foo.Bar.--.Baz :ls<CR>', 'E332:') + call assert_fails('menu disable Test.Foo.Bar', 'E327:') + call assert_fails('menu disable T.Foo', 'E329:') + call assert_fails('unmenu Test.Foo.Bar', 'E327:') + call assert_fails('cunmenu Test.Foo', 'E328:') + call assert_fails('unmenu Test.Bar', 'E329:') + call assert_fails('menu Test.Foo.Bar', 'E327:') + call assert_fails('cmenu Test.Foo', 'E328:') + call assert_fails('emenu x Test.Foo', 'E475:') + call assert_fails('emenu Test.Foo.Bar', 'E334:') + call assert_fails('menutranslate Test', 'E474:') + + silent! unmenu Foo + unmenu Test +endfunc + " Test for menu item completion in command line func Test_menu_expand() " Create the menu itmes for test @@ -119,8 +148,336 @@ func Test_menu_expand() \ "\<C-A>\<C-B>\"\<CR>", 'xt') call assert_equal('"emenu Buffers. Xmenu.', @:) + " Test for expanding only submenus + call feedkeys(":popup Xmenu.\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"popup Xmenu.A1 A2 A3 A4', @:) + + " Test for expanding menus after enable/disable + call feedkeys(":menu enable Xmenu.\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"menu enable Xmenu.A1. A2. A3. A4.', @:) + call feedkeys(":menu disable Xmenu.\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"menu disable Xmenu.A1. A2. A3. A4.', @:) + + " Test for expanding non-existing menu path + call feedkeys(":menu xyz.\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"menu xyz.', @:) + call feedkeys(":menu Xmenu.A1.A1B1.xyz.\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"menu Xmenu.A1.A1B1.xyz.', @:) + set wildmenu& unmenu Xmenu + + " Test for expanding popup menus with some hidden items + menu Xmenu.foo.A1 a1 + menu Xmenu.]bar bar + menu Xmenu.]baz.B1 b1 + menu Xmenu.-sep- : + call feedkeys(":popup Xmenu.\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"popup Xmenu.foo', @:) + unmenu Xmenu + +endfunc + +" Test for the menu_info() function +func Test_menu_info() + " Define menus with various attributes + 10nnoremenu 10.10 T&est.F&oo :echo 'foo'<CR> + 10nmenu <silent> 10.20 T&est.B&ar<Tab>:bar :echo 'bar'<CR> + 10nmenu <script> 10.30.5 T&est.Ba&z.Qu&x :echo 'qux'<CR> + + let d = #{name: "B&ar\t:bar", display: 'Bar', modes: 'n', shortcut: 'a', + \ accel: ':bar', priority: 20, enabled: v:true, silent: v:true, + \ noremenu: v:false, script: v:false, rhs: ":echo 'bar'<CR>"} + call assert_equal(d, menu_info('Test.Bar')) + + let d = #{name: 'Ba&z', display: 'Baz', modes: 'n', shortcut: 'z', + \ priority: 30, submenus: ['Qux']} + call assert_equal(d, menu_info('Test.Baz')) + + let d = #{name: 'T&est', display: 'Test', modes: 'n', shortcut: 'e', + \ priority: 10, submenus: ['Foo', 'Bar', 'Baz']} + call assert_equal(d, menu_info('Test')) + call assert_equal({}, menu_info('Test.Dummy')) + call assert_equal({}, menu_info('Dummy')) + + nmenu disable Test.Foo + call assert_equal(v:false, menu_info('Test.Foo').enabled) + nmenu enable Test.Foo + call assert_equal(v:true, menu_info('Test.Foo').enabled) + + call assert_equal(menu_info('Test.Foo'), menu_info('Test.Foo', '')) + nmenu Test.abc <Nop> + call assert_equal('<Nop>', menu_info('Test.abc').rhs) + call assert_fails('call menu_info([])', 'E730:') + nunmenu Test + + " Test for defining menus in different modes + menu Test.menu :menu<CR> + menu! Test.menu! :menu!<CR> + amenu Test.amenu :amenu<CR> + nmenu Test.nmenu :nmenu<CR> + omenu Test.omenu :omenu<CR> + vmenu Test.vmenu :vmenu<CR> + xmenu Test.xmenu :xmenu<CR> + smenu Test.smenu :smenu<CR> + imenu <silent> <script> Test.imenu :imenu<CR> + cmenu Test.cmenu :cmenu<CR> + tlmenu Test.tlmenu :tlmenu<CR> + tmenu Test.nmenu Normal mode menu + tmenu Test.omenu Op-pending mode menu + noremenu Test.noremenu :noremenu<CR> + noremenu! Test.noremenu! :noremenu!<CR> + anoremenu Test.anoremenu :anoremenu<CR> + nnoremenu Test.nnoremenu :nnoremenu<CR> + onoremenu Test.onoremenu :onoremenu<CR> + vnoremenu Test.vnoremenu :vnoremenu<CR> + xnoremenu Test.xnoremenu :xnoremenu<CR> + snoremenu Test.snoremenu :snoremenu<CR> + inoremenu <silent> Test.inoremenu :inoremenu<CR> + cnoremenu Test.cnoremenu :cnoremenu<CR> + tlnoremenu Test.tlnoremenu :tlnoremenu<CR> + call assert_equal(#{name: 'menu', priority: 500, shortcut: '', + \ display: 'menu', modes: ' ', enabled: v:true, silent: v:false, + \ rhs: ":menu<CR>", noremenu: v:false, script: v:false}, + \ menu_info('Test.menu')) + call assert_equal(#{name: 'menu!', priority: 500, shortcut: '', + \ display: 'menu!', modes: '!', enabled: v:true, silent: v:false, + \ rhs: ":menu!<CR>", noremenu: v:false, script: v:false}, + \ menu_info('Test.menu!', '!')) + call assert_equal(#{name: 'amenu', priority: 500, shortcut: '', + \ display: 'amenu', modes: 'a', enabled: v:true, silent: v:false, + \ rhs: ":amenu<CR>", noremenu: v:false, script: v:false}, + \ menu_info('Test.amenu', 'a')) + call assert_equal(#{name: 'nmenu', priority: 500, shortcut: '', + \ display: 'nmenu', modes: 'n', enabled: v:true, silent: v:false, + \ rhs: ':nmenu<CR>', noremenu: v:false, script: v:false}, + \ menu_info('Test.nmenu', 'n')) + call assert_equal(#{name: 'omenu', priority: 500, shortcut: '', + \ display: 'omenu', modes: 'o', enabled: v:true, silent: v:false, + \ rhs: ':omenu<CR>', noremenu: v:false, script: v:false}, + \ menu_info('Test.omenu', 'o')) + call assert_equal(#{name: 'vmenu', priority: 500, shortcut: '', + \ display: 'vmenu', modes: 'v', enabled: v:true, silent: v:false, + \ rhs: ':vmenu<CR>', noremenu: v:false, script: v:false}, + \ menu_info('Test.vmenu', 'v')) + call assert_equal(#{name: 'xmenu', priority: 500, shortcut: '', + \ display: 'xmenu', modes: 'x', enabled: v:true, silent: v:false, + \ rhs: ':xmenu<CR>', noremenu: v:false, script: v:false}, + \ menu_info('Test.xmenu', 'x')) + call assert_equal(#{name: 'smenu', priority: 500, shortcut: '', + \ display: 'smenu', modes: 's', enabled: v:true, silent: v:false, + \ rhs: ':smenu<CR>', noremenu: v:false, script: v:false}, + \ menu_info('Test.smenu', 's')) + call assert_equal(#{name: 'imenu', priority: 500, shortcut: '', + \ display: 'imenu', modes: 'i', enabled: v:true, silent: v:true, + \ rhs: ':imenu<CR>', noremenu: v:false, script: v:true}, + \ menu_info('Test.imenu', 'i')) + call assert_equal(#{ name: 'cmenu', priority: 500, shortcut: '', + \ display: 'cmenu', modes: 'c', enabled: v:true, silent: v:false, + \ rhs: ':cmenu<CR>', noremenu: v:false, script: v:false}, + \ menu_info('Test.cmenu', 'c')) + call assert_equal(#{name: 'tlmenu', priority: 500, shortcut: '', + \ display: 'tlmenu', modes: 'tl', enabled: v:true, silent: v:false, + \ rhs: ':tlmenu<CR>', noremenu: v:false, script: v:false}, + \ menu_info('Test.tlmenu', 'tl')) + call assert_equal(#{name: 'noremenu', priority: 500, shortcut: '', + \ display: 'noremenu', modes: ' ', enabled: v:true, silent: v:false, + \ rhs: ":noremenu<CR>", noremenu: v:true, script: v:false}, + \ menu_info('Test.noremenu')) + call assert_equal(#{name: 'noremenu!', priority: 500, shortcut: '', + \ display: 'noremenu!', modes: '!', enabled: v:true, silent: v:false, + \ rhs: ":noremenu!<CR>", noremenu: v:true, script: v:false}, + \ menu_info('Test.noremenu!', '!')) + call assert_equal(#{name: 'anoremenu', priority: 500, shortcut: '', + \ display: 'anoremenu', modes: 'a', enabled: v:true, silent: v:false, + \ rhs: ":anoremenu<CR>", noremenu: v:true, script: v:false}, + \ menu_info('Test.anoremenu', 'a')) + call assert_equal(#{name: 'nnoremenu', priority: 500, shortcut: '', + \ display: 'nnoremenu', modes: 'n', enabled: v:true, silent: v:false, + \ rhs: ':nnoremenu<CR>', noremenu: v:true, script: v:false}, + \ menu_info('Test.nnoremenu', 'n')) + call assert_equal(#{name: 'onoremenu', priority: 500, shortcut: '', + \ display: 'onoremenu', modes: 'o', enabled: v:true, silent: v:false, + \ rhs: ':onoremenu<CR>', noremenu: v:true, script: v:false}, + \ menu_info('Test.onoremenu', 'o')) + call assert_equal(#{name: 'vnoremenu', priority: 500, shortcut: '', + \ display: 'vnoremenu', modes: 'v', enabled: v:true, silent: v:false, + \ rhs: ':vnoremenu<CR>', noremenu: v:true, script: v:false}, + \ menu_info('Test.vnoremenu', 'v')) + call assert_equal(#{name: 'xnoremenu', priority: 500, shortcut: '', + \ display: 'xnoremenu', modes: 'x', enabled: v:true, silent: v:false, + \ rhs: ':xnoremenu<CR>', noremenu: v:true, script: v:false}, + \ menu_info('Test.xnoremenu', 'x')) + call assert_equal(#{name: 'snoremenu', priority: 500, shortcut: '', + \ display: 'snoremenu', modes: 's', enabled: v:true, silent: v:false, + \ rhs: ':snoremenu<CR>', noremenu: v:true, script: v:false}, + \ menu_info('Test.snoremenu', 's')) + call assert_equal(#{name: 'inoremenu', priority: 500, shortcut: '', + \ display: 'inoremenu', modes: 'i', enabled: v:true, silent: v:true, + \ rhs: ':inoremenu<CR>', noremenu: v:true, script: v:false}, + \ menu_info('Test.inoremenu', 'i')) + call assert_equal(#{ name: 'cnoremenu', priority: 500, shortcut: '', + \ display: 'cnoremenu', modes: 'c', enabled: v:true, silent: v:false, + \ rhs: ':cnoremenu<CR>', noremenu: v:true, script: v:false}, + \ menu_info('Test.cnoremenu', 'c')) + call assert_equal(#{name: 'tlnoremenu', priority: 500, shortcut: '', + \ display: 'tlnoremenu', modes: 'tl', enabled: v:true, silent: v:false, + \ rhs: ':tlnoremenu<CR>', noremenu: v:true, script: v:false}, + \ menu_info('Test.tlnoremenu', 'tl')) + aunmenu Test + tlunmenu Test + call assert_equal({}, menu_info('Test')) + call assert_equal({}, menu_info('Test', '!')) + call assert_equal({}, menu_info('Test', 'a')) + call assert_equal({}, menu_info('Test', 'n')) + call assert_equal({}, menu_info('Test', 'o')) + call assert_equal({}, menu_info('Test', 'v')) + call assert_equal({}, menu_info('Test', 'x')) + call assert_equal({}, menu_info('Test', 's')) + call assert_equal({}, menu_info('Test', 'i')) + call assert_equal({}, menu_info('Test', 'c')) + call assert_equal({}, menu_info('Test', 't')) + call assert_equal({}, menu_info('Test', 'tl')) + + amenu Test.amenu :amenu<CR> + call assert_equal(':amenu<CR>', menu_info('Test.amenu', '').rhs) + call assert_equal('<C-\><C-O>:amenu<CR>', menu_info('Test.amenu', '!').rhs) + call assert_equal(':amenu<CR>', menu_info('Test.amenu', 'n').rhs) + call assert_equal('<C-C>:amenu<CR><C-\><C-G>', + \ menu_info('Test.amenu', 'o').rhs) + call assert_equal('<C-C>:amenu<CR><C-\><C-G>', + \ menu_info('Test.amenu', 'v').rhs) + call assert_equal('<C-C>:amenu<CR><C-\><C-G>', + \ menu_info('Test.amenu', 'x').rhs) + call assert_equal('<C-C>:amenu<CR><C-\><C-G>', + \ menu_info('Test.amenu', 's').rhs) + call assert_equal('<C-\><C-O>:amenu<CR>', menu_info('Test.amenu', 'i').rhs) + call assert_equal('<C-C>:amenu<CR><C-\><C-G>', + \ menu_info('Test.amenu', 'c').rhs) + aunmenu Test.amenu + + " Test for hidden menus + menu ]Test.menu :menu<CR> + call assert_equal(#{name: ']Test', display: ']Test', priority: 500, + \ shortcut: '', modes: ' ', submenus: ['menu']}, + \ menu_info(']Test')) + unmenu ]Test +endfunc + +" Test for <special> keyword in a menu with 'cpo' containing '<' +func Test_menu_special() + new + set cpo+=< + nmenu Test.Sign am<Tab>n<Esc> + call feedkeys(":emenu n Test.Sign\<CR>", 'x') + call assert_equal("m<Tab>n<Esc>", getline(1)) + nunmenu Test.Sign + nmenu <special> Test.Sign am<Tab>n<Esc> + call setline(1, '') + call feedkeys(":emenu n Test.Sign\<CR>", 'x') + call assert_equal("m\tn", getline(1)) + set cpo-=< + close! + nunmenu Test.Sign +endfunc + +" Test for "icon=filname" in a toolbar +func Test_menu_icon() + CheckFeature toolbar + nmenu icon=myicon.xpm Toolbar.Foo :echo "Foo"<CR> + call assert_equal('myicon.xpm', "Toolbar.Foo"->menu_info().icon) + nunmenu Toolbar.Foo + + " Test for using the builtin icon + amenu ToolBar.BuiltIn22 :echo "BuiltIn22"<CR> + call assert_equal(#{name: 'BuiltIn22', display: 'BuiltIn22', + \ enabled: v:true, shortcut: '', modes: 'a', script: v:false, + \ iconidx: 22, priority: 500, silent: v:false, + \ rhs: ':echo "BuiltIn22"<CR>', noremenu: v:false}, + \ menu_info("ToolBar.BuiltIn22")) + aunmenu ToolBar.BuiltIn22 +endfunc + +" Test for ":emenu" command in different modes +func Test_emenu_cmd() + new + xmenu Test.foo rx + call setline(1, ['aaaa', 'bbbb']) + normal ggVj + %emenu Test.foo + call assert_equal(['xxxx', 'xxxx'], getline(1, 2)) + call setline(1, ['aaaa', 'bbbb']) + exe "normal ggVj\<Esc>" + %emenu Test.foo + call assert_equal(['xxxx', 'xxxx'], getline(1, 2)) + call setline(1, ['aaaa', 'bbbb']) + exe "normal ggV\<Esc>" + 2emenu Test.foo + call assert_equal(['aaaa', 'xxxx'], getline(1, 2)) + xunmenu Test.foo + close! +endfunc + +" Test for PopUp menus +func Test_popup_menu() + 20menu PopUp.foo :echo 'foo'<CR> + 20menu PopUp.bar :echo 'bar'<CR> + call assert_equal(#{name: 'PopUp', display: 'PopUp', priority: 20, + \ shortcut: '', modes: ' ', submenus: ['foo', 'bar']}, + \ menu_info('PopUp')) + menu disable PopUp.bar + call assert_equal(v:true, "PopUp.foo"->menu_info().enabled) + call assert_equal(v:false, "PopUp.bar"->menu_info().enabled) + menu enable PopUp.bar + call assert_equal(v:true, "PopUp.bar"->menu_info().enabled) + unmenu PopUp +endfunc + +" Test for listing the menus using the :menu command +func Test_show_menus() + " In the GUI, tear-off menu items are present in the output below + " So skip this test + CheckNotGui + aunmenu * + call assert_equal(['--- Menus ---'], split(execute('menu'), "\n")) + nmenu <script> 200.10 Test.nmenu1 :nmenu1<CR> + nmenu 200.20 Test.nmenu2 :nmenu2<CR> + nnoremenu 200.30 Test.nmenu3 :nmenu3<CR> + nmenu 200.40 Test.nmenu4 :nmenu4<CR> + nmenu 200.50 disable Test.nmenu4 + let exp =<< trim [TEXT] + --- Menus --- + 200 Test + 10 nmenu1 + n& :nmenu1<CR> + 20 nmenu2 + n :nmenu2<CR> + 30 nmenu3 + n* :nmenu3<CR> + 40 nmenu4 + n - :nmenu4<CR> + [TEXT] + call assert_equal(exp, split(execute('nmenu'), "\n")) + nunmenu Test +endfunc + +" Test for menu tips +func Test_tmenu() + tunmenu * + call assert_equal(['--- Menus ---'], split(execute('tmenu'), "\n")) + tmenu Test.nmenu1 nmenu1 + tmenu Test.nmenu2.sub1 nmenu2.sub1 + let exp =<< trim [TEXT] + --- Menus --- + 500 Test + 500 nmenu1 + t - nmenu1 + 500 nmenu2 + 500 sub1 + t - nmenu2.sub1 + [TEXT] + call assert_equal(exp, split(execute('tmenu'), "\n")) + tunmenu Test endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/testdir/test_popup.vim b/src/testdir/test_popup.vim index 5922efa0e..fdc009f4e 100644 --- a/src/testdir/test_popup.vim +++ b/src/testdir/test_popup.vim @@ -851,6 +851,12 @@ func Test_popup_command() CheckScreendump CheckFeature menu + menu Test.Foo Foo + call assert_fails('popup Test.Foo', 'E336:') + call assert_fails('popup Test.Foo.X', 'E327:') + call assert_fails('popup Foo', 'E337:') + unmenu Test.Foo + let lines =<< trim END one two three four five and one two Xthree four five diff --git a/src/testdir/test_termcodes.vim b/src/testdir/test_termcodes.vim index 33ac52f11..3925bd7c1 100644 --- a/src/testdir/test_termcodes.vim +++ b/src/testdir/test_termcodes.vim @@ -979,6 +979,39 @@ func Test_term_mouse_middle_click_in_cmdline_to_paste() call test_override('no_query_mouse', 0) endfunc +" Test for displaying the popup menu using the right mouse click +func Test_mouse_popup_menu() + CheckFeature menu + new + call setline(1, 'popup menu test') + let save_mouse = &mouse + let save_term = &term + let save_ttymouse = &ttymouse + let save_mousemodel = &mousemodel + call test_override('no_query_mouse', 1) + set mouse=a term=xterm mousemodel=popup + + menu PopUp.foo :let g:menustr = 'foo'<CR> + menu PopUp.bar :let g:menustr = 'bar'<CR> + menu PopUp.baz :let g:menustr = 'baz'<CR> + + for ttymouse_val in s:ttymouse_values + exe 'set ttymouse=' .. ttymouse_val + let g:menustr = '' + call feedkeys(MouseRightClickCode(1, 4) + \ .. MouseRightReleaseCode(1, 4) .. "\<Down>\<Down>\<CR>", "x") + call assert_equal('bar', g:menustr) + endfor + + unmenu PopUp + let &mouse = save_mouse + let &term = save_term + let &ttymouse = save_ttymouse + let &mousemodel = save_mousemodel + call test_override('no_query_mouse', 0) + close! +endfunc + " This only checks if the sequence is recognized. func Test_term_rgb_response() set t_RF=x @@ -1501,3 +1534,5 @@ func Test_cmdline_literal() set timeoutlen& endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/version.c b/src/version.c index e9e1ec908..abf732227 100644 --- a/src/version.c +++ b/src/version.c @@ -739,6 +739,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 385, +/**/ 384, /**/ 383, |