Subject: bug#73600: 31.0.50; Visual wrap prefix mode and image display

From: Jim Porter <[email-masked]>
Date: Sat, 5 Oct 2024 17:28:02 -0700

On 10/5/2024 11:07 AM, Jim Porter wrote:
> Yes, see attached.

It would probably help if I attached the current version of my patch...
From aa679b0945ffb298a8556a60e163727acb7fe68f Mon Sep 17 00:00:00 2001
From: Jim Porter <[email-masked]>
Date: Thu, 3 Oct 2024 17:24:18 -0700
Subject: [PATCH] Don't add visual-wrap-prefix properties to multi-line display
 specs

This makes sure we don't interfere with other display specs, e.g. for
images displayed in 'image-mode' (bug#73600).

* lisp/visual-wrap.el (visual-wrap--safe-display-specs): New variable.
(visual-wrap--display-property-safe-p): New function.
(visual-wrap--apply-to-line): Bail out if we're in the middle of an
unsafe multi-line display spec.
---
 lisp/visual-wrap.el | 57 +++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 52 insertions(+), 5 deletions(-)

diff --git a/lisp/visual-wrap.el b/lisp/visual-wrap.el
index 76276c0f474..d798302d091 100644
--- a/lisp/visual-wrap.el
+++ b/lisp/visual-wrap.el
@@ -73,6 +73,46 @@ visual-wrap--face-extend-p
         (face-extend-p face nil t)
       (face-background face nil t)))))
 
+(defvar visual-wrap--safe-display-specs
+  '(height raise)
+  "A list of display specs that don't interfere with wrap prefixes.
+A \"safe\" display spec is one that won't interfere with the additional
+text properties that `visual-wrap-prefix-mode' uses.
+
+Specs that replace the text are unsafe, since they generally determine
+the range of text to replace via `eq'.  If `visual-wrap-prefix-mode'
+were to add text properties to some subset of this range, it would
+violate this assumption.")
+
+(defun visual-wrap--display-property-safe-p (position offset)
+  "Return non-nil if the display property at POSITION is \"safe\".
+A \"safe\" display property is one where all the display specs are
+members of `visual-wrap--safe-display-specs' (which see).
+
+OFFSET should be 1 or -1 and tells which direction to look to see if the
+display property has changed.  If it has changed, then the display
+property is always considered safe: since POSITION is at the
+beginning (or end) of that property, our additional text properties
+don't cause problems."
+  (let ((pos-limit (if (= offset 1) (point-max) (point-min)))
+        (display (get-char-property position 'display)))
+    (or (= position pos-limit)
+        (not display)
+        (not (eq (get-char-property (+ position offset) 'display) display))
+        ;; Iterate over all the display specs to check if they're safe.
+        ;; Assume any display property other than a vector or list
+        ;; (e.g. a string) is unsafe.
+        (when (or (vectorp display) (listp display))
+          ;; The display property could be a single display spec; if so,
+          ;; wrap it in a list to iterate over.
+          (unless (consp (car-safe display)) (setq display (list display)))
+          (not (catch 'unsafe
+                 (mapc (lambda (spec)
+                         (unless (memq (car-safe spec)
+                                       visual-wrap--safe-display-specs)
+                           (throw 'unsafe t)))
+                       display)))))))
+
 (defun visual-wrap--prefix-face (fcp _beg end)
   ;; If the fill-context-prefix already specifies a face, just use that.
   (cond ((get-text-property 0 'face fcp))
@@ -117,9 +157,16 @@ visual-wrap--apply-to-line
   "Apply visual-wrapping properties to the logical line starting at POSITION."
   (save-excursion
     (goto-char position)
-    (when-let ((first-line-prefix (fill-match-adaptive-prefix))
+    (when-let ((eol (pos-eol))
+               ;; Don't add wrapping properties if we're in the middle
+               ;; of an unsafe multi-line display spec.  (For example,
+               ;; this could be an image containing a newline-byte being
+               ;; displayed in `image-mode'; see bug#73600.)
+               ((visual-wrap--display-property-safe-p (point) -1))
+               ((visual-wrap--display-property-safe-p eol 1))
+               (first-line-prefix (fill-match-adaptive-prefix))
                (next-line-prefix (visual-wrap--content-prefix
-                                  first-line-prefix position)))
+                                  first-line-prefix (point))))
       (when (numberp next-line-prefix)
         ;; Set a minimum width for the prefix so it lines up correctly
         ;; with subsequent lines.  Make sure not to do this past the end
@@ -127,12 +174,12 @@ visual-wrap--apply-to-line
         ;; potentially return a prefix longer than the current line in
         ;; the buffer.)
         (add-display-text-property
-         position (min (+ position (length first-line-prefix))
-                       (line-end-position))
+         (point) (min (+ (point) (length first-line-prefix))
+                      eol)
          'min-width `((,next-line-prefix . width))))
       (setq next-line-prefix (visual-wrap--adjust-prefix next-line-prefix))
       (put-text-property
-       position (line-end-position) 'wrap-prefix
+       (point) eol 'wrap-prefix
        (if (numberp next-line-prefix)
            `(space :align-to (,next-line-prefix . width))
          next-line-prefix)))))
-- 
2.25.1