Add outline-show-entry-and-parents to reveal entry hierarchy

* lisp/outline.el (outline-mode-prefix-map):
Rebind 'C-e' from 'outline-show-entry'
to 'outline-show-entry-and-parents'.
(outline-mode-menu-bar-map): Use 'outline-show-entry-and-parents'
instead of 'outline-show-entry'.
(outline-isearch-open-invisible): Use the new command instead of
the 'outline-show-entry' primitive.  This prevents unintended
side effects for packages relying on the base API and avoids the
'isolated item' effect.
(outline-show-entry-and-parents): New function to climb the tree,
reveal parent headings, and unfold the current entry (bug#79286).
This commit is contained in:
James Cherti 2026-06-04 16:55:10 -04:00 committed by Juri Linkov
parent 971fa88a58
commit e7e9c55ba7
2 changed files with 71 additions and 4 deletions

View file

@ -88,6 +88,12 @@ This means it won't get in your way even if it's slow for your
repository. As such, the 'vc-dir-show-outgoing-count' option is now
obsolete.
** Outline mode
*** New command 'outline-show-entry-and-parents'.
It is bound to 'C-e' and reveals the current entry
with its parent hierarchy.
* New Modes and Packages in Emacs 32.1

View file

@ -86,7 +86,7 @@ imitate the function `looking-at'.")
"C-t" #'outline-hide-body
"C-a" #'outline-show-all
"C-c" #'outline-hide-entry
"C-e" #'outline-show-entry
"C-e" #'outline-show-entry-and-parents
"C-l" #'outline-hide-leaves
"C-k" #'outline-show-branches
"C-q" #'outline-hide-sublevels
@ -130,8 +130,8 @@ imitate the function `looking-at'.")
(define-key map [show outline-show-branches]
'(menu-item "Show Branches" outline-show-branches
:help "Show all subheadings of this heading, but not their bodies"))
(define-key map [show outline-show-entry]
'(menu-item "Show Entry" outline-show-entry
(define-key map [show outline-show-entry-and-parents]
'(menu-item "Show Entry" outline-show-entry-and-parents
:help "Show the body directly following this heading"))
(define-key map [show outline-show-all]
'(menu-item "Show All" outline-show-all
@ -1095,7 +1095,7 @@ If FLAG is nil then text is shown, while if FLAG is t the text is hidden."
;; `outline-flag-region').
(defun outline-isearch-open-invisible (_overlay)
;; We rely on the fact that isearch places point on the matched text.
(outline-show-entry))
(outline-show-entry-and-parents))
(defun outline-hide-entry ()
"Hide the body directly following this heading."
@ -1123,6 +1123,67 @@ Show the heading too, if it is currently invisible."
(define-obsolete-function-alias 'show-entry #'outline-show-entry "25.1")
(defun outline-show-entry-and-parents ()
"Reveal the current entry and its parent hierarchy.
This command ensures that the current entry, all of its ancestor
headings, and their immediate sibling headings are visible.
The function iteratively unfolds the children and body of the target
entry until it is fully revealed. If invoked when the point is inside
a completely hidden subtree, it manages the visibility state to avoid
leaving the buffer in an inconsistent layout. This guarantees a safe
and predictable visual expansion."
(interactive)
;; Wrap in `save-match-data' because outline functions use regular
;; expressions. Without this, calling `outline-show-entry-and-parents'
;; programmatically would clobber the caller's match data, leading to
;; subtle, hard-to-trace bugs.
(save-match-data
;; Repeatedly expand the outline structure at point from the outside
;; in until the target text is fully visible.
;;
;; Think of this block as manually opening nested folds:
;; - It checks whether the heading at point is folded.
;; - If it is folded, it moves backward to that parent heading.
;; - It opens the heading to reveal its text and subheadings.
;; - It repeats this process layer by layer down to the target.
(let (heading-point
prior-heading-point)
(while (condition-case nil
(save-excursion
;; Workaround: `outline-back-to-heading' throws an
;; `outline-before-first-heading' error if the
;; heading is on the first line (e.g., in
;; `markdown-ts-mode') and point is deep within the
;; hidden body of that folded first heading.
(vertical-motion 0)
;; Navigate backward to the nearest visible heading
(outline-back-to-heading)
(setq heading-point (point))
;; Break the loop if we stop making progress,
;; preventing infinite recursion
(if (eq heading-point prior-heading-point)
;; Break out of the loop
nil
(setq prior-heading-point heading-point)
;; Check if the heading is folded by inspecting the
;; end of the line
(when (invisible-p (pos-eol))
;; Ignore errors to guarantee the target entry is
;; still revealed via `outline-show-entry' even
;; if a buggy third-party `outline-level'
;; function fails during child expansion.
(ignore-errors (outline-show-children))
;; Show the body directly following this heading
(outline-show-entry)
;; Return t to continue drilling down to the next
;; layer of the outline hierarchy
t)))
(outline-before-first-heading
nil))))))
(defun outline-hide-body ()
"Hide all body lines in buffer, leaving all headings visible.
Note that this does not hide the lines preceding the first heading line."