|
@@ -1096,6 +1096,49 @@ the agenda to display all available LOG items temporarily."
|
|
|
:group 'org-agenda-daily/weekly
|
|
|
:type '(set :greedy t (const closed) (const clock) (const state)))
|
|
|
|
|
|
+(defcustom org-agenda-clock-consistency-checks
|
|
|
+ '(:max-duration "10:00" :min-duration 0 :max-gap "0:05"
|
|
|
+ :gap-ok-around ("4:00")
|
|
|
+ :default-face ((:background "DarkRed") (:foreground "white"))
|
|
|
+ :overlap-face nil :gap-face nil :no-end-time-face nil
|
|
|
+ :long-face nil :short-face nil)
|
|
|
+ "This is a property list, with the following keys:
|
|
|
+
|
|
|
+:max-duration Mark clocking chunks that are longer than this time.
|
|
|
+ This is a time string like \"HH:MM\", or the number
|
|
|
+ of minutes as an integer.
|
|
|
+
|
|
|
+:min-duration Mark clocking chunks that are shorter that this.
|
|
|
+ This is a time string like \"HH:MM\", or the number
|
|
|
+ of minutes as an integer.
|
|
|
+
|
|
|
+:max-gap Mark gaps between clocking chunks that are longer than
|
|
|
+ this duration. A number of minutes, or a string
|
|
|
+ like \"HH:MM\".
|
|
|
+
|
|
|
+:gap-ok-around List of times during the day which are usually not working
|
|
|
+ times. When a gap is detected, but the gap contains any
|
|
|
+ of these times, the gap is *not* reported. For example,
|
|
|
+ if this is (\"4:00\" \"13:00\") then gaps that contain
|
|
|
+ 4:00 in the morning (i.e. the night) and 13:00
|
|
|
+ (i.e. a typical lunch time) do not cause a warning.
|
|
|
+ You should have at least one time during the night in this
|
|
|
+ list, or otherwise the first task each morning will trigger
|
|
|
+ a warning because it follows a long gap.
|
|
|
+
|
|
|
+Furthermore, the following properties can be used to define faces for
|
|
|
+issue display.
|
|
|
+
|
|
|
+:default-face the default face, if the specific face is undefined
|
|
|
+:overlap-face face for overlapping clocks
|
|
|
+:gap-face face for gaps between clocks
|
|
|
+:no-end-time-face face for incomplete clocks
|
|
|
+:long-face face for clock intervals that are too long
|
|
|
+:short-face face for clock intervals that are too short"
|
|
|
+ :group 'org-agenda-daily/weekly
|
|
|
+ :group 'org-clock
|
|
|
+ :type 'plist)
|
|
|
+
|
|
|
(defcustom org-agenda-log-mode-add-notes t
|
|
|
"Non-nil means add first line of notes to log entries in agenda views.
|
|
|
If a log item like a state change or a clock entry is associated with
|
|
@@ -3412,7 +3455,8 @@ When EMPTY is non-nil, also include days without any entries."
|
|
|
;;; Agenda Daily/Weekly
|
|
|
|
|
|
(defvar org-agenda-start-day nil ; dynamically scoped parameter
|
|
|
-"Custom commands can set this variable in the options section.")
|
|
|
+"Start day for the agenda view.
|
|
|
+Custom commands can set this variable in the options section.")
|
|
|
(defvar org-starting-day nil) ; local variable in the agenda buffer
|
|
|
(defvar org-agenda-current-span nil
|
|
|
"The current span used in the agenda view.") ; local variable in the agenda buffer
|
|
@@ -3457,11 +3501,6 @@ the daily/weekly agenda, see `org-agenda-skip-function'.")
|
|
|
The view will be for the current day or week, but from the overview buffer
|
|
|
you will be able to go to other days/weeks.
|
|
|
|
|
|
-With one \\[universal-argument] prefix argument INCLUDE-ALL,
|
|
|
-all unfinished TODO items will also be shown, before the agenda.
|
|
|
-This feature is considered obsolete, please use the TODO list or a block
|
|
|
-agenda instead.
|
|
|
-
|
|
|
With a numeric prefix argument in an interactive call, the agenda will
|
|
|
span INCLUDE-ALL days. Lisp programs should instead specify SPAN to change
|
|
|
the number of days. SPAN defaults to `org-agenda-span'.
|
|
@@ -3516,24 +3555,6 @@ given in `org-agenda-start-on-weekday'."
|
|
|
(org-set-local 'org-starting-day (car day-numbers))
|
|
|
(org-set-local 'org-include-all-loc include-all)
|
|
|
(org-set-local 'org-agenda-current-span (org-agenda-ndays-to-span span))
|
|
|
- (when (and (or include-all org-agenda-include-all-todo)
|
|
|
- (member today day-numbers))
|
|
|
- (setq files thefiles
|
|
|
- rtnall nil)
|
|
|
- (while (setq file (pop files))
|
|
|
- (catch 'nextfile
|
|
|
- (org-check-agenda-file file)
|
|
|
- (setq date (calendar-gregorian-from-absolute today)
|
|
|
- rtn (org-agenda-get-day-entries
|
|
|
- file date :todo))
|
|
|
- (setq rtnall (append rtnall rtn))))
|
|
|
- (when rtnall
|
|
|
- (insert "All currently open TODO items:\n")
|
|
|
- (add-text-properties (point-min) (1- (point))
|
|
|
- (list 'face 'org-agenda-structure
|
|
|
- 'short-heading "All TODO items"))
|
|
|
- (org-agenda-mark-header-line (point-min))
|
|
|
- (insert (org-finalize-agenda-entries rtnall) "\n")))
|
|
|
(unless org-agenda-compact-blocks
|
|
|
(let* ((d1 (car day-numbers))
|
|
|
(d2 (org-last day-numbers))
|
|
@@ -3572,7 +3593,7 @@ given in `org-agenda-start-on-weekday'."
|
|
|
(setq org-agenda-entry-types
|
|
|
(delq :deadline org-agenda-entry-types)))
|
|
|
(cond
|
|
|
- ((eq org-agenda-show-log 'only)
|
|
|
+ ((memq org-agenda-show-log '(only clockcheck))
|
|
|
(setq rtn (org-agenda-get-day-entries
|
|
|
file date :closed)))
|
|
|
(org-agenda-show-log
|
|
@@ -3643,6 +3664,8 @@ given in `org-agenda-start-on-weekday'."
|
|
|
(recenter 1))))
|
|
|
(goto-char (or start-pos 1))
|
|
|
(add-text-properties (point-min) (point-max) '(org-agenda-type agenda))
|
|
|
+ (if (eq org-agenda-show-log 'clockcheck)
|
|
|
+ (org-agenda-show-clocking-issues))
|
|
|
(org-finalize-agenda)
|
|
|
(setq buffer-read-only t)
|
|
|
(message "")))
|
|
@@ -4732,7 +4755,7 @@ This function is invoked if `org-agenda-todo-ignore-deadlines',
|
|
|
(setq hdmarker (org-agenda-new-marker)
|
|
|
tags (org-get-tags-at))
|
|
|
(looking-at "\\*+[ \t]+\\([^\r\n]+\\)")
|
|
|
- (setq head (match-string 1))
|
|
|
+ (setq head (or (match-string 1) ""))
|
|
|
(setq txt (org-format-agenda-item
|
|
|
(if inactivep org-agenda-inactive-leader nil)
|
|
|
head category tags timestr
|
|
@@ -4830,7 +4853,9 @@ be skipped."
|
|
|
(abbreviate-file-name buffer-file-name))))
|
|
|
(items (if (consp org-agenda-show-log)
|
|
|
org-agenda-show-log
|
|
|
- org-agenda-log-mode-items))
|
|
|
+ (if (eq org-agenda-show-log 'clockcheck)
|
|
|
+ '(clock)
|
|
|
+ org-agenda-log-mode-items)))
|
|
|
(parts
|
|
|
(delq nil
|
|
|
(list
|
|
@@ -4912,6 +4937,125 @@ be skipped."
|
|
|
(goto-char (point-at-eol))))
|
|
|
(nreverse ee)))
|
|
|
|
|
|
+(defun org-agenda-show-clocking-issues ()
|
|
|
+ "Add overlays, showing issues with clocking.
|
|
|
+See also the user option `org-agenda-clock-consistency-checks'."
|
|
|
+ (interactive)
|
|
|
+ (let* ((pl org-agenda-clock-consistency-checks)
|
|
|
+ (re (concat "^[ \t]*"
|
|
|
+ org-clock-string
|
|
|
+ "[ \t]+"
|
|
|
+ "\\(\\[.*?\\]\\)" ; group 1 is first stamp
|
|
|
+ "\\(-\\{1,3\\}\\(\\[.*?\\]\\)\\)?")) ; group 3 is second
|
|
|
+ (tlstart 0.)
|
|
|
+ (tlend 0.)
|
|
|
+ (maxtime (org-hh:mm-string-to-minutes
|
|
|
+ (or (plist-get pl :max-duration) "24:00")))
|
|
|
+ (mintime (org-hh:mm-string-to-minutes
|
|
|
+ (or (plist-get pl :min-duration) 0)))
|
|
|
+ (maxgap (org-hh:mm-string-to-minutes
|
|
|
+ ;; default 30:00 means never complain
|
|
|
+ (or (plist-get pl :max-gap) "30:00")))
|
|
|
+ (gapok (mapcar 'org-hh:mm-string-to-minutes
|
|
|
+ (plist-get pl :gap-ok-around)))
|
|
|
+ (def-face (or (plist-get pl :default-face)
|
|
|
+ '((:background "DarkRed") (:foreground "white"))))
|
|
|
+ issue)
|
|
|
+ (goto-char (point-min))
|
|
|
+ (while (re-search-forward " Clocked: +(-\\|\\([0-9]+:[0-9]+\\))" nil t)
|
|
|
+ (setq issue nil face def-face)
|
|
|
+ (catch 'next
|
|
|
+ (setq m (org-get-at-bol 'org-marker)
|
|
|
+ te nil ts nil)
|
|
|
+ (unless (and m (markerp m))
|
|
|
+ (setq issue "No valid clock line") (throw 'next t))
|
|
|
+ (org-with-point-at m
|
|
|
+ (save-excursion
|
|
|
+ (goto-char (point-at-bol))
|
|
|
+ (unless (looking-at re)
|
|
|
+ (error "No valid Clock line")
|
|
|
+ (throw 'next t))
|
|
|
+ (unless (match-end 3)
|
|
|
+ (setq issue "No end time"
|
|
|
+ face (or (plist-get pl :no-end-time-face) face))
|
|
|
+ (throw 'next t))
|
|
|
+ (setq ts (match-string 1)
|
|
|
+ te (match-string 3)
|
|
|
+ ts (org-float-time
|
|
|
+ (apply 'encode-time (org-parse-time-string ts)))
|
|
|
+ te (org-float-time
|
|
|
+ (apply 'encode-time (org-parse-time-string te)))
|
|
|
+ dt (- te ts))))
|
|
|
+ (cond
|
|
|
+ ((> dt (* 60 maxtime))
|
|
|
+ ;; a very long clocking chunk
|
|
|
+ (setq issue (format "Clocking interval is very long: %s"
|
|
|
+ (org-minutes-to-hh:mm-string
|
|
|
+ (floor (/ (float dt) 60.))))
|
|
|
+ face (or (plist-get pl :long-face) face)))
|
|
|
+ ((< dt (* 60 mintime))
|
|
|
+ ;; a very short clocking chunk
|
|
|
+ (setq issue (format "Clocking interval is very short: %s"
|
|
|
+ (org-minutes-to-hh:mm-string
|
|
|
+ (floor (/ (float dt) 60.))))
|
|
|
+ face (or (plist-get pl :short-face) face)))
|
|
|
+ ((and (> tlend 0) (< ts tlend))
|
|
|
+ ;; Two clock entries are overlapping
|
|
|
+ (setq issue (format "Clocking overlap: %d minutes"
|
|
|
+ (/ (- tlend ts) 60))
|
|
|
+ face (or (plist-get pl :overlap-face) face)))
|
|
|
+ ((and (> tlend 0) (> ts (+ tlend (* 60 maxgap))))
|
|
|
+ ;; There is a gap, lets see if we need to report it
|
|
|
+ (unless (org-agenda-check-clock-gap tlend ts gapok)
|
|
|
+ (setq issue (format "Clocking gap: %d minutes"
|
|
|
+ (/ (- ts tlend) 60))
|
|
|
+ face (or (plist-get pl :gap-face) face))))
|
|
|
+ (t nil)))
|
|
|
+ (setq tlend (or te tlend) tlstart (or ts tlstart))
|
|
|
+ (when issue
|
|
|
+ ;; OK, there was some issue, add an overlay to show the issue
|
|
|
+ (setq ov (make-overlay (point-at-bol) (point-at-eol)))
|
|
|
+ (overlay-put ov 'before-string
|
|
|
+ (concat
|
|
|
+ (org-add-props
|
|
|
+ (format "%-43s" (concat " " issue))
|
|
|
+ nil
|
|
|
+ 'face face)
|
|
|
+ "\n"))
|
|
|
+ (overlay-put ov 'evaporate t)))))
|
|
|
+
|
|
|
+(defun org-agenda-check-clock-gap (t1 t2 ok-list)
|
|
|
+ "Check if gap T1 -> T2 contains one of the OK-LIST time-of-day values."
|
|
|
+ (catch 'exit
|
|
|
+ (unless ok-list
|
|
|
+ ;; there are no OK times for gaps...
|
|
|
+ (throw 'exit nil))
|
|
|
+ (if (> (- (/ t2 36000) (/ t1 36000)) 24)
|
|
|
+ ;; This is more than 24 hours, so it is OK.
|
|
|
+ ;; because we have at least one OK time, that must be in the
|
|
|
+ ;; 24 hour interval.
|
|
|
+ (throw 'exit t))
|
|
|
+ ;; We have a shorter gap.
|
|
|
+ ;; Now we have to get the minute of the day when these times are
|
|
|
+ (let* ((t1dec (decode-time (seconds-to-time t1)))
|
|
|
+ (t2dec (decode-time (seconds-to-time t2)))
|
|
|
+ ;; compute the minute on the day
|
|
|
+ (min1 (+ (nth 1 t1dec) (* 60 (nth 2 t1dec))))
|
|
|
+ (min2 (+ (nth 1 t2dec) (* 60 (nth 2 t2dec)))))
|
|
|
+ (when (< min2 min1)
|
|
|
+ ;; if min2 is smaller than min1, this means it is on the next day.
|
|
|
+ ;; Wrap it to after midnight.
|
|
|
+ (setq min2 (+ min2 1440)))
|
|
|
+ ;; Now check if any of the OK times is in the gap
|
|
|
+ (mapcar (lambda (x)
|
|
|
+ ;; Wrap the time to after midnight if necessary
|
|
|
+ (if (< x min1) (setq x (+ x 1440)))
|
|
|
+ ;; Check if in interval
|
|
|
+ (and (<= min1 x) (>= min2 x) (throw 'exit t)))
|
|
|
+ ok-list)
|
|
|
+ ;; Nope, this gap is not OK
|
|
|
+ nil)))
|
|
|
+
|
|
|
(defun org-agenda-get-deadlines ()
|
|
|
"Return the deadline information for agenda display."
|
|
|
(let* ((props (list 'mouse-face 'highlight
|
|
@@ -5921,12 +6065,13 @@ to switch to narrowing."
|
|
|
(efforts (org-split-string
|
|
|
(or (cdr (assoc (concat org-effort-property "_ALL")
|
|
|
org-global-properties))
|
|
|
- "0 0:10 0:30 1:00 2:00 3:00 4:00 5:00 6:00 7:00 8:00" "")))
|
|
|
+ "0 0:10 0:30 1:00 2:00 3:00 4:00 5:00 6:00 7:00 8:00"
|
|
|
+ "")))
|
|
|
(effort-op org-agenda-filter-effort-default-operator)
|
|
|
(effort-prompt "")
|
|
|
(inhibit-read-only t)
|
|
|
(current org-agenda-filter)
|
|
|
- maybe-reftresh a n tag)
|
|
|
+ maybe-refresh a n tag)
|
|
|
(unless char
|
|
|
(message
|
|
|
"%s by tag [%s ], [TAB], %s[/]:off, [+-]:narrow, [>=<?]:effort: "
|
|
@@ -5973,12 +6118,12 @@ to switch to narrowing."
|
|
|
(push modifier org-agenda-filter))))
|
|
|
(if (not (null org-agenda-filter))
|
|
|
(org-agenda-filter-apply org-agenda-filter)))
|
|
|
- (setq maybe-reftresh t))
|
|
|
+ (setq maybe-refresh t))
|
|
|
((equal char ?/)
|
|
|
(org-agenda-filter-by-tag-show-all)
|
|
|
(when (get 'org-agenda-filter :preset-filter)
|
|
|
(org-agenda-filter-apply org-agenda-filter))
|
|
|
- (setq maybe-reftresh t))
|
|
|
+ (setq maybe-refresh t))
|
|
|
((or (equal char ?\ )
|
|
|
(setq a (rassoc char alist))
|
|
|
(and (>= char ?0) (<= char ?9)
|
|
@@ -5995,9 +6140,9 @@ to switch to narrowing."
|
|
|
(cons (concat (if strip "-" "+") tag)
|
|
|
(if narrow current nil)))
|
|
|
(org-agenda-filter-apply org-agenda-filter)
|
|
|
- (setq maybe-reftresh t))
|
|
|
+ (setq maybe-refresh t))
|
|
|
(t (error "Invalid tag selection character %c" char)))
|
|
|
- (when (and maybe-reftresh
|
|
|
+ (when (and maybe-refresh
|
|
|
(eq org-agenda-clockreport-mode 'with-filter))
|
|
|
(org-agenda-redo))))
|
|
|
|
|
@@ -6215,9 +6360,10 @@ With prefix ARG, go backward that many times the current span."
|
|
|
(defun org-agenda-view-mode-dispatch ()
|
|
|
"Call one of the view mode commands."
|
|
|
(interactive)
|
|
|
- (message "View: [d]ay [w]eek [m]onth [y]ear [SPC]reset [q]uit/abort
|
|
|
- time[G]rid [[]inactive [f]ollow [l]og [L]og-all [E]ntryText
|
|
|
- [a]rch-trees [A]rch-files clock[R]eport include[D]iary")
|
|
|
+ (message "View: [d]ay [w]eek [m]onth [y]ear [SPC]reset [q]uit/abort
|
|
|
+ time[G]rid [[]inactive [f]ollow [l]og [L]og-all [c]lockcheck
|
|
|
+ [a]rch-trees [A]rch-files clock[R]eport include[D]iary
|
|
|
+ [E]ntryText")
|
|
|
(let ((a (read-char-exclusive)))
|
|
|
(case a
|
|
|
(?\ (call-interactively 'org-agenda-reset-view))
|
|
@@ -6227,6 +6373,7 @@ With prefix ARG, go backward that many times the current span."
|
|
|
(?y (call-interactively 'org-agenda-year-view))
|
|
|
(?l (call-interactively 'org-agenda-log-mode))
|
|
|
(?L (org-agenda-log-mode '(4)))
|
|
|
+ (?c (org-agenda-log-mode 'clockcheck))
|
|
|
((?F ?f) (call-interactively 'org-agenda-follow-mode))
|
|
|
(?a (call-interactively 'org-agenda-archives-mode))
|
|
|
(?A (org-agenda-archives-mode 'files))
|
|
@@ -6430,10 +6577,13 @@ With a double `C-u' prefix arg, show *only* log items, nothing else."
|
|
|
(interactive "P")
|
|
|
(org-agenda-check-type t 'agenda 'timeline)
|
|
|
(setq org-agenda-show-log
|
|
|
- (if (equal special '(16))
|
|
|
- 'only
|
|
|
- (if special '(closed clock state)
|
|
|
- (not org-agenda-show-log))))
|
|
|
+ (cond
|
|
|
+ ((equal special '(16)) 'only)
|
|
|
+ ((eq special 'clockcheck)
|
|
|
+ (if (eq org-agenda-show-log 'clockcheck)
|
|
|
+ nil 'clockcheck))
|
|
|
+ (special '(closed clock state))
|
|
|
+ (t (not org-agenda-show-log))))
|
|
|
(org-agenda-set-mode-name)
|
|
|
(org-agenda-redo)
|
|
|
(message "Log mode is %s"
|
|
@@ -6502,8 +6652,11 @@ When called with a prefix argument, include all archive files as well."
|
|
|
(if org-agenda-use-time-grid " Grid" "")
|
|
|
(if (and (boundp 'org-habit-show-habits)
|
|
|
org-habit-show-habits) " Habit" "")
|
|
|
- (if (consp org-agenda-show-log) " LogAll"
|
|
|
- (if org-agenda-show-log " Log" ""))
|
|
|
+ (cond
|
|
|
+ ((consp org-agenda-show-log) " LogAll")
|
|
|
+ ((eq org-agenda-show-log 'clockcheck) " ClkCk")
|
|
|
+ (org-agenda-show-log " Log")
|
|
|
+ (t ""))
|
|
|
(if (or org-agenda-filter (get 'org-agenda-filter
|
|
|
:preset-filter))
|
|
|
(concat " {" (mapconcat
|
|
@@ -7954,8 +8107,18 @@ This will remove the markers, and the overlays."
|
|
|
"Execute an remote-editing action on all marked entries.
|
|
|
The prefix arg is passed through to the command if possible."
|
|
|
(interactive "P")
|
|
|
- (unless org-agenda-bulk-marked-entries
|
|
|
- (error "No entries are marked"))
|
|
|
+ ;; Make sure we have markers, and only valid ones
|
|
|
+ (unless org-agenda-bulk-marked-entries (error "No entries are marked"))
|
|
|
+ (mapc
|
|
|
+ (lambda (m)
|
|
|
+ (unless (and (markerp m)
|
|
|
+ (marker-buffer m)
|
|
|
+ (buffer-live-p (marker-buffer m))
|
|
|
+ (marker-position m))
|
|
|
+ (error "Marker %s for bulk command is invalid" m)))
|
|
|
+ org-agenda-bulk-marked-entries)
|
|
|
+
|
|
|
+ ;; Prompt for the bulk command
|
|
|
(message "Bulk: [r]efile [$]arch [A]rch->sib [t]odo [+/-]tag [s]chd [S]catter [d]eadline [f]unction")
|
|
|
(let* ((action (read-char-exclusive))
|
|
|
(org-log-refile (if org-log-refile 'time nil))
|
|
@@ -8020,7 +8183,7 @@ The prefix arg is passed through to the command if possible."
|
|
|
(fmakunbound 'read-string)))))))
|
|
|
|
|
|
((equal action ?S)
|
|
|
- (if (not (org-agenda-check-type nil 'agenda 'timeline))
|
|
|
+ (if (not (org-agenda-check-type nil 'agenda 'timeline 'todo))
|
|
|
(error "Can't scatter tasks in \"%s\" agenda view" org-agenda-type)
|
|
|
(let ((days (read-number
|
|
|
(format "Scatter tasks across how many %sdays: "
|
|
@@ -8043,7 +8206,9 @@ The prefix arg is passed through to the command if possible."
|
|
|
(setq day-of-week 0)))))
|
|
|
;; silently fail when try to replan a sexp entry
|
|
|
(condition-case nil
|
|
|
- (org-agenda-date-later distance)
|
|
|
+ (org-agenda-schedule nil
|
|
|
+ (days-to-time
|
|
|
+ (+ (org-today) distance)))
|
|
|
(error nil)))))))
|
|
|
|
|
|
((equal action ?f)
|