Subject: Re: bug#76018: 31.0.50; wrap-prefix properties from visual-wrap-prefix-mode proliferate

From: Jim Porter <[email-masked]>
Date: Sat, 31 May 2025 11:06:24 -0700

On 5/31/2025 10:48 AM, Jim Porter wrote:
> It *should* be supported. However, it turns out that 
> 'add-display-text-property' doesn't work right with '(margin nil)'. I 
> think the first step here should be to fix all the bugs I've found with 
> 'add-display-text-property', and then I'll work on finishing up the rest.

Ok, that was simpler than I thought. Eli, what do you think about adding 
this patch to the Emacs 30 branch? It just fixes some bugs in 
'add-display-text-property', as described in the commit message.

From 2204db497a32de1263808c4e547945399d03235a Mon Sep 17 00:00:00 2001
From: Jim Porter <[email-masked]>
Date: Wed, 28 May 2025 09:55:58 -0700
Subject: [PATCH 1/3] Don't delete in-place when replacing a display property

When calling 'add-display-property' on a region of text that already
contains the specified display PROP, we delete the old property from the
list of values.  If this list came directly from the property value, we
need to be careful not to delete it in-place, since other regions of
text could be using the same list object.  Instead, if deleting a
property from a list, use 'remove'.

In addition, be more careful when working with a display property like
((margin ...) ...).  This is a single display specification, not a list
of display specs.

* lisp/emacs-lisp/subr-x.el (add-display-text-property): Don't delete
in-place for list values.  Handle (margin ...) display specification
type correctly.

* test/lisp/emacs-lisp/subr-x-tests.el
(subr-x-test-add-display-text-property): Update test.
---
 lisp/emacs-lisp/subr-x.el            | 20 +++++++++++----
 test/lisp/emacs-lisp/subr-x-tests.el | 37 ++++++++++++++++++++++------
 2 files changed, 44 insertions(+), 13 deletions(-)

diff --git a/lisp/emacs-lisp/subr-x.el b/lisp/emacs-lisp/subr-x.el
index eaa8119ead7..4d56c67be5b 100644
--- a/lisp/emacs-lisp/subr-x.el
+++ b/lisp/emacs-lisp/subr-x.el
@@ -438,22 +438,32 @@ add-display-text-property
           (put-text-property sub-start sub-end 'display (list prop value)
                              object)
         ;; We have old properties.
-        (let ((vector nil))
+        (let (type)
           ;; Make disp into a list.
           (setq disp
                 (cond
                  ((vectorp disp)
-                  (setq vector t)
+                  (setq type 'vector)
                   (seq-into disp 'list))
-                 ((not (consp (car disp)))
+                 ((or (not (consp (car-safe disp)))
+                      ;; If disp looks like ((margin ...) ...), that's
+                      ;; still a single display specification.
+                      (eq (caar disp) 'margin))
+                  (setq type 'scalar)
                   (list disp))
                  (t
+                  (setq type 'list)
                   disp)))
           ;; Remove any old instances.
           (when-let* ((old (assoc prop disp)))
-            (setq disp (delete old disp)))
+            ;; If the property value was a list, don't modify the
+            ;; original value in place; it could be used by other
+            ;; regions of text.
+            (setq disp (if (eq type 'list)
+                           (remove old disp)
+                         (delete old disp))))
           (setq disp (cons (list prop value) disp))
-          (when vector
+          (when (eq type 'vector)
             (setq disp (seq-into disp 'vector)))
           ;; Finally update the range.
           (put-text-property sub-start sub-end 'display disp object)))
diff --git a/test/lisp/emacs-lisp/subr-x-tests.el b/test/lisp/emacs-lisp/subr-x-tests.el
index f6675637fef..5ffbe64ae40 100644
--- a/test/lisp/emacs-lisp/subr-x-tests.el
+++ b/test/lisp/emacs-lisp/subr-x-tests.el
@@ -696,18 +696,39 @@ subr-x-test-add-display-text-property
     (insert "Foo bar zot gazonk")
     (add-display-text-property 4 8 'height 2.0)
     (add-display-text-property 2 12 'raise 0.5)
-    (should (equal (get-text-property 2 'display) '(raise 0.5)))
-    (should (equal (get-text-property 5 'display)
-                   '((raise 0.5) (height 2.0))))
-    (should (equal (get-text-property 9 'display) '(raise 0.5))))
+    (add-display-text-property 6 10 'height 1.0)
+    (should (equal-including-properties
+             (buffer-string)
+             #("Foo bar zot gazonk"
+               1 3 (display (raise 0.5))
+               3 5 (display ((raise 0.5) (height 2.0)))
+               5 9 (display ((height 1.0) (raise 0.5)))
+               9 11 (display (raise 0.5))))))
   (with-temp-buffer
     (insert "Foo bar zot gazonk")
     (put-text-property 4 8 'display [(height 2.0)])
     (add-display-text-property 2 12 'raise 0.5)
-    (should (equal (get-text-property 2 'display) '(raise 0.5)))
-    (should (equal (get-text-property 5 'display)
-                   [(raise 0.5) (height 2.0)]))
-    (should (equal (get-text-property 9 'display) '(raise 0.5))))
+    (add-display-text-property 6 10 'height 1.0)
+    (should (equal-including-properties
+             (buffer-string)
+             #("Foo bar zot gazonk"
+               1 3 (display (raise 0.5))
+               3 5 (display [(raise 0.5) (height 2.0)])
+               5 7 (display [(height 1.0) (raise 0.5)])
+               7 9 (display ((height 1.0) (raise 0.5)))
+               9 11 (display (raise 0.5))))))
+  (with-temp-buffer
+    (insert "Foo bar zot gazonk")
+    (add-display-text-property 4 8 '(margin nil) "Hi")
+    (add-display-text-property 2 12 'raise 0.5)
+    (add-display-text-property 6 10 '(margin nil) "Bye")
+    (should (equal-including-properties
+             (buffer-string)
+             #("Foo bar zot gazonk"
+               1 3 (display (raise 0.5))
+               3 5 (display ((raise 0.5) ((margin nil) "Hi")))
+               5 9 (display (((margin nil) "Bye") (raise 0.5)))
+               9 11 (display (raise 0.5))))))
   (with-temp-buffer
     (should (equal-including-properties
              (let ((str (copy-sequence "some useless string")))
-- 
2.25.1