mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 16:26:08 +03:00
terminal: basic lineIterator on screen
This commit is contained in:
@ -900,6 +900,63 @@ pub const GraphemeData = union(enum) {
|
||||
}
|
||||
};
|
||||
|
||||
/// A line represents a line of text, potentially across soft-wrapped
|
||||
/// boundaries. This differs from row, which is a single physical row within
|
||||
/// the terminal screen.
|
||||
pub const Line = struct {
|
||||
screen: *Screen,
|
||||
tag: RowIndexTag,
|
||||
start: usize,
|
||||
len: usize,
|
||||
|
||||
/// The string contents of this line.
|
||||
pub fn string(self: *const Line, alloc: Allocator) ![:0]const u8 {
|
||||
// Get the start and end screen point.
|
||||
const start_idx = self.tag.index(self.start).toScreen(self.screen).screen;
|
||||
const end_idx = self.tag.index(self.start + (self.len - 1)).toScreen(self.screen).screen;
|
||||
|
||||
// Convert the start and end screen points into a selection across
|
||||
// the entire rows. We then use selectionString because it handles
|
||||
// unwrapping, graphemes, etc.
|
||||
const sel: Selection = .{
|
||||
.start = .{ .y = start_idx, .x = 0 },
|
||||
.end = .{ .y = end_idx, .x = self.screen.cols - 1 },
|
||||
};
|
||||
return try self.screen.selectionString(alloc, sel, false);
|
||||
}
|
||||
};
|
||||
|
||||
/// Iterator over textual lines within the terminal. This will unwrap
|
||||
/// wrapped lines and consider them a single line.
|
||||
pub const LineIterator = struct {
|
||||
row_it: RowIterator,
|
||||
|
||||
pub fn next(self: *LineIterator) ?Line {
|
||||
const start = self.row_it.value;
|
||||
|
||||
// Get our current row
|
||||
var row = self.row_it.next() orelse return null;
|
||||
var len: usize = 1;
|
||||
|
||||
// While the row is wrapped we keep iterating over the rows
|
||||
// and incrementing the length.
|
||||
while (row.isWrapped()) {
|
||||
// Note: this orelse shouldn't happen. A wrapped row should
|
||||
// always have a next row. However, this isn't the place where
|
||||
// we want to assert that.
|
||||
row = self.row_it.next() orelse break;
|
||||
len += 1;
|
||||
}
|
||||
|
||||
return .{
|
||||
.screen = self.row_it.screen,
|
||||
.tag = self.row_it.tag,
|
||||
.start = start,
|
||||
.len = len,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize to header and not a cell so that we can check header.init
|
||||
// to know if the remainder of the row has been initialized or not.
|
||||
const StorageBuf = CircBuf(StorageCell, .{ .header = .{} });
|
||||
@ -1097,6 +1154,13 @@ pub fn rowIterator(self: *Screen, tag: RowIndexTag) RowIterator {
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns an iterator that iterates over the lines of the screen. A line
|
||||
/// is a single line of text which may wrap across multiple rows. A row
|
||||
/// is a single physical row of the terminal.
|
||||
pub fn lineIterator(self: *Screen, tag: RowIndexTag) LineIterator {
|
||||
return .{ .row_it = self.rowIterator(tag) };
|
||||
}
|
||||
|
||||
/// Returns the row at the given index. This row is writable, although
|
||||
/// only the active area should probably be written to.
|
||||
pub fn getRow(self: *Screen, index: RowIndex) Row {
|
||||
@ -3404,6 +3468,62 @@ test "Screen: write long emoji" {
|
||||
try testing.expectEqual(@as(usize, 5), s.cursor.x);
|
||||
}
|
||||
|
||||
test "Screen: lineIterator" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var s = try init(alloc, 5, 5, 0);
|
||||
defer s.deinit();
|
||||
|
||||
// Sanity check that our test helpers work
|
||||
const str = "1ABCD\n2EFGH";
|
||||
try s.testWriteString(str);
|
||||
|
||||
// Test the line iterator
|
||||
var iter = s.lineIterator(.viewport);
|
||||
{
|
||||
const line = iter.next().?;
|
||||
const actual = try line.string(alloc);
|
||||
defer alloc.free(actual);
|
||||
try testing.expectEqualStrings("1ABCD", actual);
|
||||
}
|
||||
{
|
||||
const line = iter.next().?;
|
||||
const actual = try line.string(alloc);
|
||||
defer alloc.free(actual);
|
||||
try testing.expectEqualStrings("2EFGH", actual);
|
||||
}
|
||||
try testing.expect(iter.next() == null);
|
||||
}
|
||||
|
||||
test "Screen: lineIterator soft wrap" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var s = try init(alloc, 5, 5, 0);
|
||||
defer s.deinit();
|
||||
|
||||
// Sanity check that our test helpers work
|
||||
const str = "1ABCD2EFGH\n3ABCD";
|
||||
try s.testWriteString(str);
|
||||
|
||||
// Test the line iterator
|
||||
var iter = s.lineIterator(.viewport);
|
||||
{
|
||||
const line = iter.next().?;
|
||||
const actual = try line.string(alloc);
|
||||
defer alloc.free(actual);
|
||||
try testing.expectEqualStrings("1ABCD2EFGH", actual);
|
||||
}
|
||||
{
|
||||
const line = iter.next().?;
|
||||
const actual = try line.string(alloc);
|
||||
defer alloc.free(actual);
|
||||
try testing.expectEqualStrings("3ABCD", actual);
|
||||
}
|
||||
try testing.expect(iter.next() == null);
|
||||
}
|
||||
|
||||
test "Screen: scrolling" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
Reference in New Issue
Block a user