ADD agenda scanner

This commit is contained in:
Nathan Dwarshuis 2022-02-04 18:59:37 -05:00
parent 04c177216c
commit 8fdec11d79
1 changed files with 290 additions and 106 deletions

View File

@ -173,7 +173,8 @@ has a valid (meaning in KWS) keyword and either its parent has a
valid keyword or none of its parents have valid keywords."
(let ((more t)
cur-path this-point this-key this-level this-todo has-todo this-parent
tags this-file-links acc acc-meta this-parent-key)
this-tags this-meta all-tags this-file-links this-links acc acc-meta
this-parent-key)
;; TODO add org-mode sanity check
(goto-char (point-min))
;; If not on a headline, check for a property drawer with links in it
@ -219,20 +220,22 @@ valid keyword or none of its parents have valid keywords."
;; If parent is not a todo and we want tag inheritance, store all tags
;; above this headline (sans file-tags which we can get later easily)
;; (org-entry-get nil org-x-prop-parent-type)
(setq tags (if (and (not this-parent-key) org-use-tag-inheritance)
(->> cur-path
(--mapcat (nth 2 it))
(append this-tags))
this-tags)
(setq all-tags (if (and (not this-parent-key)
org-use-tag-inheritance)
(->> cur-path
(--mapcat (nth 2 it))
(append this-tags))
this-tags)
this-links (or (org-x-dag-get-link-property)
(when (not this-parent-key) this-file-links))
this-meta (org-x-dag-build-meta file
this-point
this-level
(substring-no-properties this-todo)
tags
all-tags
this-parent-key))
(!cons (cons this-key this-meta) acc-meta)
(!cons (cons this-key (append (list (nth 1 this-parent))
(org-x-dag-get-link-property)))
(!cons (cons this-key (append (list (nth 1 this-parent)) this-links))
acc))
;; Add current headline to stack
(!cons (list this-level this-key this-tags) cur-path)))
@ -399,6 +402,162 @@ encountered will be returned."
(-when-let (p (save-excursion (re-search-forward re end t)))
(list (1- (match-beginning 1)) (match-string 1)))))
(defun org-x-dag-timestamp-to-absolute (ts)
(->> (org-ml-get-properties '(:month-start :day-start :year-start) ts)
(calendar-absolute-from-gregorian)))
;; TODO 'modulus' only applies to the repeater
(defun org-ml-timestamp-extract-modulus (modtype ts)
"Return the modulus of timestamp TS for MODTYPE."
(cl-flet
((convert-value
(islongp value unit)
(pcase unit
('year (* 12 value))
('month value)
(_ (if islongp
;; TODO make these messages not suck
(pcase unit
('week (* 7 value))
('day value)
((or 'hour 'minute) (message "WARNING: ..."))
(_ (error)))
(pcase unit
('week (* 7 1440 value))
('day (* 1440 value))
('hour (* 60 value))
('minute value)
(_ (error)))))))
(convert-unit
(unit)
(if (memq unit '(year month)) 'month 'submonth)))
(-let* ((props (pcase modtype
('warning '(:warning-value :warning-unit :warning-type))
('repeater '(:repeater-value :repeater-unit :repeater-type))))
(islongp (->> (org-ml-timestamp-get-start-time ts)
(org-ml-time-is-long))))
(-when-let ((value unit type) (org-ml-get-properties props ts))
(let ((v (convert-value islongp value unit))
(u (convert-unit unit)))
`(,v ,u ,type))))))
(defun org-x-dag-partition-timestamp (ts)
(list :datetime (org-ml-timestamp-get-start-time ts)
:pos (org-ml-get-property :begin ts)
:repeater (org-ml-timestamp-extract-modulus 'repeater ts)
:warning (org-ml-timestamp-extract-modulus 'warning ts)))
(defmacro org-x-dag-with-times (datetime0 datetime1 form)
;; ASSUME all digits in this comparison are on the calendar/clock (eg day 32
;; does not 'rollover' to day 1 on the next month)
(declare (indent 2))
`(if (or (and (org-ml-time-is-long ,datetime0)
(org-ml-time-is-long ,datetime1))
(not (or (org-ml-time-is-long ,datetime0)
(org-ml-time-is-long ,datetime1))))
,form
(error "Datetimes are invalid lengths: %S and %S" ,datetime0 ,datetime1)))
;; TODO this function name sucks, call it datetime
(defun org-x-dag-date< (datetime0 datetime1)
(org-x-dag-with-times datetime0 datetime1
(-when-let (next (->> (-zip-with #'cons datetime0 datetime1)
(--drop-while (= (car it) (cdr it)))
(car)))
(< (car next) (cdr next)))))
(defun org-x-dag-date= (datetime0 datetime1)
(org-x-dag-with-times datetime0 datetime1
(->> (-zip-with #'cons datetime0 datetime1)
(--drop-while (= (car it) (cdr it)))
(not))))
(defun org-x-dag-time-shift (datetime shift unit)
(cl-flet*
((enc-dec-long
(y m d M H)
(-let (((_ M* H* d* m* y* _ _ _)
(->> (list 0 M H d m y nil nil (current-time-zone))
(encode-time)
(decode-time))))
(list y* m* d* H* M*)))
(enc-dec-short
(y m d)
(-take 3 (enc-dec-long y m d 0 0))))
(pcase datetime
((or `(,y ,m ,d) `(,y ,m ,d nil nil))
(pcase unit
('month (enc-dec-short y (+ m shift) d))
('submonth (enc-dec-short y m (+ d shift)))))
(`(,y ,m ,d ,H ,M)
(pcase unit
('month (enc-dec-long y (+ m shift) d H M))
('submonth (enc-dec-long y m d H (+ M shift))))))))
(defun org-x-dag-repeater-get-next (cur time shift shifttype reptype)
(pcase reptype
('catch-up
;; Next time is a multiple of repeater in the future relative to the base
;; time; shift one interval at a time since they may not be spaced evenly
;; (DST, leap year, different days in each month, etc). Think of this like
;; a path function from p-chem; shifting 3 months once might be different
;; than shifting by 1 month three times.
(let ((next time))
(while (org-x-dag-date< next cur)
(setq next (org-x-dag-time-shift next shift shifttype)))
next))
('restart
;; Next time is one repeater interval after now
;;
;; TODO cur needs to match the length of time
(org-x-dag-time-shift cur shift shifttype))
('cumulate
;; Next time is one repeater interval after the base timestamp
(org-x-dag-time-shift time shift shifttype))))
(defun org-x-dag-unfold-timestamp (cur time rep future-limit)
;; ASSUME pts and future-limit are both long or short timestamps
(pcase rep
(`nil
(unless (org-x-dag-date< future-limit time)
(list time)))
(`(,value ,unit ,reptype)
(->> (org-x-dag-repeater-get-next cur time value unit reptype)
(--unfold (let ((next (org-x-dag-time-shift it value unit)))
(unless (org-x-dag-date< future-limit next)
(cons next next))))
(cons time)))))
(defun org-x-dag-date-add-time (date)
(-let (((_ M H) (decode-time (current-time))))
`(,@date ,H ,M)))
(defun org-x-dag-get-scheduled-at (sel-date pts)
(-let* (((&plist :datetime d :repeater r) pts)
(islongp (org-ml-time-is-long d))
(future-limit (if islongp `(,@sel-date 23 59) sel-date))
(sel-datetime (if islongp (org-x-dag-date-add-time sel-date) sel-date)))
(org-x-dag-unfold-timestamp sel-datetime d r future-limit)))
(defun org-x-dag-get-deadlines-at (sel-date pts)
(-let* (((&plist :datetime d :repeater r :warning w) pts)
(islongp (org-ml-time-is-long d))
((warn-shift warn-shifttype)
(if w w
(let ((f (if islongp 1440 1)))
`(,(* f org-deadline-warning-days) submonth))))
(sel-datetime (if islongp (org-x-dag-date-add-time sel-date) sel-date))
(future-limit (org-x-dag-time-shift sel-datetime warn-shift warn-shifttype)))
(org-x-dag-unfold-timestamp sel-datetime d r future-limit)))
(defun org-x-dag-headline-get-planning ()
(let ((end (save-excursion (outline-next-heading))))
(save-excursion
(when (re-search-forward org-planning-line-re end t)
(-let* ((pl (org-ml-parse-this-element)))
(->> (org-ml-get-properties '(:deadline :scheduled) pl)
(--map (-some-> it (org-x-dag-partition-timestamp)))))))))
(defun org-x-dag-format-tag-node (category tags key)
;; ASSUME I don't use subtree-level categories
(-let* (;; (category (org-get-category))
@ -426,101 +585,95 @@ encountered will be returned."
;; misc
'type (concat "tagsmatch" ts-type)))))
;; (defun org-x-dag-format-timestamp-node (date category tags id)
;; (let* ((help-echo
;; (format "mouse-2 or RET jump to Org file %s"
;; (abbreviate-file-name buffer-file-name)))
;; (extra)
;; (head)
;; (level)
;; (tags)
;; (timestamp)
;; ;; idk if this matters, nothing we care about here will be a habit
;; (habitp)
;; (ts-marker)
;; (hd-marker (org-agenda-new-marker))
;; (item (org-agenda-format-item extra head level category tags timestamp
;; org-ts-regexp habitp))
;; ;; TODO why am I getting the priority after sending the headline
;; ;; through some crazy formatting function?
;; (priority (org-get-priority item)))
;; (-> (org-x-dag-add-default-props item)
;; (org-add-props nil
;; ;; face
;; 'face 'org-agenda-calendar-event
;; 'done-face 'org-agenda-done
;; ;; marker
;; 'org-hd-marker hd-marker
;; 'org-marker ts-marker
;; ;; headline stuff
;; 'todo-state (org-x-dag-id->todo id)
;; 'priority priority
;; 'date date
;; 'ts-date ts
;; ;; this appears to be either obsolete or such an obscure feature
;; ;; that I would never care about it
;; ;; 'warntime warntime
;; ;; misc
;; 'type "timestamp"
;; 'help-echo help-echo))))
(defun org-x-dag-date-to-gregorian (date)
(-let (((y m d) date))
`(,m ,d ,y)))
;; TODO it might make sense to make this also parse deadline
(defun org-x-dag-format-scheduled-node (date category tags id)
(-let* (((pos ts-match) (org-x-dag-headlines-get-regexp org-scheduled-time-regexp))
(current (calendar-absolute-from-gregorian date))
(schedule (org-time-string-to-absolute ts-match))
(diff (- current schedule))
(pastschedp (< diff 0))
(futureschedp (> diff 0))
;; TODO wtf does this actually do?
(repeat (if (<= current today) schedule
(org-time-string-to-absolute s current 'future (current-buffer) pos)))
(habitp (and (fboundp 'org-is-habit-p) (org-is-habit-p)))
;; hopefully this is right...
(time (unless (and (not habitp) (/= current schedule) (/= current repeat))
(-if-let (m (s-match " \\([012]?[0-9]:[0-9][0-9]\\)" ts-match))
(concat m " ")
'time)))
(today (org-today))
(todayp (org-agenda-today-p date))
(head (org-get-heading))
(level (org-x-dag-id->formatted-level id))
(tags* (org-x-dag-prepare-tags tags))
(todo-state (org-x-dag-id->todo id))
(donep (member todo-state org-x-done-keywords))
(hd-marker (org-agenda-new-marker))
(ts-marker (org-agenda-new-marker pos))
(extra (->> (if pastschedp (format "%sd Ago" diff) "Scheduled")
(format "%s: ")))
(item (org-agenda-format-item extra head level category tags* time
nil habitp))
;; TODO why am I getting the priority after sending the headline
;; through some crazy formatting function?
(priority (org-get-priority item))
(face (cond ((and (not habitp) pastschedp) 'org-scheduled-previously)
((and habitp futureschedp) 'org-agenda-done)
(todayp 'org-scheduled-today)
(t 'org-scheduled))))
(defun org-x-dag-time-get-clock-time (time)
(when (org-ml-time-is-long time) (list (nth 3 time) (nth 4 time))))
(defun org-x-dag-time-partition (time)
(if (org-ml-time-is-long time) (-split-at 3 time) `(,(-take 3 time) nil)))
(defun org-x-dag-date-diff (date0 date1)
""
(pcase (list date0 date1)
(`((,y0 ,m0 ,d0) (,y1 ,m1 ,d1))
(- (calendar-absolute-from-gregorian `(,m0 ,d0 ,y0))
(calendar-absolute-from-gregorian `(,m1 ,d1 ,y1))))
(_ (error "Invalid date format(s): %S or %S" date0 date1))))
(defun org-x-dag-format-item (id extra category tags time)
(let* ((tags* (org-x-dag-prepare-tags tags))
(head (org-get-heading))
(level (org-x-dag-id->formatted-level id))
(todo-state (org-x-dag-id->todo id))
(time-str (-some->> time (apply #'format "%02i:%02i ")))
(item (org-agenda-format-item extra head level category tags* time-str))
;; TODO why am I getting the priority after sending the headline
;; through some crazy formatting function?
(priority (org-get-priority item)))
(-> (org-x-dag-add-default-props item)
(org-add-props nil
;; face
'face (if donep 'org-agenda-done face)
'undone-face face
'done-face 'org-agenda-done
;; marker
'org-hd-marker hd-marker
'org-marker ts-marker
;; headline stuff
'todo-state (org-x-dag-id->todo id)
'priority priority
'date (if pastschedp schedule date)
'ts-date schedule
'org-habit-p habitp
;; this appears to be either obsolete or such an obscure feature
;; that I would never care about it
;; 'warntime warntime
;; misc
'type (if pastschedp "past-scheduled" "scheduled")))))
'todo-state todo-state
'priority priority))))
(defun org-x-dag-planning-props (id face pos date ts-date type)
(list
;; face
'face (if (org-x-dag-id->is-done-p id) 'org-agenda-done face)
'undone-face face
'done-face 'org-agenda-done
;; marker
'org-hd-marker (org-agenda-new-marker)
'org-marker (org-agenda-new-marker pos)
;; headline stuff
'date (org-x-dag-date-to-gregorian date)
'ts-date (org-x-dag-date-to-gregorian ts-date)
'type type))
(defun org-x-dag-format-scheduled-node (sel-date pos datetime category tags id)
(-let* (((this-date this-time) (org-x-dag-time-partition datetime))
(diff (org-x-dag-date-diff this-date sel-date))
(pastp (< diff 0))
(todayp (= diff 0))
;; hopefully this is right...if it is this seems silly
(extra (-let (((today past) org-agenda-scheduled-leaders))
(cond (todayp today)
(pastp (format past (- diff)))
(t "")))) ;; This should never actually be used
(face (cond (pastp 'org-scheduled-previously)
(todayp 'org-scheduled-today)
(t 'org-scheduled)))
((date type) (if pastp `(,this-date "past-scheduled")
`(,sel-date "scheduled")))
(props (org-x-dag-planning-props id face pos date this-date type)))
;; NOTE: I don't care about habits, no need to consider them
(-> (org-x-dag-format-item id extra category tags this-time)
(org-add-props props))))
(defun org-x-dag-format-deadline-node (sel-date pos datetime category tags id)
(-let* (((this-date this-time) (org-x-dag-time-partition datetime))
(diff (org-x-dag-date-diff this-date sel-date))
(pastp (< diff 0))
(futurep (< 0 diff))
(extra (-let* (((now future past) org-agenda-deadline-leaders))
(cond
(futurep (format future diff))
(pastp (format past diff))
(t now))))
;; TODO the stock deadline formatter uses the warning time to
;; determine this based on percentage; I'm lazy and don't feel like
;; doing that (now) but I might in the future
(face (cond
((< 5 diff) 'org-upcoming-distant-deadline)
((< 1 diff) 'org-upcoming-deadline)
(t 'org-warning)))
((date type) (if futurep `(,sel-date "upcoming-deadline")
`(,this-date "deadline")))
(props (org-x-dag-planning-props id face pos date this-date type)))
(-> (org-x-dag-format-item id extra category tags this-time)
(org-add-props props))))
;;; HEADLINE PREDICATES
;;
@ -699,12 +852,15 @@ encountered will be returned."
((:empt)
(:project-error :unscheduled :actv))
(if (member it-kw org-x-done-keywords) 0 1)
;; TODO this has an argument mismatch
#'org-x-dag-headline-get-iterator-project-status))
((equal kw org-x-kw-todo)
(org-x-dag-descend-into-project children nil
((:unscheduled :project-error)
(:empt)
(:actv))
;; TODO this triggers a compiler warning because I don't use
;; `it-kw'
(let ((ts (org-x-dag-headline-is-scheduled-p t)))
(cond
((not ts) 0)
@ -872,7 +1028,7 @@ encountered will be returned."
(org-x-dag-with-key key
(-let (((is-archivable is-project)
(-if-let (children (org-x-dag-id->headline-children key))
(-> (org-x-dag-headline-get-project-status org-x-dag key tags children)
(-> (org-x-dag-headline-get-project-status key tags children)
(alist-get org-x-project-status-priorities)
(eq :archivable)
(list t))
@ -888,7 +1044,8 @@ encountered will be returned."
(org-x-dag-id->is-toplevel-p it)
(org-x-dag-with-key it
(if (org-x-dag-headline-is-iterator-p)
(--map (format-key category it) (org-x-dag-id->headline-children it))
(->> (org-x-dag-id->headline-children it)
(--map (format-key it-category it)))
(list (format-key it-category it)))))))
(defun org-x-dag-scan-ltgs ()
@ -965,6 +1122,35 @@ encountered will be returned."
(org-x-dag-with-key it
(list (format-id it-category it))))))
(defun org-x-dag-scan-agenda (sel-date)
(cl-flet*
((format-timestamps
(sel-date cat id pts get-datetimes-fun format-datetime-fun)
(-when-let (datetimes (funcall get-datetimes-fun sel-date pts))
(-let (((&plist :pos) pts)
(tags (org-x-dag-id->tags t org-file-tags id))
(donep (org-x-dag-id->is-done-p id)))
(->> datetimes
(--remove (and donep (not (org-x-dag-date= (-take 3 it) sel-date))))
(--map (funcall format-datetime-fun sel-date pos it cat tags id))))))
(format-id
(cat id)
(org-x-dag-with-key id
(-when-let (res (org-x-dag-headline-get-planning))
(-let (((dead sched) res))
(append (when dead
(format-timestamps sel-date cat id dead
#'org-x-dag-get-deadlines-at
#'org-x-dag-format-deadline-node))
(when sched
(format-timestamps sel-date cat id sched
#'org-x-dag-get-scheduled-at
#'org-x-dag-format-scheduled-node))))))))
(org-x-dag-with-files (org-x-get-action-files)
nil
(org-x-dag-with-key it
(format-id it-category it)))))
;;; AGENDA VIEWS
;; (defun org-x-dag-show-tasks (_match)
@ -978,9 +1164,7 @@ encountered will be returned."
(defun org-x-dag-show-nodes (get-nodes)
(org-x-dag-sync)
(let* ((org-tags-match-list-sublevels org-tags-match-list-sublevels)
(completion-ignore-case t)
rtnall files file pos matcher
buffer)
(completion-ignore-case t))
(catch 'exit
(org-agenda-prepare (concat "DAG-TAG"))
(org-compile-prefix-format 'tags)