mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 00:36:07 +03:00
terminfo: for XTGETTCAP, non-parameterized string values return encoded
Fixes #1699 See #1699 for a lot more research. I haven't found a definitive source of this behavior but this appears to match most of the mainstream terminals and xterm for the realistic terminfo entries we have and I'm going to consider that good enough. For non-parameterized string values in a terminfo, the XTGETTCAP result should be the tcap encoded form. This means replacing characters such as `\E` with their byte form of 0x1B, and control characters such as `^H` with their actual character 0x08, and so on. "Non-parameterized" is a bit weird, the comment in this commit explains my best guess (thanks to some community help) of why this may be. This commit does NOT handle all encodings, but it handles the encodings we use and the encodings I could cross-check with other terminals. The actual tcap encoding is much more complicated and I'm sure for specific types of terminfo values our encoding is not correct. If and when those come up we should fix them.
This commit is contained in:
@ -81,17 +81,39 @@ pub fn xtgettcapMap(comptime self: Source) type {
|
|||||||
kvs[1] = .{ "Co", "256" };
|
kvs[1] = .{ "Co", "256" };
|
||||||
kvs[2] = .{ "RGB", "8" };
|
kvs[2] = .{ "RGB", "8" };
|
||||||
for (self.capabilities, 3..) |cap, i| {
|
for (self.capabilities, 3..) |cap, i| {
|
||||||
kvs[i] = .{ cap.name, switch (cap.value) {
|
kvs[i] = .{
|
||||||
|
cap.name, switch (cap.value) {
|
||||||
.canceled => @compileError("canceled not handled yet"),
|
.canceled => @compileError("canceled not handled yet"),
|
||||||
.boolean => "",
|
.boolean => "",
|
||||||
.string => |v| v,
|
.string => |v| string: {
|
||||||
|
@setEvalBranchQuota(100_000);
|
||||||
|
// If a string contains parameters, then we do not escape
|
||||||
|
// anything within the string. I BELIEVE the history here is
|
||||||
|
// xterm initially only supported specific capabilities and none
|
||||||
|
// had parameters so it returned the tcap encoded form. Later,
|
||||||
|
// Kitty added support for more capabilities some of which
|
||||||
|
// have parameters. But Kitty returned them in terminfo source
|
||||||
|
// format. So we need to handle both cases.
|
||||||
|
if (std.mem.indexOfScalar(u8, v, '%') != null) break :string v;
|
||||||
|
// No-parameters. Encode and return.
|
||||||
|
// First replace \E with the escape char (0x1B)
|
||||||
|
var result = comptimeReplace(v, "\\E", "\x1b");
|
||||||
|
// Replace '^' with the control char version of that char.
|
||||||
|
while (std.mem.indexOfScalar(u8, result, '^')) |idx| {
|
||||||
|
if (idx > 0) @compileError("handle control-char in middle of string");
|
||||||
|
const c = result[idx + 1];
|
||||||
|
result = comptimeReplace(result, result[idx .. idx + 2], &.{c - 64});
|
||||||
|
}
|
||||||
|
break :string result;
|
||||||
|
},
|
||||||
.numeric => |v| numeric: {
|
.numeric => |v| numeric: {
|
||||||
var buf: [10]u8 = undefined;
|
var buf: [10]u8 = undefined;
|
||||||
const num_len = std.fmt.formatIntBuf(&buf, v, 10, .upper, .{});
|
const num_len = std.fmt.formatIntBuf(&buf, v, 10, .upper, .{});
|
||||||
const final = buf;
|
const final = buf;
|
||||||
break :numeric final[0..num_len];
|
break :numeric final[0..num_len];
|
||||||
},
|
},
|
||||||
} };
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now go through and convert them all to hex-encoded strings.
|
// Now go through and convert them all to hex-encoded strings.
|
||||||
@ -123,6 +145,22 @@ fn hexencode(comptime input: []const u8) []const u8 {
|
|||||||
return comptime &(std.fmt.bytesToHex(input, .upper));
|
return comptime &(std.fmt.bytesToHex(input, .upper));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// std.mem.replace but comptime-only so we can return the string
|
||||||
|
/// allocated in comptime memory.
|
||||||
|
fn comptimeReplace(
|
||||||
|
input: []const u8,
|
||||||
|
needle: []const u8,
|
||||||
|
replacement: []const u8,
|
||||||
|
) []const u8 {
|
||||||
|
comptime {
|
||||||
|
const len = std.mem.replacementSize(u8, input, needle, replacement);
|
||||||
|
var buf: [len]u8 = undefined;
|
||||||
|
_ = std.mem.replace(u8, input, needle, replacement, &buf);
|
||||||
|
const final = buf;
|
||||||
|
return &final;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
test "xtgettcap map" {
|
test "xtgettcap map" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
@ -136,6 +174,8 @@ test "xtgettcap map" {
|
|||||||
.capabilities = &.{
|
.capabilities = &.{
|
||||||
.{ .name = "am", .value = .{ .boolean = {} } },
|
.{ .name = "am", .value = .{ .boolean = {} } },
|
||||||
.{ .name = "colors", .value = .{ .numeric = 256 } },
|
.{ .name = "colors", .value = .{ .numeric = 256 } },
|
||||||
|
.{ .name = "kbs", .value = .{ .string = "^H" } },
|
||||||
|
.{ .name = "kf1", .value = .{ .string = "\\EOP" } },
|
||||||
.{ .name = "Smulx", .value = .{ .string = "\\E[4:%p1%dm" } },
|
.{ .name = "Smulx", .value = .{ .string = "\\E[4:%p1%dm" } },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -145,6 +185,14 @@ test "xtgettcap map" {
|
|||||||
"\x1bP1+r616D\x1b\\",
|
"\x1bP1+r616D\x1b\\",
|
||||||
map.get(hexencode("am")).?,
|
map.get(hexencode("am")).?,
|
||||||
);
|
);
|
||||||
|
try testing.expectEqualStrings(
|
||||||
|
"\x1bP1+r6B6273=08\x1b\\",
|
||||||
|
map.get(hexencode("kbs")).?,
|
||||||
|
);
|
||||||
|
try testing.expectEqualStrings(
|
||||||
|
"\x1bP1+r6B6631=1B4F50\x1b\\",
|
||||||
|
map.get(hexencode("kf1")).?,
|
||||||
|
);
|
||||||
try testing.expectEqualStrings(
|
try testing.expectEqualStrings(
|
||||||
"\x1bP1+r536D756C78=5C455B343A25703125646D\x1b\\",
|
"\x1bP1+r536D756C78=5C455B343A25703125646D\x1b\\",
|
||||||
map.get(hexencode("Smulx")).?,
|
map.get(hexencode("Smulx")).?,
|
||||||
|
@ -304,7 +304,7 @@ pub const ghostty: Source = .{
|
|||||||
.{ .name = "kUP5", .value = .{ .string = "\\E[1;5A" } },
|
.{ .name = "kUP5", .value = .{ .string = "\\E[1;5A" } },
|
||||||
.{ .name = "kUP6", .value = .{ .string = "\\E[1;6A" } },
|
.{ .name = "kUP6", .value = .{ .string = "\\E[1;6A" } },
|
||||||
.{ .name = "kUP7", .value = .{ .string = "\\E[1;7A" } },
|
.{ .name = "kUP7", .value = .{ .string = "\\E[1;7A" } },
|
||||||
.{ .name = "kbs", .value = .{ .string = "^?" } },
|
.{ .name = "kbs", .value = .{ .string = "^H" } },
|
||||||
.{ .name = "kcbt", .value = .{ .string = "\\E[Z" } },
|
.{ .name = "kcbt", .value = .{ .string = "\\E[Z" } },
|
||||||
.{ .name = "kcub1", .value = .{ .string = "\\EOD" } },
|
.{ .name = "kcub1", .value = .{ .string = "\\EOD" } },
|
||||||
.{ .name = "kcud1", .value = .{ .string = "\\EOB" } },
|
.{ .name = "kcud1", .value = .{ .string = "\\EOB" } },
|
||||||
|
Reference in New Issue
Block a user