Subject: bug#63785: 29.0.91; Clearing package list filters also clears flags

From: Eshel Yaron <[email-masked]>
Date: Mon, 29 May 2023 19:48:45 +0300

Robert Pluim <[email-masked]> writes:

> M-x list-packages
> / n ack ;; navigate to the line for 'ack'
> i ;; 'ack' has the 'I' flag
> / / ;; 'ack' no longer has the I flag
>
> This makes it harder to do 'search for foo, mark,
> search for bar, mark, install', you have to remember to execute the
> action after each mark command.
>

That's an interesting issue, it can be a bit tricky because while
filtering for "bar", "foo" is no longer anywhere to be found in the
buffer.  So AFAIU to solve this Emacs needs to maintain something like a
buffer-local association between list entry (package) ids and the tags
they were given.

This sounds like something that applies more broadly to other
`tabulated-list-mode` derivatives as well.  The attached patch adds a
`tabulated-list-maintain-tags` variable and enables it in
`package-menu-mode`.  With it I can search and mark foo, then search and
mark bar, and then install both, as you say.  WDYT?

>From d94febc93f840a28187cd9e3831be0deb0fa4c61 Mon Sep 17 00:00:00 2001
From: Eshel Yaron <[email-masked]>
Date: Mon, 29 May 2023 19:23:12 +0300
Subject: [PATCH] Maintain tags when filtering packages

Allow 'tabulated-list-mode' derivatives to maintain tags when
repopulating the buffer.  Enable this option in 'package-menu-mode'.

* lisp/emacs-lisp/tabulated-list.el (tabulated-list-tags-alist)
(tabulated-list-maintain-tags): New buffer-local variables.
(tabulated-list-put-tag): Associate entries with tags in
'tabulated-list-tags-alist'.
(tabulated-list-print): Restore tags if 'tabulated-list-maintain-tags'
is non-nil.

* lisp/emacs-lisp/package.el (package-menu-mode): Enable
'tabulated-list-maintain-tags'.
(Bug#63785)
---
 lisp/emacs-lisp/package.el        |  1 +
 lisp/emacs-lisp/tabulated-list.el | 16 ++++++++++++++++
 2 files changed, 17 insertions(+)

diff --git a/lisp/emacs-lisp/package.el b/lisp/emacs-lisp/package.el
index 69595601bc8..a050f405ba0 100644
--- a/lisp/emacs-lisp/package.el
+++ b/lisp/emacs-lisp/package.el
@@ -3145,6 +3145,7 @@ package-menu-mode
           ("Description" 0 package-menu--description-predicate)])
   (setq tabulated-list-padding 2)
   (setq tabulated-list-sort-key (cons "Status" nil))
+  (setq tabulated-list-maintain-tags t)
   (add-hook 'tabulated-list-revert-hook #'package-menu--refresh nil t)
   (tabulated-list-init-header)
   (setq revert-buffer-function 'package-menu--refresh-contents)
diff --git a/lisp/emacs-lisp/tabulated-list.el b/lisp/emacs-lisp/tabulated-list.el
index ddac6ed1746..e1922279039 100644
--- a/lisp/emacs-lisp/tabulated-list.el
+++ b/lisp/emacs-lisp/tabulated-list.el
@@ -167,6 +167,12 @@ tabulated-list-sort-key
 non-nil, means to invert the resulting sort.")
 (put 'tabulated-list-sort-key 'permanent-local t)
 
+(defvar-local tabulated-list-tags-alist nil
+  "Association between entry ids and their tags in the current buffer.")
+
+(defvar-local tabulated-list-maintain-tags nil
+  "When non-nil, maintain tags when repopulating the current tabulated list.")
+
 (defsubst tabulated-list-get-id (&optional pos)
   "Return the entry ID of the Tabulated List entry at POS.
 The value is an ID object from `tabulated-list-entries', or nil.
@@ -200,6 +206,8 @@ tabulated-list-put-tag
 		       (make-string (- tabulated-list-padding width) ?\s))
 	     (truncate-string-to-width tag tabulated-list-padding))))
 	(delete-region beg (+ beg tabulated-list-padding)))))
+  (when-let ((id (tabulated-list-get-id)))
+    (setf (alist-get id tabulated-list-tags-alist) tag))
   (if advance
       (forward-line)))
 
@@ -497,6 +505,14 @@ tabulated-list-print
       (setq entries (cdr entries)))
     (when update
       (delete-region (point) (point-max)))
+    (when tabulated-list-maintain-tags
+      (save-excursion
+        (goto-char (point-min))
+        (while (not (eobp))
+          (when-let ((id (tabulated-list-get-id))
+                     (tag (alist-get id tabulated-list-tags-alist)))
+            (tabulated-list-put-tag tag))
+          (forward-line))))
     (set-buffer-modified-p nil)
     ;; If REMEMBER-POS was specified, move to the "old" location.
     (if saved-pt
-- 
2.40.1