Dont rewrap text in the alternate screen buffer

Avoids flicker during live resize with no resize_debounce_time. See
https://github.com/kovidgoyal/kitty/discussions/9142 for discussion.
This commit is contained in:
Kovid Goyal
2025-11-26 10:29:12 +05:30
parent 9e918547e8
commit 6db24b66fa
5 changed files with 109 additions and 3 deletions

View File

@@ -174,6 +174,9 @@ Detailed list of changes
- macOS: Fix closing an OS Window when another OS Window is minimized causing
the minimized window to be un-minimized (:iss:`8913`)
- Do not rewrap the text in the alternate screen buffer. Avoids flicker during
live resize with no :opt:`resize_debounce_time` (:disc:`9142`)
0.44.0 [2025-11-03]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -311,3 +311,83 @@ resize_screen_buffers(LineBuf *lb, HistoryBuf *hb, index_type lines, index_type
ans.ok = true;
return ans;
}
static void
nuke_in_line(CPUCell *cp, GPUCell *gp, index_type start, index_type x_limit) {
for (index_type x = start; x < x_limit; x++) {
cell_set_char(cp + x, 0); cp[x].is_multicell = false;
clear_sprite_position(gp[x]);
}
}
static void
nuke_multicell_char_at(LineBuf *lb, index_type x_, index_type y_) {
CPUCell *cp; GPUCell *gp;
linebuf_init_cells(lb, y_, &cp, &gp);
index_type num_lines_above = cp[x_].y;
index_type y_max_limit = MIN(lb->ynum, y_ + cp[x_].scale - num_lines_above);
while (cp[x_].x && x_ > 0) x_--;
index_type x_limit = MIN(lb->xnum, x_ + mcd_x_limit(&cp[x_]));
for (index_type y = y_; y < y_max_limit; y++) {
linebuf_init_cells(lb, y, &cp, &gp);
nuke_in_line(cp, gp, x_, x_limit);
}
for (int y = (int)y_ - 1; y > -1 && num_lines_above; y--, num_lines_above--) {
linebuf_init_cells(lb, y, &cp, &gp);
nuke_in_line(cp, gp, x_, x_limit);
}
}
ResizeResult
resize_screen_buffer_without_rewrap(LineBuf *lb, index_type lines, index_type columns, TrackCursor *cursors) {
ResizeResult ans = {0};
ans.lb = alloc_linebuf(lines, columns, lb->text_cache);
if (!ans.lb) return ans;
Rewrap r = { .src = {.lb=lb},};
exclude_empty_lines_at_bottom(&r);
ans.num_content_lines_before = r.num_content_lines_before;
ans.num_content_lines_after = MIN(lines, r.num_content_lines_before);
index_type xcommon = MIN(lb->xnum, ans.lb->xnum);
for (index_type y = 0; y < ans.num_content_lines_after; y++) {
linebuf_init_line(lb, y); linebuf_init_line(ans.lb, y);
ans.lb->line_attrs[y] = lb->line_attrs[y]; ans.lb->line_attrs[y].has_dirty_text = true;
memcpy(ans.lb->line->cpu_cells, lb->line->cpu_cells, xcommon * sizeof(lb->line->cpu_cells[0]));
memcpy(ans.lb->line->gpu_cells, lb->line->gpu_cells, xcommon * sizeof(lb->line->gpu_cells[0]));
if (xcommon > lb->line->xnum) {
// extend the colors/styles of the last cell to edge
GPUCell e = lb->line->gpu_cells[xcommon-1]; clear_sprite_position(e);
for (index_type x = xcommon; x < ans.lb->line->xnum; x++) ans.lb->line->gpu_cells[x] = e;
} else if (xcommon < lb->line->xnum) {
// remove multicell chars that were split at the right edge
index_type last_x = xcommon - 1;
CPUCell *c = ans.lb->line->cpu_cells + last_x;
if (c->is_multicell && c->x + 1u < mcd_x_limit(c)) {
while (ans.lb->line->cpu_cells[last_x].x && last_x > 0) last_x--;
nuke_in_line(ans.lb->line->cpu_cells, ans.lb->line->gpu_cells, last_x, ans.lb->line->xnum);
}
}
}
// Set bg color for extra lines at bottom
if (ans.num_content_lines_before < lines) {
linebuf_init_line(lb, lb->ynum-1); GPUCell *g = lb->line->gpu_cells;
for (index_type y = ans.num_content_lines_after; y < ans.lb->ynum; y++) {
linebuf_init_line(ans.lb, y);
for (index_type x = 0; x < ans.lb->xnum; x++) ans.lb->line->gpu_cells[x].bg = g->bg;
}
} else if (ans.num_content_lines_after < ans.num_content_lines_before) {
// delete multicell chars split at the bottom
linebuf_init_line(ans.lb, ans.num_content_lines_after-1);
for (index_type x = 0; x < ans.lb->xnum; x++) {
CPUCell *c = ans.lb->line->cpu_cells + x;
if (c->is_multicell && c->y < c->scale-1) nuke_multicell_char_at(ans.lb, x, ans.num_content_lines_after-1);
}
}
for (TrackCursor *tc = cursors; !tc->is_sentinel; tc++) {
tc->dest_x = MIN(tc->x, ans.lb->xnum-1);
tc->dest_y = MIN(tc->y, ans.lb->ynum-1);
}
ans.ok = true;
return ans;
}

View File

@@ -23,3 +23,5 @@ typedef struct ResizeResult {
ResizeResult
resize_screen_buffers(LineBuf *lb, HistoryBuf *hb, index_type lines, index_type columns, ANSIBuf *as_ansi_buf, TrackCursor *cursors);
ResizeResult
resize_screen_buffer_without_rewrap(LineBuf *lb, index_type lines, index_type columns, TrackCursor *cursors);

View File

@@ -249,10 +249,9 @@ rewrap(Screen *screen, unsigned int lines, unsigned int columns, index_type *ncl
cursors[0] = (TrackCursor){.x=alt_saved_cursor->before.x, .y=alt_saved_cursor->before.y};
if (!main_is_active) cursors[1] = (TrackCursor){.x=cursor->before.x, .y=cursor->before.y};
else cursors[1].is_sentinel = true;
ResizeResult ar = resize_screen_buffers(screen->alt_linebuf, NULL, lines, columns, &screen->as_ansi_buf, cursors);
ResizeResult ar = resize_screen_buffer_without_rewrap(screen->alt_linebuf, lines, columns, cursors);
if (!ar.ok) {
Py_DecRef((PyObject*)mr.lb); Py_DecRef((PyObject*)mr.hb);
PyErr_NoMemory(); return false;
Py_DecRef((PyObject*)ar.lb); PyErr_NoMemory(); return false;
}
alt_saved_cursor->temp.x = cursors[0].dest_x; alt_saved_cursor->temp.y = cursors[0].dest_y;
if (!main_is_active) { cursor->temp.x = cursors[1].dest_x; cursor->temp.y = cursors[1].dest_y; }

View File

@@ -643,6 +643,28 @@ def test_multicell(self: TestMulticell) -> None:
ac(x, -1, is_multicell=True, x=x-2, y=0, text='' if x > 2 else 'B')
ac(x, 0, is_multicell=True, x=x-2, y=1, text='')
# resize without rewrap (alt screen)
s = self.create_screen(lines=6, cols=6)
s.toggle_alt_screen()
def reset_alt():
s.reset()
s.resize(6, 6)
s.toggle_alt_screen()
multicell(s, 'XY', scale=3)
s.resize(6, 5)
for y in range(3):
for x in range(3):
ac(x, y, is_multicell=True, text='X' if x == 0 and y == 0 else '')
for x in range(3, 5):
ac(x, y, is_multicell=False, text='')
reset_alt()
multicell(s, 'XY', scale=3)
s.resize(2, 6)
for y in range(s.lines):
for x in range(s.columns):
ac(x, y, is_multicell=False, text='')
# selections
s = self.create_screen(lines=5, cols=8)