neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

commit 5e470c7af556a7d02db88f3ba0c80b84715d8129
parent b16dc698cf445635fe094fc3482d1647341ed952
Author: zeertzjq <zeertzjq@outlook.com>
Date:   Thu,  5 Jun 2025 09:18:00 +0800

fix(menu): fix listing of submenus (#34315)

Problem:  Listing submenus with :menu doesn't work.
Solution: Don't go to the parent of the return value of find_menu(), and
          handle empty path at the caller.

Related #8194, which actually only fixed the problem for menu_get(), not
for :menu Ex command.
Diffstat:
Msrc/nvim/menu.c | 33++++++++++++++++++---------------
Mtest/functional/ex_cmds/menu_spec.lua | 86++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
2 files changed, 88 insertions(+), 31 deletions(-)

diff --git a/src/nvim/menu.c b/src/nvim/menu.c @@ -708,9 +708,12 @@ static dict_T *menu_get_recursive(const vimmenu_T *menu, int modes) /// @return false if could not find path_name bool menu_get(char *const path_name, int modes, list_T *list) { - vimmenu_T *menu = find_menu(*get_root_menu(path_name), path_name, modes); - if (!menu) { - return false; + vimmenu_T *menu = *get_root_menu(path_name); + if (*path_name != NUL) { + menu = find_menu(menu, path_name, modes); + if (!menu) { + return false; + } } for (; menu != NULL; menu = menu->next) { dict_T *d = menu_get_recursive(menu, modes); @@ -726,13 +729,15 @@ bool menu_get(char *const path_name, int modes, list_T *list) return true; } -/// Find menu matching `name` and `modes`. +/// Find menu matching `name` and `modes`. Does not handle empty `name`. /// /// @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 +/// @return found menu or NULL static vimmenu_T *find_menu(vimmenu_T *menu, char *name, int modes) { + assert(*name); + while (*name) { // find the end of one dot-separated name and put a NUL at the dot char *p = menu_name_skip(name); @@ -759,31 +764,29 @@ static vimmenu_T *find_menu(vimmenu_T *menu, char *name, int modes) } // Found a match, search the sub-menu. name = p; + assert(*name); menu = menu->children; } - return menu; + + abort(); } /// Show the mapping associated with a menu item or hierarchy in a sub-menu. static int show_menus(char *const path_name, int modes) { - vimmenu_T *menu = *get_root_menu(path_name); - if (menu != NULL) { + vimmenu_T *menu = NULL; + if (*path_name != NUL) { // First, find the (sub)menu with the given name - menu = find_menu(menu, path_name, modes); + menu = find_menu(*get_root_menu(path_name), path_name, modes); if (menu == NULL) { return FAIL; } } - // When there are no menus at all, the title still needs to be shown. - // Now we have found the matching menu, and we list the mappings - // Highlight title + // Now we have found the matching menu, and we list the mappings. msg_puts_title(_("\n--- Menus ---")); + show_menus_recursive(menu, modes, 0); - if (menu != NULL) { - show_menus_recursive(menu->parent, modes, 0); - } return OK; } diff --git a/test/functional/ex_cmds/menu_spec.lua b/test/functional/ex_cmds/menu_spec.lua @@ -59,26 +59,80 @@ describe(':emenu', function() end) end) -describe('menu_get', function() +local test_menus_cmd = [=[ + aunmenu * + + nnoremenu &Test.Test inormal<ESC> + inoremenu Test.Test insert + vnoremenu Test.Test x + cnoremenu Test.Test cmdmode + menu Test.Nested.test level1 + menu Test.Nested.Nested2 level2 + + nnoremenu <script> Export.Script p + tmenu Export.Script This is the tooltip + menu ]Export.hidden thisoneshouldbehidden + + nnoremenu Edit.Paste p + cnoremenu Edit.Paste <C-R>" +]=] + +describe(':menu listing', function() before_each(function() clear() - command([=[ - aunmenu * - - nnoremenu &Test.Test inormal<ESC> - inoremenu Test.Test insert - vnoremenu Test.Test x - cnoremenu Test.Test cmdmode - menu Test.Nested.test level1 - menu Test.Nested.Nested2 level2 + command(test_menus_cmd) + end) - nnoremenu <script> Export.Script p - tmenu Export.Script This is the tooltip - menu ]Export.hidden thisoneshouldbehidden + it('matches by path argument', function() + eq( + [[ +--- Menus --- +500 Edit + 500 Paste + c* <C-R>"]], + n.exec_capture('cmenu Edit') + ) + eq( + [[ +--- Menus --- +500 &Test + 500 Test + n* inormal<Esc> + 500 Nested + 500 test + n level1 + 500 Nested2 + n level2]], + n.exec_capture('nmenu Test') + ) + eq( + [[ +--- Menus --- +500 Nested + 500 test + o level1 + 500 Nested2 + o level2]], + n.exec_capture('omenu Test.Nested') + ) + eq( + [[ +--- Menus --- +500 Test + n* inormal<Esc> + v* x + s* x + i* insert + c* cmdmode]], + n.exec_capture('amenu Test.Test') + ) + end) +end) - nnoremenu Edit.Paste p - cnoremenu Edit.Paste <C-R>" - ]=]) +describe('menu_get', function() + before_each(function() + clear() + command(test_menus_cmd) end) it("path='', modes='a'", function()