diff --git a/kitty/screen.c b/kitty/screen.c index 5c1343e47..55f3d3046 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -1101,6 +1101,11 @@ draw_control_char(Screen *self, text_loop_state *s, uint32_t ch) { } } +static bool +is_roundtripped_zero_width_char(char_type ch) { + return ch == 0xad || ch == 0x200b || ch == 0x2060; +} + static void draw_text_loop(Screen *self, const uint32_t *chars, size_t num_chars, text_loop_state *s) { init_text_loop_line(self, s); @@ -1122,7 +1127,7 @@ draw_text_loop(Screen *self, const uint32_t *chars, size_t num_chars, text_loop_ // check for some zero width chars that we want to preserve for // round tripping that are not added to prev cell by grapheme // segmentation. - if (s->prev.cc && (ch == 0xad || ch == 0x200b || ch == 0x2060)) { // soft hyphen, zero width space, word joiner + if (s->prev.cc && is_roundtripped_zero_width_char(ch)) { // soft hyphen, zero width space, word joiner draw_combining_char(self, s, ch); } continue; // we cannot represent zero width chars except as combining chars @@ -1312,10 +1317,11 @@ screen_handle_multicell_command(Screen *self, const MultiCellCommand *cmd, const char_type ch = self->lc->chars[i]; CharProps cp = char_props_for(ch); if (cp.is_invalid) continue; - if ((s = grapheme_segmentation_step(s, cp)).add_to_current_cell) lc.chars[lc.count++] = ch; + if ((s = grapheme_segmentation_step(s, cp)).add_to_current_cell || (wcwidth_std(cp) == 0 && is_roundtripped_zero_width_char(ch) && lc.count)) lc.chars[lc.count++] = ch; else { if (lc.count) handle_variable_width_multicell_command(self, mcd, &lc); - lc.chars[0] = ch; lc.count = 1; + if (wcwidth_std(cp) < 1) lc.count = 0; + else { lc.chars[0] = ch; lc.count = 1; } } } if (lc.count) handle_variable_width_multicell_command(self, mcd, &lc); diff --git a/kitty_tests/multicell.py b/kitty_tests/multicell.py index 94c2010c9..42ee5d85c 100644 --- a/kitty_tests/multicell.py +++ b/kitty_tests/multicell.py @@ -141,6 +141,13 @@ def test_multicell(self: TestMulticell) -> None: for x in range(s.columns): ac(x, y, is_multicell=True, x=x, y=y) + # Test zero width roundtripping + for preserved in '\xad\u200b\u2060': + s.reset() + multicell(s, f'|{preserved}|', scale=2) + assert_cursor_at(4, 0) + ac(0, 0, text=f'|{preserved}') + # Test wrapping s = self.create_screen(cols=6, lines=6) s.draw('x' * (s.columns - 1))