mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
fix: correct handling of CTC and DECST8C (#3121)
Cursor Tab Control (CTC) `CSI <n> W` has a default value of 0, which is the same as setting a tab stop at the current cursor position. Set Tab at every 8th Column (DECST8C) `CSI ? 5 W` is a DEC private sequence that resets the tab stops to every 8th column. Reference: https://vt100.net/docs/vt510-rm/DECST8C.html Reference: https://wezfurlong.org/ecma48/07-control.html?highlight=ctc#7210-ctc---cursor-tabulation-control Reference: https://gitlab.gnome.org/GNOME/vte/-/blob/master/src/parser-seq.py#L596-L601 Fixes: https://github.com/ghostty-org/ghostty/issues/3120
This commit is contained in:
@ -601,30 +601,37 @@ pub fn Stream(comptime Handler: type) type {
|
||||
// Cursor Tabulation Control
|
||||
'W' => {
|
||||
switch (input.params.len) {
|
||||
0 => if (input.intermediates.len == 1 and input.intermediates[0] == '?') {
|
||||
if (@hasDecl(T, "tabReset"))
|
||||
try self.handler.tabReset()
|
||||
else
|
||||
log.warn("unimplemented tab reset callback: {}", .{input});
|
||||
},
|
||||
0 => if (@hasDecl(T, "tabSet"))
|
||||
try self.handler.tabSet()
|
||||
else
|
||||
log.warn("unimplemented tab set callback: {}", .{input}),
|
||||
|
||||
1 => switch (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}),
|
||||
|
||||
2 => if (@hasDecl(T, "tabClear"))
|
||||
try self.handler.tabClear(.current)
|
||||
else
|
||||
log.warn("unimplemented tab clear callback: {}", .{input}),
|
||||
2 => if (@hasDecl(T, "tabClear"))
|
||||
try self.handler.tabClear(.current)
|
||||
else
|
||||
log.warn("unimplemented tab clear callback: {}", .{input}),
|
||||
|
||||
5 => if (@hasDecl(T, "tabClear"))
|
||||
try self.handler.tabClear(.all)
|
||||
else
|
||||
log.warn("unimplemented tab clear callback: {}", .{input}),
|
||||
5 => if (@hasDecl(T, "tabClear"))
|
||||
try self.handler.tabClear(.all)
|
||||
else
|
||||
log.warn("unimplemented tab clear callback: {}", .{input}),
|
||||
|
||||
else => {},
|
||||
else => {},
|
||||
}
|
||||
},
|
||||
|
||||
else => {},
|
||||
@ -2331,3 +2338,58 @@ test "stream: CSI t pop title with index" {
|
||||
.index = 5,
|
||||
}, s.handler.op.?);
|
||||
}
|
||||
|
||||
test "stream CSI W clear tab stops" {
|
||||
const H = struct {
|
||||
op: ?csi.TabClear = null,
|
||||
|
||||
pub fn tabClear(self: *@This(), op: csi.TabClear) !void {
|
||||
self.op = op;
|
||||
}
|
||||
};
|
||||
|
||||
var s: Stream(H) = .{ .handler = .{} };
|
||||
|
||||
try s.nextSlice("\x1b[2W");
|
||||
try testing.expectEqual(csi.TabClear.current, s.handler.op.?);
|
||||
|
||||
try s.nextSlice("\x1b[5W");
|
||||
try testing.expectEqual(csi.TabClear.all, s.handler.op.?);
|
||||
}
|
||||
|
||||
test "stream CSI W tab set" {
|
||||
const H = struct {
|
||||
called: bool = false,
|
||||
|
||||
pub fn tabSet(self: *@This()) !void {
|
||||
self.called = true;
|
||||
}
|
||||
};
|
||||
|
||||
var s: Stream(H) = .{ .handler = .{} };
|
||||
|
||||
try s.nextSlice("\x1b[W");
|
||||
try testing.expect(s.handler.called);
|
||||
|
||||
s.handler.called = false;
|
||||
try s.nextSlice("\x1b[0W");
|
||||
try testing.expect(s.handler.called);
|
||||
}
|
||||
|
||||
test "stream CSI ? W reset tab stops" {
|
||||
const H = struct {
|
||||
reset: bool = false,
|
||||
|
||||
pub fn tabReset(self: *@This()) !void {
|
||||
self.reset = true;
|
||||
}
|
||||
};
|
||||
|
||||
var s: Stream(H) = .{ .handler = .{} };
|
||||
|
||||
try s.nextSlice("\x1b[?2W");
|
||||
try testing.expect(!s.handler.reset);
|
||||
|
||||
try s.nextSlice("\x1b[?5W");
|
||||
try testing.expect(s.handler.reset);
|
||||
}
|
||||
|
Reference in New Issue
Block a user