8

I'm wondering if there is a good method of counting subheadings of a particular heading in org-mode. For example, with the following, return 4 (non-recursively) or 7 (recursively). Note that I don't want to use keywords (ex: TODO) and [/] syntax (https://stackoverflow.com/questions/13372550/orgmode-show-number-of-children-under-heading); I just want to calculate it programmatically using elisp.

sample org-mode outline

I'm hoping that I'm just missing a useful org function. Otherwise I may resort to counting results of a regex search using the outline patterns of levels ^\\*{n} bounded by the heading and org-forward-heading-same-level. This is messy and I'd like to do it the "org way" in case org mode ever ignores heading patterns found in source blocks or edge cases I haven't considered.

2
  • Even if elisp is used programmatically, something is still being counted -- e.g., the number of headings with a given number of stars (e.g., a particular level in the outline), the keywords, or the number of stars and keywords. Org-mode uses regex -- it's just a little complicated, that's all.
    – lawlist
    Commented Mar 23, 2015 at 19:41

2 Answers 2

12

The org function you are looking for is org-map-entries.

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

This calls function for each heading determined by MATCH and SCOPE and returns a list of all return values. Thus

(1-  (length (org-map-entries nil nil 'tree)))

returns the number of subentries of the heading at point. To get something a little bit more sophisticated, use FUNC:

(defun org-number-of-subentries (&optional pos match scope level)
  "Return number of subentries for entry at POS.
MATCH and SCOPE are the same as for `org-map-entries', but
SCOPE defaults to 'tree.
By default, all subentries are counted; restrict with LEVEL."
  (interactive)
  (save-excursion
    (goto-char (or pos (point)))
    ;; If we are in the middle ot an entry, use the current heading.
    (org-back-to-heading t)
    (let ((maxlevel (when (and level (org-current-level))
                      (+ level (org-current-level)))))
      (message "%s subentries"
               (1- (length
                    (delq nil
                          (org-map-entries
                           (lambda ()
                             ;; Return true, unless below maxlevel.
                             (or (not maxlevel)
                                 (<= (org-current-level) maxlevel)))
                           match (or scope 'tree)))))))))

The idea here is to call org-map-entries and have the function return true for any heading, you want to count.

4

Here's a function that will count the subheadings (which works more generally for outline-mode, and therefore for org-mode, which is built on top of outline-mode). You could, presumably, write a more elegant version, but this one works fine:

(defun outline-count-subheadings ()
  "Count the number of subheadings below the current heading."
  (let ((sum 0)
        (end (save-excursion
               (ignore-errors
                 (outline-end-of-subtree)
                 (point)))))
    (when end
      (save-excursion 
        (while (and (outline-next-heading)
                    (< (point) end)
                    (setf sum (1+ sum))))
        sum))))
2
  • I like the use of org (outline) movement-- a good DRY solution for a user function. This is probably more efficient than org-map-entries (suggested by @olaf-b), but isn't as neatly generalizable.
    – ebpa
    Commented Mar 25, 2015 at 20:02
  • 2
    This solution is about 10 times slower than using org-map-entries for a file that's 1.5MB with about 70 "hits" with several thousand headings. In a brief testing, it became apparent that it would be hard to beat the built-in API on efficiency, which org-map-entries is based on.
    – Emacs User
    Commented Apr 18, 2016 at 14:18

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.