added error detection...untested
This commit is contained in:
parent
241197b670
commit
518a18fb40
240
conf.el
240
conf.el
|
@ -317,27 +317,93 @@
|
||||||
(setq org-agenda-dim-blocked-tasks nil)
|
(setq org-agenda-dim-blocked-tasks nil)
|
||||||
(setq org-agenda-compact-blocks t)
|
(setq org-agenda-compact-blocks t)
|
||||||
|
|
||||||
(evil-define-key 'motion org-agenda-mode-map "T" 'nd/toggle-project-toplevel-display)
|
(setq org-agenda-tags-todo-honor-ignore-options t)
|
||||||
|
(setq org-agenda-custom-commands
|
||||||
|
`(("t"
|
||||||
|
"Task view"
|
||||||
|
((agenda "" nil)
|
||||||
|
,(macroexpand '(nd/agenda-base-task-command "Next Project" 'nd/skip-non-next-project-tasks))
|
||||||
|
,(macroexpand '(nd/agenda-base-task-command "Waiting Project" 'nd/skip-non-waiting-project-tasks))
|
||||||
|
,(macroexpand '(nd/agenda-base-task-command "Atomic" 'nd/skip-non-atomic-tasks))
|
||||||
|
,(macroexpand '(nd/agenda-base-task-command "Held Project" 'nd/skip-non-held-project-tasks))))
|
||||||
|
("o"
|
||||||
|
"Project Overview"
|
||||||
|
(,(macroexpand '(nd/agenda-base-project-command "Stuck" 10))
|
||||||
|
,(macroexpand '(nd/agenda-base-project-command "Waiting" 20))
|
||||||
|
,(macroexpand '(nd/agenda-base-project-command "Active" 40))
|
||||||
|
,(macroexpand '(nd/agenda-base-project-command "Held" 30))))
|
||||||
|
("r"
|
||||||
|
"Refile and errors"
|
||||||
|
;; TODO add error detection here
|
||||||
|
((tags "REFILE" ((org-agenda-overriding-header "Tasks to Refile")) (org-tags-match-list-sublevels nil))
|
||||||
|
,(macroexpand '(nd/agenda-base-task-command "Discontinous Project" 'nd/skip-non-discontinuous-project-tasks))
|
||||||
|
,(macroexpand '(nd/agenda-base-project-command "Invalid" 50))))))
|
||||||
|
|
||||||
(setq org-agenda-span 'day)
|
(defvar nd/agenda-limit-project-toplevel t
|
||||||
(setq org-agenda-time-grid (quote ((daily today remove-match)
|
"used to filter projects by all levels or top-level only")
|
||||||
#("----------------" 0 16 (org-heading t))
|
|
||||||
(0900 1100 1300 1500 1700))))
|
|
||||||
|
|
||||||
(add-hook 'org-finalize-agenda-hook 'place-agenda-tags)
|
(defun nd/toggle-project-toplevel-display ()
|
||||||
(defun place-agenda-tags ()
|
(interactive)
|
||||||
"Put the agenda tags by the right border of the agenda window."
|
(setq nd/agenda-limit-project-toplevel (not nd/agenda-limit-project-toplevel))
|
||||||
(setq org-agenda-tags-column (- 4 (window-width)))
|
(when (equal major-mode 'org-agenda-mode)
|
||||||
(org-agenda-align-tags))
|
(org-agenda-redo))
|
||||||
|
(message "Showing %s project view in agenda" (if nd/agenda-limit-project-toplevel "toplevel" "complete")))
|
||||||
|
|
||||||
(defun nd/org-auto-exclude-function (tag)
|
(defmacro nd/agenda-base-task-command (keyword skip-fun)
|
||||||
"Automatic task exclusion in the agenda with / RET"
|
"shorter syntax to define task agenda commands"
|
||||||
(and (cond
|
`(tags-todo
|
||||||
((string= tag "hold")
|
"-NA-REFILE/!"
|
||||||
t))
|
((org-agenda-overriding-header (concat ,keyword " Tasks"))
|
||||||
(concat "-" tag)))
|
(org-agenda-skip-function ,skip-fun)
|
||||||
|
(org-agenda-todo-ignore-with-date 'all)
|
||||||
|
(org-agenda-sorting-strategy '(category-keep)))))
|
||||||
|
|
||||||
(setq org-agenda-auto-exclude-function 'nd/org-auto-exclude-function)
|
(defmacro nd/agenda-base-project-command (keyword statuscode)
|
||||||
|
"shorter syntax to define project agenda commands"
|
||||||
|
`(tags-todo
|
||||||
|
"-NA-REFILE-ATOMIC/!"
|
||||||
|
((org-agenda-overriding-header (concat
|
||||||
|
(and nd/agenda-limit-project-toplevel "Toplevel ")
|
||||||
|
,keyword
|
||||||
|
" Projects"))
|
||||||
|
(org-agenda-skip-function (if nd/agenda-limit-project-toplevel
|
||||||
|
'(nd/skip-subprojects-without-statuscode ,statuscode)
|
||||||
|
'(nd/skip-projects-without-statuscode ,statuscode)))
|
||||||
|
(org-agenda-sorting-strategy '(category-keep)))))
|
||||||
|
|
||||||
|
;; NOTE: use save-restriction and widen if we ever actually use narrowing
|
||||||
|
;; tasks
|
||||||
|
(defun nd/skip-non-atomic-tasks ()
|
||||||
|
(if (not (nd/is-atomic-task-p))
|
||||||
|
(save-excursion (or (outline-next-heading) (point-max)))))
|
||||||
|
|
||||||
|
(defun nd/skip-non-next-project-tasks ()
|
||||||
|
(if (not (equal (nd/is-project-task-p) "NEXT"))
|
||||||
|
(save-excursion (or (outline-next-heading) (point-max)))))
|
||||||
|
|
||||||
|
(defun nd/skip-non-waiting-project-tasks ()
|
||||||
|
(if (not (equal (nd/is-project-task-p) "WAITING"))
|
||||||
|
(save-excursion (or (outline-next-heading) (point-max)))))
|
||||||
|
|
||||||
|
(defun nd/skip-non-held-project-tasks ()
|
||||||
|
(if (not (equal (nd/is-project-task-p) "HOLD"))
|
||||||
|
(save-excursion (or (outline-next-heading) (point-max)))))
|
||||||
|
|
||||||
|
(defun nd/skip-non-discontinous-project-tasks ()
|
||||||
|
(if (not (nd/is-discontinous-project-task-p))
|
||||||
|
(save-excursion (or (outline-next-heading) (point-max)))))
|
||||||
|
|
||||||
|
;; projects
|
||||||
|
;; TODO skip entire subtree if we don't need to evaluate anything inside
|
||||||
|
;; otherwise (for example) a held project will still have it's subtasks show up
|
||||||
|
(defun nd/skip-projects-without-statuscode (statuscode)
|
||||||
|
(if (not (nd/is-project-status-p statuscode))
|
||||||
|
(save-excursion (or (outline-next-heading) (point-max)))))
|
||||||
|
|
||||||
|
;; top-level projects
|
||||||
|
(defun nd/skip-subprojects-without-statuscode (statuscode)
|
||||||
|
(if (or (nd/heading-has-parent) (not (nd/is-project-status-p statuscode)))
|
||||||
|
(save-excursion (or (outline-next-heading) (point-max)))))
|
||||||
|
|
||||||
(defun nd/is-todoitem-p ()
|
(defun nd/is-todoitem-p ()
|
||||||
"return todo keyword if present in headline (which defines the heading as a todoitem)
|
"return todo keyword if present in headline (which defines the heading as a todoitem)
|
||||||
|
@ -376,10 +442,6 @@ this is used to both test if a heading is a todoitem and retrieving the keyword"
|
||||||
"return keyword if task is WAITING"
|
"return keyword if task is WAITING"
|
||||||
(equal (nd/is-task-p) "WAITING"))
|
(equal (nd/is-task-p) "WAITING"))
|
||||||
|
|
||||||
(defconst nd/project-invalid-todostates
|
|
||||||
'("WAITING" "NEXT")
|
|
||||||
"projects cannot have these todostates")
|
|
||||||
|
|
||||||
(defun nd/heading-has-children ()
|
(defun nd/heading-has-children ()
|
||||||
"returns t if heading has todoitems in its immediate subtree"
|
"returns t if heading has todoitems in its immediate subtree"
|
||||||
;; TODO make this more efficient (and accurate) by only testing
|
;; TODO make this more efficient (and accurate) by only testing
|
||||||
|
@ -400,6 +462,19 @@ this is used to both test if a heading is a todoitem and retrieving the keyword"
|
||||||
"returns parent keyword if heading is in the immediate subtree of a todoitem"
|
"returns parent keyword if heading is in the immediate subtree of a todoitem"
|
||||||
(save-excursion (and (org-up-heading-safe) (nd/is-todoitem-p))))
|
(save-excursion (and (org-up-heading-safe) (nd/is-todoitem-p))))
|
||||||
|
|
||||||
|
(defun nd/is-discontinuous-project-task-p ()
|
||||||
|
"detects todoitems that are children of non-todoitems
|
||||||
|
that in turn are children of todoitems (discontinous project)"
|
||||||
|
(let ((has-todoitem-parent)
|
||||||
|
(has-non-todoitem-parent))
|
||||||
|
(save-excursion
|
||||||
|
(while (and (org-up-heading-safe)
|
||||||
|
has-todoitem-parent)
|
||||||
|
(if (nd/is-todoitem-p)
|
||||||
|
(setq has-todoitem-parent t)
|
||||||
|
(setq has-non-todoitem-parent t))))
|
||||||
|
(and has-todoitem-parent has-non-todoitem-parent)))
|
||||||
|
|
||||||
(defun nd/test-first-order-project ()
|
(defun nd/test-first-order-project ()
|
||||||
"tests the state of a project assuming first order.
|
"tests the state of a project assuming first order.
|
||||||
if not first order, this function will iterate to the next project
|
if not first order, this function will iterate to the next project
|
||||||
|
@ -420,6 +495,10 @@ function is not meant to be called independently."
|
||||||
(org-forward-heading-same-level 1 t)))
|
(org-forward-heading-same-level 1 t)))
|
||||||
found-active))
|
found-active))
|
||||||
|
|
||||||
|
(defconst nd/project-invalid-todostates
|
||||||
|
'("WAITING" "NEXT")
|
||||||
|
"projects cannot have these todostates")
|
||||||
|
|
||||||
;; project level testing
|
;; project level testing
|
||||||
;; TODO: is there a better way to handle statuscodes like this??? (array like thingy)
|
;; TODO: is there a better way to handle statuscodes like this??? (array like thingy)
|
||||||
(defun nd/descend-into-project ()
|
(defun nd/descend-into-project ()
|
||||||
|
@ -436,7 +515,9 @@ This function works on an assumed order of precendence:
|
||||||
- if project has any TODO (regardless of DONE or CANCELLED) it is stuck
|
- if project has any TODO (regardless of DONE or CANCELLED) it is stuck
|
||||||
- if project has any HOLD (regardless of DONE, CANCELLED, or TODO) it is held
|
- if project has any HOLD (regardless of DONE, CANCELLED, or TODO) it is held
|
||||||
- in the same manner WAITING means waiting project
|
- in the same manner WAITING means waiting project
|
||||||
- in the same manner, NEXT means active. NEXT overrides all
|
- in the same manner, NEXT or scheduled means active.
|
||||||
|
- can also detect errors which override all
|
||||||
|
- anything higher than active breaks the recursion/tree walk
|
||||||
|
|
||||||
Using this scheme, we simply compare the magnitude of the statuscodes"
|
Using this scheme, we simply compare the magnitude of the statuscodes"
|
||||||
(let ((project-state 0)
|
(let ((project-state 0)
|
||||||
|
@ -451,15 +532,23 @@ Using this scheme, we simply compare the magnitude of the statuscodes"
|
||||||
(if keyword
|
(if keyword
|
||||||
(let ((cur-state
|
(let ((cur-state
|
||||||
(if has-children
|
(if has-children
|
||||||
(cond ((equal keyword "HOLD") 20)
|
(cond ((member keyword nd/project-invalid-todostates) 50)
|
||||||
((equal keyword "TODO") (nd/descend-into-project))
|
((nd/is-scheduled-heading-p) 50)
|
||||||
;; NOTE: all projects are assumed to only have TODO, HOLD, CANCELLED, or DONE, hence the three possible statuscodes
|
;; cancelled and hold work independent of everything underneath
|
||||||
(t 0))
|
((equal keyword "CANCELLED") 0)
|
||||||
|
((equal keyword "HOLD") 20)
|
||||||
|
;; all other tests require a descent into the child project hence let form
|
||||||
|
(t (let ((child-statuscode (nd/descend-into-project)))
|
||||||
|
;; projects marked TODO should not be complete
|
||||||
|
(cond ((equal keyword "TODO") (if (> child-statuscode 0) child-statuscode 50))
|
||||||
|
;; projects marked DONE should have all subtasks/projects marked DONE/CANCELLED
|
||||||
|
(t (if (= child-statuscode 0) 0 50))))))
|
||||||
(cond ((equal keyword "HOLD") 20)
|
(cond ((equal keyword "HOLD") 20)
|
||||||
((equal keyword "WAITING") 30)
|
((equal keyword "WAITING") 30)
|
||||||
((equal keyword "NEXT") 40)
|
((equal keyword "NEXT") 40)
|
||||||
((and (equal keyword "TODO") (nd/is-scheduled-heading-p)) 40)
|
((and (equal keyword "TODO") (nd/is-scheduled-heading-p)) 40)
|
||||||
((equal keyword "TODO") 10)
|
((equal keyword "TODO") 10)
|
||||||
|
;; catchall means CANCELLED or DONE (complete)
|
||||||
(t 0)))))
|
(t 0)))))
|
||||||
(if (> cur-state project-state)
|
(if (> cur-state project-state)
|
||||||
(setq project-state cur-state)))))
|
(setq project-state cur-state)))))
|
||||||
|
@ -470,88 +559,33 @@ Using this scheme, we simply compare the magnitude of the statuscodes"
|
||||||
(defun nd/is-project-status-p (statuscode)
|
(defun nd/is-project-status-p (statuscode)
|
||||||
(let ((keyword (nd/is-project-p)))
|
(let ((keyword (nd/is-project-p)))
|
||||||
(if keyword
|
(if keyword
|
||||||
(cond ((member keyword nd/project-invalid-todostates) nil)
|
(if (member keyword nd/project-invalid-todostates)
|
||||||
((and (equal keyword "HOLD") (= statuscode 20)) keyword)
|
(if (= statuscode 50) keyword)
|
||||||
((and (equal keyword "HOLD") (/= statuscode 20)) nil)
|
(if (equal keyword "HOLD")
|
||||||
((= statuscode (nd/descend-into-project)) keyword)))))
|
(if (= statuscode 20) keyword)
|
||||||
|
(if (= statuscode (nd/descend-into-project)) keyword))))))
|
||||||
|
|
||||||
;; NOTE: use save-restriction and widen if we ever actually use narrowing
|
(evil-define-key 'motion org-agenda-mode-map "T" 'nd/toggle-project-toplevel-display)
|
||||||
;; tasks
|
|
||||||
(defun nd/skip-non-atomic-tasks ()
|
|
||||||
(if (not (nd/is-atomic-task-p))
|
|
||||||
(save-excursion (or (outline-next-heading) (point-max)))))
|
|
||||||
|
|
||||||
(defun nd/skip-non-next-project-tasks ()
|
(setq org-agenda-span 'day)
|
||||||
(if (not (equal (nd/is-project-task-p) "NEXT"))
|
(setq org-agenda-time-grid (quote ((daily today remove-match)
|
||||||
(save-excursion (or (outline-next-heading) (point-max)))))
|
#("----------------" 0 16 (org-heading t))
|
||||||
|
(0900 1100 1300 1500 1700))))
|
||||||
|
|
||||||
(defun nd/skip-non-waiting-project-tasks ()
|
(add-hook 'org-finalize-agenda-hook 'place-agenda-tags)
|
||||||
(if (not (equal (nd/is-project-task-p) "WAITING"))
|
(defun place-agenda-tags ()
|
||||||
(save-excursion (or (outline-next-heading) (point-max)))))
|
"Put the agenda tags by the right border of the agenda window."
|
||||||
|
(setq org-agenda-tags-column (- 4 (window-width)))
|
||||||
|
(org-agenda-align-tags))
|
||||||
|
|
||||||
(defun nd/skip-non-held-project-tasks ()
|
(defun nd/org-auto-exclude-function (tag)
|
||||||
(if (not (equal (nd/is-project-task-p) "HOLD"))
|
"Automatic task exclusion in the agenda with / RET"
|
||||||
(save-excursion (or (outline-next-heading) (point-max)))))
|
(and (cond
|
||||||
|
((string= tag "hold")
|
||||||
|
t))
|
||||||
|
(concat "-" tag)))
|
||||||
|
|
||||||
;; projects
|
(setq org-agenda-auto-exclude-function 'nd/org-auto-exclude-function)
|
||||||
(defun nd/skip-projects-without-statuscode (statuscode)
|
|
||||||
(if (not (nd/is-project-status-p statuscode))
|
|
||||||
(save-excursion (or (outline-next-heading) (point-max)))))
|
|
||||||
|
|
||||||
;; top-level projects
|
|
||||||
(defun nd/skip-subprojects-without-statuscode (statuscode)
|
|
||||||
(if (or (nd/heading-has-parent) (not (nd/is-project-status-p statuscode)))
|
|
||||||
(save-excursion (or (outline-next-heading) (point-max)))))
|
|
||||||
|
|
||||||
(defvar nd/agenda-limit-project-toplevel t
|
|
||||||
"used to filter projects by all levels or top-level only")
|
|
||||||
|
|
||||||
(defun nd/toggle-project-toplevel-display ()
|
|
||||||
(interactive)
|
|
||||||
(setq nd/agenda-limit-project-toplevel (not nd/agenda-limit-project-toplevel))
|
|
||||||
(when (equal major-mode 'org-agenda-mode)
|
|
||||||
(org-agenda-redo))
|
|
||||||
(message "Showing %s project view in agenda" (if nd/agenda-limit-project-toplevel "toplevel" "complete")))
|
|
||||||
|
|
||||||
(defmacro nd/agenda-base-task-command (keyword skip-fun)
|
|
||||||
"shorter syntax to define task agenda commands"
|
|
||||||
`(tags-todo
|
|
||||||
"-NA-REFILE/!"
|
|
||||||
((org-agenda-overriding-header (concat ,keyword " Tasks"))
|
|
||||||
(org-agenda-skip-function ,skip-fun)
|
|
||||||
(org-agenda-todo-ignore-with-date 'all)
|
|
||||||
(org-agenda-sorting-strategy '(category-keep)))))
|
|
||||||
|
|
||||||
(defmacro nd/agenda-base-project-command (keyword statuscode)
|
|
||||||
"shorter syntax to define project agenda commands"
|
|
||||||
`(tags-todo
|
|
||||||
"-NA-REFILE-ATOMIC/!"
|
|
||||||
((org-agenda-overriding-header (concat
|
|
||||||
(and nd/agenda-limit-project-toplevel "Toplevel ")
|
|
||||||
,keyword
|
|
||||||
" Projects"))
|
|
||||||
(org-agenda-skip-function (if nd/agenda-limit-project-toplevel
|
|
||||||
'(nd/skip-subprojects-without-statuscode ,statuscode)
|
|
||||||
'(nd/skip-projects-without-statuscode ,statuscode)))
|
|
||||||
(org-agenda-sorting-strategy '(category-keep)))))
|
|
||||||
|
|
||||||
(setq org-agenda-tags-todo-honor-ignore-options t)
|
|
||||||
(setq org-agenda-custom-commands
|
|
||||||
`(("t" "Task view"
|
|
||||||
((agenda "" nil)
|
|
||||||
,(macroexpand '(nd/agenda-base-task-command "Next Project" 'nd/skip-non-next-project-tasks))
|
|
||||||
,(macroexpand '(nd/agenda-base-task-command "Waiting Project" 'nd/skip-non-waiting-project-tasks))
|
|
||||||
,(macroexpand '(nd/agenda-base-task-command "Atomic" 'nd/skip-non-atomic-tasks))
|
|
||||||
,(macroexpand '(nd/agenda-base-task-command "Held Project" 'nd/skip-non-held-project-tasks))))
|
|
||||||
("o" "Project Overview"
|
|
||||||
(,(macroexpand '(nd/agenda-base-project-command "Stuck" 10))
|
|
||||||
,(macroexpand '(nd/agenda-base-project-command "Waiting" 20))
|
|
||||||
,(macroexpand '(nd/agenda-base-project-command "Active" 40))
|
|
||||||
,(macroexpand '(nd/agenda-base-project-command "Held" 30))))
|
|
||||||
("r" "Refile and errors"
|
|
||||||
((tags "REFILE"
|
|
||||||
((org-agenda-overriding-header "Tasks to Refile"))
|
|
||||||
(org-tags-match-list-sublevels nil))))))
|
|
||||||
|
|
||||||
(use-package org-bullets
|
(use-package org-bullets
|
||||||
:ensure t
|
:ensure t
|
||||||
|
|
275
conf.org
275
conf.org
|
@ -480,37 +480,102 @@ I use tags for filtering in the agenda view to narrow down tasks by project/cont
|
||||||
(setq org-agenda-dim-blocked-tasks nil)
|
(setq org-agenda-dim-blocked-tasks nil)
|
||||||
(setq org-agenda-compact-blocks t)
|
(setq org-agenda-compact-blocks t)
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
*** keymap
|
*** custom commands
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(evil-define-key 'motion org-agenda-mode-map "T" 'nd/toggle-project-toplevel-display)
|
(setq org-agenda-tags-todo-honor-ignore-options t)
|
||||||
|
(setq org-agenda-custom-commands
|
||||||
|
`(("t"
|
||||||
|
"Task view"
|
||||||
|
((agenda "" nil)
|
||||||
|
,(macroexpand '(nd/agenda-base-task-command "Next Project" 'nd/skip-non-next-project-tasks))
|
||||||
|
,(macroexpand '(nd/agenda-base-task-command "Waiting Project" 'nd/skip-non-waiting-project-tasks))
|
||||||
|
,(macroexpand '(nd/agenda-base-task-command "Atomic" 'nd/skip-non-atomic-tasks))
|
||||||
|
,(macroexpand '(nd/agenda-base-task-command "Held Project" 'nd/skip-non-held-project-tasks))))
|
||||||
|
("o"
|
||||||
|
"Project Overview"
|
||||||
|
(,(macroexpand '(nd/agenda-base-project-command "Stuck" 10))
|
||||||
|
,(macroexpand '(nd/agenda-base-project-command "Waiting" 20))
|
||||||
|
,(macroexpand '(nd/agenda-base-project-command "Active" 40))
|
||||||
|
,(macroexpand '(nd/agenda-base-project-command "Held" 30))))
|
||||||
|
("r"
|
||||||
|
"Refile and errors"
|
||||||
|
;; TODO add error detection here
|
||||||
|
((tags "REFILE" ((org-agenda-overriding-header "Tasks to Refile")) (org-tags-match-list-sublevels nil))
|
||||||
|
,(macroexpand '(nd/agenda-base-task-command "Discontinous Project" 'nd/skip-non-discontinuous-project-tasks))
|
||||||
|
,(macroexpand '(nd/agenda-base-project-command "Invalid" 50))))))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
*** views
|
*** interactive view functions
|
||||||
**** calendar display
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(setq org-agenda-span 'day)
|
(defvar nd/agenda-limit-project-toplevel t
|
||||||
(setq org-agenda-time-grid (quote ((daily today remove-match)
|
"used to filter projects by all levels or top-level only")
|
||||||
#("----------------" 0 16 (org-heading t))
|
|
||||||
(0900 1100 1300 1500 1700))))
|
|
||||||
#+End_src
|
|
||||||
**** right align tags
|
|
||||||
the agenda does not do this by default...it's annoying
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(add-hook 'org-finalize-agenda-hook 'place-agenda-tags)
|
|
||||||
(defun place-agenda-tags ()
|
|
||||||
"Put the agenda tags by the right border of the agenda window."
|
|
||||||
(setq org-agenda-tags-column (- 4 (window-width)))
|
|
||||||
(org-agenda-align-tags))
|
|
||||||
#+END_SRC
|
|
||||||
*** auto exclusion
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(defun nd/org-auto-exclude-function (tag)
|
|
||||||
"Automatic task exclusion in the agenda with / RET"
|
|
||||||
(and (cond
|
|
||||||
((string= tag "hold")
|
|
||||||
t))
|
|
||||||
(concat "-" tag)))
|
|
||||||
|
|
||||||
(setq org-agenda-auto-exclude-function 'nd/org-auto-exclude-function)
|
(defun nd/toggle-project-toplevel-display ()
|
||||||
|
(interactive)
|
||||||
|
(setq nd/agenda-limit-project-toplevel (not nd/agenda-limit-project-toplevel))
|
||||||
|
(when (equal major-mode 'org-agenda-mode)
|
||||||
|
(org-agenda-redo))
|
||||||
|
(message "Showing %s project view in agenda" (if nd/agenda-limit-project-toplevel "toplevel" "complete")))
|
||||||
|
|
||||||
|
(defmacro nd/agenda-base-task-command (keyword skip-fun)
|
||||||
|
"shorter syntax to define task agenda commands"
|
||||||
|
`(tags-todo
|
||||||
|
"-NA-REFILE/!"
|
||||||
|
((org-agenda-overriding-header (concat ,keyword " Tasks"))
|
||||||
|
(org-agenda-skip-function ,skip-fun)
|
||||||
|
(org-agenda-todo-ignore-with-date 'all)
|
||||||
|
(org-agenda-sorting-strategy '(category-keep)))))
|
||||||
|
|
||||||
|
(defmacro nd/agenda-base-project-command (keyword statuscode)
|
||||||
|
"shorter syntax to define project agenda commands"
|
||||||
|
`(tags-todo
|
||||||
|
"-NA-REFILE-ATOMIC/!"
|
||||||
|
((org-agenda-overriding-header (concat
|
||||||
|
(and nd/agenda-limit-project-toplevel "Toplevel ")
|
||||||
|
,keyword
|
||||||
|
" Projects"))
|
||||||
|
(org-agenda-skip-function (if nd/agenda-limit-project-toplevel
|
||||||
|
'(nd/skip-subprojects-without-statuscode ,statuscode)
|
||||||
|
'(nd/skip-projects-without-statuscode ,statuscode)))
|
||||||
|
(org-agenda-sorting-strategy '(category-keep)))))
|
||||||
|
|
||||||
|
#+END_SRC
|
||||||
|
*** skip functions
|
||||||
|
These are the primary means we use to sort through tasks. Note that we could do this with
|
||||||
|
tags in the custom commands section but I find this easier to maintain and possibly faster.
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
;; NOTE: use save-restriction and widen if we ever actually use narrowing
|
||||||
|
;; tasks
|
||||||
|
(defun nd/skip-non-atomic-tasks ()
|
||||||
|
(if (not (nd/is-atomic-task-p))
|
||||||
|
(save-excursion (or (outline-next-heading) (point-max)))))
|
||||||
|
|
||||||
|
(defun nd/skip-non-next-project-tasks ()
|
||||||
|
(if (not (equal (nd/is-project-task-p) "NEXT"))
|
||||||
|
(save-excursion (or (outline-next-heading) (point-max)))))
|
||||||
|
|
||||||
|
(defun nd/skip-non-waiting-project-tasks ()
|
||||||
|
(if (not (equal (nd/is-project-task-p) "WAITING"))
|
||||||
|
(save-excursion (or (outline-next-heading) (point-max)))))
|
||||||
|
|
||||||
|
(defun nd/skip-non-held-project-tasks ()
|
||||||
|
(if (not (equal (nd/is-project-task-p) "HOLD"))
|
||||||
|
(save-excursion (or (outline-next-heading) (point-max)))))
|
||||||
|
|
||||||
|
(defun nd/skip-non-discontinous-project-tasks ()
|
||||||
|
(if (not (nd/is-discontinous-project-task-p))
|
||||||
|
(save-excursion (or (outline-next-heading) (point-max)))))
|
||||||
|
|
||||||
|
;; projects
|
||||||
|
;; TODO skip entire subtree if we don't need to evaluate anything inside
|
||||||
|
;; otherwise (for example) a held project will still have it's subtasks show up
|
||||||
|
(defun nd/skip-projects-without-statuscode (statuscode)
|
||||||
|
(if (not (nd/is-project-status-p statuscode))
|
||||||
|
(save-excursion (or (outline-next-heading) (point-max)))))
|
||||||
|
|
||||||
|
;; top-level projects
|
||||||
|
(defun nd/skip-subprojects-without-statuscode (statuscode)
|
||||||
|
(if (or (nd/heading-has-parent) (not (nd/is-project-status-p statuscode)))
|
||||||
|
(save-excursion (or (outline-next-heading) (point-max)))))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
*** task helper functions
|
*** task helper functions
|
||||||
These are the building blocks for skip functions.
|
These are the building blocks for skip functions.
|
||||||
|
@ -552,10 +617,6 @@ These are the building blocks for skip functions.
|
||||||
"return keyword if task is WAITING"
|
"return keyword if task is WAITING"
|
||||||
(equal (nd/is-task-p) "WAITING"))
|
(equal (nd/is-task-p) "WAITING"))
|
||||||
|
|
||||||
(defconst nd/project-invalid-todostates
|
|
||||||
'("WAITING" "NEXT")
|
|
||||||
"projects cannot have these todostates")
|
|
||||||
|
|
||||||
(defun nd/heading-has-children ()
|
(defun nd/heading-has-children ()
|
||||||
"returns t if heading has todoitems in its immediate subtree"
|
"returns t if heading has todoitems in its immediate subtree"
|
||||||
;; TODO make this more efficient (and accurate) by only testing
|
;; TODO make this more efficient (and accurate) by only testing
|
||||||
|
@ -576,6 +637,19 @@ These are the building blocks for skip functions.
|
||||||
"returns parent keyword if heading is in the immediate subtree of a todoitem"
|
"returns parent keyword if heading is in the immediate subtree of a todoitem"
|
||||||
(save-excursion (and (org-up-heading-safe) (nd/is-todoitem-p))))
|
(save-excursion (and (org-up-heading-safe) (nd/is-todoitem-p))))
|
||||||
|
|
||||||
|
(defun nd/is-discontinuous-project-task-p ()
|
||||||
|
"detects todoitems that are children of non-todoitems
|
||||||
|
that in turn are children of todoitems (discontinous project)"
|
||||||
|
(let ((has-todoitem-parent)
|
||||||
|
(has-non-todoitem-parent))
|
||||||
|
(save-excursion
|
||||||
|
(while (and (org-up-heading-safe)
|
||||||
|
has-todoitem-parent)
|
||||||
|
(if (nd/is-todoitem-p)
|
||||||
|
(setq has-todoitem-parent t)
|
||||||
|
(setq has-non-todoitem-parent t))))
|
||||||
|
(and has-todoitem-parent has-non-todoitem-parent)))
|
||||||
|
|
||||||
(defun nd/test-first-order-project ()
|
(defun nd/test-first-order-project ()
|
||||||
"tests the state of a project assuming first order.
|
"tests the state of a project assuming first order.
|
||||||
if not first order, this function will iterate to the next project
|
if not first order, this function will iterate to the next project
|
||||||
|
@ -596,6 +670,10 @@ These are the building blocks for skip functions.
|
||||||
(org-forward-heading-same-level 1 t)))
|
(org-forward-heading-same-level 1 t)))
|
||||||
found-active))
|
found-active))
|
||||||
|
|
||||||
|
(defconst nd/project-invalid-todostates
|
||||||
|
'("WAITING" "NEXT")
|
||||||
|
"projects cannot have these todostates")
|
||||||
|
|
||||||
;; project level testing
|
;; project level testing
|
||||||
;; TODO: is there a better way to handle statuscodes like this??? (array like thingy)
|
;; TODO: is there a better way to handle statuscodes like this??? (array like thingy)
|
||||||
(defun nd/descend-into-project ()
|
(defun nd/descend-into-project ()
|
||||||
|
@ -612,7 +690,9 @@ These are the building blocks for skip functions.
|
||||||
- if project has any TODO (regardless of DONE or CANCELLED) it is stuck
|
- if project has any TODO (regardless of DONE or CANCELLED) it is stuck
|
||||||
- if project has any HOLD (regardless of DONE, CANCELLED, or TODO) it is held
|
- if project has any HOLD (regardless of DONE, CANCELLED, or TODO) it is held
|
||||||
- in the same manner WAITING means waiting project
|
- in the same manner WAITING means waiting project
|
||||||
- in the same manner, NEXT means active. NEXT overrides all
|
- in the same manner, NEXT or scheduled means active.
|
||||||
|
- can also detect errors which override all
|
||||||
|
- anything higher than active breaks the recursion/tree walk
|
||||||
|
|
||||||
Using this scheme, we simply compare the magnitude of the statuscodes"
|
Using this scheme, we simply compare the magnitude of the statuscodes"
|
||||||
(let ((project-state 0)
|
(let ((project-state 0)
|
||||||
|
@ -627,15 +707,23 @@ These are the building blocks for skip functions.
|
||||||
(if keyword
|
(if keyword
|
||||||
(let ((cur-state
|
(let ((cur-state
|
||||||
(if has-children
|
(if has-children
|
||||||
(cond ((equal keyword "HOLD") 20)
|
(cond ((member keyword nd/project-invalid-todostates) 50)
|
||||||
((equal keyword "TODO") (nd/descend-into-project))
|
((nd/is-scheduled-heading-p) 50)
|
||||||
;; NOTE: all projects are assumed to only have TODO, HOLD, CANCELLED, or DONE, hence the three possible statuscodes
|
;; cancelled and hold work independent of everything underneath
|
||||||
(t 0))
|
((equal keyword "CANCELLED") 0)
|
||||||
|
((equal keyword "HOLD") 20)
|
||||||
|
;; all other tests require a descent into the child project hence let form
|
||||||
|
(t (let ((child-statuscode (nd/descend-into-project)))
|
||||||
|
;; projects marked TODO should not be complete
|
||||||
|
(cond ((equal keyword "TODO") (if (> child-statuscode 0) child-statuscode 50))
|
||||||
|
;; projects marked DONE should have all subtasks/projects marked DONE/CANCELLED
|
||||||
|
(t (if (= child-statuscode 0) 0 50))))))
|
||||||
(cond ((equal keyword "HOLD") 20)
|
(cond ((equal keyword "HOLD") 20)
|
||||||
((equal keyword "WAITING") 30)
|
((equal keyword "WAITING") 30)
|
||||||
((equal keyword "NEXT") 40)
|
((equal keyword "NEXT") 40)
|
||||||
((and (equal keyword "TODO") (nd/is-scheduled-heading-p)) 40)
|
((and (equal keyword "TODO") (nd/is-scheduled-heading-p)) 40)
|
||||||
((equal keyword "TODO") 10)
|
((equal keyword "TODO") 10)
|
||||||
|
;; catchall means CANCELLED or DONE (complete)
|
||||||
(t 0)))))
|
(t 0)))))
|
||||||
(if (> cur-state project-state)
|
(if (> cur-state project-state)
|
||||||
(setq project-state cur-state)))))
|
(setq project-state cur-state)))))
|
||||||
|
@ -646,98 +734,43 @@ These are the building blocks for skip functions.
|
||||||
(defun nd/is-project-status-p (statuscode)
|
(defun nd/is-project-status-p (statuscode)
|
||||||
(let ((keyword (nd/is-project-p)))
|
(let ((keyword (nd/is-project-p)))
|
||||||
(if keyword
|
(if keyword
|
||||||
(cond ((member keyword nd/project-invalid-todostates) nil)
|
(if (member keyword nd/project-invalid-todostates)
|
||||||
((and (equal keyword "HOLD") (= statuscode 20)) keyword)
|
(if (= statuscode 50) keyword)
|
||||||
((and (equal keyword "HOLD") (/= statuscode 20)) nil)
|
(if (equal keyword "HOLD")
|
||||||
((= statuscode (nd/descend-into-project)) keyword)))))
|
(if (= statuscode 20) keyword)
|
||||||
|
(if (= statuscode (nd/descend-into-project)) keyword))))))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
*** skip functions
|
*** keymap
|
||||||
These are the primary means we use to sort through tasks. Note that we could do this with
|
|
||||||
tags in the custom commands section but I find this easier to maintain and possibly faster.
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
;; NOTE: use save-restriction and widen if we ever actually use narrowing
|
(evil-define-key 'motion org-agenda-mode-map "T" 'nd/toggle-project-toplevel-display)
|
||||||
;; tasks
|
|
||||||
(defun nd/skip-non-atomic-tasks ()
|
|
||||||
(if (not (nd/is-atomic-task-p))
|
|
||||||
(save-excursion (or (outline-next-heading) (point-max)))))
|
|
||||||
|
|
||||||
(defun nd/skip-non-next-project-tasks ()
|
|
||||||
(if (not (equal (nd/is-project-task-p) "NEXT"))
|
|
||||||
(save-excursion (or (outline-next-heading) (point-max)))))
|
|
||||||
|
|
||||||
(defun nd/skip-non-waiting-project-tasks ()
|
|
||||||
(if (not (equal (nd/is-project-task-p) "WAITING"))
|
|
||||||
(save-excursion (or (outline-next-heading) (point-max)))))
|
|
||||||
|
|
||||||
(defun nd/skip-non-held-project-tasks ()
|
|
||||||
(if (not (equal (nd/is-project-task-p) "HOLD"))
|
|
||||||
(save-excursion (or (outline-next-heading) (point-max)))))
|
|
||||||
|
|
||||||
;; projects
|
|
||||||
(defun nd/skip-projects-without-statuscode (statuscode)
|
|
||||||
(if (not (nd/is-project-status-p statuscode))
|
|
||||||
(save-excursion (or (outline-next-heading) (point-max)))))
|
|
||||||
|
|
||||||
;; top-level projects
|
|
||||||
(defun nd/skip-subprojects-without-statuscode (statuscode)
|
|
||||||
(if (or (nd/heading-has-parent) (not (nd/is-project-status-p statuscode)))
|
|
||||||
(save-excursion (or (outline-next-heading) (point-max)))))
|
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
*** interactive view functions
|
*** views
|
||||||
|
**** calendar display
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(defvar nd/agenda-limit-project-toplevel t
|
(setq org-agenda-span 'day)
|
||||||
"used to filter projects by all levels or top-level only")
|
(setq org-agenda-time-grid (quote ((daily today remove-match)
|
||||||
|
#("----------------" 0 16 (org-heading t))
|
||||||
(defun nd/toggle-project-toplevel-display ()
|
(0900 1100 1300 1500 1700))))
|
||||||
(interactive)
|
#+End_src
|
||||||
(setq nd/agenda-limit-project-toplevel (not nd/agenda-limit-project-toplevel))
|
**** right align tags
|
||||||
(when (equal major-mode 'org-agenda-mode)
|
the agenda does not do this by default...it's annoying
|
||||||
(org-agenda-redo))
|
#+BEGIN_SRC emacs-lisp
|
||||||
(message "Showing %s project view in agenda" (if nd/agenda-limit-project-toplevel "toplevel" "complete")))
|
(add-hook 'org-finalize-agenda-hook 'place-agenda-tags)
|
||||||
|
(defun place-agenda-tags ()
|
||||||
(defmacro nd/agenda-base-task-command (keyword skip-fun)
|
"Put the agenda tags by the right border of the agenda window."
|
||||||
"shorter syntax to define task agenda commands"
|
(setq org-agenda-tags-column (- 4 (window-width)))
|
||||||
`(tags-todo
|
(org-agenda-align-tags))
|
||||||
"-NA-REFILE/!"
|
|
||||||
((org-agenda-overriding-header (concat ,keyword " Tasks"))
|
|
||||||
(org-agenda-skip-function ,skip-fun)
|
|
||||||
(org-agenda-todo-ignore-with-date 'all)
|
|
||||||
(org-agenda-sorting-strategy '(category-keep)))))
|
|
||||||
|
|
||||||
(defmacro nd/agenda-base-project-command (keyword statuscode)
|
|
||||||
"shorter syntax to define project agenda commands"
|
|
||||||
`(tags-todo
|
|
||||||
"-NA-REFILE-ATOMIC/!"
|
|
||||||
((org-agenda-overriding-header (concat
|
|
||||||
(and nd/agenda-limit-project-toplevel "Toplevel ")
|
|
||||||
,keyword
|
|
||||||
" Projects"))
|
|
||||||
(org-agenda-skip-function (if nd/agenda-limit-project-toplevel
|
|
||||||
'(nd/skip-subprojects-without-statuscode ,statuscode)
|
|
||||||
'(nd/skip-projects-without-statuscode ,statuscode)))
|
|
||||||
(org-agenda-sorting-strategy '(category-keep)))))
|
|
||||||
|
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
*** custom commands
|
*** auto exclusion
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(setq org-agenda-tags-todo-honor-ignore-options t)
|
(defun nd/org-auto-exclude-function (tag)
|
||||||
(setq org-agenda-custom-commands
|
"Automatic task exclusion in the agenda with / RET"
|
||||||
`(("t" "Task view"
|
(and (cond
|
||||||
((agenda "" nil)
|
((string= tag "hold")
|
||||||
,(macroexpand '(nd/agenda-base-task-command "Next Project" 'nd/skip-non-next-project-tasks))
|
t))
|
||||||
,(macroexpand '(nd/agenda-base-task-command "Waiting Project" 'nd/skip-non-waiting-project-tasks))
|
(concat "-" tag)))
|
||||||
,(macroexpand '(nd/agenda-base-task-command "Atomic" 'nd/skip-non-atomic-tasks))
|
|
||||||
,(macroexpand '(nd/agenda-base-task-command "Held Project" 'nd/skip-non-held-project-tasks))))
|
|
||||||
("o" "Project Overview"
|
|
||||||
(,(macroexpand '(nd/agenda-base-project-command "Stuck" 10))
|
|
||||||
,(macroexpand '(nd/agenda-base-project-command "Waiting" 20))
|
|
||||||
,(macroexpand '(nd/agenda-base-project-command "Active" 40))
|
|
||||||
,(macroexpand '(nd/agenda-base-project-command "Held" 30))))
|
|
||||||
("r" "Refile and errors"
|
|
||||||
((tags "REFILE"
|
|
||||||
((org-agenda-overriding-header "Tasks to Refile"))
|
|
||||||
(org-tags-match-list-sublevels nil))))))
|
|
||||||
|
|
||||||
|
(setq org-agenda-auto-exclude-function 'nd/org-auto-exclude-function)
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
** ui
|
** ui
|
||||||
*** bullets
|
*** bullets
|
||||||
|
|
Loading…
Reference in New Issue