org-mode todo setup so far

As I described a few weeks ago, I've been using a complex Getting Things Done system for around fifteen years. I've been using toodledo for my task system but recently I've gotten more interested in Emacs's org-mode. I've realized that I've been missing features and not knowing it; as one simple example, it's very easy to have multiple sets of tags in org-mode.

Below I'll describe how I have org-mode configured so far.

Files and directory structure

I have a Dropbox folder org/ at the path ~/Dropbox/org. Inside this directory I have several files:

backlog.org
"someday/maybe" ideas that are not otherwise tracked.
home goals.org
experimental place for "someday/maybe" home ideas.
inbox.org
I configured this as where org-capture dumps tasks.
main-todo.org
Non-project todo items.
now.org
Stuff I'm doing right now, mainly to make using orgzly easier.
orgzly-inbox.org
Default place where orgzly saves tasks.1
projects.org
Location for larger projects with many todo items.
web site ideas.org
experimental place for "someday/maybe" web site ideas.
work ideas.org
experimental place for "someday/maybe" work ideas.

I am still experimenting with what files make the most sense. With org-mode you can do searches across many files at once, so I'm still figuring out when to use separate files. The main factor in using separate files seems to be orgzly's file synchronization.

Task intake: org-capture

One way to create tasks is just to go to a file and start typing. This is really strange to me, because you can tell you're just using a text file. But if you have a list of tasks this can be the fastest way, hitting M-S-RET after each item to create a new line.

However, if you're doing other stuff and want to create a task on the fly, org-capture is really useful.

(use-package org
  :ensure org-plus-contrib
  :config
    ; really long config section omitted
    ; not sure if I need this:
    (setq org-default-notes-file "~/Dropbox/org/inbox.org")
    (setq org-capture-templates
          '(("t" "Todo [inbox]" entry
          (file+headline "~/Dropbox/org/inbox.org" "Tasks")
          "* TODO %i%?\n%a")
            ("n" "Todo [inbox, no link]" entry
            (file+headline "~/Dropbox/org/inbox.org" "Tasks")
            "* TODO %i%?\n")
            ("b" "Backlog" entry
            (file+headline "~/Dropbox/org/backlog.org" "Backlog")
            "* %i%?\n%a")))
  :bind (; other bindings removed
         ("C-c c" . org-capture)))

With the above, I can hit C-c c to capture something; I then have three options:

  • t to capture a todo item with a link
  • n to capture a todo item without a link
  • b to capture a backlog item (i.e. idea)

The "link" is a link to wherever you were when you created the todo. For example I hit C-c c t right here and the link was to file:~/Dropbox/jb.com/org/_posts/2019-02-23-org-todo-setup.org::*Task%20intake:%20org-capture. That is a link to the section of the org-mode file that I'm using to write this article!

This is especially helpful if I'm reading email (in Emacs); I can hit C-c c t and create a todo that links to an email. In the todo later I can click the link to get back to that same email!

Archiving and refiling

Archiving is built into org-mode directly, I believe. If you're on a heading you want to archive, you can hit C-c C-x C-a to archive that item. If you archive a task in main-todo.org, the archive file will by default be main-todo.org_archive. Archiving just moves your todo to another file for preservation.

Refiling lets you move a todo from one physical file to another. Here's how I set this up:

(setq org-refile-targets
  '((("~/Dropbox/org/main-todo.org") :maxlevel . 3)
    (("~/Dropbox/org/projects.org") :maxlevel . 3)
    (("~/Dropbox/org/backlog.org") :maxlevel . 3)
    (("~/Dropbox/org/now.org") :maxlevel . 3)
    ))

When you're on a heading, if you hit C-c C-w, you'll be presented with all the headings down to level 3 for each of these files. This makes it easy to process inbox.org or other files to move content to the right place.

Tags and states

Here are my todo "proper" settings:

(setq org-todo-keyword-faces
  '(("WORKING" . "orange")
   ("CANCELLED" . "grey")))
(setq org-todo-keywords
  '((sequence "TODO(t)" "WORKING(n)" "WAITING(w@/!)"
              "POSTPONED(p)" "|" "DONE(d@/!)" "CANCELLED(c)")))

This means that tasks can be in the states TODO, WORKING, WAITING, POSTPONED, DONE, or CANCELLED. The extra stuff after each keyword defines a shortcut key and whether a log entry should be added. For example WAITING(w@/!) means w is the keyboard shortcut, @ means "note with timestamp" and /! means "add a timestamp even if I bailed on writing a note."

With those "states" defined, I next defined my contexts:

(setq org-tag-alist '(
  (:startgroup . nil)
    ("home" . ?h)
    ("work" . ?w)
  (:endgroup . nil)
  (:startgroup . nil)
    ("@errands" . ?e)
    ("@house" . ?s)
    ("@now" . ?n)
    ("@online" . ?o)
    ("@phone" . ?p)
    ("@campus" . ?c)
    ("@office" . ?f)
  (:endgroup . nil)
))

This is a neat thing that toodledo didn't have–you can have different "groups" of tags, where you can only choose one item from the group. My group #1 is either home or work; my group #2 is the GTD "context" i.e. where you need to be to do this work.

Theoretically for example I could have a "work" task with a context of "house," if I needed to be home to do it but the task was for work. This has helped me move past GTD's insistence that your context is the only thing you should be tracking, and it also makes searching for work vs. home tasks much easier.

SCHEDULED vs. DEADLINE

To set dates on tasks, when you're on the task you can use C-c C-d to set the DEADLINE date and/or C-c C-s to set the SCHEDULED date. (You could also manually type these in.)

DEADLINE best corresponds to the due date of a task. Just like normal in GTD, the date should only be set if it's a real due date.

SCHEDULED best corresponds to the start date of a task. There are a few org-agenda options to hide tasks with a future scheduled date:

(setq org-agenda-todo-ignore-scheduled 'future)
(setq org-agenda-tags-todo-honor-ignore-options t)
(setq org-agenda-skip-deadline-prewarning-if-scheduled t)

Agendas and viewing consolidated tasks

The last big piece is configuring org-agenda to show me a summary of tasks to be done. To set org-agenda up, at a minimum you need to tell it what files to read:

(setq org-agenda-files (list
  "~/Dropbox/org/inbox.org"
  "~/Dropbox/org/main-todo.org"
  "~/Dropbox/org/orgzly-inbox.org"
  "~/Dropbox/org/projects.org"))

; also bind "C-c a" to org-agenda e.g. via use-package

After adding the keybinding, I can hit C-c a to get to the org-agenda list of options, one of which is a for agenda: C-c a a in total. This lets me see the default org-agenda.

configuring custom org-agenda options

I'd like to have more control over what I see in org-agenda than the default view. This is done through as many parentheses as possible:

(setq org-agenda-custom-commands
 (("w" "Work agenda"
   ; Priority A
   ((tags-todo "PRIORITY=\"A\"&-home"
   ((org-agenda-overriding-header "Priority A")))
   ; Due soon
   (tags-todo "-PRIORITY=\"A\"&DEADLINE<=\"<+7d>\"&-home"
   ((org-agenda-overriding-header "Due soon")))
   ; Project list
   (tags "LEVEL=2&-home"
   ((org-agenda-files '("~/Dropbox/org/projects.org"))
    (org-agenda-overriding-header "Projects")))
   (tags-todo (concat "-home&-TODO=\"WAITING\"&-FILE=\""
                       (expand-file-name "~/Dropbox/org/projects.org")
                       "\"")
    ((org-agenda-overriding-header "All non-project tasks")))
   )
   ((org-agenda-compact-blocks t))) ;; options set here apply to the entire block
 ("h" "Home agenda"
   ; Priority A
   ((tags-todo "PRIORITY=\"A\"&-work"
   ((org-agenda-overriding-header "Priority A")))
   ; Due soon
   (tags-todo "-PRIORITY=\"A\"&DEADLINE<=\"<+7d>\"&-work"
   ((org-agenda-overriding-header "Due soon")))
   ; Project list
   (tags "LEVEL=2&-work"
   ((org-agenda-files '("~/Dropbox/org/projects.org"))
    (org-agenda-overriding-header "Projects")))

   (tags-todo (concat "-work&-TODO=\"WAITING\"&-FILE=\""
                       (expand-file-name "~/Dropbox/org/projects.org")
                       "\"")
    ((org-agenda-overriding-header "All non-project tasks"))))
    ((org-agenda-compact-blocks t))) ;; options set here apply to the entire block
 ("W" "Waiting"
   ((tags-todo "WAITING"
    ((org-agenda-overriding-header "Waiting tasks"))))
   )))

The above defines three org-agenda keys: w ("work agenda"), h ("home agenda"), and W ("waiting"). I'm going to go through each of the sections within "work agenda:"

Priority A tasks

   ; Priority A
   ((tags-todo "PRIORITY=\"A\"&-home"
   ((org-agenda-overriding-header "Priority A")))

First, show me the "Priority A" tasks, only if they don't have a "home" tag. Very notably, tags get inherited, so if you have a block like so:

  * example heading                                                      :home:
  ** TODO thing #1

"thing #1" gets the ":home:" tag. This means I can have a section tagged "home" called "Random home tasks" and nothing underneath it will show up if I hide the "home" tag.

Due soon

   ; Due soon
   (tags-todo "-PRIORITY=\"A\"&DEADLINE<=\"<+7d>\"&-home"
   ((org-agenda-overriding-header "Due soon")))

Next, I want to see anything with a deadline within 7 days, and I don't want to see anything tagged PRIORITY "A" because I already saw that.

Project list

   ; Project list
   (tags "LEVEL=2&-home"
   ((org-agenda-files '("~/Dropbox/org/projects.org"))
    (org-agenda-overriding-header "Projects")))

Next, I want to see the "LEVEL 2" headings from the projects file unless they have a "home" task. THIS IS AMAZING. I'll see any tasks due soon because of the Due soon rule. Otherwise, I don't want my task system cluttered up with dozens of project tasks–I just want to remember that these projects are happening.

Non-project tasks

   (tags-todo (concat "-home&-TODO=\"WAITING\"&-FILE=\""
                       (expand-file-name "~/Dropbox/org/projects.org")
                       "\"")
    ((org-agenda-overriding-header "All non-project tasks")))
   )

Finally, I want to see all the non-project tasks that aren't in a "waiting" state. There's a special property "FILE" that lets you filter on a file, so I can filter out all the "projects.org" files. (FILE is apparently populated with the complete path, which is why I need to call expand-file-name.)

checklists and org-checklist

I really like org-mode's checklist option–these checklists don't show up in my org-agenda view, but they help me remember to do parts of my tasks. For example I can have a task:

  * TODO test-task
   - [ ] do this
   - [ ] do that
   - [ ] do the other thing

and I can then check the boxes with C-c C-c as I complete them. This has been extremely helpful to me so that I can focus on one task that may have many parts to it.

I especially like using these checklists with repeating tasks. I'm using org-plus-contrib rather than just org because I want access to org-checklist:

(add-to-list 'org-modules 'org-checklist)
(require 'org-checklist)

This module will look for a property RESET_CHECK_BOXES:

  ** TODO Weekly home tasks
     DEADLINE: <2019-03-03 Sun +1w> SCHEDULED: <2019-02-25 Mon +1w>
     :PROPERTIES:
     :RESET_CHECK_BOXES: t
     :END:
   - [ ] process paper inbox
   - [ ] clean fridge
   - [ ] clear out onetab tabs
   - [ ] review w/f and follow-up email

When this task is marked complete, the checklists will become unchecked again.

Lessons learned so far

I'm still halfway using toodledo and halfway using org-mode. So far I've found that org-mode is much less intrusive for todo intake; I can be checking my email and quickly add a task rather than having to switch to my web browser to type up a task with more context (because previously my email and task systems weren't integrated at all).

I also really like how projects and non-projects can be blended with this system. I've always had to use separate systems in the past.

Footnotes:

1

This is to reduce the chance of orgzly editing a file that has also been edited elsewhere; orgzly doesn't have a good merge function so it's easy to have to throw away content if you aren't synchronizing.

Updated: