diff --git a/TODO.md b/TODO.md index b690b4222..5f429ddcc 100644 --- a/TODO.md +++ b/TODO.md @@ -1,8 +1,6 @@ Bugs: * Underline should use freetype underline thickness hint -* Any printing action forces scroll to jump to bottom, this makes it impossible - to scroll up while logs are coming in or something Performance: diff --git a/src/terminal/Screen.zig b/src/terminal/Screen.zig index 6c1025c47..11fa350ba 100644 --- a/src/terminal/Screen.zig +++ b/src/terminal/Screen.zig @@ -107,6 +107,11 @@ pub const RowIndex = union(enum) { /// on where the user has scrolled the viewport, "0" is different. viewport: usize, + /// The index is from the top of the active area. The active area is + /// always "rows" tall, and 0 is the top row. The active area is the + /// "edit-able" area where the terminal cursor is. + active: usize, + // TODO: others }; @@ -204,7 +209,7 @@ pub fn getRow(self: Screen, idx: RowIndex) Row { pub fn getCell(self: Screen, row: usize, col: usize) *Cell { assert(row < self.rows); assert(col < self.cols); - const row_idx = self.rowIndex(.{ .viewport = row }); + const row_idx = self.rowIndex(.{ .active = row }); return &self.storage[row_idx + col]; } @@ -221,6 +226,11 @@ fn rowIndex(self: Screen, idx: RowIndex) usize { assert(y < self.rows); break :y y + self.visible_offset; }, + + .active => |y| y: { + assert(y < self.rows); + break :y self.bottomOffset() + y; + }, }; const val = (self.top + y) * self.cols; @@ -369,8 +379,8 @@ 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(.{ .viewport = src }); - const dst_row = self.getRow(.{ .viewport = dst }); + const src_row = self.getRow(.{ .active = src }); + const dst_row = self.getRow(.{ .active = dst }); std.mem.copy(Cell, dst_row, src_row); } @@ -581,13 +591,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(.{ .viewport = y }); + var row = self.getRow(.{ .active = y }); for (text) |c| { // Explicit newline forces a new row if (c == '\n') { y += 1; x = 0; - row = self.getRow(.{ .viewport = y }); + row = self.getRow(.{ .active = y }); continue; } @@ -596,7 +606,7 @@ fn testWriteString(self: *Screen, text: []const u8) void { row[x - 1].attrs.wrap = 1; y += 1; x = 0; - row = self.getRow(.{ .viewport = y }); + row = self.getRow(.{ .active = y }); } row[x].char = @intCast(u32, c); @@ -647,9 +657,9 @@ test "Screen: scrolling" { try testing.expect(s.viewportIsBottom()); // Test our row index - 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 })); + try testing.expectEqual(@as(usize, 5), s.rowIndex(.{ .active = 0 })); + try testing.expectEqual(@as(usize, 10), s.rowIndex(.{ .active = 1 })); + try testing.expectEqual(@as(usize, 0), s.rowIndex(.{ .active = 2 })); { // Test our contents rotated @@ -718,9 +728,9 @@ test "Screen: scrollback" { s.scroll(.{ .delta = 1 }); // Test our row index - 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 })); + try testing.expectEqual(@as(usize, 5), s.rowIndex(.{ .active = 0 })); + try testing.expectEqual(@as(usize, 10), s.rowIndex(.{ .active = 1 })); + try testing.expectEqual(@as(usize, 15), s.rowIndex(.{ .active = 2 })); { // Test our contents rotated @@ -949,7 +959,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.rowIndex(.{ .viewport = 2 })); + try testing.expectEqual(@as(usize, 0), s.rowIndex(.{ .active = 2 })); s.testWriteString("1ABCD\n2EFGH\n3IJKL"); { diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index bcd5f17bb..91e9dd450 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -339,9 +339,6 @@ pub fn print(self: *Terminal, c: u21) !void { // If we're not on the main display, do nothing for now if (self.status_display != .main) return; - // If we're not at the bottom, then we need to move there - if (!self.screen.viewportIsBottom()) self.screen.scroll(.{ .bottom = {} }); - // If we're soft-wrapping, then handle that first. if (self.screen.cursor.pending_wrap and self.modes.autowrap == 1) { // Mark that the cell is wrapped, which guarantees that there is @@ -383,7 +380,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(.{ .viewport = 0 }); + const filled = self.screen.getRow(.{ .active = 0 }); var col: usize = 0; while (col < self.cols) : (col += 1) { filled[col] = .{ .char = 'E' }; @@ -391,7 +388,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(.{ .viewport = row }), filled); + std.mem.copy(Screen.Cell, self.screen.getRow(.{ .active = row }), filled); } } @@ -615,12 +612,12 @@ pub fn eraseLine( switch (mode) { .right => { - const row = self.screen.getRow(.{ .viewport = self.screen.cursor.y }); + const row = self.screen.getRow(.{ .active = self.screen.cursor.y }); std.mem.set(Screen.Cell, row[self.screen.cursor.x..], self.screen.cursor.pen); }, .left => { - const row = self.screen.getRow(.{ .viewport = self.screen.cursor.y }); + const row = self.screen.getRow(.{ .active = 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 +625,7 @@ pub fn eraseLine( }, .complete => { - const row = self.screen.getRow(.{ .viewport = self.screen.cursor.y }); + const row = self.screen.getRow(.{ .active = self.screen.cursor.y }); std.mem.set(Screen.Cell, row, self.screen.cursor.pen); }, @@ -653,7 +650,7 @@ pub fn deleteChars(self: *Terminal, count: usize) !void { const tracy = trace(@src()); defer tracy.end(); - const line = self.screen.getRow(self.screen.cursor.y); + const line = self.screen.getRow(.{ .active = self.screen.cursor.y }); // Our last index is at most the end of the number of chars we have // in the current line. @@ -821,7 +818,7 @@ pub fn insertBlanks(self: *Terminal, count: usize) void { } // Get the current row - const row = self.screen.getRow(.{ .viewport = self.screen.cursor.y }); + const row = self.screen.getRow(.{ .active = self.screen.cursor.y }); // Determine our indexes. const start = self.screen.cursor.x; @@ -943,7 +940,7 @@ pub fn deleteLines(self: *Terminal, count: usize) void { } while (y <= self.scrolling_region.bottom) : (y += 1) { - const row = self.screen.getRow(.{ .viewport = y }); + const row = self.screen.getRow(.{ .active = y }); std.mem.set(Screen.Cell, row, self.screen.cursor.pen); } } @@ -985,6 +982,10 @@ pub fn scrollUp(self: *Terminal, count: usize) void { pub const ScrollViewport = union(enum) { /// Scroll to the top of the scrollback top: void, + + /// Scroll to the bottom, i.e. the top of the active area + bottom: void, + delta: isize, }; @@ -995,6 +996,7 @@ pub fn scrollViewport(self: *Terminal, behavior: ScrollViewport) void { self.screen.scroll(switch (behavior) { .top => .{ .top = {} }, + .bottom => .{ .bottom = {} }, .delta => |delta| .{ .delta_no_grow = delta }, }); } @@ -1067,7 +1069,7 @@ test "Terminal: soft wrap" { } } -test "Terminal: print scrolls back to bottom" { +test "Terminal: print writes to bottom if scrolled" { var t = try init(testing.allocator, 5, 2); defer t.deinit(testing.allocator); @@ -1095,6 +1097,7 @@ test "Terminal: print scrolls back to bottom" { // Type try t.print('A'); + t.scrollViewport(.{ .bottom = {} }); { var str = try t.plainString(testing.allocator); defer testing.allocator.free(str);