Subject: bug#59141: 28.1.90; Face :extend when all the line but trailing \n is invisible

From: Kévin Le Gouguec <[email-masked]>
Date: Sat, 12 Nov 2022 12:18:49 +0100

Eli Zaretskii <[email-masked]> writes:

> You are basically suggesting to hide the entire buffer text and
> instead display something else via display properties.  That's not
> what outline modes do.
>
> The solution to this problem is simple: don't use the :extend
> attribute for these faces.  That's all.  This attribute is not
> intended for what you want to achieve here.  A whole bunch of problems
> automatically gets resolved if you don't use :extend.

Can't speak to what :extend is intended for, but I think it's worth
noting that magit-section successfully combines overlays with :extend to
do exactly what outline.el does (a hierarchy of collapsable buffer
sections), without the problem under discussion.

If someone came up with a patch to allow outline.el to delimit and hide
sections using a logic closer to magit-section's[1] (via an opt-in
variable, to be set e.g. by major modes, so that users happy with the
status quo are not affected), would that patch be given any
consideration?

Asking because OT1H, it seems to me that this kind of change would be on
par with Juri's proposed outline-search-function in bug#53981, i.e. an
opt-in change to a core part of outline.el's design; OTOH the above
sounds quite final, so I don't want to waste anyone's time.

[1] Here are the relevant parts AFAIU, based on a cursory exploration.

Obviously nothing can be ported verbatim to outline.el: e.g. the eieio
idioms are not suited, neither are the parts that manipulate buffer
content directly (like magit-diff-wash-hunk deleting then re-inserting
the hunk heading).

Still, I'm hopeful we could teach outline.el the logic of where headings
end and content starts, and how to set overlays to hide the latter.

* magit-section.el:

(defclass magit-section ()
  ([…]
   (start    :initform nil :initarg :start)
   (content  :initform nil)
   (end      :initform nil)
   […]))

(defun magit-section-hide (section)
  "Hide the body of the current section."
  (interactive (list (magit-current-section)))
  (if (eq section magit-root-section)
      (user-error "Cannot hide root section")
    (oset section hidden t)
    (when-let ((beg (oref section content)))
      (let ((end (oref section end)))
        (when (< beg (point) end)
          (goto-char (oref section start)))
        (remove-overlays beg end 'invisible t)
        (let ((o (make-overlay beg end)))
          (overlay-put o 'evaporate t)
          (overlay-put o 'invisible t))))
    […]))

(defmacro magit-insert-section (&rest args)
  "Insert a section at point.
[…]"
  […]
                         :start (point-marker)
  […])

* magit-diff.el:

(defun magit-diff-wash-hunk ()
  (when (looking-at "^@\\{2,\\} \\(.+?\\) @\\{2,\\}\\(?: \\(.*\\)\\)?")
    (let* ((heading  (match-string 0))
           (ranges   (mapcar
                      (lambda (str)
                        (let ((range
                               (mapcar #'string-to-number
                                       (split-string (substring str 1) ","))))
                          ;; A single line is +1 rather than +1,1.
                          (if (length= range 1)
                              (nconc range (list 1))
                            range)))
                      (split-string (match-string 1))))
           (about    (match-string 2))
           (combined (length= ranges 3))
           (value    (cons about ranges)))
      (magit-delete-line)
      (magit-insert-section section (hunk value)
        (insert (propertize (concat heading "\n")
                            'font-lock-face 'magit-diff-hunk-heading))
        (magit-insert-heading)
        (while (not (or (eobp) (looking-at "^[^-+\s\\]")))
          (forward-line))
        (oset section end (point))
        (oset section washer #'magit-diff-paint-hunk)
        (oset section combined combined)
        […]))
    t))

JS-less, text/plain references:

https://raw.githubusercontent.com/magit/magit/master/lisp/magit-section.el
https://raw.githubusercontent.com/magit/magit/master/lisp/magit-diff.el