branch: externals/tmr
commit cb867cc72037334ee72b1eb3eab5a74dcf2e683d
Merge: 32e522b07b cc3b8db6dd
Author: Protesilaos Stavrou <i...@protesilaos.com>
Commit: GitHub <nore...@github.com>

    Merge pull request #2 from Stebalien/steb/mode-line
    
    Add support for displaying timers in the mode-line
---
 tmr.el | 147 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 146 insertions(+), 1 deletion(-)

diff --git a/tmr.el b/tmr.el
index 5641dfafeb..0ef313ddd5 100644
--- a/tmr.el
+++ b/tmr.el
@@ -37,7 +37,10 @@
 ;;; Code:
 
 (require 'seq)
-(eval-when-compile (require 'cl-lib))
+(require 'format-spec)
+(eval-when-compile
+  (require 'cl-lib)
+  (require 'subr-x))
 
 (defgroup tmr ()
   "TMR May Ring: set timers using a simple notation."
@@ -143,6 +146,38 @@ meant for experienced users."
           :value-type ,display-buffer--action-custom-type)
   :package-version '(tmr . "1.1.0"))
 
+(defcustom tmr-mode-line-format "%r%d"
+  "Format string for displaying a timer in the mode-line.
+Available format specifiers:
+- %r: Remaining time.
+- %d: Timer description (truncated to `tmr-mode-line-max-desc-length')."
+  :type 'string
+  :group 'tmr)
+
+(defcustom tmr-mode-line-separator " | "
+  "String used to separate multiple timers in the mode-line."
+  :type 'string
+  :group 'tmr)
+
+(defcustom tmr-mode-line-max-timers 3
+  "Maximum number of timers to display in the mode-line.
+Set to nil to show all timers."
+  :type '(choice (const :tag "Show all" nil)
+                 (integer :tag "Maximum number"))
+  :group 'tmr)
+
+(defcustom tmr-mode-line-max-desc-length 15
+  "Maximum length for timer descriptions in the mode-line.
+Longer descriptions will be truncated."
+  :type '(choice (const :tag "Don't truncate" nil)
+                 (integer :tag "Truncate"))
+  :group 'tmr)
+
+(defcustom tmr-mode-line-prefix "⏰"
+  "Prefix string displayed before the timer list."
+  :type 'string
+  :group 'tmr)
+
 ;;;; Faces
 
 (defgroup tmr-faces ()
@@ -229,6 +264,21 @@ meant for experienced users."
   :package-version '(tmr . "1.1.0")
   :group 'tmr-faces)
 
+(defface tmr-mode-line-active
+  '((t :inherit mode-line-emphasis))
+  "Face for active timers in the mode-line."
+  :group 'tmr-faces)
+
+(defface tmr-mode-line-soon
+  '((t :inherit warning))
+  "Face for timers that will expire in the next 2 minutes."
+  :group 'tmr-faces)
+
+(defface tmr-mode-line-urgent
+  '((t :inherit error))
+  "Face for timers that will expire in the next 30 seconds."
+  :group 'tmr-faces)
+
 ;;;; Common helpers
 
 (cl-defstruct (tmr--timer
@@ -843,6 +893,101 @@ they are set to reasonable default values."
 (add-hook 'tmr--update-hook #'tmr-tabulated--refresh)
 (add-hook 'tmr--read-timer-hook #'tmr-tabulated--timer-at-point)
 
+;;;; Mode-line indicator
+
+(defvar tmr-mode-line-string nil
+  "TMR mode-line string.")
+(put 'tmr-mode-line-string 'risky-local-variable t)
+
+(defvar tmr-mode-line--update-timer nil
+  "Timer to update the mode-line.")
+
+(defun tmr-mode-line--format-remaining (timer)
+  "Format remaining time for TIMER with appropriate face."
+  (let* ((secs (float-time (time-subtract (tmr--timer-end-date timer) nil)))
+         (face (cond ((and (< secs 5) (evenp (truncate secs)))
+                      '((t :inherit tmr-mode-line-urgent :inverse-video t)))
+                     ((< secs 30) 'tmr-mode-line-urgent)
+                     ((= (truncate secs) 30)
+                      '((t :inherit tmr-mode-line-urgent :inverse-video t)))
+                     ((= (truncate secs) 60)
+                      '((t :inherit tmr-mode-line-soon :inverse-video t)))
+                     ((< secs 120) 'tmr-mode-line-soon)
+                     ((= (truncate secs) 120)
+                      '((t :inherit tmr-mode-line-soon :inverse-video t)))
+                     (t 'tmr-mode-line-active)))
+         (formatted (format-seconds
+                     (cond ((< secs 120) "%mm %ss%z")
+                           ((< secs (* 24 60 60)) "%hh %mm%z")
+                           (t "%dd %hh%z"))
+                     secs)))
+    (propertize formatted 'face face)))
+
+(defun tmr-mode-line--format-description (timer)
+  "Format description for TIMER, truncating if necessary."
+  (if-let* ((desc (tmr--timer-description timer)))
+    (concat " " (if tmr-mode-line-max-desc-length
+                    (truncate-string-to-width
+                     desc tmr-mode-line-max-desc-length
+                     nil nil t)
+                  desc))
+    ""))
+
+(defun tmr-mode-line--format-timer (timer)
+  "Format a single TIMER for display in the mode-line."
+  (propertize
+   (format-spec tmr-mode-line-format
+                `((?r . ,(tmr-mode-line--format-remaining timer))
+                  (?d . ,(tmr-mode-line--format-description timer))))
+   'help-echo (tmr--long-description timer)))
+
+(defun tmr-mode-line--get-active-timers ()
+  "Return a sorted list of active timers."
+  (thread-last tmr--timers
+               (seq-remove #'tmr--timer-finishedp)
+               (seq-sort-by #'tmr--timer-end-date #'time-less-p)))
+
+(defun tmr-mode-line--update ()
+  "Updates `tmr-mode-line-string' based on the current timer state."
+  (setq
+   tmr-mode-line-string
+   (if-let* ((active-timers (tmr-mode-line--get-active-timers)))
+       (let* ((truncate (and tmr-mode-line-max-timers
+                             (length> active-timers tmr-mode-line-max-timers)))
+              (timers-to-show (if truncate
+                                 (seq-take active-timers
+                                           tmr-mode-line-max-timers)
+                                active-timers)))
+         (concat
+          " " tmr-mode-line-prefix " "
+          (string-join (mapcar #'tmr-mode-line--format-timer timers-to-show)
+                       tmr-mode-line-separator)
+          (when truncate
+            (format " +%d" (- (length active-timers) 
tmr-mode-line-max-timers)))
+          " "))
+     ""))
+  (force-mode-line-update t))
+
+;;;###autoload
+(define-minor-mode tmr-mode-line-mode
+  "Display TMR May Ring timers in the global mode line."
+  :global t
+  :group 'tmr-mode-line
+  (if tmr-mode-line-mode
+      (progn
+        (unless global-mode-string (setq global-mode-string '("")))
+        (unless (memq 'tmr-mode-line-string global-mode-string)
+          (setq global-mode-string
+                (append global-mode-string '(tmr-mode-line-string))))
+        (setq tmr-mode-line--update-timer
+              (run-at-time t 1 #'tmr-mode-line--update))
+        (add-hook 'tmr--update-hook #'tmr-mode-line--update))
+    (when tmr-mode-line--update-timer
+      (cancel-timer tmr-mode-line--update-timer)
+      (setq tmr-mode-line--update-timer nil))
+    (setq tmr-mode-line-string nil)
+    (remove-hook 'tmr--update-hook #'tmr-mode-line--update)))
+
 ;;;; Ask if there are timers before exiting Emacs
 
 (defun tmr-kill-emacs-query-function ()

Reply via email to