branch: externals/tmr
commit a637c1c17e789d422a3886a4e17c19c6b268a40b
Author: Protesilaos Stavrou <[email protected]>
Commit: Protesilaos Stavrou <[email protected]>

    Implement pause functionality
---
 README.org |  20 +++++++++---
 tmr.el     | 107 ++++++++++++++++++++++++++++++++++++++++++++++++-------------
 2 files changed, 100 insertions(+), 27 deletions(-)

diff --git a/README.org b/README.org
index d486742356..e10b04febb 100644
--- a/README.org
+++ b/README.org
@@ -113,6 +113,13 @@ minibuffer prompt (=ack= by default) can be configured via 
the user
 option ~tmr-acknoledge-timer-text~ [ The confirmation text is
 configurable as part of {{{development-version}}}. ]
 
+#+findex: tmr-toggle-pause
+The command ~tmr-toggle-pause~ pauses the given timer. The tabulated
+view has a column to show when a timer is paused 
([[#h:51fe78e0-d614-492b-b7a3-fb6d5bd52a9a][Grid or tabulated view]]).
+Similarly, the mode line indicator adapts the text of the timer to
+tell that it is paused ([[#h:a1938fd5-64ef-4f4f-ade1-c7058d4062fc][Display 
timers on the mode line]]). [ The
+~tmr-toggle-pause~ and related functionality is part of 
{{{development-version}}}. ]
+
 #+vindex: tmr-descriptions-list
 The user option ~tmr-descriptions-list~ defines the completion
 candidates that are shown at the description prompt.  Its value can be
@@ -187,12 +194,14 @@ Timers can be viewed in a grid with ~tmr-tabulated-view~ 
(alias
 ~tmr-list-timers~). The data is placed in the =*tmr-tabulated-view*=
 buffer and looks like this:
 
+[ The pause functionality is part of {{{development-version}}}. ]
+
 #+begin_example
-Start      End        Duration   Remaining  Acknowledge?   Description
+Start      End        Duration   Remaining  Paused?  Acknowledge?   Description
 
-10:26:05   10:36:05   10m        9m 45s                    Prepare tea
-10:25:50   10:30:50   5m         4m 31s     Yes            Test the feature
-10:25:04   10:35:04   10m        8m 44s
+08:49:41   09:19:46   30m        29m 17s    Yes                     Work on 
TMR for 30 minutes
+08:49:31   08:54:31   5m         3m 53s                             Prepare tea
+08:49:21   08:59:21   10m        8m 42s              Yes            Edit the 
description with this one instead
 #+end_example
 
 If a timer has elapsed, it has a check mark associated with it,
@@ -250,6 +259,9 @@ Faces used in the tabulated view:
 #+vindex: tmr-tabulated-remaining-time
 - ~tmr-tabulated-remaining-time~ :: The timer's remaining time.
 
+#+vindex: tmr-tabulated-paused
+- ~tmr-tabulated-paused~ :: Whether the timer is paused or not.
+
 #+vindex: tmr-tabulated-acknowledgement
 - ~tmr-tabulated-acknowledgement~ :: Whether the timer needs to be
   acknowledged.
diff --git a/tmr.el b/tmr.el
index c97575ae0e..d88c2d2883 100644
--- a/tmr.el
+++ b/tmr.el
@@ -120,6 +120,20 @@ Each function must accept a timer as argument."
 Each function must accept a timer as argument."
   :type 'hook)
 
+(defcustom tmr-timer-paused-functions
+  (list #'tmr-print-message-for-paused-timer)
+  "Functions to execute when a timer is paused.
+Each function must accept a timer as argument."
+  :package-version '(tmr . "1.3.0")
+  :type 'hook)
+
+(defcustom tmr-timer-resumed-functions
+  (list #'tmr-print-message-for-resumed-timer)
+  "Functions to execute when a timer is resumed.
+Each function must accept a timer as argument."
+  :package-version '(tmr . "1.3.0")
+  :type 'hook)
+
 (defcustom tmr-finished-indicator "✔"
   "Indicator for a finished timer, shown in `tmr-tabulated-view'."
   :package-version '(tmr . "1.0.0")
@@ -249,6 +263,11 @@ Longer descriptions will be truncated."
   :package-version '(tmr . "1.0.0")
   :group 'tmr-faces)
 
+(defface tmr-paused '((t :inherit bold))
+  "Face for styling the description of a paused timer."
+  :package-version '(tmr . "1.3.0")
+  :group 'tmr-faces)
+
 (defface tmr-tabulated-start-time
   '((((class color) (min-colors 88) (background light))
      :foreground "#004476")
@@ -279,6 +298,11 @@ Longer descriptions will be truncated."
   :package-version '(tmr . "1.1.0")
   :group 'tmr-faces)
 
+(defface tmr-tabulated-paused '((t :inherit bold))
+  "Face for styling the description of a paused timer."
+  :package-version '(tmr . "1.3.0")
+  :group 'tmr-faces)
+
 (defface tmr-tabulated-acknowledgement
   '((t :inherit bold))
   "Acknowledgement indicator in the `tmr-tabulated-view'."
@@ -320,7 +344,6 @@ Longer descriptions will be truncated."
    :documentation "Time at which the timer was created.")
   (end-date
    nil
-   :read-only t
    :documentation "Time at which the timer finishes.")
   (finishedp
    nil
@@ -341,7 +364,11 @@ Longer descriptions will be truncated."
   (description
    nil
    :read-only nil
-   :documentation "Optional string describing the purpose of the timer, e.g., 
\"Stop the oven\"."))
+   :documentation "Optional string describing the purpose of the timer, e.g., 
\"Stop the oven\".")
+  (paused-remaining
+   nil
+   :read-only nil
+   :documentation "Remaining seconds when the timer was paused or nil if not 
paused."))
 
 (defun tmr--long-description (timer)
   "Return a human-readable description for TIMER."
@@ -368,6 +395,8 @@ Longer descriptions will be truncated."
               (concat "; " (propertize "acknowledge" 'face 
'tmr-must-be-acknowledged)))
              ((tmr--timer-finishedp timer)
               (concat "; " (propertize "finished" 'face 'tmr-finished)))
+             ((when-let* ((remaining (tmr--timer-paused-remaining timer)))
+              (format "; %s: remaining %s" (propertize "PAUSED" 'face 
'tmr-paused) (tmr--format-seconds remaining))))
              (t ""))
             (if description
                 (concat "; " (propertize description 'face 'tmr-description))
@@ -507,6 +536,24 @@ cancelling the original one."
   (setf (tmr--timer-description timer) description)
   (run-hooks 'tmr--update-hook))
 
+(defun tmr-toggle-pause (timer)
+  "Toggle pause/resume state of TIMER."
+  (interactive (list (tmr--read-timer "Pause/resume timer: " :active)))
+  (if-let* ((remaining (tmr--timer-paused-remaining timer)))
+      (progn
+        (setf (tmr--timer-end-date timer) (time-add (current-time) remaining))
+        (setf (tmr--timer-timer-object timer) (run-with-timer remaining nil 
#'tmr--complete timer))
+        (setf (tmr--timer-paused-remaining timer) nil)
+        (run-hooks 'tmr--update-hook)
+        (run-hook-with-args 'tmr-timer-resumed-functions timer))
+    (let ((remaining (tmr--get-seconds timer)))
+      (when (> remaining 0)
+        (cancel-timer (tmr--timer-timer-object timer))
+        (setf (tmr--timer-paused-remaining timer) remaining)
+        (run-hooks 'tmr--update-hook)
+        (run-hook-with-args 'tmr-timer-paused-functions timer)))))
+
+
 (defun tmr-toggle-acknowledge (timer)
   "Toggle ackowledge flag of TIMER."
   (interactive
@@ -628,6 +675,14 @@ Read Info node `(elisp) Desktop Notifications' for 
details."
   "Show a `message' informing the user that TIMER is cancelled."
   (message "Cancelled: <<%s>>" (tmr--long-description timer)))
 
+(defun tmr-print-message-for-paused-timer (timer)
+  "Show a `message' informing the user that TIMER is paused."
+  (message "Paused: <<%s>> (REMAINING %s)" (tmr--long-description timer) 
(tmr--format-remaining timer)))
+
+(defun tmr-print-message-for-resumed-timer (timer)
+  "Show a `message' informing the user that TIMER is resumed."
+  (message "Resumed: <<%s>> (REMAINING %s)" (tmr--long-description timer) 
(tmr--format-remaining timer)))
+
 (defvar tmr-duration-history nil
   "Minibuffer history of `tmr' durations.")
 
@@ -808,8 +863,8 @@ This map should be bound to a global prefix key."
   "t" #'tmr
   "T" #'tmr-with-details
   "l" #'tmr-tabulated-view
-  "c" #'tmr-clone
   "s" #'tmr-reschedule
+  "p" #'tmr-toggle-pause
   "a" #'tmr-toggle-acknowledge
   "e" #'tmr-edit-description
   "r" #'tmr-remove
@@ -831,7 +886,8 @@ This map should be bound to a global prefix key."
   "c" #'tmr-clone
   "a" #'tmr-toggle-acknowledge
   "e" #'tmr-edit-description
-  "s" #'tmr-reschedule)
+  "s" #'tmr-reschedule
+  "p" #'tmr-toggle-pause)
 
 ;;;;; Integration with the `embark' package
 
@@ -843,7 +899,8 @@ This map should be bound to a global prefix key."
   "c" #'tmr-clone
   "a" #'tmr-toggle-acknowledge
   "e" #'tmr-edit-description
-  "s" #'tmr-reschedule)
+  "s" #'tmr-reschedule
+  "p" #'tmr-toggle-pause)
 
 (defvar embark-keymap-alist)
 (defvar embark-post-action-hooks)
@@ -887,6 +944,7 @@ they are set to reasonable default values."
     (propertize (tmr--format-end-date timer) 'face 'tmr-tabulated-end-time)
     (propertize (tmr--format-duration timer) 'face 'tmr-duration)
     (propertize (tmr--format-remaining timer) 'face 
'tmr-tabulated-remaining-time)
+    (propertize (if (tmr--timer-paused-remaining timer) "Yes" "") 'face 
'tmr-paused)
     (propertize (if (tmr--timer-acknowledgep timer) "Yes" "") 'face 
'tmr-tabulated-acknowledgement)
     (propertize (or (tmr--timer-description timer) "") 'face 
'tmr-tabulated-description))))
 
@@ -938,6 +996,7 @@ they are set to reasonable default values."
                ("End" 10 t)
                ("Duration" 10 t)
                ("Remaining" 10 tmr-tabulated--compare-remaining)
+               ("Paused?" 8 t)
                ("Acknowledge?" 14 t)
                ("Description" 0 t)])
   (add-hook 'window-configuration-change-hook #'tmr-tabulated--window-hook nil 
t)
@@ -975,24 +1034,26 @@ they are set to reasonable default values."
 
 (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) (= (% (truncate secs) 2) 0))
-                      '(tmr-mode-line-urgent (:inverse-video t)))
-                     ((< secs 30) 'tmr-mode-line-urgent)
-                     ((= (truncate secs) 30)
-                      '(tmr-mode-line-urgent (:inverse-video t)))
-                     ((= (truncate secs) 60)
-                      '(tmr-mode-line-soon (:inverse-video t)))
-                     ((< secs 120) 'tmr-mode-line-soon)
-                     ((= (truncate secs) 120)
-                      '(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)))
+  (if-let* ((remaining (tmr--timer-paused-remaining timer)))
+      (propertize (format "PAUSED %s" (tmr--format-seconds remaining)) 'face 
'tmr-paused)
+    (let* ((secs (float-time (time-subtract (tmr--timer-end-date timer) nil)))
+           (face (cond ((and (< secs 5) (= (% (truncate secs) 2) 0))
+                        '(tmr-mode-line-urgent (:inverse-video t)))
+                       ((< secs 30) 'tmr-mode-line-urgent)
+                       ((= (truncate secs) 30)
+                        '(tmr-mode-line-urgent (:inverse-video t)))
+                       ((= (truncate secs) 60)
+                        '(tmr-mode-line-soon (:inverse-video t)))
+                       ((< secs 120) 'tmr-mode-line-soon)
+                       ((= (truncate secs) 120)
+                        '(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."

Reply via email to