Subject: bug#37858: 27.0.50; Ensure a minimum width for `space` display prop

From: Stefan Monnier <[email-masked]>
Date: Mon, 21 Oct 2019 16:03:58 -0400

Package: Emacs
Version: 27.0.50

For text displayed in columns, alignment is generally obtained with
a `display` text-property of the form

    (space :align-to FOO)

This works great when the previous text ends before FOO, but when we
mis-calculate (or didn't calculate at all) and the previous text already
extends further than the desired alignment of the following text, such
space is reduced down to 0 pixels which is often not what we want.

Sometimes one can workaround this by placing 2 spaces in the buffer: one
with the :align-to and another fixed size space.  But it can be
cumbersome to do that and it leads to undesirable artifacts (e.g. the
cursor can be placed between the two space).

So, I'd like to extend our `space` specifications so as to be able to
specify a minimum width.  I came up with the patch below which lets you
write:

    (space :align-to FOO :min-width BAR)

which seems to work fine, but while trying to update the Elisp doc for
it I realized that maybe a better option is to extend the acceptable
forms for FOO so it can be of the form:

    (space :align-to (max FOO (+ BAR current-x)))

WDYT?

        Stefan

diff --git a/src/xdisp.c b/src/xdisp.c
index be1c209553..fd825d2dfe 100644
--- a/src/xdisp.c
+++ b/src/xdisp.c
@@ -29218,6 +29218,28 @@ append_stretch_glyph (struct it *it, Lisp_Object object,
 
 #endif	/* HAVE_WINDOW_SYSTEM */
 
+static int
+compute_relative_width (struct it *it, Lisp_Object prop)
+{
+  struct it it2;
+  unsigned char *p = BYTE_POS_ADDR (IT_BYTEPOS (*it));
+
+  it2 = *it;
+  if (it->multibyte_p)
+    it2.c = it2.char_to_display = STRING_CHAR_AND_LENGTH (p, it2.len);
+  else
+    {
+      it2.c = it2.char_to_display = *p, it2.len = 1;
+      if (! ASCII_CHAR_P (it2.c))
+	it2.char_to_display = BYTE8_TO_CHAR (it2.c);
+    }
+
+  it2.glyph_row = NULL;
+  it2.what = IT_CHARACTER;
+  PRODUCE_GLYPHS (&it2);
+  return NUMVAL (prop) * it2.pixel_width;
+}
+
 /* Produce a stretch glyph for iterator IT.  IT->object is the value
    of the glyph property displayed.  The value must be a list
    `(space KEYWORD VALUE ...)' with the following KEYWORD/VALUE pairs
@@ -29288,23 +29310,7 @@ produce_stretch_glyph (struct it *it)
       /* Relative width `:relative-width FACTOR' specified and valid.
 	 Compute the width of the characters having the `glyph'
 	 property.  */
-      struct it it2;
-      unsigned char *p = BYTE_POS_ADDR (IT_BYTEPOS (*it));
-
-      it2 = *it;
-      if (it->multibyte_p)
-	it2.c = it2.char_to_display = STRING_CHAR_AND_LENGTH (p, it2.len);
-      else
-	{
-	  it2.c = it2.char_to_display = *p, it2.len = 1;
-	  if (! ASCII_CHAR_P (it2.c))
-	    it2.char_to_display = BYTE8_TO_CHAR (it2.c);
-	}
-
-      it2.glyph_row = NULL;
-      it2.what = IT_CHARACTER;
-      PRODUCE_GLYPHS (&it2);
-      width = NUMVAL (prop) * it2.pixel_width;
+      width = compute_relative_width (it, prop);
     }
   else if ((prop = Fplist_get (plist, QCalign_to), !NILP (prop))
 	   && calc_pixel_width_or_height (&tem, it, prop, font, true,
@@ -29323,6 +29329,21 @@ produce_stretch_glyph (struct it *it)
     /* Nothing specified -> width defaults to canonical char width.  */
     width = FRAME_COLUMN_WIDTH (it->f);
 
+  if ((prop = Fplist_get (plist, QCmin_width), !NILP (prop))
+      && calc_pixel_width_or_height (&tem, it, prop, font, true, 0))
+    {
+      /* Absolute minimum width `:min-width WIDTH' specified and valid.  */
+      if (width < tem)
+        width = tem;
+    }
+  else if (prop = Fplist_get (plist, QCmin_relative_width), NUMVAL (prop) > 0)
+    {
+      /* Relative width `:min-relative-width FACTOR' specified and valid.  */
+      int tem = compute_relative_width (it, prop);
+      if (width < tem)
+        width = tem;
+    }
+
   if (width <= 0 && (width < 0 || !zero_width_ok_p))
     width = 1;
 
@@ -34275,6 +34296,8 @@ syms_of_xdisp (void)
   DEFSYM (QCalign_to, ":align-to");
   DEFSYM (QCrelative_width, ":relative-width");
   DEFSYM (QCrelative_height, ":relative-height");
+  DEFSYM (QCmin_relative_width, ":min-relative-width");
+  DEFSYM (QCmin_width, ":min-width");
   DEFSYM (QCeval, ":eval");
   DEFSYM (QCpropertize, ":propertize");
   DEFSYM (QCfile, ":file");