terminal/new: scrollactive

This commit is contained in:
Mitchell Hashimoto
2024-02-21 22:30:29 -08:00
parent 5628fa36d8
commit 46b59b4c7d
4 changed files with 92 additions and 27 deletions

30
src/bench/stream-new.sh Executable file
View File

@ -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 </tmp/ghostty_bench_data" \
-n old \
"./zig-out/bin/bench-stream --mode=simd --terminal=old </tmp/ghostty_bench_data" \
-n new \
"./zig-out/bin/bench-stream --mode=simd --terminal=new </tmp/ghostty_bench_data"

View File

@ -117,7 +117,7 @@ pub fn deinit(self: *PageList) void {
/// Scroll the active area down by n lines. If the n lines go beyond the
/// end of the screen, this will add new pages as necessary. This does
/// not move the viewport.
pub fn scrollDown(self: *PageList, n: usize) !void {
pub fn scrollActive(self: *PageList, n: usize) !void {
// Move our active area down as much as possible towards n. The return
// value is the amount of rows we were short in any existing page, and
// we must expand at least that much. This does not include the size
@ -297,6 +297,10 @@ pub const RowOffset = struct {
page: *List.Node,
row_offset: usize = 0,
pub fn eql(self: RowOffset, other: RowOffset) bool {
return self.page == other.page and self.row_offset == other.row_offset;
}
pub fn rowAndCell(self: RowOffset, x: usize) struct {
row: *pagepkg.Row,
cell: *pagepkg.Cell,
@ -327,7 +331,7 @@ pub const RowOffset = struct {
},
} {
// Index fits within this page
var rows = self.page.data.size.rows - (self.row_offset + 1);
const rows = self.page.data.size.rows - (self.row_offset + 1);
if (n <= rows) return .{ .offset = .{
.page = self.page,
.row_offset = n + self.row_offset,
@ -335,20 +339,18 @@ pub const RowOffset = struct {
// Need to traverse page links to find the page
var page: *List.Node = self.page;
var n_left: usize = n;
while (n_left >= 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.

View File

@ -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.

View File

@ -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');
}