term: insert-mode: handle combining characters correctly

When the client application emits combining characters, for example
multi-codepoint emojis, in insert-mode, we ended up pushing partial
graphemes to the right, for each codepoint, resulting in too many
cells (and with the wrong content) being inserted.

The fix is fairly simple; don't "insert" when appending characters to
an existing grapheme cluster.

This isn't something we can detect easily in print_insert() (it would
require us to do grapheme clustering again). Fortunately, we do have
the required information in action_utf8_print(). So, pass this
information as a boolean to term_print().

Closes #1947
This commit is contained in:
Daniel Eklöf 2025-02-06 07:31:30 +01:00
parent dd01783f88
commit 88dcde3ed8
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
5 changed files with 14 additions and 6 deletions

View file

@ -105,9 +105,13 @@
([#1929][1929]). ([#1929][1929]).
* Foot not closing file descriptors for unrecognized or `no_keymap` * Foot not closing file descriptors for unrecognized or `no_keymap`
keymaps. keymaps.
* Combining characters (including emojis consisting of multiple
codepoints) not being handled correctly when _insert mode_ is
enabled ([#1947][1947]).
[1918]: https://codeberg.org/dnkl/foot/issues/1918 [1918]: https://codeberg.org/dnkl/foot/issues/1918
[1929]: https://codeberg.org/dnkl/foot/issues/1929 [1929]: https://codeberg.org/dnkl/foot/issues/1929
[1947]: https://codeberg.org/dnkl/foot/issues/1947
### Security ### Security

2
csi.c
View file

@ -793,7 +793,7 @@ csi_dispatch(struct terminal *term, uint8_t final)
const int width = c32width(term->vt.last_printed); const int width = c32width(term->vt.last_printed);
if (width > 0) { if (width > 0) {
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
term_print(term, term->vt.last_printed, width); term_print(term, term->vt.last_printed, width, false);
} }
} }
break; break;

View file

@ -3896,7 +3896,7 @@ term_fill(struct terminal *term, int r, int c, uint8_t data, size_t count,
} }
void void
term_print(struct terminal *term, char32_t wc, int width) term_print(struct terminal *term, char32_t wc, int width, bool insert_mode_disable)
{ {
xassert(width > 0); xassert(width > 0);
@ -3918,7 +3918,8 @@ term_print(struct terminal *term, char32_t wc, int width)
} }
print_linewrap(term); print_linewrap(term);
print_insert(term, width); if (!insert_mode_disable)
print_insert(term, width);
int col = grid->cursor.point.col; int col = grid->cursor.point.col;
@ -3990,7 +3991,7 @@ term_print(struct terminal *term, char32_t wc, int width)
static void static void
ascii_printer_generic(struct terminal *term, char32_t wc) ascii_printer_generic(struct terminal *term, char32_t wc)
{ {
term_print(term, wc, 1); term_print(term, wc, 1, false);
} }
static void static void

View file

@ -894,7 +894,8 @@ void term_cursor_up(struct terminal *term, int count);
void term_cursor_down(struct terminal *term, int count); void term_cursor_down(struct terminal *term, int count);
void term_cursor_blink_update(struct terminal *term); void term_cursor_blink_update(struct terminal *term);
void term_print(struct terminal *term, char32_t wc, int width); void term_print(struct terminal *term, char32_t wc, int width,
bool insert_mode_disable);
void term_fill(struct terminal *term, int row, int col, uint8_t c, size_t count, void term_fill(struct terminal *term, int row, int col, uint8_t c, size_t count,
bool use_sgr_attrs); bool use_sgr_attrs);

4
vt.c
View file

@ -703,6 +703,7 @@ static void
action_utf8_print(struct terminal *term, char32_t wc) action_utf8_print(struct terminal *term, char32_t wc)
{ {
int width = c32width(wc); int width = c32width(wc);
bool insert_mode_disable = false;
const bool grapheme_clustering = term->grapheme_shaping; const bool grapheme_clustering = term->grapheme_shaping;
#if !defined(FOOT_GRAPHEME_CLUSTERING) #if !defined(FOOT_GRAPHEME_CLUSTERING)
@ -757,6 +758,7 @@ action_utf8_print(struct terminal *term, char32_t wc)
if (base_width > 0) { if (base_width > 0) {
term->grid->cursor.point.col = col; term->grid->cursor.point.col = col;
term->grid->cursor.lcf = false; term->grid->cursor.lcf = false;
insert_mode_disable = true;
if (composed == NULL) { if (composed == NULL) {
bool base_from_primary; bool base_from_primary;
@ -954,7 +956,7 @@ action_utf8_print(struct terminal *term, char32_t wc)
out: out:
if (width > 0) if (width > 0)
term_print(term, wc, width); term_print(term, wc, width, insert_mode_disable);
} }
static void static void