IME: Render overlay at the last visible cursor position with a separate cursor

Fix the problem caused by wrong cursor coordinates. No more messing with
the main cursor, instead the cursor is saved when receiving a pre-edit
text update and used for drawing later.

Update the overlay to the last visible cursor position before rendering
to ensure it always moves with the cursor. Finally, draw the overlay
after line rendering is complete, and restore the line buffer after
updating the rendered data to ensure that the line text being read is
correct at all times.

This also improves performance by only rendering once when changes are
made, eliminating the need to repeatedly disable and draw after various
commands and not even comprehensively.
This commit is contained in:
pagedown 2023-02-22 22:36:06 +08:00
parent de188faf55
commit 126aaddccb
No known key found for this signature in database
GPG Key ID: E921CF18AC8FF6EB
6 changed files with 213 additions and 154 deletions

View File

@ -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`)

View File

@ -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;

View File

@ -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

View File

@ -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;
}

View File

@ -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);

View File

@ -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)) {