To: vim_dev@googlegroups.com Subject: Patch 8.2.0385 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.0385 Problem: Menu functionality insufficiently tested. Solution: Add tests. Add menu_info(). (Yegappan Lakshmanan, closes #5760) Files: runtime/doc/eval.txt, runtime/doc/gui.txt, runtime/doc/usr_41.txt, src/evalfunc.c, src/menu.c, src/proto/menu.pro, src/testdir/test_menu.vim, src/testdir/test_popup.vim, src/testdir/test_termcodes.vim *** ../vim-8.2.0384/runtime/doc/eval.txt 2020-02-26 16:15:31.060386992 +0100 --- runtime/doc/eval.txt 2020-03-15 16:04:07.301905982 +0100 *************** *** 2599,2604 **** --- 2601,2607 ---- 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} *************** *** 2903,2908 **** --- 2906,2912 ---- win_screenpos({nr}) List get screen position of window {nr} win_splitmove({nr}, {target} [, {options}]) Number move window {nr} to split of {target} + win_type([{nr}]) String type of window {nr} winbufnr({nr}) Number buffer number of window {nr} wincol() Number window column of the cursor winheight({nr}) Number height of window {nr} *************** *** 4017,4032 **** arguments. executable() uses the value of $PATH and/or the normal searchpath for programs. *PATHEXT* ! On MS-DOS and MS-Windows the ".exe", ".bat", etc. can ! optionally be included. Then the extensions in $PATHEXT are ! tried. Thus if "foo.exe" does not exist, "foo.exe.bat" can be ! found. If $PATHEXT is not set then ".exe;.com;.bat;.cmd" is ! used. A dot by itself can be used in $PATHEXT to try using ! the name without an extension. When 'shell' looks like a ! Unix shell, then the name is also tried without adding an ! extension. ! On MS-DOS and MS-Windows it only checks if the file exists and ! is not a directory, not if it's really executable. On MS-Windows an executable in the same directory as Vim is always found. Since this directory is added to $PATH it should also work to execute it |win32-PATH|. --- 4021,4035 ---- arguments. executable() uses the value of $PATH and/or the normal searchpath for programs. *PATHEXT* ! On MS-Windows the ".exe", ".bat", etc. can optionally be ! included. Then the extensions in $PATHEXT are tried. Thus if ! "foo.exe" does not exist, "foo.exe.bat" can be found. If ! $PATHEXT is not set then ".exe;.com;.bat;.cmd" is used. A dot ! by itself can be used in $PATHEXT to try using the name ! without an extension. When 'shell' looks like a Unix shell, ! then the name is also tried without adding an extension. ! On MS-Windows it only checks if the file exists and is not a ! directory, not if it's really executable. On MS-Windows an executable in the same directory as Vim is always found. Since this directory is added to $PATH it should also work to execute it |win32-PATH|. *************** *** 7107,7128 **** 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, ! it returns the maximum of all values in the dictionary. ! If {expr} is neither a list nor a dictionary, or one of the items in {expr} cannot be used as a Number this results in an error. An empty |List| or |Dictionary| results in zero. Can also be used as a |method|: > mylist->max() < *min()* min({expr}) Return the minimum value of all items in {expr}. ! {expr} can be a list or a dictionary. For a dictionary, ! it returns the minimum of all values in the dictionary. ! If {expr} is neither a list nor a dictionary, or one of the items in {expr} cannot be used as a Number this results in an error. An empty |List| or |Dictionary| results in zero. --- 7125,7207 ---- 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, ! it returns the maximum of all values in the Dictionary. ! If {expr} is neither a List nor a Dictionary, or one of the items in {expr} cannot be used as a Number this results in an error. An empty |List| or |Dictionary| results in zero. 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 + "" 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 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, ! it returns the minimum of all values in the Dictionary. ! If {expr} is neither a List nor a Dictionary, or one of the items in {expr} cannot be used as a Number this results in an error. An empty |List| or |Dictionary| results in zero. *** ../vim-8.2.0384/runtime/doc/gui.txt 2020-02-26 16:15:31.060386992 +0100 --- runtime/doc/gui.txt 2020-03-15 16:00:11.478894806 +0100 *************** *** 578,586 **** --- 578,588 ---- 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* Separates the menu name from right-aligned text. This can be used to show the equivalent typed command. The text "" can be used here for convenience. If you are using a real *************** *** 954,960 **** mappings, or put these lines in your gvimrc; "" is CTRL-R, "" is the key. |<>|) ! 5.8 Tooltips & Menu tips See section |42.4| in the user manual. --- 956,962 ---- mappings, or put these lines in your gvimrc; "" is CTRL-R, "" is the key. |<>|) ! *tooltips* *menu-tips* 5.8 Tooltips & Menu tips See section |42.4| in the user manual. *** ../vim-8.2.0384/runtime/doc/usr_41.txt 2020-02-12 22:15:14.852205223 +0100 --- runtime/doc/usr_41.txt 2020-03-15 16:07:47.604979784 +0100 *************** *** 942,951 **** winsaveview() get view of current window winrestview() restore saved view of current window ! Mappings: *mapping-functions* hasmapto() check if a mapping exists mapcheck() check if a matching mapping exists maparg() get rhs of a mapping wildmenumode() check if the wildmode is active Testing: *test-functions* --- 942,952 ---- winsaveview() get view of current window winrestview() restore saved view of current window ! 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* *** ../vim-8.2.0384/src/evalfunc.c 2020-03-01 20:33:57.175639667 +0100 --- src/evalfunc.c 2020-03-15 16:00:11.482894787 +0100 *************** *** 646,651 **** --- 646,652 ---- {"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,2475 **** if (lowlevel) { #ifdef USE_INPUT_BUF ! add_to_input_buf(keys, (int)STRLEN(keys)); #else emsg(_("E980: lowlevel input not supported")); #endif --- 2470,2486 ---- if (lowlevel) { #ifdef USE_INPUT_BUF ! 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 *** ../vim-8.2.0384/src/menu.c 2020-02-26 16:15:31.072386953 +0100 --- src/menu.c 2020-03-15 16:09:24.108661467 +0100 *************** *** 1685,1690 **** --- 1685,1733 ---- } /* + * 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,2432 **** } /* ! * 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 *name; char_u *saved_name; ! char_u *arg = eap->arg; 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); if (saved_name == NULL) ! return; menu = *get_root_menu(saved_name); name = saved_name; --- 2436,2456 ---- } /* ! * Lookup a menu by the descriptor name e.g. "File.New" ! * Returns NULL if the menu is not found */ ! static vimmenu_T * ! menu_getbyname(char_u *name_arg) { char_u *name; char_u *saved_name; ! vimmenu_T *menu; char_u *p; int gave_emsg = FALSE; ! saved_name = vim_strsave(name_arg); if (saved_name == NULL) ! return NULL; menu = *get_root_menu(saved_name); name = saved_name; *************** *** 2463,2472 **** if (menu == NULL) { if (!gave_emsg) ! semsg(_("E334: Menu not found: %s"), arg); ! return; } // Found the menu, so execute. execute_menu(eap, menu, mode_idx); } --- 2487,2531 ---- if (menu == NULL) { if (!gave_emsg) ! 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,2776 **** --- 2832,2989 ---- 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 *)"") : + 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 *** ../vim-8.2.0384/src/proto/menu.pro 2019-12-12 12:55:27.000000000 +0100 --- src/proto/menu.pro 2020-03-15 16:09:27.524653321 +0100 *************** *** 23,26 **** --- 23,27 ---- 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 : */ *** ../vim-8.2.0384/src/testdir/test_menu.vim 2020-02-25 21:47:42.303474610 +0100 --- src/testdir/test_menu.vim 2020-03-15 16:00:11.482894787 +0100 *************** *** 89,94 **** --- 89,123 ---- unlet g:did_menu endfun + " Test various menu related errors + func Test_menu_errors() + menu Test.Foo :version + + " Error cases + call assert_fails('menu .Test.Foo :ls', 'E475:') + call assert_fails('menu Test. :ls', 'E330:') + call assert_fails('menu Foo. :ls', 'E331:') + call assert_fails('unmenu Test.Foo abc', 'E488:') + call assert_fails('menu :ls :ls', 'E792:') + call assert_fails('menu Test.:ls :ls', 'E792:') + call assert_fails('menu Test.Foo.Bar :ls', 'E327:') + call assert_fails('menu Test.-Sep-.Baz :ls', 'E332:') + call assert_fails('menu Foo.Bar.--.Baz :ls', '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,126 **** --- 148,483 ---- \ "\\\"\", 'xt') call assert_equal('"emenu Buffers. Xmenu.', @:) + " Test for expanding only submenus + call feedkeys(":popup Xmenu.\\\"\", 'xt') + call assert_equal('"popup Xmenu.A1 A2 A3 A4', @:) + + " Test for expanding menus after enable/disable + call feedkeys(":menu enable Xmenu.\\\"\", 'xt') + call assert_equal('"menu enable Xmenu.A1. A2. A3. A4.', @:) + call feedkeys(":menu disable Xmenu.\\\"\", 'xt') + call assert_equal('"menu disable Xmenu.A1. A2. A3. A4.', @:) + + " Test for expanding non-existing menu path + call feedkeys(":menu xyz.\\\"\", 'xt') + call assert_equal('"menu xyz.', @:) + call feedkeys(":menu Xmenu.A1.A1B1.xyz.\\\"\", '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.\\\"\", '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' + 10nmenu 10.20 T&est.B&ar:bar :echo 'bar' + 10nmenu