diff --git a/TODO.md b/TODO.md index b1e2aa5f0..c3a361be2 100644 --- a/TODO.md +++ b/TODO.md @@ -1,6 +1,8 @@ Bugs: * Underline should use freetype underline thickness hint +* Any printing action forces scroll to jump to bottom, this makes it impossible + to scroll up while logs are coming in or something Performance: diff --git a/src/terminal/Screen.zig b/src/terminal/Screen.zig index e049be38c..0563f0c1c 100644 --- a/src/terminal/Screen.zig +++ b/src/terminal/Screen.zig @@ -121,6 +121,15 @@ pub fn deinit(self: *Screen, alloc: Allocator) void { self.* = undefined; } +/// This returns true if the display area is anchored at the bottom currently. +pub fn displayIsBottom(self: Screen) bool { + return self.visible_offset == self.bottomOffset(); +} + +fn bottomOffset(self: Screen) usize { + return self.bottom - self.rows; +} + /// Returns an iterator that can be used to iterate over all of the rows /// from index zero. pub fn rowIterator(self: *const Screen) RowIterator { @@ -429,7 +438,12 @@ test "Screen: scrolling" { var s = try init(alloc, 3, 5, 0); defer s.deinit(alloc); s.testWriteString("1ABCD\n2EFGH\n3IJKL"); + + try testing.expect(s.displayIsBottom()); + + // Scroll down, should still be bottom s.scroll(.{ .delta = 1 }); + try testing.expect(s.displayIsBottom()); // Test our row index try testing.expectEqual(@as(usize, 5), s.rowIndex(0)); @@ -462,6 +476,7 @@ test "Screen: scroll down from 0" { defer s.deinit(alloc); s.testWriteString("1ABCD\n2EFGH\n3IJKL"); s.scroll(.{ .delta = -1 }); + try testing.expect(s.displayIsBottom()); { // Test our contents rotated @@ -494,6 +509,7 @@ test "Screen: scrollback" { // Scrolling to the bottom s.scroll(.{ .bottom = {} }); + try testing.expect(s.displayIsBottom()); { // Test our contents rotated @@ -504,6 +520,7 @@ test "Screen: scrollback" { // Scrolling back should make it visible again s.scroll(.{ .delta = -1 }); + try testing.expect(!s.displayIsBottom()); { // Test our contents rotated diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index d92a00abe..63515e60e 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -203,6 +203,9 @@ pub fn print(self: *Terminal, c: u21) !void { const tracy = trace(@src()); defer tracy.end(); + // If we're not at the bottom, then we need to move there + if (!self.screen.displayIsBottom()) self.screen.scroll(.{ .bottom = {} }); + // If we're soft-wrapping, then handle that first. if (self.cursor.pending_wrap and self.mode_autowrap) { // Mark that the cell is wrapped, which guarantees that there is @@ -691,12 +694,15 @@ pub fn scrollDown(self: *Terminal, count: usize) void { /// Options for scrolling the viewport of the terminal grid. pub const ScrollViewport = union(enum) { + /// Scroll to the top of the scrollback + top: void, delta: isize, }; /// Scroll the viewport of the terminal grid. pub fn scrollViewport(self: *Terminal, behavior: ScrollViewport) void { self.screen.scroll(switch (behavior) { + .top => .{ .top = {} }, .delta => |delta| .{ .delta_no_grow = delta }, }); } @@ -766,6 +772,41 @@ test "Terminal: soft wrap" { } } +test "Terminal: print scrolls back to bottom" { + var t = try init(testing.allocator, 5, 2); + defer t.deinit(testing.allocator); + + // Basic grid writing + for ("hello") |c| try t.print(c); + + // Make newlines so we create scrollback + // 3 pushes hello off the screen + t.index(); + t.index(); + t.index(); + { + var str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("", str); + } + + // Scroll to the top + t.scrollViewport(.{ .top = {} }); + { + var str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("hello", str); + } + + // Type + try t.print('A'); + { + var str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("\nA", str); + } +} + test "Terminal: linefeed and carriage return" { var t = try init(testing.allocator, 80, 80); defer t.deinit(testing.allocator);