Doom Emacs tweaks: Org Journal and Super Agenda
I’ve been back a week from a work trip, and — as I often do after a busy period — I’ve been taking stock of what I need to do next and refining my systems a bit. Cynics might argue that this is either procrastination or yak shaving (and they wouldn’t be entirely wrong), but when I have been at full stretch at work I do find it helpful to have a period of sorting out the mess that I had to let accumulate, to tie up loose ends and to plan the next bit of work.
I am spending more and more of my time in Emacs for all things, so I wanted to refine both my agenda and journalling setup.
I had tried using org-super-agenda a while back, but had somehow failed to get
it set up correctly. I couldn’t seem to get it to output what I wanted, and
eventually went back to setting up a few custom groups using the built-in agenda
features. This was OK, but didn’t allow me to do quite what I wanted. Then I
noticed that Jack Baty had posted his org-super-agenda
configuration, and I
decided to give it another go. I copied Jack’s configuration (substituting
def-package!
for use-package
because I am using Doom Emacs), and found that
it worked — success! After some thinking about what was important to me to see
on my agenda, I ended up with this configuration:
(def-package! org-super-agenda
:after org-agenda
:init
(setq org-agenda-skip-scheduled-if-done t
org-agenda-skip-deadline-if-done t
org-agenda-include-deadlines t
org-agenda-block-separator nil
org-agenda-compact-blocks t
org-agenda-start-day nil ;; i.e. today
org-agenda-span 1
org-agenda-start-on-weekday nil)
(setq org-agenda-custom-commands
'(("c" "Super view"
((agenda "" ((org-agenda-overriding-header "")
(org-super-agenda-groups
'((:name "Today"
:time-grid t
:date today
:order 1)))))
(alltodo "" ((org-agenda-overriding-header "")
(org-super-agenda-groups
'((:log t)
(:name "To refile"
:file-path "refile\\.org")
(:name "Next to do"
:todo "NEXT"
:order 1)
(:name "Important"
:priority "A"
:order 6)
(:name "Today's tasks"
:file-path "journal/")
(:name "Due Today"
:deadline today
:order 2)
(:name "Scheduled Soon"
:scheduled future
:order 8)
(:name "Overdue"
:deadline past
:order 7)
(:name "Meetings"
:and (:todo "MEET" :scheduled future)
:order 10)
(:discard (:not (:todo "TODO")))))))))))
:config
(org-super-agenda-mode))
It looks really complicated, but the nice thing about org-super-agenda
is that
items aren’t duplicated between the headings, and also that if there is nothing
for particular heading (for example, there are no tasks marked priority ‘A’),
then that section will not be shown. What I end up with is a concise list
of the key stuff I need on my radar right now. I should say that I don’t keep
all my tasks in org-mode
: I use OmniFocus for that, but I copy tasks over each
day into my journal entry for the day, which is the source for the “Today’s
tasks” heading. I also sometimes enter tasks or meeting entries (“MEET”
keyword) for particular projects into the Org file for that project so that I
can see it in the context of my notes. The todos get duplicated in OmniFocus and
meetings are in my calendar. It might seem like needless duplication, but the
way I look at it is that OmniFocus and my calendar are my trusted repositories
of information which I am scrupulous about keeping updated, but copying some
information over to my Org files helps me to focus on actually doing the
tasks, and also creating a record of what was done when and why, by adding
contextual notes around each item. Plus, I get to mark something as done twice,
which is double the satisfaction!
The other component of this is org-journal. This is another package which I had tried out before and stopped using. When I last investigated it (which was probably at least a couple of years ago), I think it only offered the option to create one journal file per day, which felt too fragmented to me. I like having a bit of context so that I can see the current day’s entry together with at least the rest of the week. At the time, I settled for using an org-capture template with a datetree, so that Emacs would create year, month and day headings for each of the entries in one file. That worked OK, and searching was easy because it was all in one file, but navigating around entries wasn’t as smooth as it could be.
When I went back to look at org-journal
I found that there is now a setting to
create either daily, weekly or monthly files. I settled on generating monthly
files and it is working really well for me. The file stays a more manageable
size while still providing me with the context I need, and the navigation
features of the package make it easy to move back and forth between entries. I
also like the calendar integration, which marks dates on the calendar which have
journal entries. The calendar mode map also has keybindings which enable you to
move to the next/previous day with entries, view the entry on a particular day,
or search for entries in the selected day, week or month.
However, I did have to tweak the keybindings a little. The bindings built in to
org-journal
conflict with the evil
mappings used in Doom Emacs. I therefore
added some custom bindings to the SPC
leader mapping and also to the
calendar-mode-map
to fix this.
;; in ~/.doom.d/+bindings.el
(map! :leader
(:prefix ("j" . "journal") ;; org-journal bindings
:desc "Create new journal entry" "j" #'org-journal-new-entry
:desc "Open previous entry" "p" #'org-journal-open-previous-entry
:desc "Open next entry" "n" #'org-journal-open-next-entry
:desc "Search journal" "s" #'org-journal-search-forever))
;; The built-in calendar mode mappings for org-journal
;; conflict with evil bindings
(map!
(:map calendar-mode-map
:n "o" #'org-journal-display-entry
:n "p" #'org-journal-previous-entry
:n "n" #'org-journal-next-entry
:n "O" #'org-journal-new-date-entry))
;; Local leader (<SPC m>) bindings for org-journal in calendar-mode
;; I was running out of bindings, and these are used less frequently
;; so it is convenient to have them under the local leader prefix
(map!
:map (calendar-mode-map)
:localleader
"w" #'org-journal-search-calendar-week
"m" #'org-journal-search-calendar-month
"y" #'org-journal-search-calendar-year)
So now I can hit SPC j
and then see a list of single key bindings to create,
search or navigate among journal entries. In calendar mode, I only need to hit a
single key (e.g. ‘o’ to open the entry for the selected day), unless I want to
use the searches, which are under the local leader binding (SPC m
).
My final tweak was to alter they way that the clocktable is presented. As I
mentioned, at the start of each day, I copy across the tasks for the day from
OmniFocus to my journal entry for the day. I am trying to get more accurate at
estimating how long something will take to accomplish, so I have started adding
‘Effort’ properties to these tasks and using org-clock
to time how long I
work on them. Incidentally, clocking in to tasks has other useful side effects,
because I can use various functions in org-mode that allow you to jump to the
clocked-in task to add notes (either directly or via org-capture
), which saves
me hunting around for it.
At the start of the month’s file, I add a clocktable report, which lists all the timed entries and their estimates, with subtotals of clocked time per day. After a lot of searches to figure out how to do it, I’ve also managed to tweak the table so that it adds another column which subtracts the actual time from the estimated time to show me how much I over- or under-estimated the time needed, and also renames the columns and moves the estimate columns to the right of the table.
;; Format org-mode clocktables the way we want to include Effort
;; In the clocktable header:
;; :formatter my/clocktable-write
(defun my/clocktable-write (&rest args)
"Custom clocktable writer.
Uses the default writer but shifts the first column right 3 columns,
and names the estimation error column."
(apply #'org-clocktable-write-default args)
(save-excursion
(forward-char) ;; move into the first table field
(org-table-move-column-right)
(org-table-move-column-right)
(org-table-move-column-right)
(org-table-next-field)
(insert "Est. error")
(org-table-previous-field)))
This is used in the header of the table, and automatically generates the
#+TBLFM:
line below the table, like so (actual entries removed for reasons of
privacy!):
#+BEGIN: clocktable :scope file :maxlevel 3 :link t :properties ("Effort") :formula "$5='(- $1 $4);U::@1$1=string(\"Estimate\")::@1$3=string(\"Total\")::@1$4=string(\"Task time\")" :formatter my/clocktable-write
#+CAPTION: Clock summary at [2019-08-10 Sat 19:02]
| Headline | Total | Task time | Estimate | Est. error |
|---------------------------------------------+--------+-----------+----------+------------|
| *Total time* | *6:53* | | | 00:00 |
|---------------------------------------------+--------+-----------+----------+------------|
#+TBLFM: $5='(- $4 $3);U::@1$4=string("Estimate")::@1$2=string("Total")::@1$3=string("Task time")
#+END:
I didn’t know you could programmatically manipulate tables like this — it’s
really useful. However, I did come up against a frustrating oddity. Even though
the ‘Est. error’ column is clearly column 5, (that’s how I create it above with
$5
), there doesn’t seem to be a way to target that column later to change the
column heading (which is why I ended up doing that in my function) or to alter
the total time line to sum up the estimation errors. If I try that I get error
messages that the column doesn’t exist, and if I try to refer to the last column
using the $>
shortcut, it modifies column 4. If I add the code manually to the
#+TBLFM:
line it works fine, but gets over-written when I update the table
with C-c C-c
. Anyway, it’s not really a problem. The table is useful as it is,
and I can finalise its appearance manually at the end of the month by modifying
the #+TBLFM:
line.
Emacs may provide the gnarliest of rabbit holes in which to get lost, but I
continue to be amazed about how adaptable it is, even if — like me — you
don’t really know much Lisp. Even if you are a beginner, being able to use the
built-in help system to easily find the source code for particular built-in
features (like that for org-clocktable-write-default
above) and use that as a
model for the changes you want to make, or to figure out which package is
setting a variable or a keybinding is incredibly powerful. Essentially you are
modifying and building upon your copy of Emacs itself, not just sticking on a
plug-in which is limited in power and scope.