mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +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
|
// Cursor Tabulation Control
|
||||||
'W' => {
|
'W' => {
|
||||||
switch (input.params.len) {
|
switch (input.params.len) {
|
||||||
0 => if (input.intermediates.len == 1 and input.intermediates[0] == '?') {
|
0 => if (@hasDecl(T, "tabSet"))
|
||||||
if (@hasDecl(T, "tabReset"))
|
try self.handler.tabSet()
|
||||||
try self.handler.tabReset()
|
else
|
||||||
else
|
log.warn("unimplemented tab set callback: {}", .{input}),
|
||||||
log.warn("unimplemented tab reset callback: {}", .{input});
|
|
||||||
},
|
|
||||||
|
|
||||||
1 => switch (input.params[0]) {
|
1 => if (input.intermediates.len == 1 and input.intermediates[0] == '?') {
|
||||||
0 => if (@hasDecl(T, "tabSet"))
|
if (input.params[0] == 5) {
|
||||||
try self.handler.tabSet()
|
if (@hasDecl(T, "tabReset"))
|
||||||
else
|
try self.handler.tabReset()
|
||||||
log.warn("unimplemented tab set callback: {}", .{input}),
|
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"))
|
2 => if (@hasDecl(T, "tabClear"))
|
||||||
try self.handler.tabClear(.current)
|
try self.handler.tabClear(.current)
|
||||||
else
|
else
|
||||||
log.warn("unimplemented tab clear callback: {}", .{input}),
|
log.warn("unimplemented tab clear callback: {}", .{input}),
|
||||||
|
|
||||||
5 => if (@hasDecl(T, "tabClear"))
|
5 => if (@hasDecl(T, "tabClear"))
|
||||||
try self.handler.tabClear(.all)
|
try self.handler.tabClear(.all)
|
||||||
else
|
else
|
||||||
log.warn("unimplemented tab clear callback: {}", .{input}),
|
log.warn("unimplemented tab clear callback: {}", .{input}),
|
||||||
|
|
||||||
else => {},
|
else => {},
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
else => {},
|
else => {},
|
||||||
@ -2331,3 +2338,58 @@ test "stream: CSI t pop title with index" {
|
|||||||
.index = 5,
|
.index = 5,
|
||||||
}, s.handler.op.?);
|
}, 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