cursor belongs to screen (prep for alternate screen)

This commit is contained in:
Mitchell Hashimoto
2022-07-22 13:08:40 -07:00
parent cf1aeacc4b
commit 5564bd7213
5 changed files with 173 additions and 173 deletions

View File

@ -401,8 +401,8 @@ pub fn updateCells(self: *Grid, term: Terminal) !void {
if (self.cursor_visible and term.screen.displayIsBottom()) { if (self.cursor_visible and term.screen.displayIsBottom()) {
self.cells.appendAssumeCapacity(.{ self.cells.appendAssumeCapacity(.{
.mode = @enumToInt(self.cursor_style), .mode = @enumToInt(self.cursor_style),
.grid_col = @intCast(u16, term.cursor.x), .grid_col = @intCast(u16, term.screen.cursor.x),
.grid_row = @intCast(u16, term.cursor.y), .grid_row = @intCast(u16, term.screen.cursor.y),
.fg_r = 0, .fg_r = 0,
.fg_g = 0, .fg_g = 0,
.fg_b = 0, .fg_b = 0,

View File

@ -700,12 +700,12 @@ pub fn setCursorUp(self: *Window, amount: u16) !void {
pub fn setCursorCol(self: *Window, col: u16) !void { pub fn setCursorCol(self: *Window, col: u16) !void {
if (self.terminal.modes.origin == 1) unreachable; // TODO if (self.terminal.modes.origin == 1) unreachable; // TODO
self.terminal.setCursorPos(self.terminal.cursor.y + 1, col); self.terminal.setCursorPos(self.terminal.screen.cursor.y + 1, col);
} }
pub fn setCursorRow(self: *Window, row: u16) !void { pub fn setCursorRow(self: *Window, row: u16) !void {
if (self.terminal.modes.origin == 1) unreachable; // TODO if (self.terminal.modes.origin == 1) unreachable; // TODO
self.terminal.setCursorPos(row, self.terminal.cursor.x + 1); self.terminal.setCursorPos(row, self.terminal.screen.cursor.x + 1);
} }
pub fn setCursorPos(self: *Window, row: u16, col: u16) !void { pub fn setCursorPos(self: *Window, row: u16, col: u16) !void {
@ -835,11 +835,11 @@ pub fn deviceStatusReport(
y: usize, y: usize,
} = if (self.terminal.modes.origin == 1) .{ } = if (self.terminal.modes.origin == 1) .{
// TODO: what do we do if cursor is outside scrolling region? // TODO: what do we do if cursor is outside scrolling region?
.x = self.terminal.cursor.x, .x = self.terminal.screen.cursor.x,
.y = self.terminal.cursor.y -| self.terminal.scrolling_region.top, .y = self.terminal.screen.cursor.y -| self.terminal.scrolling_region.top,
} else .{ } else .{
.x = self.terminal.cursor.x, .x = self.terminal.screen.cursor.x,
.y = self.terminal.cursor.y, .y = self.terminal.screen.cursor.y,
}; };
// Response always is at least 4 chars, so this leaves the // Response always is at least 4 chars, so this leaves the

View File

@ -29,6 +29,20 @@ const log = std.log.scoped(.screen);
/// A row is a set of cells. /// A row is a set of cells.
pub const Row = []Cell; pub const Row = []Cell;
/// Cursor represents the cursor state.
pub const Cursor = struct {
// x, y where the cursor currently exists (0-indexed). This x/y is
// always the offset in the display area.
x: usize = 0,
y: usize = 0,
// pen is the current cell styling to apply to new cells.
pen: Cell = .{ .char = 0 },
// The last column flag (LCF) used to do soft wrapping.
pending_wrap: bool = false,
};
/// Cell is a single cell within the screen. /// Cell is a single cell within the screen.
pub const Cell = struct { pub const Cell = struct {
/// Each cell contains exactly one character. The character is UTF-32 /// Each cell contains exactly one character. The character is UTF-32
@ -70,6 +84,12 @@ pub const RowIterator = struct {
} }
}; };
/// Each screen maintains its own cursor state.
cursor: Cursor = .{},
/// Saved cursor saved with DECSC (ESC 7).
saved_cursor: Cursor = .{},
/// The full list of rows, including any scrollback. /// The full list of rows, including any scrollback.
storage: []Cell, storage: []Cell,
@ -106,6 +126,7 @@ pub fn init(
std.mem.set(Cell, buf, .{ .char = 0 }); std.mem.set(Cell, buf, .{ .char = 0 });
return Screen{ return Screen{
.cursor = .{},
.storage = buf, .storage = buf,
.top = 0, .top = 0,
.bottom = rows, .bottom = rows,

View File

@ -25,12 +25,6 @@ const TABSTOP_INTERVAL = 8;
/// Screen is the current screen state. /// Screen is the current screen state.
screen: Screen, screen: Screen,
/// Cursor position.
cursor: Cursor,
/// Saved cursor saved with DECSC (ESC 7).
saved_cursor: Cursor,
/// Where the tabstops are. /// Where the tabstops are.
tabstops: Tabstops, tabstops: Tabstops,
@ -61,19 +55,6 @@ const ScrollingRegion = struct {
bottom: usize, bottom: usize,
}; };
/// Cursor represents the cursor state.
const Cursor = struct {
// x, y where the cursor currently exists (0-indexed).
x: usize = 0,
y: usize = 0,
// pen is the current cell styling to apply to new cells.
pen: Screen.Cell = .{ .char = 0 },
// The last column flag (LCF) used to do soft wrapping.
pending_wrap: bool = false,
};
/// Initialize a new terminal. /// Initialize a new terminal.
pub fn init(alloc: Allocator, cols: usize, rows: usize) !Terminal { pub fn init(alloc: Allocator, cols: usize, rows: usize) !Terminal {
return Terminal{ return Terminal{
@ -81,8 +62,6 @@ pub fn init(alloc: Allocator, cols: usize, rows: usize) !Terminal {
.rows = rows, .rows = rows,
// TODO: configurable scrollback // TODO: configurable scrollback
.screen = try Screen.init(alloc, rows, cols, 1000), .screen = try Screen.init(alloc, rows, cols, 1000),
.cursor = .{},
.saved_cursor = .{},
.tabstops = try Tabstops.init(alloc, cols, TABSTOP_INTERVAL), .tabstops = try Tabstops.init(alloc, cols, TABSTOP_INTERVAL),
.scrolling_region = .{ .scrolling_region = .{
.top = 0, .top = 0,
@ -123,8 +102,8 @@ pub fn resize(self: *Terminal, alloc: Allocator, cols: usize, rows: usize) !void
}; };
// Move our cursor // Move our cursor
self.cursor.x = @minimum(self.cursor.x, self.cols - 1); self.screen.cursor.x = @minimum(self.screen.cursor.x, self.cols - 1);
self.cursor.y = @minimum(self.cursor.y, self.rows - 1); self.screen.cursor.y = @minimum(self.screen.cursor.y, self.rows - 1);
} }
/// Return the current string value of the terminal. Newlines are /// Return the current string value of the terminal. Newlines are
@ -141,7 +120,7 @@ pub fn plainString(self: Terminal, alloc: Allocator) ![]const u8 {
/// is kept per screen (main / alternative). If for the current screen state /// is kept per screen (main / alternative). If for the current screen state
/// was already saved it is overwritten. /// was already saved it is overwritten.
pub fn saveCursor(self: *Terminal) void { pub fn saveCursor(self: *Terminal) void {
self.saved_cursor = self.cursor; self.screen.saved_cursor = self.screen.cursor;
} }
/// Restore cursor position and other state. /// Restore cursor position and other state.
@ -149,32 +128,32 @@ pub fn saveCursor(self: *Terminal) void {
/// The primary and alternate screen have distinct save state. /// The primary and alternate screen have distinct save state.
/// If no save was done before values are reset to their initial values. /// If no save was done before values are reset to their initial values.
pub fn restoreCursor(self: *Terminal) void { pub fn restoreCursor(self: *Terminal) void {
self.cursor = self.saved_cursor; self.screen.cursor = self.screen.saved_cursor;
} }
/// TODO: test /// TODO: test
pub fn setAttribute(self: *Terminal, attr: sgr.Attribute) !void { pub fn setAttribute(self: *Terminal, attr: sgr.Attribute) !void {
switch (attr) { switch (attr) {
.unset => { .unset => {
self.cursor.pen.fg = null; self.screen.cursor.pen.fg = null;
self.cursor.pen.bg = null; self.screen.cursor.pen.bg = null;
self.cursor.pen.attrs = .{}; self.screen.cursor.pen.attrs = .{};
}, },
.bold => { .bold => {
self.cursor.pen.attrs.bold = 1; self.screen.cursor.pen.attrs.bold = 1;
}, },
.underline => { .underline => {
self.cursor.pen.attrs.underline = 1; self.screen.cursor.pen.attrs.underline = 1;
}, },
.inverse => { .inverse => {
self.cursor.pen.attrs.inverse = 1; self.screen.cursor.pen.attrs.inverse = 1;
}, },
.direct_color_fg => |rgb| { .direct_color_fg => |rgb| {
self.cursor.pen.fg = .{ self.screen.cursor.pen.fg = .{
.r = rgb.r, .r = rgb.r,
.g = rgb.g, .g = rgb.g,
.b = rgb.b, .b = rgb.b,
@ -182,24 +161,24 @@ pub fn setAttribute(self: *Terminal, attr: sgr.Attribute) !void {
}, },
.direct_color_bg => |rgb| { .direct_color_bg => |rgb| {
self.cursor.pen.bg = .{ self.screen.cursor.pen.bg = .{
.r = rgb.r, .r = rgb.r,
.g = rgb.g, .g = rgb.g,
.b = rgb.b, .b = rgb.b,
}; };
}, },
.@"8_fg" => |n| self.cursor.pen.fg = color.default[@enumToInt(n)], .@"8_fg" => |n| self.screen.cursor.pen.fg = color.default[@enumToInt(n)],
.@"8_bg" => |n| self.cursor.pen.bg = color.default[@enumToInt(n)], .@"8_bg" => |n| self.screen.cursor.pen.bg = color.default[@enumToInt(n)],
.@"8_bright_fg" => |n| self.cursor.pen.fg = color.default[@enumToInt(n)], .@"8_bright_fg" => |n| self.screen.cursor.pen.fg = color.default[@enumToInt(n)],
.@"8_bright_bg" => |n| self.cursor.pen.bg = color.default[@enumToInt(n)], .@"8_bright_bg" => |n| self.screen.cursor.pen.bg = color.default[@enumToInt(n)],
.@"256_fg" => |idx| self.cursor.pen.fg = color.default[idx], .@"256_fg" => |idx| self.screen.cursor.pen.fg = color.default[idx],
.@"256_bg" => |idx| self.cursor.pen.bg = color.default[idx], .@"256_bg" => |idx| self.screen.cursor.pen.bg = color.default[idx],
else => return error.InvalidAttribute, else => return error.InvalidAttribute,
} }
@ -213,31 +192,31 @@ pub fn print(self: *Terminal, c: u21) !void {
if (!self.screen.displayIsBottom()) self.screen.scroll(.{ .bottom = {} }); if (!self.screen.displayIsBottom()) self.screen.scroll(.{ .bottom = {} });
// If we're soft-wrapping, then handle that first. // If we're soft-wrapping, then handle that first.
if (self.cursor.pending_wrap and self.modes.autowrap == 1) { if (self.screen.cursor.pending_wrap and self.modes.autowrap == 1) {
// Mark that the cell is wrapped, which guarantees that there is // Mark that the cell is wrapped, which guarantees that there is
// at least one cell after it in the next row. // at least one cell after it in the next row.
const cell = self.screen.getCell(self.cursor.y, self.cursor.x); const cell = self.screen.getCell(self.screen.cursor.y, self.screen.cursor.x);
cell.attrs.wrap = 1; cell.attrs.wrap = 1;
// Move to the next line // Move to the next line
self.index(); self.index();
self.cursor.x = 0; self.screen.cursor.x = 0;
} }
// Build our cell // Build our cell
const cell = self.screen.getCell(self.cursor.y, self.cursor.x); const cell = self.screen.getCell(self.screen.cursor.y, self.screen.cursor.x);
cell.* = self.cursor.pen; cell.* = self.screen.cursor.pen;
cell.char = @intCast(u32, c); cell.char = @intCast(u32, c);
// Move the cursor // Move the cursor
self.cursor.x += 1; self.screen.cursor.x += 1;
// If we're at the column limit, then we need to wrap the next time. // If we're at the column limit, then we need to wrap the next time.
// This is unlikely so we do the increment above and decrement here // This is unlikely so we do the increment above and decrement here
// if we need to rather than check once. // if we need to rather than check once.
if (self.cursor.x == self.cols) { if (self.screen.cursor.x == self.cols) {
self.cursor.x -= 1; self.screen.cursor.x -= 1;
self.cursor.pending_wrap = true; self.screen.cursor.pending_wrap = true;
} }
} }
@ -276,20 +255,20 @@ pub fn decaln(self: *Terminal) void {
/// This unsets the pending wrap state without wrapping. /// This unsets the pending wrap state without wrapping.
pub fn index(self: *Terminal) void { pub fn index(self: *Terminal) void {
// Unset pending wrap state // Unset pending wrap state
self.cursor.pending_wrap = false; self.screen.cursor.pending_wrap = false;
// Outside of the scroll region we move the cursor one line down. // Outside of the scroll region we move the cursor one line down.
if (self.cursor.y < self.scrolling_region.top or if (self.screen.cursor.y < self.scrolling_region.top or
self.cursor.y > self.scrolling_region.bottom) self.screen.cursor.y > self.scrolling_region.bottom)
{ {
self.cursor.y = @minimum(self.cursor.y + 1, self.rows - 1); self.screen.cursor.y = @minimum(self.screen.cursor.y + 1, self.rows - 1);
return; return;
} }
// If the cursor is inside the scrolling region and on the bottom-most // If the cursor is inside the scrolling region and on the bottom-most
// line, then we scroll up. If our scrolling region is the full screen // line, then we scroll up. If our scrolling region is the full screen
// we create scrollback. // we create scrollback.
if (self.cursor.y == self.scrolling_region.bottom) { if (self.screen.cursor.y == self.scrolling_region.bottom) {
// If our scrolling region is the full screen, we create scrollback. // If our scrolling region is the full screen, we create scrollback.
// Otherwise, we simply scroll the region. // Otherwise, we simply scroll the region.
if (self.scrolling_region.top == 0 and if (self.scrolling_region.top == 0 and
@ -303,7 +282,7 @@ pub fn index(self: *Terminal) void {
} }
// Increase cursor by 1, maximum to bottom of scroll region // Increase cursor by 1, maximum to bottom of scroll region
self.cursor.y = @minimum(self.cursor.y + 1, self.scrolling_region.bottom); self.screen.cursor.y = @minimum(self.screen.cursor.y + 1, self.scrolling_region.bottom);
} }
/// Move the cursor to the previous line in the scrolling region, possibly /// Move the cursor to the previous line in the scrolling region, possibly
@ -321,10 +300,10 @@ pub fn index(self: *Terminal) void {
pub fn reverseIndex(self: *Terminal) !void { pub fn reverseIndex(self: *Terminal) !void {
// TODO: scrolling region // TODO: scrolling region
if (self.cursor.y == 0) { if (self.screen.cursor.y == 0) {
self.scrollDown(1); self.scrollDown(1);
} else { } else {
self.cursor.y -|= 1; self.screen.cursor.y -|= 1;
} }
} }
@ -357,12 +336,12 @@ pub fn setCursorPos(self: *Terminal, row: usize, col: usize) void {
.y_max = self.rows, .y_max = self.rows,
}; };
self.cursor.x = @minimum(params.x_max, col) -| 1; self.screen.cursor.x = @minimum(params.x_max, col) -| 1;
self.cursor.y = @minimum(params.y_max, row + params.y_offset) -| 1; self.screen.cursor.y = @minimum(params.y_max, row + params.y_offset) -| 1;
log.info("set cursor position: col={} row={}", .{ self.cursor.x, self.cursor.y }); log.info("set cursor position: col={} row={}", .{ self.screen.cursor.x, self.screen.cursor.y });
// Unset pending wrap state // Unset pending wrap state
self.cursor.pending_wrap = false; self.screen.cursor.pending_wrap = false;
} }
/// Erase the display. /// Erase the display.
@ -374,25 +353,25 @@ pub fn eraseDisplay(
switch (mode) { switch (mode) {
.complete => { .complete => {
const all = self.screen.getVisible(); const all = self.screen.getVisible();
std.mem.set(Screen.Cell, all, self.cursor.pen); std.mem.set(Screen.Cell, all, self.screen.cursor.pen);
}, },
.below => { .below => {
// All lines to the right (including the cursor) // All lines to the right (including the cursor)
var x: usize = self.cursor.x; var x: usize = self.screen.cursor.x;
while (x < self.cols) : (x += 1) { while (x < self.cols) : (x += 1) {
const cell = self.getOrPutCell(x, self.cursor.y); const cell = self.getOrPutCell(x, self.screen.cursor.y);
cell.* = self.cursor.pen; cell.* = self.screen.cursor.pen;
cell.char = 0; cell.char = 0;
} }
// All lines below // All lines below
var y: usize = self.cursor.y + 1; var y: usize = self.screen.cursor.y + 1;
while (y < self.rows) : (y += 1) { while (y < self.rows) : (y += 1) {
x = 0; x = 0;
while (x < self.cols) : (x += 1) { while (x < self.cols) : (x += 1) {
const cell = self.getOrPutCell(x, y); const cell = self.getOrPutCell(x, y);
cell.* = self.cursor.pen; cell.* = self.screen.cursor.pen;
cell.char = 0; cell.char = 0;
} }
} }
@ -401,19 +380,19 @@ pub fn eraseDisplay(
.above => { .above => {
// Erase to the left (including the cursor) // Erase to the left (including the cursor)
var x: usize = 0; var x: usize = 0;
while (x <= self.cursor.x) : (x += 1) { while (x <= self.screen.cursor.x) : (x += 1) {
const cell = self.getOrPutCell(x, self.cursor.y); const cell = self.getOrPutCell(x, self.screen.cursor.y);
cell.* = self.cursor.pen; cell.* = self.screen.cursor.pen;
cell.char = 0; cell.char = 0;
} }
// All lines above // All lines above
var y: usize = 0; var y: usize = 0;
while (y < self.cursor.y) : (y += 1) { while (y < self.screen.cursor.y) : (y += 1) {
x = 0; x = 0;
while (x < self.cols) : (x += 1) { while (x < self.cols) : (x += 1) {
const cell = self.getOrPutCell(x, y); const cell = self.getOrPutCell(x, y);
cell.* = self.cursor.pen; cell.* = self.screen.cursor.pen;
cell.char = 0; cell.char = 0;
} }
} }
@ -434,18 +413,18 @@ pub fn eraseLine(
) void { ) void {
switch (mode) { switch (mode) {
.right => { .right => {
const row = self.screen.getRow(self.cursor.y); const row = self.screen.getRow(self.screen.cursor.y);
std.mem.set(Screen.Cell, row[self.cursor.x..], self.cursor.pen); std.mem.set(Screen.Cell, row[self.screen.cursor.x..], self.screen.cursor.pen);
}, },
.left => { .left => {
const row = self.screen.getRow(self.cursor.y); const row = self.screen.getRow(self.screen.cursor.y);
std.mem.set(Screen.Cell, row[0..self.cursor.x], self.cursor.pen); std.mem.set(Screen.Cell, row[0..self.screen.cursor.x], self.screen.cursor.pen);
}, },
.complete => { .complete => {
const row = self.screen.getRow(self.cursor.y); const row = self.screen.getRow(self.screen.cursor.y);
std.mem.set(Screen.Cell, row, self.cursor.pen); std.mem.set(Screen.Cell, row, self.screen.cursor.pen);
}, },
else => { else => {
@ -466,14 +445,14 @@ pub fn eraseLine(
/// ///
/// TODO: test /// TODO: test
pub fn deleteChars(self: *Terminal, count: usize) !void { pub fn deleteChars(self: *Terminal, count: usize) !void {
const line = self.screen.getRow(self.cursor.y); const line = self.screen.getRow(self.screen.cursor.y);
// Our last index is at most the end of the number of chars we have // Our last index is at most the end of the number of chars we have
// in the current line. // in the current line.
const end = self.cols - count; const end = self.cols - count;
// Shift // Shift
var i: usize = self.cursor.x; var i: usize = self.screen.cursor.x;
while (i < end) : (i += 1) { while (i < end) : (i += 1) {
const j = i + count; const j = i + count;
line[i] = line[j]; line[i] = line[j];
@ -485,13 +464,13 @@ pub fn deleteChars(self: *Terminal, count: usize) !void {
pub fn eraseChars(self: *Terminal, count: usize) void { pub fn eraseChars(self: *Terminal, count: usize) void {
// Our last index is at most the end of the number of chars we have // Our last index is at most the end of the number of chars we have
// in the current line. // in the current line.
const end = @minimum(self.cols, self.cursor.x + count); const end = @minimum(self.cols, self.screen.cursor.x + count);
// Shift // Shift
var x: usize = self.cursor.x; var x: usize = self.screen.cursor.x;
while (x < end) : (x += 1) { while (x < end) : (x += 1) {
const cell = self.getOrPutCell(x, self.cursor.y); const cell = self.getOrPutCell(x, self.screen.cursor.y);
cell.* = self.cursor.pen; cell.* = self.screen.cursor.pen;
cell.char = 0; cell.char = 0;
} }
} }
@ -504,7 +483,7 @@ pub fn cursorLeft(self: *Terminal, count: usize) void {
// TODO: scroll region, wrap // TODO: scroll region, wrap
self.cursor.x -|= if (count == 0) 1 else count; self.screen.cursor.x -|= if (count == 0) 1 else count;
} }
/// Move the cursor right amount columns. If amount is greater than the /// Move the cursor right amount columns. If amount is greater than the
@ -516,9 +495,9 @@ pub fn cursorRight(self: *Terminal, count: usize) void {
const tracy = trace(@src()); const tracy = trace(@src());
defer tracy.end(); defer tracy.end();
self.cursor.x += count; self.screen.cursor.x += count;
if (self.cursor.x >= self.cols) { if (self.screen.cursor.x >= self.cols) {
self.cursor.x = self.cols - 1; self.screen.cursor.x = self.cols - 1;
} }
} }
@ -530,9 +509,9 @@ pub fn cursorDown(self: *Terminal, count: usize) void {
const tracy = trace(@src()); const tracy = trace(@src());
defer tracy.end(); defer tracy.end();
self.cursor.y += count; self.screen.cursor.y += count;
if (self.cursor.y >= self.rows) { if (self.screen.cursor.y >= self.rows) {
self.cursor.y = self.rows - 1; self.screen.cursor.y = self.rows - 1;
} }
} }
@ -544,7 +523,7 @@ pub fn cursorUp(self: *Terminal, count: usize) void {
const tracy = trace(@src()); const tracy = trace(@src());
defer tracy.end(); defer tracy.end();
self.cursor.y -|= count; self.screen.cursor.y -|= count;
} }
/// Backspace moves the cursor back a column (but not less than 0). /// Backspace moves the cursor back a column (but not less than 0).
@ -552,7 +531,7 @@ pub fn backspace(self: *Terminal) void {
const tracy = trace(@src()); const tracy = trace(@src());
defer tracy.end(); defer tracy.end();
self.cursor.x -|= 1; self.screen.cursor.x -|= 1;
} }
/// Horizontal tab moves the cursor to the next tabstop, clearing /// Horizontal tab moves the cursor to the next tabstop, clearing
@ -561,14 +540,14 @@ pub fn horizontalTab(self: *Terminal) !void {
const tracy = trace(@src()); const tracy = trace(@src());
defer tracy.end(); defer tracy.end();
while (self.cursor.x < self.cols) { while (self.screen.cursor.x < self.cols) {
// Move the cursor right // Move the cursor right
self.cursor.x += 1; self.screen.cursor.x += 1;
// If the last cursor position was a tabstop we return. We do // If the last cursor position was a tabstop we return. We do
// "last cursor position" because we want a space to be written // "last cursor position" because we want a space to be written
// at the tabstop unless we're at the end (the while condition). // at the tabstop unless we're at the end (the while condition).
if (self.tabstops.get(self.cursor.x)) return; if (self.tabstops.get(self.screen.cursor.x)) return;
} }
} }
@ -576,7 +555,7 @@ pub fn horizontalTab(self: *Terminal) !void {
/// TODO: test /// TODO: test
pub fn tabClear(self: *Terminal, cmd: csi.TabClear) void { pub fn tabClear(self: *Terminal, cmd: csi.TabClear) void {
switch (cmd) { switch (cmd) {
.current => self.tabstops.unset(self.cursor.x), .current => self.tabstops.unset(self.screen.cursor.x),
.all => self.tabstops.reset(0), .all => self.tabstops.reset(0),
else => log.warn("invalid or unknown tab clear setting: {}", .{cmd}), else => log.warn("invalid or unknown tab clear setting: {}", .{cmd}),
} }
@ -585,7 +564,7 @@ pub fn tabClear(self: *Terminal, cmd: csi.TabClear) void {
/// Set a tab stop on the current cursor. /// Set a tab stop on the current cursor.
/// TODO: test /// TODO: test
pub fn tabSet(self: *Terminal) void { pub fn tabSet(self: *Terminal) void {
self.tabstops.set(self.cursor.x); self.tabstops.set(self.screen.cursor.x);
} }
/// Carriage return moves the cursor to the first column. /// Carriage return moves the cursor to the first column.
@ -596,8 +575,8 @@ pub fn carriageReturn(self: *Terminal) void {
// TODO: left/right margin mode // TODO: left/right margin mode
// TODO: origin mode // TODO: origin mode
self.cursor.x = 0; self.screen.cursor.x = 0;
self.cursor.pending_wrap = false; self.screen.cursor.pending_wrap = false;
} }
/// Linefeed moves the cursor to the next line. /// Linefeed moves the cursor to the next line.
@ -614,20 +593,20 @@ pub fn linefeed(self: *Terminal) void {
/// The inserted cells are colored according to the current SGR state. /// The inserted cells are colored according to the current SGR state.
pub fn insertBlanks(self: *Terminal, count: usize) void { pub fn insertBlanks(self: *Terminal, count: usize) void {
// Unset pending wrap state without wrapping // Unset pending wrap state without wrapping
self.cursor.pending_wrap = false; self.screen.cursor.pending_wrap = false;
// If our count is larger than the remaining amount, we just erase right. // If our count is larger than the remaining amount, we just erase right.
if (count > self.cols - self.cursor.x) { if (count > self.cols - self.screen.cursor.x) {
self.eraseLine(.right); self.eraseLine(.right);
return; return;
} }
// Get the current row // Get the current row
const row = self.screen.getRow(self.cursor.y); const row = self.screen.getRow(self.screen.cursor.y);
// Determine our indexes. // Determine our indexes.
const start = self.cursor.x; const start = self.screen.cursor.x;
const pivot = self.cursor.x + count; const pivot = self.screen.cursor.x + count;
// This is the number of spaces we have left to shift existing data. // This is the number of spaces we have left to shift existing data.
// If count is bigger than the available space left after the cursor, // If count is bigger than the available space left after the cursor,
@ -648,7 +627,7 @@ pub fn insertBlanks(self: *Terminal, count: usize) void {
} }
// Insert zero // Insert zero
var pen = self.cursor.pen; var pen = self.screen.cursor.pen;
pen.char = ' '; // NOTE: this should be 0 but we need space for tests pen.char = ' '; // NOTE: this should be 0 but we need space for tests
std.mem.set(Screen.Cell, row[start..pivot], pen); std.mem.set(Screen.Cell, row[start..pivot], pen);
} }
@ -673,10 +652,10 @@ pub fn insertBlanks(self: *Terminal, count: usize) void {
/// Moves the cursor to the left margin. /// Moves the cursor to the left margin.
pub fn insertLines(self: *Terminal, count: usize) void { pub fn insertLines(self: *Terminal, count: usize) void {
// Move the cursor to the left margin // Move the cursor to the left margin
self.cursor.x = 0; self.screen.cursor.x = 0;
// Remaining rows from our cursor // Remaining rows from our cursor
const rem = self.scrolling_region.bottom - self.cursor.y + 1; const rem = self.scrolling_region.bottom - self.screen.cursor.y + 1;
// If count is greater than the amount of rows, adjust down. // If count is greater than the amount of rows, adjust down.
const adjusted_count = @minimum(count, rem); const adjusted_count = @minimum(count, rem);
@ -693,12 +672,12 @@ pub fn insertLines(self: *Terminal, count: usize) void {
} }
// Insert count blank lines // Insert count blank lines
y = self.cursor.y; y = self.screen.cursor.y;
while (y < self.cursor.y + adjusted_count) : (y += 1) { while (y < self.screen.cursor.y + adjusted_count) : (y += 1) {
var x: usize = 0; var x: usize = 0;
while (x < self.cols) : (x += 1) { while (x < self.cols) : (x += 1) {
const cell = self.getOrPutCell(x, y); const cell = self.getOrPutCell(x, y);
cell.* = self.cursor.pen; cell.* = self.screen.cursor.pen;
cell.char = 0; cell.char = 0;
} }
} }
@ -724,23 +703,23 @@ pub fn deleteLines(self: *Terminal, count: usize) void {
// TODO: scroll region bounds // TODO: scroll region bounds
// Move the cursor to the left margin // Move the cursor to the left margin
self.cursor.x = 0; self.screen.cursor.x = 0;
// Remaining number of lines in the scrolling region // Remaining number of lines in the scrolling region
const rem = self.scrolling_region.bottom - self.cursor.y + 1; const rem = self.scrolling_region.bottom - self.screen.cursor.y + 1;
// If the count is more than our remaining lines, we adjust down. // If the count is more than our remaining lines, we adjust down.
const adjusted_count = @minimum(count, rem); const adjusted_count = @minimum(count, rem);
// Scroll up the count amount. // Scroll up the count amount.
var y: usize = self.cursor.y; var y: usize = self.screen.cursor.y;
while (y <= self.scrolling_region.bottom - adjusted_count) : (y += 1) { while (y <= self.scrolling_region.bottom - adjusted_count) : (y += 1) {
self.screen.copyRow(y, y + adjusted_count); self.screen.copyRow(y, y + adjusted_count);
} }
while (y <= self.scrolling_region.bottom) : (y += 1) { while (y <= self.scrolling_region.bottom) : (y += 1) {
const row = self.screen.getRow(y); const row = self.screen.getRow(y);
std.mem.set(Screen.Cell, row, self.cursor.pen); std.mem.set(Screen.Cell, row, self.screen.cursor.pen);
} }
} }
@ -751,11 +730,11 @@ pub fn scrollDown(self: *Terminal, count: usize) void {
defer tracy.end(); defer tracy.end();
// Preserve the cursor // Preserve the cursor
const cursor = self.cursor; const cursor = self.screen.cursor;
defer self.cursor = cursor; defer self.screen.cursor = cursor;
// Move to the top of the scroll region // Move to the top of the scroll region
self.cursor.y = self.scrolling_region.top; self.screen.cursor.y = self.scrolling_region.top;
self.insertLines(count); self.insertLines(count);
} }
@ -769,11 +748,11 @@ pub fn scrollDown(self: *Terminal, count: usize) void {
// TODO: test // TODO: test
pub fn scrollUp(self: *Terminal, count: usize) void { pub fn scrollUp(self: *Terminal, count: usize) void {
// Preserve the cursor // Preserve the cursor
const cursor = self.cursor; const cursor = self.screen.cursor;
defer self.cursor = cursor; defer self.screen.cursor = cursor;
// Move to the top of the scroll region // Move to the top of the scroll region
self.cursor.y = self.scrolling_region.top; self.screen.cursor.y = self.scrolling_region.top;
self.deleteLines(count); self.deleteLines(count);
} }
@ -833,8 +812,8 @@ test "Terminal: input with no control characters" {
// Basic grid writing // Basic grid writing
for ("hello") |c| try t.print(c); for ("hello") |c| try t.print(c);
try testing.expectEqual(@as(usize, 0), t.cursor.y); try testing.expectEqual(@as(usize, 0), t.screen.cursor.y);
try testing.expectEqual(@as(usize, 5), t.cursor.x); try testing.expectEqual(@as(usize, 5), t.screen.cursor.x);
{ {
var str = try t.plainString(testing.allocator); var str = try t.plainString(testing.allocator);
defer testing.allocator.free(str); defer testing.allocator.free(str);
@ -848,8 +827,8 @@ test "Terminal: soft wrap" {
// Basic grid writing // Basic grid writing
for ("hello") |c| try t.print(c); for ("hello") |c| try t.print(c);
try testing.expectEqual(@as(usize, 1), t.cursor.y); try testing.expectEqual(@as(usize, 1), t.screen.cursor.y);
try testing.expectEqual(@as(usize, 2), t.cursor.x); try testing.expectEqual(@as(usize, 2), t.screen.cursor.x);
{ {
var str = try t.plainString(testing.allocator); var str = try t.plainString(testing.allocator);
defer testing.allocator.free(str); defer testing.allocator.free(str);
@ -901,8 +880,8 @@ test "Terminal: linefeed and carriage return" {
t.carriageReturn(); t.carriageReturn();
t.linefeed(); t.linefeed();
for ("world") |c| try t.print(c); for ("world") |c| try t.print(c);
try testing.expectEqual(@as(usize, 1), t.cursor.y); try testing.expectEqual(@as(usize, 1), t.screen.cursor.y);
try testing.expectEqual(@as(usize, 5), t.cursor.x); try testing.expectEqual(@as(usize, 5), t.screen.cursor.x);
{ {
var str = try t.plainString(testing.allocator); var str = try t.plainString(testing.allocator);
defer testing.allocator.free(str); defer testing.allocator.free(str);
@ -916,9 +895,9 @@ test "Terminal: linefeed unsets pending wrap" {
// Basic grid writing // Basic grid writing
for ("hello") |c| try t.print(c); for ("hello") |c| try t.print(c);
try testing.expect(t.cursor.pending_wrap == true); try testing.expect(t.screen.cursor.pending_wrap == true);
t.linefeed(); t.linefeed();
try testing.expect(t.cursor.pending_wrap == false); try testing.expect(t.screen.cursor.pending_wrap == false);
} }
test "Terminal: carriage return unsets pending wrap" { test "Terminal: carriage return unsets pending wrap" {
@ -927,9 +906,9 @@ test "Terminal: carriage return unsets pending wrap" {
// Basic grid writing // Basic grid writing
for ("hello") |c| try t.print(c); for ("hello") |c| try t.print(c);
try testing.expect(t.cursor.pending_wrap == true); try testing.expect(t.screen.cursor.pending_wrap == true);
t.carriageReturn(); t.carriageReturn();
try testing.expect(t.cursor.pending_wrap == false); try testing.expect(t.screen.cursor.pending_wrap == false);
} }
test "Terminal: backspace" { test "Terminal: backspace" {
@ -940,8 +919,8 @@ test "Terminal: backspace" {
for ("hello") |c| try t.print(c); for ("hello") |c| try t.print(c);
t.backspace(); t.backspace();
try t.print('y'); try t.print('y');
try testing.expectEqual(@as(usize, 0), t.cursor.y); try testing.expectEqual(@as(usize, 0), t.screen.cursor.y);
try testing.expectEqual(@as(usize, 5), t.cursor.x); try testing.expectEqual(@as(usize, 5), t.screen.cursor.x);
{ {
var str = try t.plainString(testing.allocator); var str = try t.plainString(testing.allocator);
defer testing.allocator.free(str); defer testing.allocator.free(str);
@ -957,59 +936,59 @@ test "Terminal: horizontal tabs" {
// HT // HT
try t.print('1'); try t.print('1');
try t.horizontalTab(); try t.horizontalTab();
try testing.expectEqual(@as(usize, 7), t.cursor.x); try testing.expectEqual(@as(usize, 7), t.screen.cursor.x);
// HT // HT
try t.horizontalTab(); try t.horizontalTab();
try testing.expectEqual(@as(usize, 15), t.cursor.x); try testing.expectEqual(@as(usize, 15), t.screen.cursor.x);
} }
test "Terminal: setCursorPosition" { test "Terminal: setCursorPosition" {
var t = try init(testing.allocator, 80, 80); var t = try init(testing.allocator, 80, 80);
defer t.deinit(testing.allocator); defer t.deinit(testing.allocator);
try testing.expectEqual(@as(usize, 0), t.cursor.x); try testing.expectEqual(@as(usize, 0), t.screen.cursor.x);
try testing.expectEqual(@as(usize, 0), t.cursor.y); try testing.expectEqual(@as(usize, 0), t.screen.cursor.y);
// Setting it to 0 should keep it zero (1 based) // Setting it to 0 should keep it zero (1 based)
t.setCursorPos(0, 0); t.setCursorPos(0, 0);
try testing.expectEqual(@as(usize, 0), t.cursor.x); try testing.expectEqual(@as(usize, 0), t.screen.cursor.x);
try testing.expectEqual(@as(usize, 0), t.cursor.y); try testing.expectEqual(@as(usize, 0), t.screen.cursor.y);
// Should clamp to size // Should clamp to size
t.setCursorPos(81, 81); t.setCursorPos(81, 81);
try testing.expectEqual(@as(usize, 79), t.cursor.x); try testing.expectEqual(@as(usize, 79), t.screen.cursor.x);
try testing.expectEqual(@as(usize, 79), t.cursor.y); try testing.expectEqual(@as(usize, 79), t.screen.cursor.y);
// Should reset pending wrap // Should reset pending wrap
t.setCursorPos(0, 80); t.setCursorPos(0, 80);
try t.print('c'); try t.print('c');
try testing.expect(t.cursor.pending_wrap); try testing.expect(t.screen.cursor.pending_wrap);
t.setCursorPos(0, 80); t.setCursorPos(0, 80);
try testing.expect(!t.cursor.pending_wrap); try testing.expect(!t.screen.cursor.pending_wrap);
// Origin mode // Origin mode
t.modes.origin = 1; t.modes.origin = 1;
// No change without a scroll region // No change without a scroll region
t.setCursorPos(81, 81); t.setCursorPos(81, 81);
try testing.expectEqual(@as(usize, 79), t.cursor.x); try testing.expectEqual(@as(usize, 79), t.screen.cursor.x);
try testing.expectEqual(@as(usize, 79), t.cursor.y); try testing.expectEqual(@as(usize, 79), t.screen.cursor.y);
// Set the scroll region // Set the scroll region
t.setScrollingRegion(10, t.rows); t.setScrollingRegion(10, t.rows);
t.setCursorPos(0, 0); t.setCursorPos(0, 0);
try testing.expectEqual(@as(usize, 0), t.cursor.x); try testing.expectEqual(@as(usize, 0), t.screen.cursor.x);
try testing.expectEqual(@as(usize, 9), t.cursor.y); try testing.expectEqual(@as(usize, 9), t.screen.cursor.y);
t.setCursorPos(100, 0); t.setCursorPos(100, 0);
try testing.expectEqual(@as(usize, 0), t.cursor.x); try testing.expectEqual(@as(usize, 0), t.screen.cursor.x);
try testing.expectEqual(@as(usize, 79), t.cursor.y); try testing.expectEqual(@as(usize, 79), t.screen.cursor.y);
t.setScrollingRegion(10, 11); t.setScrollingRegion(10, 11);
t.setCursorPos(2, 0); t.setCursorPos(2, 0);
try testing.expectEqual(@as(usize, 0), t.cursor.x); try testing.expectEqual(@as(usize, 0), t.screen.cursor.x);
try testing.expectEqual(@as(usize, 10), t.cursor.y); try testing.expectEqual(@as(usize, 10), t.screen.cursor.y);
} }
test "Terminal: setScrollingRegion" { test "Terminal: setScrollingRegion" {
@ -1025,8 +1004,8 @@ test "Terminal: setScrollingRegion" {
t.setScrollingRegion(3, 7); t.setScrollingRegion(3, 7);
// Cursor should move back to top-left // Cursor should move back to top-left
try testing.expectEqual(@as(usize, 0), t.cursor.x); try testing.expectEqual(@as(usize, 0), t.screen.cursor.x);
try testing.expectEqual(@as(usize, 0), t.cursor.y); try testing.expectEqual(@as(usize, 0), t.screen.cursor.y);
// Scroll region is set // Scroll region is set
try testing.expectEqual(@as(usize, 2), t.scrolling_region.top); try testing.expectEqual(@as(usize, 2), t.scrolling_region.top);
@ -1068,8 +1047,8 @@ test "Terminal: deleteLines" {
t.linefeed(); t.linefeed();
// We should be // We should be
try testing.expectEqual(@as(usize, 0), t.cursor.x); try testing.expectEqual(@as(usize, 0), t.screen.cursor.x);
try testing.expectEqual(@as(usize, 2), t.cursor.y); try testing.expectEqual(@as(usize, 2), t.screen.cursor.y);
{ {
var str = try t.plainString(testing.allocator); var str = try t.plainString(testing.allocator);
@ -1104,8 +1083,8 @@ test "Terminal: deleteLines with scroll region" {
t.linefeed(); t.linefeed();
// We should be // We should be
// try testing.expectEqual(@as(usize, 0), t.cursor.x); // try testing.expectEqual(@as(usize, 0), t.screen.cursor.x);
// try testing.expectEqual(@as(usize, 2), t.cursor.y); // try testing.expectEqual(@as(usize, 2), t.screen.cursor.y);
{ {
var str = try t.plainString(testing.allocator); var str = try t.plainString(testing.allocator);
@ -1311,10 +1290,10 @@ test "Terminal: index outside of scrolling region" {
var t = try init(alloc, 2, 5); var t = try init(alloc, 2, 5);
defer t.deinit(alloc); defer t.deinit(alloc);
try testing.expectEqual(@as(usize, 0), t.cursor.y); try testing.expectEqual(@as(usize, 0), t.screen.cursor.y);
t.setScrollingRegion(2, 5); t.setScrollingRegion(2, 5);
t.index(); t.index();
try testing.expectEqual(@as(usize, 1), t.cursor.y); try testing.expectEqual(@as(usize, 1), t.screen.cursor.y);
} }
test "Terminal: index from the bottom outside of scroll region" { test "Terminal: index from the bottom outside of scroll region" {
@ -1347,8 +1326,8 @@ test "Terminal: DECALN" {
try t.print('B'); try t.print('B');
t.decaln(); t.decaln();
try testing.expectEqual(@as(usize, 0), t.cursor.y); try testing.expectEqual(@as(usize, 0), t.screen.cursor.y);
try testing.expectEqual(@as(usize, 0), t.cursor.x); try testing.expectEqual(@as(usize, 0), t.screen.cursor.x);
{ {
var str = try t.plainString(testing.allocator); var str = try t.plainString(testing.allocator);

View File

@ -48,11 +48,11 @@ pub fn Stream(comptime Handler: type) type {
//log.debug("char: {x}", .{c}); //log.debug("char: {x}", .{c});
const actions = self.parser.next(c); const actions = self.parser.next(c);
for (actions) |action_opt| { for (actions) |action_opt| {
// if (action_opt) |action| { if (action_opt) |action| {
// if (action != .print) { if (action != .print) {
// log.info("action: {}", .{action}); log.info("action: {}", .{action});
// } }
// } }
switch (action_opt orelse continue) { switch (action_opt orelse continue) {
.print => |p| if (@hasDecl(T, "print")) try self.handler.print(p), .print => |p| if (@hasDecl(T, "print")) try self.handler.print(p),
.execute => |code| try self.execute(code), .execute => |code| try self.execute(code),