setting and clearing tab stops

This commit is contained in:
Mitchell Hashimoto
2022-06-24 18:15:14 -07:00
parent 4d2d4322f4
commit 090e580cc7
6 changed files with 76 additions and 1 deletions

View File

@ -773,3 +773,11 @@ pub fn setCursorStyle(
pub fn decaln(self: *Window) !void { pub fn decaln(self: *Window) !void {
self.terminal.decaln(); self.terminal.decaln();
} }
pub fn tabClear(self: *Window, cmd: terminal.TabClear) !void {
self.terminal.tabClear(cmd);
}
pub fn tabSet(self: *Window) !void {
self.terminal.tabSet();
}

View File

@ -83,6 +83,20 @@ pub fn set(self: *Tabstops, col: usize) void {
self.dynamic_stops[dynamic_i] |= masks[idx]; self.dynamic_stops[dynamic_i] |= masks[idx];
} }
/// Unset the tabstop at a certain column. The columns are 0-indexed.
pub fn unset(self: *Tabstops, col: usize) void {
const i = entry(col);
const idx = index(col);
if (i < prealloc_count) {
self.prealloc_stops[i] ^= masks[idx];
return;
}
const dynamic_i = i - prealloc_count;
assert(dynamic_i < self.dynamic_stops.len);
self.dynamic_stops[dynamic_i] ^= masks[idx];
}
/// Get the value of a tabstop at a specific column. The columns are 0-indexed. /// Get the value of a tabstop at a specific column. The columns are 0-indexed.
pub fn get(self: Tabstops, col: usize) bool { pub fn get(self: Tabstops, col: usize) bool {
const i = entry(col); const i = entry(col);
@ -160,6 +174,11 @@ test "Tabstops: basic" {
t.reset(0); t.reset(0);
try testing.expect(!t.get(4)); try testing.expect(!t.get(4));
t.set(4);
try testing.expect(t.get(4));
t.unset(4);
try testing.expect(!t.get(4));
} }
test "Tabstops: dynamic allocations" { test "Tabstops: dynamic allocations" {

View File

@ -19,6 +19,9 @@ const Screen = @import("Screen.zig");
const log = std.log.scoped(.terminal); const log = std.log.scoped(.terminal);
/// Default tabstop interval
const TABSTOP_INTERVAL = 8;
/// Screen is the current screen state. /// Screen is the current screen state.
screen: Screen, screen: Screen,
@ -64,7 +67,7 @@ pub fn init(alloc: Allocator, cols: usize, rows: usize) !Terminal {
.rows = rows, .rows = rows,
.screen = try Screen.init(alloc, rows, cols), .screen = try Screen.init(alloc, rows, cols),
.cursor = .{ .x = 0, .y = 0 }, .cursor = .{ .x = 0, .y = 0 },
.tabstops = try Tabstops.init(alloc, cols, 8), .tabstops = try Tabstops.init(alloc, cols, TABSTOP_INTERVAL),
.scrolling_region = .{ .scrolling_region = .{
.top = 0, .top = 0,
.bottom = rows - 1, .bottom = rows - 1,
@ -487,6 +490,22 @@ pub fn horizontalTab(self: *Terminal) !void {
} }
} }
/// Clear tab stops.
/// TODO: test
pub fn tabClear(self: *Terminal, cmd: csi.TabClear) void {
switch (cmd) {
.current => self.tabstops.unset(self.cursor.x - 1),
.all => self.tabstops.reset(0),
else => log.warn("invalid or unknown tab clear setting: {}", .{cmd}),
}
}
/// Set a tab stop on the current cursor.
/// TODO: test
pub fn tabSet(self: *Terminal) void {
self.tabstops.set(self.cursor.x - 1);
}
/// Carriage return moves the cursor to the first column. /// Carriage return moves the cursor to the first column.
pub fn carriageReturn(self: *Terminal) void { pub fn carriageReturn(self: *Terminal) void {
const tracy = trace(@src()); const tracy = trace(@src());

View File

@ -17,3 +17,13 @@ pub const EraseLine = enum(u8) {
// user-generated. // user-generated.
_, _,
}; };
// Modes for the TBC (tab clear) command.
pub const TabClear = enum(u8) {
current = 0,
all = 3,
// Non-exhaustive so that @intToEnum never fails since the inputs are
// user-generated.
_,
};

View File

@ -13,6 +13,7 @@ pub const DeviceStatusReq = ansi.DeviceStatusReq;
pub const Mode = ansi.Mode; pub const Mode = ansi.Mode;
pub const EraseDisplay = csi.EraseDisplay; pub const EraseDisplay = csi.EraseDisplay;
pub const EraseLine = csi.EraseLine; pub const EraseLine = csi.EraseLine;
pub const TabClear = csi.TabClear;
pub const Attribute = sgr.Attribute; pub const Attribute = sgr.Attribute;
// Not exported because they're just used for tests. // Not exported because they're just used for tests.

View File

@ -299,6 +299,18 @@ pub fn Stream(comptime Handler: type) type {
}, },
) else log.warn("unimplemented CSI callback: {}", .{action}), ) else log.warn("unimplemented CSI callback: {}", .{action}),
// TBC - Tab Clear
// TODO: test
'g' => if (@hasDecl(T, "tabClear")) try self.handler.tabClear(
switch (action.params.len) {
1 => @intToEnum(csi.TabClear, action.params[0]),
else => {
log.warn("invalid tab clear command: {}", .{action});
return;
},
},
) else log.warn("unimplemented CSI callback: {}", .{action}),
// SM - Set Mode // SM - Set Mode
'h' => if (@hasDecl(T, "setMode")) { 'h' => if (@hasDecl(T, "setMode")) {
for (action.params) |mode| for (action.params) |mode|
@ -399,6 +411,12 @@ pub fn Stream(comptime Handler: type) type {
}, },
} else log.warn("unimplemented ESC callback: {}", .{action}), } else log.warn("unimplemented ESC callback: {}", .{action}),
// HTS - Horizontal Tab Set
'H' => if (@hasDecl(T, "tabSet"))
try self.handler.tabSet()
else
log.warn("unimplemented tab set callback: {}", .{action}),
// RI - Reverse Index // RI - Reverse Index
'M' => if (@hasDecl(T, "reverseIndex")) switch (action.intermediates.len) { 'M' => if (@hasDecl(T, "reverseIndex")) switch (action.intermediates.len) {
0 => try self.handler.reverseIndex(), 0 => try self.handler.reverseIndex(),