#+title: Switch TODO state when clocking in #+options: toc:nil num:nil #+tags: emacs #+comment_status: closed #+status: draft #+DATE: Thu, 29 Jun 2023 05:42:03 GMT #+UPDATE_URL: /admin/modify-post/2023%252f06%252f29%252fswitch-todo-state-when-clocking-in This [[id:56919921-d783-4264-9a6a-7f6ae2066508][Emacs]] configuration snippet for [[id:f8d05ece-d1eb-477f-99a7-5ff780c4b948][org-mode]] changes a task's state from whatever “head” ’state it's in into the next state in its sequence. I do this by setting the =org-clock-in-switch-to-state= variable. You can set this variable to either a string, which will just always change it to that particular state, or a function that takes a single parameter. For this case I want the function, because I might not know which state I want to change to for all states it might already have. #+begin_src emacs-lisp (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)) #+end_src So the parameter to the function is the current state of the task I've clocked in to. 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: #+begin_src org ,#+SEQ_TODO: TODO WIP BLOCKED | DONE #+end_src 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 [[id:f8d05ece-d1eb-477f-99a7-5ff780c4b948][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. I'm also not quite sure how that would work with automatic clocking out when a task is marked as done. My first guess would be that it just starts the clock, switches the state to done, stops the clock, and looks like it didn't start a clock at all. Finally you just set this function as the value of =org-clock-in-switch-to-state= and then you're good to go. #+begin_src elisp (setq org-clock-in-switch-to-state #'oni-org-maybe-change-todo-state) #+end_src * Different sequences of TODO states Just to make sure that this is explained in here, it's possible in [[id:f8d05ece-d1eb-477f-99a7-5ff780c4b948][org-mode]] to specify multiple sequences of task states, for example I had this in one of my org files: #+begin_src org ,#+SEQ_TODO: TODO WIP BLOCKED | DONE ,#+SEQ_TODO: READ READING | FINISHED STOPPED ,#+SEQ_TODO: WATCH WATCHING | WATCHED ,#+SEQ_TODO: LISTEN LISTENING | DONE #+end_src 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 state depending on which initial state was picked. =WIP= comes after =TODO=, =WATCHING= after =WATCH=, etc. They generally don't cross, although I'm sure [[id:f8d05ece-d1eb-477f-99a7-5ff780c4b948][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. Here is the graph showing the potential paths of each sequence. #+begin_src dot :file data/20230628161532-switch_todo_state_when_clocking_in-state_sequences.svg :export result digraph { rankdir=LR; TODO -> WIP -> BLOCKED -> DONE -> TODO READ -> READING -> FINISHED -> STOPPED -> READ WATCH -> WATCHING -> WATCHED -> WATCH LISTEN -> LISTENING -> DONE } #+end_src #+RESULTS: [[file:data/20230628161532-switch_todo_state_when_clocking_in-state_sequences.svg]] Although this doesn't actually affect me at all in any way because I have =org-use-fast-todo-selection= set to =t=.