GNU bug report logs - #79275
30.2.50; overlay line-prefix display property fighting with text display property

Previous Next

Package: emacs;

Reported by: JD Smith <jdtsmith <at> gmail.com>

Date: Wed, 20 Aug 2025 01:40:02 UTC

Severity: normal

Found in version 30.2.50

Done: Eli Zaretskii <eliz <at> gnu.org>

Full log


View this message in rfc822 format

From: Eli Zaretskii <eliz <at> gnu.org>
To: "J.D. Smith" <jdtsmith <at> gmail.com>
Cc: 79275 <at> debbugs.gnu.org
Subject: bug#79275: 30.2.50; overlay line-prefix display property fighting with text display property
Date: Thu, 21 Aug 2025 21:13:58 +0300
> From: "J.D. Smith" <jdtsmith <at> gmail.com>
> Cc: 79275 <at> debbugs.gnu.org
> Date: Wed, 20 Aug 2025 11:06:20 -0400
> 
> Eli Zaretskii <eliz <at> gnu.org> writes:
> 
> >> From: JD Smith <jdtsmith <at> gmail.com>
> >> 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?
> 
> Yes it does, thanks.  There still appears to be another corner case,
> however.  If the conflict occurs at the /beginning/ of the overlay:
> 
>   (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 1 2 'display ">testing fringe display")) ;; changed from 2->3 to 1->2
> 
> SHOULDNOTSEETHIS still appears (and remains).  That situation is what
> actually motivated the report.

Please try the patch below, I hope it fixes both of the situations you
described.

diff --git a/src/xdisp.c b/src/xdisp.c
index 2691296..b8088f6 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
+      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;
 
@@ -24710,15 +24727,29 @@ cursor_row_p (struct glyph_row *row)
 
 
 /* Push the property PROP so that it will be rendered at the current
-   position in IT.  Return true if PROP was successfully pushed, false
-   otherwise.  Called from handle_line_prefix to handle the
-   `line-prefix' and `wrap-prefix' properties.  */
+   position in IT.  FROM_BUFFER non-zero means the property was found on
+   buffer text, even though IT is set to iterate a string.
+   Return true if PROP was successfully pushed, false otherwise.
+   Called from handle_line_prefix to handle the `line-prefix' and
+   `wrap-prefix' properties.  */
 
 static bool
-push_prefix_prop (struct it *it, Lisp_Object prop)
+push_prefix_prop (struct it *it, Lisp_Object prop, int from_buffer)
 {
-  struct text_pos pos =
-    STRINGP (it->string) ? it->current.string_pos : it->current.pos;
+  struct text_pos pos;
+
+  if (STRINGP (it->string))
+    {
+      if (from_buffer)	/* a string, but prefix property from buffer */
+	pos = it->current.string_pos;
+      else		/* a string and prefix property from string */
+	pos.charpos = pos.bytepos = 0; /* we have yet to iterate that string */
+    }
+  else			/* a buffer and prefix property from buffer */
+    pos = it->current.pos;
+
+  bool phoney_display_string =
+    from_buffer && STRINGP (it->string) && it->string_from_display_prop_p;
 
   eassert (it->method == GET_FROM_BUFFER
 	   || it->method == GET_FROM_DISPLAY_VECTOR
@@ -24737,6 +24768,13 @@ push_prefix_prop (struct it *it, Lisp_Object prop)
      it->position not yet set when this function is called.  */
   push_it (it, &pos);
 
+  /* Reset this flag, since it is not relevant (comes from a display
+     string that follows iterator position).  If we don't do that, any
+     display properties on the prefix string will be ignored.  The call
+     to pop_it when we are done with the prefix will restore the flag.  */
+  if (phoney_display_string)
+    it->string_from_display_prop_p = false;
+
   if (STRINGP (prop))
     {
       if (SCHARS (prop) == 0)
@@ -24794,7 +24832,7 @@ push_prefix_prop (struct it *it, Lisp_Object prop)
 #endif /* HAVE_WINDOW_SYSTEM */
   else
     {
-      pop_it (it);		/* bogus display property, give up */
+      pop_it (it);		/* bogus prefix property, give up */
       return false;
     }
 
@@ -24806,11 +24844,14 @@ push_prefix_prop (struct it *it, Lisp_Object prop)
 static Lisp_Object
 get_it_property (struct it *it, Lisp_Object prop)
 {
-  Lisp_Object position, object = it->object;
+  Lisp_Object position, object;
 
-  if (STRINGP (object))
-    position = make_fixnum (IT_STRING_CHARPOS (*it));
-  else if (BUFFERP (object))
+  if (STRINGP (it->string))
+    {
+      position = make_fixnum (IT_STRING_CHARPOS (*it));
+      object = it->string;
+    }
+  else if (BUFFERP (it->object))
     {
       position = make_fixnum (IT_CHARPOS (*it));
       object = it->window;
@@ -24825,15 +24866,21 @@ get_it_property (struct it *it, Lisp_Object prop)
    current IT->OBJECT and the underlying buffer text.  */
 
 static Lisp_Object
-get_line_prefix_it_property (struct it *it, Lisp_Object prop)
+get_line_prefix_it_property (struct it *it, Lisp_Object prop,
+			     int *from_buffer)
 {
   Lisp_Object prefix = get_it_property (it, prop);
 
+  *from_buffer = false;
+
   /* If we are looking at a display or overlay string, check also the
      underlying buffer text.  */
-  if (NILP (prefix) && it->sp > 0 && STRINGP (it->object))
-    return Fget_char_property (make_fixnum (IT_CHARPOS (*it)), prop,
-			       it->w->contents);
+  if (NILP (prefix) && it->sp > 0 && STRINGP (it->string))
+    {
+      *from_buffer = true;
+      return Fget_char_property (make_fixnum (IT_CHARPOS (*it)), prop,
+				 it->w->contents);
+    }
   return prefix;
 }
 
@@ -24844,21 +24891,22 @@ handle_line_prefix (struct it *it)
 {
   Lisp_Object prefix;
   bool wrap_prop = false;
+  int from_buffer;
 
   if (it->continuation_lines_width > 0)
     {
-      prefix = get_line_prefix_it_property (it, Qwrap_prefix);
+      prefix = get_line_prefix_it_property (it, Qwrap_prefix, &from_buffer);
       if (NILP (prefix))
 	prefix = Vwrap_prefix;
       wrap_prop = true;
     }
   else
     {
-      prefix = get_line_prefix_it_property (it, Qline_prefix);
+      prefix = get_line_prefix_it_property (it, Qline_prefix, &from_buffer);
       if (NILP (prefix))
 	prefix = Vline_prefix;
     }
-  if (! NILP (prefix) && push_prefix_prop (it, prefix))
+  if (! NILP (prefix) && push_prefix_prop (it, prefix, from_buffer))
     {
       /* If the prefix is wider than the window, and we try to wrap
 	 it, it would acquire its own wrap prefix, and so on till the




This bug report was last modified 20 days ago.

Previous Next


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