From c871140419f0d41c7b53ab2f55432d6df0f4f2ca Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 28 Jan 2024 08:04:14 -0800 Subject: [PATCH] terminal: handle SCOSC/SCORC Fixes #1401 SCOSC is ambiguous with regards to DECSLRM. This commit copies the logic of xterm: if left/right mode is enabled, then CSI S is always DECSLRM. But if left/right mode is disabled then CSI S empty always uses SCOSC. SCORC always works. --- src/terminal/stream.zig | 50 +++++++++++++++++++++++++++++++++++++++-- src/termio/Exec.zig | 8 +++++++ 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/src/terminal/stream.zig b/src/terminal/stream.zig index a3400421f..69ef485b0 100644 --- a/src/terminal/stream.zig +++ b/src/terminal/stream.zig @@ -805,7 +805,11 @@ pub fn Stream(comptime Handler: type) type { // DECSLRM 0 => if (@hasDecl(T, "setLeftAndRightMargin")) { switch (action.params.len) { - 0 => try self.handler.setLeftAndRightMargin(0, 0), + // 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(action.params[0], 0), 2 => try self.handler.setLeftAndRightMargin(action.params[0], action.params[1]), else => log.warn("invalid DECSLRM command: {}", .{action}), @@ -865,8 +869,13 @@ pub fn Stream(comptime Handler: type) type { ), }, - // Kitty keyboard protocol 'u' => switch (action.intermediates.len) { + 0 => if (@hasDecl(T, "restoreCursor")) + try self.handler.restoreCursor() + else + log.warn("unimplemented CSI callback: {}", .{action}), + + // Kitty keyboard protocol 1 => switch (action.intermediates[0]) { '?' => if (@hasDecl(T, "queryKittyKeyboard")) { try self.handler.queryKittyKeyboard(); @@ -1676,6 +1685,43 @@ test "stream: insert characters" { try testing.expect(!s.handler.called); } +test "stream: SCOSC" { + const H = struct { + 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; + } + }; + + var s: Stream(H) = .{ .handler = .{} }; + for ("\x1B[s") |c| try s.next(c); + try testing.expect(s.handler.called); +} + +test "stream: SCORC" { + const H = struct { + const Self = @This(); + called: bool = false, + + pub fn restoreCursor(self: *Self) !void { + self.called = true; + } + }; + + var s: Stream(H) = .{ .handler = .{} }; + for ("\x1B[u") |c| try s.next(c); + try testing.expect(s.handler.called); +} + test "stream: too many csi params" { const H = struct { pub fn setCursorRight(self: *@This(), v: u16) !void { diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index a90df93a8..175a8d4b2 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -2044,6 +2044,14 @@ const StreamHandler = struct { self.terminal.setTopAndBottomMargin(top, bot); } + pub 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 fn setLeftAndRightMargin(self: *StreamHandler, left: u16, right: u16) !void { self.terminal.setLeftAndRightMargin(left, right); }