mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-25 13:16:11 +03:00
input: KittySequence for encoding sequences
This commit is contained in:
@ -476,8 +476,187 @@ const KittyMods = packed struct(u8) {
|
|||||||
/// CSI unicode-key-code:alternate-key-codes ; modifiers:event-type ; text-as-codepoints u
|
/// CSI unicode-key-code:alternate-key-codes ; modifiers:event-type ; text-as-codepoints u
|
||||||
const KittySequence = struct {
|
const KittySequence = struct {
|
||||||
key: u16,
|
key: u16,
|
||||||
|
final: u8,
|
||||||
|
event: Event = .none,
|
||||||
|
alternates: []const u16 = &.{},
|
||||||
|
mods: KittyMods = .{},
|
||||||
|
text: []const u8 = "",
|
||||||
|
|
||||||
|
/// Values for the event code (see "event-type" in above comment).
|
||||||
|
/// Note that Kitty omits the ":1" for the press event but other
|
||||||
|
/// terminals include it. We'll include it.
|
||||||
|
const Event = enum(u2) {
|
||||||
|
none = 0,
|
||||||
|
press = 1,
|
||||||
|
repeat = 2,
|
||||||
|
release = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn encode(self: KittySequence, buf: []u8) ![]const u8 {
|
||||||
|
if (self.final == 'u' or self.final == '~') return try self.encodeFull(buf);
|
||||||
|
return try self.encodeSpecial(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encodeFull(self: KittySequence, buf: []u8) ![]const u8 {
|
||||||
|
// Boilerplate to basically create a string builder that writes
|
||||||
|
// over our buffer (but no more).
|
||||||
|
var fba = std.heap.FixedBufferAllocator.init(buf);
|
||||||
|
const alloc = fba.allocator();
|
||||||
|
var builder = try std.ArrayListUnmanaged(u8).initCapacity(alloc, buf.len);
|
||||||
|
const writer = builder.writer(alloc);
|
||||||
|
|
||||||
|
// Key section
|
||||||
|
try writer.print("\x1B[{d}", .{self.key});
|
||||||
|
for (self.alternates) |alt| try writer.print(":{d}", .{alt});
|
||||||
|
|
||||||
|
// Mods and events section
|
||||||
|
const mods = self.mods.seqInt();
|
||||||
|
var emit_prior = false;
|
||||||
|
if (self.event != .none) {
|
||||||
|
try writer.print(";{d}:{d}", .{ mods, @intFromEnum(self.event) });
|
||||||
|
emit_prior = true;
|
||||||
|
} else if (mods > 1) {
|
||||||
|
try writer.print(";{d}", .{mods});
|
||||||
|
emit_prior = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text section
|
||||||
|
if (self.text.len > 0) {
|
||||||
|
// We need to add our ";". We need to add two if we didn't emit
|
||||||
|
// the modifier section.
|
||||||
|
if (!emit_prior) try writer.writeByte(';');
|
||||||
|
try writer.writeByte(';');
|
||||||
|
|
||||||
|
// First one has no prefix
|
||||||
|
const view = try std.unicode.Utf8View.init(self.text);
|
||||||
|
var it = view.iterator();
|
||||||
|
if (it.nextCodepoint()) |cp| {
|
||||||
|
try writer.print("{d}", .{cp});
|
||||||
|
}
|
||||||
|
while (it.nextCodepoint()) |cp| {
|
||||||
|
try writer.print(":{d}", .{cp});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try writer.print("{c}", .{self.final});
|
||||||
|
return builder.items;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encodeSpecial(self: KittySequence, buf: []u8) ![]const u8 {
|
||||||
|
const mods = self.mods.seqInt();
|
||||||
|
if (self.event != .none) {
|
||||||
|
return try std.fmt.bufPrint(buf, "\x1B[1;{d}:{d}{c}", .{
|
||||||
|
mods,
|
||||||
|
@intFromEnum(self.event),
|
||||||
|
self.final,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mods > 1) {
|
||||||
|
return try std.fmt.bufPrint(buf, "\x1B[1;{d}{c}", .{
|
||||||
|
mods,
|
||||||
|
self.final,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return try std.fmt.bufPrint(buf, "\x1B[{c}", .{self.final});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
test "KittySequence: backspace" {
|
||||||
|
var buf: [128]u8 = undefined;
|
||||||
|
|
||||||
|
// Plain
|
||||||
|
{
|
||||||
|
var seq: KittySequence = .{ .key = 127, .final = 'u' };
|
||||||
|
const actual = try seq.encode(&buf);
|
||||||
|
try testing.expectEqualStrings("\x1B[127u", actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release event
|
||||||
|
{
|
||||||
|
var seq: KittySequence = .{ .key = 127, .final = 'u', .event = .release };
|
||||||
|
const actual = try seq.encode(&buf);
|
||||||
|
try testing.expectEqualStrings("\x1B[127;1:3u", actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shift
|
||||||
|
{
|
||||||
|
var seq: KittySequence = .{
|
||||||
|
.key = 127,
|
||||||
|
.final = 'u',
|
||||||
|
.mods = .{ .shift = true },
|
||||||
|
};
|
||||||
|
const actual = try seq.encode(&buf);
|
||||||
|
try testing.expectEqualStrings("\x1B[127;2u", actual);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "KittySequence: text" {
|
||||||
|
var buf: [128]u8 = undefined;
|
||||||
|
|
||||||
|
// Plain
|
||||||
|
{
|
||||||
|
var seq: KittySequence = .{
|
||||||
|
.key = 127,
|
||||||
|
.final = 'u',
|
||||||
|
.text = "A",
|
||||||
|
};
|
||||||
|
const actual = try seq.encode(&buf);
|
||||||
|
try testing.expectEqualStrings("\x1B[127;;65u", actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release
|
||||||
|
{
|
||||||
|
var seq: KittySequence = .{
|
||||||
|
.key = 127,
|
||||||
|
.final = 'u',
|
||||||
|
.event = .release,
|
||||||
|
.text = "A",
|
||||||
|
};
|
||||||
|
const actual = try seq.encode(&buf);
|
||||||
|
try testing.expectEqualStrings("\x1B[127;1:3;65u", actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shift
|
||||||
|
{
|
||||||
|
var seq: KittySequence = .{
|
||||||
|
.key = 127,
|
||||||
|
.final = 'u',
|
||||||
|
.mods = .{ .shift = true },
|
||||||
|
.text = "A",
|
||||||
|
};
|
||||||
|
const actual = try seq.encode(&buf);
|
||||||
|
try testing.expectEqualStrings("\x1B[127;2;65u", actual);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "KittySequence: special no mods" {
|
||||||
|
var buf: [128]u8 = undefined;
|
||||||
|
var seq: KittySequence = .{ .key = 1, .final = 'A' };
|
||||||
|
const actual = try seq.encode(&buf);
|
||||||
|
try testing.expectEqualStrings("\x1B[A", actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "KittySequence: special mods only" {
|
||||||
|
var buf: [128]u8 = undefined;
|
||||||
|
var seq: KittySequence = .{ .key = 1, .final = 'A', .mods = .{ .shift = true } };
|
||||||
|
const actual = try seq.encode(&buf);
|
||||||
|
try testing.expectEqualStrings("\x1B[1;2A", actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "KittySequence: special mods and event" {
|
||||||
|
var buf: [128]u8 = undefined;
|
||||||
|
var seq: KittySequence = .{
|
||||||
|
.key = 1,
|
||||||
|
.final = 'A',
|
||||||
|
.event = .release,
|
||||||
|
.mods = .{ .shift = true },
|
||||||
|
};
|
||||||
|
const actual = try seq.encode(&buf);
|
||||||
|
try testing.expectEqualStrings("\x1B[1;2:3A", actual);
|
||||||
|
}
|
||||||
|
|
||||||
test "kitty: plain text" {
|
test "kitty: plain text" {
|
||||||
var buf: [128]u8 = undefined;
|
var buf: [128]u8 = undefined;
|
||||||
var enc: KeyEncoder = .{
|
var enc: KeyEncoder = .{
|
||||||
|
Reference in New Issue
Block a user