fix: handle intermediate bytes in CSI and ESC sequences (#4063)

This adds missing handling for CSI and ESC commands.

Fixes: https://github.com/ghostty-org/ghostty/issues/3122

Supersedes: #3132
This commit is contained in:
Mitchell Hashimoto
2025-01-02 13:53:19 -08:00
committed by GitHub

View File

@ -380,109 +380,172 @@ pub fn Stream(comptime Handler: type) type {
fn csiDispatch(self: *Self, input: Parser.Action.CSI) !void { fn csiDispatch(self: *Self, input: Parser.Action.CSI) !void {
switch (input.final) { switch (input.final) {
// CUU - Cursor Up // CUU - Cursor Up
'A', 'k' => if (@hasDecl(T, "setCursorUp")) try self.handler.setCursorUp( 'A', 'k' => switch (input.intermediates.len) {
switch (input.params.len) { 0 => if (@hasDecl(T, "setCursorUp")) try self.handler.setCursorUp(
0 => 1, switch (input.params.len) {
1 => input.params[0], 0 => 1,
else => { 1 => input.params[0],
log.warn("invalid cursor up command: {}", .{input}); else => {
return; log.warn("invalid cursor up command: {}", .{input});
return;
},
}, },
}, false,
false, ) 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},
),
},
// CUD - Cursor Down // CUD - Cursor Down
'B' => if (@hasDecl(T, "setCursorDown")) try self.handler.setCursorDown( 'B' => switch (input.intermediates.len) {
switch (input.params.len) { 0 => if (@hasDecl(T, "setCursorDown")) try self.handler.setCursorDown(
0 => 1, switch (input.params.len) {
1 => input.params[0], 0 => 1,
else => { 1 => input.params[0],
log.warn("invalid cursor down command: {}", .{input}); else => {
return; log.warn("invalid cursor down command: {}", .{input});
return;
},
}, },
}, false,
false, ) 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},
),
},
// CUF - Cursor Right // CUF - Cursor Right
'C' => if (@hasDecl(T, "setCursorRight")) try self.handler.setCursorRight( 'C' => switch (input.intermediates.len) {
switch (input.params.len) { 0 => if (@hasDecl(T, "setCursorRight")) try self.handler.setCursorRight(
0 => 1, switch (input.params.len) {
1 => input.params[0], 0 => 1,
else => { 1 => input.params[0],
log.warn("invalid cursor right command: {}", .{input}); else => {
return; 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 // CUB - Cursor Left
'D', 'j' => if (@hasDecl(T, "setCursorLeft")) try self.handler.setCursorLeft( 'D', 'j' => switch (input.intermediates.len) {
switch (input.params.len) { 0 => if (@hasDecl(T, "setCursorLeft")) try self.handler.setCursorLeft(
0 => 1, switch (input.params.len) {
1 => input.params[0], 0 => 1,
else => { 1 => input.params[0],
log.warn("invalid cursor left command: {}", .{input}); else => {
return; 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 // CNL - Cursor Next Line
'E' => if (@hasDecl(T, "setCursorDown")) try self.handler.setCursorDown( 'E' => switch (input.intermediates.len) {
switch (input.params.len) { 0 => if (@hasDecl(T, "setCursorDown")) try self.handler.setCursorDown(
0 => 1, switch (input.params.len) {
1 => input.params[0], 0 => 1,
else => { 1 => input.params[0],
log.warn("invalid cursor up command: {}", .{input}); else => {
return; log.warn("invalid cursor up command: {}", .{input});
return;
},
}, },
}, true,
true, ) 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},
),
},
// CPL - Cursor Previous Line // CPL - Cursor Previous Line
'F' => if (@hasDecl(T, "setCursorUp")) try self.handler.setCursorUp( 'F' => switch (input.intermediates.len) {
switch (input.params.len) { 0 => if (@hasDecl(T, "setCursorUp")) try self.handler.setCursorUp(
0 => 1, switch (input.params.len) {
1 => input.params[0], 0 => 1,
else => { 1 => input.params[0],
log.warn("invalid cursor down command: {}", .{input}); else => {
return; log.warn("invalid cursor down command: {}", .{input});
return;
},
}, },
}, true,
true, ) else log.warn("unimplemented CSI callback: {}", .{input}),
) else log.warn("unimplemented CSI callback: {}", .{input}),
else => log.warn(
"ignoring unimplemented CSI F with intermediates: {s}",
.{input.intermediates},
),
},
// HPA - Cursor Horizontal Position Absolute // HPA - Cursor Horizontal Position Absolute
// TODO: test // TODO: test
'G', '`' => if (@hasDecl(T, "setCursorCol")) switch (input.params.len) { 'G', '`' => switch (input.intermediates.len) {
0 => try self.handler.setCursorCol(1), 0 => if (@hasDecl(T, "setCursorCol")) switch (input.params.len) {
1 => try self.handler.setCursorCol(input.params[0]), 0 => try self.handler.setCursorCol(1),
else => log.warn("invalid HPA command: {}", .{input}), 1 => try self.handler.setCursorCol(input.params[0]),
} else log.warn("unimplemented CSI callback: {}", .{input}), 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. // CUP - Set Cursor Position.
// TODO: test // TODO: test
'H', 'f' => if (@hasDecl(T, "setCursorPos")) switch (input.params.len) { 'H', 'f' => switch (input.intermediates.len) {
0 => try self.handler.setCursorPos(1, 1), 0 => if (@hasDecl(T, "setCursorPos")) switch (input.params.len) {
1 => try self.handler.setCursorPos(input.params[0], 1), 0 => try self.handler.setCursorPos(1, 1),
2 => try self.handler.setCursorPos(input.params[0], input.params[1]), 1 => try self.handler.setCursorPos(input.params[0], 1),
else => log.warn("invalid CUP command: {}", .{input}), 2 => try self.handler.setCursorPos(input.params[0], input.params[1]),
} else log.warn("unimplemented CSI callback: {}", .{input}), 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 // CHT - Cursor Horizontal Tabulation
'I' => if (@hasDecl(T, "horizontalTab")) try self.handler.horizontalTab( 'I' => switch (input.intermediates.len) {
switch (input.params.len) { 0 => if (@hasDecl(T, "horizontalTab")) try self.handler.horizontalTab(
0 => 1, switch (input.params.len) {
1 => input.params[0], 0 => 1,
else => { 1 => input.params[0],
log.warn("invalid horizontal tab command: {}", .{input}); else => {
return; 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 // Erase Display
'J' => if (@hasDecl(T, "eraseDisplay")) { 'J' => if (@hasDecl(T, "eraseDisplay")) {
@ -540,31 +603,52 @@ pub fn Stream(comptime Handler: type) type {
// IL - Insert Lines // IL - Insert Lines
// TODO: test // TODO: test
'L' => if (@hasDecl(T, "insertLines")) switch (input.params.len) { 'L' => switch (input.intermediates.len) {
0 => try self.handler.insertLines(1), 0 => if (@hasDecl(T, "insertLines")) switch (input.params.len) {
1 => try self.handler.insertLines(input.params[0]), 0 => try self.handler.insertLines(1),
else => log.warn("invalid IL command: {}", .{input}), 1 => try self.handler.insertLines(input.params[0]),
} else log.warn("unimplemented CSI callback: {}", .{input}), 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 // DL - Delete Lines
// TODO: test // TODO: test
'M' => if (@hasDecl(T, "deleteLines")) switch (input.params.len) { 'M' => switch (input.intermediates.len) {
0 => try self.handler.deleteLines(1), 0 => if (@hasDecl(T, "deleteLines")) switch (input.params.len) {
1 => try self.handler.deleteLines(input.params[0]), 0 => try self.handler.deleteLines(1),
else => log.warn("invalid DL command: {}", .{input}), 1 => try self.handler.deleteLines(input.params[0]),
} else log.warn("unimplemented CSI callback: {}", .{input}), 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) // Delete Character (DCH)
'P' => if (@hasDecl(T, "deleteChars")) try self.handler.deleteChars( 'P' => switch (input.intermediates.len) {
switch (input.params.len) { 0 => if (@hasDecl(T, "deleteChars")) try self.handler.deleteChars(
0 => 1, switch (input.params.len) {
1 => input.params[0], 0 => 1,
else => { 1 => input.params[0],
log.warn("invalid delete characters command: {}", .{input}); else => {
return; 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) // Scroll Up (SD)
@ -587,38 +671,43 @@ pub fn Stream(comptime Handler: type) type {
}, },
// Scroll Down (SD) // Scroll Down (SD)
'T' => if (@hasDecl(T, "scrollDown")) try self.handler.scrollDown( 'T' => switch (input.intermediates.len) {
switch (input.params.len) { 0 => if (@hasDecl(T, "scrollDown")) try self.handler.scrollDown(
0 => 1, switch (input.params.len) {
1 => input.params[0], 0 => 1,
else => { 1 => input.params[0],
log.warn("invalid scroll down command: {}", .{input}); else => {
return; 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 // Cursor Tabulation Control
'W' => { 'W' => switch (input.intermediates.len) {
switch (input.params.len) { 0 => {
0 => if (@hasDecl(T, "tabSet")) if (input.params.len == 0 or
try self.handler.tabSet() (input.params.len == 1 and input.params[0] == 0))
else {
log.warn("unimplemented tab set callback: {}", .{input}), 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] == '?') { return;
if (input.params[0] == 5) { }
if (@hasDecl(T, "tabReset"))
try self.handler.tabReset() switch (input.params.len) {
else 0 => unreachable,
log.warn("unimplemented tab reset callback: {}", .{input});
} else log.warn("invalid cursor tabulation control: {}", .{input}); 1 => switch (input.params[0]) {
} else { 0 => unreachable,
switch (input.params[0]) {
0 => if (@hasDecl(T, "tabSet"))
try self.handler.tabSet()
else
log.warn("unimplemented tab set callback: {}", .{input}),
2 => if (@hasDecl(T, "tabClear")) 2 => if (@hasDecl(T, "tabClear"))
try self.handler.tabClear(.current) try self.handler.tabClear(.current)
@ -631,63 +720,103 @@ pub fn Stream(comptime Handler: type) type {
log.warn("unimplemented tab clear callback: {}", .{input}), log.warn("unimplemented tab clear callback: {}", .{input}),
else => {}, else => {},
} },
},
else => {}, else => {},
} }
log.warn("invalid cursor tabulation control: {}", .{input}); log.warn("invalid cursor tabulation control: {}", .{input});
return; 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) // Erase Characters (ECH)
'X' => if (@hasDecl(T, "eraseChars")) try self.handler.eraseChars( 'X' => switch (input.intermediates.len) {
switch (input.params.len) { 0 => if (@hasDecl(T, "eraseChars")) try self.handler.eraseChars(
0 => 1, switch (input.params.len) {
1 => input.params[0], 0 => 1,
else => { 1 => input.params[0],
log.warn("invalid erase characters command: {}", .{input}); else => {
return; 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 // CHT - Cursor Horizontal Tabulation Back
'Z' => if (@hasDecl(T, "horizontalTabBack")) try self.handler.horizontalTabBack( 'Z' => switch (input.intermediates.len) {
switch (input.params.len) { 0 => if (@hasDecl(T, "horizontalTabBack")) try self.handler.horizontalTabBack(
0 => 1, switch (input.params.len) {
1 => input.params[0], 0 => 1,
else => { 1 => input.params[0],
log.warn("invalid horizontal tab back command: {}", .{input}); else => {
return; 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 // HPR - Cursor Horizontal Position Relative
'a' => if (@hasDecl(T, "setCursorColRelative")) try self.handler.setCursorColRelative( 'a' => switch (input.intermediates.len) {
switch (input.params.len) { 0 => if (@hasDecl(T, "setCursorColRelative")) try self.handler.setCursorColRelative(
0 => 1, switch (input.params.len) {
1 => input.params[0], 0 => 1,
else => { 1 => input.params[0],
log.warn("invalid HPR command: {}", .{input}); else => {
return; 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) // Repeat Previous Char (REP)
'b' => if (@hasDecl(T, "printRepeat")) try self.handler.printRepeat( 'b' => switch (input.intermediates.len) {
switch (input.params.len) { 0 => if (@hasDecl(T, "printRepeat")) try self.handler.printRepeat(
0 => 1, switch (input.params.len) {
1 => input.params[0], 0 => 1,
else => { 1 => input.params[0],
log.warn("invalid print repeat command: {}", .{input}); else => {
return; 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 - Device Attributes (DA1)
'c' => if (@hasDecl(T, "deviceAttributes")) { 'c' => if (@hasDecl(T, "deviceAttributes")) {
@ -708,40 +837,61 @@ pub fn Stream(comptime Handler: type) type {
} else log.warn("unimplemented CSI callback: {}", .{input}), } else log.warn("unimplemented CSI callback: {}", .{input}),
// VPA - Cursor Vertical Position Absolute // VPA - Cursor Vertical Position Absolute
'd' => if (@hasDecl(T, "setCursorRow")) try self.handler.setCursorRow( 'd' => switch (input.intermediates.len) {
switch (input.params.len) { 0 => if (@hasDecl(T, "setCursorRow")) try self.handler.setCursorRow(
0 => 1, switch (input.params.len) {
1 => input.params[0], 0 => 1,
else => { 1 => input.params[0],
log.warn("invalid VPA command: {}", .{input}); else => {
return; 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 // VPR - Cursor Vertical Position Relative
'e' => if (@hasDecl(T, "setCursorRowRelative")) try self.handler.setCursorRowRelative( 'e' => switch (input.intermediates.len) {
switch (input.params.len) { 0 => if (@hasDecl(T, "setCursorRowRelative")) try self.handler.setCursorRowRelative(
0 => 1, switch (input.params.len) {
1 => input.params[0], 0 => 1,
else => { 1 => input.params[0],
log.warn("invalid VPR command: {}", .{input}); else => {
return; 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 // TBC - Tab Clear
// TODO: test // TODO: test
'g' => if (@hasDecl(T, "tabClear")) try self.handler.tabClear( 'g' => switch (input.intermediates.len) {
switch (input.params.len) { 0 => if (@hasDecl(T, "tabClear")) try self.handler.tabClear(
1 => @enumFromInt(input.params[0]), switch (input.params.len) {
else => { 1 => @enumFromInt(input.params[0]),
log.warn("invalid tab clear command: {}", .{input}); else => {
return; 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 // SM - Set Mode
'h' => if (@hasDecl(T, "setMode")) mode: { 'h' => if (@hasDecl(T, "setMode")) mode: {
@ -1564,10 +1714,13 @@ pub fn Stream(comptime Handler: type) type {
} else log.warn("unimplemented ESC callback: {}", .{action}), } else log.warn("unimplemented ESC callback: {}", .{action}),
// HTS - Horizontal Tab Set // HTS - Horizontal Tab Set
'H' => if (@hasDecl(T, "tabSet")) 'H' => if (@hasDecl(T, "tabSet")) switch (action.intermediates.len) {
try self.handler.tabSet() 0 => try self.handler.tabSet(),
else else => {
log.warn("unimplemented tab set callback: {}", .{action}), log.warn("invalid tab set command: {}", .{action});
return;
},
} else log.warn("unimplemented tab set callback: {}", .{action}),
// RI - Reverse Index // RI - Reverse Index
'M' => if (@hasDecl(T, "reverseIndex")) switch (action.intermediates.len) { 'M' => if (@hasDecl(T, "reverseIndex")) switch (action.intermediates.len) {
@ -1597,17 +1750,17 @@ pub fn Stream(comptime Handler: type) type {
} else log.warn("unimplemented invokeCharset: {}", .{action}), } else log.warn("unimplemented invokeCharset: {}", .{action}),
// SPA - Start of Guarded Area // 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); try self.handler.setProtectedMode(ansi.ProtectedMode.iso);
} else log.warn("unimplemented ESC callback: {}", .{action}), } else log.warn("unimplemented ESC callback: {}", .{action}),
// EPA - End of Guarded Area // 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); try self.handler.setProtectedMode(ansi.ProtectedMode.off);
} else log.warn("unimplemented ESC callback: {}", .{action}), } else log.warn("unimplemented ESC callback: {}", .{action}),
// DECID // DECID
'Z' => if (@hasDecl(T, "deviceAttributes")) { 'Z' => if (@hasDecl(T, "deviceAttributes") and action.intermediates.len == 0) {
try self.handler.deviceAttributes(.primary, &.{}); try self.handler.deviceAttributes(.primary, &.{});
} else log.warn("unimplemented ESC callback: {}", .{action}), } else log.warn("unimplemented ESC callback: {}", .{action}),
@ -1666,12 +1819,12 @@ pub fn Stream(comptime Handler: type) type {
} else log.warn("unimplemented invokeCharset: {}", .{action}), } else log.warn("unimplemented invokeCharset: {}", .{action}),
// Set application keypad mode // Set application keypad mode
'=' => if (@hasDecl(T, "setMode")) { '=' => if (@hasDecl(T, "setMode") and action.intermediates.len == 0) {
try self.handler.setMode(.keypad_keys, true); try self.handler.setMode(.keypad_keys, true);
} else log.warn("unimplemented setMode: {}", .{action}), } else log.warn("unimplemented setMode: {}", .{action}),
// Reset application keypad mode // Reset application keypad mode
'>' => if (@hasDecl(T, "setMode")) { '>' => if (@hasDecl(T, "setMode") and action.intermediates.len == 0) {
try self.handler.setMode(.keypad_keys, false); try self.handler.setMode(.keypad_keys, false);
} else log.warn("unimplemented setMode: {}", .{action}), } else log.warn("unimplemented setMode: {}", .{action}),
@ -1753,6 +1906,10 @@ test "stream: cursor right (CUF)" {
s.handler.amount = 0; s.handler.amount = 0;
try s.nextSlice("\x1B[5;4C"); try s.nextSlice("\x1B[5;4C");
try testing.expectEqual(@as(u16, 0), s.handler.amount); 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)" { test "stream: dec set mode (SM) and reset mode (RM)" {
@ -1770,6 +1927,10 @@ test "stream: dec set mode (SM) and reset mode (RM)" {
try s.nextSlice("\x1B[?6l"); try s.nextSlice("\x1B[?6l");
try testing.expectEqual(@as(modes.Mode, @enumFromInt(1)), s.handler.mode); try testing.expectEqual(@as(modes.Mode, @enumFromInt(1)), s.handler.mode);
s.handler.mode = @as(modes.Mode, @enumFromInt(1));
try s.nextSlice("\x1B[6 h");
try testing.expectEqual(@as(modes.Mode, @enumFromInt(1)), s.handler.mode);
} }
test "stream: ansi set mode (SM) and reset mode (RM)" { test "stream: ansi set mode (SM) and reset mode (RM)" {
@ -1788,6 +1949,10 @@ test "stream: ansi set mode (SM) and reset mode (RM)" {
try s.nextSlice("\x1B[4l"); try s.nextSlice("\x1B[4l");
try testing.expect(s.handler.mode == null); 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" { test "stream: ansi set mode (SM) and reset mode (RM) with unknown value" {
@ -1937,6 +2102,12 @@ test "stream: DECED, DECSED" {
try testing.expectEqual(csi.EraseDisplay.scrollback, s.handler.mode.?); try testing.expectEqual(csi.EraseDisplay.scrollback, s.handler.mode.?);
try testing.expect(!s.handler.protected.?); try testing.expect(!s.handler.protected.?);
} }
{
// Invalid and ignored by the handler
for ("\x1B[>0J") |c| try s.next(c);
try testing.expectEqual(csi.EraseDisplay.scrollback, s.handler.mode.?);
try testing.expect(!s.handler.protected.?);
}
} }
test "stream: DECEL, DECSEL" { test "stream: DECEL, DECSEL" {
@ -1997,6 +2168,12 @@ test "stream: DECEL, DECSEL" {
try testing.expectEqual(csi.EraseLine.complete, s.handler.mode.?); try testing.expectEqual(csi.EraseLine.complete, s.handler.mode.?);
try testing.expect(!s.handler.protected.?); try testing.expect(!s.handler.protected.?);
} }
{
// Invalid and ignored by the handler
for ("\x1B[<1K") |c| try s.next(c);
try testing.expectEqual(csi.EraseLine.complete, s.handler.mode.?);
try testing.expect(!s.handler.protected.?);
}
} }
test "stream: DECSCUSR" { test "stream: DECSCUSR" {
@ -2014,6 +2191,10 @@ test "stream: DECSCUSR" {
try s.nextSlice("\x1B[1 q"); try s.nextSlice("\x1B[1 q");
try testing.expect(s.handler.style.? == .blinking_block); try testing.expect(s.handler.style.? == .blinking_block);
// Invalid and ignored by the handler
try s.nextSlice("\x1B[?0 q");
try testing.expect(s.handler.style.? == .blinking_block);
} }
test "stream: DECSCUSR without space" { test "stream: DECSCUSR without space" {
@ -2054,6 +2235,10 @@ test "stream: XTSHIFTESCAPE" {
try s.nextSlice("\x1B[>1s"); try s.nextSlice("\x1B[>1s");
try testing.expect(s.handler.escape.? == true); try testing.expect(s.handler.escape.? == true);
// Invalid and ignored by the handler
try s.nextSlice("\x1B[1 s");
try testing.expect(s.handler.escape.? == true);
} }
test "stream: change window title with invalid utf-8" { test "stream: change window title with invalid utf-8" {
@ -2374,6 +2559,14 @@ test "stream CSI W tab set" {
s.handler.called = false; s.handler.called = false;
try s.nextSlice("\x1b[0W"); try s.nextSlice("\x1b[0W");
try testing.expect(s.handler.called); 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" { test "stream CSI ? W reset tab stops" {
@ -2392,4 +2585,8 @@ test "stream CSI ? W reset tab stops" {
try s.nextSlice("\x1b[?5W"); try s.nextSlice("\x1b[?5W");
try testing.expect(s.handler.reset); try testing.expect(s.handler.reset);
// Invalid and ignored by the handler
try s.nextSlice("\x1b[?1;2;3W");
try testing.expect(s.handler.reset);
} }