Complete Computing Environment: Agenda

Table of Contents

Coalescing Org Mode tasks

Having a huge tree of work is daunting and unproductive. Where do you dive in when you have a huge firehose of information staring you in the face? There are two mains ways to solve this problem with Org mode, either via sparse trees or block agendas.

Sparse trees are interesting; they allow you to filter an Org mode tree based on given tags, dates, etc, but I haven't put nearly as much exploration in to this as I should.

Instead of this I've chosen to take on a path of relying largely on block agendas, and the creation of custom block agendas to build out a sort of template of things to work on. In the effort of aiding this, I've inherited a bunch of custom filter functions written for the awesome literate Org mode configuration made by Bernt Hansen1. These functions serve as the base definition of what a Project is, as well as the basic flow of tasks that he uses. These should be updated, when I've defined it, to reflect how I work.

(provide 'cce-agenda)

Core ideas:

  • Investigate and integrate sparse trees more deeply in to my workflow.
  • Creating Agendas to manage and group aspects of my work should be intuitive and easy to do. If I have a new project that I am working on, generating a workboard for it should be simple and manageable with five minutes or less of setup.
  • Agendas should be accessible from more than my main computing devices. If I were to want my daily agenda on my Kindle every morning, my environment should allow for that, by automatically exporting and sending to the kindle that document.

Basic Agenda Configuration

We want to, by default, load all of ~/org by default. This is a bit heavy handed, but proper Agendas and Filtering make this an Okay thing to do.

(setq org-agenda-files '())
(when (file-exists-p "~/org")
  (add-to-list 'org-agenda-files "~/org" t))
(when (file-exists-p "~/Projects/fsem")
  (add-to-list 'org-agenda-files "~/sync/cce" t))
(when (file-exists-p "~/Projects/projects.rix.si/projects")
  (add-to-list 'org-agenda-files "~/Projects/projects.rix.si/projects" t))
(when (file-exists-p "~/Projects/notes")
  (add-to-list 'org-agenda-files "~/Projects/notes" t))
(when (file-exists-p "~/org/memacs")
  (add-to-list 'org-agenda-files "~/org/memacs" t))

(setq org-directory "~/org")

Agenda Filtering Functions

These functions are a bunch of filtering functions that infer information about tasks and use that to augment and filter the Agenda. To stick with my new understanding of things, they may need to be updated.

(defun rrix/org-agenda-skip-tag-checker (tags-to-check heading-tags)
  (if tags-to-check
      (or (member (car tags-to-check) heading-tags)
          (rrix/org-agenda-skip-tag-checker (cdr tags-to-check) heading-tags))))

(defun zin/org-agenda-skip-tag (tags &optional others)
  "Skip all entries that correspond to TAG.

If OTHERS is true, skip all entries that do not correspond to TAG."
  (let* ((next-headline (save-excursion (or (outline-next-heading) (point-max))))
         (current-headline (or (and (org-at-heading-p) (point))
                               (save-excursion (org-back-to-heading)))))
    (if others
        (if (not (rrix/org-agenda-skip-tag-checker tags (org-get-tags-at current-headline)))
            next-headline
          nil)
      (if (rrix/org-agenda-skip-tag-checker tags (org-get-tags-at current-headline))
          next-headline
        nil))))
(defun bh/is-project-p ()
  "Any task with a todo keyword subtask"
  (save-restriction
    (widen)
    (let ((has-subtask)
          (subtree-end (save-excursion (org-end-of-subtree t)))
          (is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1)))
      (save-excursion
        (forward-line 1)
        (while (and (not has-subtask)
                    (< (point) subtree-end)
                    (re-search-forward "^\*+ " subtree-end t))
          (when (member (org-get-todo-state) org-todo-keywords-1)
            (setq has-subtask t))))
      (and is-a-task has-subtask))))
(defun bh/is-project-subtree-p ()
  "Any task with a todo keyword that is in a project subtree.
Callers of this function already widen the buffer view."
  (let ((task (save-excursion (org-back-to-heading 'invisible-ok)
                              (point))))
    (save-excursion
      (bh/find-project-task)
      (if (equal (point) task)
          nil
        t))))
(defun bh/is-task-p ()
  "Any task with a todo keyword and no subtask"
  (save-restriction
    (widen)
    (let ((has-subtask)
          (subtree-end (save-excursion (org-end-of-subtree t)))
          (is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1)))
      (save-excursion
        (forward-line 1)
        (while (and (not has-subtask)
                    (< (point) subtree-end)
                    (re-search-forward "^\*+ " subtree-end t))
          (when (member (org-get-todo-state) org-todo-keywords-1)
            (setq has-subtask t))))
      (and is-a-task (not has-subtask)))))
(defun bh/is-subproject-p ()
  "Any task which is a subtask of another project"
  (let ((is-subproject)
        (is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1)))
    (save-excursion
      (while (and (not is-subproject) (org-up-heading-safe))
        (when (member (nth 2 (org-heading-components)) org-todo-keywords-1)
          (setq is-subproject t))))
    (and is-a-task is-subproject)))
(defun bh/list-sublevels-for-projects-indented ()
  "Set org-tags-match-list-sublevels so when restricted to a subtree we list all subtasks.
  This is normally used by skipping functions where this variable is already local to the agenda."
  (if (marker-buffer org-agenda-restrict-begin)
      (setq org-tags-match-list-sublevels 'indented)
    (setq org-tags-match-list-sublevels nil))
  nil)
(defun bh/list-sublevels-for-projects ()
  "Set org-tags-match-list-sublevels so when restricted to a subtree we list all subtasks.
  This is normally used by skipping functions where this variable is already local to the agenda."
  (if (marker-buffer org-agenda-restrict-begin)
      (setq org-tags-match-list-sublevels t)
    (setq org-tags-match-list-sublevels nil))
  nil)
(defun bh/skip-stuck-projects ()
  "Skip trees that are not stuck projects"
  (save-restriction
    (widen)
    (let ((next-headline (save-excursion (or (outline-next-heading) (point-max)))))
      (if (bh/is-project-p)
          (let* ((subtree-end (save-excursion (org-end-of-subtree t)))
                 (has-next))
            (save-excursion
              (forward-line 1)
              (while (and (not has-next)
                          (< (point) subtree-end)
                          (or (re-search-forward "^\\*+ NEXT " subtree-end t)
                              (re-search-forward "^\\*+ INPROGRESS " subtree-end t)))
                (unless (member "WAITING" (org-get-tags-at))
                  (setq has-next t))))
            (if has-next
                nil
              next-headline)) ; a stuck project, has subtasks but no next task
        nil))))
(defun bh/skip-non-stuck-projects ()
  "Skip trees that are not stuck projects"
  ;; (bh/list-sublevels-for-projects-indented)
  (save-restriction
    (widen)
    (let ((next-headline (save-excursion (or (outline-next-heading) (point-max)))))
      (if (bh/is-project-p)
          (let* ((subtree-end (save-excursion (org-end-of-subtree t)))
                 (has-next))
            (save-excursion
              (forward-line 1)
              (while (and (not has-next)
                          (< (point) subtree-end)
                          (or (re-search-forward "^\\*+ NEXT " subtree-end t)
                              (re-search-forward "^\\*+ INPROGRESS " subtree-end t)))
                (unless (member "WAITING" (org-get-tags-at))
                  (setq has-next t))))
            (if has-next
                next-headline
              nil)) ; a stuck project, has subtasks but no next task
        next-headline))))
(defun bh/skip-non-projects ()
  "Skip trees that are not projects"
  ;; (bh/list-sublevels-for-projects-indented)
  (if (save-excursion (bh/skip-non-stuck-projects))
      (save-restriction
        (widen)
        (let ((subtree-end (save-excursion (org-end-of-subtree t))))
          (cond
           ((bh/is-project-p)
            nil)
           ((and (bh/is-project-subtree-p) (not (bh/is-task-p)))
            nil)
           (t
            subtree-end))))
    (save-excursion (org-end-of-subtree t))))
(defun bh/skip-project-trees-and-habits ()
  "Skip trees that are projects"
  (save-restriction
    (widen)
    (let ((subtree-end (save-excursion (org-end-of-subtree t))))
      (cond
       ((bh/is-project-p)
        subtree-end)
       ((org-is-habit-p)
        subtree-end)
       (t
        nil)))))
(defun bh/skip-projects-and-habits-and-single-tasks ()
  "Skip trees that are projects, tasks that are habits, single non-project tasks"
  (save-restriction
    (widen)
    (let ((next-headline (save-excursion (or (outline-next-heading) (point-max)))))
      (cond
       ((org-is-habit-p)
        next-headline)
       ((and bh/hide-scheduled-and-waiting-next-tasks
             (member "WAITING" (org-get-tags-at)))
        next-headline)
       ((bh/is-project-p)
        next-headline)
       ((and (bh/is-task-p) (not (bh/is-project-subtree-p)))
        next-headline)
       (t
        nil)))))
(defun bh/skip-project-tasks-maybe ()
  "Show tasks related to the current restriction.
When restricted to a project, skip project and sub project tasks, habits, NEXT tasks, and loose tasks.
When not restricted, skip project and sub-project tasks, habits, and project related tasks."
  (save-restriction
    (widen)
    (let* ((subtree-end (save-excursion (org-end-of-subtree t)))
           (next-headline (save-excursion (or (outline-next-heading) (point-max))))
           (limit-to-project (marker-buffer org-agenda-restrict-begin)))
      (cond
       ((bh/is-project-p)
        next-headline)
       ((org-is-habit-p)
        subtree-end)
       ((and (not limit-to-project)
             (bh/is-project-subtree-p))
        subtree-end)
       ((and limit-to-project
             (bh/is-project-subtree-p)
             (member (org-get-todo-state) (list "NEXT")))
        subtree-end)
       (t
        nil)))))
(defun bh/skip-project-tasks ()
  "Show non-project tasks.
Skip project and sub-project tasks, habits, and project related tasks."
  (save-restriction
    (widen)
    (let* ((subtree-end (save-excursion (org-end-of-subtree t))))
      (cond
       ((bh/is-project-p)
        subtree-end)
       ((org-is-habit-p)
        subtree-end)
       ((bh/is-project-subtree-p)
        subtree-end)
       (t
        nil)))))
(defun bh/skip-non-project-tasks ()
  "Show project tasks.
Skip project and sub-project tasks, habits, and loose non-project tasks."
  (save-restriction
    (widen)
    (let* ((subtree-end (save-excursion (org-end-of-subtree t)))
           (next-headline (save-excursion (or (outline-next-heading) (point-max)))))
      (cond
       ((bh/is-project-p)
        next-headline)
       ((org-is-habit-p)
        subtree-end)
       ((and (bh/is-project-subtree-p)
             (member (org-get-todo-state) (list "NEXT")))
        subtree-end)
       ((not (bh/is-project-subtree-p))
        subtree-end)
       (t
        nil)))))
(defun bh/skip-projects-and-habits ()
  "Skip trees that are projects and tasks that are habits"
  (save-restriction
    (widen)
    (let ((subtree-end (save-excursion (org-end-of-subtree t))))
      (cond
       ((bh/is-project-p)
        subtree-end)
       ((org-is-habit-p)
        subtree-end)
       (t
        nil)))))
(defun bh/skip-non-subprojects ()
  "Skip trees that are not projects"
  (let ((next-headline (save-excursion (outline-next-heading))))
    (if (bh/is-subproject-p)
        nil
      next-headline)))
(defun bh/skip-non-archivable-tasks ()
  "Skip trees that are not available for archiving"
  (save-restriction
    (widen)
    ;; Consider only tasks with done todo headings as archivable candidates
    (let ((next-headline (save-excursion (or (outline-next-heading) (point-max))))
          (subtree-end (save-excursion (org-end-of-subtree t))))
      (if (member (org-get-todo-state) org-todo-keywords-1)
          (if (member (org-get-todo-state) org-done-keywords)
              (let* ((daynr (string-to-int (format-time-string "%d" (current-time))))
                     (a-month-ago (* 60 60 24 (+ daynr 1)))
                     (last-month (format-time-string "%Y-%m-" (time-subtract (current-time) (seconds-to-time a-month-ago))))
                     (this-month (format-time-string "%Y-%m-" (current-time)))
                     (subtree-is-current (save-excursion
                                           (forward-line 1)
                                           (and (< (point) subtree-end)
                                                (re-search-forward (concat last-month "\\|" this-month) subtree-end t)))))
                (if subtree-is-current
                    subtree-end ; Has a date in this month or last month, skip it
                  nil))  ; available to archive
            (or subtree-end (point-max)))
        next-headline))))

Agenda Movement and Narrowing Hooks and Keys

Define some helper functions.

(defun bh/narrow-up-one-org-level ()
  (widen)
  (save-excursion
    (outline-up-heading 1 'invisible-ok)
    (bh/narrow-to-org-subtree)))

(defun bh/narrow-to-org-subtree ()
  (widen)
  (org-narrow-to-subtree)
  (save-restriction
    (org-agenda-set-restriction-lock)))

(defun bh/get-pom-from-agenda-restriction-or-point ()
  (or (org-get-at-bol 'org-hd-marker)
      (and (marker-position org-agenda-restrict-begin) org-agenda-restrict-begin)
      (and (equal major-mode 'org-mode) (point))
      org-clock-marker))

(defun bh/narrow-to-org-project ()
  (widen)
  (save-excursion
    (bh/find-project-task)
    (bh/narrow-to-org-subtree)))

Hitting F in Agenda will restrict the Agenda to the file the Entry lives in.

(add-hook 'org-agenda-mode-hook
          '(lambda () (org-defkey org-agenda-mode-map "F" 'bh/restrict-to-file-or-follow))
          'append)
(defun bh/restrict-to-file-or-follow (arg)
  "Set agenda restriction to 'file or with argument invoke follow mode.
I don't use follow mode very often but I restrict to file all the time
so change the default 'F' binding in the agenda to allow both"
  (interactive "p")
  (if (equal arg 4)
      (org-agenda-follow-mode)
    (widen)
    (bh/set-agenda-restriction-lock 4)
    (org-agenda-redo)
    (beginning-of-buffer)))

Hitting N in the Agenda will restrict the Agenda to the subtree of the Entry.

(add-hook 'org-agenda-mode-hook
          '(lambda () (org-defkey org-agenda-mode-map "N" 'bh/narrow-to-subtree))
          'append)
(defun bh/narrow-to-subtree ()
  (interactive)
  (if (equal major-mode 'org-agenda-mode)
      (progn
        (org-with-point-at (org-get-at-bol 'org-hd-marker)
          (bh/narrow-to-org-subtree))
        (when org-agenda-sticky
          (org-agenda-redo)))
    (bh/narrow-to-org-subtree)))

Hitting U in the Agenda will narrow an Agenda "up", ie the parent task will become the task to narrow upon.

(add-hook 'org-agenda-mode-hook
          '(lambda () (org-defkey org-agenda-mode-map "U" 'bh/narrow-up-one-level))
          'append)
(defun bh/narrow-up-one-level ()
  (interactive)
  (if (equal major-mode 'org-agenda-mode)
      (org-with-point-at (bh/get-pom-from-agenda-restriction-or-point)
        (bh/narrow-up-one-org-level))
    (bh/narrow-up-one-org-level)))

Hitting P narrows us to the Project a task is in.

(add-hook 'org-agenda-mode-hook
          '(lambda () (org-defkey org-agenda-mode-map "P" 'bh/narrow-to-project))
          'append)
(defun bh/narrow-to-project ()
  (interactive)
  (if (equal major-mode 'org-agenda-mode)
      (progn
        (org-with-point-at (bh/get-pom-from-agenda-restriction-or-point)
          (bh/narrow-to-org-project)
          (save-excursion
            (bh/find-project-task)
            (org-agenda-set-restriction-lock)))
        (org-agenda-redo)
        (beginning-of-buffer))
    (bh/narrow-to-org-project)
    (save-restriction
      (org-agenda-set-restriction-lock))))

Hitting V in an Agenda bounces it between an Agenda for each of my Projects

(add-hook 'org-agenda-mode-hook
          '(lambda () (org-defkey org-agenda-mode-map "V" 'bh/view-next-project))
          'append)
(defvar bh/project-list nil)
(defun bh/view-next-project ()
  (interactive)
  (let (num-project-left current-project)
    (unless (marker-position org-agenda-restrict-begin)
      (goto-char (point-min))
      ; Clear all of the existing markers on the list
      (while bh/project-list
        (set-marker (pop bh/project-list) nil))
      (re-search-forward "Tasks to Refile")
      (forward-visible-line 1))

    ; Build a new project marker list
    (unless bh/project-list
      (while (< (point) (point-max))
        (while (and (< (point) (point-max))
                    (or (not (org-get-at-bol 'org-hd-marker))
                        (org-with-point-at (org-get-at-bol 'org-hd-marker)
                          (or (not (bh/is-project-p))
                              (bh/is-project-subtree-p)))))
          (forward-visible-line 1))
        (when (< (point) (point-max))
          (add-to-list 'bh/project-list (copy-marker (org-get-at-bol 'org-hd-marker)) 'append))
        (forward-visible-line 1)))

    ; Pop off the first marker on the list and display
    (setq current-project (pop bh/project-list))
    (when current-project
      (org-with-point-at current-project
        (setq bh/hide-scheduled-and-waiting-next-tasks nil)
        (bh/narrow-to-project))
      ; Remove the marker
      (setq current-project nil)
      (org-agenda-redo)
      (beginning-of-buffer)
      (setq num-projects-left (length bh/project-list))
      (if (> num-projects-left 0)
          (message "%s projects left to view" num-projects-left)
        (beginning-of-buffer)
        (setq bh/hide-scheduled-and-waiting-next-tasks t)
        (error "All projects viewed.")))))

This is a special overridden subtree lock, but I'm not really sure what to make of it.

(add-hook 'org-agenda-mode-hook
          '(lambda () (org-defkey org-agenda-mode-map "\C-c\C-x<" 'bh/set-agenda-restriction-lock))
          'append)
(defun bh/set-agenda-restriction-lock (arg)
  "Set restriction lock to current task subtree or file if prefix is specified"
  (interactive "p")
  (let* ((pom (bh/get-pom-from-agenda-restriction-or-point))
         (tags (org-with-point-at pom (org-get-tags-at))))
    (let ((restriction-type (if (equal arg 4) 'file 'subtree)))
      (save-restriction
        (cond
         ((and (equal major-mode 'org-agenda-mode) pom)
          (org-with-point-at pom
            (org-agenda-set-restriction-lock restriction-type))
          (org-agenda-redo))
         ((and (equal major-mode 'org-mode) (org-before-first-heading-p))
          (org-agenda-set-restriction-lock 'file))
         (pom
          (org-with-point-at pom
            (org-agenda-set-restriction-lock restriction-type))))))))
(setq org-agenda-restriction-lock-highlight-subtree nil)

Define a bunch of Speed Commands for the Agenda view.

(setq org-use-speed-commands t)
(setq org-speed-commands-user '(("0" . ignore)
                                ("1" . ignore)
                                ("2" . ignore)
                                ("3" . ignore)
                                ("4" . ignore)
                                ("5" . ignore)
                                ("6" . ignore)
                                ("7" . ignore)
                                ("8" . ignore)
                                ("9" . ignore)

                                ("a" . ignore)
                                ("d" . ignore)
                                ("h" . bh/hide-other)
                                ("i" progn
                                 (forward-char 1)
                                 (call-interactively 'org-insert-heading-respect-content))
                                ("k" . org-kill-note-or-show-branches)
                                ("l" . ignore)
                                ("m" . ignore)
                                ("q" . bh/show-org-agenda)
                                ("r" . ignore)
                                ("s" . org-save-all-org-buffers)
                                ("w" . org-refile)
                                ("x" . ignore)
                                ("y" . ignore)
                                ("z" . org-add-note)

                                ("A" . ignore)
                                ("B" . ignore)
                                ("E" . ignore)
                                ("F" . bh/restrict-to-file-or-follow)
                                ("G" . ignore)
                                ("H" . ignore)
                                ("J" . org-clock-goto)
                                ("K" . ignore)
                                ("L" . ignore)
                                ("M" . ignore)
                                ("N" . bh/narrow-to-org-subtree)
                                ("P" . bh/narrow-to-org-project)
                                ("Q" . ignore)
                                ("R" . ignore)
                                ("S" . ignore)
                                ("T" . bh/org-todo)
                                ("U" . bh/narrow-up-one-org-level)
                                ("V" . ignore)
                                ("W" . bh/widen)
                                ("X" . ignore)
                                ("Y" . ignore)
                                ("Z" . ignore)))

Here are the definitions for bh/org-todo and bh/org-widen. org-todo narrows a subtree with just TODO entries; not terribly useful for me right now and I need to find a way to get it to show NEXT entries. org-widen removes restriction locks and redraws the agendas.

(global-set-key (kbd "<S-f5>") 'bh/widen)
(defun bh/org-todo (arg)
  (interactive "p")
  (if (equal arg 4)
      (save-restriction
        (bh/narrow-to-org-subtree)
        (org-show-todo-tree nil))
    (bh/narrow-to-org-subtree)
    (org-show-todo-tree nil)))

(defun bh/widen ()
  (interactive)
  (if (equal major-mode 'org-agenda-mode)
      (progn
        (org-agenda-remove-restriction-lock)
        (when org-agenda-sticky
          (org-agenda-redo)))
    (progn
      (org-agenda-remove-restriction-lock)
      (widen))))

In Agenda hit W to toggle between showing and hiding scheduled and waiting tasks.

(add-hook 'org-agenda-mode-hook
          '(lambda () (org-defkey org-agenda-mode-map "W" (lambda () (interactive) (setq bh/hide-scheduled-and-waiting-next-tasks t) (bh/widen))))
          'append)

Global Agenda Configuration

Use sticky agenda's so they persist

(setq org-agenda-sticky t)

Keep tasks with dates on the global todo lists

(setq org-agenda-todo-ignore-with-date nil)

Keep tasks with deadlines on the global todo lists

(setq org-agenda-todo-ignore-deadlines nil)

Keep tasks with scheduled dates on the global todo lists

(setq org-agenda-todo-ignore-scheduled nil)

Keep tasks with timestamps on the global todo lists

(setq org-agenda-todo-ignore-timestamp nil)

Remove completed deadline tasks from the agenda view

(setq org-agenda-skip-deadline-if-done t)

Remove completed scheduled tasks from the agenda view

(setq org-agenda-skip-scheduled-if-done t)

Remove completed items from search results

(setq org-agenda-skip-timestamp-if-done t)
(setq org-agenda-include-diary nil)
(setq org-agenda-diary-file "~/org/diary.org")
(setq org-agenda-insert-diary-extract-time t)

Include agenda archive files when searching for things

(setq org-agenda-text-search-extra-files '(agenda-archives))

Show all future entries for repeating tasks

(setq org-agenda-repeating-timestamp-show-all t)

Show all agenda dates - even if they are empty

(setq org-agenda-show-all-dates t)

Default sorting mode for Agenda tasks

(setq org-agenda-sorting-strategy
      '((agenda habit-down time-up user-defined-up effort-up category-keep)
        (todo category-up effort-up)
        (tags category-up effort-up)
        (search category-up)))

Start weekly agendas on Monday

(setq org-agenda-start-on-weekday 1)

Show a time grid on the Agenda; Items slot in to them, and there is a marker for the current time.

(setq org-agenda-time-grid '((daily today remove-match)
                             #("----------------" 0 16 (org-heading t))
                             (0000 0200 1200 1400 1600 1800 2000 2200)))

Put tags far right on the Agenda

(setq org-agenda-tags-column -102)

Get rid of the category in the Agenda; I only use TAGS, so this information is usually pretty useless.

(setq org-agenda-prefix-format '((agenda . " %i %?-12t% s")
                                 (timeline . "  % s")
                                 (todo . " %i")
                                 (tags . " %i")
                                 (search . " %i")))

Custom Sorting on top of the default agenda sort, this is some nice macro magic from Bernt and I don't quite understand it yet.

(setq org-agenda-cmp-user-defined 'bh/agenda-sort)

(defmacro bh/agenda-sort-test (fn a b)
  "Test for agenda sort"
  `(cond
    ; if both match leave them unsorted
    ((and (apply ,fn (list ,a))
          (apply ,fn (list ,b)))
     (setq result nil))
    ; if a matches put a first
    ((apply ,fn (list ,a))
     (setq result -1))
    ; otherwise if b matches put b first
    ((apply ,fn (list ,b))
     (setq result 1))
    ; if none match leave them unsorted
    (t nil)))

(defmacro bh/agenda-sort-test-num (fn compfn a b)
  `(cond
    ((apply ,fn (list ,a))
     (setq num-a (string-to-number (match-string 1 ,a)))
     (if (apply ,fn (list ,b))
         (progn
           (setq num-b (string-to-number (match-string 1 ,b)))
           (setq result (if (apply ,compfn (list num-a num-b))
                            -1
                          1)))
       (setq result -1)))
    ((apply ,fn (list ,b))
     (setq result 1))
    (t nil)))

(defun bh/is-not-scheduled-or-deadline (date-str)
  (and (not (bh/is-deadline date-str))
       (not (bh/is-scheduled date-str))))

(defun bh/is-due-deadline (date-str)
  (string-match "Deadline:" date-str))

(defun bh/is-late-deadline (date-str)
  (string-match "\\([0-9]*\\) d\. ago:" date-str))

(defun bh/is-pending-deadline (date-str)
  (string-match "In \\([^-]*\\)d\.:" date-str))

(defun bh/is-deadline (date-str)
  (or (bh/is-due-deadline date-str)
      (bh/is-late-deadline date-str)
      (bh/is-pending-deadline date-str)))

(defun bh/is-scheduled (date-str)
  (or (bh/is-scheduled-today date-str)
      (bh/is-scheduled-late date-str)))

(defun bh/is-scheduled-today (date-str)
  (string-match "Scheduled:" date-str))

(defun bh/is-scheduled-late (date-str)
  (string-match "Sched\.\\(.*\\)x:" date-str))


(defun bh/agenda-sort (a b)
  "Sorting strategy for agenda items.
Late deadlines first, then scheduled, then non-late deadlines"
  (let (result num-a num-b)
    (cond
     ; time specific items are already sorted first by org-agenda-sorting-strategy
     ; non-deadline and non-scheduled items next
     ((bh/agenda-sort-test 'bh/is-not-scheduled-or-deadline a b))
     ; deadlines for today next
     ((bh/agenda-sort-test 'bh/is-due-deadline a b))
     ; late deadlines next
     ((bh/agenda-sort-test-num 'bh/is-late-deadline '> a b))
     ; scheduled items for today next
     ((bh/agenda-sort-test 'bh/is-scheduled-today a b))
     ; late scheduled items next
     ((bh/agenda-sort-test-num 'bh/is-scheduled-late '> a b))
     ; pending deadlines last
     ((bh/agenda-sort-test-num 'bh/is-pending-deadline '< a b))
     ; finally default to unsorted
     (t (setq result nil)))
    result))

DONE Switch back to norang-style projects

DONE Fix zin/org-agenda-skip-tags

Agendas and Agenda Accessories

To start out with, set org-agenda-custom-commands to an empty list. We're going to dump a bunch of crap in to there, might as well make sure we have a clean slate going in to it.

(setq org-agenda-custom-commands '())

All of these Agendas are going to have to be rewritten when I define my new Todo Keywords, but for now they are placed here.

Basic Agenda Configurations

We have a few tags that exclude tasks from the Agenda; these are Idle Action tags mostly. This keeps the Agendas from distracting me, since they're limited only to things that I care about.

(defun bh/org-auto-exclude-function (tag)
  "Automatic task exclusion in the agenda with / RET"
  (and (cond
        ((string= tag "NONPROJECT") t)
        ((string= tag "READLIST") t)
        ((string= tag "IDLE") t))
       (concat "-" tag)))
(setq org-agenda-auto-exclude-function 'bh/org-auto-exclude-function)

I default to Day View in my Agendas; I really only have a reason to be looking at the week in my Monday Morning and Friday Afternoon checkin periods.

(setq org-agenda-span 'day)

I leverage the default Emacs diary and appointment system to do event notifications and such.

(setq appt-audible nil)
(setq appt-display-interval 5)
(setq appt-message-warning-time 10)

(setq org-stuck-projects '("" nil nil ""))
(defun bh/org-agenda-to-appt ()
  (interactive)
  (setq appt-time-msg-list nil)
  (org-agenda-to-appt))
(add-hook 'org-finalize-agenda-hook 'bh/org-agenda-to-appt 'append)
(add-hook 'after-init-hook 'bh/org-agenda-to-appt)
(run-at-time "24:01" nil 'bh/org-agenda-to-appt)
(appt-activate t)

Primary Agenda

This block agenda is for things I need to do for my personal life. It lists things that aren't projects, since I've relegated those to my Project Agenda, basically things like "call the doctor" and my habits. This uses a lot of magic functions written in Bernt's norang.ca org-mode configuration, I want to deprecate them, but I don't think I'll be able to get away with that any time soon.

(add-to-list 'org-agenda-custom-commands
             '(" " "Personal Agenda"
               ((agenda "" ((org-agenda-skip-function '(zin/org-agenda-skip-tag '("WORK" "PROJECT")))))
                (tags "REFILE"
                      ((org-agenda-overriding-header "Tasks to Refile")
                       (org-tags-match-list-sublevels nil)))
                (tags-todo "PERSONAL-REFILE-PROJECT-PURCHASE-IDLE-HABIT-EMAIL/!+NEXT|+INPROGRESS"
                           ((org-agenda-overriding-header (concat "Personal Next Tasks"
                                                                  (if bh/hide-scheduled-and-waiting-next-tasks
                                                                      "" " (including WAITING and SCHEDULED tasks)")))
                            (org-agenda-skip-function 'bh/skip-projects)
                            (org-tags-match-list-sublevels t)
                            (org-agenda-todo-ignore-scheduled bh/hide-scheduled-and-waiting-next-tasks)
                            (org-agenda-todo-ignore-deadlines bh/hide-scheduled-and-waiting-next-tasks)
                            (org-agenda-todo-ignore-with-date bh/hide-scheduled-and-waiting-next-tasks)
                            (org-agenda-sorting-strategy
                             '(todo-state-down priority-down effort-up category-keep))))
                (tags-todo "PERSONAL-REFILE-PROJECT-PURCHASE/!+INPROGRESS"
                           ((org-agenda-overriding-header (concat "Tasks in Progress"
                                                                  (if bh/hide-scheduled-and-waiting-next-tasks
                                                                      "" " (including WAITING and SCHEDULED tasks)")))
                            (org-tags-match-list-sublevels t)
                            (org-agenda-todo-ignore-scheduled bh/hide-scheduled-and-waiting-next-tasks)
                            (org-agenda-todo-ignore-deadlines bh/hide-scheduled-and-waiting-next-tasks)
                            (org-agenda-todo-ignore-with-date bh/hide-scheduled-and-waiting-next-tasks)
                            (org-agenda-sorting-strategy
                             '(todo-state-down priority-down effort-up category-keep))))
                (tags-todo "PERSONAL+EMAIL"
                           ((org-agenda-overriding-header "Emails")))
                (tags-todo "PERSONAL-PROJECT-PURCHASE-IDLE-HABIT/!+PLANNING|+WAITING|+ICEBOX"
                           ((org-agenda-overriding-header "Waiting and Postponed Tasks")
                            (org-agenda-skip-function 'bh/skip-stuck-projects)
                            (org-tags-match-list-sublevels nil)
                            (org-agenda-todo-ignore-scheduled t)
                            (org-agenda-todo-ignore-deadlines t)))
                (tags "PERSONAL-PROJECT-PURCHASE-IDLE/"
                      ((org-agenda-overriding-header "Tasks to Archive")
                       (org-agenda-skip-function 'bh/skip-non-archivable-tasks)
                       (org-tags-match-list-sublevels nil))))
               nil ("~/agenda.html")))

Shopping List

I use this agenda to categorize things I plan to purchase, things I need to purchase and items I ready to purchase. This is done by exploiting the semantics of my NEXT and TODO keywords. NEXT are items that I have picked out, things that I need the next time I'm shopping. TODO are things that I am still researching and things I should be preparing to buy.

I then break that down further in to "Things for the Apartment" and "Things for Other Stuff". The more I ponder on this, the more I think that I should break this in to "Things I need to buy IRL" and "Things I can buy online", with a tag for where I can buy them, such as "@GROCERY" or "@TARGET"

(add-to-list 'org-agenda-custom-commands
             '("P" "Shopping List"
               ((tags-todo "-APARTMENT-PROJECT+PURCHASE/!NEXT!INPROGRESS"
                           ((org-agenda-overriding-header "Things to Purchase")
                            (org-agenda-sorting-strategy '(todo-state-down effort-up priority-down))))
                (tags-todo "APARTMENT-PROJECT+PURCHASE/!NEXT!INPROGRESS"
                           ((org-agenda-overriding-header "Aparment Things to Purchase")
                            (org-agenda-sorting-strategy '(todo-state-down effort-up priority-down))))
                (tags-todo "PROJECT+PURCHASE/!+NEXT|+INPROGRESS"
                           ((org-agenda-overriding-header "Project Things to Purchase")
                            (org-agenda-sorting-strategy '(todo-state-down effort-up priority-down))))
                (tags-todo "-APARTMENT+PURCHASE/!PLANNING"
                           ((org-agenda-overriding-header "Things to Research")
                            (org-tags-match-list-sublevels t)
                            (org-agenda-sorting-strategy '(todo-state-down effort-up priority-down))))
                (tags-todo "PROJECT+PURCHASE/!PLANNING"
                           ((org-agenda-overriding-header "Project Things to Purchase")
                            (org-agenda-sorting-strategy '(todo-state-down effort-up priority-down))))
                (tags-todo "APARTMENT+PURCHASE/!PLANNING"
                           ((org-agenda-overriding-header "Apartment Things to Research")
                            (org-tags-match-list-sublevels t)
                            (org-agenda-sorting-strategy '(todo-state-down effort-up priority-down)))))))

Projects Agenda

This block agenda categorizes my personal projects, everything that I do as a creative outlet that isn't a part of work. This Agenda is narrowed to only show things tagged as PROJECT

(add-to-list 'org-agenda-custom-commands
             '("p" "Project Agenda"
               ((tags "REFILE"
                      ((org-agenda-overriding-header "Tasks to Refile")
                       (org-tags-match-list-sublevels nil)))
                (tags-todo "-CANCELLED+PROJECT/!"
                           ((org-agenda-overriding-header "Stuck Projects")
                            (org-agenda-skip-function 'bh/skip-non-stuck-projects)
                            (org-agenda-sorting-strategy
                             '(category-keep))))
                (tags-todo "-HOLD-CANCELLED+PROJECT/!"
                           ((org-agenda-overriding-header "Projects")
                            (org-agenda-skip-function 'bh/skip-non-projects)
                            (org-tags-match-list-sublevels 'indented)
                            (org-agenda-sorting-strategy
                             '(category-keep))))
                (tags-todo "-CANCELLED+PROJECT/!NEXT|INPROGRESS"
                           ((org-agenda-overriding-header (concat "Project Next Tasks"
                                                                  (if bh/hide-scheduled-and-waiting-next-tasks
                                                                      ""
                                                                    " (including WAITING and SCHEDULED tasks)")))
                            (org-agenda-skip-function 'bh/skip-projects-and-habits-and-single-tasks)
                            (org-tags-match-list-sublevels t)
                            (org-agenda-todo-ignore-scheduled bh/hide-scheduled-and-waiting-next-tasks)
                            (org-agenda-todo-ignore-deadlines bh/hide-scheduled-and-waiting-next-tasks)
                            (org-agenda-todo-ignore-with-date bh/hide-scheduled-and-waiting-next-tasks)
                            (org-agenda-sorting-strategy
                             '(todo-state-down effort-up category-keep))))
                (tags-todo "-REFILE-CANCELLED-WAITING-HOLD+PROJECT/!"
                           ((org-agenda-overriding-header (concat "Project Subtasks"
                                                                  (if bh/hide-scheduled-and-waiting-next-tasks
                                                                      ""
                                                                    " (including WAITING and SCHEDULED tasks)")))
                            (org-agenda-skip-function 'bh/skip-non-project-tasks)
                            (org-agenda-todo-ignore-scheduled bh/hide-scheduled-and-waiting-next-tasks)
                            (org-agenda-todo-ignore-deadlines bh/hide-scheduled-and-waiting-next-tasks)
                            (org-agenda-todo-ignore-with-date bh/hide-scheduled-and-waiting-next-tasks)
                            (org-agenda-sorting-strategy
                             '(category-keep))))
                (tags-todo "-REFILE-CANCELLED-WAITING-HOLD+PROJECT/!"
                           ((org-agenda-overriding-header (concat "Standalone Tasks"
                                                                  (if bh/hide-scheduled-and-waiting-next-tasks
                                                                      ""
                                                                    " (including WAITING and SCHEDULED tasks)")))
                            (org-agenda-skip-function 'bh/skip-project-tasks)
                            (org-agenda-todo-ignore-scheduled bh/hide-scheduled-and-waiting-next-tasks)
                            (org-agenda-todo-ignore-deadlines bh/hide-scheduled-and-waiting-next-tasks)
                            (org-agenda-todo-ignore-with-date bh/hide-scheduled-and-waiting-next-tasks)
                            (org-agenda-sorting-strategy
                             '(category-keep))))
                (tags-todo "-CANCELLED+WAITING|HOLD+PROJECT/!+WAITING|+ICEBOX|+PLANNING"
                           ((org-agenda-overriding-header (concat "Waiting and Postponed Tasks"
                                                                  (if bh/hide-scheduled-and-waiting-next-tasks
                                                                      ""
                                                                    " (including WAITING and SCHEDULED tasks)")))
                            (org-agenda-skip-function 'bh/skip-non-tasks)
                            (org-tags-match-list-sublevels nil)
                            (org-agenda-todo-ignore-scheduled bh/hide-scheduled-and-waiting-next-tasks)
                            (org-agenda-todo-ignore-deadlines bh/hide-scheduled-and-waiting-next-tasks))))))

Work Agenda

This block agenda is supposed to categorize the work that I am focusing on. I'm currently not using it very much since we use Phabricator at work and there's no easy way to sync Phab and Org-mode. I plan on fixing that some day at which point this will become actually useful.

(add-to-list 'org-agenda-custom-commands
             '("w" "Work Agenda"
               ((agenda "" ((org-agenda-skip-function '(zin/org-agenda-skip-tag '("WORK") 't))))
                (tags "REFILE"
                      ((org-agenda-overriding-header "Tasks to Refile")
                       (org-tags-match-list-sublevels nil)))
                (tags-todo "-CANCELLED+WORK/!"
                           ((org-agenda-overriding-header "Stuck Projects")
                            (org-agenda-skip-function 'bh/skip-non-stuck-projects)
                            (org-agenda-sorting-strategy
                             '(category-keep))))
                (tags-todo "-HOLD-CANCELLED+WORK/!"
                           ((org-agenda-overriding-header "Projects")
                            (org-agenda-skip-function 'bh/skip-non-projects)
                            (org-tags-match-list-sublevels 'indented)
                            (org-agenda-sorting-strategy
                             '(category-keep))))
                (tags-todo "-CANCELLED+WORK-EMAIL/!NEXT|INPROGRESS"
                           ((org-agenda-overriding-header (concat "Project Next Tasks"
                                                                  (if bh/hide-scheduled-and-waiting-next-tasks
                                                                      ""
                                                                    " (including WAITING and SCHEDULED tasks)")))
                            (org-agenda-skip-function 'bh/skip-projects-and-habits-and-single-tasks)
                            (org-tags-match-list-sublevels t)
                            (org-agenda-todo-ignore-scheduled bh/hide-scheduled-and-waiting-next-tasks)
                            (org-agenda-todo-ignore-deadlines bh/hide-scheduled-and-waiting-next-tasks)
                            (org-agenda-todo-ignore-with-date bh/hide-scheduled-and-waiting-next-tasks)
                            (org-agenda-sorting-strategy
                             '(todo-state-down effort-up category-keep))))
                (tags-todo "-REFILE-CANCELLED-WAITING-HOLD+WORK/!"
                           ((org-agenda-overriding-header (concat "Project Subtasks"
                                                                  (if bh/hide-scheduled-and-waiting-next-tasks
                                                                      ""
                                                                    " (including WAITING and SCHEDULED tasks)")))
                            (org-agenda-skip-function 'bh/skip-non-project-tasks)
                            (org-agenda-todo-ignore-scheduled bh/hide-scheduled-and-waiting-next-tasks)
                            (org-agenda-todo-ignore-deadlines bh/hide-scheduled-and-waiting-next-tasks)
                            (org-agenda-todo-ignore-with-date bh/hide-scheduled-and-waiting-next-tasks)
                            (org-agenda-sorting-strategy
                             '(category-keep))))
                (tags-todo "-REFILE-CANCELLED-WAITING-HOLD+WORK/!"
                           ((org-agenda-overriding-header (concat "Standalone Tasks"
                                                                  (if bh/hide-scheduled-and-waiting-next-tasks
                                                                      ""
                                                                    " (including WAITING and SCHEDULED tasks)")))
                            (org-agenda-skip-function 'bh/skip-project-tasks)
                            (org-agenda-todo-ignore-scheduled bh/hide-scheduled-and-waiting-next-tasks)
                            (org-agenda-todo-ignore-deadlines bh/hide-scheduled-and-waiting-next-tasks)
                            (org-agenda-todo-ignore-with-date bh/hide-scheduled-and-waiting-next-tasks)
                            (org-agenda-sorting-strategy
                             '(category-keep))))
                (tags-todo "EMAIL+WORK"
                           ((org-agenda-overriding-header "Work Email")
                            (org-agenda-sorting-strategy
                             '(category-keep))))
                (tags-todo "+WORK-CANCELLED+WAITING|HOLD/!+WAITING|+ICEBOX|+PLANNING"
                           ((org-agenda-overriding-header (concat "Waiting and Postponed Tasks"
                                                                  (if bh/hide-scheduled-and-waiting-next-tasks
                                                                      ""
                                                                    " (including WAITING and SCHEDULED tasks)")))
                            (org-agenda-skip-function 'bh/skip-non-tasks)
                            (org-tags-match-list-sublevels nil)
                            (org-agenda-todo-ignore-scheduled bh/hide-scheduled-and-waiting-next-tasks)
                            (org-agenda-todo-ignore-deadlines bh/hide-scheduled-and-waiting-next-tasks)))
                (tags "WORK+NOTE"
                      ((org-agenda-overriding-header "Notes"))))))

Email Agenda

(add-to-list 'org-agenda-custom-commands
             '("E" "Email responses"
               ((tags "REFILE"
                      ((org-agenda-overriding-header "Tasks to Refile")
                       (org-tags-match-list-sublevels nil)))
                (tags-todo "EMAIL+WORK"
                           ((org-agenda-overriding-header "Work Email")
                            (org-agenda-sorting-strategy
                             '(category-keep))))
                (tags-todo "PERSONAL+EMAIL"
                           ((org-agenda-overriding-header "Personal Email")
                            (org-agenda-sorting-strategy
                             '(category-keep))))
                (tags "EMAIL"
                      ((org-agenda-overriding-header "Tasks to Archive")
                       (org-agenda-skip-function 'bh/skip-non-archivable-tasks)
                       (org-tags-match-list-sublevels nil))))))

Idle Actions Agenda

This agenda shows IDLE actions that I can run in my free time. Reading list, IDLE habits.

(add-to-list 'org-agenda-custom-commands
             '("i" "Idle Actions"
               ((tags-todo "IDLE-READLIST-WATCH"
                           ((org-agenda-overriding-header "Idle Tasks")
                            (org-agenda-skip-function 'bh/skip-project-tasks)
                            (org-agenda-sorting-strategy
                             '(todo-state-down effort-up))))
                (tags-todo "READLIST"
                           ((org-agenda-overriding-header "Idle Reading List")
                            (org-agenda-sorting-strategy
                             '(todo-state-down effort-up))))
                (tags-todo "WATCH"
                           ((org-agenda-overriding-header "Things to Watch")
                            (org-agenda-skip-function 'bh/skip-project-tasks)
                            (org-agenda-sorting-strategy
                             '(todo-state-down effort-up)))))))

Notes

This is a simple agenda that just shows your notes; you can filter it if you want to point to a project or tag that you're working on.

(add-to-list 'org-agenda-custom-commands
      '("N" "Notes" tags "NOTE"
               ((org-agenda-overriding-header "Notes")
                (org-tags-match-list-sublevels t))))

Habits

This is a simple agenda that just shows the state of my habits.

(add-to-list 'org-agenda-custom-commands
       '("h" "Habits" tags-todo "STYLE=\"habit\""
               ((org-agenda-overriding-header "Habits")
                (org-agenda-sorting-strategy
                 '(todo-state-down effort-up category-keep)))))

DONE Clean up agendas

Mailing Trees

I use Org-mode to take notes, sometimes I want to send those out to involved parties. After a meeting, I hit <hydra=workflow> m will prepare a buffer for being mailed and <hydra-workflow> M opens up a message with the TODO tree in it.

(defun bh/prepare-meeting-notes ()
  "Prepare meeting notes for email
   Take selected region and convert tabs to spaces, mark TODOs with leading >>>, and copy to kill ring for pasting"
  (interactive)
  (let (prefix)
    (save-excursion
      (save-restriction
        (narrow-to-region (region-beginning) (region-end))
        (untabify (point-min) (point-max))
        (goto-char (point-min))
        (while (re-search-forward "^\\( *-\\\) \\(NEXT\\|DONE\\): " (point-max) t)
          (replace-match (concat (make-string (length (match-string 1)) ?>) " " (match-string 2) ": ")))
        (goto-char (point-min))
        (kill-ring-save (point-min) (point-max))))))
(add-hook 'org-mode-hook (lambda () (local-set-key (kbd "C-c M-o") 'bh/mail-subtree)) 'append)
(defun bh/mail-subtree ()
  (interactive)
  (require 'org-mime)
  (org-mark-subtree)
  (org-mime-subtree))

Statistics and Analysis

Putting this here for now; it needs expounding and usage information.

(defun sacha/org-count-tasks-by-status ()
  (interactive)
  (let ((counts (make-hash-table :test 'equal))
        (today (format-time-string "%Y-%m-%d" (current-time)))
        values output)
    (org-map-entries
     (lambda ()
       (let* ((status (elt (org-heading-components) 2)))
         (when status
           (puthash status (1+ (or (gethash status counts) 0)) counts))))
     nil
     'agenda-with-archives)
    (setq values (mapcar (lambda (x)
                           (or (gethash x counts) 0))
                         '("DONE" "INPROGRESS" "NEXT" "WAITING" "PHONE" "MEETING" "CANCELLED" "ICEBOX")))
    (setq output
          (concat "| " today " | "
                  (mapconcat 'number-to-string values " | ")
                  " | "
                  (number-to-string (apply '+ values))
                  " | "
                  (number-to-string
                   (round (/ (* 100.0 (car values)) (apply '+ values))))
                  "% |"))
    (if (called-interactively-p 'any)
        (insert output)
      output)))
#+begin_src emacs-lisp :results append raw
(sacha/org-count-tasks-by-status)
#+end_src

#+name: burndown
|       Date | DONE | INPRG | NEXT | WAIT. | PHONE | MTG. | CANCL | ICE | Total | % done | + done | +canc. | + total | + t - d - c | Note |
|------------+------+-------+------+-------+-------+------+-------+-----+-------+--------+--------+--------+---------+-------------+------|
| 2015-05-28 |  544 |   107 |  968 |    25 |     0 |    0 |   111 |   8 |  1763 |    31% |        |        |         |             |      |
#+TBLFM: @3$12..@>$12=$2-@-1$2::@4$14..@>$14=$10-@-1$10::@4$15..@>$15=$14-$12-($8-@-1$8)::@4$13..@>$13=$8-@-1$8

NEXT Create my HTML agenda system in elnode

Footnotes:

Author: Ryan Rix

Created: 2017-10-03 Tue 10:05

Validate XHTML 1.0