terminal/tmux: window add

pull/9860/head
Mitchell Hashimoto 2025-12-09 17:15:23 -08:00
parent 1a2b3c165a
commit 582ea5d84b
No known key found for this signature in database
GPG Key ID: 523D5DC389D273BC
1 changed files with 146 additions and 2 deletions

View File

@ -353,8 +353,11 @@ pub const Viewer = struct {
return self.defunct();
},
// TODO: There's real logic to do for these.
.window_add => &.{},
// A window was added to this session.
.window_add => |info| self.windowAdd(info.id) catch {
log.warn("failed to handle window add, becoming defunct", .{});
return self.defunct();
},
// The active pane changed. We don't care about this because
// we handle our own focus.
@ -447,6 +450,40 @@ pub const Viewer = struct {
return actions.items;
}
/// When a window is added to the session, we need to refresh our window
/// list to get the new window's information.
fn windowAdd(self: *Viewer, window_id: usize) ![]const Action {
_ = window_id; // We refresh all windows via list-windows
// If our command queue started out empty and becomes non-empty,
// then we need to send down the command.
const command_queue_empty = self.command_queue.empty();
// Queue list-windows to get the updated window list
try self.queueCommands(&.{.list_windows});
// If our command queue was empty and now it's not, we need to add
// a command to the output.
assert(self.state == .command_queue);
if (command_queue_empty) {
var arena = self.action_arena.promote(self.alloc);
defer self.action_arena = arena.state;
_ = arena.reset(.free_all);
const arena_alloc = arena.allocator();
var builder: std.Io.Writer.Allocating = .init(arena_alloc);
const command = self.command_queue.first().?;
command.formatCommand(&builder.writer) catch return error.OutOfMemory;
const action: Action = .{ .command = builder.writer.buffered() };
var actions: std.ArrayList(Action) = .empty;
try actions.append(arena_alloc, action);
return actions.items;
}
return &.{};
}
fn syncLayouts(
self: *Viewer,
windows: []const Window,
@ -1576,3 +1613,110 @@ test "layout_change returns command when queue was empty" {
},
});
}
test "window_add queues list_windows when queue empty" {
var viewer = try Viewer.init(testing.allocator);
defer viewer.deinit();
try testViewer(&viewer, &.{
// Initial startup
.{ .input = .{ .tmux = .{ .block_end = "" } } },
.{
.input = .{ .tmux = .{ .session_changed = .{
.id = 1,
.name = "test",
} } },
.contains_command = "list-windows",
},
// Receive initial window layout with one pane
.{
.input = .{ .tmux = .{
.block_end =
\\$0 @0 83 44 b7dd,83x44,0,0,0
,
} },
.contains_tags = &.{ .windows, .command },
},
// Complete all capture-pane commands for pane 0
.{ .input = .{ .tmux = .{ .block_end = "" } } },
.{ .input = .{ .tmux = .{ .block_end = "" } } },
.{ .input = .{ .tmux = .{ .block_end = "" } } },
.{ .input = .{ .tmux = .{ .block_end = "" } } },
// Queue should now be empty
.{
.input = .{ .tmux = .{ .block_end = "" } },
.check = (struct {
fn check(v: *Viewer, _: []const Viewer.Action) anyerror!void {
try testing.expect(v.command_queue.empty());
}
}).check,
},
// Now send window_add - should trigger list-windows command
.{
.input = .{ .tmux = .{ .window_add = .{ .id = 1 } } },
.contains_command = "list-windows",
.check = (struct {
fn check(v: *Viewer, _: []const Viewer.Action) anyerror!void {
// Command queue should have list_windows
try testing.expect(!v.command_queue.empty());
try testing.expectEqual(1, v.command_queue.len());
}
}).check,
},
.{
.input = .{ .tmux = .exit },
.contains_tags = &.{.exit},
},
});
}
test "window_add queues list_windows when queue not empty" {
var viewer = try Viewer.init(testing.allocator);
defer viewer.deinit();
try testViewer(&viewer, &.{
// Initial startup
.{ .input = .{ .tmux = .{ .block_end = "" } } },
.{
.input = .{ .tmux = .{ .session_changed = .{
.id = 1,
.name = "test",
} } },
.contains_command = "list-windows",
},
// Receive initial window layout with one pane
.{
.input = .{ .tmux = .{
.block_end =
\\$0 @0 83 44 b7dd,83x44,0,0,0
,
} },
.contains_tags = &.{ .windows, .command },
.check = (struct {
fn check(v: *Viewer, _: []const Viewer.Action) anyerror!void {
// Queue should have capture-pane commands
try testing.expect(!v.command_queue.empty());
}
}).check,
},
// Do NOT complete capture-pane commands - queue still has commands.
// Send window_add - should queue list-windows but NOT return command action
.{
.input = .{ .tmux = .{ .window_add = .{ .id = 1 } } },
.check = (struct {
fn check(v: *Viewer, actions: []const Viewer.Action) anyerror!void {
// Should not contain a command action since queue was not empty
for (actions) |action| {
try testing.expect(action != .command);
}
// But list_windows should be in the queue
try testing.expect(!v.command_queue.empty());
}
}).check,
},
.{
.input = .{ .tmux = .exit },
.contains_tags = &.{.exit},
},
});
}