From 06c428ba7b0dfbc88c9e7b5b482c514ac8febb8d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 10 Dec 2024 11:16:34 +0530 Subject: [PATCH] Fix drawing multiple chars where the second or later char is on a multicell --- kitty/screen.c | 100 +++++++++++++++++++++------------------ kitty_tests/multicell.py | 17 +++++++ 2 files changed, 71 insertions(+), 46 deletions(-) diff --git a/kitty/screen.c b/kitty/screen.c index 8cc158078..747610f7d 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -941,7 +941,7 @@ screen_on_input(Screen *self) { static bool ts_cursor_on_multicell(Screen *self, text_loop_state *s) { - return s->cp[self->cursor->x].is_multicell; + return self->cursor->x < self->columns && s->cp[self->cursor->x].is_multicell; } static void @@ -983,56 +983,62 @@ map_char(Screen *self, const uint32_t ch) { return UNLIKELY(self->charset.current && ch < 256) ? self->charset.current[ch] : ch; } +static void +draw_control_char(Screen *self, text_loop_state *s, uint32_t ch) { + switch (ch) { + case BEL: + screen_bell(self); break; + case BS: + screen_backspace(self); break; + case HT: + if (UNLIKELY(self->cursor->x >= self->columns)) { + if (self->modes.mDECAWM) { + // xterm discards the TAB in this case so match its behavior + continue_to_next_line(self); + init_text_loop_line(self, s); + } else if (self->columns > 0){ + self->cursor->x = self->columns - 1; + if (ts_cursor_on_multicell(self, s)) { + if (s->cp[self->cursor->x].y) move_cursor_past_multicell(self, 1); + else replace_multicell_char_under_cursor_with_spaces(self); + } + screen_tab(self); + } + } else screen_tab(self); + break; + case SI: + screen_change_charset(self, 0); break; + case SO: + screen_change_charset(self, 1); break; + case LF: + case VT: + case FF: + screen_linefeed(self); init_text_loop_line(self, s); break; + case CR: + screen_carriage_return(self); break; + default: + break; + } +} + 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); - const uint32_t first_char = map_char(self, chars[0]); - if (ts_cursor_on_multicell(self, s) && ' ' <= first_char && first_char != DEL) { - if (s->cp[self->cursor->x].y) { - move_cursor_past_multicell(self, 1); - } else { - if (!is_combining_char(first_char)) nuke_multicell_char_at(self, self->cursor->x, self->cursor->y, s->cp[self->cursor->x].x != 0); - } - } for (size_t i = 0; i < num_chars; i++) { uint32_t ch = map_char(self, chars[i]); if (ch < ' ') { - switch (ch) { - case BEL: - screen_bell(self); break; - case BS: - screen_backspace(self); break; - case HT: - if (UNLIKELY(self->cursor->x >= self->columns)) { - if (self->modes.mDECAWM) { - // xterm discards the TAB in this case so match its behavior - continue_to_next_line(self); - init_text_loop_line(self, s); - } else if (self->columns > 0){ - self->cursor->x = self->columns - 1; - if (ts_cursor_on_multicell(self, s)) { - if (s->cp[self->cursor->x].y) move_cursor_past_multicell(self, 1); - else replace_multicell_char_under_cursor_with_spaces(self); - } - screen_tab(self); - } - } else screen_tab(self); - break; - case SI: - screen_change_charset(self, 0); break; - case SO: - screen_change_charset(self, 1); break; - case LF: - case VT: - case FF: - screen_linefeed(self); init_text_loop_line(self, s); break; - case CR: - screen_carriage_return(self); break; - default: - break; - } + draw_control_char(self, s, ch); continue; } + if (ts_cursor_on_multicell(self, s)) { + if (s->cp[self->cursor->x].y) { + move_cursor_past_multicell(self, 1); + init_text_loop_line(self, s); + } else { + if (!is_combining_char(ch)) nuke_multicell_char_at(self, self->cursor->x, self->cursor->y, s->cp[self->cursor->x].x != 0); + } + } + int char_width = 1; if (ch > DEL) { // not printable ASCII if (is_ignored_char(ch)) continue; @@ -1055,9 +1061,11 @@ draw_text_loop(Screen *self, const uint32_t *chars, size_t num_chars, text_loop_ if (self->modes.mDECAWM) { continue_to_next_line(self); init_text_loop_line(self, s); - } else { - self->cursor->x = self->columns - char_width; - if (ts_cursor_on_multicell(self, s)) replace_multicell_char_under_cursor_with_spaces(self); + } else self->cursor->x = self->columns - char_width; + CPUCell *c = &s->cp[self->cursor->x]; + if (c->is_multicell) { + if (c->y) { move_cursor_past_multicell(self, char_width); init_text_loop_line(self, s); } + nuke_multicell_char_at(self, self->cursor->x, self->cursor->y, c->x > 0); } } if (self->modes.mIRM) insert_characters(self, self->cursor->x, char_width, self->cursor->y, true); diff --git a/kitty_tests/multicell.py b/kitty_tests/multicell.py index 4fb8ca048..214c0eafa 100644 --- a/kitty_tests/multicell.py +++ b/kitty_tests/multicell.py @@ -107,8 +107,24 @@ def test_multicell(self: TestMulticell) -> None: for x in range(0, 4): ac(x, 1, is_multicell=True, width=2, scale=2, subscale_n=3, x=x, y=1, text='', natural_width=False) + # Test wrapping + s.reset() + multicell(s, 'a', scale=2) + s.draw('x' * s.columns) + ac(s.cursor.x-1, s.cursor.y, is_multicell=False, text='x') + ac(0, 0, is_multicell=True, text='a') + ac(0, 1, is_multicell=True, text='', y=1) + # Test draw with cursor in a multicell s.reset() + multicell(s, '12', scale=2) + s.draw('\rx') + ac(0, 0, is_multicell=False, text='x') + ac(1, 0, is_multicell=False, text='') + ac(0, 1, is_multicell=False, text='') + ac(1, 1, is_multicell=False, text='') + ac(2, 0, is_multicell=True, text='2') + s.reset() s.draw('莊') s.cursor.x -= 1 s.draw('a'), ac(0, 0, is_multicell=False), ac(1, 0, is_multicell=False) @@ -140,6 +156,7 @@ def test_multicell(self: TestMulticell) -> None: multicell(s, 'a', scale=2) s.cursor.x += 1 multicell(s, 'b', scale=2) + assert_cursor_at(5, 0) s.draw('\u2716\ufe0f') assert_cursor_at(2, 2) s.reset()