terminal/tmux: handle output events

pull/9860/head
Mitchell Hashimoto 2025-12-09 13:11:58 -08:00
parent 50ac848672
commit 938e419e04
No known key found for this signature in database
GPG Key ID: 523D5DC389D273BC
1 changed files with 67 additions and 0 deletions

View File

@ -13,6 +13,12 @@ const output = @import("output.zig");
const log = std.log.scoped(.terminal_tmux_viewer);
// TODO: A list of TODOs as I think about them.
// - We need to make startup more robust so session and block can happen
// out of order.
// - We need to ignore `output` for panes that aren't yet initialized
// (until capture-panes are complete).
// NOTE: There is some fragility here that can possibly break if tmux
// changes their implementation. In particular, the order of notifications
// and assurances about what is sent when are based on reading the tmux
@ -312,6 +318,20 @@ pub const Viewer = struct {
break :err self.defunct();
},
.output => |out| output: {
self.receivedOutput(
out.pane_id,
out.data,
) catch |err| {
log.warn(
"failed to process output for pane id={}: {}",
.{ out.pane_id, err },
);
};
break :output &.{};
},
// TODO: Use exhaustive matching here, determine if we need
// to handle the other cases.
else => &.{},
@ -602,6 +622,26 @@ pub const Viewer = struct {
};
}
fn receivedOutput(
self: *Viewer,
id: usize,
data: []const u8,
) !void {
const entry = self.panes.getEntry(id) orelse {
log.info("received output for untracked pane id={}", .{id});
return;
};
const pane: *Pane = entry.value_ptr;
const t: *Terminal = &pane.terminal;
var stream = t.vtStream();
defer stream.deinit();
stream.nextSlice(data) catch |err| {
log.info("failed to process output for pane id={}: {}", .{ id, err });
return err;
};
}
fn initLayout(
gpa_alloc: Allocator,
panes_old: *const PanesMap,
@ -1074,6 +1114,33 @@ test "initial flow" {
}
}).check,
},
.{
.input = .{ .tmux = .{ .block_end = "" } },
},
.{
.input = .{ .tmux = .{ .output = .{ .pane_id = 0, .data = "new output" } } },
.check = (struct {
fn check(v: *Viewer, actions: []const Viewer.Action) anyerror!void {
try testing.expectEqual(0, actions.len);
const pane: *Viewer.Pane = v.panes.getEntry(0).?.value_ptr;
const screen: *Screen = pane.terminal.screens.active;
const str = try screen.dumpStringAlloc(
testing.allocator,
.{ .active = .{} },
);
defer testing.allocator.free(str);
try testing.expect(std.mem.containsAtLeast(u8, str, 1, "new output"));
}
}).check,
},
.{
.input = .{ .tmux = .{ .output = .{ .pane_id = 999, .data = "ignored" } } },
.check = (struct {
fn check(_: *Viewer, actions: []const Viewer.Action) anyerror!void {
try testing.expectEqual(0, actions.len);
}
}).check,
},
.{
.input = .{ .tmux = .exit },
.contains_tags = &.{.exit},