diff --git a/kitty/history.c b/kitty/history.c index c5ab589b7..b33273fce 100644 --- a/kitty/history.c +++ b/kitty/history.c @@ -143,6 +143,22 @@ line(HistoryBuf *self, PyObject *val) { return (PyObject*)self->line; } +static PyObject* +__str__(HistoryBuf *self) { + PyObject *lines = PyTuple_New(self->ynum); + if (lines == NULL) return PyErr_NoMemory(); + for (index_type i = 0; i < self->count; i++) { + init_line(self, index_of(self, i), self->line); + PyObject *t = PyObject_Str((PyObject*)self->line); + if (t == NULL) { Py_CLEAR(lines); return NULL; } + PyTuple_SET_ITEM(lines, i, t); + } + PyObject *sep = PyUnicode_FromString("\n"); + PyObject *ans = PyUnicode_Join(sep, lines); + Py_CLEAR(lines); Py_CLEAR(sep); + return ans; +} + static PyObject* push(HistoryBuf *self, PyObject *args) { #define push_doc "Push a line into this buffer, removing the oldest line, if necessary" @@ -203,6 +219,7 @@ PyTypeObject HistoryBuf_Type = { .tp_doc = "History buffers", .tp_methods = methods, .tp_members = members, + .tp_str = (reprfunc)__str__, .tp_new = new }; diff --git a/kitty/line-buf.c b/kitty/line-buf.c index ff98ca33a..3769c054c 100644 --- a/kitty/line-buf.c +++ b/kitty/line-buf.c @@ -367,6 +367,22 @@ linebuf_refresh_sprite_positions(LineBuf *self) { } } +static PyObject* +__str__(LineBuf *self) { + PyObject *lines = PyTuple_New(self->ynum); + if (lines == NULL) return PyErr_NoMemory(); + for (index_type i = 0; i < self->ynum; i++) { + init_line(self, self->line, self->line_map[i]); + PyObject *t = PyObject_Str((PyObject*)self->line); + if (t == NULL) { Py_CLEAR(lines); return NULL; } + PyTuple_SET_ITEM(lines, i, t); + } + PyObject *sep = PyUnicode_FromString("\n"); + PyObject *ans = PyUnicode_Join(sep, lines); + Py_CLEAR(lines); Py_CLEAR(sep); + return ans; +} + // Boilerplate {{{ static PyObject* copy_old(LineBuf *self, PyObject *y); @@ -410,6 +426,7 @@ PyTypeObject LineBuf_Type = { .tp_doc = "Line buffers", .tp_methods = methods, .tp_members = members, + .tp_str = (reprfunc)__str__, .tp_new = new }; @@ -437,7 +454,7 @@ copy_old(LineBuf *self, PyObject *y) { #include "rewrap.h" void -linebuf_rewrap(LineBuf *self, LineBuf *other, int *cursor_y_out, HistoryBuf *historybuf) { +linebuf_rewrap(LineBuf *self, LineBuf *other, index_type *num_content_lines_before, index_type *num_content_lines_after, HistoryBuf *historybuf) { index_type first, i; bool is_empty = true; @@ -446,34 +463,41 @@ linebuf_rewrap(LineBuf *self, LineBuf *other, int *cursor_y_out, HistoryBuf *his memcpy(other->line_map, self->line_map, sizeof(index_type) * self->ynum); memcpy(other->continued_map, self->continued_map, sizeof(bool) * self->ynum); memcpy(other->buf, self->buf, self->xnum * self->ynum * sizeof(Cell)); + *num_content_lines_before = self->ynum; *num_content_lines_after = self->ynum; return; } // Find the first line that contains some content - for (first = self->ynum - 1; true; first--) { - Cell *cells = lineptr(self, first); + first = self->ynum; + do { + first--; + Cell *cells = lineptr(self, self->line_map[first]); for(i = 0; i < self->xnum; i++) { if ((cells[i].ch) != BLANK_CHAR) { is_empty = false; break; } } - if (!is_empty || !first) break; + } while(is_empty && first > 0); + + if (is_empty) { // All lines are empty + *num_content_lines_after = 0; + *num_content_lines_before = 0; + return; } - if (first == 0) { *cursor_y_out = 0; return; } // All lines are empty - rewrap_inner(self, other, first + 1, historybuf); - *cursor_y_out = other->line->ynum; + *num_content_lines_after = other->line->ynum + 1; + *num_content_lines_before = first + 1; } static PyObject* rewrap(LineBuf *self, PyObject *args) { LineBuf* other; HistoryBuf *historybuf; - int cursor_y = -1; + unsigned int nclb, ncla; if (!PyArg_ParseTuple(args, "O!O!", &LineBuf_Type, &other, &HistoryBuf_Type, &historybuf)) return NULL; - linebuf_rewrap(self, other, &cursor_y, historybuf); + linebuf_rewrap(self, other, &nclb, &ncla, historybuf); - return Py_BuildValue("i", cursor_y); + return Py_BuildValue("II", nclb, ncla); } LineBuf *alloc_linebuf(unsigned int lines, unsigned int columns) { diff --git a/kitty/lineops.h b/kitty/lineops.h index 22e3e285b..33e3b6373 100644 --- a/kitty/lineops.h +++ b/kitty/lineops.h @@ -78,7 +78,7 @@ void linebuf_clear_line(LineBuf *self, index_type y); 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_set_attribute(LineBuf *, unsigned int , unsigned int ); -void linebuf_rewrap(LineBuf *self, LineBuf *other, int *cursor_y_out, HistoryBuf *); +void linebuf_rewrap(LineBuf *self, LineBuf *other, index_type *, index_type *, HistoryBuf *); unsigned int linebuf_char_width_at(LineBuf *self, index_type x, index_type y); void linebuf_refresh_sprite_positions(LineBuf *self); bool historybuf_resize(HistoryBuf *self, index_type lines); diff --git a/kitty/rewrap.h b/kitty/rewrap.h index f151a49d8..68c6189d1 100644 --- a/kitty/rewrap.h +++ b/kitty/rewrap.h @@ -28,7 +28,7 @@ if (dest_y >= dest->ynum - 1) { \ linebuf_index(dest, 0, dest->ynum - 1); \ if (historybuf != NULL) { \ - linebuf_init_line(dest, dest->ynum - 1); \ + init_dest_line(dest->ynum - 1); \ historybuf_add_line(historybuf, dest->line); \ }\ linebuf_clear_line(dest, dest->ynum - 1); \ diff --git a/kitty/screen.c b/kitty/screen.c index ea4b2df5d..85996833d 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -123,44 +123,40 @@ realloc_hb(HistoryBuf *old, unsigned int lines, unsigned int columns) { } static inline LineBuf* -realloc_lb(LineBuf *old, unsigned int lines, unsigned int columns, int *cursor_y, HistoryBuf *hb) { +realloc_lb(LineBuf *old, unsigned int lines, unsigned int columns, index_type *nclb, index_type *ncla, HistoryBuf *hb) { LineBuf *ans = alloc_linebuf(lines, columns); if (ans == NULL) { PyErr_NoMemory(); return NULL; } - linebuf_rewrap(old, ans, cursor_y, hb); + linebuf_rewrap(old, ans, nclb, ncla, hb); return ans; } +static inline void +adjust_cursor_position_after_resize(Cursor *c, unsigned int lines, unsigned int columns, index_type num_content_lines_before, index_type num_content_lines_after) { + if (c->y >= num_content_lines_before - 1) { + index_type delta = c->y - (num_content_lines_before - 1); + c->y = num_content_lines_after - 1 + delta; + } + c->y = MIN(lines - 1, c->y); c->x = MIN(columns - 1, c->x); +} + static bool screen_resize(Screen *self, unsigned int lines, unsigned int columns) { lines = MAX(1, lines); columns = MAX(1, columns); - bool is_main = self->linebuf == self->main_linebuf, is_x_shrink = columns < self->columns; - int cursor_y = -1; unsigned int cursor_x = self->cursor->x; + bool is_main = self->linebuf == self->main_linebuf; + index_type num_content_lines_before, num_content_lines_after; HistoryBuf *nh = realloc_hb(self->historybuf, self->historybuf->ynum, columns); if (nh == NULL) return false; Py_CLEAR(self->historybuf); self->historybuf = nh; - LineBuf *n = realloc_lb(self->main_linebuf, lines, columns, &cursor_y, self->historybuf); + LineBuf *n = realloc_lb(self->main_linebuf, lines, columns, &num_content_lines_before, &num_content_lines_after, self->historybuf); if (n == NULL) return false; Py_CLEAR(self->main_linebuf); self->main_linebuf = n; - bool index_after_resize = false; - if (is_main) { - index_type cy = MIN(self->cursor->y, lines - 1); - linebuf_init_line(self->main_linebuf, cy); - if (is_x_shrink && (self->main_linebuf->continued_map[cy] || line_length(self->main_linebuf->line) > columns)) { - // If the client is in line drawing mode, it will redraw the cursor - // line, this can cause rendering artifacts, so ensure that the - // cursor is on a new line - index_after_resize = true; - } - self->cursor->y = MAX(0, cursor_y); - } - cursor_y = -1; - n = realloc_lb(self->alt_linebuf, lines, columns, &cursor_y, NULL); + if (is_main) adjust_cursor_position_after_resize(self->cursor, lines, columns, num_content_lines_before, num_content_lines_after); + n = realloc_lb(self->alt_linebuf, lines, columns, &num_content_lines_before, &num_content_lines_after, NULL); if (n == NULL) return false; Py_CLEAR(self->alt_linebuf); self->alt_linebuf = n; - if (!is_main) self->cursor->y = MAX(0, cursor_y); + if (!is_main) adjust_cursor_position_after_resize(self->cursor, lines, columns, num_content_lines_before, num_content_lines_after); self->linebuf = is_main ? self->main_linebuf : self->alt_linebuf; - if (is_x_shrink && cursor_x >= columns) self->cursor->x = columns - 1; self->lines = lines; self->columns = columns; self->margin_top = 0; self->margin_bottom = self->lines - 1; @@ -175,7 +171,6 @@ screen_resize(Screen *self, unsigned int lines, unsigned int columns) { self->is_dirty = true; self->selection = EMPTY_SELECTION; self->url_range = EMPTY_SELECTION; - if (index_after_resize) screen_index(self); return true; } diff --git a/kitty_tests/datatypes.py b/kitty_tests/datatypes.py index 140e51919..ba3aa9e1f 100644 --- a/kitty_tests/datatypes.py +++ b/kitty_tests/datatypes.py @@ -257,7 +257,7 @@ class TestDataTypes(BaseTest): def rewrap(self, lb, lb2): hb = HistoryBuf(lb2.ynum, lb2.xnum) cy = lb.rewrap(lb2, hb) - return hb, cy + return hb, cy[1] def test_rewrap_simple(self): ' Same width buffers ' @@ -268,7 +268,7 @@ class TestDataTypes(BaseTest): self.ae(lb2.line(i), lb.line(i)) lb2 = LineBuf(8, 5) cy = self.rewrap(lb, lb2)[1] - self.ae(cy, 4) + self.ae(cy, 5) for i in range(lb.ynum): self.ae(lb2.line(i), lb.line(i)) empty = LineBuf(1, lb2.xnum) @@ -276,7 +276,7 @@ class TestDataTypes(BaseTest): self.ae(str(lb2.line(i)), str(empty.line(0))) lb2 = LineBuf(3, 5) cy = self.rewrap(lb, lb2)[1] - self.ae(cy, 2) + self.ae(cy, 3) for i in range(lb2.ynum): self.ae(lb2.line(i), lb.line(i + 2)) diff --git a/kitty_tests/screen.py b/kitty_tests/screen.py index c657c4f40..8c902e997 100644 --- a/kitty_tests/screen.py +++ b/kitty_tests/screen.py @@ -223,14 +223,19 @@ class TestScreen(BaseTest): s.resize(5, 1) self.ae(str(s.line(0)), '4') hb = s.historybuf - for i in range(hb.ynum): - self.ae(str(hb.line(i)), '4' if i == 0 else '3') - s = self.create_screen(scrollback=6) - s.draw(''.join([str(i) * s.columns for i in range(s.lines*2)])) - self.ae(str(s.line(4)), '9'*5) + self.ae(str(hb), '3\n3\n3\n3\n3\n2') + s = self.create_screen(scrollback=20) + s.draw(''.join(str(i) * s.columns for i in range(s.lines*2))) + self.ae(str(s.linebuf), '55555\n66666\n77777\n88888\n99999') s.resize(5, 2) - self.ae(str(s.line(3)), '9') - self.ae(str(s.line(4)), '') + self.ae(str(s.linebuf), '88\n88\n99\n99\n9') + + def test_cursor_after_resize(self): + s = self.create_screen() + s.draw('123'), s.linefeed(), s.carriage_return(), s.draw('123'), s.linefeed(), s.carriage_return() + y_before = s.cursor.y + s.resize(s.lines, s.columns-1) + self.ae(y_before, s.cursor.y) def test_tab_stops(self): # Taken from vttest/main.c