mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +03:00
terminal/new: scrollactive
This commit is contained in:
30
src/bench/stream-new.sh
Executable file
30
src/bench/stream-new.sh
Executable 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"
|
@ -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
|
/// 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
|
/// end of the screen, this will add new pages as necessary. This does
|
||||||
/// not move the viewport.
|
/// 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
|
// 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
|
// 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
|
// we must expand at least that much. This does not include the size
|
||||||
@ -297,6 +297,10 @@ pub const RowOffset = struct {
|
|||||||
page: *List.Node,
|
page: *List.Node,
|
||||||
row_offset: usize = 0,
|
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 {
|
pub fn rowAndCell(self: RowOffset, x: usize) struct {
|
||||||
row: *pagepkg.Row,
|
row: *pagepkg.Row,
|
||||||
cell: *pagepkg.Cell,
|
cell: *pagepkg.Cell,
|
||||||
@ -327,7 +331,7 @@ pub const RowOffset = struct {
|
|||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
// Index fits within this page
|
// 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 = .{
|
if (n <= rows) return .{ .offset = .{
|
||||||
.page = self.page,
|
.page = self.page,
|
||||||
.row_offset = n + self.row_offset,
|
.row_offset = n + self.row_offset,
|
||||||
@ -335,20 +339,18 @@ pub const RowOffset = struct {
|
|||||||
|
|
||||||
// Need to traverse page links to find the page
|
// Need to traverse page links to find the page
|
||||||
var page: *List.Node = self.page;
|
var page: *List.Node = self.page;
|
||||||
var n_left: usize = n;
|
var n_left: usize = n - rows;
|
||||||
while (n_left >= rows) {
|
while (true) {
|
||||||
n_left -= rows;
|
|
||||||
page = page.next orelse return .{ .overflow = .{
|
page = page.next orelse return .{ .overflow = .{
|
||||||
.end = .{ .page = page, .row_offset = page.data.size.rows - 1 },
|
.end = .{ .page = page, .row_offset = page.data.size.rows - 1 },
|
||||||
.remaining = n_left,
|
.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);
|
try testing.expectEqual(s.active, s.viewport);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "scrollDown utilizes capacity" {
|
test "scrollActive utilizes capacity" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
@ -391,7 +393,7 @@ test "scrollDown utilizes capacity" {
|
|||||||
try testing.expect(s.active.row_offset == 0);
|
try testing.expect(s.active.row_offset == 0);
|
||||||
try testing.expectEqual(@as(size.CellCountInt, 1), s.active.page.data.size.rows);
|
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
|
// We should not allocate a new page because we have enough capacity
|
||||||
try testing.expect(s.active.page == s.pages.first);
|
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);
|
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 testing = std.testing;
|
||||||
const alloc = testing.allocator;
|
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
|
// The initial active is a single page so scrolling down even one
|
||||||
// should force the allocation of an entire new page.
|
// 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
|
// We should still be on the first page but offset, and we should
|
||||||
// have a second page created.
|
// have a second page created.
|
||||||
|
@ -114,6 +114,24 @@ pub fn cursorHorizontalAbsolute(self: *Screen, x: size.CellCountInt) void {
|
|||||||
self.cursor.x = x;
|
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;
|
/// Dump the screen to a string. The writer given should be buffered;
|
||||||
/// this function does not attempt to efficiently write and generally writes
|
/// this function does not attempt to efficiently write and generally writes
|
||||||
/// one byte at a time.
|
/// one byte at a time.
|
||||||
|
@ -173,9 +173,9 @@ pub fn init(alloc: Allocator, cols: size.CellCountInt, rows: size.CellCountInt)
|
|||||||
.rows = rows,
|
.rows = rows,
|
||||||
.active_screen = .primary,
|
.active_screen = .primary,
|
||||||
// TODO: configurable scrollback
|
// 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
|
// 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),
|
.tabstops = try Tabstops.init(alloc, cols, TABSTOP_INTERVAL),
|
||||||
.scrolling_region = .{
|
.scrolling_region = .{
|
||||||
.top = 0,
|
.top = 0,
|
||||||
@ -396,8 +396,7 @@ pub fn index(self: *Terminal) !void {
|
|||||||
self.scrolling_region.left == 0 and
|
self.scrolling_region.left == 0 and
|
||||||
self.scrolling_region.right == self.cols - 1)
|
self.scrolling_region.right == self.cols - 1)
|
||||||
{
|
{
|
||||||
@panic("TODO: scroll screen");
|
try self.screen.cursorDownScroll();
|
||||||
//try self.screen.scroll(.{ .screen = 1 });
|
|
||||||
} else {
|
} else {
|
||||||
@panic("TODO: scroll up");
|
@panic("TODO: scroll up");
|
||||||
//try self.scrollUp(1);
|
//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" {
|
test "Terminal: zero-width character at start" {
|
||||||
var t = try init(testing.allocator, 80, 80);
|
var t = try init(testing.allocator, 80, 80);
|
||||||
defer t.deinit(testing.allocator);
|
defer t.deinit(testing.allocator);
|
||||||
@ -466,11 +481,11 @@ test "Terminal: zero-width character at start" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/mitchellh/ghostty/issues/1400
|
// https://github.com/mitchellh/ghostty/issues/1400
|
||||||
// test "Terminal: print single very long line" {
|
test "Terminal: print single very long line" {
|
||||||
// var t = try init(testing.allocator, 5, 5);
|
var t = try init(testing.allocator, 5, 5);
|
||||||
// defer t.deinit(testing.allocator);
|
defer t.deinit(testing.allocator);
|
||||||
//
|
|
||||||
// // This would crash for issue 1400. So the assertion here is
|
// This would crash for issue 1400. So the assertion here is
|
||||||
// // that we simply do not crash.
|
// that we simply do not crash.
|
||||||
// for (0..500) |_| try t.print('x');
|
for (0..1000) |_| try t.print('x');
|
||||||
// }
|
}
|
||||||
|
Reference in New Issue
Block a user