Browse Source

Implement a basic history system for recently clocked tasks.

Recently clocked tasks are now remembered as well as a default
task and an interrupted task.

There is no automatic clock-in yet.

Also, a number of bug fixes have sneaked into this patch,
sorry for the mess.
Carsten Dominik 17 years ago
parent
commit
add34418c8
3 changed files with 158 additions and 33 deletions
  1. 15 0
      ORGWEBPAGE/Changes.org
  2. 8 3
      doc/org.texi
  3. 135 30
      lisp/org-clock.el

+ 15 - 0
ORGWEBPAGE/Changes.org

@@ -8,6 +8,21 @@
 #+LINK_UP: index.html
 #+LINK_HOME: http://orgmode.org
 
+* Version 6.02a
+** Details
+*** Clock task history
+    Org now remembers the last 5 tasks that you clocked into, to
+    make it easier to clock back into a task after interrupting
+    it for another task.
+    - `C-u C-u C-c C-x C-i' (or `C-u C-u I' from the agenda) wil
+      clock into that task and mark it as current default task.
+    - `C-u C-c C-x C-i' (or `C-u I' from the agenda) will offer a
+      list of recently clocked tasks, including the default task,
+      for selection. `d' selects the default task, `i' selects
+      the task that was interrupted by the task that is currently
+      being clocked. `1',... selects a recent task.  When you
+      select a task, you will be clocked into it.
+
 * Version 6.02
 
 ** Overview

+ 8 - 3
doc/org.texi

@@ -4493,7 +4493,11 @@ Start the clock on the current item (clock-in).  This inserts the CLOCK
 keyword together with a timestamp.  If this is not the first clocking of
 this item, the multiple CLOCK lines will be wrapped into a
 @code{:CLOCK:} drawer (see also the variable
-@code{org-clock-into-drawer}).
+@code{org-clock-into-drawer}).  When called with a @kbd{C-u} prefix argument,
+select the task from a list of recently clocked tasks.  With two @kbd{C-u
+C-u} prefixes, clock into the task at point and mark it as the default task.
+The default task will always be available when selecting a clocking task,
+with letter @kbd{d}.
 @kindex C-c C-x C-o
 @item C-c C-x C-o
 Stop the clock (clock-out).  The inserts another timestamp at the same
@@ -4518,8 +4522,9 @@ Cancel the current clock.  This is useful if a clock was started by
 mistake, or if you ended up working on something else.
 @kindex C-c C-x C-j
 @item C-c C-x C-j
-Jump to the entry that contains the currently running clock, an another
-window.
+Jump to the entry that contains the currently running clock.  With a
+@kbd{C-u} prefix arg, select the target task from a list of recently clocked
+tasks.
 @kindex C-c C-x C-d
 @item C-c C-x C-d
 Display time summaries for each subtree in the current buffer.  This

+ 135 - 30
lisp/org-clock.el

@@ -93,6 +93,82 @@ The function is called with point at the beginning of the headline."
 (defvar org-clock-heading "")
 (defvar org-clock-start-time "")
 
+(defvar org-clock-history
+  (list (make-marker) (make-marker) (make-marker) (make-marker) (make-marker))
+  "Marker pointing to the previous task teking clock time.
+This is used to find back to the previous task after interrupting work.
+When clocking into a task and the clock is currently running, this marker
+is moved to the position of the currently running task and continues
+to point there even after the task is clocked out.")
+
+(defun org-clock-history-push (&optional pos buffer)
+  (let ((m (org-last org-clock-history)))
+    (move-marker m (or pos (point)) buffer)
+    (setq org-clock-history
+	  (reverse (cdr (reverse org-clock-history))))
+    (while (member m org-clock-history)
+      (move-marker (car (member m org-clock-history)) nil))
+    (setq org-clock-history (cons m org-clock-history))))
+
+(defun org-clock-select-task (&optional prompt)
+  "Select a task that recently was associated with clocking."
+  (interactive)
+  (let (sel-list rpl file task (i 0))
+    (save-window-excursion
+      (org-switch-to-buffer-other-window
+       (get-buffer-create "*Clock Task Select*"))
+      (erase-buffer)
+      (when (marker-buffer org-clock-default-task)
+	(insert (org-add-props "Default Task\n" nil 'face 'bold))
+	(setq s (org-clock-insert-selection-line ?d org-clock-default-task))
+	(push s sel-list))
+      (when (marker-buffer org-clock-interrupted-task)
+	(insert (org-add-props "Interrupted Task\n" nil 'face 'bold))
+	(setq s (org-clock-insert-selection-line ?i org-clock-interrupted-task))
+	(push s sel-list))
+      (insert (org-add-props "Recent Tasks\n" nil 'face 'bold))
+      (mapc
+       (lambda (m)
+	 (when (marker-buffer m)
+	   (setq i (1+ i)
+		 s (org-clock-insert-selection-line
+		    (string-to-char (number-to-string i)) m))
+	   (push s sel-list)))
+       org-clock-history)
+      (shrink-window-if-larger-than-buffer)
+      (message (or prompt "Select task for clocking:"))
+      (setq rpl (read-char-exclusive))
+      (cond
+       ((eq rpl ?q) nil)
+       ((eq rpl ?x) nil)
+       ((assoc rpl sel-list) (cdr (assoc rpl sel-list)))
+       (t (error "Invalid task choice %c" rpl))))))
+
+(defun org-clock-insert-selection-line (i marker)
+  (when (marker-buffer marker)
+    (let (file cat task)
+      (with-current-buffer (marker-buffer marker)
+	(save-excursion
+	  (goto-char marker)
+	  (setq file (buffer-file-name (marker-buffer marker))
+		cat (or (org-get-category)
+			(progn (org-refresh-category-properties)
+			       (org-get-category)))
+		task (org-get-heading 'notags))))
+      (when (and cat task)
+	(insert (format "[%c] %-15s %s\n" i cat task))
+	(cons i marker)))))
+  
+(defvar org-clock-default-task (make-marker)
+  "Marker pointing to the default task that should clock time.
+The clock can be made to switch to this task after clocking out
+of a different task.")
+
+(defvar org-clock-interrupted-task (make-marker)
+  "Marker pointing to the default task that should clock time.
+The clock can be made to switch to this task after clocking out
+of a different task.")
+
 (defun org-update-mode-line ()
   (let* ((delta (- (time-to-seconds (current-time))
                    (time-to-seconds org-clock-start-time)))
@@ -106,14 +182,39 @@ The function is called with point at the beginning of the headline."
 (defvar org-clock-mode-line-entry nil
   "Information for the modeline about the running clock.")
 
-(defun org-clock-in ()
+(defun org-clock-in (&optional select)
   "Start the clock on the current item.
-If necessary, clock-out of the currently active clock."
-  (interactive)
-  (org-clock-out t)
-  (let (ts)
+If necessary, clock-out of the currently active clock.
+With prefix arg SELECT, offer a list of recently clocked tasks to
+clock into.  When SELECT is `C-u C-u', clock into the current task and mark
+is as the default task, a special task that will always be offered in
+the clocking selection, associated with the letter `d'."
+  (interactive "P")
+  (let (ts selected-task)
+    ;; Are we interrupting the clocking of a differnt task?
+    (if (marker-buffer org-clock-marker)
+	(progn
+	  (move-marker org-clock-interrupted-task
+		       (marker-position org-clock-marker)
+		       (marker-buffer org-clock-marker))
+	  (let ((org-clock-inhibit-clock-restart t))
+	    (org-clock-out t)))
+      (move-marker org-clock-interrupted-task nil))
+    
+    (cond
+     ((equal select '(16))
+      (save-excursion
+	(org-back-to-heading t)
+	(move-marker org-clock-default-task (point))))
+     ((equal select '(4))
+      (setq selected-task (org-clock-select-task "Clock-in on task: "))))
+    
     (save-excursion
       (org-back-to-heading t)
+      (when (and selected-task (marker-buffer selected-task))
+	(set-buffer (marker-buffer selected-task))
+	(goto-char selected-task))
+      (org-clock-history-push)
       (when (and org-clock-in-switch-to-state
 		 (not (looking-at (concat outline-regexp "[ \t]*"
 					  org-clock-in-switch-to-state
@@ -127,7 +228,7 @@ If necessary, clock-out of the currently active clock."
 	  (setq org-clock-heading "???")))
       (setq org-clock-heading (propertize org-clock-heading 'face nil))
       (org-clock-find-position)
-
+      
       (insert "\n") (backward-char 1)
       (indent-relative)
       (insert org-clock-string " ")
@@ -258,19 +359,23 @@ If there is no running clock, throw an error, unless FAIL-QUIETLY is set."
   (force-mode-line-update)
   (message "Clock canceled"))
 
-(defun org-clock-goto (&optional delete-windows)
-  "Go to the currently clocked-in entry."
+(defun org-clock-goto (&optional select)
+  "Go to the currently clocked-in entry.
+With prefix arg SELECT, offer recently clocked tasks."
   (interactive "P")
-  (if (not (marker-buffer org-clock-marker))
-      (error "No active clock"))
-  (switch-to-buffer-other-window
-   (marker-buffer org-clock-marker))
-  (if delete-windows (delete-other-windows))
-  (goto-char org-clock-marker)
-  (org-show-entry)
-  (org-back-to-heading)
-  (org-cycle-hide-drawers 'children)
-  (recenter))
+  (let ((m (if select
+	       (org-clock-select-task "Select task to go to: ")
+	     org-clock-marker)))
+    (if (not (marker-buffer m))
+	(if select
+	    (error "No task selected")
+	  (error "No active clock")))
+    (switch-to-buffer (marker-buffer m))
+    (goto-char m)
+    (org-show-entry)
+    (org-back-to-heading)
+    (org-cycle-hide-drawers 'children)
+    (recenter)))
 
 (defvar org-clock-file-total-minutes nil
   "Holds the file total time in minutes, after a call to `org-clock-sum'.")
@@ -666,9 +771,9 @@ the currently selected interval size."
 		 (scope 'agenda)
 		 (p1 (copy-sequence params))
 		 file)
-	    (plist-put p1 :tostring t)
-	    (plist-put p1 :multifile t)
-	    (plist-put p1 :scope 'file)
+	    (setq p1 (plist-put p1 :tostring t))
+	    (setq p1 (plist-put p1 :multifile t))
+	    (setq p1 (plist-put p1 :scope 'file))
 	    (org-prepare-agenda-buffers files)
 	    (while (setq file (pop files))
 	      (with-current-buffer (find-buffer-visiting file)
@@ -767,17 +872,17 @@ the currently selected interval size."
 		     (apply 'encode-time (org-parse-time-string ts)))))
     (if te (setq te (time-to-seconds
 		     (apply 'encode-time (org-parse-time-string te)))))
-    (plist-put p1 :header "")
-    (plist-put p1 :step nil)
-    (plist-put p1 :block nil)
+    (setq p1 (plist-put p1 :header ""))
+    (setq p1 (plist-put p1 :step nil))
+    (setq p1 (plist-put p1 :block nil))
     (while (< ts te)
       (or (bolp) (insert "\n"))
-      (plist-put p1 :tstart (format-time-string
-			     (car org-time-stamp-formats)
-			     (seconds-to-time ts)))
-      (plist-put p1 :tend (format-time-string
-			   (car org-time-stamp-formats)
-			   (seconds-to-time (setq ts (+ ts step)))))
+      (setq p1 (plist-put p1 :tstart (format-time-string
+				      (car org-time-stamp-formats)
+				      (seconds-to-time ts))))
+      (setq p1 (plist-put p1 :tend (format-time-string
+				    (car org-time-stamp-formats)
+				    (seconds-to-time (setq ts (+ ts step))))))
       (insert "\n" (if (eq step0 'day) "Daily report: " "Weekly report starting on: ")
 	      (plist-get p1 :tstart) "\n")
       (org-dblock-write:clocktable p1)