Zettelkasten Forum


Emacs org-mode: auto-update TODO item states to represent checkbox progress

edited February 2021 in Software & Gadgets

I use org-mode TODO lists for all my tasks, including programming projects. Single "things" I want to implement in The Archive, for example, often require multiple steps to be completed, not all of which are in close proximity in code, so I keep track of these, lest I forget they have to be done!

I customized my setup a bit to update the TODO items when I work with these checkbox items.

The gist of this stuff is: automatically change TODO to DOING to DONE state when ticking off checkboxes of the item.

A detailed write-up is here: https://christiantietze.de/posts/2021/02/emacs-org-todo-doing-done-checkbox-cycling/

Example of what happens when you interact with checkboxes

Initial state:

* TODO finish this [0/2]
- [ ] first step
- [ ] second step

Ticking off one checkbox:

* DOING finish this [1/2]
- [X] first step
- [ ] second step

Ticking of all:

* DONE finish this [2/2]
- [X] first step
- [X] second step

Gist of the code

(setq org-todo-keywords
      (quote ((sequence "TODO(t)" "DOING(g)" "|" "DONE(d)"))))

(defun org-todo-if-needed (state)
  "Change header state to STATE unless the current item is in STATE already."
  (unless (string-equal (org-get-todo-state) state)
    (org-todo state)))

(defun ct/org-summary-checkbox-cookie ()
  "Switch header state to DONE when all checkboxes are ticked, to TODO when none are ticked, and to DOING otherwise"
  (let (beg end)
    (unless (not (org-get-todo-state))
      (save-excursion
        (org-back-to-heading t)
        (setq beg (point))
        (end-of-line)
        (setq end (point))
        (goto-char beg)
        ;; Regex group 1: %-based cookie
        ;; Regex group 2 and 3: x/y cookie
        (if (re-search-forward "\\[\\([0-9]*%\\)\\]\\|\\[\\([0-9]*\\)/\\([0-9]*\\)\\]"
                               end t)
            (if (match-end 1)
                ;; [xx%] cookie support
                (cond ((equal (match-string 1) "100%")
                       (org-todo-if-needed "DONE"))
                      ((equal (match-string 1) "0%")
                       (org-todo-if-needed "TODO"))
                      (t
                       (org-todo-if-needed "DOING")))
              ;; [x/y] cookie support
              (if (> (match-end 2) (match-beginning 2)) ; = if not empty
                  (cond ((equal (match-string 2) (match-string 3))
                         (org-todo-if-needed "DONE"))
                        ((or (equal (string-trim (match-string 2)) "")
                             (equal (match-string 2) "0"))
                         (org-todo-if-needed "TODO"))
                        (t
                         (org-todo-if-needed "DOING")))
                (org-todo-if-needed "DOING"))))))))
(add-hook 'org-checkbox-statistics-hook #'ct/org-summary-checkbox-cookie)
Post edited by ctietze on

Author at Zettelkasten.de • https://christiantietze.de/

Comments

  • edited February 2021

    I used to have a Doing like OP, aka PRESENT. It reminds me of the "moment"
    is a "gift".

    I, later on, abandon this idea. When I am doing a task, the modeline shows
    the title of the task, so it's like a reminder severing the similar
    idea.

    On the other hand, I do have four states for marking the tasks as a
    pause: HOLD(h@/!) WAIT(w@/!) DONE(d) CANCELLED(c).

    HOLD means I need to do it later on.

    WAIT means I need to receive actions from other events, e.g. other
    people.

    CANCELLED means I do not have time to do it on that date.

    DONE means I have accomplished my goal.

    Post edited by learning_ran on
  • edited February 2021

    @ctietze You are absolutely slaying me with the cookie flashes. :D:D:D
    Comedy gold. The apotheosis of the repetition idea might be Kristen Schall is Horse

    I'm at the stage where I know just enough elisp to hopelessly drown myself in complexity. Really aware of how refined a function this is. Very useful for me. Thanks for sharing it.

    Going to implement it in my config presently because who wants to work anyway.

  • @kohled Tried to make it a bit less boring, so great to see it worked :)

    @learning_ran How do you show the current task in the modeline? If that could quickly take me back to the task from other documents, that's be cool. (I also use HOLD, WAITING, and CANCELLED 👍)

    Author at Zettelkasten.de • https://christiantietze.de/

  • edited February 2021

    @ctietze

    How do you show the current task in the modeline?

    I use org-clock-in to jump into the task, I am not sure you are using it or not.

    C-c C-x TAB runs the command org-clock-in (found in org-mode-map),
    which is an autoloaded interactive compiled Lisp function in
    ‘org-clock.el’.
    

    I remember it can show the headline info of the task you start to clock-in.

    But I am not sure, I did not test it with emacs -q.

    I did some minor modifications to modeline as well, it might help you to figure it out.

    ;;;; set modeline
    (defun my/org-mode-line ()
      (setq-default mode-line-format
            (list "%e"
                  '(:eval
                    (window-numbering-get-number-string))
                  " "
                  'mode-line-mule-info
                  'mode-line-modified
                  'mode-line-frame-identification
                  "%b"
                  "  "
                  "%n"
                  "    "
                  'mode-line-position
                  'global-mode-string
                  "    ["
                  'mode-line-modes
                  "]"
                  )))
    
    ;; this change the org-color to purple
    (set-face-attribute 'org-mode-line-clock  nil
                        :background "purple1"
                        :foreground "gray"
                        :overline nil
                        :underline nil)
    
  • I've hit the limits of my confessedly shallow skillset trying to get doom emacs to recognize this. If some kind, "yay evil" soul knows whatever simple code I'm not figuring out to get the hook recognized, please share. Meanwhile I'll stop throwing spaghetti at the wall and return to reading the manual.

  • @kohled What exactly doesn't work for you?

    In the snippet above I forgot to share org-todo-if-needed:

    (defun org-todo-if-needed (state)
      "Change header state to STATE unless the current item is in STATE already."
      (unless (string-equal (org-get-todo-state) state)
        (org-todo state)))
    

    I've edited the post accordingly. Sorry!

    When I run emacs -q and paste in the code in the scratch buffer, evaluate the stuff, then go to an org-mode buffer, things work pretty well after fiddling around with the TODO/DOING/DONE settings to make it recognize "DOING" properly. If that gives you trouble, I'll look into why it's not recognized.

    Author at Zettelkasten.de • https://christiantietze.de/

  • @ctietze org-todo-if-needed FTW!

    I'd tried running the code interactively and was getting a void function org-todo-if-needed backtrace. Should have mentioned that. I'm finding one of the myriad potholes of being an inchoate coder is not knowing enough to read and trust error messages. I was looking for documentation for org-todo-if-needed. At least I was stumbling in the right direction and reading the manual. All told a good learning experience.

    It's a sweet function that will save several keystrokes a day. Plus a little hit of 'oh dang that's neat' joy with each checkbox toggle. Truly appreciate it.

Sign In or Register to comment.