terminal: basic lineIterator on screen

This commit is contained in:
Mitchell Hashimoto
2023-11-25 15:39:27 -08:00
parent af04d891ff
commit 7fc95690bc

View File

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