mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 08:46:08 +03:00
terminal: initial kitty graphics command parsing
This commit is contained in:
@ -4,6 +4,106 @@
|
|||||||
//! https://sw.kovidgoyal.net/kitty/graphics-protocol
|
//! https://sw.kovidgoyal.net/kitty/graphics-protocol
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
/// Command parser parses the Kitty graphics protocol escape sequence.
|
||||||
|
pub const CommandParser = struct {
|
||||||
|
alloc: Allocator,
|
||||||
|
kv: std.StringHashMapUnmanaged([2]usize) = .{},
|
||||||
|
data: std.ArrayListUnmanaged(u8) = .{},
|
||||||
|
data_i: usize = 0,
|
||||||
|
value_ptr: *[2]usize = undefined,
|
||||||
|
state: State = .control_key,
|
||||||
|
|
||||||
|
const State = enum {
|
||||||
|
control_key,
|
||||||
|
control_value,
|
||||||
|
data,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn deinit(self: *CommandParser) void {
|
||||||
|
self.kv.deinit(self.alloc);
|
||||||
|
self.data.deinit(self.alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Feed a single byte to the parser.
|
||||||
|
///
|
||||||
|
/// The first byte to start parsing should be the byte immediately following
|
||||||
|
/// the "G" in the APC sequence, i.e. "\x1b_G123" the first byte should
|
||||||
|
/// be "1".
|
||||||
|
pub fn feed(self: *CommandParser, c: u8) !void {
|
||||||
|
switch (self.state) {
|
||||||
|
.control_key => switch (c) {
|
||||||
|
// '=' means the key is complete and we're moving to the value.
|
||||||
|
'=' => {
|
||||||
|
const key = self.data.items[self.data_i..];
|
||||||
|
const gop = try self.kv.getOrPut(self.alloc, key);
|
||||||
|
self.state = .control_value;
|
||||||
|
self.value_ptr = gop.value_ptr;
|
||||||
|
self.data_i = self.data.items.len;
|
||||||
|
},
|
||||||
|
|
||||||
|
else => try self.data.append(self.alloc, c),
|
||||||
|
},
|
||||||
|
|
||||||
|
.control_value => switch (c) {
|
||||||
|
// ',' means we're moving to another kv
|
||||||
|
',' => {
|
||||||
|
self.finishValue();
|
||||||
|
self.state = .control_key;
|
||||||
|
},
|
||||||
|
|
||||||
|
// ';' means we're moving to the data
|
||||||
|
';' => {
|
||||||
|
self.finishValue();
|
||||||
|
self.state = .data;
|
||||||
|
},
|
||||||
|
|
||||||
|
else => try self.data.append(self.alloc, c),
|
||||||
|
},
|
||||||
|
|
||||||
|
.data => try self.data.append(self.alloc, c),
|
||||||
|
}
|
||||||
|
|
||||||
|
// We always add to our data list because this is our stable
|
||||||
|
// array of bytes that we'll reference everywhere else.
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Complete the parsing. This must be called after all the
|
||||||
|
/// bytes have been fed to the parser.
|
||||||
|
pub fn complete(self: *CommandParser) !void {
|
||||||
|
switch (self.state) {
|
||||||
|
// We can't ever end in the control key state and be valid.
|
||||||
|
// This means the command looked something like "a=1,b"
|
||||||
|
.control_key => return error.InvalidFormat,
|
||||||
|
|
||||||
|
// Some commands (i.e. placements) end without extra data so
|
||||||
|
// we end in the value state. i.e. "a=1,b=2"
|
||||||
|
.control_value => self.finishValue(),
|
||||||
|
|
||||||
|
// Most commands end in data, i.e. "a=1,b=2;1234"
|
||||||
|
.data => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finishValue(self: *CommandParser) void {
|
||||||
|
self.value_ptr.* = .{ self.data_i, self.data.items.len };
|
||||||
|
self.data_i = self.data.items.len;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
test "parse" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
var p: CommandParser = .{ .alloc = alloc };
|
||||||
|
defer p.deinit();
|
||||||
|
|
||||||
|
const input = "f=24,s=10,v=20";
|
||||||
|
for (input) |c| try p.feed(c);
|
||||||
|
try p.complete();
|
||||||
|
|
||||||
|
try testing.expectEqual(@as(u32, 3), p.kv.count());
|
||||||
|
}
|
||||||
|
|
||||||
pub const Command = struct {
|
pub const Command = struct {
|
||||||
control: Control,
|
control: Control,
|
||||||
|
Reference in New Issue
Block a user