2

Imagine we have a tree like this:

* TODO Read X book
** TODO Chapter 1
** TODO Chapter 2

I'd like to mark all nodes in the tree as DONE:

* DONE Read X book
** DONE Chapter 1
** DONE Chapter 2

What's the best way to do it in Elisp?

3 Answers 3

1

Org has a built-in function to map over entries: org-map-entries

(org-map-entries FUNC &optional MATCH SCOPE &rest SKIP)

Call FUNC at each headline selected by MATCH in SCOPE.

FUNC is a function or a lisp form. The function will be called without arguments, with the cursor positioned at the beginning of the headline. The return values of all calls to the function will be collected and returned as a list.

For example (org-map-entries (lambda () (org-todo 'done)) nil 'tree) when called with point at the start of a headline will mark that headline and all of its children done (whatever the done state for the current keyword is).

2
  • Thanks, Erik! It seems better than my solution. I thought org-todo did some additional bookkeeping, but looks like there's nothing special, so your approach wins. Sep 5, 2019 at 13:18
  • 1
    It looks like you can write just '(org-todo 'done) instead of a lambda, btw. Sep 5, 2019 at 13:19
1

I wrote this quick function to do it. Point needs to be on a heading or you'll get an error. I got help writing this from reading this article by Xah Lee.

(defun my/replace-todos-with-done ()
  (interactive)
  (save-match-data
    (unless (org-at-heading-p) (error "not at heading!"))
    (let ((regexp (rx bol (1+ "*") (1+ "\s") (submatch "TODO")))
          (bound (save-excursion (org-end-of-subtree) (point)))
          (case-fold-search nil))
      (while (search-forward-regexp regexp bound :no-error nil)
        (replace-match "DONE" t nil nil 1)))))
1
  • Thanks! Your answer gave me some new ideas. You can check my answer if you're interested. Sep 4, 2019 at 16:58
1

At first I came up with this solution:

(defun my/org-walk-tree (fn &rest args)
  (save-excursion
    (save-restriction
      (org-narrow-to-subtree)
      (org-save-outline-visibility 'use-markers
        (apply fn args)
        (while (outline-next-heading)
          (apply fn args))))))

(my/org-walk-tree 'org-todo 'done)

It works in simple cases, but I haven't tested it thoroughly.

Then I started reading org-todo function source code (should've done it earlier) and noticed that right at the start it uses org-loop-over-headlines-in-active-region variable:

Shall some commands act upon headlines in the active region?

When set to t, some commands will be performed in all headlines within the active region.

When it's set to t, org-todo uses org-map-entries to process all nodes in the subtree.

So all we need to do is:

(defun my/org-todo-subtree (keyword)
  (let ((org-loop-over-headlines-in-active-region t))
    (save-mark-and-excursion 
      (org-mark-subtree)
      (org-todo keyword))))

(my/org-todo-subtree "DONE")

I'm not sure if we need to use save-mark-and-excursion rather than save-excursion. It'd also be great to make it interactive.

With that variable name I found a similar question on Superuser: Editing multiple TODO simultaneously — priority and/or deadine. But I can't set my question as a duplicate of that one, as it's on the other site.

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.