diff --git a/src/terminal/stream.zig b/src/terminal/stream.zig index bc8576ad8..6a8acad9c 100644 --- a/src/terminal/stream.zig +++ b/src/terminal/stream.zig @@ -259,45 +259,58 @@ pub fn Stream(comptime Handler: type) type { ) else log.warn("unimplemented CSI callback: {}", .{action}), // Erase Display - // TODO: test - 'J' => if (@hasDecl(T, "eraseDisplay")) try self.handler.eraseDisplay( - switch (action.params.len) { - 0 => .below, - 1 => mode: { - // TODO: use meta to get enum max - if (action.params[0] > 3) { - log.warn("invalid erase display command: {}", .{action}); - return; - } + 'J' => if (@hasDecl(T, "eraseDisplay")) { + const protected_: ?bool = switch (action.intermediates.len) { + 0 => false, + 1 => if (action.intermediates[0] == '?') true else null, + else => null, + }; - break :mode @enumFromInt(action.params[0]); - }, - else => { - log.warn("invalid erase display command: {}", .{action}); - return; - }, - }, - ) else log.warn("unimplemented CSI callback: {}", .{action}), + const protected = protected_ orelse { + log.warn("invalid erase display command: {}", .{action}); + return; + }; + + const mode_: ?csi.EraseDisplay = switch (action.params.len) { + 0 => .below, + 1 => if (action.params[0] <= 3) @enumFromInt(action.params[0]) else null, + else => null, + }; + + const mode = mode_ orelse { + log.warn("invalid erase display command: {}", .{action}); + return; + }; + + try self.handler.eraseDisplay(mode, protected); + } else log.warn("unimplemented CSI callback: {}", .{action}), // Erase Line - 'K' => if (@hasDecl(T, "eraseLine")) try self.handler.eraseLine( - switch (action.params.len) { - 0 => .right, - 1 => mode: { - // TODO: use meta to get enum max - if (action.params[0] > 3) { - log.warn("invalid erase line command: {}", .{action}); - return; - } + 'K' => if (@hasDecl(T, "eraseLine")) { + const protected_: ?bool = switch (action.intermediates.len) { + 0 => false, + 1 => if (action.intermediates[0] == '?') true else null, + else => null, + }; - break :mode @enumFromInt(action.params[0]); - }, - else => { - log.warn("invalid erase line command: {}", .{action}); - return; - }, - }, - ) else log.warn("unimplemented CSI callback: {}", .{action}), + const protected = protected_ orelse { + log.warn("invalid erase line command: {}", .{action}); + return; + }; + + const mode_: ?csi.EraseLine = switch (action.params.len) { + 0 => .right, + 1 => if (action.params[0] < 3) @enumFromInt(action.params[0]) else null, + else => null, + }; + + const mode = mode_ orelse { + log.warn("invalid erase line command: {}", .{action}); + return; + }; + + try self.handler.eraseLine(mode, protected); + } else log.warn("unimplemented CSI callback: {}", .{action}), // IL - Insert Lines // TODO: test @@ -1254,3 +1267,133 @@ test "stream: DECSCA" { try testing.expectEqual(ansi.ProtectedMode.dec, s.handler.v.?); } } + +test "stream: DECED, DECSED" { + const H = struct { + const Self = @This(); + mode: ?csi.EraseDisplay = null, + protected: ?bool = null, + + pub fn eraseDisplay( + self: *Self, + mode: csi.EraseDisplay, + protected: bool, + ) !void { + self.mode = mode; + self.protected = protected; + } + }; + + var s: Stream(H) = .{ .handler = .{} }; + { + for ("\x1B[?J") |c| try s.next(c); + try testing.expectEqual(csi.EraseDisplay.below, s.handler.mode.?); + try testing.expect(s.handler.protected.?); + } + { + for ("\x1B[?0J") |c| try s.next(c); + try testing.expectEqual(csi.EraseDisplay.below, s.handler.mode.?); + try testing.expect(s.handler.protected.?); + } + { + for ("\x1B[?1J") |c| try s.next(c); + try testing.expectEqual(csi.EraseDisplay.above, s.handler.mode.?); + try testing.expect(s.handler.protected.?); + } + { + for ("\x1B[?2J") |c| try s.next(c); + try testing.expectEqual(csi.EraseDisplay.complete, s.handler.mode.?); + try testing.expect(s.handler.protected.?); + } + { + for ("\x1B[?3J") |c| try s.next(c); + try testing.expectEqual(csi.EraseDisplay.scrollback, s.handler.mode.?); + try testing.expect(s.handler.protected.?); + } + + { + for ("\x1B[J") |c| try s.next(c); + try testing.expectEqual(csi.EraseDisplay.below, s.handler.mode.?); + try testing.expect(!s.handler.protected.?); + } + { + for ("\x1B[0J") |c| try s.next(c); + try testing.expectEqual(csi.EraseDisplay.below, s.handler.mode.?); + try testing.expect(!s.handler.protected.?); + } + { + for ("\x1B[1J") |c| try s.next(c); + try testing.expectEqual(csi.EraseDisplay.above, s.handler.mode.?); + try testing.expect(!s.handler.protected.?); + } + { + for ("\x1B[2J") |c| try s.next(c); + try testing.expectEqual(csi.EraseDisplay.complete, s.handler.mode.?); + try testing.expect(!s.handler.protected.?); + } + { + for ("\x1B[3J") |c| try s.next(c); + try testing.expectEqual(csi.EraseDisplay.scrollback, s.handler.mode.?); + try testing.expect(!s.handler.protected.?); + } +} + +test "stream: DECEL, DECSEL" { + const H = struct { + const Self = @This(); + mode: ?csi.EraseLine = null, + protected: ?bool = null, + + pub fn eraseLine( + self: *Self, + mode: csi.EraseLine, + protected: bool, + ) !void { + self.mode = mode; + self.protected = protected; + } + }; + + var s: Stream(H) = .{ .handler = .{} }; + { + for ("\x1B[?K") |c| try s.next(c); + try testing.expectEqual(csi.EraseLine.right, s.handler.mode.?); + try testing.expect(s.handler.protected.?); + } + { + for ("\x1B[?0K") |c| try s.next(c); + try testing.expectEqual(csi.EraseLine.right, s.handler.mode.?); + try testing.expect(s.handler.protected.?); + } + { + for ("\x1B[?1K") |c| try s.next(c); + try testing.expectEqual(csi.EraseLine.left, s.handler.mode.?); + try testing.expect(s.handler.protected.?); + } + { + for ("\x1B[?2K") |c| try s.next(c); + try testing.expectEqual(csi.EraseLine.complete, s.handler.mode.?); + try testing.expect(s.handler.protected.?); + } + + { + for ("\x1B[K") |c| try s.next(c); + try testing.expectEqual(csi.EraseLine.right, s.handler.mode.?); + try testing.expect(!s.handler.protected.?); + } + { + for ("\x1B[0K") |c| try s.next(c); + try testing.expectEqual(csi.EraseLine.right, s.handler.mode.?); + try testing.expect(!s.handler.protected.?); + } + { + for ("\x1B[1K") |c| try s.next(c); + try testing.expectEqual(csi.EraseLine.left, s.handler.mode.?); + try testing.expect(!s.handler.protected.?); + } + { + for ("\x1B[2K") |c| try s.next(c); + try testing.expectEqual(csi.EraseLine.complete, s.handler.mode.?); + try testing.expect(!s.handler.protected.?); + } +} diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index f4057b219..c8026f34e 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -1304,7 +1304,9 @@ const StreamHandler = struct { self.terminal.setCursorPos(row, col); } - pub fn eraseDisplay(self: *StreamHandler, mode: terminal.EraseDisplay) !void { + pub fn eraseDisplay(self: *StreamHandler, mode: terminal.EraseDisplay, protected: bool) !void { + _ = protected; + if (mode == .complete) { // Whenever we erase the full display, scroll to bottom. try self.terminal.scrollViewport(.{ .bottom = {} }); @@ -1314,7 +1316,8 @@ const StreamHandler = struct { self.terminal.eraseDisplay(self.alloc, mode); } - pub fn eraseLine(self: *StreamHandler, mode: terminal.EraseLine) !void { + pub fn eraseLine(self: *StreamHandler, mode: terminal.EraseLine, protected: bool) !void { + _ = protected; self.terminal.eraseLine(mode); }