From e10919496fcd08939c45233aedaab17bf82ea1b8 Mon Sep 17 00:00:00 2001 From: Eddie Lebow Date: Mon, 8 Mar 2021 22:01:21 -0500 Subject: [PATCH 1/6] Add scrollback_fill_enlarged_window config option --- kitty/config_data.py | 4 ++++ kitty/state.c | 1 + kitty/state.h | 1 + 3 files changed, 6 insertions(+) diff --git a/kitty/config_data.py b/kitty/config_data.py index de93938a6..292b8fe61 100644 --- a/kitty/config_data.py +++ b/kitty/config_data.py @@ -514,6 +514,10 @@ The current implementation stores the data in UTF-8, so approximatively 10000 lines per megabyte at 100 chars per line, for pure ASCII text, unformatted text. A value of zero or less disables this feature. The maximum allowed size is 4GB.''')) +o('scrollback_fill_enlarged_window', False, long_text=_(''' +Fill new space with lines from the scrollback buffer after enlarging a window. +''')) + o('wheel_scroll_multiplier', 5.0, long_text=_(''' Modify the amount scrolled by the mouse wheel. Note this is only used for low precision scrolling devices, not for high precision scrolling on platforms such diff --git a/kitty/state.c b/kitty/state.c index d174e0962..98fc832f7 100644 --- a/kitty/state.c +++ b/kitty/state.c @@ -688,6 +688,7 @@ PYWRAP1(set_options) { S(dynamic_background_opacity, PyObject_IsTrue); S(inactive_text_alpha, PyFloat_AsFloat); S(scrollback_pager_history_size, PyLong_AsUnsignedLong); + S(scrollback_fill_enlarged_window, PyObject_IsTrue); S(cursor_shape, PyLong_AsLong); S(cursor_beam_thickness, PyFloat_AsFloat); S(cursor_underline_thickness, PyFloat_AsFloat); diff --git a/kitty/state.h b/kitty/state.h index 1e0d237dc..042e1924e 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -32,6 +32,7 @@ typedef struct { unsigned int terminal_select_modifiers; unsigned int url_style; unsigned int scrollback_pager_history_size; + bool scrollback_fill_enlarged_window; char_type select_by_word_characters[256]; size_t select_by_word_characters_count; color_type url_color, background, foreground, active_border_color, inactive_border_color, bell_border_color; color_type mark1_foreground, mark1_background, mark2_foreground, mark2_background, mark3_foreground, mark3_background; From b76491ba8200d62ae532fa9fe74821ccb8190acd Mon Sep 17 00:00:00 2001 From: Eddie Lebow Date: Mon, 8 Mar 2021 23:51:27 -0500 Subject: [PATCH 2/6] Add tests for scrollback_fill_enlarged_window --- kitty_tests/__init__.py | 9 +++++++-- kitty_tests/screen.py | 42 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/kitty_tests/__init__.py b/kitty_tests/__init__.py index 7aa272484..5626cf067 100644 --- a/kitty_tests/__init__.py +++ b/kitty_tests/__init__.py @@ -91,10 +91,15 @@ class BaseTest(TestCase): options = Options(merge_configs(defaults._asdict(), final_options)) set_options(options) - def create_screen(self, cols=5, lines=5, scrollback=5, cell_width=10, cell_height=20, options=None): + def create_screen(self, cols=5, lines=5, scrollback=5, cell_width=10, cell_height=20, options=None, content=tuple()): self.set_options(options) c = Callbacks() - return Screen(c, lines, cols, scrollback, cell_width, cell_height, 0, c) + s = Screen(c, lines, cols, scrollback, cell_width, cell_height, 0, c) + for content_line in content: + s.draw(content_line) + s.linefeed() + s.carriage_return() + return s def assertEqualAttributes(self, c1, c2): x1, y1, c1.x, c1.y = c1.x, c1.y, 0, 0 diff --git a/kitty_tests/screen.py b/kitty_tests/screen.py index 0ac19d0b3..684ccb29d 100644 --- a/kitty_tests/screen.py +++ b/kitty_tests/screen.py @@ -303,6 +303,48 @@ class TestScreen(BaseTest): s.resize(s.lines - 1, s.columns) self.ae(x_before, s.cursor.x) + def test_scrollback_fill_after_resize(self): + def prepare_screen(content=tuple()): + return self.create_screen( + lines=5, + cols=5, + options={'scrollback_fill_enlarged_window': True}, + content=content, + ) + + def assert_lines(lines, s): + return self.ae(lines, str(s.linebuf).split("\n")) + + # Height increased, width unchanged → pull down lines to fill new space at the top + s = prepare_screen([str(i) for i in range(0, 6)]) + assert_lines(['2', '3', '4', '5', ''], s) + s.resize(7, s.columns) + assert_lines(['0', '1', '2', '3', '4', '5', ''], s) + + # Height increased, width increased → rewrap, pull down + s = prepare_screen(['0', '1', '2', '3' * 15]) + assert_lines(['2', '33333', '33333', '33333', ''], s) + s.resize(7, 12) + assert_lines(['0', '1', '2', '333333333333', '333', '', ''], s) + + # Height increased, width decreased → rewrap, pull down if possible + s = prepare_screen(['0', '1', '2', '3' * 5]) + assert_lines(['0', '1', '2', '33333', ''], s) + s.resize(6, 4) + assert_lines(['0', '1', '2', '3333', '3', ''], s) + + # Height unchanged, width increased → rewrap, pull down if possible + s = prepare_screen(['0', '1', '2', '3' * 15]) + assert_lines(['2', '33333', '33333', '33333', ''], s) + s.resize(s.lines, 12) + assert_lines(['1', '2', '333333333333', '333', ''], s) + + # Height decreased, width increased → rewrap, pull down if possible + s = prepare_screen(['0', '1', '2', '3' * 15]) + assert_lines(['2', '33333', '33333', '33333', ''], s) + s.resize(4, 12) + assert_lines(['2', '333333333333', '333', ''], s) + def test_tab_stops(self): # Taken from vttest/main.c s = self.create_screen(cols=80, lines=2) From 6c44b4f4513e11a81fb4a4ccb54d9b839bc1b2a6 Mon Sep 17 00:00:00 2001 From: Eddie Lebow Date: Tue, 9 Mar 2021 23:33:50 -0500 Subject: [PATCH 3/6] Add historybuf_pop_line() --- kitty/history.c | 13 +++++++++++++ kitty/lineops.h | 1 + 2 files changed, 14 insertions(+) diff --git a/kitty/history.c b/kitty/history.c index 1facaa556..e20749278 100644 --- a/kitty/history.c +++ b/kitty/history.c @@ -252,6 +252,19 @@ historybuf_add_line(HistoryBuf *self, const Line *line, ANSIBuf *as_ansi_buf) { *attrptr(self, idx) = (line->continued & CONTINUED_MASK) | (line->has_dirty_text ? TEXT_DIRTY_MASK : 0); } +bool +historybuf_pop_line(HistoryBuf *self, Line *line) { + if (self->count <= 0) + return false; + + index_type idx = (self->start_of_data + self->count-1) % self->ynum; + init_line(self, idx, line); + + self->count--; + + return true; +} + static PyObject* line(HistoryBuf *self, PyObject *val) { #define line_doc "Return the line with line number val. This buffer grows upwards, i.e. 0 is the most recently added line" diff --git a/kitty/lineops.h b/kitty/lineops.h index 5f350d283..c1ba8c24c 100644 --- a/kitty/lineops.h +++ b/kitty/lineops.h @@ -101,6 +101,7 @@ void linebuf_mark_line_as_not_continued(LineBuf *self, index_type y); unsigned int linebuf_char_width_at(LineBuf *self, index_type x, 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); CPUCell* historybuf_cpu_cells(HistoryBuf *self, index_type num); From c93c87e8fed088a24d53d778608e115aff1a5e37 Mon Sep 17 00:00:00 2001 From: Eddie Lebow Date: Thu, 11 Mar 2021 02:01:56 -0500 Subject: [PATCH 4/6] Add linebuf_add_line_to_top() --- kitty/line-buf.c | 7 +++++++ kitty/lineops.h | 1 + 2 files changed, 8 insertions(+) diff --git a/kitty/line-buf.c b/kitty/line-buf.c index 1a2cb4904..434d813d9 100644 --- a/kitty/line-buf.c +++ b/kitty/line-buf.c @@ -396,6 +396,13 @@ delete_lines(LineBuf *self, PyObject *args) { Py_RETURN_NONE; } +void +linebuf_add_line_to_top(LineBuf *self, Line *line) { + init_line(self, self->line, self->line_map[0]); + copy_line(line, self->line); + self->line_attrs[0] = TEXT_DIRTY_MASK | (line->continued ? CONTINUED_MASK : 0); +} + static PyObject* as_ansi(LineBuf *self, PyObject *callback) { #define as_ansi_doc "as_ansi(callback) -> The contents of this buffer as ANSI escaped text. callback is called with each successive line." diff --git a/kitty/lineops.h b/kitty/lineops.h index c1ba8c24c..6c58f61dd 100644 --- a/kitty/lineops.h +++ b/kitty/lineops.h @@ -93,6 +93,7 @@ void linebuf_reverse_index(LineBuf *self, index_type top, index_type bottom); 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_add_line_to_top(LineBuf *, Line *); void linebuf_set_attribute(LineBuf *, unsigned int , unsigned int ); void linebuf_rewrap(LineBuf *self, LineBuf *other, index_type *, index_type *, HistoryBuf *, index_type *, index_type *, ANSIBuf*); void linebuf_mark_line_dirty(LineBuf *self, index_type y); From 4682da83c128978f2b407c9d0be07f37a6f23d96 Mon Sep 17 00:00:00 2001 From: Eddie Lebow Date: Sun, 14 Mar 2021 01:49:15 -0500 Subject: [PATCH 5/6] Add linebuf_continued_lines_count() --- kitty/line-buf.c | 9 +++++++++ kitty/lineops.h | 1 + 2 files changed, 10 insertions(+) diff --git a/kitty/line-buf.c b/kitty/line-buf.c index 434d813d9..b0372436e 100644 --- a/kitty/line-buf.c +++ b/kitty/line-buf.c @@ -326,6 +326,15 @@ is_continued(LineBuf *self, PyObject *val) { Py_RETURN_FALSE; } +unsigned int +linebuf_continued_lines_count(LineBuf *self, index_type stop_at_line) { + unsigned int count = 0; + for (unsigned int i = 0; i < self->ynum && i < stop_at_line; i++) + if (self->line_attrs[i] & CONTINUED_MASK) count++; + + return count; +} + void linebuf_insert_lines(LineBuf *self, unsigned int num, unsigned int y, unsigned int bottom) { index_type i; diff --git a/kitty/lineops.h b/kitty/lineops.h index 6c58f61dd..63a0d2c34 100644 --- a/kitty/lineops.h +++ b/kitty/lineops.h @@ -91,6 +91,7 @@ void linebuf_clear(LineBuf *, char_type ch); void linebuf_index(LineBuf* self, index_type top, index_type bottom); void linebuf_reverse_index(LineBuf *self, index_type top, index_type bottom); void linebuf_clear_line(LineBuf *self, index_type y); +unsigned int linebuf_continued_lines_count(LineBuf *, index_type); 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_add_line_to_top(LineBuf *, Line *); From 9fff829ab4e8c09ab999812971e8a56f7a20cba9 Mon Sep 17 00:00:00 2001 From: Eddie Lebow Date: Fri, 12 Mar 2021 02:02:11 -0500 Subject: [PATCH 6/6] Implement scrollback_fill_enlarged_window --- kitty/screen.c | 43 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/kitty/screen.c b/kitty/screen.c index 226de7ec2..e94ce538c 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -212,6 +212,15 @@ realloc_lb(LineBuf *old, unsigned int lines, unsigned int columns, index_type *n return ans; } +#define INDEX_GRAPHICS(amtv) { \ + bool is_main = self->linebuf == self->main_linebuf; \ + static ScrollData s; \ + s.amt = amtv; s.limit = is_main ? -self->historybuf->ynum : 0; \ + s.has_margins = self->margin_top != 0 || self->margin_bottom != self->lines - 1; \ + s.margin_top = top; s.margin_bottom = bottom; \ + grman_scroll_images(self->grman, &s, self->cell_size); \ +} + static bool screen_resize(Screen *self, unsigned int lines, unsigned int columns) { if (self->overlay_line.is_active) deactivate_overlay_line(self); @@ -236,6 +245,31 @@ screen_resize(Screen *self, unsigned int lines, unsigned int columns) { index_type x = self->cursor->x, y = self->cursor->y; LineBuf *n = realloc_lb(self->main_linebuf, lines, columns, &num_content_lines_before, &num_content_lines_after, self->historybuf, &x, &y, &self->as_ansi_buf); if (n == NULL) return false; + + + if (OPT(scrollback_fill_enlarged_window)) { + int lines_to_fill = (lines - self->main_linebuf->ynum) \ + + (linebuf_continued_lines_count(self->main_linebuf, y) - linebuf_continued_lines_count(n, y)); + + while (lines_to_fill > 0) { + if (self->historybuf->count <= 0) break; + + unsigned int top = 0, bottom = lines-1; + + linebuf_reverse_index(n, top, bottom); + y++; + INDEX_GRAPHICS(1); + + Line last_history_line = {.xnum=self->historybuf->xnum}; + bool line_popped = historybuf_pop_line(self->historybuf, &last_history_line); + if (!line_popped) + break; + linebuf_add_line_to_top(n, &last_history_line); + + lines_to_fill--; + } + } + Py_CLEAR(self->main_linebuf); self->main_linebuf = n; if (is_main) setup_cursor(); grman_resize(self->main_grman, self->lines, lines, self->columns, columns); @@ -1044,15 +1078,6 @@ index_selection(const Screen *self, Selections *selections, bool up) { } } -#define INDEX_GRAPHICS(amtv) { \ - bool is_main = self->linebuf == self->main_linebuf; \ - static ScrollData s; \ - s.amt = amtv; s.limit = is_main ? -self->historybuf->ynum : 0; \ - s.has_margins = self->margin_top != 0 || self->margin_bottom != self->lines - 1; \ - s.margin_top = top; s.margin_bottom = bottom; \ - grman_scroll_images(self->grman, &s, self->cell_size); \ -} - #define INDEX_UP \ if (self->overlay_line.is_active) deactivate_overlay_line(self); \ linebuf_index(self->linebuf, top, bottom); \