diff --git a/docs/changelog.rst b/docs/changelog.rst index 463149449..28023d234 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -46,7 +46,7 @@ Detailed list of changes - When changing the cursor color via escape codes or remote control to a fixed color, do not reset cursor_text_color (:iss:`5994`) -- Input Method Extensions: Fix incorrect rendering of IME in-progress text in some situations (:pull:`6002`) +- Input Method Extensions: Fix incorrect rendering of IME in-progress and commited text in some situations (:pull:`6049`) - Linux: Reduce minimum required OpenGL version from 3.3 to 3.1 + extensions (:iss:`2790`) diff --git a/kitty/child-monitor.c b/kitty/child-monitor.c index 400703e52..e877083b3 100644 --- a/kitty/child-monitor.c +++ b/kitty/child-monitor.c @@ -626,8 +626,16 @@ cursor_needs_render(Window *w) { static bool collect_cursor_info(CursorRenderInfo *ans, Window *w, monotonic_t now, OSWindow *os_window) { ScreenRenderData *rd = &w->render_data; - Cursor *cursor = rd->screen->cursor; - ans->x = cursor->x; ans->y = cursor->y; + Cursor *cursor; + if (screen_is_overlay_active(rd->screen)) { + // Do not force the cursor to be visible here for the sake of some programs that prefer it hidden + cursor = &(rd->screen->overlay_line.original_line.cursor); + ans->x = rd->screen->overlay_line.cursor_x; + ans->y = rd->screen->overlay_line.ynum; + } else { + cursor = rd->screen->cursor; + ans->x = cursor->x; ans->y = cursor->y; + } ans->is_visible = false; if (rd->screen->scrolled_by || !screen_is_cursor_visible(rd->screen)) return cursor_needs_render(w); monotonic_t time_since_start_blink = now - os_window->cursor_blink_zero_time; diff --git a/kitty/keys.c b/kitty/keys.c index 2e12075a2..f433221c0 100644 --- a/kitty/keys.c +++ b/kitty/keys.c @@ -105,8 +105,13 @@ void update_ime_position(Window* w, Screen *screen) { unsigned int cell_width = global_state.callback_os_window->fonts_data->cell_width, cell_height = global_state.callback_os_window->fonts_data->cell_height; unsigned int left = w->geometry.left, top = w->geometry.top; - left += screen->cursor->x * cell_width; - top += screen->cursor->y * cell_height; + if (screen_is_overlay_active(screen)) { + left += screen->overlay_line.cursor_x * cell_width; + top += MIN(screen->overlay_line.ynum + screen->scrolled_by, screen->lines - 1) * cell_height; + } else { + left += screen->cursor->x * cell_width; + top += screen->cursor->y * cell_height; + } GLFWIMEUpdateEvent ev = { .type = GLFW_IME_UPDATE_CURSOR_POSITION }; ev.cursor.left = left; ev.cursor.top = top; ev.cursor.width = cell_width; ev.cursor.height = cell_height; glfwUpdateIMEState(global_state.callback_os_window->handle, &ev); @@ -154,12 +159,12 @@ on_key_input(GLFWkeyevent *ev) { case GLFW_IME_WAYLAND_DONE_EVENT: // If we update IME position here it sends GNOME's text input system into // an infinite loop. See https://github.com/kovidgoyal/kitty/issues/5105 - screen_draw_overlay_text(screen, NULL); + screen_update_overlay_text(screen, NULL); debug("handled wayland IME done event\n"); return; case GLFW_IME_PREEDIT_CHANGED: + screen_update_overlay_text(screen, text); update_ime_position(w, screen); - screen_draw_overlay_text(screen, text); debug("updated pre-edit text: '%s'\n", text); return; case GLFW_IME_COMMIT_TEXT: @@ -167,7 +172,7 @@ on_key_input(GLFWkeyevent *ev) { schedule_write_to_child(w->id, 1, text, strlen(text)); debug("committed pre-edit text: %s\n", text); } else debug("committed pre-edit text: (null)\n"); - screen_draw_overlay_text(screen, NULL); + screen_update_overlay_text(screen, NULL); return; case GLFW_IME_NONE: // for macOS, update ime position on every key input diff --git a/kitty/screen.c b/kitty/screen.c index 842396e31..85298c565 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -23,6 +23,7 @@ #include "unicode-data.h" #include "modes.h" #include "wcwidth-std.h" +#include "wcswidth.h" #include "control-codes.h" #include "charsets.h" #include "keys.h" @@ -49,25 +50,37 @@ init_tabstops(bool *tabstops, index_type count) { } static bool -init_overlay_line(Screen *self, index_type columns) { +init_overlay_line(Screen *self, index_type columns, bool keep_active) { PyMem_Free(self->overlay_line.cpu_cells); PyMem_Free(self->overlay_line.gpu_cells); + PyMem_Free(self->overlay_line.original_line.cpu_cells); + PyMem_Free(self->overlay_line.original_line.gpu_cells); self->overlay_line.cpu_cells = PyMem_Calloc(columns, sizeof(CPUCell)); self->overlay_line.gpu_cells = PyMem_Calloc(columns, sizeof(GPUCell)); - if (!self->overlay_line.cpu_cells || !self->overlay_line.gpu_cells) { + self->overlay_line.original_line.cpu_cells = PyMem_Calloc(columns, sizeof(CPUCell)); + self->overlay_line.original_line.gpu_cells = PyMem_Calloc(columns, sizeof(GPUCell)); + if (!self->overlay_line.cpu_cells || !self->overlay_line.gpu_cells || + !self->overlay_line.original_line.cpu_cells || !self->overlay_line.original_line.gpu_cells) { PyErr_NoMemory(); return false; } - self->overlay_line.is_active = false; - self->overlay_line.xnum = 0; + if (!keep_active) { + self->overlay_line.is_active = false; + self->overlay_line.xnum = 0; + } + self->overlay_line.is_dirty = true; self->overlay_line.ynum = 0; self->overlay_line.xstart = 0; + self->overlay_line.cursor_x = 0; + self->overlay_line.last_ime_pos.x = 0; + self->overlay_line.last_ime_pos.y = 0; + return true; } -static void save_overlay_line(Screen *self, const char* func_name); -static void restore_overlay_line(Screen *self); static void deactivate_overlay_line(Screen *self); -static void clear_saved_overlay_line(Screen *self); +static void update_overlay_position(Screen *self); +static void render_overlay_line(Screen *self, Line *line, FONTS_DATA_HANDLE fonts_data); +static void update_overlay_line_data(Screen *self, uint8_t *data); #define RESET_CHARSETS \ self->g0_charset = translation_table(0); \ @@ -144,8 +157,7 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { init_tabstops(self->main_tabstops, self->columns); init_tabstops(self->alt_tabstops, self->columns); self->key_encoding_flags = self->main_key_encoding_flags; - if (!init_overlay_line(self, self->columns)) { Py_CLEAR(self); return NULL; } - clear_saved_overlay_line(self); + if (!init_overlay_line(self, self->columns, false)) { Py_CLEAR(self); return NULL; } self->hyperlink_pool = alloc_hyperlink_pool(); if (!self->hyperlink_pool) { Py_CLEAR(self); return PyErr_NoMemory(); } self->as_ansi_buf.hyperlink_pool = self->hyperlink_pool; @@ -158,8 +170,11 @@ static Line* range_line_(Screen *self, int y); void screen_reset(Screen *self) { if (self->linebuf == self->alt_linebuf) screen_toggle_screen_buffer(self, true, true); - if (self->overlay_line.is_active) deactivate_overlay_line(self); - clear_saved_overlay_line(self); + if (screen_is_overlay_active(self)) { + deactivate_overlay_line(self); + // Cancel IME composition + update_ime_position_for_window(self->window_id, false, -1); + } Py_CLEAR(self->last_reported_cwd); self->render_unfocused_cursor = false; memset(self->main_key_encoding_flags, 0, sizeof(self->main_key_encoding_flags)); @@ -336,7 +351,6 @@ found: static bool screen_resize(Screen *self, unsigned int lines, unsigned int columns) { - if (self->overlay_line.is_active) save_overlay_line(self, __func__); lines = MAX(1u, lines); columns = MAX(1u, columns); bool is_main = self->linebuf == self->main_linebuf; @@ -361,7 +375,7 @@ screen_resize(Screen *self, unsigned int lines, unsigned int columns) { which.num_content_lines = num_content_lines_after; \ } // Resize overlay line - if (!init_overlay_line(self, columns)) return false; + if (!init_overlay_line(self, columns, true)) return false; // Resize main linebuf HistoryBuf *nh = realloc_hb(self->historybuf, self->historybuf->ynum, columns, &self->as_ansi_buf); @@ -426,7 +440,6 @@ screen_resize(Screen *self, unsigned int lines, unsigned int columns) { self->linebuf->line->cpu_cells[0].ch = 0; self->cursor->x = 0; } - restore_overlay_line(self); return true; } @@ -463,7 +476,9 @@ dealloc(Screen* self) { Py_CLEAR(self->marker); PyMem_Free(self->overlay_line.cpu_cells); PyMem_Free(self->overlay_line.gpu_cells); - Py_CLEAR(self->overlay_line.save.overlay_text); + PyMem_Free(self->overlay_line.original_line.cpu_cells); + PyMem_Free(self->overlay_line.original_line.gpu_cells); + Py_CLEAR(self->overlay_line.overlay_text); PyMem_Free(self->main_tabstops); free(self->pending_mode.buf); free(self->selections.items); @@ -661,7 +676,6 @@ draw_combining_char(Screen *self, char_type ch) { } } - static void draw_codepoint(Screen *self, char_type och, bool from_input_stream) { if (is_ignored_char(och)) return; @@ -713,87 +727,11 @@ draw_codepoint(Screen *self, char_type och, bool from_input_stream) { linebuf_mark_line_dirty(self->linebuf, self->cursor->y); } -void -screen_draw_overlay_text(Screen *self, const char *utf8_text) { - if (self->overlay_line.is_active) deactivate_overlay_line(self); - if (self->overlay_line.save.overlay_text) clear_saved_overlay_line(self); - if (!utf8_text || !utf8_text[0]) return; - Line *line = range_line_(self, self->cursor->y); - if (!line) return; - line_save_cells(line, 0, self->columns, self->overlay_line.gpu_cells, self->overlay_line.cpu_cells); - self->overlay_line.is_active = true; - self->overlay_line.ynum = self->cursor->y; - self->overlay_line.xstart = self->cursor->x; - self->overlay_line.xnum = 0; - uint32_t codepoint = 0; UTF8State state = UTF8_ACCEPT; - bool orig_line_wrap_mode = self->modes.mDECAWM; - self->modes.mDECAWM = false; - self->cursor->reverse ^= true; - index_type before; - while (*utf8_text) { - switch(decode_utf8(&state, &codepoint, *(utf8_text++))) { - case UTF8_ACCEPT: - before = self->cursor->x; - draw_codepoint(self, codepoint, false); - self->overlay_line.xnum += self->cursor->x - before; - break; - case UTF8_REJECT: - break; - } - } - self->cursor->reverse ^= true; - self->modes.mDECAWM = orig_line_wrap_mode; -} - -static PyObject* -get_overlay_text(Screen *self) { -#define ol self->overlay_line - if (ol.ynum >= self->lines || ol.xnum >= self->columns || !ol.xnum) return NULL; - Line *line = range_line_(self, ol.ynum); - if (!line) return NULL; - return unicode_in_range(line, ol.xstart, ol.xstart + ol.xnum, true, false, true); -#undef ol -} - -static void -save_overlay_line(Screen *self, const char* func_name) { - if (self->overlay_line.is_active && screen_is_cursor_visible(self)) { - Py_XDECREF(self->overlay_line.save.overlay_text); - self->overlay_line.save.overlay_text = get_overlay_text(self); - self->overlay_line.save.func_name = func_name; - deactivate_overlay_line(self); - } -} - -static void -restore_overlay_line(Screen *self) { - if (self->overlay_line.save.overlay_text && screen_is_cursor_visible(self)) { - debug("Received input from child (%s) while overlay active. Overlay contents: %s\n", self->overlay_line.save.func_name, PyUnicode_AsUTF8(self->overlay_line.save.overlay_text)); - screen_draw_overlay_text(self, PyUnicode_AsUTF8(self->overlay_line.save.overlay_text)); - clear_saved_overlay_line(self); - update_ime_position_for_window(self->window_id, false, 0); - } -} - -static void -clear_saved_overlay_line(Screen *self) { - Py_CLEAR(self->overlay_line.save.overlay_text); -} - -static void -restore_overlay_line_from_cleanup(Screen **self) { - restore_overlay_line(*self); -} - -#define MOVE_OVERLAY_LINE_WITH_CURSOR Screen __attribute__ ((__cleanup__(restore_overlay_line_from_cleanup))) *_sol_ = self; save_overlay_line(_sol_, __func__); - void screen_draw(Screen *self, uint32_t och, bool from_input_stream) { - MOVE_OVERLAY_LINE_WITH_CURSOR; draw_codepoint(self, och, from_input_stream); } - void screen_align(Screen *self) { self->margin_top = 0; self->margin_bottom = self->lines - 1; @@ -819,7 +757,6 @@ screen_alignment_display(Screen *self) { void select_graphic_rendition(Screen *self, int *params, unsigned int count, Region *region_) { - MOVE_OVERLAY_LINE_WITH_CURSOR; // needed in case colors have changed if (region_) { Region region = *region_; if (!region.top) region.top = 1; @@ -1018,14 +955,7 @@ set_mode_from_const(Screen *self, unsigned int mode, bool val) { self->modes.mDECCKM = val; break; case DECTCEM: - if(!val) { - save_overlay_line(self, __func__); - self->modes.mDECTCEM = val; - } else { - self->modes.mDECTCEM = val; - if (self->overlay_line.is_active && !self->overlay_line.save.overlay_text) save_overlay_line(self, __func__); - restore_overlay_line(self); - } + self->modes.mDECTCEM = val; break; case DECSCNM: // Render screen in reverse video @@ -1191,7 +1121,6 @@ screen_backspace(Screen *self) { void screen_tab(Screen *self) { - MOVE_OVERLAY_LINE_WITH_CURSOR; // Move to the next tab space, or the end of the screen if there aren't anymore left. unsigned int found = 0; for (unsigned int i = self->cursor->x + 1; i < self->columns; i++) { @@ -1223,7 +1152,6 @@ screen_tab(Screen *self) { void screen_backtab(Screen *self, unsigned int count) { - MOVE_OVERLAY_LINE_WITH_CURSOR; // Move back count tabs if (!count) count = 1; int i; @@ -1261,7 +1189,6 @@ screen_set_tab_stop(Screen *self) { void screen_cursor_back(Screen *self, unsigned int count/*=1*/, int move_direction/*=-1*/) { - MOVE_OVERLAY_LINE_WITH_CURSOR; if (count == 0) count = 1; if (move_direction < 0 && count > self->cursor->x) self->cursor->x = 0; else self->cursor->x += move_direction * count; @@ -1275,13 +1202,12 @@ screen_cursor_forward(Screen *self, unsigned int count/*=1*/) { void screen_cursor_up(Screen *self, unsigned int count/*=1*/, bool do_carriage_return/*=false*/, int move_direction/*=-1*/) { - MOVE_OVERLAY_LINE_WITH_CURSOR; bool in_margins = cursor_within_margins(self); if (count == 0) count = 1; if (move_direction < 0 && count > self->cursor->y) self->cursor->y = 0; else self->cursor->y += move_direction * count; - screen_ensure_bounds(self, true, in_margins); if (do_carriage_return) self->cursor->x = 0; + screen_ensure_bounds(self, true, in_margins); } void @@ -1301,7 +1227,6 @@ screen_cursor_down1(Screen *self, unsigned int count/*=1*/) { void screen_cursor_to_column(Screen *self, unsigned int column) { - MOVE_OVERLAY_LINE_WITH_CURSOR; unsigned int x = MAX(column, 1u) - 1; if (x != self->cursor->x) { self->cursor->x = x; @@ -1328,7 +1253,6 @@ screen_cursor_to_column(Screen *self, unsigned int column) { void screen_index(Screen *self) { - MOVE_OVERLAY_LINE_WITH_CURSOR; // Move cursor down one line, scrolling screen if needed unsigned int top = self->margin_top, bottom = self->margin_bottom; if (self->cursor->y == bottom) { @@ -1338,7 +1262,6 @@ screen_index(Screen *self) { void screen_scroll(Screen *self, unsigned int count) { - MOVE_OVERLAY_LINE_WITH_CURSOR; // Scroll the screen up by count lines, not moving the cursor unsigned int top = self->margin_top, bottom = self->margin_bottom; while (count > 0) { @@ -1349,7 +1272,6 @@ screen_scroll(Screen *self, unsigned int count) { void screen_reverse_index(Screen *self) { - MOVE_OVERLAY_LINE_WITH_CURSOR; // Move cursor up one line, scrolling screen if needed unsigned int top = self->margin_top, bottom = self->margin_bottom; if (self->cursor->y == top) { @@ -1359,7 +1281,6 @@ screen_reverse_index(Screen *self) { static void _reverse_scroll(Screen *self, unsigned int count, bool fill_from_scrollback) { - MOVE_OVERLAY_LINE_WITH_CURSOR; // Scroll the screen down by count lines, not moving the cursor unsigned int top = self->margin_top, bottom = self->margin_bottom; fill_from_scrollback = fill_from_scrollback && self->linebuf == self->main_linebuf; @@ -1389,7 +1310,6 @@ screen_reverse_scroll_and_fill_from_scrollback(Screen *self, unsigned int count) void screen_carriage_return(Screen *self) { if (self->cursor->x != 0) { - MOVE_OVERLAY_LINE_WITH_CURSOR; self->cursor->x = 0; } } @@ -1449,7 +1369,7 @@ copy_specific_mode(Screen *self, unsigned int mode, const ScreenModes *src, Scre SIMPLE_MODE(BRACKETED_PASTE) SIMPLE_MODE(FOCUS_TRACKING) SIMPLE_MODE(DECCKM) - SIDE_EFFECTS(DECTCEM) // side effect: redraw IME overlay line + SIMPLE_MODE(DECTCEM) SIMPLE_MODE(DECAWM) case MOUSE_BUTTON_TRACKING: case MOUSE_MOTION_TRACKING: case MOUSE_MOVE_TRACKING: dest->mouse_tracking_mode = src->mouse_tracking_mode; break; @@ -1504,7 +1424,6 @@ screen_save_modes(Screen *self) { void screen_restore_cursor(Screen *self) { - MOVE_OVERLAY_LINE_WITH_CURSOR; Savepoint *sp = self->linebuf == self->main_linebuf ? &self->main_savepoint : &self->alt_savepoint; if (!sp->is_valid) { screen_cursor_position(self, 1, 1); @@ -1542,7 +1461,6 @@ screen_ensure_bounds(Screen *self, bool force_use_margins/*=false*/, bool in_mar void screen_cursor_position(Screen *self, unsigned int line, unsigned int column) { - MOVE_OVERLAY_LINE_WITH_CURSOR; bool in_margins = cursor_within_margins(self); line = (line == 0 ? 1 : line) - 1; column = (column == 0 ? 1: column) - 1; @@ -1737,6 +1655,7 @@ screen_insert_lines(Screen *self, unsigned int count) { static void screen_scroll_until_cursor_prompt(Screen *self) { + bool in_margins = cursor_within_margins(self); int q = screen_cursor_at_a_shell_prompt(self); unsigned int y = q > -1 ? (unsigned int)q : self->cursor->y; unsigned int num_lines_to_scroll = MIN(self->margin_bottom, y); @@ -1744,6 +1663,7 @@ screen_scroll_until_cursor_prompt(Screen *self) { self->cursor->y = self->margin_bottom; while (num_lines_to_scroll--) screen_index(self); self->cursor->y = final_y; + screen_ensure_bounds(self, false, in_margins); } void @@ -1785,7 +1705,6 @@ screen_repeat_character(Screen *self, unsigned int count) { void screen_delete_characters(Screen *self, unsigned int count) { - MOVE_OVERLAY_LINE_WITH_CURSOR; // Delete characters, later characters are moved left const unsigned int bottom = self->lines ? self->lines - 1 : 0; if (count == 0) count = 1; @@ -1803,7 +1722,6 @@ screen_delete_characters(Screen *self, unsigned int count) { void screen_erase_characters(Screen *self, unsigned int count) { - MOVE_OVERLAY_LINE_WITH_CURSOR; // Delete characters replacing them by spaces if (count == 0) count = 1; unsigned int x = self->cursor->x; @@ -2280,11 +2198,13 @@ screen_has_marker(Screen *self) { void screen_update_cell_data(Screen *self, void *address, FONTS_DATA_HANDLE fonts_data, bool cursor_has_moved) { + const bool is_overlay_active = screen_is_overlay_active(self); unsigned int history_line_added_count = self->history_line_added_count; index_type lnum; bool was_dirty = self->is_dirty; - if (self->scrolled_by) self->scrolled_by = MIN(self->scrolled_by + history_line_added_count, self->historybuf->count); screen_reset_dirty(self); + update_overlay_position(self); + if (self->scrolled_by) self->scrolled_by = MIN(self->scrolled_by + history_line_added_count, self->historybuf->count); self->scroll_changed = false; for (index_type y = 0; y < MIN(self->lines, self->scrolled_by); y++) { lnum = self->scrolled_by - 1 - y; @@ -2303,11 +2223,18 @@ screen_update_cell_data(Screen *self, void *address, FONTS_DATA_HANDLE fonts_dat (cursor_has_moved && (self->cursor->y == lnum || self->last_rendered.cursor_y == lnum))) { render_line(fonts_data, self->linebuf->line, lnum, self->cursor, self->disable_ligatures); if (self->linebuf->line->attrs.has_dirty_text && screen_has_marker(self)) mark_text_in_line(self->marker, self->linebuf->line); - + if (is_overlay_active && lnum == self->overlay_line.ynum) render_overlay_line(self, self->linebuf->line, fonts_data); linebuf_mark_line_clean(self->linebuf, lnum); } update_line_data(self->linebuf->line, y, address); } + if (is_overlay_active && self->overlay_line.ynum + self->scrolled_by < self->lines) { + if (self->overlay_line.is_dirty) { + linebuf_init_line(self->linebuf, self->overlay_line.ynum); + render_overlay_line(self, self->linebuf->line, fonts_data); + } + update_overlay_line_data(self, address); + } if (was_dirty) clear_selection(&self->url_ranges); } @@ -2696,22 +2623,6 @@ screen_open_url(Screen *self) { return found; } -static void -deactivate_overlay_line(Screen *self) { - if (self->overlay_line.is_active && self->overlay_line.xnum && self->overlay_line.ynum < self->lines) { - Line *line = range_line_(self, self->overlay_line.ynum); - line_reset_cells(line, self->overlay_line.xstart, self->overlay_line.xnum, self->overlay_line.gpu_cells, self->overlay_line.cpu_cells); - if (self->cursor->y == self->overlay_line.ynum) self->cursor->x = self->overlay_line.xstart; - self->is_dirty = true; - linebuf_mark_line_dirty(self->linebuf, self->overlay_line.ynum); - } - self->overlay_line.is_active = false; - self->overlay_line.ynum = 0; - self->overlay_line.xnum = 0; - self->overlay_line.xstart = 0; -} - - // }}} // URLs {{{ @@ -2792,7 +2703,136 @@ screen_detect_url(Screen *screen, unsigned int x, unsigned int y) { return has_url ? -1 : 0; } +// }}} +// IME Overlay {{{ +bool +screen_is_overlay_active(Screen *self) { + return self->overlay_line.is_active; +} + +static void +deactivate_overlay_line(Screen *self) { + if (self->overlay_line.is_active && self->overlay_line.xnum && self->overlay_line.ynum < self->lines) { + self->is_dirty = true; + linebuf_mark_line_dirty(self->linebuf, self->overlay_line.ynum); + } + self->overlay_line.is_active = false; + self->overlay_line.is_dirty = true; + self->overlay_line.ynum = 0; + self->overlay_line.xstart = 0; + self->overlay_line.cursor_x = 0; +} + +void +screen_update_overlay_text(Screen *self, const char *utf8_text) { + if (screen_is_overlay_active(self)) deactivate_overlay_line(self); + if (!utf8_text || !utf8_text[0]) return; + PyObject *text = PyUnicode_FromString(utf8_text); + if (!text) return; + Py_XDECREF(self->overlay_line.overlay_text); + // Calculate the total number of cells for initial overlay cursor position + PyObject *text_len = wcswidth_std(NULL, text); + self->overlay_line.overlay_text = text; + self->overlay_line.is_active = true; + self->overlay_line.is_dirty = true; + self->overlay_line.xstart = self->cursor->x; + self->overlay_line.xnum = !text_len ? 0 : PyLong_AsLong(text_len); + self->overlay_line.cursor_x = MIN(self->overlay_line.xstart + self->overlay_line.xnum, self->columns); + self->overlay_line.ynum = self->cursor->y; + cursor_copy_to(self->cursor, &(self->overlay_line.original_line.cursor)); + linebuf_mark_line_dirty(self->linebuf, self->overlay_line.ynum); + self->is_dirty = true; + // Since we are typing, scroll to the bottom + if (self->scrolled_by != 0) { + self->scrolled_by = 0; + self->scroll_changed = true; + } +} + +static void +screen_draw_overlay_line(Screen *self) { + if (!self->overlay_line.overlay_text) return; + const char *utf8_text = PyUnicode_AsUTF8(self->overlay_line.overlay_text); + if (!utf8_text || !utf8_text[0]) return; + self->overlay_line.xnum = 0; + uint32_t codepoint = 0; UTF8State state = UTF8_ACCEPT; + bool orig_line_wrap_mode = self->modes.mDECAWM; + bool orig_cursor_enable_mode = self->modes.mDECTCEM; + bool orig_insert_replace_mode = self->modes.mIRM; + self->modes.mDECAWM = false; + self->modes.mDECTCEM = false; + self->modes.mIRM = false; + Cursor *orig_cursor = self->cursor; + self->cursor = &(self->overlay_line.original_line.cursor); + self->cursor->reverse ^= true; + self->cursor->x = self->overlay_line.xstart; + self->cursor->y = self->overlay_line.ynum; + self->overlay_line.xnum = 0; + index_type before; + while (*utf8_text) { + switch(decode_utf8(&state, &codepoint, *(utf8_text++))) { + case UTF8_ACCEPT: + before = self->cursor->x; + draw_codepoint(self, codepoint, false); + self->overlay_line.xnum += self->cursor->x - before; + break; + case UTF8_REJECT: + break; + } + } + self->overlay_line.cursor_x = self->cursor->x; + self->cursor->reverse ^= true; + self->cursor = orig_cursor; + self->modes.mDECAWM = orig_line_wrap_mode; + self->modes.mDECTCEM = orig_cursor_enable_mode; + self->modes.mIRM = orig_insert_replace_mode; +} + +static void +update_overlay_position(Screen *self) { + if (screen_is_overlay_active(self) && screen_is_cursor_visible(self)) { + bool cursor_update = false; + if (self->cursor->x != self->overlay_line.xstart) { + cursor_update = true; + self->overlay_line.xstart = self->cursor->x; + self->overlay_line.cursor_x = MIN(self->overlay_line.xstart + self->overlay_line.xnum, self->columns); + } + if (self->cursor->y != self->overlay_line.ynum) { + cursor_update = true; + linebuf_mark_line_dirty(self->linebuf, self->overlay_line.ynum); + self->overlay_line.ynum = self->cursor->y; + } + if (cursor_update) { + linebuf_mark_line_dirty(self->linebuf, self->overlay_line.ynum); + self->overlay_line.is_dirty = true; + self->is_dirty = true; + } + } +} + +static void +render_overlay_line(Screen *self, Line *line, FONTS_DATA_HANDLE fonts_data) { +#define ol self->overlay_line + line_save_cells(line, 0, line->xnum, ol.original_line.gpu_cells, ol.original_line.cpu_cells); + screen_draw_overlay_line(self); + render_line(fonts_data, line, ol.ynum, self->cursor, self->disable_ligatures); + line_save_cells(line, 0, line->xnum, ol.gpu_cells, ol.cpu_cells); + line_reset_cells(line, 0, line->xnum, ol.original_line.gpu_cells, ol.original_line.cpu_cells); + ol.is_dirty = false; + const index_type y = MIN(ol.ynum + self->scrolled_by, self->lines - 1); + if (ol.last_ime_pos.x != ol.cursor_x || ol.last_ime_pos.y != y) { + ol.last_ime_pos.x = ol.cursor_x; ol.last_ime_pos.y = y; + update_ime_position_for_window(self->window_id, false, 0); + } +#undef ol +} + +static void +update_overlay_line_data(Screen *self, uint8_t *data) { + const size_t base = sizeof(GPUCell) * (self->overlay_line.ynum + self->scrolled_by) * self->columns; + memcpy(data + base, self->overlay_line.gpu_cells, self->columns * sizeof(GPUCell)); +} // }}} @@ -3921,7 +3961,7 @@ focus_changed(Screen *self, PyObject *has_focus_) { if (has_focus != previous) { self->has_focus = has_focus; if (has_focus) self->has_activity_since_last_focus = false; - else if (self->overlay_line.is_active) deactivate_overlay_line(self); + else if (screen_is_overlay_active(self)) deactivate_overlay_line(self); if (self->modes.mFOCUS_TRACKING) write_escape_code_to_child(self, CSI, has_focus ? "I" : "O"); Py_RETURN_TRUE; } diff --git a/kitty/screen.h b/kitty/screen.h index c14162090..ece3ed0a1 100644 --- a/kitty/screen.h +++ b/kitty/screen.h @@ -68,15 +68,20 @@ typedef struct { typedef struct { + PyObject *overlay_text; CPUCell *cpu_cells; GPUCell *gpu_cells; + index_type xstart, ynum, xnum, cursor_x; bool is_active; - index_type xstart, ynum, xnum; - + bool is_dirty; struct { - PyObject *overlay_text; - const char *func_name; - } save; + CPUCell *cpu_cells; + GPUCell *gpu_cells; + Cursor cursor; + } original_line; + struct { + index_type x, y; + } last_ime_pos; } OverlayLine; typedef struct { @@ -264,7 +269,8 @@ void screen_dirty_sprite_positions(Screen *self); void screen_rescale_images(Screen *self); void screen_report_size(Screen *, unsigned int which); void screen_manipulate_title_stack(Screen *, unsigned int op, unsigned int which); -void screen_draw_overlay_text(Screen *self, const char *utf8_text); +bool screen_is_overlay_active(Screen *self); +void screen_update_overlay_text(Screen *self, const char *utf8_text); void screen_set_key_encoding_flags(Screen *self, uint32_t val, uint32_t how); void screen_push_key_encoding_flags(Screen *self, uint32_t val); void screen_pop_key_encoding_flags(Screen *self, uint32_t num); diff --git a/kitty/shaders.c b/kitty/shaders.c index b4a1b7cbb..fb27a63c8 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -329,7 +329,7 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, c // Cursor position enum { BLOCK_IDX = 0, BEAM_IDX = NUM_UNDERLINE_STYLES + 3, UNDERLINE_IDX = NUM_UNDERLINE_STYLES + 4, UNFOCUSED_IDX = NUM_UNDERLINE_STYLES + 5 }; if (cursor->is_visible) { - rd->cursor_x = screen->cursor->x, rd->cursor_y = screen->cursor->y; + rd->cursor_x = cursor->x, rd->cursor_y = cursor->y; if (cursor->is_focused) { switch(cursor->shape) { default: @@ -341,11 +341,11 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, c } } else rd->cursor_fg_sprite_idx = UNFOCUSED_IDX; color_type cell_fg = rd->default_fg, cell_bg = rd->default_bg; - index_type cell_color_x = screen->cursor->x; - bool cursor_ok = screen->cursor->x < screen->columns && screen->cursor->y < screen->lines; + index_type cell_color_x = cursor->x; + bool cursor_ok = cursor->x < screen->columns && cursor->y < screen->lines; bool reversed = false; if (cursor_ok) { - linebuf_init_line(screen->linebuf, screen->cursor->y); + linebuf_init_line(screen->linebuf, cursor->y); colors_for_cell(screen->linebuf->line, screen->color_profile, &cell_color_x, &cell_fg, &cell_bg, &reversed); } if (IS_SPECIAL_COLOR(cursor_color)) {