cleaned up org-cluster, added docstrings, and added hotkeys
This commit is contained in:
parent
38266bfebc
commit
3bafc3504c
354
conf.org
354
conf.org
|
@ -1445,48 +1445,83 @@ The modeline is a nice place to indicate if something is clocked in or out. Unfo
|
||||||
|
|
||||||
(setq spaceline-highlight-face-func 'nd/spaceline-highlight-face-clocked)
|
(setq spaceline-highlight-face-func 'nd/spaceline-highlight-face-clocked)
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
*** conflict detection
|
*** clustering
|
||||||
Somehow org-mode has no way to detect conflicts between tasks with timestamps (!!??). Luckily I can make my own.
|
Org mode has no way of detecting if conflicts exist. It also has no way of alerting someone if they have overbooked their schedule
|
||||||
**** backend
|
**** extraction filters
|
||||||
The algorithm to detect conflicts scans all org files and stores conflicts in a list of pairs of each heading with a conflicting timestamp.
|
These control which types of headlines are processed by org-cluster
|
||||||
|
|
||||||
Steps for this algorithm:
|
|
||||||
1. make a list of all entries with timestamps
|
|
||||||
2. sort timestamp list
|
|
||||||
3. Walk through list and compare entries immediately after (sorting ensures that entries can be skipped once one non-conflict is found). If conflicts are found push the pair to a new list (this is what is used to make the display)
|
|
||||||
|
|
||||||
This should be O(n) (best case/no conflicts) to O(n^2) (worst case/everything conflicts)
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(defun nd/org-conflict-make-entry (ts range offset fp hardness
|
(defvar nd/org-cluster-filter-files t
|
||||||
|
"Set to t if files should be filtered in org-cluster.
|
||||||
|
This option does nothing unless `nd/org-cluster-filtered-files' is
|
||||||
|
also non-nil.")
|
||||||
|
|
||||||
|
(defconst nd/org-cluster-filtered-files
|
||||||
|
'("incubator" "peripheral")
|
||||||
|
"Files that should be excluded from org-cluster analysis.
|
||||||
|
These are pattern-matched so they do not need to be exact names
|
||||||
|
or paths.")
|
||||||
|
|
||||||
|
(defvar nd/org-cluster-filter-todo t
|
||||||
|
"Set to t if todo keywords should be filtered in org-cluster.
|
||||||
|
This option does nothing unless `nd/org-cluster-filtered-todo' is
|
||||||
|
also non-nil.")
|
||||||
|
|
||||||
|
(defconst nd/org-cluster-filtered-todo
|
||||||
|
'("CANC" "DONE")
|
||||||
|
"TODO keywords that should be filtered from org-cluster analysis.")
|
||||||
|
|
||||||
|
(defvar nd/org-cluster-filter-past t
|
||||||
|
"Set to t to exclude files from before now in org-cluster analysis.")
|
||||||
|
|
||||||
|
(defvar nd/org-cluster-filter-habit nil
|
||||||
|
"Set to t to exclude habits from org-cluster analysis.")
|
||||||
|
#+END_SRC
|
||||||
|
**** timestamp extraction and filtering
|
||||||
|
Conflicts and overloads begin with the same list to process, which is created using =org-element-parse-buffer= and a variety of filtering functions to extract relevent timestamps.
|
||||||
|
|
||||||
|
The main object that is passed around during extraction and processing is the timestamp-plist as described in =nd/org-cluster-make-tsp= below.
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(defun nd/org-cluster-make-tsp (unixtime range offset fp hardness
|
||||||
&optional type)
|
&optional type)
|
||||||
(list :timestamp ts
|
"Construct a timestamp plist to be used in further processing.
|
||||||
|
UNIXTIME is the unixtime of the timestamp as an integer, RANGE is the
|
||||||
|
duration of the timestamp (could be 0), OFFSET is the character offset
|
||||||
|
of the timestamp in the file represented with filepath FP, HARDNESS
|
||||||
|
is a boolean denoting if the timestamp is 'hard' (has minutes and
|
||||||
|
hours) or 'soft' (only a date). TYPE can be optionally supplied to
|
||||||
|
denote kinds of timestamps (only 'scheduled' for now)."
|
||||||
|
(list :unixtime (round unixtime)
|
||||||
:range (or range 0)
|
:range (or range 0)
|
||||||
:offset offset
|
:offset offset
|
||||||
:type type
|
:type type
|
||||||
:hardness hardness
|
:hardness hardness
|
||||||
:filepath fp))
|
:filepath fp))
|
||||||
|
|
||||||
(defun nd/org-conflict-ts-hard-p (ts)
|
(defun nd/org-cluster-ts-hard-p (ts)
|
||||||
|
"Return non-nil if the timestamp TS has hours/minutes."
|
||||||
(org-element-property :hour-start ts))
|
(org-element-property :hour-start ts))
|
||||||
|
|
||||||
(defun nd/org-conflict-parse-ts (ts hl fp)
|
(defun nd/org-cluster-parse-ts (ts hl fp)
|
||||||
|
"Parse a timestamp TS belonging to headline HL and filepath FP.
|
||||||
|
TS is an object as described in the org-element API. Only active
|
||||||
|
or active-range types are considered. Returns a new timestamp-plist
|
||||||
|
for TS."
|
||||||
(when ts
|
(when ts
|
||||||
(let* ((offset (org-element-property :begin hl))
|
(let* ((offset (org-element-property :begin hl))
|
||||||
(hardness (nd/org-conflict-ts-hard-p ts))
|
(hardness (nd/org-cluster-ts-hard-p ts))
|
||||||
(split
|
(split
|
||||||
(lambda (ts &optional end)
|
(lambda (ts &optional end)
|
||||||
(--> ts
|
(--> ts
|
||||||
(org-timestamp-split-range it end)
|
(org-timestamp-split-range it end)
|
||||||
(org-element-property :raw-value it)
|
(org-element-property :raw-value it)
|
||||||
(org-2ft it)
|
(org-2ft it))))
|
||||||
(round it))))
|
|
||||||
(start (funcall split ts)))
|
(start (funcall split ts)))
|
||||||
(if (eq (org-element-property :type ts) 'active-range)
|
(if (eq (org-element-property :type ts) 'active-range)
|
||||||
(let ((range (--> ts (funcall split it t) (- it start))))
|
(let ((range (--> ts (funcall split it t) (- it start))))
|
||||||
(nd/org-conflict-make-entry start range offset fp hardness))
|
(nd/org-cluster-make-tsp start range offset fp hardness))
|
||||||
(nd/org-conflict-make-entry start 0 offset fp hardness)))))
|
(nd/org-cluster-make-tsp start 0 offset fp hardness)))))
|
||||||
|
|
||||||
(defun nd/org-conflict-effort-seconds (effort-str)
|
(defun nd/org-cluster-effort-seconds (effort-str)
|
||||||
"Convert EFFORT-STR into an integer in seconds from HH:MM format."
|
"Convert EFFORT-STR into an integer in seconds from HH:MM format."
|
||||||
(let ((effort-str (string-trim effort-str)))
|
(let ((effort-str (string-trim effort-str)))
|
||||||
(save-match-data
|
(save-match-data
|
||||||
|
@ -1503,26 +1538,35 @@ This should be O(n) (best case/no conflicts) to O(n^2) (worst case/everything co
|
||||||
(* 60))))
|
(* 60))))
|
||||||
(t (error (format "Unknown effort: %s'" effort-str)))))))
|
(t (error (format "Unknown effort: %s'" effort-str)))))))
|
||||||
|
|
||||||
(defun nd/org-conflict-extract (acc fun objs &rest args)
|
(defun nd/org-cluster-extract (acc fun objs &rest args)
|
||||||
|
"Run FUN on each of OBJS and put results into accumulator ACC.
|
||||||
|
FUN is a function that takes the accumulator as its first argument,
|
||||||
|
one member of OBJS as the second, and ARGS as the rest if supplied."
|
||||||
(while objs
|
(while objs
|
||||||
(setq acc (apply fun acc (car objs) args)
|
(setq acc (apply fun acc (car objs) args)
|
||||||
objs (cdr objs)))
|
objs (cdr objs)))
|
||||||
acc)
|
acc)
|
||||||
|
|
||||||
(defun nd/org-conflict-extract-hl-sched (acc hl fp)
|
(defun nd/org-cluster-extract-hl-sched (acc hl fp)
|
||||||
|
"Extract scheduled timestamp from headline HL in filepath FP.
|
||||||
|
Create a new timestamp-plist and add to accumulator ACC."
|
||||||
(let* ((ts (org-element-property :scheduled hl))
|
(let* ((ts (org-element-property :scheduled hl))
|
||||||
(ts-ft (->> ts (org-element-property :raw-value) org-2ft round))
|
(unixtime (->> ts (org-element-property :raw-value) org-2ft))
|
||||||
(range (-some->> hl
|
(range (-some->> hl
|
||||||
(org-element-property :EFFORT)
|
(org-element-property :EFFORT)
|
||||||
nd/org-conflict-effort-seconds))
|
nd/org-cluster-effort-seconds))
|
||||||
(hardness (nd/org-conflict-ts-hard-p ts))
|
(hardness (nd/org-cluster-ts-hard-p ts))
|
||||||
(offset (org-element-property :begin hl)))
|
(offset (org-element-property :begin hl)))
|
||||||
(if (= 0 ts-ft) acc
|
(if (= 0 unixtime) acc
|
||||||
(-> ts-ft
|
(-> unixtime
|
||||||
(nd/org-conflict-make-entry range offset fp hardness 'scheduled)
|
(nd/org-cluster-make-tsp range offset fp hardness 'scheduled)
|
||||||
(cons acc)))))
|
(cons acc)))))
|
||||||
|
|
||||||
(defun nd/org-conflict-extract-hl-ts (acc hl fp)
|
(defun nd/org-cluster-extract-hl-ts (acc hl fp)
|
||||||
|
"Extract timestamps from headline HL in filepath FP.
|
||||||
|
All active timestamps that are not in drawers or the planning header
|
||||||
|
are considered. Each timestamp is converted into a new timestamp-plist
|
||||||
|
and added to accumulator ACC."
|
||||||
(--> hl
|
(--> hl
|
||||||
(assoc 'section it)
|
(assoc 'section it)
|
||||||
(org-element-contents it)
|
(org-element-contents it)
|
||||||
|
@ -1536,143 +1580,185 @@ This should be O(n) (best case/no conflicts) to O(n^2) (worst case/everything co
|
||||||
(or (eq 'active (org-element-property :type it))
|
(or (eq 'active (org-element-property :type it))
|
||||||
(eq 'active-range (org-element-property :type it)))
|
(eq 'active-range (org-element-property :type it)))
|
||||||
it)
|
it)
|
||||||
(--map (nd/org-conflict-parse-ts it hl fp) it)
|
(--map (nd/org-cluster-parse-ts it hl fp) it)
|
||||||
(append acc it)))
|
(append acc it)))
|
||||||
|
|
||||||
(defun nd/org-conflict-extract-hl (acc hl fp)
|
(defun nd/org-cluster-extract-hl (acc hl fp)
|
||||||
|
"Extract timestamps from headline HL in filepath FP and store in ACC."
|
||||||
(-> acc
|
(-> acc
|
||||||
(nd/org-conflict-extract-hl-sched hl fp)
|
(nd/org-cluster-extract-hl-sched hl fp)
|
||||||
(nd/org-conflict-extract-hl-ts hl fp)))
|
(nd/org-cluster-extract-hl-ts hl fp)))
|
||||||
|
|
||||||
(defun nd/org-conflict-filter-todo (hls)
|
(defun nd/org-cluster-filter-todo (hls)
|
||||||
(if (not nd/org-conficts-filter-todo) hls
|
"Filter certain TODO keywords from headline list HLS."
|
||||||
|
(if (not nd/org-cluster-filter-todo) hls
|
||||||
(--remove
|
(--remove
|
||||||
(member (org-element-property :todo-keyword it)
|
(member (org-element-property :todo-keyword it)
|
||||||
nd/org-conficts-filtered-todo)
|
nd/org-cluster-filtered-todo)
|
||||||
hls)))
|
hls)))
|
||||||
|
|
||||||
(defun nd/org-conflict-filter-files (fps)
|
(defun nd/org-cluster-filter-files (fps)
|
||||||
(if (not nd/org-conficts-filter-files) fps
|
"Filter certain file names from files list FPS."
|
||||||
|
(if (not nd/org-cluster-filter-files) fps
|
||||||
(--remove
|
(--remove
|
||||||
(-find (lambda (s) (string-match-p s it)) nd/org-conficts-filtered-files)
|
(-find (lambda (s) (string-match-p s it)) nd/org-cluster-filtered-files)
|
||||||
fps)))
|
fps)))
|
||||||
|
|
||||||
(defun nd/org-conflict-filter-past (tss)
|
(defun nd/org-cluster-filter-past (tsps)
|
||||||
(if (not nd/org-conficts-filter-past) tss
|
"Filter out timestamp-plists in list TSPS if they start in the past."
|
||||||
|
(if (not nd/org-cluster-filter-past) tsps
|
||||||
(let ((ft (float-time)))
|
(let ((ft (float-time)))
|
||||||
(--remove (< (plist-get it :timestamp) ft) tss))))
|
(--remove (< (plist-get it :unixtime) ft) tsps))))
|
||||||
|
|
||||||
(defun nd/org-conflict-filter-habit (hls)
|
(defun nd/org-cluster-filter-habit (hls)
|
||||||
(if (not nd/org-conficts-filter-habit) hls
|
"Filter headlines from headline list HLS that are habits."
|
||||||
|
(if (not nd/org-cluster-filter-habit) hls
|
||||||
(--remove (org-element-property :STYLE it) hls)))
|
(--remove (org-element-property :STYLE it) hls)))
|
||||||
|
|
||||||
(defun nd/org-conflict-extract-file (acc fp)
|
(defun nd/org-cluster-extract-file (acc fp)
|
||||||
|
"Extract timestamps from filepath FP and add to accumulator ACC."
|
||||||
(-->
|
(-->
|
||||||
fp
|
fp
|
||||||
(find-file-noselect it t)
|
(find-file-noselect it t)
|
||||||
(with-current-buffer it (org-element-parse-buffer))
|
(with-current-buffer it (org-element-parse-buffer))
|
||||||
(org-element-map it 'headline #'identity)
|
(org-element-map it 'headline #'identity)
|
||||||
(nd/org-conflict-filter-todo it)
|
(nd/org-cluster-filter-todo it)
|
||||||
(nd/org-conflict-filter-habit it)
|
(nd/org-cluster-filter-habit it)
|
||||||
(nd/org-conflict-extract acc #'nd/org-conflict-extract-hl it fp)))
|
(nd/org-cluster-extract acc #'nd/org-cluster-extract-hl it fp)))
|
||||||
|
|
||||||
(defun nd/org-conflict-conflicting-p (ts-a ts-b)
|
(defun nd/org-cluster-get-unprocessed ()
|
||||||
|
"Return a list of timestamp-plists with desired filter settings."
|
||||||
|
(->>
|
||||||
|
;; (list "~/Org/reference/testconflict.org")
|
||||||
|
(org-agenda-files)
|
||||||
|
nd/org-cluster-filter-files
|
||||||
|
(nd/org-cluster-extract nil #'nd/org-cluster-extract-file)
|
||||||
|
nd/org-cluster-filter-past))
|
||||||
|
#+END_SRC
|
||||||
|
**** conflict detection
|
||||||
|
This algorithm builds a list of pairs, with each pair being a two tasks that conflict and should be O(n) (best case/no conflicts) to O(n^2) (worst case/everything conflicts).
|
||||||
|
|
||||||
|
Steps for this:
|
||||||
|
1. make a list of all entries containing timestamps (active and scheduled)
|
||||||
|
2. sort timestamp list
|
||||||
|
3. Walk through list and compare entries immediately after (sorting ensures that entries can be skipped once one non-conflict is found). If conflicts are found push the pair to new list.
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(defun nd/org-cluster-conflicting-p (tsp-a tsp-b)
|
||||||
"Return t if timestamps TS-A and TS-B conflict."
|
"Return t if timestamps TS-A and TS-B conflict."
|
||||||
;; assume that ts-a starts before ts-b
|
;; assume that ts-a starts before ts-b
|
||||||
(let* ((start-a (plist-get ts-a :timestamp))
|
(let* ((start-a (plist-get tsp-a :unixtime))
|
||||||
(start-b (plist-get ts-b :timestamp))
|
(start-b (plist-get tsp-b :unixtime))
|
||||||
(end-a (+ start-a (plist-get ts-a :range))))
|
(end-a (-> tsp-a (plist-get :range) (+ start-a))))
|
||||||
(or (= start-a start-b) (< start-b end-a))))
|
(or (= start-a start-b) (< start-b end-a))))
|
||||||
|
|
||||||
(defun nd/find-conflict (ts ts-list conlist)
|
(defun nd/org-cluster-find-conflict (tsp tsps conlist)
|
||||||
(->> ts-list
|
"Test if timestamp-plist TSP conflicts with any in TSPS.
|
||||||
(--take-while (nd/org-conflict-conflicting-p ts it))
|
If found, anything in TSPS is cons'd with TSP and added to CONLIST
|
||||||
(--map (cons ts it))
|
as a pair. New CONLIST is returned."
|
||||||
|
(->> tsps
|
||||||
|
(--take-while (nd/org-cluster-conflicting-p tsp it))
|
||||||
|
(--map (cons tsp it))
|
||||||
(append conlist)))
|
(append conlist)))
|
||||||
|
|
||||||
(defun nd/org-conflict-build-conlist (ts-list)
|
(defun nd/org-cluster-build-conlist (tsps)
|
||||||
|
"Build a list of conflict pairs from timestamp-plist TSPS."
|
||||||
(let ((conlist))
|
(let ((conlist))
|
||||||
(while (< 1 (length ts-list))
|
(while (< 1 (length tsps))
|
||||||
(setq conlist (nd/find-conflict (car ts-list) (cdr ts-list) conlist)
|
(setq conlist (nd/org-cluster-find-conflict (car tsps)
|
||||||
ts-list (cdr ts-list)))
|
(cdr tsps)
|
||||||
|
conlist)
|
||||||
|
tsps (cdr tsps)))
|
||||||
conlist))
|
conlist))
|
||||||
|
|
||||||
(defun nd/org-conflict-split-day-bounds (tss)
|
(defun nd/org-cluster-get-conflicts ()
|
||||||
|
"Return a list of cons cells representing conflict pairs.
|
||||||
|
Each member in the cons cell is a timestamp-plist."
|
||||||
|
(->>
|
||||||
|
(nd/org-cluster-get-unprocessed)
|
||||||
|
(--filter (plist-get it :hardness))
|
||||||
|
(--sort (< (plist-get it :unixtime) (plist-get other :unixtime)))
|
||||||
|
nd/org-cluster-build-conlist))
|
||||||
|
#+END_SRC
|
||||||
|
**** overload detection
|
||||||
|
Overloads are defined as days that have more than 24 hours worth of scheduled material. The algorithm is O(n) as it is basically just a bunch of filtering functions that walk through the list.
|
||||||
|
|
||||||
|
Steps for the algorithm:
|
||||||
|
1. filter only ranged entries (unranged entries have zero time)
|
||||||
|
2. maybe split timestamps if they span multiple days
|
||||||
|
3. sort from earliest to latest starting time
|
||||||
|
4. sum the range of timestamps in each day, keeping those that exceed 24 hours
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(defun nd/org-cluster-split-day-bounds (tsps)
|
||||||
|
"Split timestamp-plists in TSPS via daily boundaries.
|
||||||
|
Returns a new timestamp-plist with equal or greater length depending
|
||||||
|
on how many members needed splitting."
|
||||||
(letrec
|
(letrec
|
||||||
((new
|
((new
|
||||||
(lambda (start end ts)
|
(lambda (start end tsp)
|
||||||
(nd/org-conflict-make-entry start
|
(nd/org-cluster-make-tsp start
|
||||||
(- end start)
|
(- end start)
|
||||||
(plist-get ts :offset)
|
(plist-get tsp :offset)
|
||||||
(plist-get ts :filepath)
|
(plist-get tsp :filepath)
|
||||||
(plist-get ts :hardness)
|
(plist-get tsp :hardness)
|
||||||
(plist-get ts :type))))
|
(plist-get tsp :type))))
|
||||||
;; need to temporarily offset the epoch time so day
|
;; need to temporarily offset the epoch time so day
|
||||||
;; boundaries line up in local time
|
;; boundaries line up in local time
|
||||||
(split
|
(split
|
||||||
(lambda (start end ts)
|
(lambda (start end tsp)
|
||||||
(let* ((tzs-a (-> start current-time-zone car))
|
(let* ((tzs-a (-> start current-time-zone car))
|
||||||
(tzs-b (-> end current-time-zone car))
|
(tzs-b (-> end current-time-zone car))
|
||||||
(start* (-> end (+ tzs-b) (ceiling 86400) 1- (* 86400) (- tzs-b))))
|
(start* (-> end (+ tzs-b) (ceiling 86400) 1- (* 86400) (- tzs-b))))
|
||||||
(if (> start* (-> start (+ tzs-a) (floor 86400) (* 86400) (- tzs-a)))
|
(if (> start* (-> start (+ tzs-a) (floor 86400) (* 86400) (- tzs-a)))
|
||||||
(cons (funcall new start* end ts)
|
(cons (funcall new start* end tsp)
|
||||||
(funcall split start start* ts))
|
(funcall split start start* tsp))
|
||||||
(list (funcall new start end ts))))))
|
(list (funcall new start end tsp))))))
|
||||||
(split-maybe
|
(split-maybe
|
||||||
(lambda (ts)
|
(lambda (tsp)
|
||||||
(let* ((start (plist-get ts :timestamp))
|
(let* ((start (plist-get tsp :unixtime))
|
||||||
(end (+ start (plist-get ts :range)))
|
(end (+ start (plist-get tsp :range)))
|
||||||
(tzs (-> start current-time-zone car)))
|
(tzs (-> start current-time-zone car)))
|
||||||
(if (< (-> start (+ tzs) (ceiling 86400)) end)
|
(if (< (-> start (+ tzs) (ceiling 86400)) end)
|
||||||
(funcall split start end ts)
|
(funcall split start end tsp)
|
||||||
ts)))))
|
tsp)))))
|
||||||
(--mapcat (funcall split-maybe it) tss)))
|
(--mapcat (funcall split-maybe it) tsps)))
|
||||||
|
|
||||||
(defun nd/org-overlist-overloaded-p (tss)
|
(defun nd/org-cluster-daily-split (tsps)
|
||||||
(letrec ((ts2diff
|
"Group timestamp-plist TSPS into sublists for each day."
|
||||||
(lambda (ts)
|
(letrec ((tz-shift (lambda (tsp) (-> tsp current-time-zone car (+ tsp)))))
|
||||||
(let ((start (plist-get ts :timestamp)))
|
|
||||||
(- (-> ts (plist-get :range) (+ start)) start)))))
|
|
||||||
(->> tss (--map (funcall ts2diff it)) -sum (<= 86400))))
|
|
||||||
|
|
||||||
(defun nd/org-overlist-daily-split (tss)
|
|
||||||
(letrec ((tz-shift (lambda (ts) (-> ts current-time-zone car (+ ts)))))
|
|
||||||
(->>
|
(->>
|
||||||
tss
|
tsps
|
||||||
(--partition-by (--> it
|
(--partition-by (--> it
|
||||||
(plist-get it :timestamp)
|
(plist-get it :unixtime)
|
||||||
(funcall tz-shift it)
|
(funcall tz-shift it)
|
||||||
(floor it 86400))))))
|
(floor it 86400))))))
|
||||||
|
|
||||||
;; TODO, this isn't DRY
|
(defun nd/org-cluster-overloaded-p (tsps)
|
||||||
(defun nd/org-conflict-get ()
|
"Return t if total time of timestamp-plists in TSPS exceeds 24 hours.
|
||||||
(->>
|
It is assumed the TSPS represents tasks and appointments within one
|
||||||
;; (list "~/Org/reference/testconflict.org")
|
day."
|
||||||
(org-agenda-files)
|
(letrec ((ts2diff
|
||||||
nd/org-conflict-filter-files
|
(lambda (tsp)
|
||||||
(nd/org-conflict-extract nil #'nd/org-conflict-extract-file)
|
(let ((start (plist-get tsp :unixtime)))
|
||||||
nd/org-conflict-filter-past
|
(- (-> tsp (plist-get :range) (+ start)) start)))))
|
||||||
(--filter (plist-get it :hardness))
|
(->> tsps (--map (funcall ts2diff it)) -sum (<= 86400))))
|
||||||
(--sort (< (plist-get it :timestamp) (plist-get other :timestamp)))
|
|
||||||
nd/org-conflict-build-conlist))
|
|
||||||
|
|
||||||
(defun nd/org-overlist-get ()
|
(defun nd/org-cluster-get-overloads ()
|
||||||
|
"Return list of lists of timestamp-plists grouped by day.
|
||||||
|
Anything present represents all the tasks in a single day if that day
|
||||||
|
is overloaded. If a day is not overloaded there will be nothing for it
|
||||||
|
in the returned list."
|
||||||
(->>
|
(->>
|
||||||
;; (list "~/Org/reference/testconflict.org")
|
(nd/org-cluster-get-unprocessed)
|
||||||
(org-agenda-files)
|
|
||||||
nd/org-conflict-filter-files
|
|
||||||
(nd/org-conflict-extract nil #'nd/org-conflict-extract-file)
|
|
||||||
nd/org-conflict-filter-past
|
|
||||||
(--filter (< 0 (plist-get it :range)))
|
(--filter (< 0 (plist-get it :range)))
|
||||||
nd/org-conflict-split-day-bounds
|
nd/org-cluster-split-day-bounds
|
||||||
(--sort (< (plist-get it :timestamp) (plist-get other :timestamp)))
|
(--sort (< (plist-get it :unixtime) (plist-get other :unixtime)))
|
||||||
nd/org-overlist-daily-split
|
nd/org-cluster-daily-split
|
||||||
(--filter (nd/org-overlist-overloaded-p it))))
|
(--filter (nd/org-cluster-overloaded-p it))))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
**** frontend
|
**** frontend
|
||||||
To display any conflicts, I could just fetch the org headings and throw them into a new buffer. But that's boring, and quite limiting. I basically want all the perks of an agenda buffer...tab-follow, the nice parent display at the bottom, time adjust hotkeys, etc. So the obvious and hacky solution is to throw together a quick-n-dirty agenda buffer which displays each conflict pair in sequentional fashion.
|
I could just fetch the org headings and throw them into a new buffer. But that's boring, and quite limiting. I basically want all the perks of an agenda buffer...tab-follow, the nice parent display at the bottom, time adjust hotkeys, etc. So the obvious and hacky solution is to throw together a quick-n-dirty agenda buffer.
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(defun nd/get-conflict-header-text (ts-entry)
|
(defun nd/org-cluster-headline-text (ts-entry)
|
||||||
"Return string with text properties representing the org header for
|
"Return string with text properties representing the org header for
|
||||||
MARKER for use in the conflict agenda view."
|
MARKER for use in the conflict agenda view."
|
||||||
(let* ((offset (plist-get ts-entry :offset))
|
(let* ((offset (plist-get ts-entry :offset))
|
||||||
|
@ -1721,12 +1807,12 @@ MARKER for use in the conflict agenda view."
|
||||||
'ts-date ts-date
|
'ts-date ts-date
|
||||||
'type "timestamp")))))
|
'type "timestamp")))))
|
||||||
|
|
||||||
(defun nd/org-conflct-ts-fmt (ts)
|
(defun nd/org-cluster-ts-fmt (ts)
|
||||||
(let ((fmt "[%Y-%m-%d]"))
|
(let ((fmt "[%Y-%m-%d]"))
|
||||||
(--> ts (plist-get it :timestamp) (format-time-string fmt it))))
|
(--> ts (plist-get it :unixtime) (format-time-string fmt it))))
|
||||||
|
|
||||||
;; TODO...waaaaay too wet (not DRY)
|
;; TODO...waaaaay too wet (not DRY)
|
||||||
(defun nd/org-conflicts (&optional arg)
|
(defun nd/org-cluster-show-conflicts (&optional arg)
|
||||||
(interactive "P")
|
(interactive "P")
|
||||||
|
|
||||||
(if org-agenda-overriding-arguments
|
(if org-agenda-overriding-arguments
|
||||||
|
@ -1748,7 +1834,7 @@ MARKER for use in the conflict agenda view."
|
||||||
(org-compile-prefix-format 'agenda)
|
(org-compile-prefix-format 'agenda)
|
||||||
;; (org-set-sorting-strategy 'todo)
|
;; (org-set-sorting-strategy 'todo)
|
||||||
|
|
||||||
(setq org-agenda-redo-command '(nd/org-conflicts))
|
(setq org-agenda-redo-command '(nd/org-cluster-show-conflicts))
|
||||||
|
|
||||||
(insert "Conflicting Headings: \n")
|
(insert "Conflicting Headings: \n")
|
||||||
(add-text-properties (point-min) (1- (point))
|
(add-text-properties (point-min) (1- (point))
|
||||||
|
@ -1757,12 +1843,12 @@ MARKER for use in the conflict agenda view."
|
||||||
(org-agenda-mark-header-line (point-min))
|
(org-agenda-mark-header-line (point-min))
|
||||||
|
|
||||||
(-some->
|
(-some->
|
||||||
(nd/org-conflict-get)
|
(nd/org-cluster-get-conflicts)
|
||||||
(--each
|
(--each
|
||||||
(insert (concat
|
(insert (concat
|
||||||
"At " (nd/org-conflct-ts-fmt (car it)) "\n"
|
"At " (nd/org-cluster-ts-fmt (car it)) "\n"
|
||||||
(nd/get-conflict-header-text (car it)) "\n"
|
(nd/org-cluster-headline-text (car it)) "\n"
|
||||||
(nd/get-conflict-header-text (cdr it)) "\n"
|
(nd/org-cluster-headline-text (cdr it)) "\n"
|
||||||
"\n"))))
|
"\n"))))
|
||||||
|
|
||||||
;; clean up and finalize
|
;; clean up and finalize
|
||||||
|
@ -1777,7 +1863,7 @@ MARKER for use in the conflict agenda view."
|
||||||
(org-agenda-finalize)
|
(org-agenda-finalize)
|
||||||
(setq buffer-read-only t))))
|
(setq buffer-read-only t))))
|
||||||
|
|
||||||
(defun nd/org-overloaded (&optional arg)
|
(defun nd/org-cluster-show-overloads (&optional arg)
|
||||||
(interactive "P")
|
(interactive "P")
|
||||||
|
|
||||||
(if org-agenda-overriding-arguments
|
(if org-agenda-overriding-arguments
|
||||||
|
@ -1799,7 +1885,7 @@ MARKER for use in the conflict agenda view."
|
||||||
(org-compile-prefix-format 'agenda)
|
(org-compile-prefix-format 'agenda)
|
||||||
;; (org-set-sorting-strategy 'todo)
|
;; (org-set-sorting-strategy 'todo)
|
||||||
|
|
||||||
(setq org-agenda-redo-command '(nd/org-overloaded))
|
(setq org-agenda-redo-command '(nd/org-cluster-show-overloads))
|
||||||
|
|
||||||
(insert "Overloaded Days: \n")
|
(insert "Overloaded Days: \n")
|
||||||
(add-text-properties (point-min) (1- (point))
|
(add-text-properties (point-min) (1- (point))
|
||||||
|
@ -1808,11 +1894,11 @@ MARKER for use in the conflict agenda view."
|
||||||
(org-agenda-mark-header-line (point-min))
|
(org-agenda-mark-header-line (point-min))
|
||||||
|
|
||||||
(-some->
|
(-some->
|
||||||
(nd/org-overlist-get)
|
(nd/org-cluster-get-overloads)
|
||||||
(--each
|
(--each
|
||||||
(insert (concat
|
(insert (concat
|
||||||
"On " (nd/org-conflct-ts-fmt (car it)) "\n"
|
"On " (nd/org-cluster-ts-fmt (car it)) "\n"
|
||||||
(mapconcat #'nd/get-conflict-header-text it "\n")
|
(mapconcat #'nd/org-cluster-headline-text it "\n")
|
||||||
"\n"))))
|
"\n"))))
|
||||||
|
|
||||||
;; clean up and finalize
|
;; clean up and finalize
|
||||||
|
@ -1827,22 +1913,6 @@ MARKER for use in the conflict agenda view."
|
||||||
(org-agenda-finalize)
|
(org-agenda-finalize)
|
||||||
(setq buffer-read-only t))))
|
(setq buffer-read-only t))))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
**** filters
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(defvar nd/org-conficts-filter-files t)
|
|
||||||
|
|
||||||
(defconst nd/org-conficts-filtered-files
|
|
||||||
'("incubator" "peripheral"))
|
|
||||||
|
|
||||||
(defvar nd/org-conficts-filter-todo t)
|
|
||||||
|
|
||||||
(defconst nd/org-conficts-filtered-todo
|
|
||||||
'("CANC" "DONE"))
|
|
||||||
|
|
||||||
(defvar nd/org-conficts-filter-past t)
|
|
||||||
|
|
||||||
(defvar nd/org-conficts-filter-habit t)
|
|
||||||
#+END_SRC
|
|
||||||
*** agenda
|
*** agenda
|
||||||
**** targets
|
**** targets
|
||||||
The agenda files are limited to as few as possible to keep scanning and startup reasonably fast.
|
The agenda files are limited to as few as possible to keep scanning and startup reasonably fast.
|
||||||
|
@ -3606,6 +3676,8 @@ The function keys are nice because they are almost (not always) free in every mo
|
||||||
(global-set-key (kbd "<f1>") 'org-agenda)
|
(global-set-key (kbd "<f1>") 'org-agenda)
|
||||||
(global-set-key (kbd "<f2>") 'org-capture)
|
(global-set-key (kbd "<f2>") 'org-capture)
|
||||||
(global-set-key (kbd "<f3>") 'cfw:open-org-calendar)
|
(global-set-key (kbd "<f3>") 'cfw:open-org-calendar)
|
||||||
|
(global-set-key (kbd "C-<f3>") 'nd/org-cluster-show-conflicts)
|
||||||
|
(global-set-key (kbd "C-S-<f3>") 'nd/org-cluster-show-overloads)
|
||||||
(global-set-key (kbd "<f4>") 'org-clock-goto)
|
(global-set-key (kbd "<f4>") 'org-clock-goto)
|
||||||
(global-set-key (kbd "<f5>") 'ansi-term)
|
(global-set-key (kbd "<f5>") 'ansi-term)
|
||||||
(global-set-key (kbd "<f8>") 'helm-command-prefix)
|
(global-set-key (kbd "<f8>") 'helm-command-prefix)
|
||||||
|
|
Loading…
Reference in New Issue