1

Original title: How to display sub-heading count in the end of a heading in org-mode

It would be very helpful if such a feature can be acheived. I always have difficulty when visiting a long-time not visited org file, where it may have lots of headings and their sub-headings, and so on.

I need to have an overall grasp of which headings have sub-headings when they are collapsed. Since, headings and sub-headings are a lot, so I'm used to only showing first-level headings and opening up a certain heading when I know exactly that it has sub-headings. If I use C-Tab, all second-level sub-headings of first-headings will be shown up, which adds more obstacle for navigating!

In ideal, sub-headings of their parent heading should also have a sub-heading count in the end of them.

I saw this post Counting sub-headings in org-mode using elisp in emacs.stackchange.com, where the adopted answer implemented a function to print the total count of all the sub-headings of a heading are, where the cursor is at.

For example:

* First-level heading that has sub-headings [5]...
    ** Sub-heading1 that has its own sub-headings [3]...
        *** Heading1 [0]...
            Just some text
        *** Heading2 [1]...
            Some text
            **** Heading1 [0]...
                Just some text
    ** Sub-heading 2 [0]...
        Just some text

It would be more intuitive, only to show count of sub-headings, but not recursively, as follows:

* First-level heading that has sub-headings [2]...
    ** Sub-heading1 that has its own sub-headings [2]...
        *** Heading1 [0]...
            Just some text
        *** Heading2 [1]...
            Some text
            **** Heading1 [0]...
                Just some text
    ** Sub-heading 2 [0]...
        Just some text

Note: The behavior of ellipsis should be the default, the example above where the ellipsis is displayed when headings are expanded, is for better demonstration.

Is it possible to add it to each heading indicating how many sub-headings a heading has?

0

2 Answers 2

1

Edit: A big thank to @dalanicolai for most of the work to refactor the original code to perfection.

It toggles refreshing of sub-entry counts when an org-file is opened or saved to file.

Here's final version of the code by @dalanicolai which does not create superfluous overlay objects, and where the count displaying position is customizable (see comments):

(defcustom org-subentry-count-at-start nil
  "Show the subentry count before the title, not after."
  :group 'org-subenty-count
  :type 'boolean)

(defvar org-subentry-count-in-headings-p nil
  "t to enable org-subentry-count-in-headings
     nil to disable org-subentry-count-in-headings")

(defun org-subentry-count-in-headings ()
  (org-map-entries
   (lambda ()
     (when (looking-at org-complex-heading-regexp)
       (let* ((group (if org-subentry-count-at-start 1 0))
              (beg (match-beginning group))
          (end (match-end group))
              (level (length (match-string 1))) ; stars in headline
              (subheading-count (format "%s" (1- (length (org-map-entries nil nil 'tree)))))
              (ov (car (seq-filter
                        ;; search for subentry-count overlay
                        (lambda (o)
                          (eq (overlay-get o 'type) 'subentry-count)) 
                        (overlays-in (line-beginning-position)
                     (line-end-position))))))
         (if ov ; only create overlay if not exists yet
         (move-overlay ov beg end)
           (setq ov (make-overlay beg end))
           (overlay-put ov 'type 'subentry-count) ; add a type, for easy filtering
           (overlay-put ov 'evaporate t))
         ;; only add/remove 'after-string' prop in toggle
         (if org-subentry-count-in-headings-p
             (overlay-put ov 'after-string (propertize (format " [%s]" subheading-count)
                                                       'face (intern-soft (format "org-level-%d" level))))
           (overlay-put ov 'after-string nil)))))))

(defun toggle-org-subentry-count-in-headings ()
  (interactive)
  (if org-subentry-count-in-headings-p
      (setq org-subentry-count-in-headings-p nil)
    (setq org-subentry-count-in-headings-p t))
  (org-subentry-count-in-headings))

(defun toggle-org-subentry-position ()
  (interactive)
  (setq org-subentry-count-at-start (not org-subentry-count-at-start))
  (setq org-subentry-count-in-headings-p t)
  (org-subentry-count-in-headings))

(add-hook 'org-mode-hook (lambda ()
                           (setq org-subentry-count-in-headings-p t)
                           (org-subentry-count-in-headings)
                           (add-hook 'after-save-hook 'org-subentry-count-in-headings)))

Screenshots: enter image description here

20
  • 1
    You are only deleting the overlays between beg and end. Generally, this will be only the headline overlay, but I guess, sometimes, there could also be other overlays in that region. So you would have to filter for the right overlay somehow. I think you are doing this effectively with the dolist i.c.w. when (you are only deleting all overlays with an 'after-string' in the region, which generally will be only the single headline overlay). So it looks perfectly fine to me... Dec 17, 2023 at 11:44
  • 1
    I've updated the code again. The manual mentions that overlay objects do not get deleted when using delete-overlay. So, I would say, to prevent creating new overlay objects all the time, it is better to just keep them in place, and only add/remove the after-string on toggle. Good luck with your further improvements. It's a nice feature! B.t.w, I think you could accept your own answer by now ;) Dec 17, 2023 at 12:43
  • 1
    Unfortunately, retrieving the face from the heading face does not work reliably, because the heading gets fontified via font-lock (I guess). So instead, I am 'calculating' the face from the heading level (for explanation, take a look at the last edit). I don't fully understand why, when placing the overlay at the beginning, the face gets 'inherited', but not when placing it at the end. Dec 17, 2023 at 13:09
  • 1
    B.t.w, there exists also this solution: stackoverflow.com/a/13372929/6837095 Dec 17, 2023 at 13:21
  • 1
    I made the displaying position customizable/toggleable (and slightly modified/updated the text in the answer). You could remove the original code and the text that explains the difference if you like (to make the answer cleaner), or you could keep it as it is now. Whatever you prefer :) Dec 18, 2023 at 22:52
0

M-x org-num-mode maybe? It's a default minor-mode i Org.

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.