termio/exec: process APC callbacks

This commit is contained in:
Mitchell Hashimoto
2023-08-18 13:54:17 -07:00
parent 29e3e79b94
commit 6e061fb344
2 changed files with 64 additions and 8 deletions

View File

@ -66,9 +66,15 @@ pub fn Stream(comptime Handler: type) type {
.dcs_hook => |dcs| log.warn("unhandled DCS hook: {}", .{dcs}),
.dcs_put => |code| log.warn("unhandled DCS put: {x}", .{code}),
.dcs_unhook => log.warn("unhandled DCS unhook", .{}),
.apc_start => log.warn("unhandled APC start", .{}),
.apc_put => |code| log.warn("unhandled APC put: {x}", .{code}),
.apc_end => log.warn("unhandled APC end", .{}),
.apc_start => if (@hasDecl(T, "apcStart")) {
try self.handler.apcStart();
} else log.warn("unimplemented APC start", .{}),
.apc_put => |code| if (@hasDecl(T, "apcPut")) {
try self.handler.apcPut(code);
} else log.warn("unimplemented APC put: {x}", .{code}),
.apc_end => if (@hasDecl(T, "apcEnd")) {
try self.handler.apcEnd();
} else log.warn("unimplemented APC end", .{}),
}
}
}

View File

@ -47,10 +47,6 @@ subprocess: Subprocess,
/// just stores internal state about a grid.
terminal: terminal.Terminal,
/// The stream parser. This parses the stream of escape codes and so on
/// from the child process and calls callbacks in the stream handler.
terminal_stream: terminal.Stream(StreamHandler),
/// The shared render state
renderer_state: *renderer.State,
@ -114,7 +110,6 @@ pub fn init(alloc: Allocator, opts: termio.Options) !Exec {
return Exec{
.alloc = alloc,
.terminal = term,
.terminal_stream = undefined,
.subprocess = subprocess,
.renderer_state = opts.renderer_state,
.renderer_wakeup = opts.renderer_wakeup,
@ -445,6 +440,9 @@ const EventData = struct {
// Stop our process watcher
self.process.deinit();
// Clear any StreamHandler state
self.terminal_stream.handler.deinit();
}
/// This queues a render operation with the renderer thread. The render
@ -1050,11 +1048,25 @@ const StreamHandler = struct {
grid_size: *renderer.GridSize,
terminal: *terminal.Terminal,
/// The APC command data. This is always heap-allocated and freed on
/// apcEnd because APC commands are so very rare.
apc_data: std.ArrayListUnmanaged(u8) = .{},
apc_state: enum {
inactive,
ignore,
verify,
collect,
} = .inactive,
/// This is set to true when a message was written to the writer
/// mailbox. This can be used by callers to determine if they need
/// to wake up the writer.
writer_messaged: bool = false,
pub fn deinit(self: *StreamHandler) void {
self.apc_data.deinit(self.alloc);
}
inline fn queueRender(self: *StreamHandler) !void {
try self.ev.queueRender();
}
@ -1064,6 +1076,44 @@ const StreamHandler = struct {
self.writer_messaged = true;
}
pub fn apcStart(self: *StreamHandler) !void {
assert(self.apc_data.items.len == 0);
self.apc_state = .verify;
}
pub fn apcPut(self: *StreamHandler, byte: u8) !void {
switch (self.apc_state) {
.inactive => unreachable,
// We're ignoring this APC command, likely because we don't
// recognize it so there is no need to store the data in memory.
.ignore => return,
// Verify it is a command we expect
.verify => {
switch (byte) {
'G' => {},
else => {
log.warn("unrecognized APC command, first byte: {x}", .{byte});
self.apc_state = .ignore;
return;
},
}
self.apc_state = .collect;
},
.collect => {},
}
try self.apc_data.append(self.alloc, byte);
}
pub fn apcEnd(self: *StreamHandler) !void {
self.apc_state = .inactive;
self.apc_data.clearAndFree(self.alloc);
}
pub fn print(self: *StreamHandler, ch: u21) !void {
try self.terminal.print(ch);
}