summaryrefslogtreecommitdiff
path: root/lisp/org/org-clock.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/org/org-clock.el')
-rw-r--r--lisp/org/org-clock.el663
1 files changed, 356 insertions, 307 deletions
diff --git a/lisp/org/org-clock.el b/lisp/org/org-clock.el
index 9046661b266..df4ba62425b 100644
--- a/lisp/org/org-clock.el
+++ b/lisp/org/org-clock.el
@@ -35,12 +35,17 @@
(declare-function notifications-notify "notifications" (&rest params))
(declare-function org-element-property "org-element" (property element))
(declare-function org-element-type "org-element" (element))
+(declare-function org-link-display-format "ol" (s))
+(declare-function org-link-heading-search-string "ol" (&optional string))
+(declare-function org-link-make-string "ol" (link &optional description))
(declare-function org-table-goto-line "org-table" (n))
+(declare-function org-dynamic-block-define "org" (type func))
-(defvar org-frame-title-format-backup frame-title-format)
+(defvar org-frame-title-format-backup nil)
+(defvar org-state)
+(defvar org-link-bracket-re)
(defvar org-time-stamp-formats)
-
(defgroup org-clock nil
"Options concerning clocking working time in Org mode."
:tag "Org Clock"
@@ -156,7 +161,10 @@ state to switch it to."
(symbol :tag "Function")))
(defcustom org-clock-history-length 5
- "Number of clock tasks to remember in history."
+ "Number of clock tasks to remember in history.
+Clocking in using history works best if this is at most 35, in
+which case all digits and capital letters are used up by the
+*Clock Task Select* buffer."
:group 'org-clock
:type 'integer)
@@ -294,10 +302,12 @@ string as argument."
:stepskip0 nil
:fileskip0 nil
:tags nil
+ :match nil
:emphasize nil
:link nil
:narrow '40!
:indent t
+ :hidefiles nil
:formula nil
:timestamp nil
:level nil
@@ -328,11 +338,12 @@ For more information, see `org-clocktable-write-default'."
:version "24.1"
:type 'alist)
-(defcustom org-clock-clocktable-default-properties '(:maxlevel 2 :scope file)
+(defcustom org-clock-clocktable-default-properties '(:maxlevel 2)
"Default properties for new clocktables.
These will be inserted into the BEGIN line, to make it easy for users to
play with them."
:group 'org-clocktable
+ :package-version '(Org . "9.2")
:type 'plist)
(defcustom org-clock-idle-time nil
@@ -518,8 +529,7 @@ cannot be translated."
(cond ((functionp org-clock-heading-function)
(funcall org-clock-heading-function))
((org-before-first-heading-p) "???")
- (t (replace-regexp-in-string
- org-bracket-link-analytic-regexp "\\5"
+ (t (org-link-display-format
(org-no-properties (org-get-heading t t t t))))))
(defun org-clock-menu ()
@@ -533,7 +543,7 @@ cannot be translated."
(defun org-clock-history-push (&optional pos buffer)
"Push a marker to the clock history."
- (setq org-clock-history-length (max 1 (min 35 org-clock-history-length)))
+ (setq org-clock-history-length (max 1 org-clock-history-length))
(let ((m (move-marker (make-marker)
(or pos (point)) (org-base-buffer
(or buffer (current-buffer)))))
@@ -723,7 +733,8 @@ menu\nmouse-2 will jump to task"))
The time returned includes the time spent on this task in
previous clocking intervals."
(let ((currently-clocked-time
- (floor (time-convert (time-since org-clock-start-time) 'integer)
+ (floor (org-time-convert-to-integer
+ (org-time-since org-clock-start-time))
60)))
(+ currently-clocked-time (or org-clock-total-time 0))))
@@ -732,8 +743,8 @@ previous clocking intervals."
VALUE can be a number of minutes, or a string with format hh:mm or mm.
When the string starts with a + or a - sign, the current value of the effort
property will be changed by that amount. If the effort value is expressed
-as an `org-effort-durations' (e.g. \"3h\"), the modified value will be
-converted to a hh:mm duration.
+as an unit defined in `org-duration-units' (e.g. \"3h\"), the modified
+value will be converted to a hh:mm duration.
This command will update the \"Effort\" property of the currently
clocked item, and the value displayed in the mode line."
@@ -913,47 +924,52 @@ If necessary, clock-out of the currently active clock."
(defvar org-clock-resolving-clocks nil)
(defvar org-clock-resolving-clocks-due-to-idleness nil)
-(defun org-clock-resolve-clock (clock resolve-to clock-out-time
- &optional close-p restart-p fail-quietly)
- "Resolve `CLOCK' given the time `RESOLVE-TO', and the present.
-`CLOCK' is a cons cell of the form (MARKER START-TIME)."
- (let ((org-clock-resolving-clocks t))
- (cond
- ((null resolve-to)
- (org-clock-clock-cancel clock)
- (if (and restart-p (not org-clock-clocking-in))
- (org-clock-clock-in clock)))
-
- ((eq resolve-to 'now)
- (if restart-p
- (error "RESTART-P is not valid here"))
- (if (or close-p org-clock-clocking-in)
- (org-clock-clock-out clock fail-quietly)
- (unless (org-is-active-clock clock)
- (org-clock-clock-in clock t))))
-
- ((not (time-less-p resolve-to nil))
- (error "RESOLVE-TO must refer to a time in the past"))
-
- (t
- (if restart-p
- (error "RESTART-P is not valid here"))
- (org-clock-clock-out clock fail-quietly (or clock-out-time
- resolve-to))
- (unless org-clock-clocking-in
- (if close-p
- (setq org-clock-leftover-time (and (null clock-out-time)
- resolve-to))
- (org-clock-clock-in clock nil (and clock-out-time
- resolve-to))))))))
+(defun org-clock-resolve-clock
+ (clock resolve-to clock-out-time close restart fail-quietly)
+ "Resolve CLOCK given the time RESOLVE-TO, and the present.
+CLOCK is a cons cell of the form (MARKER START-TIME)."
+ (let ((org-clock-resolving-clocks t)
+ ;; If the clocked entry contained only a clock and possibly
+ ;; the associated drawer, and we either cancel it or clock it
+ ;; out, `org-clock-out-remove-zero-time-clocks' may clear all
+ ;; contents, and leave point on the /next/ headline. We store
+ ;; the current entry location to be able to get back here when
+ ;; we need to clock in again the previously clocked task.
+ (heading (org-with-point-at (car clock)
+ (org-back-to-heading t)
+ (point-marker))))
+ (pcase resolve-to
+ (`nil
+ (org-clock-clock-cancel clock)
+ (when (and restart (not org-clock-clocking-in))
+ (org-with-point-at heading (org-clock-in))))
+ (`now
+ (cond
+ (restart (error "RESTART is not valid here"))
+ ((or close org-clock-clocking-in)
+ (org-clock-clock-out clock fail-quietly))
+ ((org-is-active-clock clock) nil)
+ (t (org-clock-clock-in clock t))))
+ ((pred (org-time-less-p nil))
+ (error "RESOLVE-TO must refer to a time in the past"))
+ (_
+ (when restart (error "RESTART is not valid here"))
+ (org-clock-clock-out clock fail-quietly (or clock-out-time resolve-to))
+ (cond
+ (org-clock-clocking-in nil)
+ (close
+ (setq org-clock-leftover-time (and (null clock-out-time) resolve-to)))
+ (t
+ (org-with-point-at heading
+ (org-clock-in nil (and clock-out-time resolve-to)))))))))
(defun org-clock-jump-to-current-clock (&optional effective-clock)
- (interactive)
+ "When an Org clock is running, jump to it."
(let ((drawer (org-clock-into-drawer))
(clock (or effective-clock (cons org-clock-marker
org-clock-start-time))))
(unless (marker-buffer (car clock))
- (error "No clock is currently running"))
+ (user-error "No Org clock is currently running"))
(org-with-clock clock (org-clock-goto))
(with-current-buffer (marker-buffer (car clock))
(goto-char (car clock))
@@ -1033,7 +1049,7 @@ to be CLOCKED OUT."))))
nil 45)))
(and (not (memq char-pressed '(?i ?q))) char-pressed)))))
(default
- (floor (time-convert (time-since last-valid) 'integer)
+ (floor (org-time-convert-to-integer (org-time-since last-valid))
60))
(keep
(and (memq ch '(?k ?K))
@@ -1042,8 +1058,8 @@ to be CLOCKED OUT."))))
(and (memq ch '(?g ?G))
(read-number "Got back how many minutes ago? " default)))
(subtractp (memq ch '(?s ?S)))
- (barely-started-p (time-less-p
- (time-subtract last-valid (cdr clock))
+ (barely-started-p (org-time-less-p
+ (org-time-subtract last-valid (cdr clock))
45))
(start-over (and subtractp barely-started-p)))
(cond
@@ -1070,9 +1086,9 @@ to be CLOCKED OUT."))))
(and gotback (= gotback default)))
'now)
(keep
- (time-add last-valid (* 60 keep)))
+ (org-time-add last-valid (* 60 keep)))
(gotback
- (time-since (* 60 gotback)))
+ (org-time-since (* 60 gotback)))
(t
(error "Unexpected, please report this as a bug")))
(and gotback last-valid)
@@ -1102,9 +1118,9 @@ If `only-dangling-p' is non-nil, only ask to resolve dangling
(lambda (clock)
(format
"Dangling clock started %d mins ago"
- (floor (time-convert (time-since (cdr clock))
- 'integer)
- 60)))))
+ (floor (org-time-convert-to-integer
+ (org-time-since (cdr clock)))
+ 60)))))
(or last-valid
(cdr clock)))))))))))
@@ -1154,7 +1170,7 @@ so long."
org-clock-marker (marker-buffer org-clock-marker))
(let* ((org-clock-user-idle-seconds (org-user-idle-seconds))
(org-clock-user-idle-start
- (time-since org-clock-user-idle-seconds))
+ (org-time-since org-clock-user-idle-seconds))
(org-clock-resolving-clocks-due-to-idleness t))
(if (> org-clock-user-idle-seconds (* 60 org-clock-idle-time))
(org-clock-resolve
@@ -1216,33 +1232,27 @@ the default behavior."
(throw 'abort nil)))
(when (equal select '(4))
- (setq selected-task (org-clock-select-task "Clock-in on task: "))
- (if selected-task
- (setq selected-task (copy-marker selected-task))
- (error "Abort")))
+ (pcase (org-clock-select-task "Clock-in on task: ")
+ (`nil (error "Abort"))
+ (task (setq selected-task (copy-marker task)))))
(when (equal select '(16))
;; Mark as default clocking task
(org-clock-mark-default-task))
(when interrupting
- ;; We are interrupting the clocking of a different task.
- ;; Save a marker to this task, so that we can go back.
- ;; First check if we are trying to clock into the same task!
- (when (save-excursion
- (unless selected-task
- (org-back-to-heading t))
- (and (equal (marker-buffer org-clock-hd-marker)
- (if selected-task
- (marker-buffer selected-task)
- (current-buffer)))
- (= (marker-position org-clock-hd-marker)
- (if selected-task
- (marker-position selected-task)
- (point)))
- (equal org-clock-current-task (nth 4 (org-heading-components)))))
- (message "Clock continues in \"%s\"" org-clock-heading)
- (throw 'abort nil))
+ ;; We are interrupting the clocking of a different task. Save
+ ;; a marker to this task, so that we can go back. First check
+ ;; if we are trying to clock into the same task!
+ (when (or selected-task (derived-mode-p 'org-mode))
+ (org-with-point-at selected-task
+ (unless selected-task (org-back-to-heading t))
+ (when (and (eq (marker-buffer org-clock-hd-marker)
+ (org-base-buffer (current-buffer)))
+ (= (point) (marker-position org-clock-hd-marker))
+ (equal org-clock-current-task (org-get-heading t t t t)))
+ (message "Clock continues in %S" org-clock-heading)
+ (throw 'abort nil))))
(move-marker org-clock-interrupted-task
(marker-position org-clock-marker)
(marker-buffer org-clock-marker))
@@ -1267,7 +1277,7 @@ the default behavior."
(or interrupting (move-marker org-clock-interrupted-task nil))
(run-hooks 'org-clock-in-prepare-hook)
(org-clock-history-push)
- (setq org-clock-current-task (nth 4 (org-heading-components)))
+ (setq org-clock-current-task (org-get-heading t t t t))
(cond ((functionp org-clock-in-switch-to-state)
(let ((case-fold-search nil))
(looking-at org-complex-heading-regexp))
@@ -1310,7 +1320,7 @@ the default behavior."
(end-of-line 0)
(org-in-item-p)))
(beginning-of-line 1)
- (indent-line-to (- (org-get-indentation) 2)))
+ (indent-line-to (- (current-indentation) 2)))
(insert org-clock-string " ")
(setq org-clock-effort (org-entry-get (point) org-effort-property))
(setq org-clock-total-time (org-clock-sum-current-item
@@ -1321,11 +1331,10 @@ the default behavior."
(y-or-n-p
(format
"You stopped another clock %d mins ago; start this one from then? "
- (/ (time-convert
- (time-subtract
+ (/ (org-time-convert-to-integer
+ (org-time-subtract
(org-current-time org-clock-rounding-minutes t)
- leftover)
- 'integer)
+ leftover))
60)))
leftover)
start-time
@@ -1347,6 +1356,7 @@ the default behavior."
;; add to frame title
(when (or (eq org-clock-clocked-in-display 'frame-title)
(eq org-clock-clocked-in-display 'both))
+ (setq org-frame-title-format-backup frame-title-format)
(setq frame-title-format org-clock-frame-title-format))
(org-clock-update-mode-line)
(when org-clock-mode-line-timer
@@ -1501,9 +1511,9 @@ line and position cursor in that line."
(let ((beg (point)))
(insert ":" drawer ":\n:END:\n")
(org-indent-region beg (point))
- (goto-char beg)
- (org-flag-drawer t)
- (forward-line))))
+ (org-flag-region
+ (line-end-position -1) (1- (point)) t 'org-hide-drawer)
+ (forward-line -1))))
;; When a clock drawer needs to be created because of the
;; number of clock items or simply if it is missing, collect
;; all clocks in the section and wrap them within the drawer.
@@ -1527,7 +1537,7 @@ line and position cursor in that line."
(let ((end (point-marker)))
(goto-char beg)
(save-excursion (insert ":" drawer ":\n"))
- (org-flag-drawer t)
+ (org-flag-region (line-end-position) (1- end) t 'org-hide-drawer)
(org-indent-region (point) end)
(forward-line)
(unless org-log-states-order-reversed
@@ -1537,6 +1547,14 @@ line and position cursor in that line."
(org-log-states-order-reversed (goto-char (car (last positions))))
(t (goto-char (car positions))))))))
+(defun org-clock-restore-frame-title-format ()
+ "Restore `frame-title-format' from `org-frame-title-format-backup'.
+`frame-title-format' is restored if `org-frame-title-format-backup' is not nil
+and current `frame-title-format' is equal to `org-clock-frame-title-format'."
+ (when (and org-frame-title-format-backup
+ (equal frame-title-format org-clock-frame-title-format))
+ (setq frame-title-format org-frame-title-format-backup)))
+
;;;###autoload
(defun org-clock-out (&optional switch-to-state fail-quietly at-time)
"Stop the currently running clock.
@@ -1548,7 +1566,7 @@ to, overriding the existing value of `org-clock-out-switch-to-state'."
(when (not (org-clocking-p))
(setq global-mode-string
(delq 'org-mode-line-string global-mode-string))
- (setq frame-title-format org-frame-title-format-backup)
+ (org-clock-restore-frame-title-format)
(force-mode-line-update)
(if fail-quietly (throw 'exit t) (user-error "No active clock")))
(let ((org-clock-out-switch-to-state
@@ -1576,10 +1594,10 @@ to, overriding the existing value of `org-clock-out-switch-to-state'."
(delete-region (point) (point-at-eol))
(insert "--")
(setq te (org-insert-time-stamp (or at-time now) 'with-hm 'inactive))
- (setq s (time-convert (time-subtract
- (org-time-string-to-time te)
- (org-time-string-to-time ts))
- 'integer)
+ (setq s (org-time-convert-to-integer
+ (time-subtract
+ (org-time-string-to-time te)
+ (org-time-string-to-time ts)))
h (floor s 3600)
m (floor (mod s 3600) 60))
(insert " => " (format "%2d:%02d" h m))
@@ -1604,7 +1622,7 @@ to, overriding the existing value of `org-clock-out-switch-to-state'."
(setq org-clock-idle-timer nil))
(setq global-mode-string
(delq 'org-mode-line-string global-mode-string))
- (setq frame-title-format org-frame-title-format-backup)
+ (org-clock-restore-frame-title-format)
(when org-clock-out-switch-to-state
(save-excursion
(org-back-to-heading t)
@@ -1704,7 +1722,7 @@ Optional argument N tells to change by that many units."
(when (not (org-clocking-p))
(setq global-mode-string
(delq 'org-mode-line-string global-mode-string))
- (setq frame-title-format org-frame-title-format-backup)
+ (org-clock-restore-frame-title-format)
(force-mode-line-update)
(error "No active clock"))
(save-excursion ; Do not replace this with `with-current-buffer'.
@@ -1718,9 +1736,10 @@ Optional argument N tells to change by that many units."
(sit-for 2)))
(move-marker org-clock-marker nil)
(move-marker org-clock-hd-marker nil)
+ (setq org-clock-current-task nil)
(setq global-mode-string
(delq 'org-mode-line-string global-mode-string))
- (setq frame-title-format org-frame-title-format-backup)
+ (org-clock-restore-frame-title-format)
(force-mode-line-update)
(message "Clock canceled")
(run-hooks 'org-clock-cancel-hook))
@@ -1747,7 +1766,6 @@ With prefix arg SELECT, offer recently clocked tasks for selection."
(goto-char m)
(org-show-entry)
(org-back-to-heading t)
- (org-cycle-hide-drawers 'children)
(recenter org-clock-goto-before-context)
(org-reveal)
(if recent
@@ -1786,88 +1804,87 @@ HEADLINE-FILTER is a zero-arg function that, if specified, is called for
each headline in the time range with point at the headline. Headlines for
which HEADLINE-FILTER returns nil are excluded from the clock summation.
PROPNAME lets you set a custom text property instead of :org-clock-minutes."
- (org-with-silent-modifications
- (let* ((re (concat "^\\(\\*+\\)[ \t]\\|^[ \t]*"
- org-clock-string
- "[ \t]*\\(?:\\(\\[.*?\\]\\)-+\\(\\[.*?\\]\\)\\|=>[ \t]+\\([0-9]+\\):\\([0-9]+\\)\\)"))
- (lmax 30)
- (ltimes (make-vector lmax 0))
- (level 0)
- (tstart (cond ((stringp tstart) (org-time-string-to-seconds tstart))
- ((consp tstart) (float-time tstart))
- (t tstart)))
- (tend (cond ((stringp tend) (org-time-string-to-seconds tend))
- ((consp tend) (float-time tend))
- (t tend)))
- (t1 0)
- time)
- (remove-text-properties (point-min) (point-max)
- `(,(or propname :org-clock-minutes) t
- :org-clock-force-headline-inclusion t))
- (save-excursion
- (goto-char (point-max))
- (while (re-search-backward re nil t)
- (cond
- ((match-end 2)
- ;; Two time stamps.
- (let* ((ts (float-time
- (encode-time
- (save-match-data
- (org-parse-time-string (match-string 2))))))
- (te (float-time
- (encode-time
- (org-parse-time-string (match-string 3)))))
- (dt (- (if tend (min te tend) te)
- (if tstart (max ts tstart) ts))))
- (when (> dt 0) (cl-incf t1 (floor dt 60)))))
- ((match-end 4)
- ;; A naked time.
- (setq t1 (+ t1 (string-to-number (match-string 5))
- (* 60 (string-to-number (match-string 4))))))
- (t ;A headline
- ;; Add the currently clocking item time to the total.
- (when (and org-clock-report-include-clocking-task
- (eq (org-clocking-buffer) (current-buffer))
- (eq (marker-position org-clock-hd-marker) (point))
- tstart
- tend
- (>= (float-time org-clock-start-time) tstart)
- (<= (float-time org-clock-start-time) tend))
- (let ((time (floor (time-convert
- (time-since org-clock-start-time)
- 'integer)
- 60)))
- (setq t1 (+ t1 time))))
- (let* ((headline-forced
- (get-text-property (point)
- :org-clock-force-headline-inclusion))
- (headline-included
- (or (null headline-filter)
- (save-excursion
- (save-match-data (funcall headline-filter))))))
- (setq level (- (match-end 1) (match-beginning 1)))
- (when (>= level lmax)
- (setq ltimes (vconcat ltimes (make-vector lmax 0)) lmax (* 2 lmax)))
- (when (or (> t1 0) (> (aref ltimes level) 0))
- (when (or headline-included headline-forced)
- (if headline-included
- (cl-loop for l from 0 to level do
- (aset ltimes l (+ (aref ltimes l) t1))))
- (setq time (aref ltimes level))
- (goto-char (match-beginning 0))
- (put-text-property (point) (point-at-eol)
- (or propname :org-clock-minutes) time)
- (when headline-filter
- (save-excursion
- (save-match-data
- (while (org-up-heading-safe)
- (put-text-property
- (point) (line-end-position)
- :org-clock-force-headline-inclusion t))))))
- (setq t1 0)
- (cl-loop for l from level to (1- lmax) do
- (aset ltimes l 0)))))))
- (setq org-clock-file-total-minutes (aref ltimes 0))))))
+ (with-silent-modifications
+ (let* ((re (concat "^\\(\\*+\\)[ \t]\\|^[ \t]*"
+ org-clock-string
+ "[ \t]*\\(?:\\(\\[.*?\\]\\)-+\\(\\[.*?\\]\\)\\|=>[ \t]+\\([0-9]+\\):\\([0-9]+\\)\\)"))
+ (lmax 30)
+ (ltimes (make-vector lmax 0))
+ (level 0)
+ (tstart (cond ((stringp tstart) (org-time-string-to-seconds tstart))
+ ((consp tstart) (float-time tstart))
+ (t tstart)))
+ (tend (cond ((stringp tend) (org-time-string-to-seconds tend))
+ ((consp tend) (float-time tend))
+ (t tend)))
+ (t1 0)
+ time)
+ (remove-text-properties (point-min) (point-max)
+ `(,(or propname :org-clock-minutes) t
+ :org-clock-force-headline-inclusion t))
+ (save-excursion
+ (goto-char (point-max))
+ (while (re-search-backward re nil t)
+ (cond
+ ((match-end 2)
+ ;; Two time stamps.
+ (let* ((ts (float-time
+ (apply #'encode-time
+ (save-match-data
+ (org-parse-time-string (match-string 2))))))
+ (te (float-time
+ (apply #'encode-time
+ (org-parse-time-string (match-string 3)))))
+ (dt (- (if tend (min te tend) te)
+ (if tstart (max ts tstart) ts))))
+ (when (> dt 0) (cl-incf t1 (floor dt 60)))))
+ ((match-end 4)
+ ;; A naked time.
+ (setq t1 (+ t1 (string-to-number (match-string 5))
+ (* 60 (string-to-number (match-string 4))))))
+ (t ;A headline
+ ;; Add the currently clocking item time to the total.
+ (when (and org-clock-report-include-clocking-task
+ (eq (org-clocking-buffer) (current-buffer))
+ (eq (marker-position org-clock-hd-marker) (point))
+ tstart
+ tend
+ (>= (float-time org-clock-start-time) tstart)
+ (<= (float-time org-clock-start-time) tend))
+ (let ((time (floor (org-time-convert-to-integer
+ (org-time-since org-clock-start-time))
+ 60)))
+ (setq t1 (+ t1 time))))
+ (let* ((headline-forced
+ (get-text-property (point)
+ :org-clock-force-headline-inclusion))
+ (headline-included
+ (or (null headline-filter)
+ (save-excursion
+ (save-match-data (funcall headline-filter))))))
+ (setq level (- (match-end 1) (match-beginning 1)))
+ (when (>= level lmax)
+ (setq ltimes (vconcat ltimes (make-vector lmax 0)) lmax (* 2 lmax)))
+ (when (or (> t1 0) (> (aref ltimes level) 0))
+ (when (or headline-included headline-forced)
+ (if headline-included
+ (cl-loop for l from 0 to level do
+ (aset ltimes l (+ (aref ltimes l) t1))))
+ (setq time (aref ltimes level))
+ (goto-char (match-beginning 0))
+ (put-text-property (point) (point-at-eol)
+ (or propname :org-clock-minutes) time)
+ (when headline-filter
+ (save-excursion
+ (save-match-data
+ (while (org-up-heading-safe)
+ (put-text-property
+ (point) (line-end-position)
+ :org-clock-force-headline-inclusion t))))))
+ (setq t1 0)
+ (cl-loop for l from level to (1- lmax) do
+ (aset ltimes l 0)))))))
+ (setq org-clock-file-total-minutes (aref ltimes 0))))))
(defun org-clock-sum-current-item (&optional tstart)
"Return time, clocked on current item in total."
@@ -1939,29 +1956,28 @@ Use `\\[org-clock-remove-overlays]' to remove the subtree times."
(defvar-local org-clock-overlays nil)
(defun org-clock-put-overlay (time)
- "Put an overlays on the current line, displaying TIME.
-This creates a new overlay and stores it in `org-clock-overlays', so that it
-will be easy to remove."
- (let (ov tx)
- (beginning-of-line)
- (let ((case-fold-search nil))
- (when (looking-at org-complex-heading-regexp)
- (goto-char (match-beginning 4))))
- (setq ov (make-overlay (point) (point-at-eol))
- tx (concat (buffer-substring-no-properties (point) (match-end 4))
- (org-add-props
- (make-string
- (max 0 (- (- 60 (current-column))
- (- (match-end 4) (match-beginning 4))
- (length (org-get-at-bol 'line-prefix))))
- ?\·)
- '(face shadow))
- (org-add-props
- (format " %9s " (org-duration-from-minutes time))
- '(face org-clock-overlay))
- ""))
- (overlay-put ov 'display tx)
- (push ov org-clock-overlays)))
+ "Put an overlay on the headline at point, displaying TIME.
+Create a new overlay and store it in `org-clock-overlays', so
+that it will be easy to remove. This function assumes point is
+on a headline."
+ (org-match-line org-complex-heading-regexp)
+ (goto-char (match-beginning 4))
+ (let* ((headline (match-string 4))
+ (text (concat headline
+ (org-add-props
+ (make-string
+ (max (- (- 60 (current-column))
+ (org-string-width headline)
+ (length (org-get-at-bol 'line-prefix)))
+ 0)
+ ?\·)
+ '(face shadow))
+ (org-add-props
+ (format " %9s " (org-duration-from-minutes time))
+ '(face org-clock-overlay))))
+ (o (make-overlay (point) (line-end-position))))
+ (org-overlay-display o text)
+ (push o org-clock-overlays)))
;;;###autoload
(defun org-clock-remove-overlays (&optional _beg _end noremove)
@@ -1976,7 +1992,7 @@ If NOREMOVE is nil, remove this function from the
(remove-hook 'before-change-functions
'org-clock-remove-overlays 'local))))
-(defvar org-state) ;; dynamically scoped into this function
+;;;###autoload
(defun org-clock-out-if-current ()
"Clock out if the current entry contains the running clock.
This is used to stop the clock after a TODO entry is marked DONE,
@@ -1993,16 +2009,13 @@ and is only done if the variable `org-clock-out-when-done' is not nil."
(or (buffer-base-buffer (current-buffer))
(current-buffer)))
(< (point) org-clock-marker)
- (> (save-excursion (outline-next-heading) (point))
+ (> (org-with-wide-buffer (org-entry-end-position))
org-clock-marker))
;; Clock out, but don't accept a logging message for this.
(let ((org-log-note-clock-out nil)
(org-clock-out-switch-to-state nil))
(org-clock-out))))
-(add-hook 'org-after-todo-state-change-hook
- 'org-clock-out-if-current)
-
;;;###autoload
(defun org-clock-get-clocktable (&rest props)
"Get a formatted clocktable with parameters according to PROPS.
@@ -2054,6 +2067,8 @@ in the buffer and update it."
(start (goto-char start)))
(org-update-dblock))
+(org-dynamic-block-define "clocktable" #'org-clock-report)
+
(defun org-day-of-week (day month year)
"Return the day of the week as an integer."
(nth 6
@@ -2125,9 +2140,10 @@ time.
The return value is a list containing two internal times, one for
the beginning of the range and one for its end, like the ones
-returned by `current time' or `encode-time' and a string used to
+returned by `current-time' or `encode-time' and a string used to
display information. If AS-STRINGS is non-nil, the returned
-times will be formatted strings.
+times will be formatted strings. Note that the first element is
+always nil when KEY is `untilnow'.
If WSTART is non-nil, use this number to specify the starting day
of a week (monday is 1). If MSTART is non-nil, use this number
@@ -2201,13 +2217,17 @@ have priority."
(`lastq (setq key 'quarter shift -1))))
;; Prepare start and end times depending on KEY's type.
(pcase key
- ((or `day `today) (setq m 0 h 0 h1 24 d (+ d shift)))
+ ((or `day `today) (setq m 0
+ h org-extend-today-until
+ h1 (+ 24 org-extend-today-until)
+ d (+ d shift)))
((or `week `thisweek)
(let* ((ws (or wstart 1))
(diff (+ (* -7 shift) (if (= dow 0) (- 7 ws) (- dow ws)))))
- (setq m 0 h 0 d (- d diff) d1 (+ 7 d))))
+ (setq m 0 h org-extend-today-until d (- d diff) d1 (+ 7 d))))
((or `month `thismonth)
- (setq h 0 m 0 d (or mstart 1) month (+ month shift) month1 (1+ month)))
+ (setq h org-extend-today-until m 0 d (or mstart 1)
+ month (+ month shift) month1 (1+ month)))
((or `quarter `thisq)
;; Compute if this shift remains in this year. If not, compute
;; how many years and quarters we have to shift (via floor*) and
@@ -2225,32 +2245,22 @@ have priority."
(setq shiftedy (- y (+ 1 (nth 0 tmp)))
shiftedm (- 13 (* 3 (nth 1 tmp)))
shiftedq (- 5 (nth 1 tmp)))))
- (setq m 0 h 0 d 1 month shiftedm month1 (+ 3 shiftedm) y shiftedy))
+ (setq m 0 h org-extend-today-until d 1
+ month shiftedm month1 (+ 3 shiftedm) y shiftedy))
((> (+ q shift) 0) ; Shift is within this year.
(setq shiftedq (+ q shift))
(setq shiftedy y)
(let ((qshift (* 3 (1- (+ q shift)))))
- (setq m 0 h 0 d 1 month (+ 1 qshift) month1 (+ 4 qshift))))))
+ (setq m 0 h org-extend-today-until d 1
+ month (+ 1 qshift) month1 (+ 4 qshift))))))
((or `year `thisyear)
- (setq m 0 h 0 d 1 month 1 y (+ y shift) y1 (1+ y)))
+ (setq m 0 h org-extend-today-until d 1 month 1 y (+ y shift) y1 (1+ y)))
((or `interactive `untilnow)) ; Special cases, ignore them.
(_ (user-error "No such time block %s" key)))
;; Format start and end times according to AS-STRINGS.
(let* ((start (pcase key
(`interactive (org-read-date nil t nil "Range start? "))
- ;; In theory, all clocks started after the dawn of
- ;; humanity. However, the platform's clock
- ;; support might not go back that far. Choose the
- ;; POSIX timestamp -2**41 (approximately 68,000
- ;; BCE) if that works, otherwise -2**31 (1901) if
- ;; that works, otherwise 0 (1970). Going back
- ;; billions of years would loop forever on Mac OS
- ;; X 10.6 with Emacs 26 and earlier (Bug#27736).
- (`untilnow
- (let ((old 0))
- (dolist (older '((-32768 0) (-33554432 0)) old)
- (when (ignore-errors (decode-time older))
- (setq old older)))))
+ (`untilnow nil)
(_ (encode-time 0 m h d month y))))
(end (pcase key
(`interactive (org-read-date nil t nil "Range end? "))
@@ -2274,7 +2284,7 @@ have priority."
(`untilnow "now"))))
(if (not as-strings) (list start end text)
(let ((f (cdr org-time-stamp-formats)))
- (list (format-time-string f start)
+ (list (and start (format-time-string f start))
(format-time-string f end)
text))))))
@@ -2382,15 +2392,22 @@ the currently selected interval size."
(`file-with-archives
(and buffer-file-name
(org-add-archive-files (list buffer-file-name))))
+ ((or `nil `file `subtree `tree
+ (and (pred symbolp)
+ (guard (string-match "\\`tree\\([0-9]+\\)\\'"
+ (symbol-name scope)))))
+ (or (buffer-file-name (buffer-base-buffer))
+ (current-buffer)))
((pred functionp) (funcall scope))
((pred consp) scope)
- (_ (or (buffer-file-name) (current-buffer)))))
+ (_ (user-error "Unknown scope: %S" scope))))
(block (plist-get params :block))
(ts (plist-get params :tstart))
(te (plist-get params :tend))
(ws (plist-get params :wstart))
(ms (plist-get params :mstart))
(step (plist-get params :step))
+ (hide-files (plist-get params :hidefiles))
(formatter (or (plist-get params :formatter)
org-clock-clocktable-formatter
'org-clocktable-write-default))
@@ -2445,7 +2462,9 @@ the currently selected interval size."
;; Even though `file-with-archives' can consist of
;; multiple files, we consider this is one extended file
;; instead.
- (and (consp files) (not (eq scope 'file-with-archives)))))
+ (and (not hide-files)
+ (consp files)
+ (not (eq scope 'file-with-archives)))))
(funcall formatter
origin
@@ -2475,6 +2494,7 @@ from the dynamic block definition."
(narrow (or (plist-get params :narrow) (and compact? '40!)))
(level? (and (not compact?) (plist-get params :level)))
(timestamp (plist-get params :timestamp))
+ (tags (plist-get params :tags))
(properties (plist-get params :properties))
(time-columns
(if (or compact? (< maxlevel 2)) 1
@@ -2535,6 +2555,7 @@ from the dynamic block definition."
(if multifile "|" "") ;file column, maybe
(if level? "|" "") ;level column, maybe
(if timestamp "|" "") ;timestamp column, maybe
+ (if tags "|" "") ;tags columns, maybe
(if properties ;properties columns, maybe
(make-string (length properties) ?|)
"")
@@ -2552,6 +2573,8 @@ from the dynamic block definition."
(if timestamp ;timestamp column, maybe
(concat (org-clock--translate "Timestamp" lang) "|")
"")
+ (if tags "Tags |" "") ;tags columns, maybe
+
(if properties ;properties columns, maybe
(concat (mapconcat #'identity properties "|") "|")
"")
@@ -2566,8 +2589,9 @@ from the dynamic block definition."
"|" ;table line starter
(if multifile (format "| %s " (org-clock--translate "ALL" lang)) "")
;file column, maybe
- (if level? "|" "") ;level column, maybe
+ (if level? "|" "") ;level column, maybe
(if timestamp "|" "") ;timestamp column, maybe
+ (if tags "|" "") ;timestamp column, maybe
(make-string (length properties) ?|) ;properties columns, maybe
(concat (format org-clock-total-time-cell-format
(org-clock--translate "Total time" lang))
@@ -2592,13 +2616,14 @@ from the dynamic block definition."
(when multifile
;; Summarize the time collected from this file.
(insert-before-markers
- (format (concat "| %s %s | %s%s"
+ (format (concat "| %s %s | %s%s%s"
(format org-clock-file-time-cell-format
(org-clock--translate "File time" lang))
" | *%s*|\n")
(file-name-nondirectory file-name)
- (if level? "| " "") ;level column, maybe
+ (if level? "| " "") ;level column, maybe
(if timestamp "| " "") ;timestamp column, maybe
+ (if tags "| " "") ;tags column, maybe
(if properties ;properties columns, maybe
(make-string (length properties) ?|)
"")
@@ -2606,16 +2631,16 @@ from the dynamic block definition."
;; Get the list of node entries and iterate over it
(when (> maxlevel 0)
- (pcase-dolist (`(,level ,headline ,ts ,time ,props) entries)
+ (pcase-dolist (`(,level ,headline ,tgs ,ts ,time ,props) entries)
(when narrow-cut-p
(setq headline
(if (and (string-match
- (format "\\`%s\\'" org-bracket-link-regexp)
+ (format "\\`%s\\'" org-link-bracket-re)
headline)
- (match-end 3))
+ (match-end 2))
(format "[[%s][%s]]"
(match-string 1 headline)
- (org-shorten-string (match-string 3 headline)
+ (org-shorten-string (match-string 2 headline)
narrow))
(org-shorten-string headline narrow))))
(cl-flet ((format-field (f) (format (cond ((not emph) "%s |")
@@ -2628,6 +2653,7 @@ from the dynamic block definition."
(if multifile "|" "") ;free space for file name column?
(if level? (format "%d|" level) "") ;level, maybe
(if timestamp (concat ts "|") "") ;timestamp, maybe
+ (if tags (concat (mapconcat #'identity tgs ", ") "|") "") ;tags, maybe
(if properties ;properties columns, maybe
(concat (mapconcat (lambda (p) (or (cdr (assoc p props)) ""))
properties
@@ -2683,69 +2709,87 @@ LEVEL is an integer. Indent by two spaces per level above 1."
(concat "\\_" (make-string (* 2 (1- level)) ?\s))))
(defun org-clocktable-steps (params)
- "Step through the range to make a number of clock tables."
- (let* ((ts (plist-get params :tstart))
- (te (plist-get params :tend))
- (ws (plist-get params :wstart))
- (ms (plist-get params :mstart))
- (step0 (plist-get params :step))
- (step (cdr (assq step0 '((day . 86400) (week . 604800)))))
- (stepskip0 (plist-get params :stepskip0))
- (block (plist-get params :block))
- cc tsb)
- (when block
- (setq cc (org-clock-special-range block nil t ws ms)
- ts (car cc)
- te (nth 1 cc)))
- (cond
- ((numberp ts)
- ;; If ts is a number, it's an absolute day number from
- ;; org-agenda.
- (pcase-let ((`(,month ,day ,year) (calendar-gregorian-from-absolute ts)))
- (setq ts (float-time (encode-time 0 0 0 day month year)))))
- (ts
- (setq ts (float-time (org-time-string-to-time ts)))))
- (cond
- ((numberp te)
- ;; Likewise for te.
- (pcase-let ((`(,month ,day ,year) (calendar-gregorian-from-absolute te)))
- (setq te (float-time (encode-time 0 0 0 day month year)))))
- (te
- (setq te (float-time (org-time-string-to-time te)))))
- (setq tsb
- (if (eq step0 'week)
- (let ((dow (nth 6 (decode-time ts))))
- (if (<= dow ws) ts
- (- ts (* 86400 (- dow ws)))))
- ts))
- (while (< tsb te)
+ "Create one or more clock tables, according to PARAMS.
+Step through the range specifications in plist PARAMS to make
+a number of clock tables."
+ (let* ((ignore-empty-tables (plist-get params :stepskip0))
+ (step (plist-get params :step))
+ (step-header
+ (pcase step
+ (`day "Daily report: ")
+ (`week "Weekly report starting on: ")
+ (`month "Monthly report starting on: ")
+ (`year "Annual report starting on: ")
+ (_ (user-error "Unknown `:step' specification: %S" step))))
+ (week-start (or (plist-get params :wstart) 1))
+ (month-start (or (plist-get params :mstart) 1))
+ (range
+ (pcase (plist-get params :block)
+ (`nil nil)
+ (range
+ (org-clock-special-range range nil t week-start month-start))))
+ ;; For both START and END, any number is an absolute day
+ ;; number from Agenda. Otherwise, consider value to be an Org
+ ;; timestamp string. The `:block' property has precedence
+ ;; over `:tstart' and `:tend'.
+ (start
+ (pcase (if range (car range) (plist-get params :tstart))
+ ((and (pred numberp) n)
+ (pcase-let ((`(,m ,d ,y) (calendar-gregorian-from-absolute n)))
+ (apply #'encode-time (list 0 0 org-extend-today-until d m y))))
+ (timestamp
+ (seconds-to-time
+ (org-matcher-time (or timestamp
+ ;; The year Org was born.
+ "<2003-01-01 Thu 00:00>"))))))
+ (end
+ (pcase (if range (nth 1 range) (plist-get params :tend))
+ ((and (pred numberp) n)
+ (pcase-let ((`(,m ,d ,y) (calendar-gregorian-from-absolute n)))
+ (apply #'encode-time (list 0 0 org-extend-today-until d m y))))
+ (timestamp (seconds-to-time (org-matcher-time timestamp))))))
+ (while (time-less-p start end)
(unless (bolp) (insert "\n"))
- (let ((start-time (max tsb ts)))
- (cl-incf tsb (let ((dow (nth 6 (decode-time tsb))))
- (if (or (eq step0 'day)
- (= dow ws))
- step
- (* 86400 (- ws dow)))))
- (insert "\n"
- (if (eq step0 'day) "Daily report: "
- "Weekly report starting on: ")
- (format-time-string (org-time-stamp-format nil t) start-time)
- "\n")
- (let ((table-begin (line-beginning-position 0))
- (step-time
- (org-dblock-write:clocktable
- (org-combine-plists
- params
- (list
- :header "" :step nil :block nil
- :tstart (format-time-string (org-time-stamp-format t t)
- start-time)
- :tend (format-time-string (org-time-stamp-format t t)
- (min te tsb)))))))
- (re-search-forward "^[ \t]*#\\+END:")
- (when (and stepskip0 (equal step-time 0))
- ;; Remove the empty table
- (delete-region (line-beginning-position) table-begin))))
+ ;; Insert header before each clock table.
+ (insert "\n"
+ step-header
+ (format-time-string (org-time-stamp-format nil t) start)
+ "\n")
+ ;; Compute NEXT, which is the end of the current clock table,
+ ;; according to step.
+ (let* ((next
+ (apply #'encode-time
+ (pcase-let
+ ((`(,_ ,_ ,_ ,d ,m ,y ,dow . ,_) (decode-time start)))
+ (pcase step
+ (`day (list 0 0 org-extend-today-until (1+ d) m y))
+ (`week
+ (let ((offset (if (= dow week-start) 7
+ (mod (- week-start dow) 7))))
+ (list 0 0 org-extend-today-until (+ d offset) m y)))
+ (`month (list 0 0 0 month-start (1+ m) y))
+ (`year (list 0 0 org-extend-today-until 1 1 (1+ y)))))))
+ (table-begin (line-beginning-position 0))
+ (step-time
+ ;; Write clock table between START and NEXT.
+ (org-dblock-write:clocktable
+ (org-combine-plists
+ params (list :header ""
+ :step nil
+ :block nil
+ :tstart (format-time-string
+ (org-time-stamp-format t t)
+ start)
+ :tend (format-time-string
+ (org-time-stamp-format t t)
+ ;; Never include clocks past END.
+ (if (time-less-p end next) end next)))))))
+ (let ((case-fold-search t)) (re-search-forward "^[ \t]*#\\+END:"))
+ ;; Remove the table if it is empty and `:stepskip0' is
+ ;; non-nil.
+ (when (and ignore-empty-tables (equal step-time 0))
+ (delete-region (line-beginning-position) table-begin))
+ (setq start next))
(end-of-line 0))))
(defun org-clock-get-table-data (file params)
@@ -2758,13 +2802,14 @@ file time (in minutes) as 1st and 2nd elements. The third element
of this list will be a list of headline entries. Each entry has the
following structure:
- (LEVEL HEADLINE TIMESTAMP TIME PROPERTIES)
+ (LEVEL HEADLINE TAGS TIMESTAMP TIME PROPERTIES)
LEVEL: The level of the headline, as an integer. This will be
the reduced level, so 1,2,3,... even if only odd levels
are being used.
HEADLINE: The text of the headline. Depending on PARAMS, this may
already be formatted like a link.
+TAGS: The list of tags of the headline.
TIMESTAMP: If PARAMS require it, this will be a time stamp found in the
entry, any of SCHEDULED, DEADLINE, NORMAL, or first inactive,
in this sequence.
@@ -2783,9 +2828,10 @@ PROPERTIES: The list properties specified in the `:properties' parameter
(block (plist-get params :block))
(link (plist-get params :link))
(tags (plist-get params :tags))
+ (match (plist-get params :match))
(properties (plist-get params :properties))
(inherit-property-p (plist-get params :inherit-props))
- (matcher (and tags (cdr (org-make-tags-matcher tags))))
+ (matcher (and match (cdr (org-make-tags-matcher match))))
cc st p tbl)
(setq org-clock-file-total-minutes nil)
@@ -2806,10 +2852,11 @@ PROPERTIES: The list properties specified in the `:properties' parameter
(org-clock-sum ts te
(when matcher
`(lambda ()
- (let* ((tags-list (org-get-tags-at))
+ (let* ((todo (org-get-todo-state))
+ (tags-list (org-get-tags))
(org-scanner-tags tags-list)
(org-trust-scanner-tags t))
- (funcall ,matcher nil tags-list nil)))))
+ (funcall ,matcher todo tags-list nil)))))
(goto-char (point-min))
(setq st t)
(while (or (and (bobp) (prog1 st (setq st nil))
@@ -2826,8 +2873,8 @@ PROPERTIES: The list properties specified in the `:properties' parameter
(hdl
(if (not link) headline
(let ((search
- (org-make-org-heading-search-string headline)))
- (org-make-link-string
+ (org-link-heading-search-string headline)))
+ (org-link-make-string
(if (not (buffer-file-name)) search
(format "file:%s::%s" (buffer-file-name) search))
;; Prune statistics cookies. Replace
@@ -2838,6 +2885,7 @@ PROPERTIES: The list properties specified in the `:properties' parameter
(replace-regexp-in-string
"\\[[0-9]+%\\]\\|\\[[0-9]+/[0-9]+\\]" ""
headline)))))))
+ (tgs (and tags (org-get-tags)))
(tsp
(and timestamp
(cl-some (lambda (p) (org-entry-get (point) p))
@@ -2852,7 +2900,7 @@ PROPERTIES: The list properties specified in the `:properties' parameter
(point) p inherit-property-p)))
(and v (cons p v))))
properties)))))
- (push (list level hdl tsp time props) tbl)))))))
+ (push (list level hdl tgs tsp time props) tbl)))))))
(list file org-clock-file-total-minutes (nreverse tbl)))))
;; Saving and loading the clock
@@ -2889,9 +2937,10 @@ Otherwise, return nil."
(end-of-line 1)
(setq ts (match-string 1)
te (match-string 3))
- (setq s (float-time
- (time-subtract (org-time-string-to-time te)
- (org-time-string-to-time ts)))
+ (setq s (- (float-time
+ (apply #'encode-time (org-parse-time-string te)))
+ (float-time
+ (apply #'encode-time (org-parse-time-string ts))))
neg (< s 0)
s (abs s)
h (floor (/ s 3600))