patch 9.2.0578: GTK4: :unmenu does not remove entries from the menubar
Commit:
https://github.com/vim/vim/commit/63a9471e225ec8388f3d7f03ba6bc0a137533529
Author: Yasuhiro Matsumoto <
matt...@gmail.com>
Date: Sun May 31 20:32:40 2026 +0000
patch 9.2.0578: GTK4: :unmenu does not remove entries from the menubar
Problem: GTK4: gui_mch_destroy_menu() never removed the entry from its
parent GMenu, so :unmenu was effectively a no-op against the
visible menubar model. After "Refresh menu" the Buffers menu
items doubled because runtime/menu.vim BMShow() re-appended
Refresh / Delete / Alternate / Next / Previous / -SEP- into a
still-populated GMenu. The GAction registered for the item
and the reference on its submenu GMenu were also leaked.
Solution: Compute the entry's position in the parent GMenu by walking
the vimmenu_T sibling list, call g_menu_remove(), remove the
GAction we registered, and release our reference on the
submenu GMenu (Yasuhiro Matsumoto)
fixes: #20262
closes: #20314
Co-Authored-by: Claude <
nor...@anthropic.com>
Signed-off-by: Yasuhiro Matsumoto <
matt...@gmail.com>
Signed-off-by: Christian Brabandt <
c...@256bit.org>
diff --git a/src/gui_gtk4.c b/src/gui_gtk4.c
index e7bbd9ad5..8d650397c 100644
--- a/src/gui_gtk4.c
+++ b/src/gui_gtk4.c
@@ -3919,29 +3919,103 @@ gui_mch_menu_set_tip(vimmenu_T *menu UNUSED)
{
}
+/*
+ * Return TRUE if "menu" has a corresponding entry in its parent's GMenu.
+ * Popup menus, toolbar children and orphaned submenus do not.
+ */
+ static int
+menu_has_gmenu_slot(vimmenu_T *menu)
+{
+ if (menu == NULL || menu->name == NULL)
+ return FALSE;
+ if (menu->name[0] == ']' || menu_is_popup(menu->name))
+ return FALSE;
+ if (menu->parent != NULL)
+ {
+ if (menu_is_toolbar(menu->parent->name))
+ return FALSE;
+ if (menu->parent->submenu_id == NULL)
+ return FALSE;
+ return TRUE;
+ }
+ return menu_is_menubar(menu->name);
+}
+
+/*
+ * Find the parent GMenu containing the entry for "menu" and the position of
+ * that entry. Returns TRUE on success.
+ */
+ static int
+get_gmenu_pos_in_parent(vimmenu_T *menu, GMenu **parent_out, int *pos_out)
+{
+ GMenu *parent_gmenu;
+ vimmenu_T *first_sibling;
+ vimmenu_T *sib;
+ int pos = 0;
+
+ if (!menu_has_gmenu_slot(menu))
+ return FALSE;
+
+ if (menu->parent != NULL)
+ {
+ parent_gmenu = (GMenu *)(gpointer)menu->parent->submenu_id;
+ first_sibling = menu->parent->children;
+ }
+ else
+ {
+ if (gui.menubar == NULL)
+ return FALSE;
+ parent_gmenu = (GMenu *)(gpointer)g_object_get_data(
+ G_OBJECT(gui.menubar), "vim-gmenu");
+ first_sibling = root_menu;
+ }
+ if (parent_gmenu == NULL)
+ return FALSE;
+
+ for (sib = first_sibling; sib != NULL && sib != menu; sib = sib->next)
+ if (menu_has_gmenu_slot(sib))
+ pos++;
+ if (sib != menu)
+ return FALSE;
+
+ *parent_out = parent_gmenu;
+ *pos_out = pos;
+ return TRUE;
+}
+
void
gui_mch_destroy_menu(vimmenu_T *menu)
{
- // For toolbar buttons, remove from toolbar
+ GMenu *parent_gmenu = NULL;
+ int pos = 0;
+
+ // For toolbar buttons and separators, remove from the toolbar box.
if (menu->id != NULL && menu->id != (GtkWidget *)1)
{
GtkWidget *parent_widget = gtk_widget_get_parent(menu->id);
+
if (parent_widget != NULL)
gtk_box_remove(GTK_BOX(parent_widget), menu->id);
- menu->id = NULL;
}
- else
- menu->id = NULL;
+ menu->id = NULL;
+
+ // Remove the entry from the parent GMenu so the visible menu updates.
+ if (get_gmenu_pos_in_parent(menu, &parent_gmenu, &pos))
+ g_menu_remove(parent_gmenu, pos);
- // Free stored action name
- vim_free(menu->label);
- menu->label = NULL;
+ // Remove the GAction created for this item and free its name.
+ if (menu->label != NULL)
+ {
+ if (menu_action_group != NULL)
+ g_action_map_remove_action(G_ACTION_MAP(menu_action_group),
+ (const char *)menu->label);
+ VIM_CLEAR(menu->label);
+ }
- // GMenu items cannot be individually removed easily.
- // The submenu GMenu is unreffed if present.
+ // Release our reference on the submenu GMenu (if any).
if (menu->submenu_id != NULL)
{
- // Don't unref - GMenu may be referenced by the model
+ g_object_unref(menu->submenu_id);
menu->submenu_id = NULL;
}
}
diff --git a/src/version.c b/src/version.c
index b97066f50..80020f59f 100644
--- a/src/version.c
+++ b/src/version.c
@@ -729,6 +729,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 578,
/**/
577,
/**/