Browse Source

Use the new syntax .+1d/3d for habit repeaters

John Wiegley 15 years ago
parent
commit
f93ace5368
5 changed files with 49 additions and 57 deletions
  1. 12 11
      doc/org.texi
  2. 11 0
      lisp/ChangeLog
  3. 4 10
      lisp/org-agenda.el
  4. 19 33
      lisp/org-habit.el
  5. 3 3
      lisp/org.el

+ 12 - 11
doc/org.texi

@@ -3646,17 +3646,18 @@ called ``habits''.  A habit has the following properties:
 
 @enumerate
 @item
-The habit is a TODO, with a TODO keyword that represents an open state.
+You have enabled the @code{habits} module by customizing the variable
+@code{org-modules}.
+@item
+The habit is a TODO, with a TODO keyword representing an open state.
 @item
 The property @code{STYLE} is set to the value @code{habit}.
 @item
 The TODO has a scheduled date, with a @code{.+} style repeat interval.
 @item
-The TODO may also have a deadline, as long as it also has a @code{.+} style
-repeat interval and it starts a number of days after the scheduled date equal
-to the difference between the repeat interval lengths@footnote{Note that if
-you don't set this right, Org will alert you as to what's incorrect about the
-habit definition.}.
+The TODO may also have minimum and maximum ranges specified by using the
+syntax @samp{.+2d/3d}, which says that you want to do the task at least every
+three days, but at most every two days.
 @item
 You must also have state logging for the @code{DONE} state enabled, in order
 for historical data to be represented in the consistency graph.  If it's not
@@ -3669,7 +3670,7 @@ actual habit with some history:
 
 @example
 ** TODO Shave
-   SCHEDULED: <2009-10-17 Sat .+2d> DEADLINE: <2009-10-19 Mon .+4d>
+   SCHEDULED: <2009-10-17 Sat .+2d/4d>
    - State "DONE"       from "TODO"       [2009-10-15 Thu]
    - State "DONE"       from "TODO"       [2009-10-12 Mon]
    - State "DONE"       from "TODO"       [2009-10-10 Sat]
@@ -3687,10 +3688,10 @@ actual habit with some history:
 @end example
 
 What this habit says is: I want to shave at most every 2 days (given by the
-@code{SCHEDULED} date and repeat interval) at least every 4 days (given by
-the @code{DEADLINE} date and repeat interval).  If today is the 15th, then
-the habit first appears in the agenda on Oct 17, after the minimum of 2 days
-has elapsed, and will appear overdue on Oct 19, after four days have elapsed.
+@code{SCHEDULED} date and repeat interval) and at least every 4 days.  If
+today is the 15th, then the habit first appears in the agenda on Oct 17,
+after the minimum of 2 days has elapsed, and will appear overdue on Oct 19,
+after four days have elapsed.
 
 What's really useful about habits is that they are displayed along with a
 conistency graph, to show how consistent you've been at getting that task

+ 11 - 0
lisp/ChangeLog

@@ -1,5 +1,16 @@
 2009-10-20  John Wiegley  <jwiegley@gmail.com>
 
+	* org-habit.el (org-habit-duration-to-days): Made this function
+	more general.
+	(org-habit-parse-todo): Parse the new ".+N/N" style repeater.
+
+	* org-agenda.el (org-agenda-get-deadlines): Removed all mention of
+	habits, since they don't use DEADLINE anymore.
+
+	* org.el (org-repeat-re, org-display-custom-time)
+	(org-timestamp-change): Extended to support the new ".+N/N"
+	syntax, used for habits.
+
 	* org-clock.el (org-clock-resolve-clock): Fixed an incorrect
 	variable reference.
 

+ 4 - 10
lisp/org-agenda.el

@@ -4231,7 +4231,7 @@ the documentation of `org-diary'."
 	 (regexp org-deadline-time-regexp)
 	 (todayp (org-agenda-todayp date)) ; DATE bound by calendar
 	 (d1 (calendar-absolute-from-gregorian date))  ; DATE bound by calendar
-	 d2 diff dfrac wdays pos pos1 category tags habitp
+	 d2 diff dfrac wdays pos pos1 category tags
 	 ee txt head face s todo-state upcomingp donep timestr)
     (goto-char (point-min))
     (while (re-search-forward regexp nil t)
@@ -4244,12 +4244,6 @@ the documentation of `org-diary'."
 		  (match-string 1) d1 'past
 		  org-agenda-repeating-timestamp-show-all)
 	      diff (- d2 d1)
-	      ;; Never show habits as deadline entries, only as scheduled
-	      ;; entries.  The habit code already requires that every habit
-	      ;; have a scheduled date if it has a deadline, and that the
-	      ;; scheduled date is prior to the deadline.
-	      habitp (and (functionp 'org-is-habit-p)
-			  (org-is-habit-p))
 	      wdays (org-get-wdays s)
 	      dfrac (/ (* 1.0 (- wdays diff)) (max wdays 1))
 	      upcomingp (and todayp (> diff 0)))
@@ -4258,8 +4252,7 @@ the documentation of `org-diary'."
 	;; Past-due deadlines are only shown on the current date
 	(if (and (or (and (<= diff wdays)
 			  (and todayp (not org-agenda-only-exact-dates)))
-		     (= diff 0))
-		 (not habitp))
+		     (= diff 0)))
 	    (save-excursion
 	      (setq todo-state (org-get-todo-state))
 	      (setq donep (member todo-state org-done-keywords))
@@ -4402,7 +4395,8 @@ FRACTION is what fraction of the head-warning time has passed."
 	    (when txt
 	      (setq face
 		    (cond
-		     (pastschedp 'org-scheduled-previously)
+		     ((and (not habitp) pastschedp)
+		      'org-scheduled-previously)
 		     (todayp 'org-scheduled-today)
 		     (t 'org-scheduled)))
 	      (org-add-props txt props

+ 19 - 33
lisp/org-habit.el

@@ -123,7 +123,7 @@ relative to the current effective time."
   :type 'color)
 
 (defun org-habit-duration-to-days (ts)
-  (if (string-match "\\([0-9]+\\)\\([dwmy]\\)\\'" ts)
+  (if (string-match "\\([0-9]+\\)\\([dwmy]\\)" ts)
       ;; lead time is specified.
       (floor (* (string-to-number (match-string 1 ts))
 		(cdr (assoc (match-string 2 ts)
@@ -148,41 +148,27 @@ This list represents a \"habit\" for the rest of this module."
   (save-excursion
     (if pom (goto-char pom))
     (assert (org-is-habit-p (point)))
-    (let ((scheduled (org-get-scheduled-time (point)))
-	  (scheduled-repeat (org-get-repeat "SCHEDULED"))
-	  (deadline (org-get-deadline-time (point)))
-	  (deadline-repeat (org-get-repeat "DEADLINE")))
+    (let* ((scheduled (org-get-scheduled-time (point)))
+	   (scheduled-repeat (org-get-repeat "SCHEDULED"))
+	   (sr-days (org-habit-duration-to-days scheduled-repeat))
+	   (end (org-entry-end-position))
+	   closed-dates deadline dr-days)
       (unless scheduled
 	(error "Habit has no scheduled date"))
       (unless scheduled-repeat
 	(error "Habit has no scheduled repeat period"))
-      (unless (string-match "\\`\\.\\+[0-9]+" scheduled-repeat)
-	(error "Habit's scheduled repeat period does not match `.+[0-9]*'"))
-      (if (and deadline (not deadline-repeat))
-	  (error "Habit has a deadline, but no deadline repeat period"))
-      (if (and deadline
-	       (not (string-match "\\`\\.\\+[0-9]+" scheduled-repeat))) 
-	  (error "Habit's deadline repeat period does not match `.+[0-9]*'"))
-      (let ((sr-days (org-habit-duration-to-days scheduled-repeat))
-	    (dr-days (and deadline-repeat
-			  (org-habit-duration-to-days deadline-repeat))))
-	(when (and scheduled deadline)
-	  (cond
-	   ((time-less-p deadline scheduled)
-	    (error "Habit's deadline date is before the scheduled date"))
-	   ((< dr-days sr-days)
-	    (error "Habit's deadline repeat period is less than scheduled"))
-	   ((/= (- (time-to-days deadline)
-		   (time-to-days scheduled))
-		(- dr-days sr-days))
-	    (error "Habit's deadline and scheduled period lengths are off"))))
-	(let ((end (org-entry-end-position))
-	      closed-dates)
-	  (org-back-to-heading t)
-	  (while (re-search-forward "- State \"DONE\".*\\[\\([^]]+\\)\\]" end t)
-	    (push (org-time-string-to-time (match-string-no-properties 1))
-		  closed-dates))
-	  (list scheduled sr-days deadline dr-days closed-dates))))))
+      (when (string-match "/\\([0-9]+[dwmy]\\)" scheduled-repeat)
+	(setq dr-days (org-habit-duration-to-days
+		       (match-string-no-properties 1 scheduled-repeat)))
+	(if (<= dr-days sr-days)
+	    (error "Habit's deadline repeat period is less than or equal to scheduled"))
+	(setq deadline (time-add scheduled
+				 (days-to-time (- dr-days sr-days)))))
+      (org-back-to-heading t)
+      (while (re-search-forward "- State \"DONE\".*\\[\\([^]]+\\)\\]" end t)
+	(push (org-time-string-to-time (match-string-no-properties 1))
+	      closed-dates))
+      (list scheduled sr-days deadline dr-days closed-dates))))
 
 (defsubst org-habit-scheduled (habit)
   (nth 0 habit))
@@ -215,7 +201,7 @@ Habits are assigned colors on the following basis:
 	 (s-repeat (org-habit-scheduled-repeat habit))
 	 (scheduled-end (time-add scheduled (days-to-time s-repeat)))
 	 (d-repeat (org-habit-deadline-repeat habit))
-	 (deadline (if scheduled-time
+	 (deadline (if (and scheduled-time d-repeat)
 		       (time-add scheduled-time
 				 (days-to-time (- d-repeat s-repeat)))
 		     (org-habit-deadline habit))))

+ 3 - 3
lisp/org.el

@@ -475,7 +475,7 @@ An entry can be toggled between QUOTE and normal with
   :type 'string)
 
 (defconst org-repeat-re
-  "<[0-9]\\{4\\}-[0-9][0-9]-[0-9][0-9] [^>\n]*\\([.+]+?\\+[0-9]+[dwmy]\\)"
+  "<[0-9]\\{4\\}-[0-9][0-9]-[0-9][0-9] [^>\n]*?\\([.+]?\\+[0-9]+[dwmy]\\(/[0-9]+[dwmy]\\)?\\)"
   "Regular expression for specifying repeated events.
 After a match, group 1 contains the repeat expression.")
 
@@ -13052,7 +13052,7 @@ The command returns the inserted time stamp."
 	 t1 w1 with-hm tf time str w2 (off 0))
     (save-match-data
       (setq t1 (org-parse-time-string ts t))
-      (if (string-match "\\(-[0-9]+:[0-9]+\\)?\\( [.+]?\\+[0-9]+[dwmy]\\)?\\'" ts)
+      (if (string-match "\\(-[0-9]+:[0-9]+\\)?\\( [.+]?\\+[0-9]+[dwmy]\\(/[0-9]+[dwmy]\\)?\\)?\\'" ts)
 	  (setq off (- (match-end 0) (match-beginning 0)))))
     (setq end (- end off))
     (setq w1 (- end beg)
@@ -13565,7 +13565,7 @@ in the timestamp determines what will be changed."
 	    ts (match-string 0))
       (replace-match "")
       (if (string-match
-	   "\\(\\(-[012][0-9]:[0-5][0-9]\\)?\\( +[.+]?[-+][0-9]+[dwmy]\\)*\\)[]>]"
+	   "\\(\\(-[012][0-9]:[0-5][0-9]\\)?\\( +[.+]?[-+][0-9]+[dwmy]\\(/[0-9]+[dwmy]\\)?\\)*\\)[]>]"
 	   ts)
 	  (setq extra (match-string 1 ts)))
       (if (string-match "^.\\{10\\}.*?[0-9]+:[0-9][0-9]" ts)