mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
implement basic CSI dispatch action
This commit is contained in:
@ -8,6 +8,8 @@ const std = @import("std");
|
|||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const table = @import("parse_table.zig").table;
|
const table = @import("parse_table.zig").table;
|
||||||
|
|
||||||
|
const log = std.log.scoped(.parser);
|
||||||
|
|
||||||
/// States for the state machine
|
/// States for the state machine
|
||||||
pub const State = enum {
|
pub const State = enum {
|
||||||
anywhere,
|
anywhere,
|
||||||
@ -56,11 +58,34 @@ pub const Action = union(enum) {
|
|||||||
|
|
||||||
/// Execute the C0 or C1 function.
|
/// Execute the C0 or C1 function.
|
||||||
execute: u8,
|
execute: u8,
|
||||||
|
|
||||||
|
/// Execute the CSI command. Note that pointers within this
|
||||||
|
/// structure are only valid until the next call to "next".
|
||||||
|
csi_dispatch: CSI,
|
||||||
|
|
||||||
|
pub const CSI = struct {
|
||||||
|
params: []u16,
|
||||||
|
final: u8,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Maximum number of intermediate characters during parsing.
|
||||||
|
const MAX_INTERMEDIATE = 2;
|
||||||
|
const MAX_PARAMS = 16;
|
||||||
|
|
||||||
/// Current state of the state machine
|
/// Current state of the state machine
|
||||||
state: State = .ground,
|
state: State = .ground,
|
||||||
|
|
||||||
|
/// Intermediate tracking.
|
||||||
|
intermediate: [MAX_INTERMEDIATE]u8 = undefined,
|
||||||
|
intermediate_idx: u8 = 0,
|
||||||
|
|
||||||
|
/// Param tracking, building
|
||||||
|
params: [MAX_PARAMS]u16 = undefined,
|
||||||
|
params_idx: u8 = 0,
|
||||||
|
param_acc: u16 = 0,
|
||||||
|
param_acc_idx: u8 = 0,
|
||||||
|
|
||||||
pub fn init() Parser {
|
pub fn init() Parser {
|
||||||
return .{};
|
return .{};
|
||||||
}
|
}
|
||||||
@ -78,6 +103,8 @@ pub fn next(self: *Parser, c: u8) [3]?Action {
|
|||||||
break :effect table[c][@enumToInt(self.state)];
|
break :effect table[c][@enumToInt(self.state)];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
log.info("next: {x}", .{c});
|
||||||
|
|
||||||
const next_state = effect.state;
|
const next_state = effect.state;
|
||||||
const action = effect.action;
|
const action = effect.action;
|
||||||
|
|
||||||
@ -98,8 +125,11 @@ pub fn next(self: *Parser, c: u8) [3]?Action {
|
|||||||
|
|
||||||
self.doAction(action, c),
|
self.doAction(action, c),
|
||||||
|
|
||||||
switch (self.state) {
|
switch (next_state) {
|
||||||
.escape, .dcs_entry, .csi_entry => @panic("TODO"), // TODO: clear
|
.escape, .dcs_entry, .csi_entry => clear: {
|
||||||
|
self.clear();
|
||||||
|
break :clear null;
|
||||||
|
},
|
||||||
.osc_string => @panic("TODO"), // TODO: osc_start
|
.osc_string => @panic("TODO"), // TODO: osc_start
|
||||||
.dcs_passthrough => @panic("TODO"), // TODO: hook
|
.dcs_passthrough => @panic("TODO"), // TODO: hook
|
||||||
else => null,
|
else => null,
|
||||||
@ -111,10 +141,67 @@ fn doAction(self: *Parser, action: TransitionAction, c: u8) ?Action {
|
|||||||
_ = self;
|
_ = self;
|
||||||
return switch (action) {
|
return switch (action) {
|
||||||
.none, .ignore => null,
|
.none, .ignore => null,
|
||||||
.print => return Action{ .print = c },
|
.print => Action{ .print = c },
|
||||||
.execute => return Action{ .execute = c },
|
.execute => Action{ .execute = c },
|
||||||
else => @panic("TODO"),
|
.collect => collect: {
|
||||||
|
self.intermediate[self.intermediate_idx] = c;
|
||||||
|
// TODO: incr, bounds check
|
||||||
|
|
||||||
|
// The client is expected to perform no action.
|
||||||
|
break :collect null;
|
||||||
|
},
|
||||||
|
.param => param: {
|
||||||
|
// TODO: bounds check
|
||||||
|
|
||||||
|
// Semicolon separates parameters. If we encounter a semicolon
|
||||||
|
// we need to store and move on to the next parameter.
|
||||||
|
if (c == ';') {
|
||||||
|
// Set param final value
|
||||||
|
self.params[self.params_idx] = self.param_acc;
|
||||||
|
self.params_idx += 1;
|
||||||
|
|
||||||
|
// Reset current param value to 0
|
||||||
|
self.param_acc = 0;
|
||||||
|
self.param_acc_idx = 0;
|
||||||
|
break :param null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A numeric value. Add it to our accumulator.
|
||||||
|
if (self.param_acc_idx > 0) {
|
||||||
|
self.param_acc *|= 10;
|
||||||
|
}
|
||||||
|
self.param_acc +|= c - '0';
|
||||||
|
self.param_acc_idx += 1;
|
||||||
|
|
||||||
|
// The client is expected to perform no action.
|
||||||
|
break :param null;
|
||||||
|
},
|
||||||
|
.csi_dispatch => csi_dispatch: {
|
||||||
|
// Finalize parameters if we have one
|
||||||
|
if (self.param_acc_idx > 0) {
|
||||||
|
self.params[self.params_idx] = self.param_acc;
|
||||||
|
self.params_idx += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
break :csi_dispatch Action{
|
||||||
|
.csi_dispatch = .{
|
||||||
|
.params = self.params[0..self.params_idx],
|
||||||
|
.final = c,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
std.log.err("unimplemented action: {}", .{action});
|
||||||
|
@panic("TODO");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear(self: *Parser) void {
|
||||||
|
self.intermediate_idx = 0;
|
||||||
|
self.params_idx = 0;
|
||||||
|
self.param_acc = 0;
|
||||||
|
self.param_acc_idx = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
@ -140,3 +227,44 @@ test {
|
|||||||
try testing.expect(a[2] == null);
|
try testing.expect(a[2] == null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "csi: ESC [ H" {
|
||||||
|
var p = init();
|
||||||
|
_ = p.next(0x1B);
|
||||||
|
_ = p.next(0x5B);
|
||||||
|
|
||||||
|
{
|
||||||
|
const a = p.next(0x48);
|
||||||
|
try testing.expect(p.state == .ground);
|
||||||
|
try testing.expect(a[0] == null);
|
||||||
|
try testing.expect(a[1].? == .csi_dispatch);
|
||||||
|
try testing.expect(a[2] == null);
|
||||||
|
|
||||||
|
const d = a[1].?.csi_dispatch;
|
||||||
|
try testing.expect(d.final == 0x48);
|
||||||
|
try testing.expect(d.params.len == 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "csi: ESC [ 1 ; 4 H" {
|
||||||
|
var p = init();
|
||||||
|
_ = p.next(0x1B);
|
||||||
|
_ = p.next(0x5B);
|
||||||
|
_ = p.next(0x31); // 1
|
||||||
|
_ = p.next(0x3B); // ;
|
||||||
|
_ = p.next(0x34); // 4
|
||||||
|
//
|
||||||
|
{
|
||||||
|
const a = p.next(0x48); // H
|
||||||
|
try testing.expect(p.state == .ground);
|
||||||
|
try testing.expect(a[0] == null);
|
||||||
|
try testing.expect(a[1].? == .csi_dispatch);
|
||||||
|
try testing.expect(a[2] == null);
|
||||||
|
|
||||||
|
const d = a[1].?.csi_dispatch;
|
||||||
|
try testing.expect(d.final == 'H');
|
||||||
|
try testing.expect(d.params.len == 2);
|
||||||
|
try testing.expectEqual(@as(u16, 1), d.params[0]);
|
||||||
|
try testing.expectEqual(@as(u16, 4), d.params[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user