diff --git a/src/terminal/new/PageList.zig b/src/terminal/new/PageList.zig index 102093beb..cd1d2e436 100644 --- a/src/terminal/new/PageList.zig +++ b/src/terminal/new/PageList.zig @@ -364,7 +364,7 @@ fn createPage(self: *PageList) !*List.Node { /// If the top or bottom point is in the middle of a page, the other /// contents in the page will be preserved but the page itself will be /// underutilized (size < capacity). -pub fn erase( +pub fn eraseRows( self: *PageList, tl_pt: point.Point, bl_pt: ?point.Point, @@ -388,7 +388,7 @@ pub fn erase( const rows = chunk.page.data.rows.ptr(chunk.page.data.memory); const scroll_amount = chunk.page.data.size.rows - chunk.end; for (0..scroll_amount) |i| { - const src: *Row = &rows[i + scroll_amount]; + const src: *Row = &rows[i + chunk.end]; const dst: *Row = &rows[i]; const old_dst = dst.*; dst.* = src.*; @@ -400,6 +400,17 @@ pub fn erase( // be written to again (its in the past) or it will grow and the // terminal erase will automatically erase the data. + // If our viewport is on this page and the offset is beyond + // our new end, shift it. + switch (self.viewport) { + .top, .active => {}, + .exact => |*offset| exact: { + if (offset.page != chunk.page) break :exact; + offset.row_offset -|= scroll_amount; + }, + } + + // Our new size is the amount we scrolled chunk.page.data.size.rows = @intCast(scroll_amount); } } @@ -407,6 +418,20 @@ pub fn erase( /// Erase a single page, freeing all its resources. The page can be /// anywhere in the linked list. fn erasePage(self: *PageList, page: *List.Node) void { + // If our viewport is pinned to this page, then we need to update it. + switch (self.viewport) { + .top, .active => {}, + .exact => |*offset| { + if (offset.page == page) { + if (page.next) |next| { + offset.page = next; + } else { + self.viewport = .{ .active = {} }; + } + } + }, + } + // Remove the page from the linked list self.pages.remove(page); @@ -1276,6 +1301,28 @@ test "PageList erase" { try testing.expect(s.totalRows() > s.rows); // Erase the entire history, we should be back to just our active set. - s.erase(.{ .history = .{} }, null); + s.eraseRows(.{ .history = .{} }, null); try testing.expectEqual(s.rows, s.totalRows()); } + +test "PageList erase resets viewport if inside erased page" { + const testing = std.testing; + const alloc = testing.allocator; + + var s = try init(alloc, 80, 24, null); + defer s.deinit(); + + // Grow so we take up at least 5 pages. + const page = &s.pages.last.?.data; + for (0..page.capacity.rows * 5) |_| { + _ = try s.grow(); + } + + // Move our viewport to the top + s.scroll(.{ .delta_row = -@as(isize, @intCast(s.totalRows())) }); + try testing.expect(s.viewport.exact.page == s.pages.first.?); + + // Erase the entire history, we should be back to just our active set. + s.eraseRows(.{ .history = .{} }, null); + try testing.expect(s.viewport.exact.page == s.pages.first.?); +} diff --git a/src/terminal/new/Screen.zig b/src/terminal/new/Screen.zig index d533bf579..41035ef45 100644 --- a/src/terminal/new/Screen.zig +++ b/src/terminal/new/Screen.zig @@ -299,6 +299,23 @@ pub fn scrollClear(self: *Screen) !void { self.kitty_images.dirty = true; } +/// Erase the region specified by tl and br, inclusive. This will physically +/// erase the rows meaning the memory will be reclaimed (if the underlying +/// page is empty) and other rows will be shifted up. +pub fn eraseRows( + self: *Screen, + tl: point.Point, + bl: ?point.Point, +) void { + // Erase the rows + self.pages.eraseRows(tl, bl); + + // Just to be safe, reset our cursor since it is possible depending + // on the points that our active area shifted so our pointers are + // invalid. + //self.cursorAbsolute(self.cursor.x, self.cursor.y); +} + // Clear the region specified by tl and bl, inclusive. Cleared cells are // colored with the current style background color. This will clear all // cells in the rows. @@ -844,3 +861,71 @@ test "Screen clearRows active styled line" { defer alloc.free(str); try testing.expectEqualStrings("", str); } + +test "Terminal: eraseRows history" { + const testing = std.testing; + const alloc = testing.allocator; + + var s = try Screen.init(alloc, 5, 5, 1000); + defer s.deinit(); + + try s.testWriteString("1\n2\n3\n4\n5\n6"); + + { + const str = try s.dumpStringAlloc(alloc, .{ .active = .{} }); + defer alloc.free(str); + try testing.expectEqualStrings("2\n3\n4\n5\n6", str); + } + { + const str = try s.dumpStringAlloc(alloc, .{ .screen = .{} }); + defer alloc.free(str); + try testing.expectEqualStrings("1\n2\n3\n4\n5\n6", str); + } + + s.eraseRows(.{ .history = .{} }, null); + + { + const str = try s.dumpStringAlloc(alloc, .{ .active = .{} }); + defer alloc.free(str); + try testing.expectEqualStrings("2\n3\n4\n5\n6", str); + } + { + const str = try s.dumpStringAlloc(alloc, .{ .screen = .{} }); + defer alloc.free(str); + try testing.expectEqualStrings("2\n3\n4\n5\n6", str); + } +} + +test "Terminal: eraseRows history with more lines" { + const testing = std.testing; + const alloc = testing.allocator; + + var s = try Screen.init(alloc, 5, 5, 1000); + defer s.deinit(); + + try s.testWriteString("A\nB\nC\n1\n2\n3\n4\n5\n6"); + + { + const str = try s.dumpStringAlloc(alloc, .{ .active = .{} }); + defer alloc.free(str); + try testing.expectEqualStrings("2\n3\n4\n5\n6", str); + } + { + const str = try s.dumpStringAlloc(alloc, .{ .screen = .{} }); + defer alloc.free(str); + try testing.expectEqualStrings("A\nB\nC\n1\n2\n3\n4\n5\n6", str); + } + + s.eraseRows(.{ .history = .{} }, null); + + { + const str = try s.dumpStringAlloc(alloc, .{ .active = .{} }); + defer alloc.free(str); + try testing.expectEqualStrings("2\n3\n4\n5\n6", str); + } + { + const str = try s.dumpStringAlloc(alloc, .{ .screen = .{} }); + defer alloc.free(str); + try testing.expectEqualStrings("2\n3\n4\n5\n6", str); + } +} diff --git a/src/terminal/new/Terminal.zig b/src/terminal/new/Terminal.zig index 2a97432bc..0fa5e3003 100644 --- a/src/terminal/new/Terminal.zig +++ b/src/terminal/new/Terminal.zig @@ -1714,13 +1714,8 @@ pub fn eraseDisplay( // Unsets pending wrap state assert(!self.screen.cursor.pending_wrap); }, - // - // .scrollback => self.screen.clear(.history) catch |err| { - // // This isn't a huge issue, so just log it. - // log.err("failed to clear scrollback: {}", .{err}); - // }, - else => @panic("TODO"), + .scrollback => self.screen.eraseRows(.{ .history = .{} }, null), } }