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

Previous Next

Package: emacs;

Reported by: James Cherti <contact <at> jamescherti.com>

Date: Thu, 21 Aug 2025 18:10:02 UTC

Severity: normal

Full log


View this message in rfc822 format

From: James Cherti <contact <at> jamescherti.com>
To: Rahguzar <rahguzar <at> mailbox.org>
Cc: 79286 <at> debbugs.gnu.org
Subject: bug#79286: outline-mode: attempting to open folded sections does not work as expected inside a deeply nested header
Date: Thu, 21 Aug 2025 15:09:19 -0400
On 2025-08-21 14:34, Rahguzar wrote:
> Hi James,

Hi Rahguzar,

> James Cherti <contact <at> jamescherti.com> 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/




This bug report was last modified 29 days ago.

Previous Next


GNU bug tracking system
Copyright (C) 1999 Darren O. Benham, 1997,2003 nCipher Corporation Ltd, 1994-97 Ian Jackson.