From 8ea20f9e415bb814e169c964dba68fd5e4073438 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 7 Aug 2022 09:41:30 -0700 Subject: [PATCH] start changing getRow and rowIndex to use tagged indexes Lots of bugs here, but it MATCHES the bugs! --- src/terminal/Screen.zig | 113 +++++++++++++++++++++++++------------- src/terminal/Terminal.zig | 14 ++--- 2 files changed, 81 insertions(+), 46 deletions(-) diff --git a/src/terminal/Screen.zig b/src/terminal/Screen.zig index 6d9f7ef2b..6c1025c47 100644 --- a/src/terminal/Screen.zig +++ b/src/terminal/Screen.zig @@ -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"); { diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index a36c55381..bcd5f17bb 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -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); } }