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`) - 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`) - 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 static bool
collect_cursor_info(CursorRenderInfo *ans, Window *w, monotonic_t now, OSWindow *os_window) { collect_cursor_info(CursorRenderInfo *ans, Window *w, monotonic_t now, OSWindow *os_window) {
ScreenRenderData *rd = &w->render_data; ScreenRenderData *rd = &w->render_data;
Cursor *cursor = rd->screen->cursor; Cursor *cursor;
ans->x = cursor->x; ans->y = cursor->y; 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; ans->is_visible = false;
if (rd->screen->scrolled_by || !screen_is_cursor_visible(rd->screen)) return cursor_needs_render(w); 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; 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) { 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 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; unsigned int left = w->geometry.left, top = w->geometry.top;
left += screen->cursor->x * cell_width; if (screen_is_overlay_active(screen)) {
top += screen->cursor->y * cell_height; 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 }; 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; 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); glfwUpdateIMEState(global_state.callback_os_window->handle, &ev);
@ -154,12 +159,12 @@ on_key_input(GLFWkeyevent *ev) {
case GLFW_IME_WAYLAND_DONE_EVENT: case GLFW_IME_WAYLAND_DONE_EVENT:
// If we update IME position here it sends GNOME's text input system into // 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 // 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"); debug("handled wayland IME done event\n");
return; return;
case GLFW_IME_PREEDIT_CHANGED: case GLFW_IME_PREEDIT_CHANGED:
screen_update_overlay_text(screen, text);
update_ime_position(w, screen); update_ime_position(w, screen);
screen_draw_overlay_text(screen, text);
debug("updated pre-edit text: '%s'\n", text); debug("updated pre-edit text: '%s'\n", text);
return; return;
case GLFW_IME_COMMIT_TEXT: case GLFW_IME_COMMIT_TEXT:
@ -167,7 +172,7 @@ on_key_input(GLFWkeyevent *ev) {
schedule_write_to_child(w->id, 1, text, strlen(text)); schedule_write_to_child(w->id, 1, text, strlen(text));
debug("committed pre-edit text: %s\n", text); debug("committed pre-edit text: %s\n", text);
} else debug("committed pre-edit text: (null)\n"); } else debug("committed pre-edit text: (null)\n");
screen_draw_overlay_text(screen, NULL); screen_update_overlay_text(screen, NULL);
return; return;
case GLFW_IME_NONE: case GLFW_IME_NONE:
// for macOS, update ime position on every key input // for macOS, update ime position on every key input

View File

@ -23,6 +23,7 @@
#include "unicode-data.h" #include "unicode-data.h"
#include "modes.h" #include "modes.h"
#include "wcwidth-std.h" #include "wcwidth-std.h"
#include "wcswidth.h"
#include "control-codes.h" #include "control-codes.h"
#include "charsets.h" #include "charsets.h"
#include "keys.h" #include "keys.h"
@ -49,25 +50,37 @@ init_tabstops(bool *tabstops, index_type count) {
} }
static bool 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.cpu_cells);
PyMem_Free(self->overlay_line.gpu_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.cpu_cells = PyMem_Calloc(columns, sizeof(CPUCell));
self->overlay_line.gpu_cells = PyMem_Calloc(columns, sizeof(GPUCell)); 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; PyErr_NoMemory(); return false;
} }
self->overlay_line.is_active = false; if (!keep_active) {
self->overlay_line.xnum = 0; 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.ynum = 0;
self->overlay_line.xstart = 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; 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 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 \ #define RESET_CHARSETS \
self->g0_charset = translation_table(0); \ 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->main_tabstops, self->columns);
init_tabstops(self->alt_tabstops, self->columns); init_tabstops(self->alt_tabstops, self->columns);
self->key_encoding_flags = self->main_key_encoding_flags; self->key_encoding_flags = self->main_key_encoding_flags;
if (!init_overlay_line(self, self->columns)) { Py_CLEAR(self); return NULL; } if (!init_overlay_line(self, self->columns, false)) { Py_CLEAR(self); return NULL; }
clear_saved_overlay_line(self);
self->hyperlink_pool = alloc_hyperlink_pool(); self->hyperlink_pool = alloc_hyperlink_pool();
if (!self->hyperlink_pool) { Py_CLEAR(self); return PyErr_NoMemory(); } if (!self->hyperlink_pool) { Py_CLEAR(self); return PyErr_NoMemory(); }
self->as_ansi_buf.hyperlink_pool = self->hyperlink_pool; self->as_ansi_buf.hyperlink_pool = self->hyperlink_pool;
@ -158,8 +170,11 @@ static Line* range_line_(Screen *self, int y);
void void
screen_reset(Screen *self) { screen_reset(Screen *self) {
if (self->linebuf == self->alt_linebuf) screen_toggle_screen_buffer(self, true, true); if (self->linebuf == self->alt_linebuf) screen_toggle_screen_buffer(self, true, true);
if (self->overlay_line.is_active) deactivate_overlay_line(self); if (screen_is_overlay_active(self)) {
clear_saved_overlay_line(self); deactivate_overlay_line(self);
// Cancel IME composition
update_ime_position_for_window(self->window_id, false, -1);
}
Py_CLEAR(self->last_reported_cwd); Py_CLEAR(self->last_reported_cwd);
self->render_unfocused_cursor = false; self->render_unfocused_cursor = false;
memset(self->main_key_encoding_flags, 0, sizeof(self->main_key_encoding_flags)); memset(self->main_key_encoding_flags, 0, sizeof(self->main_key_encoding_flags));
@ -336,7 +351,6 @@ found:
static bool static bool
screen_resize(Screen *self, unsigned int lines, unsigned int columns) { 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); lines = MAX(1u, lines); columns = MAX(1u, columns);
bool is_main = self->linebuf == self->main_linebuf; 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; \ which.num_content_lines = num_content_lines_after; \
} }
// Resize overlay line // Resize overlay line
if (!init_overlay_line(self, columns)) return false; if (!init_overlay_line(self, columns, true)) return false;
// Resize main linebuf // Resize main linebuf
HistoryBuf *nh = realloc_hb(self->historybuf, self->historybuf->ynum, columns, &self->as_ansi_buf); 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->linebuf->line->cpu_cells[0].ch = 0;
self->cursor->x = 0; self->cursor->x = 0;
} }
restore_overlay_line(self);
return true; return true;
} }
@ -463,7 +476,9 @@ dealloc(Screen* self) {
Py_CLEAR(self->marker); Py_CLEAR(self->marker);
PyMem_Free(self->overlay_line.cpu_cells); PyMem_Free(self->overlay_line.cpu_cells);
PyMem_Free(self->overlay_line.gpu_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); PyMem_Free(self->main_tabstops);
free(self->pending_mode.buf); free(self->pending_mode.buf);
free(self->selections.items); free(self->selections.items);
@ -661,7 +676,6 @@ draw_combining_char(Screen *self, char_type ch) {
} }
} }
static void static void
draw_codepoint(Screen *self, char_type och, bool from_input_stream) { draw_codepoint(Screen *self, char_type och, bool from_input_stream) {
if (is_ignored_char(och)) return; 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); 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 void
screen_draw(Screen *self, uint32_t och, bool from_input_stream) { screen_draw(Screen *self, uint32_t och, bool from_input_stream) {
MOVE_OVERLAY_LINE_WITH_CURSOR;
draw_codepoint(self, och, from_input_stream); draw_codepoint(self, och, from_input_stream);
} }
void void
screen_align(Screen *self) { screen_align(Screen *self) {
self->margin_top = 0; self->margin_bottom = self->lines - 1; self->margin_top = 0; self->margin_bottom = self->lines - 1;
@ -819,7 +757,6 @@ screen_alignment_display(Screen *self) {
void void
select_graphic_rendition(Screen *self, int *params, unsigned int count, Region *region_) { 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_) { if (region_) {
Region region = *region_; Region region = *region_;
if (!region.top) region.top = 1; 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; self->modes.mDECCKM = val;
break; break;
case DECTCEM: case DECTCEM:
if(!val) { self->modes.mDECTCEM = 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);
}
break; break;
case DECSCNM: case DECSCNM:
// Render screen in reverse video // Render screen in reverse video
@ -1191,7 +1121,6 @@ screen_backspace(Screen *self) {
void void
screen_tab(Screen *self) { 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. // Move to the next tab space, or the end of the screen if there aren't anymore left.
unsigned int found = 0; unsigned int found = 0;
for (unsigned int i = self->cursor->x + 1; i < self->columns; i++) { for (unsigned int i = self->cursor->x + 1; i < self->columns; i++) {
@ -1223,7 +1152,6 @@ screen_tab(Screen *self) {
void void
screen_backtab(Screen *self, unsigned int count) { screen_backtab(Screen *self, unsigned int count) {
MOVE_OVERLAY_LINE_WITH_CURSOR;
// Move back count tabs // Move back count tabs
if (!count) count = 1; if (!count) count = 1;
int i; int i;
@ -1261,7 +1189,6 @@ screen_set_tab_stop(Screen *self) {
void void
screen_cursor_back(Screen *self, unsigned int count/*=1*/, int move_direction/*=-1*/) { screen_cursor_back(Screen *self, unsigned int count/*=1*/, int move_direction/*=-1*/) {
MOVE_OVERLAY_LINE_WITH_CURSOR;
if (count == 0) count = 1; if (count == 0) count = 1;
if (move_direction < 0 && count > self->cursor->x) self->cursor->x = 0; if (move_direction < 0 && count > self->cursor->x) self->cursor->x = 0;
else self->cursor->x += move_direction * count; else self->cursor->x += move_direction * count;
@ -1275,13 +1202,12 @@ screen_cursor_forward(Screen *self, unsigned int count/*=1*/) {
void void
screen_cursor_up(Screen *self, unsigned int count/*=1*/, bool do_carriage_return/*=false*/, int move_direction/*=-1*/) { 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); bool in_margins = cursor_within_margins(self);
if (count == 0) count = 1; if (count == 0) count = 1;
if (move_direction < 0 && count > self->cursor->y) self->cursor->y = 0; if (move_direction < 0 && count > self->cursor->y) self->cursor->y = 0;
else self->cursor->y += move_direction * count; else self->cursor->y += move_direction * count;
screen_ensure_bounds(self, true, in_margins);
if (do_carriage_return) self->cursor->x = 0; if (do_carriage_return) self->cursor->x = 0;
screen_ensure_bounds(self, true, in_margins);
} }
void void
@ -1301,7 +1227,6 @@ screen_cursor_down1(Screen *self, unsigned int count/*=1*/) {
void void
screen_cursor_to_column(Screen *self, unsigned int column) { screen_cursor_to_column(Screen *self, unsigned int column) {
MOVE_OVERLAY_LINE_WITH_CURSOR;
unsigned int x = MAX(column, 1u) - 1; unsigned int x = MAX(column, 1u) - 1;
if (x != self->cursor->x) { if (x != self->cursor->x) {
self->cursor->x = x; self->cursor->x = x;
@ -1328,7 +1253,6 @@ screen_cursor_to_column(Screen *self, unsigned int column) {
void void
screen_index(Screen *self) { screen_index(Screen *self) {
MOVE_OVERLAY_LINE_WITH_CURSOR;
// Move cursor down one line, scrolling screen if needed // Move cursor down one line, scrolling screen if needed
unsigned int top = self->margin_top, bottom = self->margin_bottom; unsigned int top = self->margin_top, bottom = self->margin_bottom;
if (self->cursor->y == bottom) { if (self->cursor->y == bottom) {
@ -1338,7 +1262,6 @@ screen_index(Screen *self) {
void void
screen_scroll(Screen *self, unsigned int count) { screen_scroll(Screen *self, unsigned int count) {
MOVE_OVERLAY_LINE_WITH_CURSOR;
// Scroll the screen up by count lines, not moving the cursor // Scroll the screen up by count lines, not moving the cursor
unsigned int top = self->margin_top, bottom = self->margin_bottom; unsigned int top = self->margin_top, bottom = self->margin_bottom;
while (count > 0) { while (count > 0) {
@ -1349,7 +1272,6 @@ screen_scroll(Screen *self, unsigned int count) {
void void
screen_reverse_index(Screen *self) { screen_reverse_index(Screen *self) {
MOVE_OVERLAY_LINE_WITH_CURSOR;
// Move cursor up one line, scrolling screen if needed // Move cursor up one line, scrolling screen if needed
unsigned int top = self->margin_top, bottom = self->margin_bottom; unsigned int top = self->margin_top, bottom = self->margin_bottom;
if (self->cursor->y == top) { if (self->cursor->y == top) {
@ -1359,7 +1281,6 @@ screen_reverse_index(Screen *self) {
static void static void
_reverse_scroll(Screen *self, unsigned int count, bool fill_from_scrollback) { _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 // Scroll the screen down by count lines, not moving the cursor
unsigned int top = self->margin_top, bottom = self->margin_bottom; unsigned int top = self->margin_top, bottom = self->margin_bottom;
fill_from_scrollback = fill_from_scrollback && self->linebuf == self->main_linebuf; 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 void
screen_carriage_return(Screen *self) { screen_carriage_return(Screen *self) {
if (self->cursor->x != 0) { if (self->cursor->x != 0) {
MOVE_OVERLAY_LINE_WITH_CURSOR;
self->cursor->x = 0; 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(BRACKETED_PASTE)
SIMPLE_MODE(FOCUS_TRACKING) SIMPLE_MODE(FOCUS_TRACKING)
SIMPLE_MODE(DECCKM) SIMPLE_MODE(DECCKM)
SIDE_EFFECTS(DECTCEM) // side effect: redraw IME overlay line SIMPLE_MODE(DECTCEM)
SIMPLE_MODE(DECAWM) SIMPLE_MODE(DECAWM)
case MOUSE_BUTTON_TRACKING: case MOUSE_MOTION_TRACKING: case MOUSE_MOVE_TRACKING: case MOUSE_BUTTON_TRACKING: case MOUSE_MOTION_TRACKING: case MOUSE_MOVE_TRACKING:
dest->mouse_tracking_mode = src->mouse_tracking_mode; break; dest->mouse_tracking_mode = src->mouse_tracking_mode; break;
@ -1504,7 +1424,6 @@ screen_save_modes(Screen *self) {
void void
screen_restore_cursor(Screen *self) { screen_restore_cursor(Screen *self) {
MOVE_OVERLAY_LINE_WITH_CURSOR;
Savepoint *sp = self->linebuf == self->main_linebuf ? &self->main_savepoint : &self->alt_savepoint; Savepoint *sp = self->linebuf == self->main_linebuf ? &self->main_savepoint : &self->alt_savepoint;
if (!sp->is_valid) { if (!sp->is_valid) {
screen_cursor_position(self, 1, 1); screen_cursor_position(self, 1, 1);
@ -1542,7 +1461,6 @@ screen_ensure_bounds(Screen *self, bool force_use_margins/*=false*/, bool in_mar
void void
screen_cursor_position(Screen *self, unsigned int line, unsigned int column) { screen_cursor_position(Screen *self, unsigned int line, unsigned int column) {
MOVE_OVERLAY_LINE_WITH_CURSOR;
bool in_margins = cursor_within_margins(self); bool in_margins = cursor_within_margins(self);
line = (line == 0 ? 1 : line) - 1; line = (line == 0 ? 1 : line) - 1;
column = (column == 0 ? 1: column) - 1; column = (column == 0 ? 1: column) - 1;
@ -1737,6 +1655,7 @@ screen_insert_lines(Screen *self, unsigned int count) {
static void static void
screen_scroll_until_cursor_prompt(Screen *self) { screen_scroll_until_cursor_prompt(Screen *self) {
bool in_margins = cursor_within_margins(self);
int q = screen_cursor_at_a_shell_prompt(self); int q = screen_cursor_at_a_shell_prompt(self);
unsigned int y = q > -1 ? (unsigned int)q : self->cursor->y; unsigned int y = q > -1 ? (unsigned int)q : self->cursor->y;
unsigned int num_lines_to_scroll = MIN(self->margin_bottom, 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; self->cursor->y = self->margin_bottom;
while (num_lines_to_scroll--) screen_index(self); while (num_lines_to_scroll--) screen_index(self);
self->cursor->y = final_y; self->cursor->y = final_y;
screen_ensure_bounds(self, false, in_margins);
} }
void void
@ -1785,7 +1705,6 @@ screen_repeat_character(Screen *self, unsigned int count) {
void void
screen_delete_characters(Screen *self, unsigned int count) { screen_delete_characters(Screen *self, unsigned int count) {
MOVE_OVERLAY_LINE_WITH_CURSOR;
// Delete characters, later characters are moved left // Delete characters, later characters are moved left
const unsigned int bottom = self->lines ? self->lines - 1 : 0; const unsigned int bottom = self->lines ? self->lines - 1 : 0;
if (count == 0) count = 1; if (count == 0) count = 1;
@ -1803,7 +1722,6 @@ screen_delete_characters(Screen *self, unsigned int count) {
void void
screen_erase_characters(Screen *self, unsigned int count) { screen_erase_characters(Screen *self, unsigned int count) {
MOVE_OVERLAY_LINE_WITH_CURSOR;
// Delete characters replacing them by spaces // Delete characters replacing them by spaces
if (count == 0) count = 1; if (count == 0) count = 1;
unsigned int x = self->cursor->x; unsigned int x = self->cursor->x;
@ -2280,11 +2198,13 @@ screen_has_marker(Screen *self) {
void void
screen_update_cell_data(Screen *self, void *address, FONTS_DATA_HANDLE fonts_data, bool cursor_has_moved) { 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; unsigned int history_line_added_count = self->history_line_added_count;
index_type lnum; index_type lnum;
bool was_dirty = self->is_dirty; 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); 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; self->scroll_changed = false;
for (index_type y = 0; y < MIN(self->lines, self->scrolled_by); y++) { for (index_type y = 0; y < MIN(self->lines, self->scrolled_by); y++) {
lnum = self->scrolled_by - 1 - 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))) { (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); 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 (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); linebuf_mark_line_clean(self->linebuf, lnum);
} }
update_line_data(self->linebuf->line, y, address); 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); if (was_dirty) clear_selection(&self->url_ranges);
} }
@ -2696,22 +2623,6 @@ screen_open_url(Screen *self) {
return found; 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 {{{ // URLs {{{
@ -2792,7 +2703,136 @@ screen_detect_url(Screen *screen, unsigned int x, unsigned int y) {
return has_url ? -1 : 0; 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) { if (has_focus != previous) {
self->has_focus = has_focus; self->has_focus = has_focus;
if (has_focus) self->has_activity_since_last_focus = false; 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"); if (self->modes.mFOCUS_TRACKING) write_escape_code_to_child(self, CSI, has_focus ? "I" : "O");
Py_RETURN_TRUE; Py_RETURN_TRUE;
} }

View File

@ -68,15 +68,20 @@ typedef struct {
typedef struct { typedef struct {
PyObject *overlay_text;
CPUCell *cpu_cells; CPUCell *cpu_cells;
GPUCell *gpu_cells; GPUCell *gpu_cells;
index_type xstart, ynum, xnum, cursor_x;
bool is_active; bool is_active;
index_type xstart, ynum, xnum; bool is_dirty;
struct { struct {
PyObject *overlay_text; CPUCell *cpu_cells;
const char *func_name; GPUCell *gpu_cells;
} save; Cursor cursor;
} original_line;
struct {
index_type x, y;
} last_ime_pos;
} OverlayLine; } OverlayLine;
typedef struct { typedef struct {
@ -264,7 +269,8 @@ void screen_dirty_sprite_positions(Screen *self);
void screen_rescale_images(Screen *self); void screen_rescale_images(Screen *self);
void screen_report_size(Screen *, unsigned int which); void screen_report_size(Screen *, unsigned int which);
void screen_manipulate_title_stack(Screen *, unsigned int op, 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_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_push_key_encoding_flags(Screen *self, uint32_t val);
void screen_pop_key_encoding_flags(Screen *self, uint32_t num); 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 // Cursor position
enum { BLOCK_IDX = 0, BEAM_IDX = NUM_UNDERLINE_STYLES + 3, UNDERLINE_IDX = NUM_UNDERLINE_STYLES + 4, UNFOCUSED_IDX = NUM_UNDERLINE_STYLES + 5 }; 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) { 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) { if (cursor->is_focused) {
switch(cursor->shape) { switch(cursor->shape) {
default: 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; } else rd->cursor_fg_sprite_idx = UNFOCUSED_IDX;
color_type cell_fg = rd->default_fg, cell_bg = rd->default_bg; color_type cell_fg = rd->default_fg, cell_bg = rd->default_bg;
index_type cell_color_x = screen->cursor->x; index_type cell_color_x = cursor->x;
bool cursor_ok = screen->cursor->x < screen->columns && screen->cursor->y < screen->lines; bool cursor_ok = cursor->x < screen->columns && cursor->y < screen->lines;
bool reversed = false; bool reversed = false;
if (cursor_ok) { 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); colors_for_cell(screen->linebuf->line, screen->color_profile, &cell_color_x, &cell_fg, &cell_bg, &reversed);
} }
if (IS_SPECIAL_COLOR(cursor_color)) { if (IS_SPECIAL_COLOR(cursor_color)) {