From 0df4d5c5a45846845468fb1ea7c75ed72e311027 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 23 Oct 2025 20:37:32 -0700 Subject: [PATCH] terminal: margins --- src/lib/union.zig | 2 +- src/terminal/stream.zig | 95 ++++++++++++++++------------------- src/termio/stream_handler.zig | 25 ++++----- 3 files changed, 52 insertions(+), 70 deletions(-) diff --git a/src/lib/union.zig b/src/lib/union.zig index 8e9f09049..7e15aa84d 100644 --- a/src/lib/union.zig +++ b/src/lib/union.zig @@ -121,7 +121,7 @@ pub fn TaggedUnion( /// Returns the value type for the given tag. pub fn Value(comptime tag: Tag) type { - @setEvalBranchQuota(2000); + @setEvalBranchQuota(10000); inline for (@typeInfo(Union).@"union".fields) |field| { const field_tag = @field(Tag, field.name); if (field_tag == tag) return field.type; diff --git a/src/terminal/stream.zig b/src/terminal/stream.zig index 21e7d1f51..4b92e38f9 100644 --- a/src/terminal/stream.zig +++ b/src/terminal/stream.zig @@ -75,6 +75,9 @@ pub const Action = union(Key) { restore_mode: Mode, request_mode: Mode, request_mode_unknown: RawMode, + top_and_bottom_margin: Margin, + left_and_right_margin: Margin, + left_and_right_margin_ambiguous, modify_key_format: ansi.ModifyKeyFormat, protected_mode_off, protected_mode_iso, @@ -128,6 +131,9 @@ pub const Action = union(Key) { "restore_mode", "request_mode", "request_mode_unknown", + "top_and_bottom_margin", + "left_and_right_margin", + "left_and_right_margin_ambiguous", "modify_key_format", "protected_mode_off", "protected_mode_iso", @@ -195,6 +201,11 @@ pub const Action = union(Key) { mode: u16, ansi: bool, }; + + pub const Margin = extern struct { + top_left: u16, + bottom_right: u16, + }; }; /// Returns a type that can process a stream of tty control characters. @@ -1336,17 +1347,12 @@ pub fn Stream(comptime Handler: type) type { 'r' => switch (input.intermediates.len) { // DECSTBM - Set Top and Bottom Margins - 0 => if (@hasDecl(T, "setTopAndBottomMargin")) { - switch (input.params.len) { - 0 => try self.handler.setTopAndBottomMargin(0, 0), - 1 => try self.handler.setTopAndBottomMargin(input.params[0], 0), - 2 => try self.handler.setTopAndBottomMargin(input.params[0], input.params[1]), - else => log.warn("invalid DECSTBM command: {f}", .{input}), - } - } else log.warn( - "unimplemented CSI callback: {f}", - .{input}, - ), + 0 => switch (input.params.len) { + 0 => try self.handler.vt(.top_and_bottom_margin, .{ .top_left = 0, .bottom_right = 0 }), + 1 => try self.handler.vt(.top_and_bottom_margin, .{ .top_left = input.params[0], .bottom_right = 0 }), + 2 => try self.handler.vt(.top_and_bottom_margin, .{ .top_left = input.params[0], .bottom_right = input.params[1] }), + else => log.warn("invalid DECSTBM command: {f}", .{input}), + }, 1 => switch (input.intermediates[0]) { // Restore Mode @@ -1377,21 +1383,16 @@ pub fn Stream(comptime Handler: type) type { 's' => switch (input.intermediates.len) { // DECSLRM - 0 => if (@hasDecl(T, "setLeftAndRightMargin")) { - switch (input.params.len) { - // CSI S is ambiguous with zero params so we defer - // to our handler to do the proper logic. If mode 69 - // is set, then we should invoke DECSLRM, otherwise - // we should invoke SC. - 0 => try self.handler.setLeftAndRightMarginAmbiguous(), - 1 => try self.handler.setLeftAndRightMargin(input.params[0], 0), - 2 => try self.handler.setLeftAndRightMargin(input.params[0], input.params[1]), - else => log.warn("invalid DECSLRM command: {f}", .{input}), - } - } else log.warn( - "unimplemented CSI callback: {f}", - .{input}, - ), + 0 => switch (input.params.len) { + // CSI S is ambiguous with zero params so we defer + // to our handler to do the proper logic. If mode 69 + // is set, then we should invoke DECSLRM, otherwise + // we should invoke SC. + 0 => try self.handler.vt(.left_and_right_margin_ambiguous, {}), + 1 => try self.handler.vt(.left_and_right_margin, .{ .top_left = input.params[0], .bottom_right = 0 }), + 2 => try self.handler.vt(.left_and_right_margin, .{ .top_left = input.params[0], .bottom_right = input.params[1] }), + else => log.warn("invalid DECSLRM command: {f}", .{input}), + }, 1 => switch (input.intermediates[0]) { '?' => { @@ -2227,20 +2228,16 @@ test "stream: restore mode" { const Self = @This(); called: bool = false, - pub fn setTopAndBottomMargin(self: *Self, t: u16, b: u16) !void { - _ = t; - _ = b; - self.called = true; - } - pub fn vt( - self: *@This(), - comptime action: anytype, - value: anytype, + self: *Self, + comptime action: Stream(Self).Action.Tag, + value: Stream(Self).Action.Value(action), ) !void { - _ = self; - _ = action; _ = value; + switch (action) { + .top_and_bottom_margin => self.called = true, + else => {}, + } } }; @@ -2654,25 +2651,17 @@ test "stream: SCOSC" { const Self = @This(); called: bool = false, - pub fn setLeftAndRightMargin(self: *Self, left: u16, right: u16) !void { - _ = self; - _ = left; - _ = right; - @panic("bad"); - } - - pub fn setLeftAndRightMarginAmbiguous(self: *Self) !void { - self.called = true; - } - pub fn vt( - self: *@This(), - comptime action: anytype, - value: anytype, + self: *Self, + comptime action: Stream(Self).Action.Tag, + value: Stream(Self).Action.Value(action), ) !void { - _ = self; - _ = action; _ = value; + switch (action) { + .left_and_right_margin => @panic("bad"), + .left_and_right_margin_ambiguous => self.called = true, + else => {}, + } } }; diff --git a/src/termio/stream_handler.zig b/src/termio/stream_handler.zig index d66223081..e7e965e25 100644 --- a/src/termio/stream_handler.zig +++ b/src/termio/stream_handler.zig @@ -255,6 +255,15 @@ pub const StreamHandler = struct { }, .request_mode => try self.requestMode(value.mode), .request_mode_unknown => try self.requestModeUnknown(value.mode, value.ansi), + .top_and_bottom_margin => self.terminal.setTopAndBottomMargin(value.top_left, value.bottom_right), + .left_and_right_margin => self.terminal.setLeftAndRightMargin(value.top_left, value.bottom_right), + .left_and_right_margin_ambiguous => { + if (self.terminal.modes.get(.enable_left_and_right_margin)) { + self.terminal.setLeftAndRightMargin(0, 0); + } else { + self.terminal.saveCursor(); + } + }, .modify_key_format => try self.setModifyKeyFormat(value), .protected_mode_off => self.terminal.setProtectedMode(.off), .protected_mode_iso => self.terminal.setProtectedMode(.iso), @@ -437,22 +446,6 @@ pub const StreamHandler = struct { self.terminal.carriageReturn(); } - pub inline fn setTopAndBottomMargin(self: *StreamHandler, top: u16, bot: u16) !void { - self.terminal.setTopAndBottomMargin(top, bot); - } - - pub inline fn setLeftAndRightMarginAmbiguous(self: *StreamHandler) !void { - if (self.terminal.modes.get(.enable_left_and_right_margin)) { - try self.setLeftAndRightMargin(0, 0); - } else { - try self.saveCursor(); - } - } - - pub inline fn setLeftAndRightMargin(self: *StreamHandler, left: u16, right: u16) !void { - self.terminal.setLeftAndRightMargin(left, right); - } - pub fn setModifyKeyFormat(self: *StreamHandler, format: terminal.ModifyKeyFormat) !void { self.terminal.flags.modify_other_keys_2 = false; switch (format) {