8

Is there an idiomatic way to count org-mode subtrees?

I searched here on emacs.sx and other search engines, but I could not find an answer.

I know one could easily count subtrees with a regex, but I was wondering if the org API has means to fulfill more sophisticated counting needs, like,

  • How many second-level headlines under current tree?
  • How many third-level headlines in total in the current buffer?

3 Answers 3

7

org-map-entries is a typical way to do this:

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

Call FUNC at each headline selected by MATCH in SCOPE.

It returns the results of FUNC, so you can count them to get the number of headlines that matched MATCH (which is a org-agenda style search string). SCOPE can be set to tree to check only the tree point is on, nil to search the entire buffer, region to search the current region and a few other more specialized ones.

If you only care about the number of results, the function doesn't have to do anything. Your examples turn into:

  • (length (org-map-entries t "LEVEL=2" 'tree))
  • (length (org-map-entries t "LEVEL=3" nil))

The MATCH string can search for tags, properties or anything else you can do an agenda search for.

1
  • 1
    Jinx! Great minds always meet :-)
    – NickD
    Apr 17, 2019 at 21:34
5

These are untested but they should be close:

(length (org-map-entries t "LEVEL=2" 'tree))

(length (org-map-entries t "LEVEL=3" 'file))

org-map-entries is very powerful and can do a lot more than counting things: see the doc for details (which includes an example that's very close to this).

2
  • 1
    I wish I could accept two answers, yours is as good as erikstokes'. Since it cannot be done, I had to give precedence to timing. Thank you so much for the link.
    – gsl
    Apr 18, 2019 at 12:45
  • 1
    That is entirely appropriate.
    – NickD
    Apr 18, 2019 at 15:34
2

And here's the non/less-idiomatic way:

(defun count-subtrees ()
  (interactive)
  (let (lvs)
    (save-excursion
      (goto-char (point-max))
      (while (outline-previous-heading)
        (let* ((hl (org-element-at-point))
               (lv (org-element-property :level hl)))
          (push lv lvs))))
    (let* ((depth (cl-sort lvs #'<))
           (min-lv (car depth))
           (max-lv (car (last depth))))
      (cl-loop for lv from min-lv to max-lv
               for count = (cl-count-if (lambda (elt)
                                          (= elt lv))
                                        lvs)
               collect (print (cons lv count))))))
2
  • Thank you, I tested the code a few hours back and it was not working, but now it works perfectly. I cannot accept it as answer for the "idiomatic way", but still I appreciate the code and upvoted. Very nice. Thank you.
    – gsl
    Apr 18, 2019 at 12:48
  • 1
    Phew. I was that close from a downvote. Anyhow, thanks for the kind words.
    – jagrg
    Apr 18, 2019 at 18:34

Your Answer

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

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