Subject: Re: bug#79275: 30.2.50; overlay line-prefix display property fighting with text display property

From: Eli Zaretskii <[email-masked]>
Date: Wed, 20 Aug 2025 16:35:20 +0300

> From: JD Smith <[email-masked]>
> Date: Tue, 19 Aug 2025 21:39:38 -0400
> 
> An overlay which applies a line-prefix (e.g. to set the fringe) across several lines conflicts with underlying text which has a replacing display property set.  Evaluate the following in the *scratch* buffer, with at least 3 blank lines at the top of the buffer.
> 
> (progn
>   (delete-all-overlays)
>   (let ((ov (make-overlay 1 4)))
>     (overlay-put ov 'line-prefix (propertize "SHOULDNOTSEETHIS" 'display
>                                              '(left-fringe right-triangle success))))
>   (put-text-property 2 3 'display ">testing fringe display"))
> 
> The result is very strange: the SHOULDNOTSEETHIS prefix string appears (sans fringe display), but then vanishes on the next redisplay.  Some (well-balanced) fight between overlay line-prefix display and normal display properties seems to be occurring.

Thanks.

One of the redisplay optimizations we use couldn't cope with this
tricky situation (a line-prefix immediately followed by a display
string at the beginning of a line), and needs to be disabled in this
case.

Does the patch below give good results?

diff --git a/src/xdisp.c b/src/xdisp.c
index 2691296..6026038 100644
--- a/src/xdisp.c
+++ b/src/xdisp.c
@@ -7261,6 +7261,8 @@ push_it (struct it *it, struct text_pos *position)
   p->from_disp_prop_p = it->from_disp_prop_p;
   ++it->sp;
 
+  it->string_from_prefix_prop_p = false;
+
   /* Save the state of the bidi iterator as well. */
   if (it->bidi_p)
     bidi_push_it (&it->bidi_it);
@@ -22657,8 +22659,23 @@ #define GIVE_UP(X) return 0
       /* Give up if the row starts with a display property that draws
 	 on the fringes, since that could prevent correct display of
 	 line-prefix and wrap-prefix.  */
-      if (it.sp > 1
-	  && it.method == GET_FROM_IMAGE && it.image_id == -1)
+      if ((it.sp > 1
+	   && it.method == GET_FROM_IMAGE && it.image_id == -1)
+	  /* Give up if there's a line/wrap-prefix property on buffer
+             text, and the row begins with a display or overlay string.
+             This is because in that case the iterator state produced by
+             init_to_row_end is already set to the display/overlay
+             string, and thus cannot be used to display the prefix
+             before the display/overlay string.  */
+	  || (it.sp == 1
+	      && it.method == GET_FROM_STRING
+	      && !it.string_from_prefix_prop_p
+	      && (!NILP (Fget_char_property (make_fixnum (IT_CHARPOS (it)),
+					     Qline_prefix,
+					     it.w->contents))
+		  || !NILP (Fget_char_property (make_fixnum (IT_CHARPOS (it)),
+						Qwrap_prefix,
+						it.w->contents)))))
 	GIVE_UP (26);
       start_pos = it.current.pos;