Re: [PATCH] emoji on TUI emacs

Kai Ma <[email protected]> Thu, 30 Jul 2026 17:44:40 +0200
Newsgroups gmane.emacs.devel
Message-ID <[email protected]>
The original patch forgot to update nglyphs. Here's the updated one.

On 7/30/2026 5:03 PM, Kai Ma wrote:
> Hi all,
>
> Today I took some time to look into the emoji problem on TUI emacs.
> There were some previous bug reports, for example, 79517, 81052. This
> is a super annoying problem which makes it almost unusable to edit
> files containing emojis.  (Brief description: pressing C-n/C-p can
> cause duplicate rendered lines or empty lines, and it's not possible
> to clear it easily with C-l or redisplay.)
>
> Previous discussions are usually about the width of a grapheme
> cluster.  But to my surprise, I found that the problem occurs even if
> the width is correct.  For example, ⚡ (#x26a1) composed with #fe0f
> has width 2,
>
>     (string-width "\x26a1\xfe0f") ;; ==> 2
>
> and it still causes garbled lines.  Therefore, the incorrect width of
> an emoji is not the _only_ problem.  Upon further inspection, I'm
> pretty sure it's related to composition strings, and it's due to the
> wrong x index of the next glyph: even if the composition itself has
> pixel_width=2, the next glyph's index just increases by 1.  So this
> looks like a genuine problem.  (I guess when Emacs writes
> (end - last_x) cells to fill the background, it will write one more
> cell and that can cause line wrapping and hence garbled lines.)
>
> Looking into the source code, in term.c, append_composite_glyph only
> appends one glyph, but in append_glyph, it appends pixel_width glyphs.
> Therefore, the attached patch simply copies the existing approach.  I
> have tested it on files containing many emojis, margins, and bidi
> texts, and observed no problems so far, but I might miss something so
> review is appreciated.
>
> (Note: This patch does not fix all problems related to emojis.)
>
> Also, if it's considered a safe fix, is it possible to install this
> patch on emacs-31, considering how annoying the problem is?
>
> Thanks
> Kai
0001-Produce-padding-glyphs-for-composition-strings.patch (text/plain, 5.7 KB)
From cacd2fca920c8203826702ac877ad8716d9b8ed4 Mon Sep 17 00:00:00 2001
From: Kai Ma <[email protected]>
Date: Thu, 30 Jul 2026 16:24:28 +0200
Subject: [PATCH] Produce padding glyphs for composition strings.

* src/term.c (append_composite_glyphs): Produce (pixel_width - 1)
padding glyphs.  Renamed from append_composite_glyph.
(append_composite_glyph): Renamed to append_composite_glyphs.
(produce_composite_glyph): Change its comment and the number of glyphs.
(encode_terminal_code): Skip padding glyphs first.
---
 src/term.c | 105 +++++++++++++++++++++++++++++++----------------------
 1 file changed, 61 insertions(+), 44 deletions(-)

diff --git a/src/term.c b/src/term.c
index 52ae4f61814..75769a7e91c 100644
--- a/src/term.c
+++ b/src/term.c
@@ -567,6 +567,13 @@ encode_terminal_code (struct glyph *src, int src_len,
   nchars = 0;
   while (src < src_end)
     {
+      /* We must skip glyphs to be padded for a wide character.  */
+      if (CHAR_GLYPH_PADDING_P (*src))
+	{
+	  src++;
+	  continue;
+	}
+
       if (src->type == COMPOSITE_GLYPH)
 	{
 	  struct composition *cmp;
@@ -632,8 +639,7 @@ encode_terminal_code (struct glyph *src, int src_len,
 		nchars++;
 	      }
 	}
-      /* We must skip glyphs to be padded for a wide character.  */
-      else if (! CHAR_GLYPH_PADDING_P (*src))
+      else
 	{
 	  GLYPH g;
 	  int c UNINIT;
@@ -1528,7 +1534,7 @@ #define CONDITIONAL_REASSIGN(cap1, cap2, sym)				\
    available from the initial frame as in batch mode.  */
 
 static void append_glyph (struct it *);
-static void append_composite_glyph (struct it *);
+static void append_composite_glyphs (struct it *);
 static void produce_composite_glyph (struct it *);
 static void append_glyphless_glyph (struct it *, int, const char *);
 static void produce_glyphless_glyph (struct it *, Lisp_Object);
@@ -1775,9 +1781,10 @@ produce_glyphs (struct it *it)
    face.  */
 
 static void
-append_composite_glyph (struct it *it)
+append_composite_glyphs (struct it *it)
 {
-  struct glyph *glyph;
+  struct glyph *glyph, *end;
+  int i;
 
   eassert (it->glyph_row);
   glyph = it->glyph_row->glyphs[it->area] + it->glyph_row->used[it->area];
@@ -1789,62 +1796,72 @@ append_composite_glyph (struct it *it)
     && !it->glyph_row->full_width_p
     && !WINDOW_RIGHTMOST_P (it->w)
     && WINDOW_RIGHT_MARGIN_WIDTH (it->w) == 0;
-  if (glyph < it->glyph_row->glyphs[1 + it->area] - reserve_last)
+  end = it->glyph_row->glyphs[1 + it->area] - reserve_last;
+  if (glyph < end)
     {
       /* If the glyph row is reversed, we need to prepend the glyph
 	 rather than append it.  */
       if (it->glyph_row->reversed_p && it->area == TEXT_AREA)
 	{
 	  struct glyph *g;
+	  int move_by = it->pixel_width;
 
 	  /* Make room for the new glyph.  */
+	  if (move_by > end - glyph)
+	    move_by = end - glyph;
 	  for (g = glyph - 1; g >= it->glyph_row->glyphs[it->area]; g--)
-	    g[1] = *g;
+	    g[move_by] = *g;
 	  glyph = it->glyph_row->glyphs[it->area];
+	  end = glyph + move_by;
 	}
-      glyph->type = COMPOSITE_GLYPH;
-      eassert (it->pixel_width <= SHRT_MAX);
-      glyph->pixel_width = it->pixel_width;
-      glyph->u.cmp.id = it->cmp_it.id;
-      if (it->cmp_it.ch < 0)
-	{
-	  glyph->u.cmp.automatic = 0;
-	  glyph->u.cmp.id = it->cmp_it.id;
-	}
-      else
+
+      eassert(it->pixel_width <= SHRT_MAX);
+      for (i = 0;
+	   i < it->pixel_width && glyph < end;
+	   ++i)
 	{
-	  glyph->u.cmp.automatic = 1;
+	  glyph->type = COMPOSITE_GLYPH;
+	  glyph->pixel_width = 1;
 	  glyph->u.cmp.id = it->cmp_it.id;
-	  glyph->slice.cmp.from = it->cmp_it.from;
-	  glyph->slice.cmp.to = it->cmp_it.to - 1;
-	}
+	  if (it->cmp_it.ch < 0)
+	    {
+	      glyph->u.cmp.automatic = 0;
+	      glyph->u.cmp.id = it->cmp_it.id;
+	    }
+	  else
+	    {
+	      glyph->u.cmp.automatic = 1;
+	      glyph->u.cmp.id = it->cmp_it.id;
+	      glyph->slice.cmp.from = it->cmp_it.from;
+	      glyph->slice.cmp.to = it->cmp_it.to - 1;
+	    }
+	  glyph->avoid_cursor_p = it->avoid_cursor_p;
+	  glyph->multibyte_p = it->multibyte_p;
+	  glyph->frame = it->f;
+	  glyph->face_id = it->face_id;
+	  glyph->padding_p = i > 0;
+	  glyph->charpos = CHARPOS (it->position);
+	  glyph->object = it->object;
+	  if (it->bidi_p)
+	    {
+	      glyph->resolved_level = it->bidi_it.resolved_level;
+	      eassert ((it->bidi_it.type & 7) == it->bidi_it.type);
+	      glyph->bidi_type = it->bidi_it.type;
+	    }
+	  else
+	    {
+	      glyph->resolved_level = 0;
+	      glyph->bidi_type = UNKNOWN_BT;
+	    }
 
-      glyph->avoid_cursor_p = it->avoid_cursor_p;
-      glyph->multibyte_p = it->multibyte_p;
-      glyph->frame = it->f;
-      glyph->face_id = it->face_id;
-      glyph->padding_p = false;
-      glyph->charpos = CHARPOS (it->position);
-      glyph->object = it->object;
-      if (it->bidi_p)
-	{
-	  glyph->resolved_level = it->bidi_it.resolved_level;
-	  eassert ((it->bidi_it.type & 7) == it->bidi_it.type);
-	  glyph->bidi_type = it->bidi_it.type;
+	  ++it->glyph_row->used[it->area];
+	  ++glyph;
 	}
-      else
-	{
-	  glyph->resolved_level = 0;
-	  glyph->bidi_type = UNKNOWN_BT;
-	}
-
-      ++it->glyph_row->used[it->area];
-      ++glyph;
     }
 }
 
 
-/* Produce a composite glyph for iterator IT.  IT->cmp_id is the ID of
+/* Produce composite glyphs for iterator IT.  IT->cmp_id is the ID of
    the composition.  We simply produces components of the composition
    assuming that the terminal has a capability to layout/render it
    correctly.  */
@@ -1865,9 +1882,9 @@ produce_composite_glyph (struct it *it)
       it->pixel_width = composition_gstring_width (gstring, it->cmp_it.from,
 						   it->cmp_it.to, NULL);
     }
-  it->nglyphs = 1;
+  it->nglyphs = it->pixel_width;
   if (it->glyph_row)
-    append_composite_glyph (it);
+    append_composite_glyphs (it);
 }
 
 
-- 
2.53.0