Refactor rewrap code, again

Now we do the rewrap of history and line buffers together. This is
faster and deals with multiline chars split between the two buffers
correctly. Also, considerably simpler code.
This commit is contained in:
Kovid Goyal 2024-12-31 11:28:11 +05:30
parent 97449dfddb
commit 561686c9d4
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
10 changed files with 437 additions and 476 deletions

View file

@ -8,7 +8,7 @@
#include "wcswidth.h"
#include "lineops.h"
#include "charsets.h"
#include "rewrap.h"
#include "resize.h"
#include <structmember.h>
#include "../3rdparty/ringbuf/ringbuf.h"
@ -599,6 +599,7 @@ INIT_TYPE(HistoryBuf)
HistoryBuf *alloc_historybuf(unsigned int lines, unsigned int columns, unsigned int pagerhist_sz, TextCache *tc) {
return create_historybuf(&HistoryBuf_Type, columns, lines, pagerhist_sz, tc);
}
// }}}
static void
@ -617,34 +618,47 @@ historybuf_next_dest_line(HistoryBuf *self, ANSIBuf *as_ansi_buf, Line *src_line
return dest_y + 1;
}
void
historybuf_rewrap(HistoryBuf *self, HistoryBuf *other, ANSIBuf *as_ansi_buf) {
while(other->num_segments < self->num_segments) add_segment(other);
if (other->xnum == self->xnum && other->ynum == self->ynum) {
// Fast path
for (index_type i = 0; i < self->num_segments; i++) {
memcpy(other->segments[i].cpu_cells, self->segments[i].cpu_cells, SEGMENT_SIZE * self->xnum * sizeof(CPUCell));
memcpy(other->segments[i].gpu_cells, self->segments[i].gpu_cells, SEGMENT_SIZE * self->xnum * sizeof(GPUCell));
memcpy(other->segments[i].line_attrs, self->segments[i].line_attrs, SEGMENT_SIZE * sizeof(LineAttrs));
}
other->count = self->count; other->start_of_data = self->start_of_data;
return;
}
if (other->pagerhist && other->xnum != self->xnum && ringbuf_bytes_used(other->pagerhist->ringbuf))
other->pagerhist->rewrap_needed = true;
other->count = 0; other->start_of_data = 0;
if (self->count > 0) {
historybuf_rewrap_inner(self, other, self->count, as_ansi_buf);
for (index_type i = 0; i < other->count; i++) attrptr(other, (other->start_of_data + i) % other->ynum)->has_dirty_text = true;
HistoryBuf*
historybuf_alloc_for_rewrap(unsigned int columns, HistoryBuf *self) {
if (!self) return NULL;
HistoryBuf *ans = alloc_historybuf(self->ynum, columns, 0, self->text_cache);
if (ans) {
while(ans->num_segments < self->num_segments) add_segment(ans);
ans->count = 0; ans->start_of_data = 0;
}
return ans;
}
void
historybuf_finish_rewrap(HistoryBuf *dest, HistoryBuf *src) {
for (index_type i = 0; i < dest->count; i++) attrptr(dest, (dest->start_of_data + i) % dest->ynum)->has_dirty_text = true;
dest->pagerhist = src->pagerhist; src->pagerhist = NULL;
if (dest->pagerhist && dest->xnum != src->xnum && ringbuf_bytes_used(dest->pagerhist->ringbuf)) dest->pagerhist->rewrap_needed = true;
}
void
historybuf_fast_rewrap(HistoryBuf *dest, HistoryBuf *src) {
for (index_type i = 0; i < src->num_segments; i++) {
memcpy(dest->segments[i].cpu_cells, src->segments[i].cpu_cells, SEGMENT_SIZE * src->xnum * sizeof(CPUCell));
memcpy(dest->segments[i].gpu_cells, src->segments[i].gpu_cells, SEGMENT_SIZE * src->xnum * sizeof(GPUCell));
memcpy(dest->segments[i].line_attrs, src->segments[i].line_attrs, SEGMENT_SIZE * sizeof(LineAttrs));
}
dest->count = src->count; dest->start_of_data = src->start_of_data;
}
static PyObject*
rewrap(HistoryBuf *self, PyObject *args) {
HistoryBuf *other;
if (!PyArg_ParseTuple(args, "O!", &HistoryBuf_Type, &other)) return NULL;
unsigned xnum;
if (!PyArg_ParseTuple(args, "I", &xnum)) return NULL;
ANSIBuf as_ansi_buf = {0};
historybuf_rewrap(self, other, &as_ansi_buf);
LineBuf *dummy = alloc_linebuf(4, self->xnum, self->text_cache);
if (!dummy) return PyErr_NoMemory();
RAII_PyObject(cleanup, (PyObject*)dummy); (void)cleanup;
TrackCursor cursors[1] = {{.is_sentinel=true}};
ResizeResult r = resize_screen_buffers(dummy, self, 8, xnum, &as_ansi_buf, cursors);
free(as_ansi_buf.buf);
Py_RETURN_NONE;
if (!r.ok) return PyErr_NoMemory();
Py_CLEAR(r.lb);
return (PyObject*)r.hb;
}

View file

@ -35,4 +35,7 @@ typedef struct {
HistoryBuf* alloc_historybuf(unsigned int, unsigned int, unsigned int, TextCache *tc);
HistoryBuf *historybuf_alloc_for_rewrap(unsigned int columns, HistoryBuf *self);
void historybuf_finish_rewrap(HistoryBuf *dest, HistoryBuf *src);
void historybuf_fast_rewrap(HistoryBuf *dest, HistoryBuf *src);
index_type historybuf_next_dest_line(HistoryBuf *self, ANSIBuf *as_ansi_buf, Line *src_line, index_type dest_y, Line *dest_line, bool continued);

View file

@ -7,7 +7,7 @@
#include "data-types.h"
#include "lineops.h"
#include "rewrap.h"
#include "resize.h"
#include <structmember.h>
@ -596,65 +596,16 @@ copy_old(LineBuf *self, PyObject *y) {
Py_RETURN_NONE;
}
void
linebuf_rewrap(
LineBuf *self, LineBuf *other, index_type *num_content_lines_before, index_type *num_content_lines_after,
HistoryBuf *historybuf, index_type *track_x, index_type *track_y, index_type *track_x2, index_type *track_y2,
ANSIBuf *as_ansi_buf, bool history_buf_last_line_is_split
) {
index_type first, i;
bool is_empty = true;
// Fast path
if (other->xnum == self->xnum && other->ynum == self->ynum) {
memcpy(other->line_map, self->line_map, sizeof(index_type) * self->ynum);
memcpy(other->line_attrs, self->line_attrs, sizeof(LineAttrs) * self->ynum);
memcpy(other->cpu_cell_buf, self->cpu_cell_buf, (size_t)self->xnum * self->ynum * sizeof(CPUCell));
memcpy(other->gpu_cell_buf, self->gpu_cell_buf, (size_t)self->xnum * self->ynum * sizeof(GPUCell));
*num_content_lines_before = self->ynum; *num_content_lines_after = self->ynum;
return;
}
// Find the first line that contains some content
first = self->ynum;
do {
first--;
CPUCell *cells = cpu_lineptr(self, self->line_map[first]);
for(i = 0; i < self->xnum; i++) {
if (cells[i].ch_or_idx || cells[i].ch_is_idx) { is_empty = false; break; }
}
} while(is_empty && first > 0);
if (is_empty) { // All lines are empty
*num_content_lines_after = 0;
*num_content_lines_before = 0;
return;
}
*num_content_lines_before = first + 1;
TrackCursor tcarr[3] = {
{.x = *track_x, .y = *track_y, .dest_x=*track_x, .dest_y = *track_y },
{.x = *track_x2, .y = *track_y2, .dest_x=*track_x2, .dest_y = *track_y2 },
{.is_sentinel = true}
};
*num_content_lines_after = 1 + linebuf_rewrap_inner(self, other, *num_content_lines_before, historybuf, (TrackCursor*)tcarr, as_ansi_buf, history_buf_last_line_is_split && historybuf != NULL);
*track_x = tcarr[0].dest_x; *track_y = tcarr[0].dest_y;
*track_x2 = tcarr[1].dest_x; *track_y2 = tcarr[1].dest_y;
for (i = 0; i < *num_content_lines_after; i++) other->line_attrs[i].has_dirty_text = true;
}
static PyObject*
rewrap(LineBuf *self, PyObject *args) {
LineBuf* other;
HistoryBuf *historybuf;
unsigned int nclb, ncla;
if (!PyArg_ParseTuple(args, "O!O!", &LineBuf_Type, &other, &HistoryBuf_Type, &historybuf)) return NULL;
index_type x = 0, y = 0, x2 = 0, y2 = 0;
unsigned int lines, columns;
if (!PyArg_ParseTuple(args, "II", &lines, &columns)) return NULL;
TrackCursor cursors[1] = {{.is_sentinel=true}};
ANSIBuf as_ansi_buf = {0};
linebuf_rewrap(self, other, &nclb, &ncla, historybuf, &x, &y, &x2, &y2, &as_ansi_buf, false);
ResizeResult r = resize_screen_buffers(self, NULL, lines, columns, &as_ansi_buf, cursors);
free(as_ansi_buf.buf);
return Py_BuildValue("II", nclb, ncla);
if (!r.ok) return PyErr_NoMemory();
return Py_BuildValue("NII", r.lb, r.num_content_lines_before, r.num_content_lines_after);
}

View file

@ -91,7 +91,6 @@ void linebuf_clear_line(LineBuf *self, index_type y, bool clear_attrs);
void linebuf_insert_lines(LineBuf *self, unsigned int num, unsigned int y, unsigned int bottom);
void linebuf_delete_lines(LineBuf *self, index_type num, index_type y, index_type bottom);
void linebuf_copy_line_to(LineBuf *, Line *, index_type);
void linebuf_rewrap(LineBuf *self, LineBuf *other, index_type *, index_type *, HistoryBuf *, index_type *, index_type *, index_type *, index_type *, ANSIBuf*, bool);
void linebuf_mark_line_dirty(LineBuf *self, index_type y);
void linebuf_clear_attrs_and_dirty(LineBuf *self, index_type y);
void linebuf_mark_line_clean(LineBuf *self, index_type y);
@ -102,7 +101,6 @@ bool linebuf_line_ends_with_continuation(LineBuf *self, index_type y);
void linebuf_refresh_sprite_positions(LineBuf *self);
void historybuf_add_line(HistoryBuf *self, const Line *line, ANSIBuf*);
bool historybuf_pop_line(HistoryBuf *, Line *);
void historybuf_rewrap(HistoryBuf *self, HistoryBuf *other, ANSIBuf*);
void historybuf_init_line(HistoryBuf *self, index_type num, Line *l);
bool history_buf_endswith_wrap(HistoryBuf *self);
CPUCell* historybuf_cpu_cells(HistoryBuf *self, index_type num);

317
kitty/resize.c Normal file
View file

@ -0,0 +1,317 @@
/*
* resize.c
* Copyright (C) 2024 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#include "resize.h"
#include "lineops.h"
typedef struct Rewrap {
struct {
LineBuf *lb;
HistoryBuf *hb;
index_type x, y, hb_count;
Line line, scratch_line;
} src, dest;
ANSIBuf *as_ansi_buf;
TrackCursor *cursors;
LineBuf *sb;
index_type num_content_lines_before, src_x_limit;
bool prev_src_line_ended_with_wrap, current_src_line_has_multline_cells, current_dest_line_has_multiline_cells;
bool dest_line_from_linebuf, src_is_in_linebuf;
} Rewrap;
static void
setup_line(TextCache *tc, index_type xnum, Line *l) {
l->text_cache = tc;
l->xnum = xnum;
}
#define src_xnum (r->src.lb->xnum)
#define dest_xnum (r->dest.lb->xnum)
static void
exclude_empty_lines_at_bottom(Rewrap *r) {
index_type first, i;
bool is_empty = true;
// Find the first line that contains some content
#define self (r->src.lb)
first = self->ynum;
do {
first--;
CPUCell *cells = linebuf_cpu_cells_for_line(self, first);
for(i = 0; i < self->xnum; i++) {
if (cells[i].ch_or_idx || cells[i].ch_is_idx) { is_empty = false; break; }
}
} while(is_empty && first > 0);
if (!is_empty) r->num_content_lines_before = first + 1;
#undef self
}
static void
init_src_line_basic(Rewrap *r, index_type y, Line *dest, bool update_state) {
if (r->src_is_in_linebuf) {
linebuf_init_line_at(r->src.lb, y - r->src.hb_count, dest);
} else if (r->src.y >= r->src.hb_count) {
if (update_state) r->src_is_in_linebuf = true;
linebuf_init_line_at(r->src.lb, y - r->src.hb_count, dest);
} else {
// historybuf_init_line uses reverse indexing
historybuf_init_line(r->src.hb, r->src.hb->count - y - 1, dest);
}
}
static bool
init_src_line(Rewrap *r) {
bool newline_needed = !r->prev_src_line_ended_with_wrap;
init_src_line_basic(r, r->src.y, &r->src.line, true);
r->src_x_limit = src_xnum;
r->prev_src_line_ended_with_wrap = r->src.line.cpu_cells[src_xnum - 1].next_char_was_wrapped;
r->src.line.cpu_cells[src_xnum - 1].next_char_was_wrapped = false;
// Trim trailing blanks
while (r->src_x_limit && r->src.line.cpu_cells[r->src_x_limit - 1].ch_and_idx == BLANK_CHAR) r->src_x_limit--;
r->src.x = 0;
r->current_src_line_has_multline_cells = false;
for (index_type i = 0; i < r->src_x_limit; i++) if (r->src.line.cpu_cells[i].is_multicell && r->src.line.cpu_cells[i].scale > 1) {
r->current_src_line_has_multline_cells = true;
break;
}
return newline_needed;
}
#define set_dest_line_attrs(dest_y) r->dest.lb->line_attrs[dest_y] = r->src.line.attrs; r->src.line.attrs.prompt_kind = UNKNOWN_PROMPT_KIND;
static void
first_dest_line(Rewrap *r) {
if (r->src.hb_count) historybuf_next_dest_line(r->dest.hb, r->as_ansi_buf, &r->src.line, 0, &r->dest.line, false);
else {
r->dest_line_from_linebuf = true;
linebuf_init_line_at(r->dest.lb, 0, &r->dest.line);
set_dest_line_attrs(0);
}
}
static index_type
linebuf_next_dest_line(Rewrap *r, bool continued) {
#define dest_y r->dest.y
LineBuf *dest = r->dest.lb;
linebuf_set_last_char_as_continuation(dest, dest_y, continued);
if (dest_y >= dest->ynum - 1) {
linebuf_index(dest, 0, dest->ynum - 1);
if (r->dest.hb != NULL) {
linebuf_init_line(dest, dest->ynum - 1);
dest->line->attrs.has_dirty_text = true;
historybuf_add_line(r->dest.hb, dest->line, r->as_ansi_buf);
}
linebuf_clear_line(dest, dest->ynum - 1, true);
} else dest_y++;
linebuf_init_line_at(dest, dest_y, &r->dest.line);
set_dest_line_attrs(dest_y);
return dest_y;
#undef dest_y
}
static void
next_dest_line(Rewrap *r, bool continued) {
r->dest.x = 0;
r->current_dest_line_has_multiline_cells = false;
if (r->dest_line_from_linebuf) {
r->dest.y = linebuf_next_dest_line(r, continued);
} else if (r->src_is_in_linebuf) {
r->dest_line_from_linebuf = true;
r->dest.y = 0;
linebuf_init_line_at(r->dest.lb, 0, &r->dest.line);
set_dest_line_attrs(0);
if (continued && r->dest.hb && r->dest.hb->count) {
historybuf_init_line(r->dest.hb, 0, r->dest.hb->line);
r->dest.hb->line->cpu_cells[dest_xnum-1].next_char_was_wrapped = true;
}
} else {
r->dest.y = historybuf_next_dest_line(r->dest.hb, r->as_ansi_buf, &r->src.line, r->dest.y, &r->dest.line, continued);
}
if (r->sb->line_attrs[0].has_dirty_text) {
CPUCell *cpu_cells; GPUCell *gpu_cells;
linebuf_init_cells(r->sb, 0, &cpu_cells, &gpu_cells);
memcpy(r->dest.line.cpu_cells, cpu_cells, dest_xnum * sizeof(cpu_cells[0]));
memcpy(r->dest.line.gpu_cells, gpu_cells, dest_xnum * sizeof(gpu_cells[0]));
r->current_dest_line_has_multiline_cells = true;
}
linebuf_index(r->sb, 0, r->sb->ynum - 1);
if (r->sb->line_attrs[r->sb->ynum - 1].has_dirty_text) {
linebuf_clear_line(r->sb, r->sb->ynum - 1, true);
}
}
static void
update_tracked_cursors(Rewrap *r, index_type num_cells, index_type src_y, index_type dest_y, index_type x_limit) {
if (!r->src_is_in_linebuf) return;
src_y -= r->src.hb_count;
for (TrackCursor *t = r->cursors; !t->is_sentinel; t++) {
if (t->y == src_y && r->src.x <= t->x && (t->x < r->src.x + num_cells || t->x >= x_limit)) {
t->dest_y = dest_y;
t->dest_x = r->dest.x + (t->x - r->src.x);
if (t->dest_x > dest_xnum) t->dest_x = dest_xnum;
}
}
}
static bool
find_space_in_dest_line(Rewrap *r, index_type num_cells) {
while (r->dest.x + num_cells <= dest_xnum) {
index_type before = r->dest.x;
for (index_type x = r->dest.x; x < r->dest.x + num_cells; x++) {
if (r->dest.line.cpu_cells[x].is_multicell) {
r->dest.x = x + mcd_x_limit(r->dest.line.cpu_cells + x);
break;
}
}
if (before == r->dest.x) return true;
}
return false;
}
static void
find_space_in_dest(Rewrap *r, index_type num_cells) {
while (!find_space_in_dest_line(r, num_cells)) next_dest_line(r, true);
}
static void
copy_range(Line *src, index_type src_at, Line* dest, index_type dest_at, index_type num) {
memcpy(dest->cpu_cells + dest_at, src->cpu_cells + src_at, num * sizeof(CPUCell));
memcpy(dest->gpu_cells + dest_at, src->gpu_cells + src_at, num * sizeof(GPUCell));
}
static void
copy_multiline_extra_lines(Rewrap *r, CPUCell *src_cell, index_type mc_width) {
for (index_type i = 1; i < src_cell->scale; i++) {
init_src_line_basic(r, r->src.y + i, &r->src.scratch_line, false);
linebuf_init_line_at(r->sb, i - 1, &r->dest.scratch_line);
linebuf_mark_line_dirty(r->sb, i - 1);
copy_range(&r->src.scratch_line, r->src.x, &r->dest.scratch_line, r->dest.x, mc_width);
update_tracked_cursors(r, mc_width, r->src.y + i, r->dest.y + i, src_xnum + 10000 /* ensure cursor is moved only if in region being copied */);
}
}
static void
multiline_copy_src_to_dest(Rewrap *r) {
CPUCell *c; index_type mc_width;
while (r->src.x < r->src_x_limit) {
c = &r->src.line.cpu_cells[r->src.x];
if (c->is_multicell) {
mc_width = mcd_x_limit(c);
if (mc_width > dest_xnum) {
update_tracked_cursors(r, mc_width, r->src.y, r->dest.y, r->src_x_limit);
r->src.x += mc_width;
continue;
} else if (c->y) {
r->src.x += mc_width;
continue;
}
} else mc_width = 1;
find_space_in_dest(r, mc_width);
copy_range(&r->src.line, r->src.x, &r->dest.line, r->dest.x, mc_width);
update_tracked_cursors(r, mc_width, r->src.y, r->dest.y, r->src_x_limit);
if (c->scale > 1) copy_multiline_extra_lines(r, c, mc_width);
r->src.x += mc_width; r->dest.x += mc_width;
}
}
static void
fast_copy_src_to_dest(Rewrap *r) {
CPUCell *c; index_type mc_width;
while (r->src.x < r->src_x_limit) {
if (r->dest.x >= dest_xnum) {
next_dest_line(r, true);
if (r->current_dest_line_has_multiline_cells) {
multiline_copy_src_to_dest(r);
return;
}
}
index_type num = MIN(r->src_x_limit - r->src.x, dest_xnum - r->dest.x);
if (num && (c = &r->src.line.cpu_cells[r->src.x + num - 1])->is_multicell && c->x != (mc_width = mcd_x_limit(c)) - 1) {
// we have a split multicell at the right edge of the copy region
if (num > mc_width) num = MIN(r->src_x_limit - r->src.x - mc_width, num);
else {
if (mc_width > dest_xnum) {
multiline_copy_src_to_dest(r);
return;
}
r->dest.x = dest_xnum;
continue;
}
}
copy_range(&r->src.line, r->src.x, &r->dest.line, r->dest.x, num);
update_tracked_cursors(r, num, r->src.y, r->dest.y, r->src_x_limit);
r->src.x += num; r->dest.x += num;
}
}
static void
rewrap(Rewrap *r) {
r->src.hb_count = r->src.hb ? r->src.hb->count : 0;
// Fast path
if (r->dest.lb->xnum == r->src.lb->xnum && r->dest.lb->ynum == r->src.lb->ynum) {
memcpy(r->dest.lb->line_map, r->src.lb->line_map, sizeof(index_type) * r->src.lb->ynum);
memcpy(r->dest.lb->line_attrs, r->src.lb->line_attrs, sizeof(LineAttrs) * r->src.lb->ynum);
memcpy(r->dest.lb->cpu_cell_buf, r->src.lb->cpu_cell_buf, (size_t)r->src.lb->xnum * r->src.lb->ynum * sizeof(CPUCell));
memcpy(r->dest.lb->gpu_cell_buf, r->src.lb->gpu_cell_buf, (size_t)r->src.lb->xnum * r->src.lb->ynum * sizeof(GPUCell));
r->num_content_lines_before = r->src.lb->ynum;
if (r->dest.hb && r->src.hb) historybuf_fast_rewrap(r->dest.hb, r->src.hb);
r->dest.y = r->src.lb->ynum - 1;
return;
}
setup_line(r->src.lb->text_cache, src_xnum, &r->src.line);
setup_line(r->src.lb->text_cache, dest_xnum, &r->dest.line);
setup_line(r->src.lb->text_cache, src_xnum, &r->src.scratch_line);
setup_line(r->src.lb->text_cache, dest_xnum, &r->dest.scratch_line);
exclude_empty_lines_at_bottom(r);
for (; r->src.y < r->num_content_lines_before + r->src.hb_count; r->src.y++) {
if (init_src_line(r)) {
if (r->src.y) next_dest_line(r, false);
else first_dest_line(r);
}
if (r->current_src_line_has_multline_cells || r->current_dest_line_has_multiline_cells) multiline_copy_src_to_dest(r);
else fast_copy_src_to_dest(r);
}
}
ResizeResult
resize_screen_buffers(LineBuf *lb, HistoryBuf *hb, index_type lines, index_type columns, ANSIBuf *as_ansi_buf, TrackCursor *cursors) {
ResizeResult ans = {0};
ans.lb = alloc_linebuf(lines, columns, lb->text_cache);
if (!ans.lb) return ans;
RAII_PyObject(raii_nlb, (PyObject*)ans.lb); (void) raii_nlb;
if (hb) {
ans.hb = historybuf_alloc_for_rewrap(columns, hb);
if (!ans.hb) return ans;
}
RAII_PyObject(raii_nhb, (PyObject*)ans.hb); (void) raii_nhb;
Rewrap r = {
.src = {.lb=lb, .hb=hb}, .dest = {.lb=ans.lb, .hb=ans.hb},
.as_ansi_buf = as_ansi_buf, .cursors = cursors,
};
r.sb = alloc_linebuf(SCALE_BITS << 1, columns, lb->text_cache);
if (!r.sb) return ans;
RAII_PyObject(scratch, (PyObject*)r.sb); (void)scratch;
for (TrackCursor *t = cursors; !t->is_sentinel; t++) { t->dest_x = t->x; t->dest_y = t->y; }
rewrap(&r);
ans.num_content_lines_before = r.num_content_lines_before;
ans.num_content_lines_after = MIN(r.dest.y + 1, ans.lb->ynum);
if (hb) historybuf_finish_rewrap(ans.hb, hb);
for (unsigned i = 0; i < ans.num_content_lines_after; i++) linebuf_mark_line_dirty(ans.lb, i);
for (TrackCursor *t = cursors; !t->is_sentinel; t++) { t->dest_x = MIN(t->dest_x, columns); t->dest_y = MIN(t->dest_y, lines); }
Py_INCREF(raii_nlb); Py_XINCREF(raii_nhb);
ans.ok = true;
return ans;
}

25
kitty/resize.h Normal file
View file

@ -0,0 +1,25 @@
/*
* resize.h
* Copyright (C) 2024 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#pragma once
#include "line-buf.h"
#include "history.h"
typedef struct TrackCursor {
index_type x, y;
index_type dest_x, dest_y;
bool is_sentinel;
} TrackCursor;
typedef struct ResizeResult {
LineBuf *lb; HistoryBuf *hb;
bool ok;
index_type num_content_lines_before, num_content_lines_after;
} ResizeResult;
ResizeResult
resize_screen_buffers(LineBuf *lb, HistoryBuf *hb, index_type lines, index_type columns, ANSIBuf *as_ansi_buf, TrackCursor *cursors);

View file

@ -1,305 +0,0 @@
/*
* rewrap.c
* Copyright (C) 2024 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#include "rewrap.h"
#include "lineops.h"
#include "text-cache.h"
typedef void (*init_line_func_t)(void *buf, index_type y, Line *line);
typedef index_type (*first_dest_line_func_t)(void *buf, ANSIBuf *as_ansi_buf, Line *src_line, Line *dest_line);
typedef index_type (*next_dest_line_func_t)(void *buf, HistoryBuf *historybuf, ANSIBuf *as_ansi_buf, Line *src_line, index_type dest_y, Line *dest_line, bool continued);
static void
LineBuf_init_line(void *buf, index_type y, Line *line) {
linebuf_init_line_at(buf, y, line);
}
static void
HistoryBuf_init_line(void *buf, index_type y, Line *line) {
HistoryBuf *dest = buf;
// historybuf_init_line uses reverse indexing
historybuf_init_line(dest, dest->count ? dest->count - y - 1 : 0, line);
}
#define set_dest_line_attrs(dest_y) dest->line_attrs[dest_y] = src_line->attrs; src_line->attrs.prompt_kind = UNKNOWN_PROMPT_KIND;
static index_type
LineBuf_first_dest_line(void *buf, ANSIBuf *as_ansi_buf, Line *src_line, Line *dest_line) {
(void)as_ansi_buf;
LineBuf *dest = buf;
linebuf_init_line_at(dest, 0, dest_line);
set_dest_line_attrs(0);
return 0;
}
static index_type
HistoryBuf_first_dest_line(void *buf, ANSIBuf *as_ansi_buf, Line *src_line, Line *dest_line) {
HistoryBuf *dest = buf;
historybuf_next_dest_line(dest, as_ansi_buf, src_line, 0, dest_line, false);
return 0;
}
static index_type
LineBuf_next_dest_line(void *buf, HistoryBuf *historybuf, ANSIBuf *as_ansi_buf, Line *src_line, index_type dest_y, Line *dest_line, bool continued) {
LineBuf *dest = buf;
linebuf_set_last_char_as_continuation(dest, dest_y, continued);
if (dest_y >= dest->ynum - 1) {
linebuf_index(dest, 0, dest->ynum - 1);
if (historybuf != NULL) {
linebuf_init_line(dest, dest->ynum - 1);
dest->line->attrs.has_dirty_text = true;
historybuf_add_line(historybuf, dest->line, as_ansi_buf);
}
linebuf_clear_line(dest, dest->ynum - 1, true);
} else dest_y++;
linebuf_init_line_at(dest, dest_y, dest_line);
set_dest_line_attrs(dest_y);
return dest_y;
}
static index_type
HistoryBuf_next_dest_line(void *buf, HistoryBuf *historybuf, ANSIBuf *as_ansi_buf, Line *src_line, index_type dest_y, Line *dest_line, bool continued) {
(void)historybuf;
HistoryBuf *dest = buf;
return historybuf_next_dest_line(dest, as_ansi_buf, src_line, dest_y, dest_line, continued);
}
typedef struct Rewrap {
void *src_buf, *dest_buf;
index_type src_xnum, dest_xnum;
ANSIBuf *as_ansi_buf;
TextCache *text_cache;
HistoryBuf *historybuf;
TrackCursor *cursors;
index_type src_limit;
Line src, dest, src_scratch, dest_scratch;
index_type src_y, src_x, dest_x, dest_y, num, src_x_limit, history_buf_last_line_xlimit;
init_line_func_t init_line;
first_dest_line_func_t first_dest_line;
next_dest_line_func_t next_dest_line;
LineBuf *scratch;
bool current_dest_line_has_multiline_cells, current_src_line_has_multline_cells, prev_src_line_ended_with_wrap;
bool current_dest_line_is_last_history_line;
} Rewrap;
static void
copy_range(Line *src, index_type src_at, Line* dest, index_type dest_at, index_type num) {
memcpy(dest->cpu_cells + dest_at, src->cpu_cells + src_at, num * sizeof(CPUCell));
memcpy(dest->gpu_cells + dest_at, src->gpu_cells + src_at, num * sizeof(GPUCell));
}
static void
setup_line(TextCache *tc, index_type xnum, Line *l) {
l->text_cache = tc;
l->xnum = xnum;
}
static void
next_dest_line(Rewrap *r, bool continued) {
r->dest_x = 0;
r->current_dest_line_has_multiline_cells = false;
if (r->current_dest_line_is_last_history_line) {
r->dest.cpu_cells[r->dest_xnum-1].next_char_was_wrapped = continued;
r->current_dest_line_is_last_history_line = false;
r->dest_y = r->first_dest_line(r->dest_buf, r->as_ansi_buf, &r->src, &r->dest);
} else r->dest_y = r->next_dest_line(r->dest_buf, r->historybuf, r->as_ansi_buf, &r->src, r->dest_y, &r->dest, continued);
if (r->scratch->line_attrs[0].has_dirty_text) {
CPUCell *cpu_cells; GPUCell *gpu_cells;
linebuf_init_cells(r->scratch, 0, &cpu_cells, &gpu_cells);
memcpy(r->dest.cpu_cells, cpu_cells, r->dest_xnum * sizeof(cpu_cells[0]));
memcpy(r->dest.gpu_cells, gpu_cells, r->dest_xnum * sizeof(gpu_cells[0]));
r->current_dest_line_has_multiline_cells = true;
}
linebuf_index(r->scratch, 0, r->scratch->ynum - 1);
if (r->scratch->line_attrs[r->scratch->ynum - 1].has_dirty_text) {
linebuf_clear_line(r->scratch, r->scratch->ynum - 1, true);
}
}
static void
first_dest_line(Rewrap *r) {
if (r->history_buf_last_line_xlimit < r->dest_xnum && r->historybuf) {
r->dest_y = 0;
r->dest_x = r->history_buf_last_line_xlimit;
r->current_dest_line_has_multiline_cells = true;
r->current_dest_line_is_last_history_line = true;
historybuf_init_line(r->historybuf, 0, &r->dest);
} else {
r->dest_y = r->first_dest_line(r->dest_buf, r->as_ansi_buf, &r->src, &r->dest);
}
}
static bool
init_src_line(Rewrap *r) {
bool newline_needed = !r->prev_src_line_ended_with_wrap;
r->init_line(r->src_buf, r->src_y, &r->src);
r->src_x_limit = r->src_xnum;
// Trim trailing blanks
while(r->src_x_limit && r->src.cpu_cells[r->src_x_limit - 1].ch_and_idx == BLANK_CHAR) r->src_x_limit--;
r->prev_src_line_ended_with_wrap = r->src.cpu_cells[r->src_xnum - 1].next_char_was_wrapped;
r->src.cpu_cells[r->src_xnum - 1].next_char_was_wrapped = false;
r->src_x = 0;
r->current_src_line_has_multline_cells = false;
for (index_type i = 0; i < r->src_x_limit; i++) if (r->src.cpu_cells[i].is_multicell && r->src.cpu_cells[i].scale > 1) {
r->current_src_line_has_multline_cells = true;
break;
}
return newline_needed;
}
static void
update_tracked_cursors(Rewrap *r, index_type num_cells, index_type src_y, index_type dest_y, index_type x_limit) {
for (TrackCursor *t = r->cursors; !t->is_sentinel; t++) {
if (t->y == src_y && r->src_x <= t->x && (t->x < r->src_x + num_cells || t->x >= x_limit)) {
if (r->current_dest_line_is_last_history_line) {
t->dest_x = 0; t->dest_y = 0;
} else {
t->dest_y = dest_y;
t->dest_x = r->dest_x + (t->x - r->src_x);
if (t->dest_x > r->dest_xnum) t->dest_x = r->dest_xnum;
}
}
}
}
static bool
find_space_in_dest_line(Rewrap *r, index_type num_cells) {
while (r->dest_x + num_cells <= r->dest_xnum) {
index_type before = r->dest_x;
for (index_type x = r->dest_x; x < r->dest_x + num_cells; x++) {
if (r->dest.cpu_cells[x].is_multicell) {
r->dest_x = x + mcd_x_limit(r->dest.cpu_cells + x);
break;
}
}
if (before == r->dest_x) return true;
}
return false;
}
static void
find_space_in_dest(Rewrap *r, index_type num_cells) {
while (!find_space_in_dest_line(r, num_cells)) next_dest_line(r, true);
}
static void
copy_multiline_extra_lines(Rewrap *r, CPUCell *src_cell, index_type mc_width) {
for (index_type i = 1; i < src_cell->scale; i++) {
r->init_line(r->src_buf, r->src_y + i, &r->src_scratch);
linebuf_init_line_at(r->scratch, i - 1, &r->dest_scratch);
linebuf_mark_line_dirty(r->scratch, i - 1);
copy_range(&r->src_scratch, r->src_x, &r->dest_scratch, r->dest_x, mc_width);
update_tracked_cursors(r, mc_width, r->src_y + i, r->dest_y + i, r->src_xnum + 10000 /* ensure cursor is moved only if in region being copied */);
}
}
static void
multiline_copy_src_to_dest(Rewrap *r) {
CPUCell *c; index_type mc_width;
while (r->src_x < r->src_x_limit) {
c = &r->src.cpu_cells[r->src_x];
if (c->is_multicell) {
mc_width = mcd_x_limit(c);
if (mc_width > r->dest_xnum) {
update_tracked_cursors(r, mc_width, r->src_y, r->dest_y, r->src_x_limit);
r->src_x += mc_width;
continue;
} else if (c->y) {
r->src_x += mc_width;
continue;
}
} else mc_width = 1;
find_space_in_dest(r, mc_width);
copy_range(&r->src, r->src_x, &r->dest, r->dest_x, mc_width);
update_tracked_cursors(r, mc_width, r->src_y, r->dest_y, r->src_x_limit);
if (c->scale > 1) copy_multiline_extra_lines(r, c, mc_width);
r->src_x += mc_width; r->dest_x += mc_width;
}
}
static void
fast_copy_src_to_dest(Rewrap *r) {
CPUCell *c; index_type mc_width;
while (r->src_x < r->src_x_limit) {
if (r->dest_x >= r->dest_xnum) {
next_dest_line(r, true);
if (r->current_dest_line_has_multiline_cells) {
multiline_copy_src_to_dest(r);
return;
}
}
index_type num = MIN(r->src_x_limit - r->src_x, r->dest_xnum - r->dest_x);
if (num && (c = &r->src.cpu_cells[r->src_x + num - 1])->is_multicell && c->x != (mc_width = mcd_x_limit(c)) - 1) {
// we have a split multicell at the right edge of the copy region
if (num > mc_width) num = MIN(r->src_x_limit - r->src_x - mc_width, num);
else {
if (mc_width > r->dest_xnum) {
multiline_copy_src_to_dest(r);
return;
}
r->dest_x = r->dest_xnum;
continue;
}
}
copy_range(&r->src, r->src_x, &r->dest, r->dest_x, num);
update_tracked_cursors(r, num, r->src_y, r->dest_y, r->src_x_limit);
r->src_x += num; r->dest_x += num;
}
}
static index_type
rewrap_inner(Rewrap *r) {
setup_line(r->text_cache, r->src_xnum, &r->src); setup_line(r->text_cache, r->dest_xnum, &r->dest);
setup_line(r->text_cache, r->src_xnum, &r->src_scratch); setup_line(r->text_cache, r->dest_xnum, &r->dest_scratch);
r->scratch = alloc_linebuf(SCALE_BITS << 1, r->dest_xnum, r->text_cache);
if (!r->scratch) fatal("Out of memory");
RAII_PyObject(scratch, (PyObject*)r->scratch); (void)scratch;
for (; r->src_y < r->src_limit; r->src_y++) {
if (init_src_line(r)) {
if (r->src_y) next_dest_line(r, false);
else first_dest_line(r);
}
if (r->current_src_line_has_multline_cells || r->current_dest_line_has_multiline_cells) multiline_copy_src_to_dest(r);
else fast_copy_src_to_dest(r);
}
return r->dest_y;
}
index_type
linebuf_rewrap_inner(LineBuf *src, LineBuf *dest, const index_type src_limit, HistoryBuf *historybuf, TrackCursor *track, ANSIBuf *as_ansi_buf, bool history_buf_last_line_is_split) {
index_type xlimit = dest->xnum;
if (history_buf_last_line_is_split && historybuf) {
historybuf_init_line(historybuf, 0, historybuf->line);
xlimit = xlimit_for_line(historybuf->line);
}
Rewrap r = {
.src_buf = src, .dest_buf = dest, .as_ansi_buf = as_ansi_buf, .text_cache = src->text_cache, .cursors = track,
.src_limit = src_limit, .historybuf=historybuf, .src_xnum = src->xnum, .dest_xnum = dest->xnum,
.history_buf_last_line_xlimit = xlimit,
.init_line = LineBuf_init_line, .next_dest_line = LineBuf_next_dest_line, .first_dest_line = LineBuf_first_dest_line,
};
return rewrap_inner(&r);
}
index_type
historybuf_rewrap_inner(HistoryBuf *src, HistoryBuf *dest, const index_type src_limit, ANSIBuf *as_ansi_buf) {
static TrackCursor t = {.is_sentinel = true };
Rewrap r = {
.src_buf = src, .dest_buf = dest, .as_ansi_buf = as_ansi_buf, .text_cache = src->text_cache,
.src_limit = src_limit, .src_xnum = src->xnum, .dest_xnum = dest->xnum, .cursors=&t, .history_buf_last_line_xlimit = dest->xnum,
.init_line = HistoryBuf_init_line, .next_dest_line = HistoryBuf_next_dest_line, .first_dest_line = HistoryBuf_first_dest_line,
};
return rewrap_inner(&r);
}

View file

@ -1,22 +0,0 @@
/*
* rewrap.h
* Copyright (C) 2016 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#pragma once
#include "line-buf.h"
#include "history.h"
typedef struct TrackCursor {
const index_type x, y;
index_type dest_x, dest_y;
bool is_sentinel;
} TrackCursor;
index_type linebuf_rewrap_inner(LineBuf *src, LineBuf *dest, const index_type src_limit, HistoryBuf *historybuf, TrackCursor *track, ANSIBuf *as_ansi_buf, bool history_buf_last_line_is_split);
index_type historybuf_rewrap_inner(HistoryBuf *src, HistoryBuf *dest, const index_type src_limit, ANSIBuf *as_ansi_buf);

View file

@ -30,6 +30,7 @@
#include <stdalign.h>
#include "keys.h"
#include "vt-parser.h"
#include "resize.h"
static const ScreenModes empty_modes = {0, .mDECAWM=true, .mDECTCEM=true, .mDECARM=true};
@ -216,16 +217,6 @@ screen_dirty_sprite_positions(Screen *self) {
for (index_type i = 0; i < self->historybuf->count; i++) historybuf_mark_line_dirty(self->historybuf, i);
}
static HistoryBuf*
realloc_hb(HistoryBuf *old, unsigned int columns, ANSIBuf *as_ansi_buf) {
HistoryBuf *ans = alloc_historybuf(old->ynum, columns, 0, old->text_cache);
if (ans == NULL) { PyErr_NoMemory(); return NULL; }
ans->pagerhist = old->pagerhist; old->pagerhist = NULL;
historybuf_rewrap(old, ans, as_ansi_buf);
return ans;
}
typedef struct CursorTrack {
index_type num_content_lines;
bool is_beyond_content;
@ -234,14 +225,37 @@ typedef struct CursorTrack {
struct { index_type x, y; } temp;
} CursorTrack;
static LineBuf*
realloc_lb(LineBuf *old, unsigned int lines, unsigned int columns, index_type *nclb, index_type *ncla, HistoryBuf *hb, CursorTrack *a, CursorTrack *b, ANSIBuf *as_ansi_buf, bool history_buf_last_line_is_split) {
LineBuf *ans = alloc_linebuf(lines, columns, old->text_cache);
if (ans == NULL) { PyErr_NoMemory(); return NULL; }
a->temp.x = a->before.x; a->temp.y = a->before.y;
b->temp.x = b->before.x; b->temp.y = b->before.y;
linebuf_rewrap(old, ans, nclb, ncla, hb, &a->temp.x, &a->temp.y, &b->temp.x, &b->temp.y, as_ansi_buf, history_buf_last_line_is_split);
return ans;
static bool
rewrap(Screen *screen, unsigned int lines, unsigned int columns, index_type *nclb, index_type *ncla, CursorTrack *cursor, CursorTrack *main_saved_cursor, CursorTrack *alt_saved_cursor, bool main_is_active) {
TrackCursor cursors[3];
cursors[2].is_sentinel = true;
cursors[0] = (TrackCursor){.x=main_saved_cursor->before.x, .y=main_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 mr = resize_screen_buffers(screen->main_linebuf, screen->historybuf, lines, columns, &screen->as_ansi_buf, cursors);
if (!mr.ok) { PyErr_NoMemory(); return false; }
main_saved_cursor->temp.x = cursors[0].dest_x; main_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; }
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);
if (!ar.ok) {
Py_DecRef((PyObject*)mr.lb); Py_DecRef((PyObject*)mr.hb);
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; }
Py_CLEAR(screen->main_linebuf); Py_CLEAR(screen->alt_linebuf); Py_CLEAR(screen->historybuf);
screen->main_linebuf = mr.lb; screen->historybuf = mr.hb; screen->alt_linebuf = ar.lb;
screen->linebuf = main_is_active ? screen->main_linebuf : screen->alt_linebuf;
if (main_is_active) {
*nclb = mr.num_content_lines_before; *ncla = mr.num_content_lines_after;
} else {
*nclb = ar.num_content_lines_before; *ncla = ar.num_content_lines_after;
}
return true;
}
static bool
@ -396,36 +410,22 @@ screen_resize(Screen *self, unsigned int lines, unsigned int columns) {
if (!init_overlay_line(self, columns, true)) return false;
// Resize main linebuf
bool history_buf_last_line_is_split = history_buf_endswith_wrap(self->historybuf);
HistoryBuf *nh = realloc_hb(self->historybuf, columns, &self->as_ansi_buf);
if (nh == NULL) return false;
Py_CLEAR(self->historybuf); self->historybuf = nh;
RAII_PyObject(prompt_copy, NULL);
index_type num_of_prompt_lines = 0, num_of_prompt_lines_above_cursor = 0;
if (is_main) {
prompt_copy = (PyObject*)alloc_linebuf(self->lines, self->columns, self->text_cache);
num_of_prompt_lines = prevent_current_prompt_from_rewrapping(self, (LineBuf*)prompt_copy, &num_of_prompt_lines_above_cursor);
}
LineBuf *n = realloc_lb(self->main_linebuf, lines, columns, &num_content_lines_before, &num_content_lines_after, self->historybuf, &cursor, &main_saved_cursor, &self->as_ansi_buf, history_buf_last_line_is_split);
if (n == NULL) return false;
Py_CLEAR(self->main_linebuf); self->main_linebuf = n;
if (is_main) setup_cursor(cursor);
if (!rewrap(self, lines, columns, &num_content_lines_before, &num_content_lines_after, &cursor, &main_saved_cursor, &alt_saved_cursor, is_main)) return false;
setup_cursor(cursor);
/* printf("old_cursor: (%u, %u) new_cursor: (%u, %u) beyond_content: %d\n", self->cursor->x, self->cursor->y, cursor.after.x, cursor.after.y, cursor.is_beyond_content); */
setup_cursor(main_saved_cursor);
grman_remove_all_cell_images(self->main_grman);
grman_resize(self->main_grman, self->lines, lines, self->columns, columns, num_content_lines_before, num_content_lines_after);
// Resize alt linebuf
n = realloc_lb(self->alt_linebuf, lines, columns, &num_content_lines_before, &num_content_lines_after, NULL, &cursor, &alt_saved_cursor, &self->as_ansi_buf, false);
if (n == NULL) return false;
Py_CLEAR(self->alt_linebuf); self->alt_linebuf = n;
if (!is_main) setup_cursor(cursor);
setup_cursor(alt_saved_cursor);
grman_remove_all_cell_images(self->alt_grman);
grman_resize(self->alt_grman, self->lines, lines, self->columns, columns, num_content_lines_before, num_content_lines_after);
#undef setup_cursor
self->linebuf = is_main ? self->main_linebuf : self->alt_linebuf;
/* printf("\nold_size: (%u, %u) new_size: (%u, %u)\n", self->columns, self->lines, columns, lines); */
self->lines = lines; self->columns = columns;
self->margin_top = 0; self->margin_bottom = self->lines - 1;

View file

@ -314,28 +314,25 @@ class TestDataTypes(BaseTest):
self.ae(l4.url_end_at(0), len(l4) - 2)
self.ae(l4.url_end_at(0, 0, True), len(l4) - 1)
def rewrap(self, lb, lb2):
hb = HistoryBuf(lb2.ynum, lb2.xnum)
cy = lb.rewrap(lb2, hb)
return hb, cy[1]
def rewrap(self, lb, lines, columns):
return lb.rewrap(lines, columns)
def test_rewrap_simple(self):
' Same width buffers '
lb = filled_line_buf(5, 5)
lb2 = LineBuf(lb.ynum, lb.xnum)
self.rewrap(lb, lb2)
lb2 = self.rewrap(lb, lb.ynum, lb.xnum)[0]
for i in range(lb.ynum):
self.ae(lb2.line(i), lb.line(i))
lb2 = LineBuf(8, 5)
cy = self.rewrap(lb, lb2)[1]
lb2, _, cy = self.rewrap(lb, 8, 5)
self.ae(cy, 5)
for i in range(lb.ynum):
self.ae(lb2.line(i), lb.line(i))
self.ae(lb2.line(i), lb.line(i), i)
empty = LineBuf(1, lb2.xnum)
for i in range(lb.ynum, lb2.ynum):
self.ae(str(lb2.line(i)), str(empty.line(0)))
lb2 = LineBuf(3, 5)
cy = self.rewrap(lb, lb2)[1]
lb2, _, cy = self.rewrap(lb, 3, 5)
self.ae(cy, 3)
for i in range(lb2.ynum):
self.ae(lb2.line(i), lb.line(i + 2))
@ -348,8 +345,7 @@ class TestDataTypes(BaseTest):
self.ae(l0, str(l2))
def line_comparison_rewrap(self, lb, *lines):
lb2 = LineBuf(len(lines), max(map(len, lines)))
self.rewrap(lb, lb2)
lb2 = self.rewrap(lb, len(lines), max(map(len, lines)))[0]
self.line_comparison(lb2, *lines)
return lb2
@ -493,40 +489,24 @@ class TestDataTypes(BaseTest):
hb = filled_history_buf(5, 5)
for i in range(hb.ynum):
hb.line(i).set_wrapped_flag(True)
hb2 = HistoryBuf(3, 10)
before = as_ansi(hb)
hb.rewrap(hb2)
hb2 = hb.rewrap(10)
self.ae(before, as_ansi(hb2).rstrip())
hb = filled_history_buf(5, 5)
hb2 = HistoryBuf(hb.ynum, hb.xnum)
hb.rewrap(hb2)
hb2 = hb.rewrap(hb.xnum)
for i in range(hb.ynum):
self.ae(hb2.line(i), hb.line(i))
hb2 = HistoryBuf(8, 5)
hb.rewrap(hb2)
for i in range(hb.ynum):
self.ae(hb2.line(i), hb.line(i))
for i in range(hb.ynum, hb2.ynum):
with self.assertRaises(IndexError):
hb2.line(i)
hb2 = HistoryBuf(3, 5)
hb.rewrap(hb2)
for i in range(hb2.ynum):
self.ae(hb2.line(i), hb.line(i))
self.ae(hb2.dirty_lines(), list(range(hb2.ynum)))
hb = filled_history_buf(5, 5)
hb2 = HistoryBuf(hb.ynum, hb.xnum * 2)
hb.rewrap(hb2)
hb2 = hb.rewrap(hb.xnum * 2)
hb3 = HistoryBuf(hb.ynum, hb.xnum)
hb2.rewrap(hb3)
hb3 = hb2.rewrap(hb.xnum)
for i in range(hb.ynum):
self.ae(hb.line(i), hb3.line(i))
hb2 = HistoryBuf(hb.ynum, hb.xnum)
large_hb.rewrap(hb2)
hb2 = HistoryBuf(large_hb.ynum, large_hb.xnum)
large_hb.rewrap(hb2)
hb2 = large_hb.rewrap(hb.xnum)
hb2.rewrap(large_hb.xnum)
def test_ansi_repr(self):
lb = filled_line_buf()