diff --git a/docs/changelog.rst b/docs/changelog.rst index 77a71dd6d..174da49ce 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -176,6 +176,8 @@ Detailed list of changes 0.47.1 [future] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +- Preserve user-set tab stops across window resizes (previously they were reset to every 8 columns on every resize) + - Fix a regression in the previous release that caused :ac:`copy_or_noop` to stop working correctly (:pull:`10041`) - macOS: Fix a regression in the previous release that caused URLs to be quoted when dropping into shells (:iss:`10054`) diff --git a/kitty/screen.c b/kitty/screen.c index 34e016194..4f84d3df5 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -575,16 +575,26 @@ screen_resize(Screen *self, unsigned int lines, unsigned int columns) { grman_resize(self->alt_grman, self->lines, lines, self->columns, columns, num_content_lines_before, num_content_lines_after); #undef setup_cursor /* printf("\nold_size: (%u, %u) new_size: (%u, %u)\n", self->columns, self->lines, columns, lines); */ + index_type old_columns_for_tabs = self->columns; self->lines = lines; self->columns = columns; self->margin_top = 0; self->margin_bottom = self->lines - 1; - PyMem_Free(self->main_tabstops); - self->main_tabstops = PyMem_Calloc(2*self->columns, sizeof(bool)); - if (self->main_tabstops == NULL) { PyErr_NoMemory(); return false; } - self->alt_tabstops = self->main_tabstops + self->columns; - self->tabstops = self->main_tabstops; - init_tabstops(self->main_tabstops, self->columns); - init_tabstops(self->alt_tabstops, self->columns); + bool *old_tabstops = self->main_tabstops; + bool *new_tabstops = PyMem_Calloc(2 * self->columns, sizeof(bool)); + if (new_tabstops == NULL) { PyErr_NoMemory(); return false; } + bool *new_main = new_tabstops; + bool *new_alt = new_tabstops + self->columns; + init_tabstops(new_main, self->columns); + init_tabstops(new_alt, self->columns); + if (old_tabstops && old_columns_for_tabs) { + index_type to_copy = MIN(old_columns_for_tabs, self->columns); + memcpy(new_main, old_tabstops, to_copy * sizeof(bool)); + memcpy(new_alt, old_tabstops + old_columns_for_tabs, to_copy * sizeof(bool)); + } + PyMem_Free(old_tabstops); + self->main_tabstops = new_main; + self->alt_tabstops = new_alt; + self->tabstops = is_main ? self->main_tabstops : self->alt_tabstops; self->is_dirty = true; clear_all_selections(self); self->last_visited_prompt.is_set = false; diff --git a/kitty_tests/screen.py b/kitty_tests/screen.py index b58c5d9a3..38d265147 100644 --- a/kitty_tests/screen.py +++ b/kitty_tests/screen.py @@ -531,6 +531,34 @@ class TestScreen(BaseTest): s.draw('aaaX\tbbbb') self.ae(str(s.line(0)) + str(s.line(1)), 'aaaXbbbb') + # Custom tab stops survive a window resize + s = self.create_screen(cols=20, lines=2) + s.clear_tab_stop(3) # clear all + s.cursor_position(1, 5) + s.set_tab_stop() # stop at column index 4 + s.cursor_position(1, 13) + s.set_tab_stop() # stop at column index 12 + # Grow: existing stops preserved, new columns get default every-8 stops + s.resize(s.lines, 30) + s.cursor_position(1, 1) + s.tab(); self.ae(s.cursor.x, 4) + s.tab(); self.ae(s.cursor.x, 12) + s.tab(); self.ae(s.cursor.x, 24) # default stop in newly added columns + # Shrink: stops within new width are preserved + s.resize(s.lines, 15) + s.cursor_position(1, 1) + s.tab(); self.ae(s.cursor.x, 4) + s.tab(); self.ae(s.cursor.x, 12) + # Resize on alt screen also preserves alt-screen tab stops + s = self.create_screen(cols=20, lines=2) + parse_bytes(s, b'\x1b[?1049h') # switch to alt screen + s.clear_tab_stop(3) + s.cursor_position(1, 5) + s.set_tab_stop() + s.resize(s.lines, 30) + s.cursor_position(1, 1) + s.tab(); self.ae(s.cursor.x, 4) + def test_backspace(self): s = self.create_screen() q = 'a'*s.columns