GNU bug report logs - #78968
Outline: Fix and deprecate `insert` buttons

Previous Next

Package: emacs;

Reported by: Stefan Monnier <monnier <at> iro.umontreal.ca>

Date: Sun, 6 Jul 2025 19:22:02 UTC

Severity: normal

Found in version 31.0.50

To reply to this bug, email your comments to 78968 AT debbugs.gnu.org.

Toggle the display of automated, internal messages from the tracker.

View this report as an mbox folder, status mbox, maintainer mbox


Report forwarded to monnier <at> iro.umontreal.ca, bug-gnu-emacs <at> gnu.org:
bug#78968; Package emacs. (Sun, 06 Jul 2025 19:22:02 GMT) Full text and rfc822 format available.

Acknowledgement sent to Stefan Monnier <monnier <at> iro.umontreal.ca>:
New bug report received and forwarded. Copy sent to monnier <at> iro.umontreal.ca, bug-gnu-emacs <at> gnu.org. (Sun, 06 Jul 2025 19:22:02 GMT) Full text and rfc822 format available.

Message #5 received at submit <at> debbugs.gnu.org (full text, mbox):

From: Stefan Monnier <monnier <at> iro.umontreal.ca>
To: bug-gnu-emacs <at> gnu.org
Subject: Outline: Fix and deprecate `insert` buttons
Date: Sun, 06 Jul 2025 15:21:08 -0400
[Message part 1 (text/plain, inline)]
Package: Emacs
Version: 31.0.50


For Help buffer and Xref buffers we currently use `insert` for
`outline-minor-mode-use-buttons`, but that mechanism is clunky and its
implementation suffers from various undesirable properties.

E.g. currently turning `outline-minor-mode` ON+OFF in Xref buffers
indents all the heading (aka "group") lines by 2 spaces.
A similar problem shows up in *Help* buffers (e.g. `C-h m`).

The patch serie below (also pushed to `scratch/outline-button-cover`)
tries to "fix" it in 2 ways:

- Try and make the implementation of the `insert` mode a bit more
  reliable by removing those "  " when necessary.
- Better interact with other packages which may depend on being told
  when those "  " are inserted/deleted.
- Add a new option for packages like Xref of Help whereby they can
  "prefill" the buffer with a particular chunk of text that will be used
  for those icon buttons.  This is controlled by the new
  `outline-button-cover-text` variable.

The patch series includes a patch for Xref to make use of the new
`outline-button-cover-text` mechanism.  A similar patch could/should be
added for Help, but I refrained from doing so for now, in part to make
it easier to test that the changes didn't break the `insert` mode.

Comments?  Objections?


        Stefan
[0001-lisp-outline.el-outline-minor-mode-cycle-filter-Don-.patch (text/x-diff, inline)]
From fa2af1ad8d9b953a64ba690a2ad5ad69d796cf6f Mon Sep 17 00:00:00 2001
From: Stefan Monnier <monnier <at> iro.umontreal.ca>
Date: Sun, 6 Jul 2025 12:08:51 -0400
Subject: [PATCH 1/6] lisp/outline.el (outline-minor-mode-cycle-filter): Don't
 quote lambda

---
 lisp/outline.el | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lisp/outline.el b/lisp/outline.el
index dc2b5b32685..f47e558aec4 100644
--- a/lisp/outline.el
+++ b/lisp/outline.el
@@ -202,10 +202,10 @@ outline-minor-mode-cycle-filter
 line's end.  This allows these keys to be bound to their usual commands,
 as determined by the major mode, elsewhere on the heading lines.
 This option is only in effect when `outline-minor-mode-cycle' is non-nil."
-  :type '(choice (const :tag "Everywhere" nil)
+  :type `(choice (const :tag "Everywhere" nil)
                  (const :tag "At line beginning" bolp)
                  (const :tag "Not at line beginning"
-                        (lambda () (not (bolp))))
+                        ,(lambda () (not (bolp))))
                  (const :tag "At line end" eolp)
                  (function :tag "Custom filter function"))
   :version "28.1")
-- 
2.47.2

[0002-outline.el-Remove-buttons-properly-including-the.patch (text/x-diff, inline)]
From 231199f0364dfbcbfcf21922d3fdd72113fca0b6 Mon Sep 17 00:00:00 2001
From: Stefan Monnier <monnier <at> iro.umontreal.ca>
Date: Sun, 6 Jul 2025 12:27:04 -0400
Subject: [PATCH 2/6] outline.el: Remove buttons properly, including the "  "

In Xref, enabling+disabling `outline-minor-mode` left two spaces
in front of every heading (a.k.a "group") line.

* lisp/outline.el (outline--remove-buttons): New function.
(outline-minor-mode, outline--fix-buttons-after-change): Use it.
(outline-after-change-functions): Improve docstring.
---
 lisp/outline.el | 21 ++++++++++++++++++---
 1 file changed, 18 insertions(+), 3 deletions(-)

diff --git a/lisp/outline.el b/lisp/outline.el
index f47e558aec4..0641f3b5e7f 100644
--- a/lisp/outline.el
+++ b/lisp/outline.el
@@ -624,7 +624,7 @@ outline-minor-mode
       (font-lock-flush)
       (remove-overlays nil nil 'outline-highlight t))
     (when outline-minor-mode-use-buttons
-      (remove-overlays nil nil 'outline-button t)
+      (outline--remove-buttons (point-min) (point-max))
       (when (and (eq outline-minor-mode-use-buttons 'in-margins)
                  (< 0 (if outline--use-rtl right-margin-width
                         left-margin-width)))
@@ -2030,16 +2030,31 @@ outline--fix-up-all-buttons
      (or from (point-min)) (or to (point-max)))))
 
 (defvar outline-after-change-functions nil
-  "List of functions to call after each text change in outline-mode.")
+  "Hook run before updating buttons in a region in outline-mode.
+Called with three arguments (BEG END DUMMY).  Don't use DUMMY.")
 
 (defun outline--fix-buttons-after-change (beg end len)
   (run-hook-with-args 'outline-after-change-functions beg end len)
   ;; Handle whole lines
   (save-excursion (goto-char beg) (setq beg (pos-bol)))
   (save-excursion (goto-char end) (setq end (pos-eol)))
-  (remove-overlays beg end 'outline-button t)
+  (when (eq outline-minor-mode-use-buttons 'insert)
+    ;; `outline--remove-buttons' may change the buffer's text.
+    (setq end (copy-marker end t)))
+  (outline--remove-buttons beg end)
   (save-match-data (outline--fix-up-all-buttons beg end)))
 
+(defun outline--remove-buttons (beg end)
+  (if (not (eq outline-minor-mode-use-buttons 'insert))
+      (remove-overlays beg end 'outline-button t)
+    (save-excursion
+      (dolist (ol (overlays-in beg end))
+        (when (overlay-get ol 'outline-button)
+          (goto-char (overlay-start ol))
+          (let ((inhibit-read-only t))
+            (when (looking-at "  ") (delete-char 2)))
+          (delete-overlay ol))))))
+
 
 (defvar-keymap outline-navigation-repeat-map
   :repeat t
-- 
2.47.2

[0003-outline.el-Use-jit-lock-to-maintain-the-buttons.patch (text/x-diff, inline)]
From 3942c084bf269b245d8d56924fcc777a7765ffd9 Mon Sep 17 00:00:00 2001
From: Stefan Monnier <monnier <at> iro.umontreal.ca>
Date: Sun, 6 Jul 2025 13:13:14 -0400
Subject: [PATCH 3/6] outline.el: Use jit-lock to maintain the buttons

* lisp/outline.el (outline-minor-mode): Use jit-lock instead of
`after-change-functions`.
(outline-flag-region): Run `outline--fix-buttons` since jit-lock
won't be triggered.
(outline--fix-up-all-buttons): Make the args mandatory.
(outline--fix-buttons): Rename from `outline--fix-buttons-after-change`.
Fix the END calculation.  Return the affected bounds, for jit-lock.
---
 lisp/outline.el | 42 +++++++++++++++++++++---------------------
 1 file changed, 21 insertions(+), 21 deletions(-)

diff --git a/lisp/outline.el b/lisp/outline.el
index 0641f3b5e7f..807b6c8c926 100644
--- a/lisp/outline.el
+++ b/lisp/outline.el
@@ -559,8 +559,7 @@ outline-minor-mode
   (if outline-minor-mode
       (progn
         (when outline-minor-mode-use-buttons
-          (add-hook 'after-change-functions
-                    #'outline--fix-buttons-after-change nil t)
+          (jit-lock-register #'outline--fix-buttons)
           (when (eq (current-bidi-paragraph-direction) 'right-to-left)
             (setq-local outline--use-rtl t))
           (setq-local outline--button-icons (outline--create-button-icons))
@@ -596,7 +595,6 @@ outline-minor-mode
               (outline-minor-mode-highlight-buffer)
               (add-hook 'revert-buffer-restore-functions
                         #'outline-revert-buffer-rehighlight nil t))))
-        (outline--fix-up-all-buttons)
 	;; Turn off this mode if we change major modes.
 	(add-hook 'change-major-mode-hook
 		  (lambda () (outline-minor-mode -1))
@@ -607,8 +605,7 @@ outline-minor-mode
 	;; Cause use of ellipses for invisible text.
 	(add-to-invisibility-spec '(outline . t))
 	(outline-apply-default-state))
-    (remove-hook 'after-change-functions
-                 #'outline--fix-buttons-after-change t)
+    (jit-lock-unregister #'outline--fix-buttons)
     (remove-hook 'revert-buffer-restore-functions
                  #'outline-revert-buffer-restore-visibility t)
     (remove-hook 'revert-buffer-restore-functions
@@ -1040,7 +1037,9 @@ outline-flag-region
       (overlay-put o 'isearch-open-invisible
 		   (or outline-isearch-open-invisible-function
 		       #'outline-isearch-open-invisible))))
-  (outline--fix-up-all-buttons from to)
+  ;; Jit-lock won't be triggered because we only touched overlays, so we have
+  ;; to update "by hand".
+  (outline--fix-buttons from to)
   (run-hooks 'outline-view-change-hook))
 
 (defun outline-reveal-toggle-invisible (o hidep)
@@ -2013,12 +2012,8 @@ outline--insert-button
            (overlay-put o 'before-string icon)
            (overlay-put o 'keymap outline-overlay-button-map)))))))
 
-(defun outline--fix-up-all-buttons (&optional from to)
+(defun outline--fix-up-all-buttons (from to)
   (when outline-minor-mode-use-buttons
-    (when from
-      (save-excursion
-        (goto-char from)
-        (setq from (pos-bol))))
     (outline-map-region
      (lambda ()
        (let ((close-p (save-excursion
@@ -2027,22 +2022,27 @@ outline--fix-up-all-buttons
                                                   'outline))
                                   (overlays-at (point))))))
          (outline--insert-button (if close-p 'close 'open))))
-     (or from (point-min)) (or to (point-max)))))
+     from to)))
 
 (defvar outline-after-change-functions nil
   "Hook run before updating buttons in a region in outline-mode.
 Called with three arguments (BEG END DUMMY).  Don't use DUMMY.")
 
-(defun outline--fix-buttons-after-change (beg end len)
-  (run-hook-with-args 'outline-after-change-functions beg end len)
+(defun outline--fix-buttons (&optional beg end)
+  (run-hook-with-args 'outline-after-change-functions beg end 0)
   ;; Handle whole lines
-  (save-excursion (goto-char beg) (setq beg (pos-bol)))
-  (save-excursion (goto-char end) (setq end (pos-eol)))
-  (when (eq outline-minor-mode-use-buttons 'insert)
-    ;; `outline--remove-buttons' may change the buffer's text.
-    (setq end (copy-marker end t)))
-  (outline--remove-buttons beg end)
-  (save-match-data (outline--fix-up-all-buttons beg end)))
+  (save-excursion
+    (setq beg (if (null beg) (point-min) (goto-char beg) (pos-bol)))
+    ;; Include a final newline in the region, otherwise
+    ;; `outline-search-text-property' may consider a heading to be outside
+    ;; of the bounds.
+    (setq end (if (null end) (point-max) (goto-char end) (pos-bol 2)))
+    (when (eq outline-minor-mode-use-buttons 'insert)
+      ;; `outline--remove-buttons' may change the buffer's text.
+      (setq end (copy-marker end t)))
+    (outline--remove-buttons beg end)
+    (save-match-data (outline--fix-up-all-buttons beg end))
+    `(jit-lock-bounds ,beg . ,end)))
 
 (defun outline--remove-buttons (beg end)
   (if (not (eq outline-minor-mode-use-buttons 'insert))
-- 
2.47.2

[0004-outline.el-Don-t-silence-buffer-text-modification.patch (text/x-diff, inline)]
From 3719972b6185c6113983a399218380bcb10ed21c Mon Sep 17 00:00:00 2001
From: Stefan Monnier <monnier <at> iro.umontreal.ca>
Date: Sun, 6 Jul 2025 13:23:50 -0400
Subject: [PATCH 4/6] outline.el: Don't silence buffer text modification

It's important not to hide buffer text modifications from
`after/after-change-functions` since it can break other packages'
assumptions.

* lisp/outline.el (outline--insert-button): Don't silence
buffer text modification.
(outline--fix-up-all-buttons): Save buffer's modified state instead.
---
 lisp/outline.el | 80 ++++++++++++++++++++++++++-----------------------
 1 file changed, 43 insertions(+), 37 deletions(-)

diff --git a/lisp/outline.el b/lisp/outline.el
index 807b6c8c926..392c60a4604 100644
--- a/lisp/outline.el
+++ b/lisp/outline.el
@@ -1983,46 +1983,52 @@ outline--create-button-icons
             (if outline--use-rtl 'outline-close-rtl 'outline-close))))))
 
 (defun outline--insert-button (type)
-  (with-silent-modifications
-    (save-excursion
-      (forward-line 0)
-      (let ((icon (nth (if (eq type 'close) 1 0) outline--button-icons))
-            (o (seq-find (lambda (o) (overlay-get o 'outline-button))
-                         (overlays-at (point)))))
-        (unless o
-          (when (eq outline-minor-mode-use-buttons 'insert)
-            (let ((inhibit-read-only t))
-              (insert (apply #'propertize "  " (text-properties-at (point))))
-              (forward-line 0)))
-          (setq o (make-overlay (point) (1+ (point))))
-          (overlay-put o 'outline-button t)
-          (overlay-put o 'evaporate t))
-        (pcase outline-minor-mode-use-buttons
-          ('insert
-           (overlay-put o 'display (or (plist-get icon 'image)
-                                       (plist-get icon 'string)))
-           (overlay-put o 'face (plist-get icon 'face))
-           (overlay-put o 'follow-link 'mouse-face)
-           (overlay-put o 'mouse-face 'highlight)
-           (overlay-put o 'keymap outline-inserted-button-map))
-          ('in-margins
-           (overlay-put o 'before-string icon)
-           (overlay-put o 'keymap outline-overlay-button-map))
-          (_
-           (overlay-put o 'before-string icon)
-           (overlay-put o 'keymap outline-overlay-button-map)))))))
+  (save-excursion
+    (forward-line 0)
+    (let ((icon (nth (if (eq type 'close) 1 0) outline--button-icons))
+          (o (seq-find (lambda (o) (overlay-get o 'outline-button))
+                       (overlays-at (point)))))
+      (unless o
+        (when (eq outline-minor-mode-use-buttons 'insert)
+          (let ((inhibit-read-only t))
+            (insert (apply #'propertize "  " (text-properties-at (point))))
+            (forward-line 0)))
+        (setq o (make-overlay (point) (1+ (point))))
+        (overlay-put o 'outline-button t)
+        (overlay-put o 'evaporate t))
+      (pcase outline-minor-mode-use-buttons
+        ('insert
+         (overlay-put o 'display (or (plist-get icon 'image)
+                                     (plist-get icon 'string)))
+         (overlay-put o 'face (plist-get icon 'face))
+         (overlay-put o 'follow-link 'mouse-face)
+         (overlay-put o 'mouse-face 'highlight)
+         (overlay-put o 'keymap outline-inserted-button-map))
+        ('in-margins
+         (overlay-put o 'before-string icon)
+         (overlay-put o 'keymap outline-overlay-button-map))
+        (_
+         (overlay-put o 'before-string icon)
+         (overlay-put o 'keymap outline-overlay-button-map))))))
 
 (defun outline--fix-up-all-buttons (from to)
   (when outline-minor-mode-use-buttons
-    (outline-map-region
-     (lambda ()
-       (let ((close-p (save-excursion
-                        (outline-end-of-heading)
-                        (seq-some (lambda (o) (eq (overlay-get o 'invisible)
-                                                  'outline))
-                                  (overlays-at (point))))))
-         (outline--insert-button (if close-p 'close 'open))))
-     from to)))
+    ;; If `outline-minor-mode-use-buttons' is `insert',
+    ;; `outline--insert-button' can modify the buffer's text.  We shouldn't
+    ;; use `with-silent-modifications' around changes to the buffer's text,
+    ;; but we still don't want to mark the buffer as modified whenever
+    ;; we expand/collapse an element.
+    (let ((modified (buffer-modified-p)))
+      (outline-map-region
+       (lambda ()
+         (let ((close-p (save-excursion
+                          (outline-end-of-heading)
+                          (seq-some (lambda (o)
+                                      (eq (overlay-get o 'invisible) 'outline))
+                                    (overlays-at (point))))))
+           (outline--insert-button (if close-p 'close 'open))))
+       from to)
+      (restore-buffer-modified-p modified))))
 
 (defvar outline-after-change-functions nil
   "Hook run before updating buttons in a region in outline-mode.
-- 
2.47.2

[0005-outline.el-Add-outline-button-cover-text.patch (text/x-diff, inline)]
From da92b5b7cbd26b34f94a8f141d20d403c1a50bd5 Mon Sep 17 00:00:00 2001
From: Stefan Monnier <monnier <at> iro.umontreal.ca>
Date: Sun, 6 Jul 2025 12:04:42 -0400
Subject: [PATCH 5/6] outline.el: Add `outline-button-cover-text`

* lisp/outline.el (outline-minor-mode-use-buttons): De-advertize `insert`.
(outline-button-cover-text): New variable.
(outline--button-icons): Adjust var docstring.
(outline-minor-mode): Flush the button icons memoization table.
(outline--button-icons): Fill the memoization var lazily.
Rename function from `outline--create-button-icons`.  Add 2 args.
Use the `location` arg to determine what to return instead of relying
on `outline-minor-mode-use-buttons`.
(outline--insert-button): Adjust calls accordingly.
---
 lisp/outline.el | 114 +++++++++++++++++++++++++++---------------------
 1 file changed, 64 insertions(+), 50 deletions(-)

diff --git a/lisp/outline.el b/lisp/outline.el
index 392c60a4604..73bbe8cf393 100644
--- a/lisp/outline.el
+++ b/lisp/outline.el
@@ -326,8 +326,8 @@ outline-minor-mode-use-buttons
 When the value is `insert', additional placeholders for buttons are
 inserted to the buffer, so buttons are not only clickable,
 but also typing `RET' on them can hide and show the body.
-Using the value `insert' is not recommended in editable
-buffers because it modifies them.
+The value `insert' is for internal use only in some non-editable
+buffers, because it modifies them.
 When the value is `in-margins', then clickable buttons are
 displayed in the margins before the headings.
 When the value is t, clickable buttons are displayed
@@ -341,8 +341,11 @@ outline-minor-mode-use-buttons
   :safe #'symbolp
   :version "29.1")
 
+(defvar-local outline-button-cover-text nil
+  "If non-nil, buttons can&should cover/hide the first char of the header.")
+
 (defvar-local outline--button-icons nil
-  "A list of pre-computed button icons.")
+  "A memoization table for button icons.")
 
 (defvar-local outline--use-rtl nil
   "Non-nil when direction of clickable buttons is right-to-left.")
@@ -556,13 +559,13 @@ outline-minor-mode
             :parent outline-minor-mode-cycle-map
             "<menu-bar>" outline-minor-mode-menu-bar-map
             (key-description outline-minor-mode-prefix) outline-mode-prefix-map)
+  (kill-local-variable outline--button-icons)
   (if outline-minor-mode
       (progn
         (when outline-minor-mode-use-buttons
           (jit-lock-register #'outline--fix-buttons)
           (when (eq (current-bidi-paragraph-direction) 'right-to-left)
             (setq-local outline--use-rtl t))
-          (setq-local outline--button-icons (outline--create-button-icons))
           (when (and (eq outline-minor-mode-use-buttons 'in-margins)
                      (> 1 (if outline--use-rtl right-margin-width
                             left-margin-width)))
@@ -1946,47 +1949,53 @@ outline-inserted-button-map
   :parent (make-composed-keymap outline-button-icon-map
                                 outline-overlay-button-map))
 
-(defun outline--create-button-icons ()
-  (pcase outline-minor-mode-use-buttons
-    ('in-margins
-     (mapcar
-      (lambda (icon-name)
-        (let* ((icon (icon-elements icon-name))
-               (face   (plist-get icon 'face))
-               (string (plist-get icon 'string))
-               (image  (plist-get icon 'image))
-               (display `((margin ,(if outline--use-rtl
-                                       'right-margin 'left-margin))
-                          ,(or image (if face (propertize
-                                               string 'face face)
-                                       string))))
-               (space (propertize " " 'display display)))
-          (if (and image face) (propertize space 'face face) space)))
-      (list 'outline-open-in-margins
-            (if outline--use-rtl
-                'outline-close-rtl-in-margins
-              'outline-close-in-margins))))
-    ('insert
-     (mapcar
-      (lambda (icon-name)
-        (icon-elements icon-name))
-      (list 'outline-open
-            (if outline--use-rtl 'outline-close-rtl 'outline-close))))
-    (_
-     (mapcar
-      (lambda (icon-name)
-        (propertize (icon-string icon-name)
-                    'mouse-face 'default
-                    'follow-link 'mouse-face
-                    'keymap outline-button-icon-map))
-      (list 'outline-open
-            (if outline--use-rtl 'outline-close-rtl 'outline-close))))))
+(defun outline--button-icons (type location)
+  (unless outline--button-icons
+    (setq outline--button-icons (make-hash-table)))
+  (let ((buttons
+         (with-memoization (gethash location outline--button-icons)
+           (pcase-exhaustive location
+             ('in-margins
+              (mapcar
+               (lambda (icon-name)
+                 (let* ((icon (icon-elements icon-name))
+                        (face   (plist-get icon 'face))
+                        (string (plist-get icon 'string))
+                        (image  (plist-get icon 'image))
+                        (display `((margin ,(if outline--use-rtl
+                                                'right-margin 'left-margin))
+                                   ,(or image (if face (propertize
+                                                        string 'face face)
+                                                string))))
+                        (space (propertize " " 'display display)))
+                   (if (and image face) (propertize space 'face face) space)))
+               (list 'outline-open-in-margins
+                     (if outline--use-rtl
+                         'outline-close-rtl-in-margins
+                       'outline-close-in-margins))))
+             ('display
+              (mapcar
+               (lambda (icon-name)
+                 (icon-elements icon-name))
+               (list 'outline-open
+                     (if outline--use-rtl 'outline-close-rtl 'outline-close))))
+             ('before-string
+              (mapcar
+               (lambda (icon-name)
+                 (propertize (icon-string icon-name)
+                             'mouse-face 'default
+                             'follow-link 'mouse-face
+                             'keymap outline-button-icon-map))
+               (list 'outline-open
+                     (if outline--use-rtl 'outline-close-rtl 'outline-close))))))))
+    (nth (if (eq type 'close) 1 0) buttons)))
 
 (defun outline--insert-button (type)
   (save-excursion
     (forward-line 0)
-    (let ((icon (nth (if (eq type 'close) 1 0) outline--button-icons))
-          (o (seq-find (lambda (o) (overlay-get o 'outline-button))
+    ;; `icon' is either plist or a string, depending on
+    ;; the `outline-minor-mode-use-buttons' settings
+    (let ((o (seq-find (lambda (o) (overlay-get o 'outline-button))
                        (overlays-at (point)))))
       (unless o
         (when (eq outline-minor-mode-use-buttons 'insert)
@@ -1997,18 +2006,23 @@ outline--insert-button
         (overlay-put o 'outline-button t)
         (overlay-put o 'evaporate t))
       (pcase outline-minor-mode-use-buttons
-        ('insert
-         (overlay-put o 'display (or (plist-get icon 'image)
-                                     (plist-get icon 'string)))
-         (overlay-put o 'face (plist-get icon 'face))
-         (overlay-put o 'follow-link 'mouse-face)
-         (overlay-put o 'mouse-face 'highlight)
-         (overlay-put o 'keymap outline-inserted-button-map))
         ('in-margins
-         (overlay-put o 'before-string icon)
+         (when outline-button-cover-text
+           (overlay-put o 'invisible t))
+         (overlay-put o 'before-string
+                      (outline--button-icons type 'in-margins))
          (overlay-put o 'keymap outline-overlay-button-map))
+        ((or 'insert (guard outline-button-cover-text))
+         (let ((icon (outline--button-icons type 'display)))
+           (overlay-put o 'display (or (plist-get icon 'image)
+                                       (plist-get icon 'string)))
+           (overlay-put o 'face (plist-get icon 'face))
+           (overlay-put o 'follow-link 'mouse-face)
+           (overlay-put o 'mouse-face 'highlight)
+           (overlay-put o 'keymap outline-inserted-button-map)))
         (_
-         (overlay-put o 'before-string icon)
+         (overlay-put o 'before-string
+                      (outline--button-icons type ''before-string))
          (overlay-put o 'keymap outline-overlay-button-map))))))
 
 (defun outline--fix-up-all-buttons (from to)
-- 
2.47.2

[0006-xref.el-Use-outline-button-cover-text.patch (text/x-diff, inline)]
From 619ae5a143a0c99d0e1ca953d79481c4ec5618d7 Mon Sep 17 00:00:00 2001
From: Stefan Monnier <monnier <at> iro.umontreal.ca>
Date: Sun, 6 Jul 2025 12:06:30 -0400
Subject: [PATCH 6/6] xref.el: Use `outline-button-cover-text`

* lisp/progmodes/xref.el (xref--xref-buffer-mode):
Use `outline-button-cover-text`.  Unconditionally enable
`outline-minor-mode`.
(xref--insert-xrefs): Add a marker char for groups, for outline's benefit.
---
 lisp/progmodes/xref.el | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/lisp/progmodes/xref.el b/lisp/progmodes/xref.el
index ddf10b6e4ee..a015dc82159 100644
--- a/lisp/progmodes/xref.el
+++ b/lisp/progmodes/xref.el
@@ -1017,12 +1017,16 @@ xref--xref-buffer-mode
               #'xref--add-log-current-defun)
   (setq-local revert-buffer-function #'xref--revert-buffer)
   (setq-local outline-minor-mode-cycle t)
-  (setq-local outline-minor-mode-use-buttons 'insert)
+  (setq-local outline-minor-mode-use-buttons
+              (or (bound-and-true-p outline-minor-mode-use-buttons) t))
   (setq-local outline-search-function
               (lambda (&optional bound move backward looking-at)
                 (outline-search-text-property
                  'xref-group nil bound move backward looking-at)))
+  (setq-local outline-regexp "•")
+  (setq-local outline-button-cover-text t)
   (setq-local outline-level (lambda () 1))
+  (outline-minor-mode 1)
   (add-hook 'revert-buffer-restore-functions
             #'xref-revert-buffer-restore-point nil t))
 
@@ -1163,7 +1167,7 @@ xref--insert-xrefs
            with prev-line = nil
            do
            (xref--insert-propertized '(face xref-file-header xref-group t)
-                                     group "\n")
+                                    "•" group "\n")
            (dolist (xref xrefs)
              (cl-incf xref-num-matches-found)
              (pcase-let (((cl-struct xref-item summary location) xref))
-- 
2.47.2


Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#78968; Package emacs. (Mon, 07 Jul 2025 06:46:05 GMT) Full text and rfc822 format available.

Message #8 received at 78968 <at> debbugs.gnu.org (full text, mbox):

From: Juri Linkov <juri <at> linkov.net>
To: Stefan Monnier <monnier <at> iro.umontreal.ca>
Cc: 78968 <at> debbugs.gnu.org
Subject: Re: bug#78968: Outline: Fix and deprecate `insert` buttons
Date: Mon, 07 Jul 2025 09:41:19 +0300
> The patch series includes a patch for Xref to make use of the new
> `outline-button-cover-text` mechanism.  A similar patch could/should be
> added for Help, but I refrained from doing so for now, in part to make
> it easier to test that the changes didn't break the `insert` mode.
>
> Comments?  Objections?

I'm testing the branch `scratch/outline-button-cover`.




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#78968; Package emacs. (Mon, 07 Jul 2025 17:54:02 GMT) Full text and rfc822 format available.

Message #11 received at 78968 <at> debbugs.gnu.org (full text, mbox):

From: Juri Linkov <juri <at> linkov.net>
To: Stefan Monnier <monnier <at> iro.umontreal.ca>
Cc: 78968 <at> debbugs.gnu.org
Subject: Re: bug#78968: Outline: Fix and deprecate `insert` buttons
Date: Mon, 07 Jul 2025 20:52:09 +0300
>> The patch series includes a patch for Xref to make use of the new
>> `outline-button-cover-text` mechanism.  A similar patch could/should be
>> added for Help, but I refrained from doing so for now, in part to make
>> it easier to test that the changes didn't break the `insert` mode.
>>
>> Comments?  Objections?
>
> I'm testing the branch `scratch/outline-button-cover`.

There are some strange problems:

1. C-h C-t
2. M-x outline-minor-mode (enable)
3. M-x outline-minor-mode (disable)

Debugger entered--Lisp error: (wrong-type-argument symbolp #<hash-table eql 0/0 0x12dce1cbcb79 ...>)
  outline-minor-mode(toggle)
  funcall-interactively(outline-minor-mode toggle)
  command-execute(outline-minor-mode record)
  execute-extended-command(nil "outline-minor-mode" nil)
  funcall-interactively(execute-extended-command nil "outline-minor-mode" nil)
  command-execute(execute-extended-command)

Also:

1. C-h C-t
2. M-x outline-minor-mode
3. S-<iso-lefttab> (outline-cycle-buffer)

Debugger entered--Lisp error: (error "No clause matching ‘'before-string’")
  error("No clause matching `%S'" 'before-string)
  outline--button-icons(close 'before-string)
  outline--insert-button(close)
  #<subr F616e6f6e796d6f75732d6c616d626461_anonymous_lambda_49>()
  outline-map-region(#<subr F616e6f6e796d6f75732d6c616d626461_anonymous_lambda_49> 1737 75449)
  outline--fix-up-all-buttons(1737 75449)
  outline--fix-buttons(1737 75448)
  outline-flag-region(1737 75448 t)
  outline-hide-sublevels(1)
  outline-cycle-buffer(nil)
  funcall-interactively(outline-cycle-buffer nil)
  command-execute(outline-cycle-buffer)




This bug report was last modified today.

Previous Next


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