From 090e580cc719ca7fb016d23529329c33ee9bb4e6 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 24 Jun 2022 18:15:14 -0700 Subject: [PATCH] setting and clearing tab stops --- src/Window.zig | 8 ++++++++ src/terminal/Tabstops.zig | 19 +++++++++++++++++++ src/terminal/Terminal.zig | 21 ++++++++++++++++++++- src/terminal/csi.zig | 10 ++++++++++ src/terminal/main.zig | 1 + src/terminal/stream.zig | 18 ++++++++++++++++++ 6 files changed, 76 insertions(+), 1 deletion(-) diff --git a/src/Window.zig b/src/Window.zig index 6167ca5a4..46b4780e2 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -773,3 +773,11 @@ pub fn setCursorStyle( pub fn decaln(self: *Window) !void { 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(); +} diff --git a/src/terminal/Tabstops.zig b/src/terminal/Tabstops.zig index cbf304bb9..afa61787c 100644 --- a/src/terminal/Tabstops.zig +++ b/src/terminal/Tabstops.zig @@ -83,6 +83,20 @@ pub fn set(self: *Tabstops, col: usize) void { 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. pub fn get(self: Tabstops, col: usize) bool { const i = entry(col); @@ -160,6 +174,11 @@ test "Tabstops: basic" { t.reset(0); 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" { diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index e1e31f599..0f8695c9c 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -19,6 +19,9 @@ const Screen = @import("Screen.zig"); const log = std.log.scoped(.terminal); +/// Default tabstop interval +const TABSTOP_INTERVAL = 8; + /// Screen is the current screen state. screen: Screen, @@ -64,7 +67,7 @@ pub fn init(alloc: Allocator, cols: usize, rows: usize) !Terminal { .rows = rows, .screen = try Screen.init(alloc, rows, cols), .cursor = .{ .x = 0, .y = 0 }, - .tabstops = try Tabstops.init(alloc, cols, 8), + .tabstops = try Tabstops.init(alloc, cols, TABSTOP_INTERVAL), .scrolling_region = .{ .top = 0, .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. pub fn carriageReturn(self: *Terminal) void { const tracy = trace(@src()); diff --git a/src/terminal/csi.zig b/src/terminal/csi.zig index 6f1fa7ae7..757d932f7 100644 --- a/src/terminal/csi.zig +++ b/src/terminal/csi.zig @@ -17,3 +17,13 @@ pub const EraseLine = enum(u8) { // 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. + _, +}; diff --git a/src/terminal/main.zig b/src/terminal/main.zig index 170b45d12..e0a56ea1b 100644 --- a/src/terminal/main.zig +++ b/src/terminal/main.zig @@ -13,6 +13,7 @@ pub const DeviceStatusReq = ansi.DeviceStatusReq; pub const Mode = ansi.Mode; pub const EraseDisplay = csi.EraseDisplay; pub const EraseLine = csi.EraseLine; +pub const TabClear = csi.TabClear; pub const Attribute = sgr.Attribute; // Not exported because they're just used for tests. diff --git a/src/terminal/stream.zig b/src/terminal/stream.zig index 2ab33dcf7..a07ca39a2 100644 --- a/src/terminal/stream.zig +++ b/src/terminal/stream.zig @@ -299,6 +299,18 @@ pub fn Stream(comptime Handler: type) type { }, ) 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 'h' => if (@hasDecl(T, "setMode")) { for (action.params) |mode| @@ -399,6 +411,12 @@ pub fn Stream(comptime Handler: type) type { }, } 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 'M' => if (@hasDecl(T, "reverseIndex")) switch (action.intermediates.len) { 0 => try self.handler.reverseIndex(),