diff --git a/src/terminal/Screen.zig b/src/terminal/Screen.zig index 856d36a3f..7d9fd76be 100644 --- a/src/terminal/Screen.zig +++ b/src/terminal/Screen.zig @@ -431,6 +431,51 @@ pub fn copyRow(self: *Screen, dst: usize, src: usize) void { std.mem.copy(Cell, dst_row, src_row); } +/// Resize the screen. The rows or cols can be bigger or smaller. This +/// function can only be used to resize the viewport. The scrollback size +/// (in lines) can't be changed. But due to the resize, more or less scrollback +/// "space" becomes available due to the width of lines. +/// +/// Due to the internal representation of a screen, this usually involves a +/// significant amount of copying compared to any other operations. +/// +/// This will trim data if the size is getting smaller. This will reflow the +/// soft wrapped text. +pub fn resize2(self: *Screen, alloc: Allocator, rows: usize, cols: usize) !void { + _ = cols; + + // We always grow first so we don't lose any data. + var storage = self.storage; + if (rows > self.rows) { + storage = try alloc.alloc( + Cell, + (rows + self.max_scrollback) * cols, + ); + + // Copy our screen into the new storage area. Since we're growing + // rows, we know that the full buffer will fit so we copy it in + // order. + const reg = self.region(.screen); + std.mem.copy(Cell, storage, reg[0]); + std.mem.copy(Cell, storage[reg[0].len..], reg[1]); + std.mem.set(Cell, storage[reg[0].len + reg[1].len ..], .{ .char = 0 }); + + // Modify our storage, our lines have grown + alloc.free(self.storage); + self.storage = storage; + + // Fix our row count + self.rows = rows; + + // Top is now 0 because we reoriented the ring buffer to be ordered. + // Bottom must be at least "rows" since we always show at least that + // much in the viewport. + self.top = 0; + self.bottom = @maximum(rows, self.bottom); + self.scroll(.{ .bottom = {} }); + } +} + /// Resize the screen. The rows or cols can be bigger or smaller. This /// function can only be used to resize the viewport. The scrollback size /// (in lines) can't be changed. But due to the resize, more or less scrollback @@ -608,13 +653,15 @@ fn selectionSlices(self: Screen, sel: Selection) struct { }; } -/// Turns the screen into a string. -pub fn testString(self: Screen, alloc: Allocator) ![]const u8 { +/// Turns the screen into a string. Different regions of the screen can +/// be selected using the "tag", i.e. if you want to output the viewport, +/// the scrollback, the full screen, etc. +pub fn testString(self: Screen, alloc: Allocator, tag: RowIndexTag) ![]const u8 { const buf = try alloc.alloc(u8, self.storage.len + self.rows); var i: usize = 0; var y: usize = 0; - var rows = self.rowIterator(.viewport); + var rows = self.rowIterator(tag); while (rows.next()) |row| { defer y += 1; @@ -642,16 +689,23 @@ pub fn testString(self: Screen, alloc: Allocator) ![]const u8 { fn testWriteString(self: *Screen, text: []const u8) void { var y: usize = 0; var x: usize = 0; - var row = self.getRow(.{ .active = y }); for (text) |c| { // Explicit newline forces a new row if (c == '\n') { y += 1; x = 0; - row = self.getRow(.{ .active = y }); continue; } + // If we're writing past the end of the active area, scroll. + if (y >= self.rows) { + y -= 1; + self.scroll(.{ .delta = 1 }); + } + + // Get our row + var row = self.getRow(.{ .active = y }); + // If we're writing past the end, we need to soft wrap. if (x == self.cols) { row[x - 1].attrs.wrap = 1; @@ -676,7 +730,7 @@ test "Screen" { const str = "1ABCD\n2EFGH\n3IJKL"; s.testWriteString(str); { - var contents = try s.testString(alloc); + var contents = try s.testString(alloc, .screen); defer alloc.free(contents); try testing.expectEqualStrings(str, contents); } @@ -699,7 +753,7 @@ test "Screen" { std.mem.set(Cell, reg[0], .{ .char = 'A' }); std.mem.set(Cell, reg[1], .{ .char = 'A' }); { - var contents = try s.testString(alloc); + var contents = try s.testString(alloc, .screen); defer alloc.free(contents); try testing.expectEqualStrings("AAAAA\nAAAAA\nAAAAA", contents); } @@ -726,7 +780,7 @@ test "Screen: scrolling" { { // Test our contents rotated - var contents = try s.testString(alloc); + var contents = try s.testString(alloc, .viewport); defer alloc.free(contents); try testing.expectEqualStrings("2EFGH\n3IJKL", contents); } @@ -736,7 +790,7 @@ test "Screen: scrolling" { { // Test our contents rotated - var contents = try s.testString(alloc); + var contents = try s.testString(alloc, .viewport); defer alloc.free(contents); try testing.expectEqualStrings("2EFGH\n3IJKL", contents); } @@ -775,7 +829,7 @@ test "Screen: scroll down from 0" { { // Test our contents rotated - var contents = try s.testString(alloc); + var contents = try s.testString(alloc, .viewport); defer alloc.free(contents); try testing.expectEqualStrings("1ABCD\n2EFGH\n3IJKL", contents); } @@ -797,7 +851,7 @@ test "Screen: scrollback" { { // Test our contents rotated - var contents = try s.testString(alloc); + var contents = try s.testString(alloc, .viewport); defer alloc.free(contents); try testing.expectEqualStrings("2EFGH\n3IJKL", contents); } @@ -808,7 +862,7 @@ test "Screen: scrollback" { { // Test our contents rotated - var contents = try s.testString(alloc); + var contents = try s.testString(alloc, .viewport); defer alloc.free(contents); try testing.expectEqualStrings("2EFGH\n3IJKL", contents); } @@ -819,7 +873,7 @@ test "Screen: scrollback" { { // Test our contents rotated - var contents = try s.testString(alloc); + var contents = try s.testString(alloc, .viewport); defer alloc.free(contents); try testing.expectEqualStrings("1ABCD\n2EFGH\n3IJKL", contents); } @@ -829,7 +883,7 @@ test "Screen: scrollback" { { // Test our contents rotated - var contents = try s.testString(alloc); + var contents = try s.testString(alloc, .viewport); defer alloc.free(contents); try testing.expectEqualStrings("1ABCD\n2EFGH\n3IJKL", contents); } @@ -839,7 +893,7 @@ test "Screen: scrollback" { { // Test our contents rotated - var contents = try s.testString(alloc); + var contents = try s.testString(alloc, .viewport); defer alloc.free(contents); try testing.expectEqualStrings("2EFGH\n3IJKL", contents); } @@ -849,7 +903,7 @@ test "Screen: scrollback" { { // Test our contents rotated - var contents = try s.testString(alloc); + var contents = try s.testString(alloc, .viewport); defer alloc.free(contents); try testing.expectEqualStrings("2EFGH\n3IJKL", contents); } @@ -859,7 +913,7 @@ test "Screen: scrollback" { { // Test our contents rotated - var contents = try s.testString(alloc); + var contents = try s.testString(alloc, .viewport); defer alloc.free(contents); try testing.expectEqualStrings("1ABCD\n2EFGH\n3IJKL", contents); } @@ -869,7 +923,7 @@ test "Screen: scrollback" { std.mem.set(Cell, reg[0], .{ .char = 0 }); std.mem.set(Cell, reg[1], .{ .char = 0 }); { - var contents = try s.testString(alloc); + var contents = try s.testString(alloc, .viewport); defer alloc.free(contents); try testing.expectEqualStrings("1ABCD", contents); } @@ -879,7 +933,7 @@ test "Screen: scrollback" { { // Test our contents rotated - var contents = try s.testString(alloc); + var contents = try s.testString(alloc, .viewport); defer alloc.free(contents); try testing.expectEqualStrings("", contents); } @@ -896,7 +950,7 @@ test "Screen: scrollback empty" { { // Test our contents - var contents = try s.testString(alloc); + var contents = try s.testString(alloc, .viewport); defer alloc.free(contents); try testing.expectEqualStrings("1ABCD\n2EFGH\n3IJKL", contents); } @@ -915,7 +969,7 @@ test "Screen: row copy" { s.copyRow(2, 0); // Test our contents - var contents = try s.testString(alloc); + var contents = try s.testString(alloc, .viewport); defer alloc.free(contents); try testing.expectEqualStrings("2EFGH\n3IJKL\n2EFGH", contents); } @@ -931,7 +985,7 @@ test "Screen: resize more rows" { try s.resize(alloc, 10, 5); { - var contents = try s.testString(alloc); + var contents = try s.testString(alloc, .viewport); defer alloc.free(contents); try testing.expectEqualStrings(str, contents); } @@ -948,7 +1002,7 @@ test "Screen: resize less rows" { try s.resize(alloc, 2, 5); { - var contents = try s.testString(alloc); + var contents = try s.testString(alloc, .viewport); defer alloc.free(contents); try testing.expectEqualStrings("2EFGH\n3IJKL", contents); } @@ -965,7 +1019,7 @@ test "Screen: resize more cols" { try s.resize(alloc, 3, 10); { - var contents = try s.testString(alloc); + var contents = try s.testString(alloc, .viewport); defer alloc.free(contents); try testing.expectEqualStrings(str, contents); } @@ -982,7 +1036,7 @@ test "Screen: resize less cols" { try s.resize(alloc, 3, 4); { - var contents = try s.testString(alloc); + var contents = try s.testString(alloc, .viewport); defer alloc.free(contents); const expected = "1ABC\n2EFG\n3IJK"; try testing.expectEqualStrings(expected, contents); @@ -1055,3 +1109,75 @@ test "Screen: selectionString wrap around" { try testing.expectEqualStrings(expected, contents); } } + +// ---------------------------------------------------------------------------- +// NEW RESIZE TESTS + +test "Screen: resize more rows no scrollback" { + 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.resize2(alloc, 10, 5); + + { + var contents = try s.testString(alloc, .viewport); + defer alloc.free(contents); + try testing.expectEqualStrings(str, contents); + } + { + var contents = try s.testString(alloc, .screen); + defer alloc.free(contents); + try testing.expectEqualStrings(str, contents); + } +} + +test "Screen: resize more rows with empty scrollback" { + const testing = std.testing; + const alloc = testing.allocator; + + var s = try init(alloc, 3, 5, 10); + defer s.deinit(alloc); + const str = "1ABCD\n2EFGH\n3IJKL"; + s.testWriteString(str); + try s.resize2(alloc, 10, 5); + + { + var contents = try s.testString(alloc, .viewport); + defer alloc.free(contents); + try testing.expectEqualStrings(str, contents); + } + { + var contents = try s.testString(alloc, .screen); + defer alloc.free(contents); + try testing.expectEqualStrings(str, contents); + } +} + +test "Screen: resize more rows with populated scrollback" { + const testing = std.testing; + const alloc = testing.allocator; + + var s = try init(alloc, 3, 5, 5); + defer s.deinit(alloc); + const str = "1ABCD\n2EFGH\n3IJKL\n4ABCD\n5EFGH"; + s.testWriteString(str); + { + var contents = try s.testString(alloc, .viewport); + defer alloc.free(contents); + const expected = "3IJKL\n4ABCD\n5EFGH"; + try testing.expectEqualStrings(expected, contents); + } + + // Resize + try s.resize2(alloc, 10, 5); + + { + var contents = try s.testString(alloc, .viewport); + defer alloc.free(contents); + try testing.expectEqualStrings(str, contents); + } +} diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 775ca0452..6c37a80d9 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -252,7 +252,7 @@ pub fn resize(self: *Terminal, alloc: Allocator, cols_req: usize, rows: usize) ! /// /// The caller must free the string. pub fn plainString(self: Terminal, alloc: Allocator) ![]const u8 { - return try self.screen.testString(alloc); + return try self.screen.testString(alloc, .viewport); } /// Save cursor position and further state.