This file provides details on my current org
setup. It is intended as a literate programming approach to structure my org
setup. It may work, or it may not (!) but I thought I’d give it a go.
If you want to use some (all) of this setup, you’ll need to change directories appropriately. You can tangle this file to Emacs lisp using C-c C-v C-t
, which will create org-setup.el in the same folder as this file. You can then source this in your init.el file.
This setup has been greatly influenced by Gregory Stein (GS) and in turn by Bernt Hansen (BH). You should check out those great resources.
First up, we’ll add a block to load all this org config after spacemacs starts, else we’ll get conflicts. If you’re following along at home, don’t forget to complete the block at the end of the file.
The default directory for org files is ~/org, and I’m going to stick with that convention, but make it explicit. When I capture tasks/notes (C-c t
/ C-c n
), I’m going to have them sent to the ~/org/refile.org file, when I can refile them at my convenience. This saves time when capturing TODO items, as it means I don’t have to decide which project they’ll go into straight away. Speaking of, when it comes time to refile, these can be put into separate project files; the refile-targets allows the nesting to be searched. Finally, I set the agenda from the default org directory.
(setq
;; Default directory for org files
org-directory "~/github/org"
;; Directory for notes/tasks to be refiled
org-default-notes-file (concat org-directory "/refile.org")
org-journal-file (concat org-directory "/journal-club.org")
;; Allows to store agenda files in their appropriate files.
;; This is useful when per project task lists are used.
;; Only show level 1 headings for refiling (level 2 are the task headers)
org-refile-targets (quote ((nil :maxlevel . 2)
(org-agenda-files :maxlevel . 2)))
;; Org agenda files read from here
org-agenda-files '(
"~/github/org/waiting-for.org" "~/github/org/refile.org" "~/github/org/issue-tracking.org" "~/github/org/habits-and-schedules.org" "~/github/org/general.org" "~/github/org/analysis.org" "~/github/org/someday.org" "~/github/org/meetings.org"
)
)
I spoke above about refiling. Here’s where I set that up. I’m telling org to use the filename and the headers (so a level 2 header will be filename/level1/level2). complete-in-steps nil means that we can use helm/ivy to provide nice completion, and finally allow the creation of parent headers (with confirmation).
(setq
;; Be sure to use the full path for refile setup
org-refile-use-outline-path 'file
;; Set this to nil to use helm/ivy for completion
org-outline-path-complete-in-steps nil
;; Allow refile to create parent tasks with confirmation
org-refile-allow-creating-parent-nodes 'confirm
)
I now need to set up capture templates. When I go to capture a task/note etc, I am presented with the options listed below.
I’m migrating/supplementing a BuJo. The following provides my current ‘bullets’ from my BuJo, translated to an org setup. What the code does is define the TODO keywords, and the transition between them. Keywords to the left of the |
are in a state of ‘not done’ whilst those on the right are done.
(setq org-todo-keywords
(quote ((sequence "TODO(t)" "|" "DONE(d)")
(sequence "TODAY(T)" "|" "DONE(d)")
(sequence "PROJECT(p)" "|" "DONE(d)" "CANCELLED(c)")
(sequence "WAITING(w)" "|")
(sequence "|" "CANCELLED(c)")
(sequence "SOMEDAY(s)" "|" "CANCELLED(c)")
(sequence "|" "MEETING")
)
)
)
My BuJo is pretty simple, I’ve mapped it as follows:
- todo
- (t) this is the standard task/todo item, which can be marked done
- waiting
- (w) this keyword is added to a task when I’m waiting to hear back from someone before progressing the task
- cancelled
- (c) pretty self-explanatory …
- someday
- (s) this is for tasks that I may want to do at some stage, but not just yet. I still want to keep track of them though.
- project
- (p) I’m using as a top-level to keep a track of projects.
On top of this, I’m going to try and use GS/BH’s keyword:
- meeting
- This will be used for ‘true’ meetings, as well as any other interruptions that may carry on for a long time…
When we display these in the agenda, we’d also like to have different colours for different keywords:
(setq
;; Coloured faces for agenda/todo items
org-todo-keyword-faces
'(
("DONE" . (:foreground "#2B4450" :weight bold))
("TODO" . (:foreground "#ff3030" :weight bold))
("WAITING" . (:foreground "#fe2f92" :weight bold))
("CANCELLED" . (:foreground "#999999" :weight bold))
("SOMEDAY" . (:foreground "#ab82ff" :weight bold))
("MEETING" . (:foreground "#1874cd" :weight bold))
)
)
The following code provides the capture templates when I add TODO tasks. Best place to check how these work is the help for that variable. You can find the help for a variable by placing the cursor over the variable and entering C-h v
(help for variable). To make sure that folded views keep some space between headers, we set the separator-lines variable.
(setq
;; Define the custum capture templates
org-capture-templates
'(("t" "Todo's and other general notes")
("tt" "Todo (inbox/to refile)" entry (file org-default-notes-file)
"* TODO %?\nCaptured: %u\n%a\n" :clock-in t :clock-resume t)
("tT" "Todo (with headline choice)" entry (function sprazza/org-get-target-headline)
"* TODO %?\nCaptured: %u\n%a\n" :clock-in t :clock-resume t)
("tr" "Ticklers/Reminders" entry (file+headline (lambda() (concat org-directory "/general.org")) "Ticklers")
"* TODO %?\nCaptured: %u\n%a\n" :clock-in t :clock-resume t)
("m" "Meeting templates")
("mp" "People Leaders" entry (file+headline (lambda() (concat org-directory "/meetings.org")) "People Leaders")
"* MEETING :MEETING:\n** Date: %^u\n** Attendees: MN, RN, SL\n** SL Updates\n*** %?\n** RN Updates\n** MN Updates\n** Mood assessment\n" :clock-in nil :clock-resume t)
("mg" "General Meeting" entry (file+headline (lambda() (concat org-directory "/meetings.org")) "General Meetings")
"* MEETING Topic: %? :MEETING:\n** Date: %^u\n** Attendees: Steve Lane\n** Notes Prior\n** Notes During\n*** Mood assessment\n" :clock-in nil :clock-resume t)
("mr" "Regular One-on-one's" entry (file+function (lambda() (concat org-directory "/meetings.org")) sprazza/org-get-target-headline)
"* MEETING Regular meeting with %? :MEETING:\n** Date: %^u\n** Notes Prior\n** Notes During\n*** Mood assessment\n" :clock-in nil :clock-resume t)
("mw" "Weekly Review" entry (file+headline (lambda() (concat org-directory "/meetings.org")) "Weekly Review")
"* MEETING Weekly Review - %^u :MEETING:\n** Attendees: Steve, Sally, Ryan, Jason, Seb\n** Notes Prior\n*** All\n*** Ryan\n*** Sally\n*** Jason\n*** Seb\n** Notes During\n*** Mood assessment\n" :clock-in nil :clock-resume t)
("j" "Journal Club" entry (file org-journal-file)
"* DETAILS: %? :JOURNAL CLUB: \n%u\n" :clock-in t :clock-resume t :empty-lines 1)
("P" "Professional development templates")
("Ps" "Seminar" entry (file+headline (lambda() (concat org-directory "/professional-development.org")) "Seminars")
"* Title: %?\n** Date: %^u\n** Speaker: \n** Notes\n" :clock-in t :clock-resume t)
("Pw" "Workshop" entry (file+headline (lambda() (concat org-directory "/professional-development.org")) "Workshops")
"* Title: %?\n** Date: %^u\n** Presenter: " :clock-in t :clock-resume t)
("i" "Issue tracking" entry (file (lambda() (concat org-directory "/issue-tracking.org")))
"* TODO %^{Title}\n%^{Type}p\nCaptured: %u\n** Description\n" :clock-in nil :clock-resume t)
("J" "Journal wins/losses" entry (file+function (lambda() (concat org-directory "/wins-and-losses.org")) sprazza/org-get-target-headline)
"* %u\n** %U\n%?" :clock-in nil)
)
;; Keep a line between headers
org-cycle-separator-lines 1
)
For the regular people meetings, we can search for the org target of interest, using this function (called in the above template):
(defun sprazza/org-get-target-headline (&optional targets prompt)
"Prompt for a location in an org file and jump to it.
This is for promping for refile targets when doing captures.
Targets are selected from `org-refile-targets'. If TARGETS is
given it temporarily overrides `org-refile-targets'. PROMPT will
replace the default prompt message.
"
(let ((org-refile-targets (or targets org-refile-targets))
(prompt (or prompt "Capture Location")))
(org-refile t nil nil prompt))
)
Source for this function is this stack.
Extensibility is provided by tagging. You can tag on the fly, or use a predefined list. Below I set a predefined list. Everything between :startgroup
:endgroup
below is a single tag for that task—i.e. you can only choose one. ?
gives the shortcut to add the tag.
;; Custom tags
(setq org-tag-alist
'(
("meeting" . ?m)
("review" . ?r)
("analysis" . ?a)
("statistical" . ?s)
("documenting" . ?d)
("coding" . ?c)
)
)
Tasks can have automatic actions taken to tag them. Following BH, I’m using the following triggers:
- moving a task to CANCELLED adds a :CANCELLED: tag
- moving a task to a done state removes cancelled tags
(setq
;; Triggers for state changes
org-todo-state-tags-triggers
(quote (
;; Move to cancelled adds the cancelled tag
("CANCELLED" ("CANCELLED" . t))
;; Move to waiting adds the waiting tag
("WAITING" ("WAITING" . t))
;; Move to a done state removes waiting/cancelled
(done ("WAITING") ("CANCELLED"))
("DONE" ("WAITING") ("CANCELLED"))
;; Move to todo, removes waiting/cancelled
("TODO" ("WAITING") ("CANCELLED"))
)
)
)
To make sure that tasks with child tasks are not completed prematurely:
(setq
;; Ensure child dependencies complete before parents can be marked complete
org-enforce-todo-dependencies t
)
We’ll want to move stuff out of our agenda tree at some stage. This sets up our archiving to go into the archive sub-directory:
(setq
;; Where I'm going to archive stuff
org-archive-location "archive/%s_archive::"
)
;; How archive files will appear
(defvar org-archive-file-header-format "#+FILETAGS: ARCHIVE\nArchived entries from file %s\n")
When setting up a task, you can add effort estimates for billing/budgeting, and tracking how you’re going with your tasks. Apparently the best way to set effort is by using column view. Next I define the columns to display (and their widths), and also provide default effort values.
Related to this is how tasks are clocked. If I clock-in and clock-out immediately (such as when capturing an email), I shouldn’t record that clock.
(setq
;; Set column view headings
org-columns-default-format "%50ITEM(Task) %10Effort(Effort){:} %10CLOCKSUM"
;; Set default effort values
org-global-properties (quote (("Effort_ALL" . "0:15 0:30 1:00 2:00 4:00 6:00 8:00 16:00")))
;; When there's 0 time spent, remove the entry
org-clock-out-remove-zero-time-clocks t
)
I wanted some bling! I added the all-the-icons requirement above, now the next block of code sets some bling in the agenda:
(setq org-agenda-category-icon-alist
`(("TODO" (list (all-the-icons-faicon "tasks")) nil nil :ascent center)))
;; (setq
;; Add fancy icons to the agenda...
;; org-agenda-category-icon-alist
;; '(
;; (("TODO" (#("" 0 1 (font-lock-ignore t rear-nonsticky t display (raise -0.24) face (:family "FontAwesome" :height 1.2)))) nil nil :ascent center))
;; ;; (`(("MEETING" ,(list (all-the-icons-faicon "tasks")) nil nil :ascent center)))
;; )
;; )
Habits allows some recurring tasks to reappear when marked done. See here for some good info. To enable habits, you need to load the org-habits
module into org, which is achieved with the following:
(add-to-list 'org-modules 'org-habit t)
The default agenda lacks a little oomph. What I’d like to see is collections such as:
- tasks for today
- tasks to be refiled
- tasks for next week
- tasks that are unscheduled
- tasks that are waiting/someday
This next bit of setup uses ~org-super-agenda~ to set up a nicely grouped agenda:
(setq org-agenda-custom-commands
'(("a" "Super Agenda"
((agenda "" ((org-agenda-span 'day)
(org-agenda-overriding-header "Today's Items")
(org-agenda-skip-scheduled-if-deadline-is-shown t)
(org-super-agenda-groups
'((:discard (:todo ("DONE" "CANCELLED")))
(:discard (:tag "MEETING"))
(:name "Today"
:time-grid t
:todo "TODAY"
:scheduled today
:order 0)
(:habit t)
(:name "Due Today"
:deadline today
:order 2)
(:name "Due Soon"
:deadline future
:order 8)
(:name "Overdue"
:deadline past
:order 7)
))))
(todo "" ((org-agenda-overriding-header "All Other TODOs")
(org-super-agenda-groups
'((:discard (:category ("Issues" "Refile" "Waiting")))
(:discard (:todo ("SOMEDAY")))
(:and (:deadline nil :scheduled nil))
(:discard (:scheduled t))
(:auto-category t :order 9)
))))
(todo "" ((org-agenda-overriding-header "Waiting for")
(org-super-agenda-groups
'((:category "Waiting")
(:discard (:anything t))
))))
(todo "" ((org-agenda-overriding-header "Issues")
(org-super-agenda-groups
'((:category "Issues")
(:discard (:anything t))
))))))
("r" "Daily review"
(
(todo "" ((org-agenda-overriding-header "Daily review")
(org-super-agenda-groups
'((:category "Refile")
(:discard (:anything t))
))))))
("i" "Issues Tracking"
(
(todo "" ((org-agenda-overriding-header "Issues tracking")
(org-super-agenda-groups
'((:category "Issues")
(:discard (:anything t))
))))))
("w" "Waiting for"
(
(todo "" ((org-agenda-overriding-header "Waiting for")
(org-super-agenda-groups
'((:category "Waiting")
(:discard (:anything t))
))))))
("s" "Someday"
(
(todo "" ((org-agenda-overriding-header "Someday")
(org-super-agenda-groups
'(
(:name "Someday"
:todo "SOMEDAY")
(:discard (:anything t))
)
)))))
("d" "Done in the last week" tags "+TODO=\"DONE\"+CLOSED>=\"<-6d>\"")
)
)
(org-super-agenda-mode);; Custom agenda views
The last line only shows one copy of the task—without it, both the scheduled, and the deadline task will show up.
2019-01-14: I’ve now added a new ‘task’ view in the custom agenda above. This is to list all projects that I have a hand in, just to keep me on track. I’ve added a new keyword ‘PROJECT’ to do this, which can be marked as DONE once the whole project is completed or cancelled.
Finally close off the with=eval-after-load
from earlier:
To make it a little easier to search, use ripgrep. It replaces the current org-search-view which is not helpful (imo).
(defun sprazza/org-search ()
"Search in the org directory with `rg'."
(interactive)
(spacemacs/compleseus-search nil (expand-file-name org-directory))
)
(progn
(spacemacs/set-leader-keys
"aos" 'sprazza/org-search
)
;; Is not as helpful in major mode, but can be done...
;; (spacemacs/set-leader-keys-for-major-mode 'org-mode
;; "q" 'sprazza/org-search
;; )
)