Subject: 29.0.50; [PATCH] Prevent further cases of duplicated separators in context menus

From: Jim Porter <[email-masked]>
Date: Sat, 4 Dec 2021 21:58:24 -0800

This is a followup to bug#52237. I'll just quote my message describing 
the issue from there[1]:

> I found another odd case, but I'm not 100% sure the best way to fix it:
> 
>   emacs -Q --eval '(context-menu-mode)'
>   C-h o identity RET
>   ;; Right-click somewhere in the Help buffer
> 
> There's a doubled separator after "Next Topic". Looking at the code, this is because `help-mode-context-menu' inserts new items using `define-key', which has the effect of putting the new items *before* the (hidden) menu title. The resulting keymap ends up looking like this:
> 
>   (keymap
>    (Previous\ Topic ...)
>    (Next\ Topic ...)
>    (help-mode-separator "--")
>    #("Context Menu" 0 12 (hide t))
>    (separator-undo "--")
>    ...)
> 
> Since there's a hidden item (the keymap title) between the `help-mode-separator' and `separator-undo'[1], the de-duplication doesn't handle that.

Attached is a patch to fix this based on the discussion in bug#52237. 
One slightly odd thing is that for context menu functions that put their 
items at the top, they place their separator *below* the items. Other 
functions place the separator *above* the items. It might be too late to 
fix this though, given that Emacs 28 is only open for fixing 
regressions, and changing it in 29 would be a (small) compatibility 
break. However, I can update the patch to put the separators in these 
functions at the top if people think that's best.

[1] https://lists.gnu.org/archive/html/bug-gnu-emacs/2021-12/msg00143.html
From 2b395a2cb1efca85cdca992804b614cdf2641413 Mon Sep 17 00:00:00 2001
From: Jim Porter <[email-masked]>
Date: Sat, 4 Dec 2021 21:42:57 -0800
Subject: [PATCH] Add a 'top-separator' to the context menu

This provides an anchor for context menu functions to use to insert
their menu items after it. Using 'define-key' doesn't work properly in
this case, since it inserts the items before the menu title, confusing
the separator de-duplication in 'context-menu-map'.

* lisp/mouse.el (context-menu-top-separator): New function.
(context-menu-functions): Use it.
* lisp/dired.el (dired-context-menu):
* lisp/help-mode.el (help-mode-context-menu):
* lisp/info.el (Info-context-menu):
* lisp/net/eww.el (eww-context-menu):
* lisp/net/goto-addr.el (goto-address-context-menu):
Use 'top-separator'.
---
 lisp/dired.el         |  5 +++--
 lisp/help-mode.el     | 10 ++++++----
 lisp/info.el          |  9 +++++----
 lisp/mouse.el         | 12 ++++++++++--
 lisp/net/eww.el       | 14 ++++++++------
 lisp/net/goto-addr.el |  8 +++++---
 6 files changed, 37 insertions(+), 21 deletions(-)

diff --git a/lisp/dired.el b/lisp/dired.el
index d0e547ba0b..b004d81495 100644
--- a/lisp/dired.el
+++ b/lisp/dired.el
@@ -2282,7 +2282,7 @@ dired-mode-operate-menu
 (defun dired-context-menu (menu click)
   "Populate MENU with Dired mode commands at CLICK."
   (when (mouse-posn-property (event-start click) 'dired-filename)
-    (define-key menu [dired-separator] menu-bar-separator)
+    (define-key-after menu [dired-separator] menu-bar-separator 'top-separator)
     (let ((easy-menu (make-sparse-keymap "Immediate")))
       (easy-menu-define nil easy-menu nil
         '("Immediate"
@@ -2292,7 +2292,8 @@ dired-context-menu
            :help "Edit file at mouse click in other window"]))
       (dolist (item (reverse (lookup-key easy-menu [menu-bar immediate])))
         (when (consp item)
-          (define-key menu (vector (car item)) (cdr item))))))
+          (define-key-after menu (vector (car item)) (cdr item)
+            'top-separator)))))
   menu)
 
 
diff --git a/lisp/help-mode.el b/lisp/help-mode.el
index 792f2e5af3..2ed20577f5 100644
--- a/lisp/help-mode.el
+++ b/lisp/help-mode.el
@@ -74,7 +74,8 @@ help-mode-menu
 
 (defun help-mode-context-menu (menu click)
   "Populate MENU with Help mode commands at CLICK."
-  (define-key menu [help-mode-separator] menu-bar-separator)
+  (define-key-after menu [help-mode-separator] menu-bar-separator
+    'top-separator)
   (let ((easy-menu (make-sparse-keymap "Help-Mode")))
     (easy-menu-define nil easy-menu nil
       '("Help-Mode"
@@ -86,14 +87,15 @@ help-mode-context-menu
          :active help-xref-forward-stack]))
     (dolist (item (reverse (lookup-key easy-menu [menu-bar help-mode])))
       (when (consp item)
-        (define-key menu (vector (car item)) (cdr item)))))
+        (define-key-after menu (vector (car item)) (cdr item) 'top-separator))))
 
   (when (mouse-posn-property (event-start click) 'mouse-face)
-    (define-key menu [help-mode-push-button]
+    (define-key-after menu [help-mode-push-button]
       '(menu-item "Follow Link" (lambda (event)
                                   (interactive "e")
                                   (push-button event))
-                  :help "Follow the link at click")))
+                  :help "Follow the link at click")
+      'top-separator))
 
   menu)
 
diff --git a/lisp/info.el b/lisp/info.el
index 94537c2417..76963af41c 100644
--- a/lisp/info.el
+++ b/lisp/info.el
@@ -4191,7 +4191,7 @@ Info-check-pointer
 
 (defun Info-context-menu (menu click)
   "Populate MENU with Info commands at CLICK."
-  (define-key menu [Info-separator] menu-bar-separator)
+  (define-key-after menu [Info-separator] menu-bar-separator 'top-separator)
   (let ((easy-menu (make-sparse-keymap "Info")))
     (easy-menu-define nil easy-menu nil
       '("Info"
@@ -4201,12 +4201,13 @@ Info-context-menu
          :help "Go forward in history"]))
     (dolist (item (reverse (lookup-key easy-menu [menu-bar info])))
       (when (consp item)
-        (define-key menu (vector (car item)) (cdr item)))))
+        (define-key-after menu (vector (car item)) (cdr item) 'top-separator))))
 
   (when (mouse-posn-property (event-start click) 'mouse-face)
-    (define-key menu [Info-mouse-follow-nearest-node]
+    (define-key-after menu [Info-mouse-follow-nearest-node]
       '(menu-item "Follow Link" Info-mouse-follow-nearest-node
-                  :help "Follow a link where you click")))
+                  :help "Follow a link where you click")
+      'top-separator))
 
   menu)
 
diff --git a/lisp/mouse.el b/lisp/mouse.el
index b5ca80a446..a7a7bb6eae 100644
--- a/lisp/mouse.el
+++ b/lisp/mouse.el
@@ -279,7 +279,8 @@ mouse-menu-bar-map
 
 ;; Context menus.
 
-(defcustom context-menu-functions '(context-menu-undo
+(defcustom context-menu-functions '(context-menu-top-separator
+                                    context-menu-undo
                                     context-menu-region
                                     context-menu-middle-separator
                                     context-menu-local
@@ -288,7 +289,8 @@ context-menu-functions
 Each function receives the menu and the mouse click event as its arguments
 and should return the same menu with changes such as added new menu items."
   :type '(repeat
-          (choice (function-item context-menu-undo)
+          (choice (function-item context-menu-top-separator)
+                  (function-item context-menu-undo)
                   (function-item context-menu-region)
                   (function-item context-menu-middle-separator)
                   (function-item context-menu-toolbar)
@@ -348,6 +350,12 @@ context-menu-map
       (setq menu (funcall context-menu-filter-function menu click)))
     menu))
 
+(defun context-menu-top-separator (menu _click)
+  "Add separator to the top of the context menu.
+Some context functions add menu items below the separator."
+  (define-key-after menu [top-separator] menu-bar-separator)
+  menu)
+
 (defun context-menu-middle-separator (menu _click)
   "Add separator to the middle of the context menu.
 Some context functions add menu items below the separator."
diff --git a/lisp/net/eww.el b/lisp/net/eww.el
index e86d21f889..7303e01a37 100644
--- a/lisp/net/eww.el
+++ b/lisp/net/eww.el
@@ -1106,7 +1106,7 @@ eww-mode-map
 
 (defun eww-context-menu (menu click)
   "Populate MENU with eww commands at CLICK."
-  (define-key menu [eww-separator] menu-bar-separator)
+  (define-key-after menu [eww-separator] menu-bar-separator 'top-separator)
   (let ((easy-menu (make-sparse-keymap "Eww")))
     (easy-menu-define nil easy-menu nil
       '("Eww"
@@ -1117,20 +1117,22 @@ eww-context-menu
 	["Reload" eww-reload t]))
     (dolist (item (reverse (lookup-key easy-menu [menu-bar eww])))
       (when (consp item)
-        (define-key menu (vector (car item)) (cdr item)))))
+        (define-key-after menu (vector (car item)) (cdr item) 'top-separator))))
 
   (when (or (mouse-posn-property (event-start click) 'shr-url)
             (mouse-posn-property (event-start click) 'image-url))
-    (define-key menu [shr-mouse-browse-url-new-window]
+    (define-key-after menu [shr-mouse-browse-url-new-window]
       `(menu-item "Follow URL in new window" ,(if browse-url-new-window-flag
                                                   'shr-mouse-browse-url
                                                 'shr-mouse-browse-url-new-window)
-                  :help "Browse the URL under the mouse cursor in a new window"))
-    (define-key menu [shr-mouse-browse-url]
+                  :help "Browse the URL under the mouse cursor in a new window")
+      'top-separator)
+    (define-key-after menu [shr-mouse-browse-url]
       `(menu-item "Follow URL" ,(if browse-url-new-window-flag
                                     'shr-mouse-browse-url-new-window
                                   'shr-mouse-browse-url)
-                  :help "Browse the URL under the mouse cursor")))
+                  :help "Browse the URL under the mouse cursor")
+      'top-separator))
 
   menu)
 
diff --git a/lisp/net/goto-addr.el b/lisp/net/goto-addr.el
index 848bad3b0d..bb0f0b00ee 100644
--- a/lisp/net/goto-addr.el
+++ b/lisp/net/goto-addr.el
@@ -127,10 +127,12 @@ goto-address-highlight-keymap
 (defun goto-address-context-menu (menu click)
   "Populate MENU with `goto-address' commands at CLICK."
   (when (mouse-posn-property (event-start click) 'goto-address)
-    (define-key menu [goto-address-separator] menu-bar-separator)
-    (define-key menu [goto-address-at-mouse]
+    (define-key-after menu [goto-address-separator] menu-bar-separator
+      'top-separator)
+    (define-key-after menu [goto-address-at-mouse]
       '(menu-item "Follow Link" goto-address-at-mouse
-                  :help "Follow a link where you click")))
+                  :help "Follow a link where you click")
+      'top-separator))
   menu)
 
 (defcustom goto-address-url-face 'link
-- 
2.25.1