7

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.

ebpa
  • 7,319
  • 26
  • 53
  • 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 Mar 23 '15 at 19:41
  • http://lists.gnu.org/archive/html/emacs-orgmode/2014-04/msg00567.html – mbork Mar 23 '15 at 20:38

2 Answers2

11

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.

olaf b
  • 556
  • 2
  • 7
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))))
Dan
  • 32,584
  • 6
  • 98
  • 168
  • 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 Mar 25 '15 at 20:02
  • 1
    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 Apr 18 '16 at 14:18