Subject: 26.0.50; ibuffer-decompose-filter-group: unwanted side effects

From: Tino Calancha <[email-masked]>
Date: Tue, 29 Nov 2016 18:28:07 +0900

`ibuffer-decompose-filter-group' has unwanted side effects: it might
modify `ibuffer-saved-filter-groups'.

emacs -Q -eval "(dired (expand-file-name source-directory))"
;; Evaluate following form:
(progn
  (ibuffer)
  (setq ibuffer-show-empty-filter-groups nil
        ibuffer-saved-filter-groups
        '(("saved-filters"
           ("Shell"
            (used-mode . shell-mode))
           ("Elisp"
            (or
             (used-mode . emacs-lisp-mode)
             (used-mode . lisp-interaction-mode)))
           ("Dired"
            (used-mode . dired-mode))
           ("Info"
            (or
             (used-mode . help-mode)
             (used-mode . debugger-mode)
             (used-mode . Custom-mode)
             (used-mode . completion-list-mode)
             (name . "\\`[*]Messages[*]\\'"))))))
  (ibuffer-switch-to-saved-filter-groups "saved-filters"))
;; Now we call `ibuffer-decompose-filter-group' followed by
;; `ibuffer-filter-disable' and `ibuffer-switch-to-saved-filter-groups'.
;; These commands should bring us back to the initial Ibuffer status:
/D Elisp RET
//
/R
;; Elisp group is missing without calling `ibuffer-delete-saved-filter-groups'
M-: (assoc "Elisp" (cdar ibuffer-saved-filter-groups)) RET
=> nil

The reason seems to be that `ibuffer-saved-filter-groups' and
`ibuffer-filter-groups' share elements.
(eq (cdar ibuffer-saved-filter-groups) ibuffer-filter-groups)
=> t

Then, the destructive
function `ibuffer-delete-alist' applied on `ibuffer-filter-groups' will
modify the other list as well.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
>From 45f95151de799fc1acd19353a40b22d6783ad1a6 Mon Sep 17 00:00:00 2001
From: Tino Calancha <[email-masked]>
Date: Tue, 29 Nov 2016 18:14:32 +0900
Subject: [PATCH] ibuffer-decompose-filter-group avoid unwanted side effects

* lisp/ibuf-ext.el (ibuffer-delete-alist): Remove it.
(ibuffer-remove-alist): New defun; it supersedes 'ibuffer-delete-alist'.
All callers changed (Bug#25058).
* test/lisp/ibuffer-tests.el (ibuffer-test-Bug25058): Add test.
---
 lisp/ibuf-ext.el           | 23 +++++++++++------------
 test/lisp/ibuffer-tests.el | 33 +++++++++++++++++++++++++++++++++
 2 files changed, 44 insertions(+), 12 deletions(-)

diff --git a/lisp/ibuf-ext.el b/lisp/ibuf-ext.el
index 5ef0746..7f35ce6 100644
--- a/lisp/ibuf-ext.el
+++ b/lisp/ibuf-ext.el
@@ -38,12 +38,11 @@
   (require 'cl-lib))
 
 ;;; Utility functions
-(defun ibuffer-delete-alist (key alist)
-  "Delete all entries in ALIST that have a key equal to KEY."
-  (let (entry)
-    (while (setq entry (assoc key alist))
-      (setq alist (delete entry alist)))
-    alist))
+(defun ibuffer-remove-alist (key alist)
+  "Remove all entries in ALIST that have a key equal to KEY."
+  (while (ibuffer-awhen (assoc key alist)
+           (setq alist (remove it alist)) it))
+  alist)
 
 ;; borrowed from Gnus
 (defun ibuffer-remove-duplicates (list)
@@ -281,7 +280,7 @@ ibuffer-interactive-filter-by-mode
   (let ((buf (ibuffer-current-buffer)))
     (if (assq 'mode ibuffer-filtering-qualifiers)
 	(setq ibuffer-filtering-qualifiers
-	      (ibuffer-delete-alist 'mode ibuffer-filtering-qualifiers))
+	      (ibuffer-remove-alist 'mode ibuffer-filtering-qualifiers))
       (ibuffer-push-filter (cons 'mode (buffer-local-value 'major-mode buf)))))
   (ibuffer-update nil t))
 
@@ -556,7 +555,7 @@ ibuffer-generate-filter-groups
 			      (append ibuffer-filter-groups
 				      (list (cons "Default" nil))))))
     ;; (dolist (hidden ibuffer-hidden-filter-groups)
-    ;;   (setq filter-group-alist (ibuffer-delete-alist
+    ;;   (setq filter-group-alist (ibuffer-remove-alist
     ;;     			   hidden filter-group-alist)))
     (let ((vec (make-vector (length filter-group-alist) nil))
 	  (i 0))
@@ -640,7 +639,7 @@ ibuffer-decompose-filter-group
   (interactive
    (list (ibuffer-read-filter-group-name "Decompose filter group: " t)))
   (let ((data (cdr (assoc group ibuffer-filter-groups))))
-    (setq ibuffer-filter-groups (ibuffer-delete-alist
+    (setq ibuffer-filter-groups (ibuffer-remove-alist
 				 group ibuffer-filter-groups)
 	  ibuffer-filtering-qualifiers data))
   (ibuffer-update nil t))
@@ -688,7 +687,7 @@ ibuffer-kill-filter-group
   (ibuffer-aif (assoc name ibuffer-filter-groups)
       (progn
 	(push (copy-tree it) ibuffer-filter-group-kill-ring)
-	(setq ibuffer-filter-groups (ibuffer-delete-alist
+	(setq ibuffer-filter-groups (ibuffer-remove-alist
 				     name ibuffer-filter-groups))
 	(setq ibuffer-hidden-filter-groups
 	      (delete name ibuffer-hidden-filter-groups)))
@@ -778,7 +777,7 @@ ibuffer-delete-saved-filter-groups
       (completing-read "Delete saved filter group: "
 		       ibuffer-saved-filter-groups nil t))))
   (setq ibuffer-saved-filter-groups
-	(ibuffer-delete-alist name ibuffer-saved-filter-groups))
+	(ibuffer-remove-alist name ibuffer-saved-filter-groups))
   (ibuffer-maybe-save-stuff)
   (ibuffer-update nil t))
 
@@ -949,7 +948,7 @@ ibuffer-delete-saved-filters
       (completing-read "Delete saved filters: "
 		       ibuffer-saved-filters nil t))))
   (setq ibuffer-saved-filters
-	(ibuffer-delete-alist name ibuffer-saved-filters))
+	(ibuffer-remove-alist name ibuffer-saved-filters))
   (ibuffer-maybe-save-stuff)
   (ibuffer-update nil t))
 
diff --git a/test/lisp/ibuffer-tests.el b/test/lisp/ibuffer-tests.el
index 3a4def3..260469e 100644
--- a/test/lisp/ibuffer-tests.el
+++ b/test/lisp/ibuffer-tests.el
@@ -66,5 +66,38 @@
       (mapc (lambda (buf) (when (buffer-live-p buf)
                             (kill-buffer buf))) (list buf1 buf2)))))
 
+(ert-deftest ibuffer-test-Bug25058 ()
+  "Test for http://debbugs.gnu.org/25058 ."
+  (ibuffer)
+  (let ((orig-filters ibuffer-saved-filter-groups)
+        (tmp-filters '(("saved-filters"
+                        ("Shell"
+                         (used-mode . shell-mode))
+                        ("Elisp"
+                         (or
+                          (used-mode . emacs-lisp-mode)
+                          (used-mode . lisp-interaction-mode)))
+                        ("Dired"
+                         (used-mode . dired-mode))
+                        ("Info"
+                         (or
+                          (used-mode . help-mode)
+                          (used-mode . debugger-mode)
+                          (used-mode . Custom-mode)
+                          (used-mode . completion-list-mode)
+                          (name . "\\`[*]Messages[*]\\'")))))))
+    (unwind-protect
+        (progn
+          (setq ibuffer-saved-filter-groups tmp-filters)
+	  (ibuffer-switch-to-saved-filter-groups "saved-filters")
+          (ibuffer-decompose-filter-group "Elisp")
+          (ibuffer-filter-disable)
+          (ibuffer-switch-to-saved-filter-groups "saved-filters")
+          (should (assoc "Elisp" (cdar ibuffer-saved-filter-groups))))
+      (setq ibuffer-saved-filter-groups orig-filters)
+      (ibuffer-awhen (get-buffer "*Ibuffer*")
+        (and (buffer-live-p it) (kill-buffer it))))))
+
+
 (provide 'ibuffer-tests)
 ;; ibuffer-tests.el ends here
-- 
2.10.2

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
In GNU Emacs 26.0.50.1 (x86_64-pc-linux-gnu, GTK+ Version 3.22.4)
 of 2016-11-28
Repository revision: 2c8a7e50d24daf19ea7d86f1cfeaa98a41c56085