mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 16:26:08 +03:00
start changing getRow and rowIndex to use tagged indexes
Lots of bugs here, but it MATCHES the bugs!
This commit is contained in:
@ -86,12 +86,33 @@ pub const RowIterator = struct {
|
||||
|
||||
pub fn next(self: *RowIterator) ?Row {
|
||||
if (self.index >= self.screen.rows) return null;
|
||||
const res = self.screen.getRow(self.index);
|
||||
const res = self.screen.getRow(.{ .viewport = self.index });
|
||||
self.index += 1;
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
/// RowIndex represents a row within the screen. There are various meanings
|
||||
/// of a row index and this union represents the available types. For example,
|
||||
/// when talking about row "0" you may want the first row in the viewport,
|
||||
/// the first row in the scrollback, or the first row in the active area.
|
||||
///
|
||||
/// All row indexes are 0-indexed.
|
||||
pub const RowIndex = union(enum) {
|
||||
/// The index is from the top of the screen. The screen includes all
|
||||
/// the history.
|
||||
screen: usize,
|
||||
|
||||
/// The index is from the top of the viewport. Therefore, depending
|
||||
/// on where the user has scrolled the viewport, "0" is different.
|
||||
viewport: usize,
|
||||
|
||||
// TODO: others
|
||||
};
|
||||
|
||||
/// The tags of RowIndex
|
||||
pub const RowIndexType = std.meta.FieldEnum(RowIndex);
|
||||
|
||||
/// Each screen maintains its own cursor state.
|
||||
cursor: Cursor = .{},
|
||||
|
||||
@ -171,9 +192,9 @@ pub fn getVisible(self: Screen) []Cell {
|
||||
}
|
||||
|
||||
/// Get a single row in the active area by index (0-indexed).
|
||||
pub fn getRow(self: Screen, idx: usize) Row {
|
||||
pub fn getRow(self: Screen, idx: RowIndex) Row {
|
||||
// Get the index of the first byte of the the row at index.
|
||||
const real_idx = self.viewportRowIndex(idx);
|
||||
const real_idx = self.rowIndex(idx);
|
||||
|
||||
// The storage is sliced to return exactly the number of columns.
|
||||
return self.storage[real_idx .. real_idx + self.cols];
|
||||
@ -183,21 +204,25 @@ pub fn getRow(self: Screen, idx: usize) Row {
|
||||
pub fn getCell(self: Screen, row: usize, col: usize) *Cell {
|
||||
assert(row < self.rows);
|
||||
assert(col < self.cols);
|
||||
const row_idx = self.viewportRowIndex(row);
|
||||
const row_idx = self.rowIndex(.{ .viewport = row });
|
||||
return &self.storage[row_idx + col];
|
||||
}
|
||||
|
||||
/// Returns the index for the given row (0-indexed) into the underlying
|
||||
/// storage array. The row is 0-indexed from the top of the viewport.
|
||||
fn viewportRowIndex(self: Screen, idx: usize) usize {
|
||||
assert(idx < self.rows);
|
||||
return self.rowIndex(self.visible_offset + idx);
|
||||
}
|
||||
|
||||
/// Returns the index for the given row (0-indexed) into the underlying
|
||||
/// storage array. The row is 0-indexed from the top of the screen.
|
||||
fn rowIndex(self: Screen, y: usize) usize {
|
||||
assert(y < self.totalRows());
|
||||
fn rowIndex(self: Screen, idx: RowIndex) usize {
|
||||
const y = switch (idx) {
|
||||
.screen => |y| y: {
|
||||
assert(y < self.totalRows());
|
||||
break :y y;
|
||||
},
|
||||
|
||||
.viewport => |y| y: {
|
||||
assert(y < self.rows);
|
||||
break :y y + self.visible_offset;
|
||||
},
|
||||
};
|
||||
|
||||
const val = (self.top + y) * self.cols;
|
||||
if (val < self.storage.len) return val;
|
||||
return val - self.storage.len;
|
||||
@ -344,18 +369,28 @@ fn scrollDelta(self: *Screen, delta: isize, grow: bool) void {
|
||||
|
||||
/// Copy row at src to dst.
|
||||
pub fn copyRow(self: *Screen, dst: usize, src: usize) void {
|
||||
const src_row = self.getRow(src);
|
||||
const dst_row = self.getRow(dst);
|
||||
const src_row = self.getRow(.{ .viewport = src });
|
||||
const dst_row = self.getRow(.{ .viewport = dst });
|
||||
std.mem.copy(Cell, dst_row, src_row);
|
||||
}
|
||||
|
||||
/// Resize the screen. The rows or cols can be bigger or smaller. Due to
|
||||
/// the internal representation of a screen, this usually involves a significant
|
||||
/// amount of copying compared to any other operations.
|
||||
/// Resize the screen. The rows or cols can be bigger or smaller. This
|
||||
/// function can only be used to resize the viewport. The scrollback size
|
||||
/// (in lines) can't be changed. But due to the resize, more or less scrollback
|
||||
/// "space" becomes available due to the width of lines.
|
||||
///
|
||||
/// This will trim data if the size is getting smaller. It is expected that
|
||||
/// callers will reflow the text prior to calling this.
|
||||
/// Due to the internal representation of a screen, this usually involves a
|
||||
/// significant amount of copying compared to any other operations.
|
||||
///
|
||||
/// This will trim data if the size is getting smaller. This will reflow the
|
||||
/// soft wrapped text.
|
||||
pub fn resize(self: *Screen, alloc: Allocator, rows: usize, cols: usize) !void {
|
||||
// We do this in a pretty inefficient way because this implementation
|
||||
// is easier and resizing is relatively rare. I welcome anyone to improve
|
||||
// on this! Our naive approach is to just iterate over the entire screen
|
||||
// (including scrollback) and reflow the entire thing by rewriting it.
|
||||
// TODO: above not implemented yet
|
||||
|
||||
// Make a copy so we can access the old indexes.
|
||||
const old = self.*;
|
||||
|
||||
@ -381,8 +416,8 @@ pub fn resize(self: *Screen, alloc: Allocator, rows: usize, cols: usize) !void {
|
||||
while (y < old.rows) : (y += 1) {
|
||||
// Copy the old row into the new row, just losing the columsn
|
||||
// if we got thinner.
|
||||
const old_row = old.getRow(y);
|
||||
const new_row = self.getRow(y - start);
|
||||
const old_row = old.getRow(.{ .viewport = y });
|
||||
const new_row = self.getRow(.{ .viewport = y - start });
|
||||
std.mem.copy(Cell, new_row, old_row[0..col_end]);
|
||||
|
||||
// If our new row is wider, then we copy zeroes into the rest.
|
||||
@ -393,7 +428,7 @@ pub fn resize(self: *Screen, alloc: Allocator, rows: usize, cols: usize) !void {
|
||||
|
||||
// If we grew rows, then set the remaining data to zero.
|
||||
if (rows > old.rows) {
|
||||
std.mem.set(Cell, self.storage[self.viewportRowIndex(old.rows)..], .{ .char = 0 });
|
||||
std.mem.set(Cell, self.storage[self.rowIndex(.{ .viewport = old.rows })..], .{ .char = 0 });
|
||||
}
|
||||
|
||||
// Free the old data
|
||||
@ -491,8 +526,8 @@ fn selectionSlices(self: Screen, sel: Selection) struct {
|
||||
// Get the true "top" and "bottom"
|
||||
const sel_top = sel.topLeft();
|
||||
const sel_bot = sel.bottomRight();
|
||||
const top = self.rowIndex(sel_top.y);
|
||||
const bot = self.rowIndex(sel_bot.y);
|
||||
const top = self.rowIndex(.{ .screen = sel_top.y });
|
||||
const bot = self.rowIndex(.{ .screen = sel_bot.y });
|
||||
|
||||
// The bottom and top are available in one contiguous slice.
|
||||
if (bot >= top) {
|
||||
@ -546,13 +581,13 @@ pub fn testString(self: Screen, alloc: Allocator) ![]const u8 {
|
||||
fn testWriteString(self: *Screen, text: []const u8) void {
|
||||
var y: usize = 0;
|
||||
var x: usize = 0;
|
||||
var row = self.getRow(y);
|
||||
var row = self.getRow(.{ .viewport = y });
|
||||
for (text) |c| {
|
||||
// Explicit newline forces a new row
|
||||
if (c == '\n') {
|
||||
y += 1;
|
||||
x = 0;
|
||||
row = self.getRow(y);
|
||||
row = self.getRow(.{ .viewport = y });
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -561,7 +596,7 @@ fn testWriteString(self: *Screen, text: []const u8) void {
|
||||
row[x - 1].attrs.wrap = 1;
|
||||
y += 1;
|
||||
x = 0;
|
||||
row = self.getRow(y);
|
||||
row = self.getRow(.{ .viewport = y });
|
||||
}
|
||||
|
||||
row[x].char = @intCast(u32, c);
|
||||
@ -588,7 +623,7 @@ test "Screen" {
|
||||
var iter = s.rowIterator();
|
||||
while (iter.next()) |row| {
|
||||
// Rows should be pointer equivalent to getRow
|
||||
const row_other = s.getRow(count);
|
||||
const row_other = s.getRow(.{ .viewport = count });
|
||||
try testing.expectEqual(row.ptr, row_other.ptr);
|
||||
count += 1;
|
||||
}
|
||||
@ -612,9 +647,9 @@ test "Screen: scrolling" {
|
||||
try testing.expect(s.viewportIsBottom());
|
||||
|
||||
// Test our row index
|
||||
try testing.expectEqual(@as(usize, 5), s.viewportRowIndex(0));
|
||||
try testing.expectEqual(@as(usize, 10), s.viewportRowIndex(1));
|
||||
try testing.expectEqual(@as(usize, 0), s.viewportRowIndex(2));
|
||||
try testing.expectEqual(@as(usize, 5), s.rowIndex(.{ .viewport = 0 }));
|
||||
try testing.expectEqual(@as(usize, 10), s.rowIndex(.{ .viewport = 1 }));
|
||||
try testing.expectEqual(@as(usize, 0), s.rowIndex(.{ .viewport = 2 }));
|
||||
|
||||
{
|
||||
// Test our contents rotated
|
||||
@ -650,9 +685,9 @@ test "Screen: scrolling" {
|
||||
// try testing.expect(s.viewportIsBottom());
|
||||
//
|
||||
// // Test our row index
|
||||
// try testing.expectEqual(@as(usize, 5), s.viewportRowIndex(0));
|
||||
// try testing.expectEqual(@as(usize, 10), s.viewportRowIndex(1));
|
||||
// try testing.expectEqual(@as(usize, 15), s.viewportRowIndex(2));
|
||||
// try testing.expectEqual(@as(usize, 5), s.rowIndex(0));
|
||||
// try testing.expectEqual(@as(usize, 10), s.rowIndex(1));
|
||||
// try testing.expectEqual(@as(usize, 15), s.rowIndex(2));
|
||||
// }
|
||||
|
||||
test "Screen: scroll down from 0" {
|
||||
@ -683,9 +718,9 @@ test "Screen: scrollback" {
|
||||
s.scroll(.{ .delta = 1 });
|
||||
|
||||
// Test our row index
|
||||
try testing.expectEqual(@as(usize, 5), s.viewportRowIndex(0));
|
||||
try testing.expectEqual(@as(usize, 10), s.viewportRowIndex(1));
|
||||
try testing.expectEqual(@as(usize, 15), s.viewportRowIndex(2));
|
||||
try testing.expectEqual(@as(usize, 5), s.rowIndex(.{ .viewport = 0 }));
|
||||
try testing.expectEqual(@as(usize, 10), s.rowIndex(.{ .viewport = 1 }));
|
||||
try testing.expectEqual(@as(usize, 15), s.rowIndex(.{ .viewport = 2 }));
|
||||
|
||||
{
|
||||
// Test our contents rotated
|
||||
@ -914,7 +949,7 @@ test "Screen: selectionString wrap around" {
|
||||
// we're out of space.
|
||||
s.scroll(.{ .delta = 1 });
|
||||
try testing.expect(s.viewportIsBottom());
|
||||
try testing.expectEqual(@as(usize, 0), s.viewportRowIndex(2));
|
||||
try testing.expectEqual(@as(usize, 0), s.rowIndex(.{ .viewport = 2 }));
|
||||
s.testWriteString("1ABCD\n2EFGH\n3IJKL");
|
||||
|
||||
{
|
||||
|
@ -383,7 +383,7 @@ pub fn decaln(self: *Terminal) void {
|
||||
|
||||
// Fill with Es, does not move cursor. We reset fg/bg so we can just
|
||||
// optimize here by doing row copies.
|
||||
const filled = self.screen.getRow(0);
|
||||
const filled = self.screen.getRow(.{ .viewport = 0 });
|
||||
var col: usize = 0;
|
||||
while (col < self.cols) : (col += 1) {
|
||||
filled[col] = .{ .char = 'E' };
|
||||
@ -391,7 +391,7 @@ pub fn decaln(self: *Terminal) void {
|
||||
|
||||
var row: usize = 1;
|
||||
while (row < self.rows) : (row += 1) {
|
||||
std.mem.copy(Screen.Cell, self.screen.getRow(row), filled);
|
||||
std.mem.copy(Screen.Cell, self.screen.getRow(.{ .viewport = row }), filled);
|
||||
}
|
||||
}
|
||||
|
||||
@ -615,12 +615,12 @@ pub fn eraseLine(
|
||||
|
||||
switch (mode) {
|
||||
.right => {
|
||||
const row = self.screen.getRow(self.screen.cursor.y);
|
||||
const row = self.screen.getRow(.{ .viewport = self.screen.cursor.y });
|
||||
std.mem.set(Screen.Cell, row[self.screen.cursor.x..], self.screen.cursor.pen);
|
||||
},
|
||||
|
||||
.left => {
|
||||
const row = self.screen.getRow(self.screen.cursor.y);
|
||||
const row = self.screen.getRow(.{ .viewport = self.screen.cursor.y });
|
||||
std.mem.set(Screen.Cell, row[0 .. self.screen.cursor.x + 1], self.screen.cursor.pen);
|
||||
|
||||
// Unsets pending wrap state
|
||||
@ -628,7 +628,7 @@ pub fn eraseLine(
|
||||
},
|
||||
|
||||
.complete => {
|
||||
const row = self.screen.getRow(self.screen.cursor.y);
|
||||
const row = self.screen.getRow(.{ .viewport = self.screen.cursor.y });
|
||||
std.mem.set(Screen.Cell, row, self.screen.cursor.pen);
|
||||
},
|
||||
|
||||
@ -821,7 +821,7 @@ pub fn insertBlanks(self: *Terminal, count: usize) void {
|
||||
}
|
||||
|
||||
// Get the current row
|
||||
const row = self.screen.getRow(self.screen.cursor.y);
|
||||
const row = self.screen.getRow(.{ .viewport = self.screen.cursor.y });
|
||||
|
||||
// Determine our indexes.
|
||||
const start = self.screen.cursor.x;
|
||||
@ -943,7 +943,7 @@ pub fn deleteLines(self: *Terminal, count: usize) void {
|
||||
}
|
||||
|
||||
while (y <= self.scrolling_region.bottom) : (y += 1) {
|
||||
const row = self.screen.getRow(y);
|
||||
const row = self.screen.getRow(.{ .viewport = y });
|
||||
std.mem.set(Screen.Cell, row, self.screen.cursor.pen);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user