unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
From: Jim Porter <jporterbugs@gmail.com>
To: Juri Linkov <juri@linkov.net>
Cc: "52293@debbugs.gnu.org" <52293@debbugs.gnu.org>
Subject: bug#52293: 29.0.50; [PATCH v4] Prevent further cases of duplicated separators in context menus
Date: Sun, 2 Jan 2022 22:14:51 -0800	[thread overview]
Message-ID: <e40442d5-8989-8dd6-84a1-7cacdc181d0c@gmail.com> (raw)
In-Reply-To: <86fsq63pli.fsf@mail.linkov.net>

[-- Attachment #1: Type: text/plain, Size: 1471 bytes --]

On 1/2/2022 11:27 AM, Juri Linkov wrote:
>> I've attached an updated patch that lets context-menu-functions add items
>> to the beginning of the keymap as they currently do, while still removing
>> consecutive separators correctly. Since this logic is a bit tricky, it
>> could probably use an automated test or two, but before I write some,
>> I wanted to check that the strategy I'm using seems reasonable. It's
>> probably easiest to explain the logic by just pointing to the patch;
>> I added several comments describing the behavior so that reviewers (and
>> future readers) should be able to make sense of it.
> 
> Thanks, I suppose it's for master, not for the release branch?

Yeah, it's based on top of my previous patches that only landed on 
master, so the same applies for this one. I don't personally have an 
issue with if it merged to the release branch, but I also understand 
that we can't keep adding things to Emacs 28 forever.

Attached is an updated patch with unit tests as well as a fix to the 
behavior from the previous version; in my last patch, it didn't delete 
the last separator in the menu if it was *before* the "Context Menu" 
overall prompt string. (This could happen if all the 
context-menu-functions *only* used `define-key'.)

I've fixed that, though it did make the function a bit more complex. 
I've compensated for that with some more comments and what I hope are 
pretty thorough tests to make sure everything works as expected.

[-- Attachment #2: 0001-Prevent-further-cases-of-duplicated-separators-in-co.patch --]
[-- Type: text/plain, Size: 11505 bytes --]

From dc2b04d5f20ef861e13b0820a4377951cb528b53 Mon Sep 17 00:00:00 2001
From: Jim Porter <jporterbugs@gmail.com>
Date: Sun, 2 Jan 2022 22:08:52 -0800
Subject: [PATCH] Prevent further cases of duplicated separators in context
 menus

In some cases, context menu items are added before the overall prompt
string.  This could cause multiple consecutive separators to appear if
they "surround" the prompt string.

* lisp/mouse.el (context-menu-map): Improve the de-duplication logic
to ignore non-menu-items when checking for consecutive separators.

* test/lisp/mouse-tests.el
(context-menu-map-remove-consecutive-separators)
(context-menu-map-remove-consecutive-separators): New tests.
---
 lisp/mouse.el            |  34 ++++----
 test/lisp/mouse-tests.el | 162 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 183 insertions(+), 13 deletions(-)

diff --git a/lisp/mouse.el b/lisp/mouse.el
index 11fdd3f639..50fb1137ec 100644
--- a/lisp/mouse.el
+++ b/lisp/mouse.el
@@ -329,21 +329,29 @@ context-menu-map
 
     ;; Remove duplicate separators as well as ones at the beginning or
     ;; end of the menu.
-    (let ((l menu) saw-first-item)
+    (let ((l menu) (last-saw-separator t))
       (while (and (consp l)
                   (consp (cdr l)))
-        ;; If the next item is a separator, remove it if 1) we haven't
-        ;; seen any other items yet, or 2) it's followed by either
-        ;; another separator or the end of the list.
-        (if (and (equal (cdr-safe (cadr l)) menu-bar-separator)
-                 (or (not saw-first-item)
-                     (null (caddr l))
-                     (equal (cdr-safe (caddr l)) menu-bar-separator)))
-            (setcdr l (cddr l))
-          ;; The "first item" is any cons cell; this excludes the
-          ;; `keymap' symbol and the menu name.
-          (when (consp (cadr l)) (setq saw-first-item t))
-          (setq l (cdr l)))))
+        (if (equal (cdr-safe (cadr l)) menu-bar-separator)
+            (progn
+              ;; The next item is a separator.  Remove it if the last
+              ;; item we saw was a separator too.
+              (if last-saw-separator
+                  (setcdr l (cddr l))
+                ;; If we didn't delete this separator, update the last
+                ;; separator we saw to this one.
+                (setq last-saw-separator l
+                      l (cdr l))))
+          ;; If the next item is a cons cell, we found a non-separator
+          ;; item.  Don't remove the next separator we see.  We
+          ;; specifically check for cons cells to avoid treating the
+          ;; overall prompt string as a menu item.
+          (when (consp (cadr l))
+            (setq last-saw-separator nil))
+          (setq l (cdr l))))
+      ;; If the last item we saw was a separator, remove it.
+      (when (consp last-saw-separator)
+        (setcdr last-saw-separator (cddr last-saw-separator))))
 
     (when (functionp context-menu-filter-function)
       (setq menu (funcall context-menu-filter-function menu click)))
diff --git a/test/lisp/mouse-tests.el b/test/lisp/mouse-tests.el
index 56411d0365..96206cd9f8 100644
--- a/test/lisp/mouse-tests.el
+++ b/test/lisp/mouse-tests.el
@@ -52,5 +52,167 @@ bug26816-mouse-frame-movement
     (should (equal (mouse-position)
                    (cons frame (cons 0 0))))))
 
+(ert-deftest context-menu-map-remove-consecutive-separators ()
+  "Check that `context-menu-map' removes consecutive separators."
+  ;; Both separators after the overall prompt string.
+  (let ((context-menu-functions
+         '((lambda (menu _click)
+             (define-key-after menu [foo-item] '(menu-item "Foo" identity))
+             (define-key-after menu [separator-1] menu-bar-separator)
+             (define-key-after menu [separator-2] menu-bar-separator)
+             (define-key-after menu [bar-item] '(menu-item "Bar" identity))
+             menu))))
+    (should (equal `(keymap
+                     "Context Menu"
+                     (foo-item menu-item "Foo" identity)
+                     (separator-1 . ,menu-bar-separator)
+                     (bar-item menu-item "Bar" identity))
+                   (context-menu-map))))
+  ;; Both separators before the overall prompt string.
+  (let ((context-menu-functions
+         '((lambda (menu _click)
+             (define-key menu [bar-item] '(menu-item "Bar" identity))
+             (define-key menu [separator-2] menu-bar-separator)
+             (define-key menu [separator-1] menu-bar-separator)
+             (define-key menu [foo-item] '(menu-item "Foo" identity))
+             menu))))
+    (should (equal `(keymap
+                     (foo-item menu-item "Foo" identity)
+                     (separator-1 . ,menu-bar-separator)
+                     (bar-item menu-item "Bar" identity)
+                     "Context Menu")
+                   (context-menu-map))))
+  ;; First separator before and second separator after the overall
+  ;; prompt string.
+  (let ((context-menu-functions
+         '((lambda (menu _click)
+             (define-key-after menu [separator-2] menu-bar-separator)
+             (define-key-after menu [bar-item] '(menu-item "Bar" identity))
+             (define-key menu [separator-1] menu-bar-separator)
+             (define-key menu [foo-item] '(menu-item "Foo" identity))
+             menu))))
+    (should (equal `(keymap
+                     (foo-item menu-item "Foo" identity)
+                     (separator-1 . ,menu-bar-separator)
+                     "Context Menu"
+                     (bar-item menu-item "Bar" identity))
+                   (context-menu-map))))
+  ;; Three consecutive separators.
+  (let ((context-menu-functions
+         '((lambda (menu _click)
+             (define-key-after menu [foo-item] '(menu-item "Foo" identity))
+             (define-key-after menu [separator-1] menu-bar-separator)
+             (define-key-after menu [separator-2] menu-bar-separator)
+             (define-key-after menu [separator-3] menu-bar-separator)
+             (define-key-after menu [bar-item] '(menu-item "Bar" identity))
+             menu))))
+    (should (equal `(keymap
+                     "Context Menu"
+                     (foo-item menu-item "Foo" identity)
+                     (separator-1 . ,menu-bar-separator)
+                     (bar-item menu-item "Bar" identity))
+                   (context-menu-map)))))
+
+(ert-deftest context-menu-map-remove-separators-at-beginning-or-end ()
+  "Check that `context-menu-map' removes separators at the
+beginning or end of the menu."
+  ;; Menus with only separators.
+  (let ((test-functions
+         '(;; Separator before the overall prompt string.
+           (lambda (menu _click)
+             (define-key menu [separator] menu-bar-separator)
+             menu)
+           ;; Separator after the overall prompt string.
+           (lambda (menu _click)
+             (define-key-after menu [separator] menu-bar-separator)
+             menu)
+           ;; Begin and end separators before the overall prompt string.
+           (lambda (menu _click)
+             (define-key menu [end-separator] menu-bar-separator)
+             (define-key menu [begin-separator] menu-bar-separator)
+             menu)
+           ;; Begin and end separators after the overall prompt string.
+           (lambda (menu _click)
+             (define-key-after menu [begin-separator] menu-bar-separator)
+             (define-key-after menu [end-separator] menu-bar-separator)
+             menu)
+           ;; Begin separator before and end separator after the
+           ;; overall prompt string.
+           (lambda (menu _click)
+             (define-key menu [begin-separator] menu-bar-separator)
+             (define-key-after menu [end-separator] menu-bar-separator)
+             menu))))
+    (dolist (fun test-functions)
+      (let ((context-menu-functions (list fun)))
+        (should (equal '(keymap "Context Menu")
+                       (context-menu-map))))))
+  ;; Menus with separators at beginning and/or end with a menu-item
+  ;; before the prompt string.
+  (let ((test-functions
+         '(;; Separator before the overall prompt string and the menu-item.
+           (lambda (menu _click)
+             (define-key menu [foo-item] '(menu-item "Foo" identity))
+             (define-key menu [separator] menu-bar-separator)
+             menu)
+           ;; Separator before the overall prompt string, but after
+           ;; the menu-item.
+           (lambda (menu _click)
+             (define-key menu [separator] menu-bar-separator)
+             (define-key menu [foo-item] '(menu-item "Foo" identity))
+             menu)
+           ;; Separator at the end.
+           (lambda (menu _click)
+             (define-key menu [foo-item] '(menu-item "Foo" identity))
+             (define-key-after menu [separator] menu-bar-separator)
+             menu)
+           ;; Begin separator before and end separator after the
+           ;; overall prompt string.
+           (lambda (menu _click)
+             (define-key menu [foo-item] '(menu-item "Foo" identity))
+             (define-key menu [begin-separator] menu-bar-separator)
+             (define-key-after menu [end-separator] menu-bar-separator)
+             menu))))
+    (dolist (fun test-functions)
+      (let ((context-menu-functions (list fun)))
+        (should (equal '(keymap (foo-item menu-item "Foo" identity)
+                                "Context Menu")
+                       (context-menu-map))))))
+  ;; Menus with separators at beginning and/or end with a menu-item
+  ;; after the prompt string.
+  (let ((test-functions
+         '(;; Separator before the overall prompt string.
+           (lambda (menu _click)
+             (define-key menu [separator] menu-bar-separator)
+             (define-key-after menu [foo-item] '(menu-item "Foo" identity))
+             menu)
+           ;; Separator after the overall prompt string, but before
+           ;; the menu-item.
+           (lambda (menu _click)
+             (define-key-after menu [separator] menu-bar-separator)
+             (define-key-after menu [foo-item] '(menu-item "Foo" identity))
+             menu)
+           ;; Separator at the end.
+           (lambda (menu _click)
+             (define-key-after menu [foo-item] '(menu-item "Foo" identity))
+             (define-key-after menu [separator] menu-bar-separator)
+             menu)
+           ;; Begin and end separators after the overall prompt string.
+           (lambda (menu _click)
+             (define-key-after menu [begin-separator] menu-bar-separator)
+             (define-key-after menu [foo-item] '(menu-item "Foo" identity))
+             (define-key-after menu [end-separator] menu-bar-separator)
+             menu)
+           ;; Begin separator before and end separator after the
+           ;; overall prompt string.
+           (lambda (menu _click)
+             (define-key menu [begin-separator] menu-bar-separator)
+             (define-key-after menu [foo-item] '(menu-item "Foo" identity))
+             (define-key-after menu [end-separator] menu-bar-separator)
+             menu))))
+    (dolist (fun test-functions)
+      (let ((context-menu-functions (list fun)))
+        (should (equal '(keymap "Context Menu"
+                                (foo-item menu-item "Foo" identity))
+                       (context-menu-map)))))))
 
 ;;; mouse-tests.el ends here
-- 
2.25.1


  reply	other threads:[~2022-01-03  6:14 UTC|newest]

Thread overview: 53+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-12-05  5:58 bug#52293: 29.0.50; [PATCH] Prevent further cases of duplicated separators in context menus Jim Porter
2021-12-05  9:39 ` Juri Linkov
2021-12-06  4:07   ` Jim Porter
2021-12-05 17:59 ` Juri Linkov
2021-12-06  4:50   ` Jim Porter
2021-12-06  9:23     ` Juri Linkov
2021-12-07  3:54       ` bug#52293: 29.0.50; [PATCH v2] " Jim Porter
2021-12-07  8:19         ` Juri Linkov
2021-12-08  4:37           ` bug#52293: 29.0.50; [PATCH v3] " Jim Porter
2021-12-08 12:59             ` Eli Zaretskii
2021-12-08 17:43               ` Jim Porter
2021-12-08 19:07               ` Juri Linkov
2021-12-08 19:47                 ` Eli Zaretskii
2021-12-09  9:06                   ` Juri Linkov
2021-12-09  9:39                     ` Eli Zaretskii
2021-12-12  4:02                       ` Jim Porter
2021-12-12  7:02                         ` Eli Zaretskii
2021-12-12 20:27                           ` Jim Porter
2021-12-12 20:43                             ` Eli Zaretskii
2021-12-12 21:59                               ` Jim Porter
2021-12-13 12:23                                 ` Eli Zaretskii
2021-12-13 18:13                                   ` Jim Porter
2021-12-12 21:00                             ` bug#52293: [External] : " Drew Adams
2021-12-12 22:12                               ` Jim Porter
2021-12-12 23:14                                 ` Jim Porter
2021-12-13  1:16                                 ` Drew Adams
2021-12-13  1:46                                   ` Jim Porter
2021-12-13  2:41                                     ` Drew Adams
2021-12-13  8:47                                     ` Juri Linkov
2021-12-13 17:25                                       ` Jim Porter
2021-12-13 18:58                                         ` Juri Linkov
2021-12-14  5:41                                           ` Jim Porter
2021-12-14  8:30                                             ` Juri Linkov
2021-12-14 13:04                                               ` Eli Zaretskii
2021-12-14 16:49                                                 ` Drew Adams
2021-12-14 20:51                                                   ` Juri Linkov
2021-12-14 22:02                                                     ` Drew Adams
2021-12-15  8:59                                                       ` Juri Linkov
2021-12-15 18:10                                                         ` Drew Adams
2021-12-15 18:24                                                           ` Juri Linkov
2021-12-15 21:36                                                             ` Drew Adams
2021-12-16 17:20                                                               ` Juri Linkov
2021-12-16 17:51                                                                 ` Drew Adams
2021-12-16 17:56                                                                   ` Drew Adams
2021-12-17  8:20                                                                   ` Juri Linkov
2021-12-17 17:21                                                                     ` Drew Adams
2021-12-15  0:17                                               ` Jim Porter
2021-12-15  8:57                                                 ` Juri Linkov
2022-01-01  7:13                                                   ` Jim Porter
2022-01-02 19:27                                                     ` Juri Linkov
2022-01-03  6:14                                                       ` Jim Porter [this message]
2022-01-04  8:19                                                         ` bug#52293: 29.0.50; [PATCH v4] " Juri Linkov
2022-01-04 21:14                                                           ` Jim Porter

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.gnu.org/software/emacs/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=e40442d5-8989-8dd6-84a1-7cacdc181d0c@gmail.com \
    --to=jporterbugs@gmail.com \
    --cc=52293@debbugs.gnu.org \
    --cc=juri@linkov.net \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).