Subject: Re: bug#79286: outline-mode: attempting to open folded sections does not work as expected inside a deeply nested header

From: James Cherti <[email-masked]>
Date: Fri, 22 Aug 2025 10:47:15 -0400

Here is an improved version of my-outline-show-entry that
I am currently using to address this issue in my
configuration.

I would greatly appreciate any comments or suggestions
prior to finalizing the patch.

--8<---------------cut here---------------start------------->8---
;; Author James Cherti
;; GitHub: https://github.com/jamescherti
(defun my-outline-legacy-show-entry ()
   "Show the body directly following this heading.
Show the heading too, if it is currently invisible.
(This is the Emacs version of `outline-show-entry'.)"
   (interactive)
   (save-excursion
     (outline-back-to-heading t)
     (outline-flag-region (1- (point))
                          (progn
                            (outline-next-preface)
                            (if (= 1 (- (point-max) (point)))
                                (point-max)
                              (point)))
                          nil)))

(defun my-outline-heading-folded-p ()
   "Return non-nil if the body following the current heading is folded."
   (save-excursion
     (end-of-line)
     (outline-invisible-p (point))))

(defun my-outline-show-entry ()
   "Ensure the current heading and body are fully visible.
Repeatedly reveal children and body until the entry is no longer folded."
   (interactive)
   (save-excursion
     ;; Repeatedly reveal children and body until the entry is no longer 
folded
     (while (my-outline-heading-folded-p)
       (save-excursion
         (outline-back-to-heading)
         (outline-show-children)
         (my-outline-legacy-show-entry)))

     ;; Final pass to guarantee the heading under the cursor and its 
body are
     ;; visible
     (my-outline-legacy-show-entry)))

;; Advice
(advice-add 'outline-show-entry :override #'my-outline-show-entry)
--8<---------------cut here---------------start------------->8---

--
James Cherti
GitHub: https://github.com/jamescherti
Website: https://www.jamescherti.com/

On 2025-08-21 15:09, James Cherti wrote:
> On 2025-08-21 14:34, Rahguzar wrote:
>> Hi James,
> 
> Hi Rahguzar,
> 
>> James Cherti <[email-masked]> writes:
>>
>>> When using outline-mode or outline-minor-mode, attempting to
>>> open folded sections (outline-show-entry) does not work as
>>> expected when the cursor is positioned inside a deeply
>>> nested header.
>>>
>>> Steps to Reproduce:
>>> -------------------
>>> 1. Open an outline-mode buffer with multiple
>>>      nested headers, for example:
>>>
>>> Level 1
>>> ** Level 2
>>> *** Level 3
>>> **** Level 4: A
>>> Some content here
>>> Some content here
>>> Some content here
>>> Some content here
>>> **** Level 4: B
>>> Some content here
>>> Some content here
>>> Some content here
>>> Some content here
>>> **** Level 4: C
>>> Some content here
>>> Some content here< PUT THE CURSOR HERE
>>> Some content here
>>> Some content here
>>> **** Level 4: D
>>> Some content here
>>> Some content here
>>> Some content here
>>> Some content here
>>> Some content here
>>> **** Level 4: E
>>> Some content here
>>> Some content here
>>> Some content here
>>>
>>> 2. Put the cursor before `< PUT THE CURSOR HERE`
>>>
>>> 3. Collapse all folds with: (hide-sublevels 1)
>>>
>>> 6. Show entry: (outline-show-entry)
>>>
>>> The folds fail to expand fully in nested hierarchies, leaving all
>>> content hidden except for `level 4: C`.
>>>
>>> Screenshot:
>>> https://github.com/user-attachments/ 
>>> assets/2ea6c3bd-0284-40a7-90e5-1619bdf3acc5
>>>
>>> Expected Behavior:
>>> ------------------
>>> The expected behavior is that only the heading at point (and
>>> its associated content) should be expanded, while all
>>> ancestor headings remain collapsed. (Similar to other editors.)
>>>
>>> Actual Behavior:
>>> ----------------
>>> Only the heading at point expands, while other headings
>>> remain invisible, which is confusing. In some cases, these
>>> hidden headings cannot be revealed until the parent heading
>>> is first collapsed and then expanded again.
>>
>> I have encountered this with isearch and was able to fix this
>> with in my init file.
>>
>> ```emacs-lisp
>>    (defun +outline-reveal (pos)
>>      (let ((heading-pos nil))
>>        (while (not (eq heading-pos (progn (outline-back-to-heading) 
>> (point))))
>>          (setq heading-pos (point))
>>          (outline-show-children)
>>          (goto-char pos))
>>        (outline-show-entry)
>>        (goto-char pos)))
>>
>>    (defun +outline-invsible-open (&optional _)
>>      (when (and outline-minor-mode
>>                 (outline-invisible-p))
>>        (+outline-reveal (point))))
>>
>>    (setq outline-isearch-open-invisible-function #'+outline-invsible- 
>> open)
>> ```
>> With an interactive spec the function +outline-reveal can be used as an
>> alternative to outline-show-entry without this problem but I can't
>> imagine a scenario where that might be needed.
>>
> 
> Unfortunately, neither `+outline-reveal` nor `+outline-invisible-open` 
> resolves this issue.
> 
> I wrote the following Elisp code to fix this issue:
> --8<---------------cut here---------------start------------->8---
> ;; Author James Cherti
> ;; GitHub: https://github.com/jamescherti
> (defun my-outline-show-entry ()
>    "Show the body directly following this heading.
> Show the heading too, if it is currently invisible."
>    (interactive)
> 
>    ;; Show previous headers
>    (unless (derived-mode-p 'outline-indent-minor-mode)
>      (save-excursion
>        (let ((func-invisible-p
>               (cond
>                ((and (derived-mode-p 'org-mode)
>                      (fboundp 'org-invisible-p))
>                 'org-invisible-p)
> 
>                ((and (or (derived-mode-p 'outline-mode)
>                          (derived-mode-p 'outline-minor-mode))
>                      (fboundp 'outline-invisible-p))
>                 'outline-invisible-p)
> 
>                (t
>                 'invisible-p)))
>              (initial-point (point))
>              (visual-point nil)
>              (visual-init-point nil))
>          ;; Unfolding loop
>          (while (and
>                  ;; Folded?
>                  (save-excursion
>                    (end-of-line)
>                    (funcall func-invisible-p (point)))
>                  ;; When the real point differs from the visual point, 
> it indicates
>                  ;; that the cursor is still located within a hidden 
> header.
>                  ;;
>                  ;; This prevents infinite loops if the unfolding stops 
> changing
>                  ;; the cursor position.
>                  (or
>                   (not visual-init-point)
>                   (not visual-point)
>                   (/= visual-init-point visual-point)))
> 
>            (save-excursion
>              (outline-back-to-heading)
>              (beginning-of-line)
>              (setq visual-point (point))
>              (with-no-warnings
>                (show-children))
> 
>              ;; Go back to point
>              (goto-char initial-point)
>              (outline-back-to-heading)
>              (beginning-of-line)
>              (setq visual-init-point (point))))
> 
>          ;; Previous version of `outline-show-entry'
>          (save-excursion
>            (outline-back-to-heading t)
>            (outline-flag-region (1- (point))
>                                 (progn
>                                   (outline-next-preface)
>                                   (if (= 1 (- (point-max) (point)))
>                                       (point-max)
>                                     (point)))
>                                 nil))))))
> 
> (advice-add 'outline-show-entry :override #'my-outline-show-entry)
> --8<---------------cut here---------------start------------->8---
> 
> This could likely be simplified using built-in functions.
> I welcome any suggestions for improvement.
> 
>>> Environment:
>>> ------------
>>> * Emacs version: 30.2 and master branch
>>> (ade6608e2587452c8ea565ce3057879379ebd0b5)
>>>
>>> -- 
>>> James Cherti
>>> GitHub: https://github.com/jamescherti
>>> Website: https://www.jamescherti.com/
>>
>> Rahguzar
> 
> -- 
> James Cherti
> GitHub: https://github.com/jamescherti
> Website: https://www.jamescherti.com/
> 
> 
>