From d916e4e4035ffb8d171c1df02431dc01211792e5 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 26 Dec 2024 13:37:30 +0300 Subject: [PATCH] fix: handle intermediate bytes in CSI and ESC sequences This adds missing handling for CSI and ESC commands. Fixes: https://github.com/ghostty-org/ghostty/issues/3122 --- src/terminal/stream.zig | 671 +++++++++++++++++++++++++--------------- 1 file changed, 427 insertions(+), 244 deletions(-) diff --git a/src/terminal/stream.zig b/src/terminal/stream.zig index 59a8e704d..3af8ebbf8 100644 --- a/src/terminal/stream.zig +++ b/src/terminal/stream.zig @@ -380,191 +380,289 @@ pub fn Stream(comptime Handler: type) type { fn csiDispatch(self: *Self, input: Parser.Action.CSI) !void { switch (input.final) { // CUU - Cursor Up - 'A', 'k' => if (@hasDecl(T, "setCursorUp")) try self.handler.setCursorUp( - switch (input.params.len) { - 0 => 1, - 1 => input.params[0], - else => { - log.warn("invalid cursor up command: {}", .{input}); - return; + 'A', 'k' => switch (input.intermediates.len) { + 0 => if (@hasDecl(T, "setCursorUp")) try self.handler.setCursorUp( + switch (input.params.len) { + 0 => 1, + 1 => input.params[0], + else => { + log.warn("invalid cursor up command: {}", .{input}); + return; + }, }, - }, - false, - ) else log.warn("unimplemented CSI callback: {}", .{input}), + false, + ) else log.warn("unimplemented CSI callback: {}", .{input}), + + else => log.warn( + "ignoring unimplemented CSI A with intermediates: {s}", + .{input.intermediates}, + ), + }, // CUD - Cursor Down - 'B' => if (@hasDecl(T, "setCursorDown")) try self.handler.setCursorDown( - switch (input.params.len) { - 0 => 1, - 1 => input.params[0], - else => { - log.warn("invalid cursor down command: {}", .{input}); - return; + 'B' => switch (input.intermediates.len) { + 0 => if (@hasDecl(T, "setCursorDown")) try self.handler.setCursorDown( + switch (input.params.len) { + 0 => 1, + 1 => input.params[0], + else => { + log.warn("invalid cursor down command: {}", .{input}); + return; + }, }, - }, - false, - ) else log.warn("unimplemented CSI callback: {}", .{input}), + false, + ) else log.warn("unimplemented CSI callback: {}", .{input}), + + else => log.warn( + "ignoring unimplemented CSI B with intermediates: {s}", + .{input.intermediates}, + ), + }, // CUF - Cursor Right - 'C' => if (@hasDecl(T, "setCursorRight")) try self.handler.setCursorRight( - switch (input.params.len) { - 0 => 1, - 1 => input.params[0], - else => { - log.warn("invalid cursor right command: {}", .{input}); - return; + 'C' => switch (input.intermediates.len) { + 0 => if (@hasDecl(T, "setCursorRight")) try self.handler.setCursorRight( + switch (input.params.len) { + 0 => 1, + 1 => input.params[0], + else => { + log.warn("invalid cursor right command: {}", .{input}); + return; + }, }, - }, - ) else log.warn("unimplemented CSI callback: {}", .{input}), + ) else log.warn("unimplemented CSI callback: {}", .{input}), + + else => log.warn( + "ignoring unimplemented CSI C with intermediates: {s}", + .{input.intermediates}, + ), + }, // CUB - Cursor Left - 'D', 'j' => if (@hasDecl(T, "setCursorLeft")) try self.handler.setCursorLeft( - switch (input.params.len) { - 0 => 1, - 1 => input.params[0], - else => { - log.warn("invalid cursor left command: {}", .{input}); - return; + 'D', 'j' => switch (input.intermediates.len) { + 0 => if (@hasDecl(T, "setCursorLeft")) try self.handler.setCursorLeft( + switch (input.params.len) { + 0 => 1, + 1 => input.params[0], + else => { + log.warn("invalid cursor left command: {}", .{input}); + return; + }, }, - }, - ) else log.warn("unimplemented CSI callback: {}", .{input}), + ) else log.warn("unimplemented CSI callback: {}", .{input}), + + else => log.warn( + "ignoring unimplemented CSI D with intermediates: {s}", + .{input.intermediates}, + ), + }, // CNL - Cursor Next Line - 'E' => if (@hasDecl(T, "setCursorDown")) try self.handler.setCursorDown( - switch (input.params.len) { - 0 => 1, - 1 => input.params[0], - else => { - log.warn("invalid cursor up command: {}", .{input}); - return; + 'E' => switch (input.intermediates.len) { + 0 => if (@hasDecl(T, "setCursorDown")) try self.handler.setCursorDown( + switch (input.params.len) { + 0 => 1, + 1 => input.params[0], + else => { + log.warn("invalid cursor up command: {}", .{input}); + return; + }, }, - }, - true, - ) else log.warn("unimplemented CSI callback: {}", .{input}), + true, + ) else log.warn("unimplemented CSI callback: {}", .{input}), + + else => log.warn( + "ignoring unimplemented CSI E with intermediates: {s}", + .{input.intermediates}, + ), + }, // CPL - Cursor Previous Line - 'F' => if (@hasDecl(T, "setCursorUp")) try self.handler.setCursorUp( - switch (input.params.len) { - 0 => 1, - 1 => input.params[0], - else => { - log.warn("invalid cursor down command: {}", .{input}); - return; + 'F' => switch (input.intermediates.len) { + 0 => if (@hasDecl(T, "setCursorUp")) try self.handler.setCursorUp( + switch (input.params.len) { + 0 => 1, + 1 => input.params[0], + else => { + log.warn("invalid cursor down command: {}", .{input}); + return; + }, }, - }, - true, - ) else log.warn("unimplemented CSI callback: {}", .{input}), + true, + ) else log.warn("unimplemented CSI callback: {}", .{input}), + + else => log.warn( + "ignoring unimplemented CSI F with intermediates: {s}", + .{input.intermediates}, + ), + }, // HPA - Cursor Horizontal Position Absolute // TODO: test - 'G', '`' => if (@hasDecl(T, "setCursorCol")) switch (input.params.len) { - 0 => try self.handler.setCursorCol(1), - 1 => try self.handler.setCursorCol(input.params[0]), - else => log.warn("invalid HPA command: {}", .{input}), - } else log.warn("unimplemented CSI callback: {}", .{input}), + 'G', '`' => switch (input.intermediates.len) { + 0 => if (@hasDecl(T, "setCursorCol")) switch (input.params.len) { + 0 => try self.handler.setCursorCol(1), + 1 => try self.handler.setCursorCol(input.params[0]), + else => log.warn("invalid HPA command: {}", .{input}), + } else log.warn("unimplemented CSI callback: {}", .{input}), + + else => log.warn( + "ignoring unimplemented CSI G with intermediates: {s}", + .{input.intermediates}, + ), + }, // CUP - Set Cursor Position. // TODO: test - 'H', 'f' => if (@hasDecl(T, "setCursorPos")) switch (input.params.len) { - 0 => try self.handler.setCursorPos(1, 1), - 1 => try self.handler.setCursorPos(input.params[0], 1), - 2 => try self.handler.setCursorPos(input.params[0], input.params[1]), - else => log.warn("invalid CUP command: {}", .{input}), - } else log.warn("unimplemented CSI callback: {}", .{input}), + 'H', 'f' => switch (input.intermediates.len) { + 0 => if (@hasDecl(T, "setCursorPos")) switch (input.params.len) { + 0 => try self.handler.setCursorPos(1, 1), + 1 => try self.handler.setCursorPos(input.params[0], 1), + 2 => try self.handler.setCursorPos(input.params[0], input.params[1]), + else => log.warn("invalid CUP command: {}", .{input}), + } else log.warn("unimplemented CSI callback: {}", .{input}), + + else => log.warn( + "ignoring unimplemented CSI H with intermediates: {s}", + .{input.intermediates}, + ), + }, // CHT - Cursor Horizontal Tabulation - 'I' => if (@hasDecl(T, "horizontalTab")) try self.handler.horizontalTab( - switch (input.params.len) { - 0 => 1, - 1 => input.params[0], - else => { - log.warn("invalid horizontal tab command: {}", .{input}); - return; + 'I' => switch (input.intermediates.len) { + 0 => if (@hasDecl(T, "horizontalTab")) try self.handler.horizontalTab( + switch (input.params.len) { + 0 => 1, + 1 => input.params[0], + else => { + log.warn("invalid horizontal tab command: {}", .{input}); + return; + }, }, - }, - ) else log.warn("unimplemented CSI callback: {}", .{input}), + ) else log.warn("unimplemented CSI callback: {}", .{input}), + + else => log.warn( + "ignoring unimplemented CSI I with intermediates: {s}", + .{input.intermediates}, + ), + }, // Erase Display - 'J' => if (@hasDecl(T, "eraseDisplay")) { - const protected_: ?bool = switch (input.intermediates.len) { - 0 => false, - 1 => if (input.intermediates[0] == '?') true else null, - else => null, - }; + 'J' => switch (input.intermediates.len) { + 0 => if (@hasDecl(T, "eraseDisplay")) { + const protected_: ?bool = switch (input.intermediates.len) { + 0 => false, + 1 => if (input.intermediates[0] == '?') true else null, + else => null, + }; - const protected = protected_ orelse { - log.warn("invalid erase display command: {}", .{input}); - return; - }; + const protected = protected_ orelse { + log.warn("invalid erase display command: {}", .{input}); + return; + }; - const mode_: ?csi.EraseDisplay = switch (input.params.len) { - 0 => .below, - 1 => std.meta.intToEnum(csi.EraseDisplay, input.params[0]) catch null, - else => null, - }; + const mode_: ?csi.EraseDisplay = switch (input.params.len) { + 0 => .below, + 1 => std.meta.intToEnum(csi.EraseDisplay, input.params[0]) catch null, + else => null, + }; - const mode = mode_ orelse { - log.warn("invalid erase display command: {}", .{input}); - return; - }; + const mode = mode_ orelse { + log.warn("invalid erase display command: {}", .{input}); + return; + }; - try self.handler.eraseDisplay(mode, protected); - } else log.warn("unimplemented CSI callback: {}", .{input}), + try self.handler.eraseDisplay(mode, protected); + } else log.warn("unimplemented CSI callback: {}", .{input}), + + else => log.warn( + "ignoring unimplemented CSI J with intermediates: {s}", + .{input.intermediates}, + ), + }, // Erase Line - 'K' => if (@hasDecl(T, "eraseLine")) { - const protected_: ?bool = switch (input.intermediates.len) { - 0 => false, - 1 => if (input.intermediates[0] == '?') true else null, - else => null, - }; + 'K' => switch (input.intermediates.len) { + 0 => if (@hasDecl(T, "eraseLine")) { + const protected_: ?bool = switch (input.intermediates.len) { + 0 => false, + 1 => if (input.intermediates[0] == '?') true else null, + else => null, + }; - const protected = protected_ orelse { - log.warn("invalid erase line command: {}", .{input}); - return; - }; + const protected = protected_ orelse { + log.warn("invalid erase line command: {}", .{input}); + return; + }; - const mode_: ?csi.EraseLine = switch (input.params.len) { - 0 => .right, - 1 => if (input.params[0] < 3) @enumFromInt(input.params[0]) else null, - else => null, - }; + const mode_: ?csi.EraseLine = switch (input.params.len) { + 0 => .right, + 1 => if (input.params[0] < 3) @enumFromInt(input.params[0]) else null, + else => null, + }; - const mode = mode_ orelse { - log.warn("invalid erase line command: {}", .{input}); - return; - }; + const mode = mode_ orelse { + log.warn("invalid erase line command: {}", .{input}); + return; + }; - try self.handler.eraseLine(mode, protected); - } else log.warn("unimplemented CSI callback: {}", .{input}), + try self.handler.eraseLine(mode, protected); + } else log.warn("unimplemented CSI callback: {}", .{input}), + + else => log.warn( + "ignoring unimplemented CSI K with intermediates: {s}", + .{input.intermediates}, + ), + }, // IL - Insert Lines // TODO: test - 'L' => if (@hasDecl(T, "insertLines")) switch (input.params.len) { - 0 => try self.handler.insertLines(1), - 1 => try self.handler.insertLines(input.params[0]), - else => log.warn("invalid IL command: {}", .{input}), - } else log.warn("unimplemented CSI callback: {}", .{input}), + 'L' => switch (input.intermediates.len) { + 0 => if (@hasDecl(T, "insertLines")) switch (input.params.len) { + 0 => try self.handler.insertLines(1), + 1 => try self.handler.insertLines(input.params[0]), + else => log.warn("invalid IL command: {}", .{input}), + } else log.warn("unimplemented CSI callback: {}", .{input}), + + else => log.warn( + "ignoring unimplemented CSI L with intermediates: {s}", + .{input.intermediates}, + ), + }, // DL - Delete Lines // TODO: test - 'M' => if (@hasDecl(T, "deleteLines")) switch (input.params.len) { - 0 => try self.handler.deleteLines(1), - 1 => try self.handler.deleteLines(input.params[0]), - else => log.warn("invalid DL command: {}", .{input}), - } else log.warn("unimplemented CSI callback: {}", .{input}), + 'M' => switch (input.intermediates.len) { + 0 => if (@hasDecl(T, "deleteLines")) switch (input.params.len) { + 0 => try self.handler.deleteLines(1), + 1 => try self.handler.deleteLines(input.params[0]), + else => log.warn("invalid DL command: {}", .{input}), + } else log.warn("unimplemented CSI callback: {}", .{input}), + + else => log.warn( + "ignoring unimplemented CSI M with intermediates: {s}", + .{input.intermediates}, + ), + }, // Delete Character (DCH) - 'P' => if (@hasDecl(T, "deleteChars")) try self.handler.deleteChars( - switch (input.params.len) { - 0 => 1, - 1 => input.params[0], - else => { - log.warn("invalid delete characters command: {}", .{input}); - return; + 'P' => switch (input.intermediates.len) { + 0 => if (@hasDecl(T, "deleteChars")) try self.handler.deleteChars( + switch (input.params.len) { + 0 => 1, + 1 => input.params[0], + else => { + log.warn("invalid delete characters command: {}", .{input}); + return; + }, }, - }, - ) else log.warn("unimplemented CSI callback: {}", .{input}), + ) else log.warn("unimplemented CSI callback: {}", .{input}), + + else => log.warn( + "ignoring unimplemented CSI P with intermediates: {s}", + .{input.intermediates}, + ), + }, // Scroll Up (SD) @@ -587,38 +685,39 @@ pub fn Stream(comptime Handler: type) type { }, // Scroll Down (SD) - 'T' => if (@hasDecl(T, "scrollDown")) try self.handler.scrollDown( - switch (input.params.len) { - 0 => 1, - 1 => input.params[0], - else => { - log.warn("invalid scroll down command: {}", .{input}); - return; + 'T' => switch (input.intermediates.len) { + 0 => if (@hasDecl(T, "scrollDown")) try self.handler.scrollDown( + switch (input.params.len) { + 0 => 1, + 1 => input.params[0], + else => { + log.warn("invalid scroll down command: {}", .{input}); + return; + }, }, - }, - ) else log.warn("unimplemented CSI callback: {}", .{input}), + ) else log.warn("unimplemented CSI callback: {}", .{input}), + + else => log.warn( + "ignoring unimplemented CSI T with intermediates: {s}", + .{input.intermediates}, + ), + }, // Cursor Tabulation Control - 'W' => { - switch (input.params.len) { - 0 => if (@hasDecl(T, "tabSet")) - try self.handler.tabSet() - else - log.warn("unimplemented tab set callback: {}", .{input}), + 'W' => switch (input.intermediates.len) { + 0 => { + if (input.params.len == 0 or input.params[0] == 0) { + if (@hasDecl(T, "tabSet")) + try self.handler.tabSet() + else + log.warn("unimplemented tab set callback: {}", .{input}); + } - 1 => if (input.intermediates.len == 1 and input.intermediates[0] == '?') { - if (input.params[0] == 5) { - if (@hasDecl(T, "tabReset")) - try self.handler.tabReset() - else - log.warn("unimplemented tab reset callback: {}", .{input}); - } else log.warn("invalid cursor tabulation control: {}", .{input}); - } else { - switch (input.params[0]) { - 0 => if (@hasDecl(T, "tabSet")) - try self.handler.tabSet() - else - log.warn("unimplemented tab set callback: {}", .{input}), + switch (input.params.len) { + 0 => unreachable, + + 1 => switch (input.params[0]) { + 0 => unreachable, 2 => if (@hasDecl(T, "tabClear")) try self.handler.tabClear(.current) @@ -631,63 +730,103 @@ pub fn Stream(comptime Handler: type) type { log.warn("unimplemented tab clear callback: {}", .{input}), else => {}, - } - }, + }, - else => {}, - } + else => {}, + } - log.warn("invalid cursor tabulation control: {}", .{input}); - return; + log.warn("invalid cursor tabulation control: {}", .{input}); + return; + }, + + 1 => if (input.intermediates[0] == '?' and input.params[0] == 5) { + if (@hasDecl(T, "tabReset")) + try self.handler.tabReset() + else + log.warn("unimplemented tab reset callback: {}", .{input}); + } else log.warn("invalid cursor tabulation control: {}", .{input}), + + else => log.warn( + "ignoring unimplemented CSI W with intermediates: {s}", + .{input.intermediates}, + ), }, // Erase Characters (ECH) - 'X' => if (@hasDecl(T, "eraseChars")) try self.handler.eraseChars( - switch (input.params.len) { - 0 => 1, - 1 => input.params[0], - else => { - log.warn("invalid erase characters command: {}", .{input}); - return; + 'X' => switch (input.intermediates.len) { + 0 => if (@hasDecl(T, "eraseChars")) try self.handler.eraseChars( + switch (input.params.len) { + 0 => 1, + 1 => input.params[0], + else => { + log.warn("invalid erase characters command: {}", .{input}); + return; + }, }, - }, - ) else log.warn("unimplemented CSI callback: {}", .{input}), + ) else log.warn("unimplemented CSI callback: {}", .{input}), + + else => log.warn( + "ignoring unimplemented CSI X with intermediates: {s}", + .{input.intermediates}, + ), + }, // CHT - Cursor Horizontal Tabulation Back - 'Z' => if (@hasDecl(T, "horizontalTabBack")) try self.handler.horizontalTabBack( - switch (input.params.len) { - 0 => 1, - 1 => input.params[0], - else => { - log.warn("invalid horizontal tab back command: {}", .{input}); - return; + 'Z' => switch (input.intermediates.len) { + 0 => if (@hasDecl(T, "horizontalTabBack")) try self.handler.horizontalTabBack( + switch (input.params.len) { + 0 => 1, + 1 => input.params[0], + else => { + log.warn("invalid horizontal tab back command: {}", .{input}); + return; + }, }, - }, - ) else log.warn("unimplemented CSI callback: {}", .{input}), + ) else log.warn("unimplemented CSI callback: {}", .{input}), + + else => log.warn( + "ignoring unimplemented CSI Z with intermediates: {s}", + .{input.intermediates}, + ), + }, // HPR - Cursor Horizontal Position Relative - 'a' => if (@hasDecl(T, "setCursorColRelative")) try self.handler.setCursorColRelative( - switch (input.params.len) { - 0 => 1, - 1 => input.params[0], - else => { - log.warn("invalid HPR command: {}", .{input}); - return; + 'a' => switch (input.intermediates.len) { + 0 => if (@hasDecl(T, "setCursorColRelative")) try self.handler.setCursorColRelative( + switch (input.params.len) { + 0 => 1, + 1 => input.params[0], + else => { + log.warn("invalid HPR command: {}", .{input}); + return; + }, }, - }, - ) else log.warn("unimplemented CSI callback: {}", .{input}), + ) else log.warn("unimplemented CSI callback: {}", .{input}), + + else => log.warn( + "ignoring unimplemented CSI a with intermediates: {s}", + .{input.intermediates}, + ), + }, // Repeat Previous Char (REP) - 'b' => if (@hasDecl(T, "printRepeat")) try self.handler.printRepeat( - switch (input.params.len) { - 0 => 1, - 1 => input.params[0], - else => { - log.warn("invalid print repeat command: {}", .{input}); - return; + 'b' => switch (input.intermediates.len) { + 0 => if (@hasDecl(T, "printRepeat")) try self.handler.printRepeat( + switch (input.params.len) { + 0 => 1, + 1 => input.params[0], + else => { + log.warn("invalid print repeat command: {}", .{input}); + return; + }, }, - }, - ) else log.warn("unimplemented CSI callback: {}", .{input}), + ) else log.warn("unimplemented CSI callback: {}", .{input}), + + else => log.warn( + "ignoring unimplemented CSI b with intermediates: {s}", + .{input.intermediates}, + ), + }, // c - Device Attributes (DA1) 'c' => if (@hasDecl(T, "deviceAttributes")) { @@ -708,40 +847,61 @@ pub fn Stream(comptime Handler: type) type { } else log.warn("unimplemented CSI callback: {}", .{input}), // VPA - Cursor Vertical Position Absolute - 'd' => if (@hasDecl(T, "setCursorRow")) try self.handler.setCursorRow( - switch (input.params.len) { - 0 => 1, - 1 => input.params[0], - else => { - log.warn("invalid VPA command: {}", .{input}); - return; + 'd' => switch (input.intermediates.len) { + 0 => if (@hasDecl(T, "setCursorRow")) try self.handler.setCursorRow( + switch (input.params.len) { + 0 => 1, + 1 => input.params[0], + else => { + log.warn("invalid VPA command: {}", .{input}); + return; + }, }, - }, - ) else log.warn("unimplemented CSI callback: {}", .{input}), + ) else log.warn("unimplemented CSI callback: {}", .{input}), + + else => log.warn( + "ignoring unimplemented CSI d with intermediates: {s}", + .{input.intermediates}, + ), + }, // VPR - Cursor Vertical Position Relative - 'e' => if (@hasDecl(T, "setCursorRowRelative")) try self.handler.setCursorRowRelative( - switch (input.params.len) { - 0 => 1, - 1 => input.params[0], - else => { - log.warn("invalid VPR command: {}", .{input}); - return; + 'e' => switch (input.intermediates.len) { + 0 => if (@hasDecl(T, "setCursorRowRelative")) try self.handler.setCursorRowRelative( + switch (input.params.len) { + 0 => 1, + 1 => input.params[0], + else => { + log.warn("invalid VPR command: {}", .{input}); + return; + }, }, - }, - ) else log.warn("unimplemented CSI callback: {}", .{input}), + ) else log.warn("unimplemented CSI callback: {}", .{input}), + + else => log.warn( + "ignoring unimplemented CSI e with intermediates: {s}", + .{input.intermediates}, + ), + }, // TBC - Tab Clear // TODO: test - 'g' => if (@hasDecl(T, "tabClear")) try self.handler.tabClear( - switch (input.params.len) { - 1 => @enumFromInt(input.params[0]), - else => { - log.warn("invalid tab clear command: {}", .{input}); - return; + 'g' => switch (input.intermediates.len) { + 0 => if (@hasDecl(T, "tabClear")) try self.handler.tabClear( + switch (input.params.len) { + 1 => @enumFromInt(input.params[0]), + else => { + log.warn("invalid tab clear command: {}", .{input}); + return; + }, }, - }, - ) else log.warn("unimplemented CSI callback: {}", .{input}), + ) else log.warn("unimplemented CSI callback: {}", .{input}), + + else => log.warn( + "ignoring unimplemented CSI g with intermediates: {s}", + .{input.intermediates}, + ), + }, // SM - Set Mode 'h' => if (@hasDecl(T, "setMode")) mode: { @@ -1564,10 +1724,13 @@ 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}), + 'H' => if (@hasDecl(T, "tabSet")) switch (action.intermediates.len) { + 0 => try self.handler.tabSet(), + else => { + log.warn("invalid tab set command: {}", .{action}); + return; + }, + } else log.warn("unimplemented tab set callback: {}", .{action}), // RI - Reverse Index 'M' => if (@hasDecl(T, "reverseIndex")) switch (action.intermediates.len) { @@ -1597,17 +1760,17 @@ pub fn Stream(comptime Handler: type) type { } else log.warn("unimplemented invokeCharset: {}", .{action}), // SPA - Start of Guarded Area - 'V' => if (@hasDecl(T, "setProtectedMode")) { + 'V' => if (@hasDecl(T, "setProtectedMode") and action.intermediates.len == 0) { try self.handler.setProtectedMode(ansi.ProtectedMode.iso); } else log.warn("unimplemented ESC callback: {}", .{action}), // EPA - End of Guarded Area - 'W' => if (@hasDecl(T, "setProtectedMode")) { + 'W' => if (@hasDecl(T, "setProtectedMode") and action.intermediates.len == 0) { try self.handler.setProtectedMode(ansi.ProtectedMode.off); } else log.warn("unimplemented ESC callback: {}", .{action}), // DECID - 'Z' => if (@hasDecl(T, "deviceAttributes")) { + 'Z' => if (@hasDecl(T, "deviceAttributes") and action.intermediates.len == 0) { try self.handler.deviceAttributes(.primary, &.{}); } else log.warn("unimplemented ESC callback: {}", .{action}), @@ -1666,12 +1829,12 @@ pub fn Stream(comptime Handler: type) type { } else log.warn("unimplemented invokeCharset: {}", .{action}), // Set application keypad mode - '=' => if (@hasDecl(T, "setMode")) { + '=' => if (@hasDecl(T, "setMode") and action.intermediates.len == 0) { try self.handler.setMode(.keypad_keys, true); } else log.warn("unimplemented setMode: {}", .{action}), // Reset application keypad mode - '>' => if (@hasDecl(T, "setMode")) { + '>' => if (@hasDecl(T, "setMode") and action.intermediates.len == 0) { try self.handler.setMode(.keypad_keys, false); } else log.warn("unimplemented setMode: {}", .{action}), @@ -1753,6 +1916,10 @@ test "stream: cursor right (CUF)" { s.handler.amount = 0; try s.nextSlice("\x1B[5;4C"); try testing.expectEqual(@as(u16, 0), s.handler.amount); + + s.handler.amount = 0; + try s.nextSlice("\x1b[?3C"); + try testing.expectEqual(@as(u16, 0), s.handler.amount); } test "stream: dec set mode (SM) and reset mode (RM)" { @@ -1770,6 +1937,10 @@ test "stream: dec set mode (SM) and reset mode (RM)" { try s.nextSlice("\x1B[?6l"); try testing.expectEqual(@as(modes.Mode, @enumFromInt(1)), s.handler.mode); + + s.handler.mode = null; + try s.nextSlice("\x1B[6 h"); + try testing.expectEqual(null, s.handler.mode); } test "stream: ansi set mode (SM) and reset mode (RM)" { @@ -1788,6 +1959,10 @@ test "stream: ansi set mode (SM) and reset mode (RM)" { try s.nextSlice("\x1B[4l"); try testing.expect(s.handler.mode == null); + + s.handler.mode = null; + try s.nextSlice("\x1B[>5h"); + try testing.expect(s.handler.mode == null); } test "stream: ansi set mode (SM) and reset mode (RM) with unknown value" { @@ -2374,6 +2549,14 @@ test "stream CSI W tab set" { s.handler.called = false; try s.nextSlice("\x1b[0W"); try testing.expect(s.handler.called); + + s.handler.called = false; + try s.nextSlice("\x1b[>W"); + try testing.expect(!s.handler.called); + + s.handler.called = false; + try s.nextSlice("\x1b[99W"); + try testing.expect(!s.handler.called); } test "stream CSI ? W reset tab stops" {