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