update org mode interactive functions and agenda

This commit is contained in:
petrucci4prez 2018-05-04 20:36:22 -04:00
parent 59010e234f
commit 6aaea10491
2 changed files with 513 additions and 299 deletions

383
conf.el
View File

@ -238,6 +238,21 @@
(delight 'org-indent-mode) (delight 'org-indent-mode)
(setq org-directory "~/Org") (setq org-directory "~/Org")
(use-package org-bullets
:ensure t
:config
(add-hook 'org-mode-hook (lambda () (org-bullets-mode))))
(defun nd/org-ui-heading-same-font-height ()
(let ((heading-height 1.15))
(set-face-attribute 'org-level-1 nil :weight 'bold :height heading-height)
(set-face-attribute 'org-level-2 nil :weight 'semi-bold :height heading-height)
(set-face-attribute 'org-level-3 nil :weight 'normal :height heading-height)
(set-face-attribute 'org-level-4 nil :weight 'normal :height heading-height)
(set-face-attribute 'org-level-5 nil :weight 'normal :height heading-height)))
(add-hook 'org-mode-hook 'nd/org-ui-heading-same-font-height)
;;(add-hook 'org-capture-mode-hook 'evil-append) ;;(add-hook 'org-capture-mode-hook 'evil-append)
(add-to-list 'org-structure-template-alist (add-to-list 'org-structure-template-alist
@ -247,49 +262,74 @@
(setq org-special-ctrl-k t) (setq org-special-ctrl-k t)
(setq org-yank-adjusted-subtrees t) (setq org-yank-adjusted-subtrees t)
(add-hook 'org-mode-hook
(lambda ()
(local-set-key (kbd "C-c C-x x") 'nd/mark-subtree-done)
(local-set-key (kbd "C-c C-x c") 'nd/org-clone-subtree-with-time-shift-reset)))
(setq org-todo-keywords (setq org-todo-keywords
'((sequence "TODO(t)" "NEXT(n)" "|" "DONE(d)") '((sequence "TODO(t)" "NEXT(n)" "|" "DONE(d)")
(sequence "WAITING(w@/!)" "HOLD(h@/!)" "|" "CANCELLED(c@/!)"))) (sequence "WAIT(w@/!)" "HOLD(h@/!)" "|" "CANC(c@/!)")))
(setq org-todo-keyword-faces (setq org-todo-keyword-faces
(quote (("TODO" :foreground "light coral" :weight bold) (quote (("TODO" :foreground "light coral" :weight bold)
("NEXT" :foreground "khaki" :weight bold) ("NEXT" :foreground "khaki" :weight bold)
("DONE" :foreground "light green" :weight bold) ("DONE" :foreground "light green" :weight bold)
("WAITING" :foreground "orange" :weight bold) ("WAIT" :foreground "orange" :weight bold)
("HOLD" :foreground "violet" :weight bold) ("HOLD" :foreground "violet" :weight bold)
("CANCELLED" :foreground "deep sky blue" :weight bold)))) ("CANC" :foreground "deep sky blue" :weight bold))))
(setq org-tag-alist '((:startgroup) (setq org-tag-alist '((:startgroup)
("@errand" . ?e) ("@errand" . ?e)
("@work" . ?w) ("@work" . ?w)
("@home" . ?h) ("@home" . ?h)
("@travel" . ?f) ("@travel" . ?t)
(:endgroup) (:endgroup)
("#laptop" . ?L) ("#laptop" . ?L)
("#hood" . ?H) ("#tcult" . ?T)
("WORK" . ?W)
("PERSONAL" . ?P)
("NOTE" . ?N)
("FLAGGED" . ??)))
;; TODO I'm sure there is a better way to do this in lisp ("%note" . ?n)
(setq org-tag-faces ("%subdiv" . ?s)
'(("@errand" . (:foreground "PaleGreen"))
("@work" . (:foreground "PaleGreen")) (:startgroup)
("@home" . (:foreground "PaleGreen")) ("_env" . ?E)
("@travel" . (:foreground "PaleGreen")) ("_fin" . ?F)
("#laptop" . (:foreground "SkyBlue")) ("_int" . ?I)
("#hood" . (:foreground "SkyBlue")))) ("_met" . ?M)
("_phy" . ?H)
("_pro" . ?P)
("_rec" . ?R)
("_soc" . ?S)
(:endgroup)))
;; not the most elegant but this will work
(setq org-tag-faces '())
(defun nd/add-tag-face (fg-name start end)
"Adds list of cons cells to org-tag-faces with foreground set to fg-name.
Start and end specify the positions in org-tag-alist which define the tags
to which the faces are applied"
(dolist (tag (mapcar #'car (subseq org-tag-alist start end)))
(push `(,tag . (:foreground ,fg-name)) org-tag-faces)))
(nd/add-tag-face "PaleGreen" 1 5)
(nd/add-tag-face "SkyBlue" 6 8)
(nd/add-tag-face "PaleGoldenrod" 8 10)
(nd/add-tag-face "violet" 11 19)
(add-to-list 'org-default-properties "PROJECT_TYPE")
(add-to-list 'org-default-properties "OWNER")
(setq org-global-properties
'(("Project_Type_ALL" . "series")
("Effort_ALL" . "00 10 30 60 90")))
;; this is basically the same as putting the properties at the top of all org files
(add-to-list 'org-default-properties "Project_Type")
(setq org-global-properties '(("Project_Type_ALL" . "series")))
;; TODO this may not be needed ;; TODO this may not be needed
(setq org-use-property-inheritance '("Project_Type")) (setq org-use-property-inheritance '("Project_Type"))
(setq org-capture-templates (setq org-capture-templates
'(("t" "todo" entry (file "~/Org/capture.org") "* TODO %?\ndeliverable: \n%U\n") '(("t" "todo" entry (file "~/Org/capture.org") "* TODO %?\ndeliverable: \n%U\n")
("n" "note" entry (file "~/Org/capture.org") "* %? :NOTE:\n%U\n" ) ("n" "note" entry (file "~/Org/capture.org") "* %? :\\%note:\n%U\n" )
("a" "appointment" entry (file "~/Org/capture.org") "* TODO %?\n%U\n%^t\n" ) ("a" "appointment" entry (file "~/Org/capture.org") "* TODO %?\n%U\n%^t\n" )
("m" "multi-day" entry (file "~/Org/capture.org") "* TODO %?\n%U\n%^t--%^t\n" ) ("m" "multi-day" entry (file "~/Org/capture.org") "* TODO %?\n%U\n%^t--%^t\n" )
("d" "deadline" entry (file "~/Org/capture.org") "* TODO %?\nDEADLINE: %^t\ndeliverable:\n%U\n" ) ("d" "deadline" entry (file "~/Org/capture.org") "* TODO %?\nDEADLINE: %^t\ndeliverable:\n%U\n" )
@ -319,82 +359,61 @@
(not (member (nth 2 (org-heading-components)) org-done-keywords))) (not (member (nth 2 (org-heading-components)) org-done-keywords)))
(setq org-refile-target-verify-function 'nd/verify-refile-target) (setq org-refile-target-verify-function 'nd/verify-refile-target)
(use-package org-bullets
:ensure t
:config
(add-hook 'org-mode-hook (lambda () (org-bullets-mode))))
(setq org-agenda-files '("~/Org" (setq org-agenda-files '("~/Org"
"~/Org/large_projects" "~/Org/projects"
"~/Org/reference")) "~/Org/reference"))
;; (setq org-agenda-files '("~/Org/reference/agendatest.org")) ;; (setq org-agenda-files '("~/Org/reference/agendatest.org"))
(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)
(defun nd/is-todoitem-p () (defun nd/get-date-property (date-property)
"return todo keyword if present in headline (which defines the heading as a todoitem) "Helper function to get the date property and convert to a number.
this is used to both test if a heading is a todoitem and retrieving the keyword" If it does not have a date, it will return nil."
(let ((keyword (nth 2 (org-heading-components)))) (let ((timestamp (org-entry-get nil date-property)))
(if (member keyword org-todo-keywords-1) (if timestamp (float-time (date-to-time timestamp)))))
keyword)))
(defun nd/is-project-p () (defun nd/is-timestamped-heading-p ()
"return todo keyword if heading is todoitem and has children" (nd/get-date-property "TIMESTAMP"))
(and (nd/heading-has-children) (nd/is-todoitem-p)))
(defun nd/is-task-p ()
"return todo keyword if heading is todoitem with no children"
(and (not (nd/heading-has-children)) (nd/is-todoitem-p)))
(defun nd/is-atomic-task-p ()
"return todo keyword if heading is task with no parents"
(and (not (nd/heading-has-parent)) (nd/is-task-p)))
(defun nd/is-project-task-p ()
"return todo keyword if heading is task with parents"
(and (nd/heading-has-parent) (nd/is-task-p)))
(defun nd/is-scheduled-heading-p () (defun nd/is-scheduled-heading-p ()
"return timestamp if headline is scheduled" (nd/get-date-property "SCHEDULED"))
(org-entry-get nil "SCHEDULED"))
(defun nd/is-series-header-p () (defun nd/is-deadlined-heading-p ()
"return t if headline has property Project_Type=series" (nd/get-date-property "DEADLINE"))
(equal "series" (org-entry-get nil "Project_Type")))
(defun nd/is-closed-heading-p () (defun nd/is-closed-heading-p ()
"return timestamp if headline is closed" (nd/get-date-property "CLOSED"))
(let ((timestamp (org-entry-get nil "CLOSED")))
(if timestamp (float-time (date-to-time timestamp))))) (defun nd/is-stale-heading-p ()
(let ((timestamp (nd/is-timestamped-heading-p)))
(if (and timestamp (> (- (float-time) timestamp) 0))
timestamp)))
(defvar nd/archive-delay-days 30 (defvar nd/archive-delay-days 30
"the number of days to wait before tasks show up in the archive view") "the number of days to wait before tasks show up in the archive view")
(defun nd/is-archivable-heading-p () (defun nd/is-archivable-heading-p ()
"return timestamp if todoitem is closed and older than specified time"
(let ((timestamp (nd/is-closed-heading-p))) (let ((timestamp (nd/is-closed-heading-p)))
;; NOTE we do not ensure that the todo state is in done keywords
;; this is to allow easier error correction in slip functions
(if (and timestamp (> (- (float-time) timestamp) (* 60 60 24 nd/archive-delay-days))) (if (and timestamp (> (- (float-time) timestamp) (* 60 60 24 nd/archive-delay-days)))
timestamp))) timestamp)))
(defun nd/is-archivable-atomic-task-p () (defun nd/is-todoitem-p ()
"return keyword if heading is an archivable task" (let ((keyword (nth 2 (org-heading-components))))
(and (nd/is-archivable-heading-p) (nd/is-atomic-task-p))) (if (member keyword org-todo-keywords-1)
(defun nd/is-archivable-project-p ()
"return keyword if heading is an archivable task"
(and (nd/is-archivable-heading-p) (nd/is-project-p)))
(defun nd/is-active-task-p ()
"return keyword if task is either NEXT or scheduled"
(let ((keyword (nd/is-task-p)))
(if (or (equal keyword "NEXT") (nd/is-scheduled-heading-p))
keyword))) keyword)))
(defun nd/is-blocked-task-p () (defun nd/is-project-p ()
"return keyword if task is WAITING" (and (nd/heading-has-children) (nd/is-todoitem-p)))
(equal (nd/is-task-p) "WAITING"))
(defun nd/is-task-p ()
(and (not (nd/heading-has-children)) (nd/is-todoitem-p)))
(defun nd/is-atomic-task-p ()
(and (not (nd/heading-has-parent)) (nd/is-task-p)))
(defun nd/is-series-heading-p ()
"return t if headline has property Project_Type=series"
(equal "series" (org-entry-get nil "Project_Type" t)))
(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"
@ -430,10 +449,9 @@ todoitem which in turn has a parent which is a todoitem"
(and has-todoitem-parent has-non-todoitem-parent))) (and has-todoitem-parent has-non-todoitem-parent)))
(defconst nd/project-invalid-todostates (defconst nd/project-invalid-todostates
'("WAITING" "NEXT") '("WAIT" "NEXT")
"projects cannot have these todostates") "projects cannot have these todostates")
;; project level testing
(defconst nd/project-statuscodes (defconst nd/project-statuscodes
'(:archivable '(:archivable
:complete :complete
@ -485,7 +503,7 @@ down the list override higher items")
(if (nd/heading-has-children) (if (nd/heading-has-children)
(cond ((member keyword nd/project-invalid-todostates) :invalid-todostate) (cond ((member keyword nd/project-invalid-todostates) :invalid-todostate)
((nd/is-scheduled-heading-p) :scheduled-project) ((nd/is-scheduled-heading-p) :scheduled-project)
((equal keyword "CANCELLED") (if (nd/is-archivable-heading-p) ((equal keyword "CANC") (if (nd/is-archivable-heading-p)
:archivable :archivable
:complete)) :complete))
((equal keyword "HOLD") :held) ((equal keyword "HOLD") :held)
@ -499,11 +517,11 @@ down the list override higher items")
(:archivable (if (nd/is-archivable-heading-p) (:archivable (if (nd/is-archivable-heading-p)
:archivable :archivable
:complete)) :complete))
(t (if (= child-statuscode :complete) (t (if (nd/status= child-statuscode :complete)
:complete :complete
:done-imcomplete)))))))) :done-incomplete))))))))
(cond ((equal keyword "HOLD") :held) (cond ((equal keyword "HOLD") :held)
((equal keyword "WAITING") :waiting) ((equal keyword "WAIT") :waiting)
((equal keyword "NEXT") :active) ((equal keyword "NEXT") :active)
((and (equal keyword "TODO") (nd/is-scheduled-heading-p)) :active) ((and (equal keyword "TODO") (nd/is-scheduled-heading-p)) :active)
((equal keyword "TODO") :stuck) ((equal keyword "TODO") :stuck)
@ -528,17 +546,17 @@ both are true"
Note that this assumes the headline being tested is a valid project" Note that this assumes the headline being tested is a valid project"
(case statuscode (case statuscode
;; projects closed more than 30 days ago ;; projects closed more than 30 days ago
;; note CANCELLED overrides all subtasks/projects ;; note CANC overrides all subtasks/projects
(:archivable (:archivable
(if (nd/is-archivable-heading-p) (if (nd/is-archivable-heading-p)
(or (equal keyword "CANCELLED") (or (equal keyword "CANC")
(nd/is-project-keyword-status-p "DONE" = :archivable)))) (nd/is-project-keyword-status-p "DONE" = :archivable))))
;; projects closed less than 30 days ago ;; projects closed less than 30 days ago
;; note CANCELLED overrides all subtasks/projects ;; note CANC overrides all subtasks/projects
(:complete (:complete
(if (not (nd/is-archivable-heading-p)) (if (not (nd/is-archivable-heading-p))
(or (equal keyword "CANCELLED") (or (equal keyword "CANC")
(nd/is-project-keyword-status-p "DONE" = :complete)))) (nd/is-project-keyword-status-p "DONE" = :complete))))
;; projects with no waiting, held, or active components ;; projects with no waiting, held, or active components
@ -587,7 +605,7 @@ Note that this assumes the headline being tested is a valid project"
(save-excursion (or (org-end-of-subtree t) (point-max)))) (save-excursion (or (org-end-of-subtree t) (point-max))))
(defconst nd/project-skip-todostates (defconst nd/project-skip-todostates
'("HOLD" "CANCELLED") '("HOLD" "CANC")
"These keywords override all contents within their subtrees. "These keywords override all contents within their subtrees.
Currently used to tell skip functions when they can hop over Currently used to tell skip functions when they can hop over
entire subtrees to save time and ignore tasks") entire subtrees to save time and ignore tasks")
@ -605,27 +623,50 @@ test-fun return true"
(message keyword) (message keyword)
(if (not (and keyword ,test-fun)) (if (not (and keyword ,test-fun))
(nd/skip-item))))) (nd/skip-item)))))
;; stale headings
;; For archiving headings with old timestamps
;; Note that these are not always todo items
;; I only care about those that are not part
;; of projects (projects will get taken care
;; of when the entire project is finished)
;; and those that are not DONE/CANC (as
;; those appear in the regular archive
;; section)
(defun nd/skip-non-stale-headings ()
(save-restriction
(widen)
(let ((keyword (nd/is-todoitem-p)))
(if (not
(and (nd/is-stale-heading-p)
(not (member keyword org-done-keywords))
(not (nd/heading-has-children))
(not (nd/heading-has-parent))))
(nd/skip-item)))))
;; atomic tasks ;; atomic tasks
;; by definition these have no parents, so ;; by definition these have no parents, so
;; we don't need to worry about skipping over projects ;; we don't need to worry about skipping over projects
;; any todo state is valid and we only sort by done/cancelled ;; any todo state is valid and we only sort by done/canc
(defun nd/skip-non-unclosed-atomic-tasks () (defun nd/skip-non-unclosed-atomic-tasks ()
(nd/skip-heading-with (nd/skip-heading-with
nd/is-atomic-task-p nd/is-atomic-task-p
(not (member keyword org-done-keywords)))) (and (not (nd/is-timestamped-heading-p))
(not (nd/is-scheduled-heading-p))
(not (nd/is-deadlined-heading-p))
(not (member keyword org-done-keywords)))))
(defun nd/skip-non-closed-atomic-tasks () (defun nd/skip-non-closed-atomic-tasks ()
(nd/skip-heading-with (nd/skip-heading-with
nd/is-atomic-task-p nd/is-atomic-task-p
(and (member keyword org-done-keywords) (and (member keyword org-done-keywords)
(not (nd/is-archivable-heading))))) (not (nd/is-archivable-heading-p)))))
(defun nd/skip-non-archivable-atomic-tasks () (defun nd/skip-non-archivable-atomic-tasks ()
(nd/skip-heading-with (nd/skip-heading-with
nd/is-atomic-task-p nd/is-atomic-task-p
(and (member keyword org-done-keywords) (and (member keyword org-done-keywords)
(nd/is-archivable-heading)))) (nd/is-archivable-heading-p))))
;; project tasks ;; project tasks
;; since these are part of projects I need to assess ;; since these are part of projects I need to assess
@ -634,7 +675,7 @@ test-fun return true"
;; Note that I only care about the keyword in these ;; Note that I only care about the keyword in these
;; cases because I don't archive these, I archive ;; cases because I don't archive these, I archive
;; their parent projects. The keywords I care about ;; their parent projects. The keywords I care about
;; are NEXT, WAITING, and HOLD because these are ;; are NEXT, WAIT, and HOLD because these are
;; definitive project tasks that require/inhibit ;; definitive project tasks that require/inhibit
;; futher action ;; futher action
(defun nd/skip-non-keyword-project-tasks (skip-keyword) (defun nd/skip-non-keyword-project-tasks (skip-keyword)
@ -647,6 +688,9 @@ test-fun return true"
(nd/skip-subtree) (nd/skip-subtree)
(nd/skip-item)) (nd/skip-item))
(if (not (and (nd/heading-has-parent) (if (not (and (nd/heading-has-parent)
(not (nd/is-timestamped-heading-p))
(not (nd/is-scheduled-heading-p))
(not (nd/is-deadlined-heading-p))
(equal keyword skip-keyword))) (equal keyword skip-keyword)))
(nd/skip-item))) (nd/skip-item)))
(nd/skip-item))))) (nd/skip-item)))))
@ -679,14 +723,11 @@ test-fun return true"
(save-restriction (save-restriction
(widen) (widen)
(let ((keyword (nd/is-project-p))) (let ((keyword (nd/is-project-p)))
;; TODO there may be a way to skip over skippable projects
;; and save a few cycles. Not a huge deal, but would require
;; keeping the skippable line and then skipping over the others
;; in one fell swoop, not easy to do efficiently
(if keyword (if keyword
(if (not (nd/is-project-status-p statuscode)) (if (and nd/agenda-limit-project-toplevel
(if nd/agenda-limit-project-toplevel (nd/heading-has-parent))
(nd/skip-subtree) (nd/skip-subtree)
(if (not (nd/is-project-status-p statuscode))
(nd/skip-item))) (nd/skip-item)))
(nd/skip-item))))) (nd/skip-item)))))
@ -696,68 +737,81 @@ test-fun return true"
(defun nd/toggle-project-toplevel-display () (defun nd/toggle-project-toplevel-display ()
(interactive) (interactive)
(setq nd/agenda-limit-project-toplevel (not nd/agenda-limit-project-toplevel)) (setq nd/agenda-limit-project-toplevel (not nd/agenda-limit-project-toplevel))
(when (equal major-mode 'org-agenda-mode) (when (equal major-mode 'org-agenda-mode)
(org-agenda-redo)) (org-agenda-redo))
(message "Showing %s project view in agenda" (if nd/agenda-limit-project-toplevel "toplevel" "complete"))) (message "Showing %s project view in agenda"
(if nd/agenda-limit-project-toplevel "toplevel" "complete")))
(defun nd/agenda-base-task-command (keyword skip-fun) (setq org-agenda-tags-todo-honor-ignore-options t)
(setq org-agenda-prefix-format
'((agenda . " %-12:c%-5:e%?-12t% s")
(timeline . " % s")
(todo . " %-12:c")
(tags . " %-12:c%-5:e")
(search . " %-12:c")))
(defun nd/agenda-base-task-command (match keyword skip-fun)
"shorter syntax to define task agenda commands" "shorter syntax to define task agenda commands"
`(tags `(tags
"-NA-REFILE/" ,match
((org-agenda-overriding-header (concat ,keyword " Tasks")) ((org-agenda-overriding-header (concat ,keyword " Tasks"))
(org-agenda-skip-function ,skip-fun) (org-agenda-skip-function ,skip-fun)
(org-agenda-todo-ignore-with-date 'all)
(org-agenda-sorting-strategy '(category-keep))))) (org-agenda-sorting-strategy '(category-keep)))))
(defun nd/agenda-base-project-command (match keyword statuscode) (defun nd/agenda-base-project-command (match keyword statuscode)
"shorter syntax to define project agenda commands" "shorter syntax to define project agenda commands"
`(tags `(tags
,match ,match
((org-agenda-overriding-header (concat ((org-agenda-overriding-header
(and nd/agenda-limit-project-toplevel "Toplevel ") (concat (and nd/agenda-limit-project-toplevel "Toplevel ") ,keyword " Projects"))
,keyword
" Projects"))
(org-agenda-skip-function '(nd/skip-projects-without-statuscode ,statuscode)) (org-agenda-skip-function '(nd/skip-projects-without-statuscode ,statuscode))
(org-agenda-sorting-strategy '(category-keep))))) (org-agenda-sorting-strategy '(category-keep)))))
(setq org-agenda-tags-todo-honor-ignore-options t) (let ((task-view-match "-NA-REFILE")
(setq org-agenda-custom-commands (project-view-match "-NA-REFILE-Project_Type=\"series\"/")
`(("t" (series-view-match "-NA-REFILE+Project_Type=\"series\"/"))
"Task View" (setq org-agenda-custom-commands
((agenda "" nil) `(("t"
,(nd/agenda-base-task-command "Next Project" ''(nd/skip-non-keyword-project-tasks "NEXT")) "Task View"
,(nd/agenda-base-task-command "Waiting Project" ''(nd/skip-non-keyword-project-tasks "WAITING")) ((agenda "" nil)
,(nd/agenda-base-task-command "Atomic" ''nd/skip-non-unclosed-atomic-tasks) ,(nd/agenda-base-task-command task-view-match "Next Project" ''(nd/skip-non-keyword-project-tasks "NEXT"))
,(nd/agenda-base-task-command "Held Project" ''(nd/skip-non-keyword-project-tasks "HOLD")))) ,(nd/agenda-base-task-command task-view-match "Waiting Project" ''(nd/skip-non-keyword-project-tasks "WAIT"))
("o" ,(nd/agenda-base-task-command task-view-match "Atomic" ''nd/skip-non-unclosed-atomic-tasks)
"Project Overview" ,(nd/agenda-base-task-command task-view-match "Held Project" ''(nd/skip-non-keyword-project-tasks "HOLD"))))
(,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC-Project_Type=\"series\"/!" "Stuck" :stuck) ("p"
,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC-Project_Type=\"series\"/!" "Waiting" :waiting) "Project View"
,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC-Project_Type=\"series\"/!" "Active" :active) (,(nd/agenda-base-project-command project-view-match "Stuck" :stuck)
,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC-Project_Type=\"series\"/!" "Held" :held))) ,(nd/agenda-base-project-command project-view-match "Waiting" :waiting)
("r" ,(nd/agenda-base-project-command project-view-match "Active" :active)
"Refile and errors" ,(nd/agenda-base-project-command project-view-match "Held" :held)))
((tags "REFILE" ("s"
((org-agenda-overriding-header "Tasks to Refile")) "Series View"
(org-tags-match-list-sublevels nil)) (,(nd/agenda-base-project-command series-view-match "Stuck Series" :stuck)
,(nd/agenda-base-task-command "Discontinous Project" ''nd/skip-non-discontinuous-project-tasks) ,(nd/agenda-base-project-command series-view-match "Empty Series" :undone-complete)
,(nd/agenda-base-task-command "Undone Closed" ''nd/skip-non-undone-closed-todoitems) ,(nd/agenda-base-project-command series-view-match "Active Series" :active)
,(nd/agenda-base-task-command "Done Unclosed" ''nd/skip-non-done-unclosed-todoitems) ,(nd/agenda-base-project-command series-view-match "Waiting Series" :waiting)
,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC-Project_Type=\"series\"/" "Undone Completed" :undone-complete) ,(nd/agenda-base-project-command series-view-match "Held Series" :held)
,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC-Project_Type=\"series\"/" "Done Incompleted" :done-incomplete) ,(nd/agenda-base-task-command series-view-match "Uninitialized Series" ''nd/skip-non-series-atomic-tasks)))
,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC-Project_Type=\"series\"/" "Invalid Todostate" :invalid-todostate))) ("r"
("s" "Refile and Critical Errors"
"Series projects" ((tags "REFILE"
(,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC+Project_Type=\"series\"/!" "Active Series" :active) ((org-agenda-overriding-header "Tasks to Refile"))
,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC+Project_Type=\"series\"/!" "Empty Series" :complete))) (org-tags-match-list-sublevels nil))
("A" ,(nd/agenda-base-task-command task-view-match "Discontinous Project" ''nd/skip-non-discontinuous-project-tasks)
"Archivable Tasks and Projects" ,(nd/agenda-base-project-command project-view-match "Invalid Todostate" :invalid-todostate)))
((tags "-NA-REFILE/" ("e"
((org-agenda-overriding-header "Atomic Tasks to Archive") "Non-critical Errors"
(org-agenda-skip-function 'nd/skip-non-archivable-atomic-tasks) (,(nd/agenda-base-task-command task-view-match "Undone Closed" ''nd/skip-non-undone-closed-todoitems)
(org-tags-match-list-sublevels nil))) ,(nd/agenda-base-task-command task-view-match "Done Unclosed" ''nd/skip-non-done-unclosed-todoitems)
,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC+Project_Type=\"series\"/!" "Archivable Series" :archivable) ,(nd/agenda-base-project-command project-view-match "Undone Completed" :undone-complete)
,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC/" "Archivable" :archivable))))) ,(nd/agenda-base-project-command project-view-match "Done Incompleted" :done-incomplete)))
("A"
"Archivable Tasks and Projects"
(,(nd/agenda-base-task-command task-view-match "Archivable Atomic" ''nd/skip-non-archivable-atomic-tasks)
,(nd/agenda-base-task-command task-view-match "Stale" ''nd/skip-non-stale-headings)
,(nd/agenda-base-project-command series-view-match "Archivable Series" :archivable)
,(nd/agenda-base-project-command project-view-match "Archivable" :archivable))))))
(evil-define-key 'motion org-agenda-mode-map "T" 'nd/toggle-project-toplevel-display) (evil-define-key 'motion org-agenda-mode-map "T" 'nd/toggle-project-toplevel-display)
@ -781,6 +835,47 @@ test-fun return true"
(setq org-agenda-auto-exclude-function 'nd/org-auto-exclude-function) (setq org-agenda-auto-exclude-function 'nd/org-auto-exclude-function)
(setq org-columns-default-format
"%25ITEM %4TODO %TAGS %3Effort{+} %OWNER(OWN)")
(set-face-attribute 'org-column nil :background "#1e2023")
;; org-columns-summary-types
(defun nd/mark-subtree-keyword (new-keyword &optional exclude)
"marks all tasks in a subtree with keyword unless original keyword
is in the optional argument exclude"
(let ((subtree-end (save-excursion (org-end-of-subtree t))))
(if (not (listp exclude))
(error "exlude must be a list if provided"))
(save-excursion
(while (< (point) subtree-end)
(let ((keyword (nd/is-todoitem-p)))
(if (and keyword (not (member keyword exclude)))
(org-todo new-keyword)))
(outline-next-heading)))))
(defun nd/mark-subtree-done ()
"marks all tasks in subtree as DONE unless they are already canc"
(interactive)
(nd/mark-subtree-keyword "DONE" '("CANC")))
(defun nd/org-clone-subtree-with-time-shift-reset (n &optional shift)
"Like `org-clone-subtree-with-time-shift' except it resets checkboxes
and reverts all todo keywords to TODO"
(interactive "nNumber of clones to produce: ")
(let ((shift (read-from-minibuffer
"Date shift per clone (e.g. +1w, empty to copy unchanged): ")))
(condition-case err
(progn
(org-clone-subtree-with-time-shift n shift)
(save-excursion
(dotimes (i n)
(org-forward-heading-same-level 1 t)
(org-reset-checkbox-state-subtree)
(nd/mark-subtree-keyword "TODO")
(org-cycle))))
(error (message "%s" (error-message-string err))))))
(use-package calfw-org (use-package calfw-org
:init :init
:ensure t :ensure t

429
conf.org
View File

@ -369,6 +369,27 @@ vim is all about escape, not...ctrl+g???
(delight 'org-indent-mode) (delight 'org-indent-mode)
(setq org-directory "~/Org") (setq org-directory "~/Org")
#+END_SRC #+END_SRC
** ui
*** bullets
#+BEGIN_SRC emacs-lisp
(use-package org-bullets
:ensure t
:config
(add-hook 'org-mode-hook (lambda () (org-bullets-mode))))
#+END_SRC
*** font height
the fonts in org headings bug me, make them smaller and less invasive
#+BEGIN_SRC emacs-lisp
(defun nd/org-ui-heading-same-font-height ()
(let ((heading-height 1.15))
(set-face-attribute 'org-level-1 nil :weight 'bold :height heading-height)
(set-face-attribute 'org-level-2 nil :weight 'semi-bold :height heading-height)
(set-face-attribute 'org-level-3 nil :weight 'normal :height heading-height)
(set-face-attribute 'org-level-4 nil :weight 'normal :height heading-height)
(set-face-attribute 'org-level-5 nil :weight 'normal :height heading-height)))
(add-hook 'org-mode-hook 'nd/org-ui-heading-same-font-height)
#+END_SRC
** evil modes ** evil modes
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
;;(add-hook 'org-capture-mode-hook 'evil-append) ;;(add-hook 'org-capture-mode-hook 'evil-append)
@ -386,12 +407,19 @@ vim is all about escape, not...ctrl+g???
(setq org-special-ctrl-k t) (setq org-special-ctrl-k t)
(setq org-yank-adjusted-subtrees t) (setq org-yank-adjusted-subtrees t)
#+END_SRC #+END_SRC
*** custom
#+BEGIN_SRC emacs-lisp
(add-hook 'org-mode-hook
(lambda ()
(local-set-key (kbd "C-c C-x x") 'nd/mark-subtree-done)
(local-set-key (kbd "C-c C-x c") 'nd/org-clone-subtree-with-time-shift-reset)))
#+END_SRC
** todo states ** todo states
*** sequences *** sequences
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
(setq org-todo-keywords (setq org-todo-keywords
'((sequence "TODO(t)" "NEXT(n)" "|" "DONE(d)") '((sequence "TODO(t)" "NEXT(n)" "|" "DONE(d)")
(sequence "WAITING(w@/!)" "HOLD(h@/!)" "|" "CANCELLED(c@/!)"))) (sequence "WAIT(w@/!)" "HOLD(h@/!)" "|" "CANC(c@/!)")))
#+END_SRC #+END_SRC
*** colors *** colors
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
@ -399,40 +427,68 @@ vim is all about escape, not...ctrl+g???
(quote (("TODO" :foreground "light coral" :weight bold) (quote (("TODO" :foreground "light coral" :weight bold)
("NEXT" :foreground "khaki" :weight bold) ("NEXT" :foreground "khaki" :weight bold)
("DONE" :foreground "light green" :weight bold) ("DONE" :foreground "light green" :weight bold)
("WAITING" :foreground "orange" :weight bold) ("WAIT" :foreground "orange" :weight bold)
("HOLD" :foreground "violet" :weight bold) ("HOLD" :foreground "violet" :weight bold)
("CANCELLED" :foreground "deep sky blue" :weight bold)))) ("CANC" :foreground "deep sky blue" :weight bold))))
#+END_SRC #+END_SRC
** tags ** tags
I use tags for contexts (mostly). The "@" represents location contexts and a mutually exclusive as there is only one of me. The "#" contexts represent tools which must be available. I use tags for agenda filtering. Very fast and simple.
Each tag here starts with a symbol to define its group. Some groups are mutually exclusive, and each group has a different color.
Any tag that is not part of these groups (eg some filetags in the few cases I use those) is easy to distinguish as it has the default tag color and is all caps.
There are several types of tags I use:
- location: a GTD contexts; these start with "@"
- tools: also a GTD contexts; these start with "#"
- attribute: useful flags for filtering; these start with "%"
- life areas: key areas of life which define priorities and goals; these start with "_"
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
(setq org-tag-alist '((:startgroup) (setq org-tag-alist '((:startgroup)
("@errand" . ?e) ("@errand" . ?e)
("@work" . ?w) ("@work" . ?w)
("@home" . ?h) ("@home" . ?h)
("@travel" . ?f) ("@travel" . ?t)
(:endgroup) (:endgroup)
("#laptop" . ?L)
("#hood" . ?H)
("WORK" . ?W)
("PERSONAL" . ?P)
("NOTE" . ?N)
("FLAGGED" . ??)))
;; TODO I'm sure there is a better way to do this in lisp ("#laptop" . ?L)
(setq org-tag-faces ("#tcult" . ?T)
'(("@errand" . (:foreground "PaleGreen"))
("@work" . (:foreground "PaleGreen")) ("%note" . ?n)
("@home" . (:foreground "PaleGreen")) ("%subdiv" . ?s)
("@travel" . (:foreground "PaleGreen"))
("#laptop" . (:foreground "SkyBlue")) (:startgroup)
("#hood" . (:foreground "SkyBlue")))) ("_env" . ?E)
("_fin" . ?F)
("_int" . ?I)
("_met" . ?M)
("_phy" . ?H)
("_pro" . ?P)
("_rec" . ?R)
("_soc" . ?S)
(:endgroup)))
;; not the most elegant but this will work
(setq org-tag-faces '())
(defun nd/add-tag-face (fg-name start end)
"Adds list of cons cells to org-tag-faces with foreground set to fg-name.
Start and end specify the positions in org-tag-alist which define the tags
to which the faces are applied"
(dolist (tag (mapcar #'car (subseq org-tag-alist start end)))
(push `(,tag . (:foreground ,fg-name)) org-tag-faces)))
(nd/add-tag-face "PaleGreen" 1 5)
(nd/add-tag-face "SkyBlue" 6 8)
(nd/add-tag-face "PaleGoldenrod" 8 10)
(nd/add-tag-face "violet" 11 19)
#+END_SRC #+END_SRC
** properties ** properties
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
;; this is basically the same as putting the properties at the top of all org files (add-to-list 'org-default-properties "PROJECT_TYPE")
(add-to-list 'org-default-properties "Project_Type") (add-to-list 'org-default-properties "OWNER")
(setq org-global-properties '(("Project_Type_ALL" . "series"))) (setq org-global-properties
'(("Project_Type_ALL" . "series")
("Effort_ALL" . "00 10 30 60 90")))
;; TODO this may not be needed ;; TODO this may not be needed
(setq org-use-property-inheritance '("Project_Type")) (setq org-use-property-inheritance '("Project_Type"))
#+END_SRC #+END_SRC
@ -440,7 +496,7 @@ I use tags for contexts (mostly). The "@" represents location contexts and a mut
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
(setq org-capture-templates (setq org-capture-templates
'(("t" "todo" entry (file "~/Org/capture.org") "* TODO %?\ndeliverable: \n%U\n") '(("t" "todo" entry (file "~/Org/capture.org") "* TODO %?\ndeliverable: \n%U\n")
("n" "note" entry (file "~/Org/capture.org") "* %? :NOTE:\n%U\n" ) ("n" "note" entry (file "~/Org/capture.org") "* %? :\\%note:\n%U\n" )
("a" "appointment" entry (file "~/Org/capture.org") "* TODO %?\n%U\n%^t\n" ) ("a" "appointment" entry (file "~/Org/capture.org") "* TODO %?\n%U\n%^t\n" )
("m" "multi-day" entry (file "~/Org/capture.org") "* TODO %?\n%U\n%^t--%^t\n" ) ("m" "multi-day" entry (file "~/Org/capture.org") "* TODO %?\n%U\n%^t--%^t\n" )
("d" "deadline" entry (file "~/Org/capture.org") "* TODO %?\nDEADLINE: %^t\ndeliverable:\n%U\n" ) ("d" "deadline" entry (file "~/Org/capture.org") "* TODO %?\nDEADLINE: %^t\ndeliverable:\n%U\n" )
@ -481,19 +537,11 @@ I use tags for contexts (mostly). The "@" represents location contexts and a mut
(not (member (nth 2 (org-heading-components)) org-done-keywords))) (not (member (nth 2 (org-heading-components)) org-done-keywords)))
(setq org-refile-target-verify-function 'nd/verify-refile-target) (setq org-refile-target-verify-function 'nd/verify-refile-target)
#+END_SRC #+END_SRC
** ui
*** bullets
#+BEGIN_SRC emacs-lisp
(use-package org-bullets
:ensure t
:config
(add-hook 'org-mode-hook (lambda () (org-bullets-mode))))
#+END_SRC
** agenda ** agenda
*** basic config *** basic config
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
(setq org-agenda-files '("~/Org" (setq org-agenda-files '("~/Org"
"~/Org/large_projects" "~/Org/projects"
"~/Org/reference")) "~/Org/reference"))
;; (setq org-agenda-files '("~/Org/reference/agendatest.org")) ;; (setq org-agenda-files '("~/Org/reference/agendatest.org"))
(setq org-agenda-dim-blocked-tasks nil) (setq org-agenda-dim-blocked-tasks nil)
@ -501,72 +549,62 @@ I use tags for contexts (mostly). The "@" represents location contexts and a mut
#+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.
**** timestamps
Each of these returns the timestamp if found.
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
(defun nd/is-todoitem-p () (defun nd/get-date-property (date-property)
"return todo keyword if present in headline (which defines the heading as a todoitem) "Helper function to get the date property and convert to a number.
this is used to both test if a heading is a todoitem and retrieving the keyword" If it does not have a date, it will return nil."
(let ((keyword (nth 2 (org-heading-components)))) (let ((timestamp (org-entry-get nil date-property)))
(if (member keyword org-todo-keywords-1) (if timestamp (float-time (date-to-time timestamp)))))
keyword)))
(defun nd/is-project-p () (defun nd/is-timestamped-heading-p ()
"return todo keyword if heading is todoitem and has children" (nd/get-date-property "TIMESTAMP"))
(and (nd/heading-has-children) (nd/is-todoitem-p)))
(defun nd/is-task-p ()
"return todo keyword if heading is todoitem with no children"
(and (not (nd/heading-has-children)) (nd/is-todoitem-p)))
(defun nd/is-atomic-task-p ()
"return todo keyword if heading is task with no parents"
(and (not (nd/heading-has-parent)) (nd/is-task-p)))
(defun nd/is-project-task-p ()
"return todo keyword if heading is task with parents"
(and (nd/heading-has-parent) (nd/is-task-p)))
(defun nd/is-scheduled-heading-p () (defun nd/is-scheduled-heading-p ()
"return timestamp if headline is scheduled" (nd/get-date-property "SCHEDULED"))
(org-entry-get nil "SCHEDULED"))
(defun nd/is-series-header-p () (defun nd/is-deadlined-heading-p ()
"return t if headline has property Project_Type=series" (nd/get-date-property "DEADLINE"))
(equal "series" (org-entry-get nil "Project_Type")))
(defun nd/is-closed-heading-p () (defun nd/is-closed-heading-p ()
"return timestamp if headline is closed" (nd/get-date-property "CLOSED"))
(let ((timestamp (org-entry-get nil "CLOSED")))
(if timestamp (float-time (date-to-time timestamp))))) (defun nd/is-stale-heading-p ()
(let ((timestamp (nd/is-timestamped-heading-p)))
(if (and timestamp (> (- (float-time) timestamp) 0))
timestamp)))
(defvar nd/archive-delay-days 30 (defvar nd/archive-delay-days 30
"the number of days to wait before tasks show up in the archive view") "the number of days to wait before tasks show up in the archive view")
(defun nd/is-archivable-heading-p () (defun nd/is-archivable-heading-p ()
"return timestamp if todoitem is closed and older than specified time"
(let ((timestamp (nd/is-closed-heading-p))) (let ((timestamp (nd/is-closed-heading-p)))
;; NOTE we do not ensure that the todo state is in done keywords
;; this is to allow easier error correction in slip functions
(if (and timestamp (> (- (float-time) timestamp) (* 60 60 24 nd/archive-delay-days))) (if (and timestamp (> (- (float-time) timestamp) (* 60 60 24 nd/archive-delay-days)))
timestamp))) timestamp)))
#+END_SRC
(defun nd/is-archivable-atomic-task-p () **** task level testing
"return keyword if heading is an archivable task" #+BEGIN_SRC emacs-lisp
(and (nd/is-archivable-heading-p) (nd/is-atomic-task-p))) (defun nd/is-todoitem-p ()
(let ((keyword (nth 2 (org-heading-components))))
(defun nd/is-archivable-project-p () (if (member keyword org-todo-keywords-1)
"return keyword if heading is an archivable task"
(and (nd/is-archivable-heading-p) (nd/is-project-p)))
(defun nd/is-active-task-p ()
"return keyword if task is either NEXT or scheduled"
(let ((keyword (nd/is-task-p)))
(if (or (equal keyword "NEXT") (nd/is-scheduled-heading-p))
keyword))) keyword)))
(defun nd/is-blocked-task-p () (defun nd/is-project-p ()
"return keyword if task is WAITING" (and (nd/heading-has-children) (nd/is-todoitem-p)))
(equal (nd/is-task-p) "WAITING"))
(defun nd/is-task-p ()
(and (not (nd/heading-has-children)) (nd/is-todoitem-p)))
(defun nd/is-atomic-task-p ()
(and (not (nd/heading-has-parent)) (nd/is-task-p)))
(defun nd/is-series-heading-p ()
"return t if headline has property Project_Type=series"
(equal "series" (org-entry-get nil "Project_Type" t)))
#+END_SRC
**** relational testing
#+BEGIN_SRC emacs-lisp
(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
@ -599,12 +637,13 @@ These are the building blocks for skip functions.
(setq has-todoitem-parent t) (setq has-todoitem-parent t)
(setq has-non-todoitem-parent t)))) (setq has-non-todoitem-parent t))))
(and has-todoitem-parent has-non-todoitem-parent))) (and has-todoitem-parent has-non-todoitem-parent)))
#+END_SRC
**** project level testing
#+BEGIN_SRC emacs-lisp
(defconst nd/project-invalid-todostates (defconst nd/project-invalid-todostates
'("WAITING" "NEXT") '("WAIT" "NEXT")
"projects cannot have these todostates") "projects cannot have these todostates")
;; project level testing
(defconst nd/project-statuscodes (defconst nd/project-statuscodes
'(:archivable '(:archivable
:complete :complete
@ -656,7 +695,7 @@ These are the building blocks for skip functions.
(if (nd/heading-has-children) (if (nd/heading-has-children)
(cond ((member keyword nd/project-invalid-todostates) :invalid-todostate) (cond ((member keyword nd/project-invalid-todostates) :invalid-todostate)
((nd/is-scheduled-heading-p) :scheduled-project) ((nd/is-scheduled-heading-p) :scheduled-project)
((equal keyword "CANCELLED") (if (nd/is-archivable-heading-p) ((equal keyword "CANC") (if (nd/is-archivable-heading-p)
:archivable :archivable
:complete)) :complete))
((equal keyword "HOLD") :held) ((equal keyword "HOLD") :held)
@ -670,11 +709,11 @@ These are the building blocks for skip functions.
(:archivable (if (nd/is-archivable-heading-p) (:archivable (if (nd/is-archivable-heading-p)
:archivable :archivable
:complete)) :complete))
(t (if (= child-statuscode :complete) (t (if (nd/status= child-statuscode :complete)
:complete :complete
:done-imcomplete)))))))) :done-incomplete))))))))
(cond ((equal keyword "HOLD") :held) (cond ((equal keyword "HOLD") :held)
((equal keyword "WAITING") :waiting) ((equal keyword "WAIT") :waiting)
((equal keyword "NEXT") :active) ((equal keyword "NEXT") :active)
((and (equal keyword "TODO") (nd/is-scheduled-heading-p)) :active) ((and (equal keyword "TODO") (nd/is-scheduled-heading-p)) :active)
((equal keyword "TODO") :stuck) ((equal keyword "TODO") :stuck)
@ -699,17 +738,17 @@ These are the building blocks for skip functions.
Note that this assumes the headline being tested is a valid project" Note that this assumes the headline being tested is a valid project"
(case statuscode (case statuscode
;; projects closed more than 30 days ago ;; projects closed more than 30 days ago
;; note CANCELLED overrides all subtasks/projects ;; note CANC overrides all subtasks/projects
(:archivable (:archivable
(if (nd/is-archivable-heading-p) (if (nd/is-archivable-heading-p)
(or (equal keyword "CANCELLED") (or (equal keyword "CANC")
(nd/is-project-keyword-status-p "DONE" = :archivable)))) (nd/is-project-keyword-status-p "DONE" = :archivable))))
;; projects closed less than 30 days ago ;; projects closed less than 30 days ago
;; note CANCELLED overrides all subtasks/projects ;; note CANC overrides all subtasks/projects
(:complete (:complete
(if (not (nd/is-archivable-heading-p)) (if (not (nd/is-archivable-heading-p))
(or (equal keyword "CANCELLED") (or (equal keyword "CANC")
(nd/is-project-keyword-status-p "DONE" = :complete)))) (nd/is-project-keyword-status-p "DONE" = :complete))))
;; projects with no waiting, held, or active components ;; projects with no waiting, held, or active components
@ -762,7 +801,7 @@ tags in the custom commands section but I find this easier to maintain and possi
(save-excursion (or (org-end-of-subtree t) (point-max)))) (save-excursion (or (org-end-of-subtree t) (point-max))))
(defconst nd/project-skip-todostates (defconst nd/project-skip-todostates
'("HOLD" "CANCELLED") '("HOLD" "CANC")
"These keywords override all contents within their subtrees. "These keywords override all contents within their subtrees.
Currently used to tell skip functions when they can hop over Currently used to tell skip functions when they can hop over
entire subtrees to save time and ignore tasks") entire subtrees to save time and ignore tasks")
@ -780,27 +819,50 @@ tags in the custom commands section but I find this easier to maintain and possi
(message keyword) (message keyword)
(if (not (and keyword ,test-fun)) (if (not (and keyword ,test-fun))
(nd/skip-item))))) (nd/skip-item)))))
;; stale headings
;; For archiving headings with old timestamps
;; Note that these are not always todo items
;; I only care about those that are not part
;; of projects (projects will get taken care
;; of when the entire project is finished)
;; and those that are not DONE/CANC (as
;; those appear in the regular archive
;; section)
(defun nd/skip-non-stale-headings ()
(save-restriction
(widen)
(let ((keyword (nd/is-todoitem-p)))
(if (not
(and (nd/is-stale-heading-p)
(not (member keyword org-done-keywords))
(not (nd/heading-has-children))
(not (nd/heading-has-parent))))
(nd/skip-item)))))
;; atomic tasks ;; atomic tasks
;; by definition these have no parents, so ;; by definition these have no parents, so
;; we don't need to worry about skipping over projects ;; we don't need to worry about skipping over projects
;; any todo state is valid and we only sort by done/cancelled ;; any todo state is valid and we only sort by done/canc
(defun nd/skip-non-unclosed-atomic-tasks () (defun nd/skip-non-unclosed-atomic-tasks ()
(nd/skip-heading-with (nd/skip-heading-with
nd/is-atomic-task-p nd/is-atomic-task-p
(not (member keyword org-done-keywords)))) (and (not (nd/is-timestamped-heading-p))
(not (nd/is-scheduled-heading-p))
(not (nd/is-deadlined-heading-p))
(not (member keyword org-done-keywords)))))
(defun nd/skip-non-closed-atomic-tasks () (defun nd/skip-non-closed-atomic-tasks ()
(nd/skip-heading-with (nd/skip-heading-with
nd/is-atomic-task-p nd/is-atomic-task-p
(and (member keyword org-done-keywords) (and (member keyword org-done-keywords)
(not (nd/is-archivable-heading))))) (not (nd/is-archivable-heading-p)))))
(defun nd/skip-non-archivable-atomic-tasks () (defun nd/skip-non-archivable-atomic-tasks ()
(nd/skip-heading-with (nd/skip-heading-with
nd/is-atomic-task-p nd/is-atomic-task-p
(and (member keyword org-done-keywords) (and (member keyword org-done-keywords)
(nd/is-archivable-heading)))) (nd/is-archivable-heading-p))))
;; project tasks ;; project tasks
;; since these are part of projects I need to assess ;; since these are part of projects I need to assess
@ -809,7 +871,7 @@ tags in the custom commands section but I find this easier to maintain and possi
;; Note that I only care about the keyword in these ;; Note that I only care about the keyword in these
;; cases because I don't archive these, I archive ;; cases because I don't archive these, I archive
;; their parent projects. The keywords I care about ;; their parent projects. The keywords I care about
;; are NEXT, WAITING, and HOLD because these are ;; are NEXT, WAIT, and HOLD because these are
;; definitive project tasks that require/inhibit ;; definitive project tasks that require/inhibit
;; futher action ;; futher action
(defun nd/skip-non-keyword-project-tasks (skip-keyword) (defun nd/skip-non-keyword-project-tasks (skip-keyword)
@ -822,6 +884,9 @@ tags in the custom commands section but I find this easier to maintain and possi
(nd/skip-subtree) (nd/skip-subtree)
(nd/skip-item)) (nd/skip-item))
(if (not (and (nd/heading-has-parent) (if (not (and (nd/heading-has-parent)
(not (nd/is-timestamped-heading-p))
(not (nd/is-scheduled-heading-p))
(not (nd/is-deadlined-heading-p))
(equal keyword skip-keyword))) (equal keyword skip-keyword)))
(nd/skip-item))) (nd/skip-item)))
(nd/skip-item))))) (nd/skip-item)))))
@ -854,14 +919,11 @@ tags in the custom commands section but I find this easier to maintain and possi
(save-restriction (save-restriction
(widen) (widen)
(let ((keyword (nd/is-project-p))) (let ((keyword (nd/is-project-p)))
;; TODO there may be a way to skip over skippable projects
;; and save a few cycles. Not a huge deal, but would require
;; keeping the skippable line and then skipping over the others
;; in one fell swoop, not easy to do efficiently
(if keyword (if keyword
(if (not (nd/is-project-status-p statuscode)) (if (and nd/agenda-limit-project-toplevel
(if nd/agenda-limit-project-toplevel (nd/heading-has-parent))
(nd/skip-subtree) (nd/skip-subtree)
(if (not (nd/is-project-status-p statuscode))
(nd/skip-item))) (nd/skip-item)))
(nd/skip-item))))) (nd/skip-item)))))
#+END_SRC #+END_SRC
@ -873,71 +935,83 @@ tags in the custom commands section but I find this easier to maintain and possi
(defun nd/toggle-project-toplevel-display () (defun nd/toggle-project-toplevel-display ()
(interactive) (interactive)
(setq nd/agenda-limit-project-toplevel (not nd/agenda-limit-project-toplevel)) (setq nd/agenda-limit-project-toplevel (not nd/agenda-limit-project-toplevel))
(when (equal major-mode 'org-agenda-mode) (when (equal major-mode 'org-agenda-mode)
(org-agenda-redo)) (org-agenda-redo))
(message "Showing %s project view in agenda" (if nd/agenda-limit-project-toplevel "toplevel" "complete"))) (message "Showing %s project view in agenda"
(if nd/agenda-limit-project-toplevel "toplevel" "complete")))
#+END_SRC
*** custom commands
#+BEGIN_SRC emacs-lisp
(setq org-agenda-tags-todo-honor-ignore-options t)
(defun nd/agenda-base-task-command (keyword skip-fun) (setq org-agenda-prefix-format
'((agenda . " %-12:c%-5:e%?-12t% s")
(timeline . " % s")
(todo . " %-12:c")
(tags . " %-12:c%-5:e")
(search . " %-12:c")))
(defun nd/agenda-base-task-command (match keyword skip-fun)
"shorter syntax to define task agenda commands" "shorter syntax to define task agenda commands"
`(tags `(tags
"-NA-REFILE/" ,match
((org-agenda-overriding-header (concat ,keyword " Tasks")) ((org-agenda-overriding-header (concat ,keyword " Tasks"))
(org-agenda-skip-function ,skip-fun) (org-agenda-skip-function ,skip-fun)
(org-agenda-todo-ignore-with-date 'all)
(org-agenda-sorting-strategy '(category-keep))))) (org-agenda-sorting-strategy '(category-keep)))))
(defun nd/agenda-base-project-command (match keyword statuscode) (defun nd/agenda-base-project-command (match keyword statuscode)
"shorter syntax to define project agenda commands" "shorter syntax to define project agenda commands"
`(tags `(tags
,match ,match
((org-agenda-overriding-header (concat ((org-agenda-overriding-header
(and nd/agenda-limit-project-toplevel "Toplevel ") (concat (and nd/agenda-limit-project-toplevel "Toplevel ") ,keyword " Projects"))
,keyword
" Projects"))
(org-agenda-skip-function '(nd/skip-projects-without-statuscode ,statuscode)) (org-agenda-skip-function '(nd/skip-projects-without-statuscode ,statuscode))
(org-agenda-sorting-strategy '(category-keep))))) (org-agenda-sorting-strategy '(category-keep)))))
#+END_SRC (let ((task-view-match "-NA-REFILE")
*** custom commands (project-view-match "-NA-REFILE-Project_Type=\"series\"/")
#+BEGIN_SRC emacs-lisp (series-view-match "-NA-REFILE+Project_Type=\"series\"/"))
(setq org-agenda-tags-todo-honor-ignore-options t) (setq org-agenda-custom-commands
(setq org-agenda-custom-commands `(("t"
`(("t" "Task View"
"Task View" ((agenda "" nil)
((agenda "" nil) ,(nd/agenda-base-task-command task-view-match "Next Project" ''(nd/skip-non-keyword-project-tasks "NEXT"))
,(nd/agenda-base-task-command "Next Project" ''(nd/skip-non-keyword-project-tasks "NEXT")) ,(nd/agenda-base-task-command task-view-match "Waiting Project" ''(nd/skip-non-keyword-project-tasks "WAIT"))
,(nd/agenda-base-task-command "Waiting Project" ''(nd/skip-non-keyword-project-tasks "WAITING")) ,(nd/agenda-base-task-command task-view-match "Atomic" ''nd/skip-non-unclosed-atomic-tasks)
,(nd/agenda-base-task-command "Atomic" ''nd/skip-non-unclosed-atomic-tasks) ,(nd/agenda-base-task-command task-view-match "Held Project" ''(nd/skip-non-keyword-project-tasks "HOLD"))))
,(nd/agenda-base-task-command "Held Project" ''(nd/skip-non-keyword-project-tasks "HOLD")))) ("p"
("o" "Project View"
"Project Overview" (,(nd/agenda-base-project-command project-view-match "Stuck" :stuck)
(,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC-Project_Type=\"series\"/!" "Stuck" :stuck) ,(nd/agenda-base-project-command project-view-match "Waiting" :waiting)
,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC-Project_Type=\"series\"/!" "Waiting" :waiting) ,(nd/agenda-base-project-command project-view-match "Active" :active)
,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC-Project_Type=\"series\"/!" "Active" :active) ,(nd/agenda-base-project-command project-view-match "Held" :held)))
,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC-Project_Type=\"series\"/!" "Held" :held))) ("s"
("r" "Series View"
"Refile and errors" (,(nd/agenda-base-project-command series-view-match "Stuck Series" :stuck)
((tags "REFILE" ,(nd/agenda-base-project-command series-view-match "Empty Series" :undone-complete)
((org-agenda-overriding-header "Tasks to Refile")) ,(nd/agenda-base-project-command series-view-match "Active Series" :active)
(org-tags-match-list-sublevels nil)) ,(nd/agenda-base-project-command series-view-match "Waiting Series" :waiting)
,(nd/agenda-base-task-command "Discontinous Project" ''nd/skip-non-discontinuous-project-tasks) ,(nd/agenda-base-project-command series-view-match "Held Series" :held)
,(nd/agenda-base-task-command "Undone Closed" ''nd/skip-non-undone-closed-todoitems) ,(nd/agenda-base-task-command series-view-match "Uninitialized Series" ''nd/skip-non-series-atomic-tasks)))
,(nd/agenda-base-task-command "Done Unclosed" ''nd/skip-non-done-unclosed-todoitems) ("r"
,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC-Project_Type=\"series\"/" "Undone Completed" :undone-complete) "Refile and Critical Errors"
,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC-Project_Type=\"series\"/" "Done Incompleted" :done-incomplete) ((tags "REFILE"
,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC-Project_Type=\"series\"/" "Invalid Todostate" :invalid-todostate))) ((org-agenda-overriding-header "Tasks to Refile"))
("s" (org-tags-match-list-sublevels nil))
"Series projects" ,(nd/agenda-base-task-command task-view-match "Discontinous Project" ''nd/skip-non-discontinuous-project-tasks)
(,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC+Project_Type=\"series\"/!" "Active Series" :active) ,(nd/agenda-base-project-command project-view-match "Invalid Todostate" :invalid-todostate)))
,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC+Project_Type=\"series\"/!" "Empty Series" :complete))) ("e"
("A" "Non-critical Errors"
"Archivable Tasks and Projects" (,(nd/agenda-base-task-command task-view-match "Undone Closed" ''nd/skip-non-undone-closed-todoitems)
((tags "-NA-REFILE/" ,(nd/agenda-base-task-command task-view-match "Done Unclosed" ''nd/skip-non-done-unclosed-todoitems)
((org-agenda-overriding-header "Atomic Tasks to Archive") ,(nd/agenda-base-project-command project-view-match "Undone Completed" :undone-complete)
(org-agenda-skip-function 'nd/skip-non-archivable-atomic-tasks) ,(nd/agenda-base-project-command project-view-match "Done Incompleted" :done-incomplete)))
(org-tags-match-list-sublevels nil))) ("A"
,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC+Project_Type=\"series\"/!" "Archivable Series" :archivable) "Archivable Tasks and Projects"
,(nd/agenda-base-project-command "-NA-REFILE-ATOMIC/" "Archivable" :archivable))))) (,(nd/agenda-base-task-command task-view-match "Archivable Atomic" ''nd/skip-non-archivable-atomic-tasks)
,(nd/agenda-base-task-command task-view-match "Stale" ''nd/skip-non-stale-headings)
,(nd/agenda-base-project-command series-view-match "Archivable Series" :archivable)
,(nd/agenda-base-project-command project-view-match "Archivable" :archivable))))))
#+END_SRC #+END_SRC
*** keymap *** keymap
@ -972,6 +1046,51 @@ the agenda does not do this by default...it's annoying
(setq org-agenda-auto-exclude-function 'nd/org-auto-exclude-function) (setq org-agenda-auto-exclude-function 'nd/org-auto-exclude-function)
#+END_SRC #+END_SRC
** column_view
#+BEGIN_SRC emacs-lisp
(setq org-columns-default-format
"%25ITEM %4TODO %TAGS %3Effort{+} %OWNER(OWN)")
(set-face-attribute 'org-column nil :background "#1e2023")
;; org-columns-summary-types
#+END_SRC
** interactive functions
#+BEGIN_SRC emacs-lisp
(defun nd/mark-subtree-keyword (new-keyword &optional exclude)
"marks all tasks in a subtree with keyword unless original keyword
is in the optional argument exclude"
(let ((subtree-end (save-excursion (org-end-of-subtree t))))
(if (not (listp exclude))
(error "exlude must be a list if provided"))
(save-excursion
(while (< (point) subtree-end)
(let ((keyword (nd/is-todoitem-p)))
(if (and keyword (not (member keyword exclude)))
(org-todo new-keyword)))
(outline-next-heading)))))
(defun nd/mark-subtree-done ()
"marks all tasks in subtree as DONE unless they are already canc"
(interactive)
(nd/mark-subtree-keyword "DONE" '("CANC")))
(defun nd/org-clone-subtree-with-time-shift-reset (n &optional shift)
"Like `org-clone-subtree-with-time-shift' except it resets checkboxes
and reverts all todo keywords to TODO"
(interactive "nNumber of clones to produce: ")
(let ((shift (read-from-minibuffer
"Date shift per clone (e.g. +1w, empty to copy unchanged): ")))
(condition-case err
(progn
(org-clone-subtree-with-time-shift n shift)
(save-excursion
(dotimes (i n)
(org-forward-heading-same-level 1 t)
(org-reset-checkbox-state-subtree)
(nd/mark-subtree-keyword "TODO")
(org-cycle))))
(error (message "%s" (error-message-string err))))))
#+END_SRC
** caldav ** caldav
+BEGIN_SRC emacs-lisp +BEGIN_SRC emacs-lisp
(use-package org-caldav (use-package org-caldav