13

I have several tasks in my Org file, one of which has items starting with a checkbox (but only those). They all have a statistic cookie at their end ([n/m]).

I'm looking for a way to automatically update the parent task from TODO to DONE once all of the checkboxes are marked as done as well.

The documentation says:

If you would like a TODO entry to automatically change to DONE when all children are done, you can use the following setup [...]

Another possibility is the use of checkboxes to identify (a hierarchy of) a large number of subtasks (see Checkboxes).

The way I understand that last part is that if children items are checkboxes, parent state should already be automatically updated (which is not the case).

What am I missing?


Edit:

From org-after-todo-statistics-hook's documentation (org.el):

Hook that is called after a TODO statistics cookie has been updated. Each function is called with two arguments: the number of not-done entries and the number of done entries.

Which means that the proposed code from orgmode.org:

(defun org-summary-todo (n-done n-not-done)
  "Switch entry to DONE when all subentries are done, to TODO otherwise."
  (let (org-log-done org-log-states)   ; turn off logging
    (org-todo (if (= n-not-done 0) "DONE" "TODO"))))

(add-hook 'org-after-todo-statistics-hook 'org-summary-todo)

won't count checkbox items.

5
  • Do you have a statistics cookie in the headline? Like this: * Heading [0/1]
    – erikstokes
    Commented Jan 26, 2016 at 14:27
  • Yes, there is. Forgot to mention that bit my bad. Commented Jan 26, 2016 at 14:30
  • 1
    I think this misunderstanding comes from the org manual not being so clear in this case. The line that starts "Another possibility is the use of checkboxes..." is a new paragraph under the heading "Breaking tasks down into subtasks". I think that "another possibility" in this case is referring to another possibility for breaking tasks into subtasks not another possibility for automatically changing the TODO state. Maybe try their suggested function and hook (though I couldn't get this to wokr)
    – elethan
    Commented Jan 26, 2016 at 15:09
  • @elethan It actually makes sense, I guess. Yes see my edit :) Commented Jan 26, 2016 at 15:26
  • 4
    This is a FAQ.
    – NickD
    Commented Feb 11, 2019 at 19:17

3 Answers 3

7

As you mentioned in your updated question, org-after-todo-statistics-hook isn't what you want. What you want is org-checkbox-statistics-hook.

I use this function to do what you describe (they should really get this functionality into org-mode):

(defun my/org-checkbox-todo ()
  "Switch header TODO state to DONE when all checkboxes are ticked, to TODO otherwise"
  (let ((todo-state (org-get-todo-state)) beg end)
    (unless (not todo-state)
      (save-excursion
    (org-back-to-heading t)
    (setq beg (point))
    (end-of-line)
    (setq end (point))
    (goto-char beg)
    (if (re-search-forward "\\[\\([0-9]*%\\)\\]\\|\\[\\([0-9]*\\)/\\([0-9]*\\)\\]"
                   end t)
        (if (match-end 1)
        (if (equal (match-string 1) "100%")
            (unless (string-equal todo-state "DONE")
              (org-todo 'done))
          (unless (string-equal todo-state "TODO")
            (org-todo 'todo)))
          (if (and (> (match-end 2) (match-beginning 2))
               (equal (match-string 2) (match-string 3)))
          (unless (string-equal todo-state "DONE")
            (org-todo 'done))
        (unless (string-equal todo-state "TODO")
          (org-todo 'todo)))))))))

(add-hook 'org-checkbox-statistics-hook 'my/org-checkbox-todo) will it call it every time you toggle a checkbox.

It does require you have a statistics cookie (the thing you get with [/] or [%] then C-c C-c) in the header.

1
  • 2
    Do you happen to have a version that, when the TODO is scheduled to repeat, unchecks the checklist for the scheduling again? :)
    – ctietze
    Commented May 16, 2018 at 17:40
0

I wanted a version that does what @ctietze is looking for--toggle the checkbox state for items in the checklist. I tried calling org-reset-checkbox-state-subtree but got errors about too many nested frames (I'm using Spacemacs).

Here is my working solution:

    (defun my/org-reset-checkbox-state-subtree ()
    "Simplified version of org-list builtin"
    ;; Begin copy from org-reset-checkbox-subtree
    (org-narrow-to-subtree)
      (org-show-subtree)
      (goto-char (point-min))
      (let ((end (point-max)))
        (while (< (point) end)
          (when (org-at-item-checkbox-p)
            (replace-match "[ ]" t t nil 1))
          (beginning-of-line 2)))
      (org-update-checkbox-count-maybe 'all)
    ;; End copy from org-reset-checkbox-subtree
    )

  (defun my/org-checkbox-todo ()
    "Switch header TODO state to DONE when all checkboxes are ticked, to TODO otherwise"
    (let ((todo-state (org-get-todo-state)) beg end)
      (unless (not todo-state)
        (save-excursion
          (org-back-to-heading t)
          (setq beg (point))
          (end-of-line)
          (setq end (point))
          (goto-char beg)
          (if (re-search-forward "\\[\\([0-9]*%\\)\\]\\|\\[\\([0-9]*\\)/\\([0-9]*\\)\\]"
                                 end t)
              (if (match-end 1)
                  (if (equal (match-string 1) "100%")
                      (unless (string-equal todo-state "DONE")
                        (my/org-reset-checkbox-state-subtree)
                        (org-todo 'done))
                    (unless (string-equal todo-state "TODO")
                      (org-todo 'todo)))
                (if (and (> (match-end 2) (match-beginning 2))
                         (equal (match-string 2) (match-string 3)))
                    (unless (string-equal todo-state "DONE")
                      (my/org-reset-checkbox-state-subtree)
                      (org-todo 'done))
                  (unless (string-equal todo-state "TODO")
                    (org-todo 'todo)))))))))
  (add-hook 'org-checkbox-statistics-hook 'my/org-checkbox-todo)
0

Here's my version of @Inops' very helpful answer above. This version also handles an intermediate "DOING" state, and I think the logic is a little easier to follow.

(defun my/org-checkbox-summary-todo ()
  "Switch todo status based on status of checkbox subentries."
  (let ((todo-state (org-get-todo-state)) beg end)
    (unless (not todo-state)
      (save-excursion
        (org-back-to-heading t)
        (setq beg (point))
        (end-of-line)
        (setq end (point))
        (goto-char beg)
        (if (re-search-forward "\\[\\([0-9]*\\)%\\]\\|\\[\\([0-9]*\\)/\\([0-9]*\\)\\]"
                               end t)
            ;; `match-string' for a group in an unmatched alternative returns nil
            (let* ((p-done (match-string 1))
                   (n-done (match-string 2))
                   (n-total (match-string 3))
                   (new-state (cond ((equal p-done "0") "TODO")
                                    ((equal p-done "100") "DONE")
                                    ((equal n-done "0") "TODO")
                                    ((and n-done (equal n-done n-total)) "DONE")
                                    (t "DOING"))))
              (unless (string-equal todo-state new-state)
                  (org-todo new-state))))))))
(add-hook 'org-checkbox-statistics-hook #'my/org-checkbox-summary-todo)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.