diff --git a/src/bench/stream-new.sh b/src/bench/stream-new.sh new file mode 100755 index 000000000..69a1c1990 --- /dev/null +++ b/src/bench/stream-new.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# +# This is a trivial helper script to help run the stream benchmark. +# You probably want to tweak this script depending on what you're +# trying to measure. + +# Options: +# - "ascii", uniform random ASCII bytes +# - "utf8", uniform random unicode characters, encoded as utf8 +# - "rand", pure random data, will contain many invalid code sequences. +DATA="ascii" +SIZE="25000000" + +# Uncomment to test with an active terminal state. +# ARGS=" --terminal" + +# Generate the benchmark input ahead of time so it's not included in the time. +./zig-out/bin/bench-stream --mode=gen-$DATA | head -c $SIZE > /tmp/ghostty_bench_data + +# Uncomment to instead use the contents of `stream.txt` as input. (Ignores SIZE) +# echo $(cat ./stream.txt) > /tmp/ghostty_bench_data + +hyperfine \ + --warmup 10 \ + -n noop \ + "./zig-out/bin/bench-stream --mode=noop = rows) { - n_left -= rows; + var n_left: usize = n - rows; + while (true) { page = page.next orelse return .{ .overflow = .{ .end = .{ .page = page, .row_offset = page.data.size.rows - 1 }, .remaining = n_left, } }; - rows = page.data.size.rows; + if (n_left <= page.data.size.rows) return .{ .offset = .{ + .page = page, + .row_offset = n_left - 1, + } }; + n_left -= page.data.size.rows; } - - return .{ .offset = .{ - .page = page, - .row_offset = n_left, - } }; } }; @@ -378,7 +380,7 @@ test "PageList" { try testing.expectEqual(s.active, s.viewport); } -test "scrollDown utilizes capacity" { +test "scrollActive utilizes capacity" { const testing = std.testing; const alloc = testing.allocator; @@ -391,7 +393,7 @@ test "scrollDown utilizes capacity" { try testing.expect(s.active.row_offset == 0); try testing.expectEqual(@as(size.CellCountInt, 1), s.active.page.data.size.rows); - try s.scrollDown(1); + try s.scrollActive(1); // We should not allocate a new page because we have enough capacity try testing.expect(s.active.page == s.pages.first); @@ -400,7 +402,7 @@ test "scrollDown utilizes capacity" { try testing.expectEqual(@as(size.CellCountInt, 2), s.active.page.data.size.rows); } -test "scrollDown adds new pages" { +test "scrollActive adds new pages" { const testing = std.testing; const alloc = testing.allocator; @@ -414,7 +416,7 @@ test "scrollDown adds new pages" { // The initial active is a single page so scrolling down even one // should force the allocation of an entire new page. - try s.scrollDown(1); + try s.scrollActive(1); // We should still be on the first page but offset, and we should // have a second page created. diff --git a/src/terminal/new/Screen.zig b/src/terminal/new/Screen.zig index 926825e39..fcf104ba3 100644 --- a/src/terminal/new/Screen.zig +++ b/src/terminal/new/Screen.zig @@ -114,6 +114,24 @@ pub fn cursorHorizontalAbsolute(self: *Screen, x: size.CellCountInt) void { self.cursor.x = x; } +/// Scroll the active area and keep the cursor at the bottom of the screen. +/// This is a very specialized function but it keeps it fast. +pub fn cursorDownScroll(self: *Screen) !void { + assert(self.cursor.y == self.pages.rows - 1); + + // We move the viewport to the active if we're already there to start. + const move_viewport = self.pages.viewport.eql(self.pages.active); + + try self.pages.scrollActive(1); + const page_offset = self.pages.active.forward(self.cursor.y).?; + const page_rac = page_offset.rowAndCell(self.cursor.x); + self.cursor.page_offset = page_offset; + self.cursor.page_row = page_rac.row; + self.cursor.page_cell = page_rac.cell; + + if (move_viewport) self.pages.viewport = self.pages.active; +} + /// Dump the screen to a string. The writer given should be buffered; /// this function does not attempt to efficiently write and generally writes /// one byte at a time. diff --git a/src/terminal/new/Terminal.zig b/src/terminal/new/Terminal.zig index 96b81ae1b..a7ff10a44 100644 --- a/src/terminal/new/Terminal.zig +++ b/src/terminal/new/Terminal.zig @@ -173,9 +173,9 @@ pub fn init(alloc: Allocator, cols: size.CellCountInt, rows: size.CellCountInt) .rows = rows, .active_screen = .primary, // TODO: configurable scrollback - .screen = try Screen.init(alloc, rows, cols, 10000), + .screen = try Screen.init(alloc, cols, rows, 10000), // No scrollback for the alternate screen - .secondary_screen = try Screen.init(alloc, rows, cols, 0), + .secondary_screen = try Screen.init(alloc, cols, rows, 0), .tabstops = try Tabstops.init(alloc, cols, TABSTOP_INTERVAL), .scrolling_region = .{ .top = 0, @@ -396,8 +396,7 @@ pub fn index(self: *Terminal) !void { self.scrolling_region.left == 0 and self.scrolling_region.right == self.cols - 1) { - @panic("TODO: scroll screen"); - //try self.screen.scroll(.{ .screen = 1 }); + try self.screen.cursorDownScroll(); } else { @panic("TODO: scroll up"); //try self.scrollUp(1); @@ -453,6 +452,22 @@ test "Terminal: input with basic wraparound" { } } +test "Terminal: input that forces scroll" { + const alloc = testing.allocator; + var t = try init(alloc, 1, 5); + defer t.deinit(alloc); + + // Basic grid writing + for ("abcdef") |c| try t.print(c); + try testing.expectEqual(@as(usize, 4), t.screen.cursor.y); + try testing.expectEqual(@as(usize, 0), t.screen.cursor.x); + { + const str = try t.plainString(alloc); + defer alloc.free(str); + try testing.expectEqualStrings("b\nc\nd\ne\nf", str); + } +} + test "Terminal: zero-width character at start" { var t = try init(testing.allocator, 80, 80); defer t.deinit(testing.allocator); @@ -466,11 +481,11 @@ test "Terminal: zero-width character at start" { } // https://github.com/mitchellh/ghostty/issues/1400 -// test "Terminal: print single very long line" { -// var t = try init(testing.allocator, 5, 5); -// defer t.deinit(testing.allocator); -// -// // This would crash for issue 1400. So the assertion here is -// // that we simply do not crash. -// for (0..500) |_| try t.print('x'); -// } +test "Terminal: print single very long line" { + var t = try init(testing.allocator, 5, 5); + defer t.deinit(testing.allocator); + + // This would crash for issue 1400. So the assertion here is + // that we simply do not crash. + for (0..1000) |_| try t.print('x'); +}