emacs-orgmode
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [O] [RFC/PATCH] Fixes for org-timer


From: Kyle Meyer
Subject: Re: [O] [RFC/PATCH] Fixes for org-timer
Date: Sun, 07 Dec 2014 03:11:04 -0500
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/24.4 (gnu/linux)

Nicolas Goaziou <address@hidden> wrote:
> Kyle Meyer <address@hidden> writes:
>
>> Do you prefer to add fixes for the current design, or just move
>> directly to merging the two APIs?
>
> The latter sounds better.

I've attached updated patches.

>From da0454ea110de0b4effb59ec220d8e15960fbf84 Mon Sep 17 00:00:00 2001
From: Kyle Meyer <address@hidden>
Date: Mon, 1 Dec 2014 02:02:19 -0500
Subject: [PATCH 1/2] org-timer.el (org-timer): Recognize double prefix

* lisp/org-timer.el (org-timer): Follow the behavior described in the
docstring for a double prefix argument.
---
 lisp/org-timer.el | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/lisp/org-timer.el b/lisp/org-timer.el
index d02362c..c9710ca 100644
--- a/lisp/org-timer.el
+++ b/lisp/org-timer.el
@@ -177,11 +177,13 @@ (defun org-timer (&optional restart no-insert-p)
 If NO-INSERT-P is non-nil, return the string instead of inserting
 it in the buffer."
   (interactive "P")
-  (when (or (equal restart '(4)) (not org-timer-start-time))
-    (org-timer-start))
-  (if no-insert-p
-      (org-timer-value-string)
-    (insert (org-timer-value-string))))
+  (if (equal restart '(16))
+      (org-timer-start restart)
+    (when (or (equal restart '(4)) (not org-timer-start-time))
+      (org-timer-start))
+    (if no-insert-p
+       (org-timer-value-string)
+      (insert (org-timer-value-string)))))
 
 (defun org-timer-value-string ()
   "Set the timer string."
-- 
2.1.3

>From 981cee236e91cd40a006eb5eef43095e449b6cc9 Mon Sep 17 00:00:00 2001
From: Kyle Meyer <address@hidden>
Date: Sun, 7 Dec 2014 02:24:54 -0500
Subject: [PATCH 2/2] org-timer.el: Merge API for the two timers

* lisp/org-timer.el (org-timer-stop): Support countdown timers in addition
to relative timers.

* lisp/org-timer.el (org-timer-cancel-timer): Remove function.

* lisp/org-timer.el (org-timer-pause-or-continue): Support countdown
timers in addition to relative timers.

* testing/lisp/test-org-timer.el: New file.

* doc/org.texi: Merge relative and countdown timer nodes.

Several previous issues are fixed with these changes.

- org-timer-set-timer and org-timer-cancel-timer did not reset
  org-timer-start-time after countdown completed.

- Because org-timer-start did not return org-timer-pause-time to nil,
  the modeline remained stuck at the paused time.

- When org-timer-start was called with a countdown timer, the modeline
  was updated for the new relative timer, but the countdown timer
  remained scheduled.

- When org-timer-pause-or-continue was called with a countdown timer
  running, the modeline was put in a paused state, but the countdown
  timer remained scheduled.

- When org-timer-stop was called with a countdown timer running, the
  timer was removed from the modeline, but the countdown timer remained
  scheduled.

- When org-timer-set-timer was called with a paused relative timer, the
  relative timer was not reset properly (org-timer-pause-time was still
  non-nil) and the modeline remained in the paused state of the relative
  timer, even though the countdown timer was scheduled with
  run-with-timer.

- Running org-timer-set-timer at the beginning of an empty buffer
  resulted in an args-out-of-range error (due to the org-get-at-eol
  call).
---
 doc/org.texi                   |  84 ++++++-------
 etc/ORG-NEWS                   |   5 +
 lisp/org-timer.el              | 207 ++++++++++++++++++-------------
 testing/lisp/test-org-timer.el | 273 +++++++++++++++++++++++++++++++++++++++++
 4 files changed, 438 insertions(+), 131 deletions(-)
 create mode 100644 testing/lisp/test-org-timer.el

diff --git a/doc/org.texi b/doc/org.texi
index f9436b3..f7444ce 100644
--- a/doc/org.texi
+++ b/doc/org.texi
@@ -462,8 +462,7 @@ Dates and times
 * Deadlines and scheduling::    Planning your work
 * Clocking work time::          Tracking how long you spend on a task
 * Effort estimates::            Planning work effort in advance
-* Relative timer::              Notes with a running timer
-* Countdown timer::             Starting a countdown timer for a task
+* Timers::                      Notes with a running timer
 
 Creating timestamps
 
@@ -5790,8 +5789,7 @@ is used in a much wider sense.
 * Deadlines and scheduling::    Planning your work
 * Clocking work time::          Tracking how long you spend on a task
 * Effort estimates::            Planning work effort in advance
-* Relative timer::              Notes with a running timer
-* Countdown timer::             Starting a countdown timer for a task
+* Timers::                      Notes with a running timer
 @end menu
 
 
@@ -6794,60 +6792,56 @@ with the @kbd{/} key in the agenda (@pxref{Agenda 
commands}).  If you have
 these estimates defined consistently, two or three key presses will narrow
 down the list to stuff that fits into an available time slot.
 
address@hidden Relative timer
address@hidden Taking notes with a relative timer
address@hidden Timers
address@hidden Taking notes with a timer
 @cindex relative timer
address@hidden countdown timer
address@hidden ;
+
+Org provides provides two types of timers.  There is a relative timer that
+counts up, which can be useful when taking notes during, for example, a
+meeting or a video viewing.  There is also a countdown timer.
+
+The relative and countdown are started with separate commands.
+
address@hidden @kbd
address@hidden C-x 0,org-timer-start}
+Start or reset the relative timer.  By default, the timer is set to 0.  When
+called with a @kbd{C-u} prefix, prompt the user for a starting offset.  If
+there is a timer string at point, this is taken as the default, providing a
+convenient way to restart taking notes after a break in the process.  When
+called with a double prefix argument @kbd{C-u C-u}, change all timer strings
+in the active region by a certain amount.  This can be used to fix timer
+strings if the timer was not started at exactly the right moment.
address@hidden C-x ;,org-timer-set-timer}
+Start a countdown timer.  The user is prompted for a duration.
address@hidden sets the default countdown value.  Giving a
+prefix numeric argument overrides this default value.  This command is
+available as @kbd{;} in agenda buffers.
address@hidden table
 
-When taking notes during, for example, a meeting or a video viewing, it can
-be useful to have access to times relative to a starting time.  Org provides
-such a relative timer and make it easy to create timed notes.
+Once started, relative and countdown timers are controlled with the same
+commands.
 
 @table @kbd
 @orgcmd{C-c C-x .,org-timer}
-Insert a relative time into the buffer.  The first time you use this, the
-timer will be started.  When called with a prefix argument, the timer is
-restarted.
+Insert the value of the current relative or countdown timer into the buffer.
+If no timer is running, the relative timer will be started.  When called with
+a prefix argument, the relative timer is restarted.
 @orgcmd{C-c C-x -,org-timer-item}
-Insert a description list item with the current relative time.  With a prefix
-argument, first reset the timer to 0.
+Insert a description list item with the value of the current relative or
+countdown timer.  With a prefix argument, first reset the relative timer to
+0.
 @address@hidden,org-insert-heading}
 Once the timer list is started, you can also use @address@hidden to insert
 new timer items.
address@hidden for key sequences with a comma, command name macros fail :(
address@hidden C-c C-x ,
address@hidden C-c C-x ,
-Pause the timer, or continue it if it is already paused
-(@command{org-timer-pause-or-continue}).
address@hidden removed the sentence because it is redundant to the following 
item
address@hidden C-u C-c C-x ,
address@hidden C-u C-c C-x ,
address@hidden C-x \,,org-timer-pause-or-continue}
+Pause the timer, or continue it if it is already paused.
address@hidden C-x _,org-timer-stop}
 Stop the timer.  After this, you can only start a new timer, not continue the
 old one.  This command also removes the timer from the mode line.
address@hidden C-x 0,org-timer-start}
-Reset the timer without inserting anything into the buffer.  By default, the
-timer is reset to 0.  When called with a @kbd{C-u} prefix, reset the timer to
-specific starting offset.  The user is prompted for the offset, with a
-default taken from a timer string at point, if any, So this can be used to
-restart taking notes after a break in the process.  When called with a double
-prefix argument @kbd{C-u C-u}, change all timer strings in the active region
-by a certain amount.  This can be used to fix timer strings if the timer was
-not started at exactly the right moment.
 @end table
 
address@hidden Countdown timer
address@hidden Countdown timer
address@hidden Countdown timer
address@hidden C-c C-x ;
address@hidden ;
-
-Calling @code{org-timer-set-timer} from an Org mode buffer runs a countdown
-timer.  Use @kbd{;} from agenda buffers, @key{C-c C-x ;} everywhere else.
-
address@hidden prompts the user for a duration and displays a
-countdown timer in the modeline.  @code{org-timer-default-timer} sets the
-default countdown value.  Giving a prefix numeric argument overrides this
-default value.
-
 @node Capture - Refile - Archive
 @chapter Capture - Refile - Archive
 @cindex capture
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index 5ce53c5..f719886 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -72,6 +72,8 @@ This function inserted a Beamer specific template at point or 
in
 current subtree.  Use ~org-export-insert-default-template~ instead, as
 it provides more features and covers all export back-ends.  It is also
 accessible from the export dispatcher.
+*** Removed function ~org-timer-cancel-timer~
+~org-timer-stop~ now stops both relative and countdown timers.
 ** Removed options
 *** ~org-list-empty-line-terminates-plain-lists~ is deprecated
 It will be kept in code base until next release, for backward
@@ -174,6 +176,9 @@ special blocks and images.  See docstring for more 
information.
 Headlines, for which the property ~UNNUMBERED~ is non-nil, are now
 exported without section numbers irrespective of their levels.  The
 property is inherited by children.
+*** Countdown timers can now be paused.
+~org-timer-pause-time~ wil now pause and restart both relative and
+countdown timers.
 ** Miscellaneous
 *** Strip all meta data from ITEM special property
 ITEM special property does not contain TODO, priority or tags anymore.
diff --git a/lisp/org-timer.el b/lisp/org-timer.el
index c9710ca..f35e8be 100644
--- a/lisp/org-timer.el
+++ b/lisp/org-timer.el
@@ -1,4 +1,4 @@
-;;; org-timer.el --- The relative timer code for Org-mode
+;;; org-timer.el --- Timer code for Org mode
 
 ;; Copyright (C) 2008-2014 Free Software Foundation, Inc.
 
@@ -24,13 +24,20 @@
 ;;
 ;;; Commentary:
 
-;; This file contains the relative timer code for Org-mode
+;; This file implements two types of timers for Org buffers:
+;;
+;; - A relative timer that counts up (from 0 or a specified offset)
+;; - A countdown timer that counts down from a specified time
+;;
+;; The relative and countdown timers differ in their entry points.
+;; Use `org-timer' or `org-timer-start' to start the relative timer,
+;; and `org-timer-set-timer' to start the countdown timer.
 
 ;;; Code:
 
 (require 'org)
+(require 'org-clock)
 
-(declare-function org-notify "org-clock" (notification &optional play-sound))
 (declare-function org-agenda-error "org-agenda" ())
 
 (defvar org-timer-start-time nil
@@ -39,13 +46,22 @@ (defvar org-timer-start-time nil
 (defvar org-timer-pause-time nil
   "Time when the timer was paused.")
 
+(defvar org-timer-countdown-timer nil
+  "Current countdown timer.
+This is a timer object if there is an active countdown timer,
+'paused' if there is a paused countdown timer, and nil
+otherwise.")
+
+(defvar org-timer-countdown-timer-title nil
+  "Title for notification displayed when a countdown finishes.")
+
 (defconst org-timer-re 
"\\([-+]?[0-9]+\\):\\([0-9]\\{2\\}\\):\\([0-9]\\{2\\}\\)"
   "Regular expression used to match timer stamps.")
 
 (defcustom org-timer-format "%s "
   "The format to insert the time of the timer.
 This format must contain one instance of \"%s\" which will be replaced by
-the value of the relative timer."
+the value of the timer."
   :group 'org-time
   :type 'string)
 
@@ -76,13 +92,13 @@ (defvar org-timer-start-hook nil
   "Hook run after relative timer is started.")
 
 (defvar org-timer-stop-hook nil
-  "Hook run before relative timer is stopped.")
+  "Hook run before relative or countdown timer is stopped.")
 
 (defvar org-timer-pause-hook nil
-  "Hook run before relative timer is paused.")
+  "Hook run before relative or countdown timer is paused.")
 
 (defvar org-timer-continue-hook nil
-  "Hook run after relative timer is continued.")
+  "Hook run after relative or countdown timer is continued.")
 
 (defvar org-timer-set-hook nil
   "Hook run after countdown timer is set.")
@@ -90,9 +106,6 @@ (defvar org-timer-set-hook nil
 (defvar org-timer-done-hook nil
   "Hook run after countdown timer reaches zero.")
 
-(defvar org-timer-cancel-hook nil
-  "Hook run before countdown timer is canceled.")
-
 ;;;###autoload
 (defun org-timer-start (&optional offset)
   "Set the starting time for the relative timer to now.
@@ -105,8 +118,12 @@ (defun org-timer-start (&optional offset)
 the amount, with the default to make the first timer string in
 the region 0:00:00."
   (interactive "P")
-  (if (equal offset '(16))
-      (call-interactively 'org-timer-change-times-in-region)
+  (cond
+   ((equal offset '(16))
+    (call-interactively 'org-timer-change-times-in-region))
+   (org-timer-countdown-timer
+    (user-error "Countdown timer is running.  Cancel first"))
+   (t
     (let (delta def s)
       (if (not offset)
          (setq org-timer-start-time (current-time))
@@ -123,45 +140,66 @@ (defun org-timer-start (&optional offset)
          (setq delta (org-timer-hms-to-secs (org-timer-fix-incomplete s)))))
        (setq org-timer-start-time
              (seconds-to-time
-              (- (org-float-time) delta))))
+              ;; Pass `current-time' result to `org-float-time'
+              ;; (instead of calling without arguments) so that only
+              ;; `current-time' has to be overriden in tests.
+              (- (org-float-time (current-time)) delta))))
+      (setq org-timer-pause-time nil)
       (org-timer-set-mode-line 'on)
       (message "Timer start time set to %s, current value is %s"
               (format-time-string "%T" org-timer-start-time)
               (org-timer-secs-to-hms (or delta 0)))
-      (run-hooks 'org-timer-start-hook))))
+      (run-hooks 'org-timer-start-hook)))))
 
 (defun org-timer-pause-or-continue (&optional stop)
-  "Pause or continue the relative timer.
+  "Pause or continue the relative or countdown timer.
 With prefix arg STOP, stop it entirely."
   (interactive "P")
   (cond
    (stop (org-timer-stop))
    ((not org-timer-start-time) (error "No timer is running"))
    (org-timer-pause-time
-    ;; timer is paused, continue
-    (setq org-timer-start-time
-         (seconds-to-time
-          (-
-           (org-float-time)
-           (- (org-float-time org-timer-pause-time)
-              (org-float-time org-timer-start-time))))
-         org-timer-pause-time nil)
-    (org-timer-set-mode-line 'on)
-    (run-hooks 'org-timer-continue-hook)
-    (message "Timer continues at %s" (org-timer-value-string)))
+    (let ((start-secs (org-float-time org-timer-start-time))
+         (pause-secs (org-float-time org-timer-pause-time)))
+      (if org-timer-countdown-timer
+         (progn
+           (let ((new-secs (- start-secs pause-secs)))
+             (setq org-timer-countdown-timer
+                   (org-timer--run-countdown-timer
+                    new-secs org-timer-countdown-timer-title))
+             (setq org-timer-start-time
+                   (time-add (current-time) (seconds-to-time new-secs)))))
+       (setq org-timer-start-time
+             ;; Pass `current-time' result to `org-float-time'
+             ;; (instead of calling without arguments) so that only
+             ;; `current-time' has to be overriden in tests.
+             (seconds-to-time (- (org-float-time (current-time))
+                                 (- pause-secs start-secs)))))
+      (setq org-timer-pause-time nil)
+      (org-timer-set-mode-line 'on)
+      (run-hooks 'org-timer-continue-hook)
+      (message "Timer continues at %s" (org-timer-value-string))))
    (t
     ;; pause timer
+    (when org-timer-countdown-timer
+      (cancel-timer org-timer-countdown-timer)
+      (setq org-timer-countdown-timer 'pause))
     (run-hooks 'org-timer-pause-hook)
     (setq org-timer-pause-time (current-time))
     (org-timer-set-mode-line 'pause)
     (message "Timer paused at %s" (org-timer-value-string)))))
 
 (defun org-timer-stop ()
-  "Stop the relative timer."
+  "Stop the relative or countdown timer."
   (interactive)
+  (unless org-timer-start-time
+    (user-error "No timer running"))
+  (when (timerp org-timer-countdown-timer)
+    (cancel-timer org-timer-countdown-timer))
   (run-hooks 'org-timer-stop-hook)
   (setq org-timer-start-time nil
-       org-timer-pause-time nil)
+       org-timer-pause-time nil
+       org-timer-countdown-timer nil)
   (org-timer-set-mode-line 'off)
   (message "Timer stopped"))
 
@@ -191,11 +229,10 @@ (defun org-timer-value-string ()
          (org-timer-secs-to-hms
           (abs (floor (org-timer-seconds))))))
 
-(defvar org-timer-timer-is-countdown nil)
 (defun org-timer-seconds ()
-  (if org-timer-timer-is-countdown
+  (if org-timer-countdown-timer
       (- (org-float-time org-timer-start-time)
-        (org-float-time (current-time)))
+        (org-float-time (or org-timer-pause-time (current-time))))
     (- (org-float-time (or org-timer-pause-time (current-time)))
        (org-float-time org-timer-start-time))))
 
@@ -290,7 +327,7 @@ (defvar org-timer-mode-line-timer nil)
 (defvar org-timer-mode-line-string nil)
 
 (defun org-timer-set-mode-line (value)
-  "Set the mode-line display of the relative timer.
+  "Set the mode-line display for relative or countdown timer.
 VALUE can be `on', `off', or `pause'."
   (when (or (eq org-timer-display 'mode-line)
            (eq org-timer-display 'both))
@@ -349,35 +386,20 @@ (defun org-timer-update-mode-line ()
          (concat " <" (substring (org-timer-value-string) 0 -1) ">"))
     (force-mode-line-update)))
 
-(defvar org-timer-current-timer nil)
-(defun org-timer-cancel-timer ()
-  "Cancel the current timer."
-  (interactive)
-  (if (not org-timer-current-timer)
-      (message "No timer to cancel")
-    (run-hooks 'org-timer-cancel-hook)
-    (cancel-timer org-timer-current-timer)
-    (setq org-timer-current-timer nil
-         org-timer-timer-is-countdown nil)
-    (org-timer-set-mode-line 'off)
-    (message "Last timer canceled")))
-
 (defun org-timer-show-remaining-time ()
   "Display the remaining time before the timer ends."
   (interactive)
   (require 'time)
-  (if (not org-timer-current-timer)
+  (if (not org-timer-countdown-timer)
       (message "No timer set")
     (let* ((rtime (decode-time
-                  (time-subtract (timer--time org-timer-current-timer)
+                  (time-subtract (timer--time org-timer-countdown-timer)
                                  (current-time))))
           (rsecs (nth 0 rtime))
           (rmins (nth 1 rtime)))
       (message "%d minute(s) %d seconds left before next time out"
               rmins rsecs))))
 
-(defvar org-clock-sound)
-
 ;;;###autoload
 (defun org-timer-set-timer (&optional opt)
   "Prompt for a duration and set a timer.
@@ -400,7 +422,10 @@ (defun org-timer-set-timer (&optional opt)
 minutes in the Effort property, if any.  You can ignore this by
 using three `C-u' prefix arguments."
   (interactive "P")
-  (let* ((effort-minutes (org-get-at-eol 'effort-minutes 1))
+  (when (and org-timer-start-time
+            (not org-timer-countdown-timer))
+    (user-error "Relative timer is running.  Stop first"))
+  (let* ((effort-minutes (ignore-errors (org-get-at-eol 'effort-minutes 1)))
         (minutes (or (and (not (equal opt '(64)))
                           effort-minutes
                           (number-to-string effort-minutes))
@@ -415,47 +440,57 @@ (defun org-timer-set-timer (&optional opt)
        (org-timer-show-remaining-time)
       (let* ((mins (string-to-number (match-string 0 minutes)))
             (secs (* mins 60))
-            (hl (cond
-                 ((string-match "Org Agenda" (buffer-name))
-                  (let* ((marker (or (get-text-property (point) 'org-marker)
-                                     (org-agenda-error)))
-                         (hdmarker (or (get-text-property (point) 
'org-hd-marker)
-                                       marker))
-                         (pos (marker-position marker)))
-                    (with-current-buffer (marker-buffer marker)
-                      (widen)
-                      (goto-char pos)
-                      (org-show-entry)
-                      (or (ignore-errors (org-get-heading))
-                          (concat "File:" (file-name-nondirectory 
(buffer-file-name)))))))
-                 ((derived-mode-p 'org-mode)
-                  (or (ignore-errors (org-get-heading))
-                      (concat "File:" (file-name-nondirectory 
(buffer-file-name)))))
-                 (t (error "Not in an Org buffer"))))
-            timer-set)
-       (if (or (and org-timer-current-timer
-                    (or (equal opt '(16))
-                        (y-or-n-p "Replace current timer? ")))
-               (not org-timer-current-timer))
+            (hl (org-timer--get-timer-title)))
+       (if (or (not org-timer-countdown-timer)
+               (equal opt '(16))
+               (y-or-n-p "Replace current timer? "))
            (progn
-             (require 'org-clock)
-             (when org-timer-current-timer
-               (cancel-timer org-timer-current-timer))
-             (setq org-timer-current-timer
-                   (run-with-timer
-                    secs nil `(lambda ()
-                                (setq org-timer-current-timer nil)
-                                (org-notify ,(format "%s: time out" hl) 
,org-clock-sound)
-                                (setq org-timer-timer-is-countdown nil)
-                                (org-timer-set-mode-line 'off)
-                                (run-hooks 'org-timer-done-hook))))
+             (when (timerp org-timer-countdown-timer)
+               (cancel-timer org-timer-countdown-timer))
+             (setq org-timer-countdown-timer-title
+                   (org-timer--get-timer-title))
+             (setq org-timer-countdown-timer
+                   (org-timer--run-countdown-timer
+                    secs org-timer-countdown-timer-title))
              (run-hooks 'org-timer-set-hook)
-             (setq org-timer-timer-is-countdown t
-                   org-timer-start-time
-                   (time-add (current-time) (seconds-to-time (* mins 60))))
+             (setq org-timer-start-time
+                   (time-add (current-time) (seconds-to-time secs)))
+             (setq org-timer-pause-time nil)
              (org-timer-set-mode-line 'on))
          (message "No timer set"))))))
 
+(defun org-timer--run-countdown-timer (secs title)
+  "Start countdown timer that will last SECS.
+TITLE will be appended to the notification message displayed when
+time is up."
+  (let ((msg (format "%s: time out" title)))
+    (run-with-timer
+     secs nil `(lambda ()
+                (setq org-timer-countdown-timer nil
+                      org-timer-start-time nil)
+                (org-notify ,msg ,org-clock-sound)
+                (org-timer-set-mode-line 'off)
+                (run-hooks 'org-timer-done-hook)))))
+
+(defun org-timer--get-timer-title ()
+  "Construct timer title from heading or file name of Org buffer."
+  (cond
+   ((string-match "Org Agenda" (buffer-name))
+    (let* ((marker (or (get-text-property (point) 'org-marker)
+                      (org-agenda-error)))
+          (hdmarker (or (get-text-property (point) 'org-hd-marker)
+                        marker)))
+      (with-current-buffer (marker-buffer marker)
+       (org-with-wide-buffer
+        (goto-char hdmarker)
+        (org-show-entry)
+        (or (ignore-errors (org-get-heading))
+            (buffer-name))))))
+   ((derived-mode-p 'org-mode)
+    (or (ignore-errors (org-get-heading))
+       (buffer-name)))
+   (t (error "Not in an Org buffer"))))
+
 (provide 'org-timer)
 
 ;; Local variables:
diff --git a/testing/lisp/test-org-timer.el b/testing/lisp/test-org-timer.el
new file mode 100644
index 0000000..6409b12
--- /dev/null
+++ b/testing/lisp/test-org-timer.el
@@ -0,0 +1,273 @@
+;;; test-org-timer.el --- Tests for org-timer.el
+
+;; Copyright (C) 2014  Kyle Meyer
+
+;; Author: Kyle Meyer <address@hidden>
+
+;; This file is not part of GNU Emacs.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Code:
+
+(defmacro test-org-timer/with-temp-text (text &rest body)
+  "Like `org-test-with-temp-text', but set timer-specific variables.
+Also, mute output from `message'."
+  (declare (indent 1))
+  `(cl-letf (((symbol-function 'message) (lambda (&rest args) nil)))
+     (org-test-with-temp-text ,text
+       (let (org-timer-start-time
+            org-timer-pause-time
+            org-timer-countdown-timer
+            org-timer-display)
+        (unwind-protect (progn ,@body)
+          (when (timerp org-timer-countdown-timer)
+            (cancel-timer org-timer-countdown-timer)))))))
+
+(defmacro test-org-timer/with-current-time (time &rest body)
+  "Run BODY, setting `current-time' output to TIME."
+  (declare (indent 1))
+  `(cl-letf (((symbol-function 'current-time) (lambda () ,time)))
+     ,@body))
+
+
+;;; Time conversion and formatting
+
+(ert-deftest test-org-timer/secs-to-hms ()
+  "Test conversion between HMS format and seconds."
+  ;; Seconds to HMS, and back again
+  (should
+   (equal "0:00:30"
+         (org-timer-secs-to-hms 30)))
+  (should
+   (equal 30
+         (org-timer-hms-to-secs (org-timer-secs-to-hms 30))))
+  ;; Minutes to HMS, and back again
+  (should
+   (equal "0:02:10"
+         (org-timer-secs-to-hms 130)))
+  (should
+   (equal 130
+         (org-timer-hms-to-secs (org-timer-secs-to-hms 130))))
+  ;; Hours to HMS, and back again
+  (should
+   (equal "1:01:30"
+         (org-timer-secs-to-hms 3690)))
+  (should
+   (equal 3690
+         (org-timer-hms-to-secs (org-timer-secs-to-hms 3690))))
+  ;; Negative seconds to HMS, and back again
+  (should
+   (equal "-1:01:30"
+         (org-timer-secs-to-hms -3690)))
+  (should
+   (equal -3690
+         (org-timer-hms-to-secs (org-timer-secs-to-hms -3690)))))
+
+(ert-deftest test-org-timer/fix-incomplete ()
+  "Test conversion to complete HMS format."
+  ;; No fix is needed.
+  (should
+   (equal "1:02:03"
+         (org-timer-fix-incomplete "1:02:03")))
+  ;; Hour is missing.
+  (should
+   (equal "0:02:03"
+         (org-timer-fix-incomplete "02:03")))
+  ;; Minute is missing.
+  (should
+   (equal "0:00:03"
+         (org-timer-fix-incomplete "03"))))
+
+(ert-deftest test-org-timer/change-times ()
+  "Test changing HMS format by offset."
+  ;; Add time.
+  (should
+   (equal "
+1:31:15
+4:00:55"
+         (org-test-with-temp-text "
+0:00:25
+2:30:05"
+           (org-timer-change-times-in-region (point-min) (point-max)
+                                             "1:30:50")
+           (buffer-string))))
+  ;; Subtract time.
+  (should
+   (equal "
+-1:30:25
+0:59:15"
+         (org-test-with-temp-text "
+0:00:25
+2:30:05"
+           (org-timer-change-times-in-region (point-min) (point-max)
+                                             "-1:30:50")
+           (buffer-string)))))
+
+
+;;; Timers
+
+;; Dummy times for overriding `current-time'
+(defvar test-org-timer/time0 '(21635 62793 797149 675000))
+;; Add 3 minutes and 26 seconds.
+(defvar test-org-timer/time1
+  (time-add test-org-timer/time0 (seconds-to-time 206)))
+;; Add 2 minutes and 41 seconds (6 minutes and 7 seconds total).
+(defvar test-org-timer/time2
+  (time-add test-org-timer/time1 (seconds-to-time 161)))
+;; Add 4 minutes and 55 seconds (11 minutes and 2 seconds total).
+(defvar test-org-timer/time3
+  (time-add test-org-timer/time2 (seconds-to-time 295)))
+
+(ert-deftest test-org-timer/start-relative ()
+  "Test starting relative timer."
+  ;; Insert plain timer string, starting with `org-timer-start'.
+  (should
+   (equal "0:03:26"
+         (test-org-timer/with-temp-text ""
+           (test-org-timer/with-current-time test-org-timer/time0
+             (org-timer-start))
+           (test-org-timer/with-current-time test-org-timer/time1
+             (org-timer))
+           (org-trim (buffer-string)))))
+  ;; Insert item timer string.
+  (should
+   (equal "- 0:03:26 ::"
+         (test-org-timer/with-temp-text ""
+           (test-org-timer/with-current-time test-org-timer/time0
+             (org-timer-start))
+           (test-org-timer/with-current-time test-org-timer/time1
+             (org-timer-item))
+           (org-trim (buffer-string)))))
+  ;; Start with `org-timer'.
+  (should
+   (equal "0:00:00 0:03:26"
+         (test-org-timer/with-temp-text ""
+           (test-org-timer/with-current-time test-org-timer/time0
+             (org-timer))
+           (test-org-timer/with-current-time test-org-timer/time1
+             (org-timer))
+           (org-trim (buffer-string)))))
+  ;; Restart with `org-timer'.
+  (should
+   (equal "0:00:00"
+         (test-org-timer/with-temp-text ""
+           (test-org-timer/with-current-time test-org-timer/time0
+             (org-timer-start))
+           (test-org-timer/with-current-time test-org-timer/time1
+             (org-timer '(4)))
+           (org-trim (buffer-string))))))
+
+(ert-deftest test-org-timer/set-timer ()
+  "Test setting countdown timer."
+  (should
+   (equal "0:06:34"
+         (test-org-timer/with-temp-text ""
+           (test-org-timer/with-current-time test-org-timer/time0
+             (org-timer-set-timer 10))
+           (test-org-timer/with-current-time test-org-timer/time1
+             (org-timer))
+           (org-trim (buffer-string))))))
+
+(ert-deftest test-org-timer/pause-timer ()
+  "Test pausing relative and countdown timers."
+  ;; Pause relative timer.
+  (should
+   (equal "0:03:26"
+         (test-org-timer/with-temp-text ""
+           (test-org-timer/with-current-time test-org-timer/time0
+             (org-timer-start))
+           (test-org-timer/with-current-time test-org-timer/time1
+             (org-timer-pause-or-continue))
+           (org-timer)
+           (org-trim (buffer-string)))))
+  ;; Pause then continue relative timer.
+  (should
+   (equal "0:08:21"
+         (test-org-timer/with-temp-text ""
+           (test-org-timer/with-current-time test-org-timer/time0
+             (org-timer-start))
+           (test-org-timer/with-current-time test-org-timer/time1
+             (org-timer-pause-or-continue))
+           (test-org-timer/with-current-time test-org-timer/time2
+             (org-timer-pause-or-continue))
+           (test-org-timer/with-current-time test-org-timer/time3
+             (org-timer))
+           (org-trim (buffer-string)))))
+  ;; Pause then continue countdown timer.
+  (should
+   (equal "0:01:39"
+         (test-org-timer/with-temp-text ""
+           (test-org-timer/with-current-time test-org-timer/time0
+             (org-timer-set-timer 10))
+           (test-org-timer/with-current-time test-org-timer/time1
+             (org-timer-pause-or-continue))
+           (test-org-timer/with-current-time test-org-timer/time2
+             (org-timer-pause-or-continue))
+           (test-org-timer/with-current-time test-org-timer/time3
+             (org-timer))
+           (org-trim (buffer-string))))))
+
+(ert-deftest test-org-timer/stop ()
+  "Test stopping relative and countdown timers."
+  ;; Stop running relative timer.
+  (test-org-timer/with-temp-text ""
+    (test-org-timer/with-current-time test-org-timer/time0
+      (org-timer-start))
+    (test-org-timer/with-current-time test-org-timer/time1
+      (org-timer-stop))
+    (should-not org-timer-start-time))
+  ;; Stop paused relative timer.
+  (test-org-timer/with-temp-text ""
+    (test-org-timer/with-current-time test-org-timer/time0
+      (org-timer-start))
+    (test-org-timer/with-current-time test-org-timer/time1
+      (org-timer-pause-or-continue)
+      (org-timer-stop))
+    (should-not org-timer-start-time)
+    (should-not org-timer-pause-time))
+  ;; Stop running countdown timer.
+  (test-org-timer/with-temp-text ""
+    (test-org-timer/with-current-time test-org-timer/time0
+      (org-timer-set-timer 10))
+    (test-org-timer/with-current-time test-org-timer/time1
+      (org-timer-stop))
+    (should-not org-timer-start-time)
+    (should-not org-timer-countdown-timer))
+  ;; Stop paused countdown timer.
+  (test-org-timer/with-temp-text ""
+    (test-org-timer/with-current-time test-org-timer/time0
+      (org-timer-set-timer 10))
+    (test-org-timer/with-current-time test-org-timer/time1
+      (org-timer-pause-or-continue)
+      (org-timer-stop))
+    (should-not org-timer-start-time)
+    (should-not org-timer-pause-time)
+    (should-not org-timer-countdown-timer)))
+
+(ert-deftest test-org-timer/other-timer-error ()
+  "Test for error when other timer running."
+  ;; Relative timer is running.
+  (should-error
+   (test-org-timer/with-temp-text ""
+     (org-timer-start)
+     (org-timer-set-timer 10)))
+  ;; Countdown timer is running.
+  (should-error
+   (test-org-timer/with-temp-text ""
+     (org-timer-set-timer 10)
+     (org-timer-start))))
+
+(provide 'test-org-timer)
+;;; test-org-timer.el end here
-- 
2.1.3


-- 
Kyle

reply via email to

[Prev in Thread] Current Thread [Next in Thread]