Subject: [PATCH] Base timer-list-mode on tabulated-list-mode and support sorting by column

From: Stefan Kangas <[email-masked]>
Date: Sat, 25 Apr 2020 22:51:29 +0200

I wanted to sort the timer-list by column, so I rebased the mode on
tabulated-list-mode and added support for sorting.  Please see the
attached patches.

Best regards,
Stefan Kangas
From 5bd0114e5d41d87b53594b825afa6912a511da25 Mon Sep 17 00:00:00 2001
From: Stefan Kangas <[email-masked]>
Date: Fri, 24 Apr 2020 23:42:37 +0200
Subject: [PATCH 1/2] Base timer-list-mode on tabulated-list-mode

* lisp/emacs-lisp/timer-list.el (list-timers)
(timer-list-mode): Inherit from 'tabulated-list-mode' instead of
'special-mode' and make the necessary changes to support that.

* doc/lispref/os.texi (Timers): Update documentation.
---
 doc/lispref/os.texi           |   6 +-
 lisp/emacs-lisp/timer-list.el | 105 ++++++++++++++++------------------
 2 files changed, 52 insertions(+), 59 deletions(-)

diff --git a/doc/lispref/os.texi b/doc/lispref/os.texi
index 5c0b1e2edf..8bf48b1dbb 100644
--- a/doc/lispref/os.texi
+++ b/doc/lispref/os.texi
@@ -2182,9 +2182,9 @@ Timers
 
 @findex list-timers
 The @code{list-timers} command lists all the currently active timers.
-There's only one command available in the buffer displayed: @kbd{c}
-(@code{timer-list-cancel}) that will cancel the timer on the line
-under point.
+The command @kbd{c} (@code{timer-list-cancel}) will cancel the timer
+on the line under point.  You can sort the list by column using the
+command @kbd{S} (@code{tabulated-list-sort}).
 
 @node Idle Timers
 @section Idle Timers
diff --git a/lisp/emacs-lisp/timer-list.el b/lisp/emacs-lisp/timer-list.el
index 4cebd739c3..17e5eb0592 100644
--- a/lisp/emacs-lisp/timer-list.el
+++ b/lisp/emacs-lisp/timer-list.el
@@ -32,41 +32,49 @@ list-timers
   "List all timers in a buffer."
   (interactive)
   (pop-to-buffer-same-window (get-buffer-create "*timer-list*"))
-  (let ((inhibit-read-only t))
-    (erase-buffer)
-    (timer-list-mode)
-    (dolist (timer (append timer-list timer-idle-list))
-      (insert (format "%4s %10s %8s %s"
-                      ;; Idle.
-                      (if (aref timer 7) "*" " ")
-                      ;; Next time.
-		      (let ((time (list (aref timer 1)
-					(aref timer 2)
-					(aref timer 3))))
-                        (format "%.2f"
-				(float-time
-				 (if (aref timer 7)
-				     time
-				   (time-subtract time nil)))))
-                      ;; Repeat.
-                      (let ((repeat (aref timer 4)))
-                        (cond
-                         ((numberp repeat)
-                          (format "%.1f" repeat))
-                         ((null repeat)
-                          "-")
-                         (t
-                          (format "%s" repeat))))
-                      ;; Function.
-                      (let ((cl-print-compiled 'static)
-                            (cl-print-compiled-button nil)
-                            (print-escape-newlines t))
-                        (cl-prin1-to-string (aref timer 5)))))
-      (put-text-property (line-beginning-position)
-                         (1+ (line-beginning-position))
-                         'timer timer)
-      (insert "\n")))
-  (goto-char (point-min)))
+  (timer-list-mode)
+  (tabulated-list-init-header)
+  (setq tabulated-list-entries
+        (mapcar
+         (lambda (timer)
+           (list
+            nil
+            `[ ;; Idle.
+              ,(propertize
+                (if (aref timer 7) "   *" " ")
+                'help-echo "* marks idle timers"
+                'timer timer)
+              ;; Next time.
+              ,(propertize
+                (let ((time (list (aref timer 1)
+				  (aref timer 2)
+				  (aref timer 3))))
+                  (format "%10.2f"
+			  (float-time
+			   (if (aref timer 7)
+			       time
+			     (time-subtract time nil)))))
+                'help-echo "Time in sec till next invocation")
+              ;; Repeat.
+              ,(propertize
+                (let ((repeat (aref timer 4)))
+                  (cond
+                   ((numberp repeat)
+                    (format "%8.1f" repeat))
+                   ((null repeat)
+                    "       -")
+                   (t
+                    (format "%8s" repeat))))
+                'help-echo "Symbol: repeat; number: repeat interval in sec")
+              ;; Function.
+              ,(propertize
+                (let ((cl-print-compiled 'static)
+                      (cl-print-compiled-button nil)
+                      (print-escape-newlines t))
+                  (cl-prin1-to-string (aref timer 5)))
+                'help-echo "Function called by timer")]))
+         (append timer-list timer-idle-list)))
+  (tabulated-list-print))
 ;; This command can be destructive if they don't know what they are
 ;; doing.  Kids, don't try this at home!
 ;;;###autoload (put 'list-timers 'disabled "Beware: manually canceling timers can ruin your Emacs session.")
@@ -74,35 +82,20 @@ list-timers
 (defvar timer-list-mode-map
   (let ((map (make-sparse-keymap)))
     (define-key map "c" 'timer-list-cancel)
-    (define-key map "n" 'next-line)
-    (define-key map "p" 'previous-line)
     (easy-menu-define nil map ""
       '("Timers"
 	["Cancel" timer-list-cancel t]))
     map))
 
-(define-derived-mode timer-list-mode special-mode "Timer-List"
+(define-derived-mode timer-list-mode tabulated-list-mode "Timer-List"
   "Mode for listing and controlling timers."
-  (setq bidi-paragraph-direction 'left-to-right)
-  (setq truncate-lines t)
   (buffer-disable-undo)
   (setq-local revert-buffer-function #'list-timers)
-  (setq buffer-read-only t)
-  (setq header-line-format
-        (concat (propertize " " 'display '(space :align-to 0))
-                (format "%4s %10s %8s %s"
-                        (propertize "Idle"
-                                    'mouse-face 'highlight
-                                    'help-echo "* marks idle timers")
-                        (propertize "Next"
-                                    'mouse-face 'highlight
-                                    'help-echo "Time in sec till next invocation")
-                        (propertize "Repeat"
-                                    'mouse-face 'highlight
-                                    'help-echo "Symbol: repeat; number: repeat interval in sec")
-                        (propertize "Function"
-                                    'mouse-face 'highlight
-                                    'help-echo "Function called by timer")))))
+  (setq tabulated-list-format
+        '[("Idle" 4)
+          ("     Next" 10)
+          ("  Repeat" 8)
+          ("Function" 0)]))
 
 (defun timer-list-cancel ()
   "Cancel the timer on the line under point."
-- 
2.26.2

From c2d44cdc85cbfe9202d63879cb8d111f0ee33c69 Mon Sep 17 00:00:00 2001
From: Stefan Kangas <[email-masked]>
Date: Fri, 24 Apr 2020 23:43:57 +0200
Subject: [PATCH 2/2] Support sorting timer-list-mode by column

* lisp/emacs-lisp/timer-list.el (timer-list-mode)
(timer-list--idle-predicate, timer-list--next-predicate)
(timer-list--repeat-predicate)
(timer-list--function-predicate): Add support for sorting by column.
---
 lisp/emacs-lisp/timer-list.el | 35 +++++++++++++++++++++++++++++++----
 1 file changed, 31 insertions(+), 4 deletions(-)

diff --git a/lisp/emacs-lisp/timer-list.el b/lisp/emacs-lisp/timer-list.el
index 17e5eb0592..00d09696d2 100644
--- a/lisp/emacs-lisp/timer-list.el
+++ b/lisp/emacs-lisp/timer-list.el
@@ -92,10 +92,37 @@ timer-list-mode
   (buffer-disable-undo)
   (setq-local revert-buffer-function #'list-timers)
   (setq tabulated-list-format
-        '[("Idle" 4)
-          ("     Next" 10)
-          ("  Repeat" 8)
-          ("Function" 0)]))
+        '[("Idle" 6 timer-list--idle-predicate)
+          ("      Next" 12 timer-list--next-predicate)
+          ("  Repeat" 11 timer-list--repeat-predicate)
+          ("Function" 10 timer-list--function-predicate)]))
+
+(defun timer-list--idle-predicate (A B)
+  "Predicate to sort Timer-List by the Idle column."
+  (let ((iA (aref (cadr A) 0))
+        (iB (aref (cadr B) 0)))
+    (cond ((string= iA iB)
+           (timer-list--next-predicate A B))
+          ((string= iA "   *") nil)
+          (t t))))
+
+(defun timer-list--next-predicate (A B)
+  "Predicate to sort Timer-List by the Next column."
+  (let ((nA (string-to-number (aref (cadr A) 1)))
+        (nB (string-to-number (aref (cadr B) 1))))
+    (< nA nB)))
+
+(defun timer-list--repeat-predicate (A B)
+  "Predicate to sort Timer-List by the Repeat column."
+  (let ((rA (aref (cadr A) 2))
+        (rB (aref (cadr B) 2)))
+    (string< rA rB)))
+
+(defun timer-list--function-predicate (A B)
+  "Predicate to sort Timer-List by the Next column."
+  (let ((fA (aref (cadr A) 3))
+        (fB (aref (cadr B) 3)))
+    (string< fA fB)))
 
 (defun timer-list-cancel ()
   "Cancel the timer on the line under point."
-- 
2.26.2