Subject: bug#20178: 24.3; Two feature requests for tabulated-list.el

From: Stefan Kangas <[email-masked]>
Date: Mon, 19 Oct 2020 17:43:44 -0700

Eli Barzilay <[email-masked]> writes:

>> What is the expected behavior here?  Does it make sense to make `C-u S'
>> go back to the default sort order?  Or should perhaps a right click at
>> the relevant column header reset it?
>
> FWIW, I'd expect a click to go from nothing to ascending to descending
> back to nothing.

Right.  But this would only be useful in certain buffers, so I'd suggest
this behavior to be optional.

The attached patch should do it.  Comments welcome.

(And as I'm about to send this, I realize that we would probably want
the same behavior also for the `tabulated-list-sort' command?  Either
for consecutive invocations or for the prefix argument.)

>> (Note that `electric-buffer-list' is the same as `list-buffers' for our
>> purposes here.)
>
> (Just tried `electric-buffer-list` again, it is broken in an amusing
> way for me, where a click leads to some infinite loop that keeps the
> window top following the mouse...)

Sounds like you have stumbled into a bug that should be reported?
From e5645e28bc49a6f9d8b3e98afcd300e050fb20e6 Mon Sep 17 00:00:00 2001
From: Stefan Kangas <[email-masked]>
Date: Tue, 20 Oct 2020 02:15:51 +0200
Subject: [PATCH] Reset sort order on third click in tabulated-list-mode

* lisp/emacs-lisp/tabulated-list.el
(tabulated-list--sort-by-column-name): Optionally support resetting
the sort order on the third click on the same column
header.  (Bug#20178)
(tabulated-list-third-click-resets-sort): New buffer local variable to
control above new optional behavior.
(tabulated-list-print): Don't set tabulated-list-entries.  This is
necessary if we are to reset the sort order on the third click.
* doc/lispref/modes.texi (Tabulated List Mode): Document new variable.
* etc/NEWS: Announce it.

* lisp/buff-menu.el (Buffer-menu-mode): Set the variable
tabulated-list-third-click-resets-sort to a non-nil value.
---
 doc/lispref/modes.texi            |  7 +++++++
 etc/NEWS                          | 14 ++++++++++++++
 lisp/buff-menu.el                 |  1 +
 lisp/emacs-lisp/tabulated-list.el | 26 ++++++++++++++++++--------
 4 files changed, 40 insertions(+), 8 deletions(-)

diff --git a/doc/lispref/modes.texi b/doc/lispref/modes.texi
index 022eda0bec..d8a4367851 100644
--- a/doc/lispref/modes.texi
+++ b/doc/lispref/modes.texi
@@ -1150,6 +1150,13 @@ Tabulated List Mode
 to invert the sort order.
 @end defvar
 
+@defvar tabulated-list-third-click-resets-sort
+When this variable is non-nil, the third click on a column header will
+reset all sorting and return to the unsorted order for
+@code{tabulated-list-entries}.  The default behavior is to simply
+toggle between sorting in ascending and descending order.
+@end defvar
+
 @defun tabulated-list-init-header
 This function computes and sets @code{header-line-format} for the
 Tabulated List buffer (@pxref{Header Lines}), and assigns a keymap to
diff --git a/etc/NEWS b/etc/NEWS
index c571fa95d1..4402bdfdb4 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -1420,6 +1420,20 @@ could have saved enough typing by using an abbrev, a hint will be
 displayed in the echo area, mentioning the abbrev that could have been
 used instead.
 
+** Tabulated List mode
+
++++
+*** Optionally support resetting sort order by clicking column header.
+Tabulated List mode now optionally supports resetting the sort order
+on the third click on a column header.  This is controlled by the new
+variable 'tabulated-list-third-click-resets-sort' that can be set by
+users, or in major modes by Emacs Lisp package authors that wish to
+enable this behavior.
+
+This new behavior is now enabled in Buffer Menu mode, used by commands
+'M-x list-buffers' and 'M-x electric-buffer-list', which now allows a
+user to return to the unsorted buffer order.
+
 
 * New Modes and Packages in Emacs 28.1
 
diff --git a/lisp/buff-menu.el b/lisp/buff-menu.el
index d06ba28787..938573683b 100644
--- a/lisp/buff-menu.el
+++ b/lisp/buff-menu.el
@@ -271,6 +271,7 @@ Buffer-menu-mode
 \\[Buffer-menu-bury]    Bury the buffer listed on this line."
   (set (make-local-variable 'buffer-stale-function)
        (lambda (&optional _noconfirm) 'fast))
+  (setq tabulated-list-third-click-resets-sort t)
   (add-hook 'tabulated-list-revert-hook 'list-buffers--refresh nil t))
 
 (defun buffer-menu (&optional arg)
diff --git a/lisp/emacs-lisp/tabulated-list.el b/lisp/emacs-lisp/tabulated-list.el
index b13f609f88..33038885f2 100644
--- a/lisp/emacs-lisp/tabulated-list.el
+++ b/lisp/emacs-lisp/tabulated-list.el
@@ -156,6 +156,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-third-click-resets-sort nil
+  "If non-nil, a third click on a column header resets the sort order.
+The default nil value means to just reverse the sort order when
+the same column header is clicked more than once.")
+(put 'tabulated-list-third-click-resets-sort 'permanent-local t)
+
 (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.
@@ -432,9 +438,7 @@ tabulated-list-print
                      (vertical-motion (buffer-size)))))))
     ;; Sort the entries, if necessary.
     (when sorter
-      (setq entries (sort entries sorter)))
-    (unless (functionp tabulated-list-entries)
-      (setq tabulated-list-entries entries))
+      (setq entries (sort (copy-sequence entries) sorter)))
     ;; Without a sorter, we have no way to just update.
     (when (and update (not sorter))
       (setq update nil))
@@ -662,11 +666,17 @@ tabulated-list-sort
 
 (defun tabulated-list--sort-by-column-name (name)
   (when (and name (derived-mode-p 'tabulated-list-mode))
-    ;; Flip the sort order on a second click.
-    (if (equal name (car tabulated-list-sort-key))
-	(setcdr tabulated-list-sort-key
-		(not (cdr tabulated-list-sort-key)))
-      (setq tabulated-list-sort-key (cons name nil)))
+    (cond
+     ;; Reset the order on a third click.
+     ((and tabulated-list-third-click-resets-sort
+           (equal (cons name t) tabulated-list-sort-key))
+      (setq tabulated-list-sort-key nil))
+     ;; Flip the sort order on a second click.
+     ((equal name (car tabulated-list-sort-key))
+      (setcdr tabulated-list-sort-key
+              (not (cdr tabulated-list-sort-key))))
+     ;; First click just sort by name.
+     ((setq tabulated-list-sort-key (cons name nil))))
     (tabulated-list-init-header)
     (tabulated-list-print t)))
 
-- 
2.28.0