10 July 2023 7:56 AM (emacs | org-mode | config)
This Emacs configuration snippet for org-mode changes a task's state from whatever “head” state it's in into the next state in its sequence when you clock in to a task. I do this by setting the org-clock-in-switch-to-state
variable.
Different sequences of TODO states
First, just to make sure that this is explained in here, it's possible in org-mode to specify multiple sequences of task states, for example I had this in one of my org files:
#+SEQ_TODO: TODO WIP BLOCKED | DONE #+SEQ_TODO: READ READING | FINISHED STOPPED #+SEQ_TODO: WATCH WATCHING | WATCHED #+SEQ_TODO: LISTEN LISTENING | DONE
This means that there are 4 sequences I've set up. A task can start as either TODO
, READ
, WATCH
, or LISTEN
, and then it'll move to a different next state1 depending on which initial state was picked. WIP
comes after TODO
, WATCHING
after WATCH
, etc. They generally don't cross, although org-mode will get confused as soon as I change any TODO
or LISTEN
task to DONE
since at that point it can't figure out what it would change back to if it turns out I wasn't done after all. It'll make it TODO
if I move forward from DONE
in either case.
Here is the graph showing the paths of each sequence:
Although this doesn't actually affect me at all in any way because I have org-use-fast-todo-selection
set to t
.
Making The Switch
Getting back to my snippet: org-clock-in-switch-to-state
can be set to either a string, which will just always change it to that particular state when you clock in, or a function that takes a single parameter (the current state of the task you're clocking in to). For this case I want the function, because I won't know which state I want to change to until I know the current state, since TODO
will change to WIP
, READ
to READING
, etc. but also when a task is already in the state READING
, for example, I don't want it to change at all.
(defun oni-org-maybe-change-todo-state (current-state) "Change the state of the current task to its next state. Only do this outside of a capture buffer and when CURRENT-STATE is the head state of whichever sequence of states applies to this task." (if (and (not org-capture-mode) (member current-state org-todo-heads)) (cadr (member current-state org-todo-keywords-1)) current-state))
First I make sure that we're not in a capture buffer. Some of my capture templates state that they should clock in to whatever I'm capturing right away, and in that case the task I'm capturing might immediately change from TODO
to WIP
, for example.
Then I check to see if the current state is in the org-todo-heads
variable, which contains only the first todo state of each possible sequence of states. Let's assume my todo states are:
#+SEQ_TODO: TODO WIP BLOCKED | DONE
Checking that the current-state
is in org-todo-heads
basically means I check to make sure that current-state
is TODO
and not any of the other ones. I do this so that if I clock in to a WIP
task, it doesn't automatically switch to blocked.
If I'm not in a capture buffer, and the current state is one of the head ones, I search for the current state in the org-todo-keywords-1
which is a simple flat list of all the possible todo states org-mode knows about. This is easier to work with than org-todo-keywords
, since that is an alist of (type . list-of-states)
and has a bunch of information I don't need. I return whatever comes right after the current state.
Returning whatever next state is in the list does mean that if the next state is DONE
, it'll immediately set it to done. But there is no real way to check that with the way I've done this. There is just the next state.
Finally you just set this function as the value of org-clock-in-switch-to-state
and then you're good to go.
(setq org-clock-in-switch-to-state #'oni-org-maybe-change-todo-state)
Footnotes:
By which I mean by pressing C-c C-t
when org-use-fast-todo-selection
is nil
or pressing C-S-<right>
on the headline.