This commit is contained in:
Kovid Goyal 2023-02-23 20:50:25 +05:30
commit dba8d278cb
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
13 changed files with 279 additions and 172 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

@ -1454,15 +1454,11 @@ is_ascii_control_char(char x) {
}
void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
[w->ns.view updateIMEStateFor: ev->type focused:(bool)ev->focused left:(CGFloat)ev->cursor.left top:(CGFloat)ev->cursor.top cellWidth:(CGFloat)ev->cursor.width cellHeight:(CGFloat)ev->cursor.height];
[w->ns.view updateIMEStateFor: ev->type focused:(bool)ev->focused];
}
- (void)updateIMEStateFor:(GLFWIMEUpdateType)which
focused:(bool)focused
left:(CGFloat)left
top:(CGFloat)top
cellWidth:(CGFloat)cellWidth
cellHeight:(CGFloat)cellHeight
{
if (which == GLFW_IME_UPDATE_FOCUS && !focused && [self hasMarkedText] && window) {
[input_context discardMarkedText];
@ -1472,16 +1468,7 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
_glfw.ns.text[0] = 0;
}
if (which != GLFW_IME_UPDATE_CURSOR_POSITION) return;
left /= window->ns.xscale;
top /= window->ns.yscale;
cellWidth /= window->ns.xscale;
cellHeight /= window->ns.yscale;
debug_key("updateIMEPosition: left=%f, top=%f, width=%f, height=%f\n", left, top, cellWidth, cellHeight);
const NSRect frame = [window->ns.view frame];
const NSRect rectInView = NSMakeRect(left,
frame.size.height - top - cellHeight,
cellWidth, cellHeight);
markedRect = [window->ns.object convertRectToScreen: rectInView];
if (_glfwPlatformWindowFocused(window)) [[window->ns.view inputContext] invalidateCharacterCoordinates];
}
@ -1507,6 +1494,21 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
actualRange:(NSRangePointer)actualRange
{
(void)range; (void)actualRange;
if (_glfw.callbacks.get_ime_cursor_position) {
GLFWIMEUpdateEvent ev = { .type = GLFW_IME_UPDATE_CURSOR_POSITION };
if (_glfw.callbacks.get_ime_cursor_position((GLFWwindow*)window, &ev)) {
const CGFloat left = (CGFloat)ev.cursor.left / window->ns.xscale;
const CGFloat top = (CGFloat)ev.cursor.top / window->ns.yscale;
const CGFloat cellWidth = (CGFloat)ev.cursor.width / window->ns.xscale;
const CGFloat cellHeight = (CGFloat)ev.cursor.height / window->ns.yscale;
debug_key("updateIMEPosition: left=%f, top=%f, width=%f, height=%f\n", left, top, cellWidth, cellHeight);
const NSRect frame = [window->ns.view frame];
const NSRect rectInView = NSMakeRect(left,
frame.size.height - top - cellHeight,
cellWidth, cellHeight);
markedRect = [window->ns.object convertRectToScreen: rectInView];
}
}
return markedRect;
}

2
glfw/glfw3.h vendored
View File

@ -1732,6 +1732,7 @@ typedef enum {
} GLFWClipboardType;
typedef GLFWDataChunk (* GLFWclipboarditerfun)(const char *mime_type, void *iter, GLFWClipboardType ctype);
typedef bool (* GLFWclipboardwritedatafun)(void *object, const char *data, size_t sz);
typedef bool (* GLFWimecursorpositionfun)(GLFWwindow *window, GLFWIMEUpdateEvent *ev);
/*! @brief Video mode type.
*
@ -1891,6 +1892,7 @@ GLFWAPI void glfwRemoveTimer(unsigned long long);
GLFWAPI GLFWdrawtextfun glfwSetDrawTextFunction(GLFWdrawtextfun function);
GLFWAPI GLFWcurrentselectionfun glfwSetCurrentSelectionCallback(GLFWcurrentselectionfun callback);
GLFWAPI GLFWhascurrentselectionfun glfwSetHasCurrentSelectionCallback(GLFWhascurrentselectionfun callback);
GLFWAPI GLFWimecursorpositionfun glfwSetIMECursorPositionCallback(GLFWimecursorpositionfun callback);
/*! @brief Terminates the GLFW library.
*

7
glfw/init.c vendored
View File

@ -402,3 +402,10 @@ GLFWAPI GLFWhascurrentselectionfun glfwSetHasCurrentSelectionCallback(GLFWhascur
_GLFW_SWAP_POINTERS(_glfw.callbacks.has_current_selection, cbfun);
return cbfun;
}
GLFWAPI GLFWimecursorpositionfun glfwSetIMECursorPositionCallback(GLFWimecursorpositionfun cbfun)
{
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
_GLFW_SWAP_POINTERS(_glfw.callbacks.get_ime_cursor_position, cbfun);
return cbfun;
}

1
glfw/internal.h vendored
View File

@ -635,6 +635,7 @@ struct _GLFWlibrary
GLFWdrawtextfun draw_text;
GLFWcurrentselectionfun get_current_selection;
GLFWhascurrentselectionfun has_current_selection;
GLFWimecursorpositionfun get_ime_cursor_position;
} callbacks;

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;

3
kitty/glfw-wrapper.c generated
View File

@ -44,6 +44,9 @@ load_glfw(const char* path) {
*(void **) (&glfwSetHasCurrentSelectionCallback_impl) = dlsym(handle, "glfwSetHasCurrentSelectionCallback");
if (glfwSetHasCurrentSelectionCallback_impl == NULL) fail("Failed to load glfw function glfwSetHasCurrentSelectionCallback with error: %s", dlerror());
*(void **) (&glfwSetIMECursorPositionCallback_impl) = dlsym(handle, "glfwSetIMECursorPositionCallback");
if (glfwSetIMECursorPositionCallback_impl == NULL) fail("Failed to load glfw function glfwSetIMECursorPositionCallback with error: %s", dlerror());
*(void **) (&glfwTerminate_impl) = dlsym(handle, "glfwTerminate");
if (glfwTerminate_impl == NULL) fail("Failed to load glfw function glfwTerminate with error: %s", dlerror());

5
kitty/glfw-wrapper.h generated
View File

@ -1470,6 +1470,7 @@ typedef enum {
} GLFWClipboardType;
typedef GLFWDataChunk (* GLFWclipboarditerfun)(const char *mime_type, void *iter, GLFWClipboardType ctype);
typedef bool (* GLFWclipboardwritedatafun)(void *object, const char *data, size_t sz);
typedef bool (* GLFWimecursorpositionfun)(GLFWwindow *window, GLFWIMEUpdateEvent *ev);
/*! @brief Video mode type.
*
@ -1667,6 +1668,10 @@ typedef GLFWhascurrentselectionfun (*glfwSetHasCurrentSelectionCallback_func)(GL
GFW_EXTERN glfwSetHasCurrentSelectionCallback_func glfwSetHasCurrentSelectionCallback_impl;
#define glfwSetHasCurrentSelectionCallback glfwSetHasCurrentSelectionCallback_impl
typedef GLFWimecursorpositionfun (*glfwSetIMECursorPositionCallback_func)(GLFWimecursorpositionfun);
GFW_EXTERN glfwSetIMECursorPositionCallback_func glfwSetIMECursorPositionCallback_impl;
#define glfwSetIMECursorPositionCallback glfwSetIMECursorPositionCallback_impl
typedef void (*glfwTerminate_func)(void);
GFW_EXTERN glfwTerminate_func glfwTerminate_impl;
#define glfwTerminate glfwTerminate_impl

View File

@ -507,6 +507,26 @@ has_current_selection(void) {
return ans;
}
void prepare_ime_position_update_event(OSWindow *osw, Window *w, Screen *screen, GLFWIMEUpdateEvent *ev);
static bool
get_ime_cursor_position(GLFWwindow *glfw_window, GLFWIMEUpdateEvent *ev) {
if (!set_callback_window(glfw_window)) return false;
bool ans = false;
OSWindow *osw = global_state.callback_os_window;
if (osw && osw->is_focused && is_window_ready_for_callbacks()) {
Tab *tab = osw->tabs + osw->active_tab;
Window *w = tab->windows + tab->active_window;
Screen *screen = w->render_data.screen;
if (screen) {
prepare_ime_position_update_event(osw, w, screen, ev);
ans = true;
}
}
global_state.callback_os_window = NULL;
return ans;
}
static void get_window_dpi(GLFWwindow *w, double *x, double *y);
@ -832,6 +852,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) {
glfwSetApplicationCloseCallback(application_close_requested_callback);
glfwSetCurrentSelectionCallback(get_current_selection);
glfwSetHasCurrentSelectionCallback(has_current_selection);
glfwSetIMECursorPositionCallback(get_ime_cursor_position);
#ifdef __APPLE__
cocoa_set_activation_policy(OPT(macos_hide_from_tasks));
glfwWindowHint(GLFW_COCOA_GRAPHICS_SWITCHING, true);

View File

@ -102,13 +102,25 @@ update_ime_focus(OSWindow *osw, bool focused) {
}
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;
prepare_ime_position_update_event(OSWindow *osw, Window *w, Screen *screen, GLFWIMEUpdateEvent *ev) {
unsigned int cell_width = osw->fonts_data->cell_width, cell_height = osw->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;
}
ev->cursor.left = left; ev->cursor.top = top; ev->cursor.width = cell_width; ev->cursor.height = cell_height;
}
void
update_ime_position(Window* w UNUSED, Screen *screen UNUSED) {
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;
#ifndef __APPLE__
prepare_ime_position_update_event(global_state.callback_os_window, w, screen, &ev);
#endif
glfwUpdateIMEState(global_state.callback_os_window->handle, &ev);
}
@ -154,12 +166,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 +179,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)) {