From 38af14ff3ab73dc7e5184c0bfc65ad9ba683d07c Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 8 Aug 2022 21:14:27 -0700 Subject: [PATCH] resize alt screen without reflow --- src/terminal/Screen.zig | 122 ++++++++++++++++++++++++++++++++++++++ src/terminal/Terminal.zig | 12 ++-- 2 files changed, 129 insertions(+), 5 deletions(-) diff --git a/src/terminal/Screen.zig b/src/terminal/Screen.zig index fb22902f4..6d0f17419 100644 --- a/src/terminal/Screen.zig +++ b/src/terminal/Screen.zig @@ -819,10 +819,63 @@ pub fn resize(self: *Screen, alloc: Allocator, rows: usize, cols: usize) !void { const viewport_pos = pos.toViewport(self); self.cursor.x = viewport_pos.x; self.cursor.y = viewport_pos.y; + } else { + // TODO: why is this necessary? Without this, neovim will + // crash when we shrink the window to the smallest size + self.cursor.x = @minimum(self.cursor.x, self.cols - 1); + self.cursor.y = @minimum(self.cursor.y, self.rows - 1); } } } +/// Resize the screen without any reflow. In this mode, columns/rows will +/// be truncated as they are shrunk. If they are grown, the new space is filled +/// with zeros. +pub fn resizeWithoutReflow(self: *Screen, alloc: Allocator, rows: usize, cols: usize) !void { + // Resize without reflow not supported for now with scrollback. + assert(self.max_scrollback == 0); + + // Make a copy so we can access the old indexes. + const old = self.*; + + // Reallocate the storage + self.storage = try alloc.alloc(Cell, (rows + self.max_scrollback) * cols); + defer alloc.free(old.storage); + std.mem.set(Cell, self.storage, .{ .char = 0 }); + self.top = 0; + self.bottom = rows; + self.rows = rows; + self.cols = cols; + + // Move our cursor if we have to so it stays on the screen. + self.cursor.x = @minimum(self.cursor.x, self.cols - 1); + self.cursor.y = @minimum(self.cursor.y, self.rows - 1); + + // If we're increasing height, then copy all rows (start at 0). + // Otherwise start at the latest row that includes the bottom row, + // aka strip the top. + var y: usize = if (rows >= old.rows) 0 else old.rows - rows; + const start = y; + const col_end = @minimum(old.cols, cols); + while (y < old.rows) : (y += 1) { + // Copy the old row into the new row, just losing the columsn + // if we got thinner. + const old_row = old.getRow(.{ .viewport = y }); + const new_row = self.getRow(.{ .viewport = y - start }); + std.mem.copy(Cell, new_row, old_row[0..col_end]); + + // If our new row is wider, then we copy zeroes into the rest. + if (new_row.len > old_row.len) { + std.mem.set(Cell, new_row[old_row.len..], .{ .char = 0 }); + } + } + + // If we grew rows, then set the remaining data to zero. + if (rows > old.rows) { + std.mem.set(Cell, self.storage[self.rowIndex(.{ .viewport = old.rows })..], .{ .char = 0 }); + } +} + /// Returns the raw text associated with a selection. This will unwrap /// soft-wrapped edges. The returned slice is owned by the caller. pub fn selectionString(self: Screen, alloc: Allocator, sel: Selection) ![:0]const u8 { @@ -1897,3 +1950,72 @@ test "Screen: resize more rows then shrink again" { try testing.expectEqualStrings(str, contents); } } + +test "Screen: resize (no reflow) more rows" { + const testing = std.testing; + const alloc = testing.allocator; + + var s = try init(alloc, 3, 5, 0); + defer s.deinit(alloc); + const str = "1ABCD\n2EFGH\n3IJKL"; + s.testWriteString(str); + try s.resizeWithoutReflow(alloc, 10, 5); + + { + var contents = try s.testString(alloc, .viewport); + defer alloc.free(contents); + try testing.expectEqualStrings(str, contents); + } +} + +test "Screen: resize (no reflow) less rows" { + const testing = std.testing; + const alloc = testing.allocator; + + var s = try init(alloc, 3, 5, 0); + defer s.deinit(alloc); + const str = "1ABCD\n2EFGH\n3IJKL"; + s.testWriteString(str); + try s.resizeWithoutReflow(alloc, 2, 5); + + { + var contents = try s.testString(alloc, .viewport); + defer alloc.free(contents); + try testing.expectEqualStrings("2EFGH\n3IJKL", contents); + } +} + +test "Screen: resize (no reflow) more cols" { + const testing = std.testing; + const alloc = testing.allocator; + + var s = try init(alloc, 3, 5, 0); + defer s.deinit(alloc); + const str = "1ABCD\n2EFGH\n3IJKL"; + s.testWriteString(str); + try s.resizeWithoutReflow(alloc, 3, 10); + + { + var contents = try s.testString(alloc, .viewport); + defer alloc.free(contents); + try testing.expectEqualStrings(str, contents); + } +} + +test "Screen: resize (no reflow) less cols" { + const testing = std.testing; + const alloc = testing.allocator; + + var s = try init(alloc, 3, 5, 0); + defer s.deinit(alloc); + const str = "1ABCD\n2EFGH\n3IJKL"; + s.testWriteString(str); + try s.resizeWithoutReflow(alloc, 3, 4); + + { + var contents = try s.testString(alloc, .viewport); + defer alloc.free(contents); + const expected = "1ABC\n2EFG\n3IJK"; + try testing.expectEqualStrings(expected, contents); + } +} diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 6c37a80d9..179ee743c 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -214,8 +214,6 @@ pub fn resize(self: *Terminal, alloc: Allocator, cols_req: usize, rows: usize) ! const tracy = trace(@src()); defer tracy.end(); - // TODO: test, wrapping, etc. - // If we have deccolm supported then we are fixed at either 80 or 132 // columns depending on if mode 3 is set or not. // TODO: test @@ -232,9 +230,13 @@ pub fn resize(self: *Terminal, alloc: Allocator, cols_req: usize, rows: usize) ! } // If we're making the screen smaller, dealloc the unused items. - // TODO: reflow - try self.screen.resize(alloc, rows, cols); - try self.secondary_screen.resize(alloc, rows, cols); + if (self.active_screen == .primary) { + try self.screen.resize(alloc, rows, cols); + try self.secondary_screen.resizeWithoutReflow(alloc, rows, cols); + } else { + try self.screen.resizeWithoutReflow(alloc, rows, cols); + try self.secondary_screen.resize(alloc, rows, cols); + } // Set our size self.cols = cols;