terminal/tmux: grab tmux version on startup
parent
58000f5821
commit
29bb18d8cd
|
|
@ -154,6 +154,8 @@ pub const Variable = enum {
|
|||
scroll_region_upper,
|
||||
/// Unique session ID prefixed with `$` (e.g., `$0`, `$42`).
|
||||
session_id,
|
||||
/// Server version (e.g., `3.5a`).
|
||||
version,
|
||||
/// Unique window ID prefixed with `@` (e.g., `@0`, `@42`).
|
||||
window_id,
|
||||
/// Width of window.
|
||||
|
|
@ -213,6 +215,7 @@ pub const Variable = enum {
|
|||
.cursor_colour,
|
||||
.cursor_shape,
|
||||
.pane_tabs,
|
||||
.version,
|
||||
.window_layout,
|
||||
=> value,
|
||||
};
|
||||
|
|
@ -253,6 +256,7 @@ pub const Variable = enum {
|
|||
.cursor_colour,
|
||||
.cursor_shape,
|
||||
.pane_tabs,
|
||||
.version,
|
||||
.window_layout,
|
||||
=> []const u8,
|
||||
};
|
||||
|
|
@ -482,6 +486,13 @@ test "parse pane_tabs" {
|
|||
try testing.expectEqualStrings("0", try Variable.parse(.pane_tabs, "0"));
|
||||
}
|
||||
|
||||
test "parse version" {
|
||||
try testing.expectEqualStrings("3.5a", try Variable.parse(.version, "3.5a"));
|
||||
try testing.expectEqualStrings("3.5", try Variable.parse(.version, "3.5"));
|
||||
try testing.expectEqualStrings("next-3.5", try Variable.parse(.version, "next-3.5"));
|
||||
try testing.expectEqualStrings("", try Variable.parse(.version, ""));
|
||||
}
|
||||
|
||||
test "parseFormatStruct single field" {
|
||||
const T = FormatStruct(&.{.session_id});
|
||||
const result = try parseFormatStruct(T, "$42", ' ');
|
||||
|
|
|
|||
|
|
@ -61,6 +61,11 @@ pub const Viewer = struct {
|
|||
/// The current session ID we're attached to.
|
||||
session_id: usize,
|
||||
|
||||
/// The tmux server version string (e.g., "3.5a"). We capture this
|
||||
/// on startup because it will allow us to change behavior between
|
||||
/// versions as necessary.
|
||||
tmux_version: []const u8,
|
||||
|
||||
/// The list of commands we've sent that we want to send and wait
|
||||
/// for a response for. We only send one command at a time just
|
||||
/// to avoid any possible confusion around ordering.
|
||||
|
|
@ -168,6 +173,7 @@ pub const Viewer = struct {
|
|||
// until we receive a session-changed notification which will
|
||||
// set this to a real value.
|
||||
.session_id = 0,
|
||||
.tmux_version = "",
|
||||
.command_queue = command_queue,
|
||||
.windows = .empty,
|
||||
.panes = .empty,
|
||||
|
|
@ -191,6 +197,9 @@ pub const Viewer = struct {
|
|||
while (it.next()) |kv| kv.value_ptr.deinit(self.alloc);
|
||||
self.panes.deinit(self.alloc);
|
||||
}
|
||||
if (self.tmux_version.len > 0) {
|
||||
self.alloc.free(self.tmux_version);
|
||||
}
|
||||
self.action_arena.promote(self.alloc).deinit();
|
||||
}
|
||||
|
||||
|
|
@ -273,9 +282,10 @@ pub const Viewer = struct {
|
|||
var arena = self.action_arena.promote(self.alloc);
|
||||
defer self.action_arena = arena.state;
|
||||
_ = arena.reset(.free_all);
|
||||
|
||||
return self.enterCommandQueue(
|
||||
arena.allocator(),
|
||||
.list_windows,
|
||||
&.{ .tmux_version, .list_windows },
|
||||
) catch {
|
||||
log.warn("failed to queue command, becoming defunct", .{});
|
||||
return self.defunct();
|
||||
|
|
@ -626,6 +636,9 @@ pub const Viewer = struct {
|
|||
try replacement.queueCommands(&.{.list_windows});
|
||||
replacement.state = .command_queue;
|
||||
|
||||
// Transfer preserved version to replacement
|
||||
replacement.tmux_version = try replacement.alloc.dupe(u8, self.tmux_version);
|
||||
|
||||
// Save arena state back before swap
|
||||
replacement.action_arena = arena.state;
|
||||
|
||||
|
|
@ -698,9 +711,33 @@ pub const Viewer = struct {
|
|||
cap.id,
|
||||
content,
|
||||
),
|
||||
|
||||
.tmux_version => try self.receivedTmuxVersion(content),
|
||||
}
|
||||
}
|
||||
|
||||
fn receivedTmuxVersion(
|
||||
self: *Viewer,
|
||||
content: []const u8,
|
||||
) !void {
|
||||
const line = std.mem.trim(u8, content, " \t\r\n");
|
||||
if (line.len == 0) return;
|
||||
|
||||
const data = output.parseFormatStruct(
|
||||
Format.tmux_version.Struct(),
|
||||
line,
|
||||
Format.tmux_version.delim,
|
||||
) catch |err| {
|
||||
log.info("failed to parse tmux version: {s}", .{line});
|
||||
return err;
|
||||
};
|
||||
|
||||
if (self.tmux_version.len > 0) {
|
||||
self.alloc.free(self.tmux_version);
|
||||
}
|
||||
self.tmux_version = try self.alloc.dupe(u8, data.version);
|
||||
}
|
||||
|
||||
fn receivedListWindows(
|
||||
self: *Viewer,
|
||||
arena_alloc: Allocator,
|
||||
|
|
@ -1031,22 +1068,23 @@ pub const Viewer = struct {
|
|||
}
|
||||
|
||||
/// Enters the command queue state from any other state, queueing
|
||||
/// the command and returning an action to execute the first command.
|
||||
/// the commands and returning an action to execute the first command.
|
||||
fn enterCommandQueue(
|
||||
self: *Viewer,
|
||||
arena_alloc: Allocator,
|
||||
command: Command,
|
||||
commands: []const Command,
|
||||
) Allocator.Error![]const Action {
|
||||
assert(self.state != .command_queue);
|
||||
assert(commands.len > 0);
|
||||
|
||||
// Build our command string to send for the action.
|
||||
var builder: std.Io.Writer.Allocating = .init(arena_alloc);
|
||||
command.formatCommand(&builder.writer) catch return error.OutOfMemory;
|
||||
commands[0].formatCommand(&builder.writer) catch return error.OutOfMemory;
|
||||
const action: Action = .{ .command = builder.writer.buffered() };
|
||||
|
||||
// Add our command
|
||||
try self.command_queue.ensureUnusedCapacity(self.alloc, 1);
|
||||
self.command_queue.appendAssumeCapacity(command);
|
||||
// Add our commands
|
||||
try self.command_queue.ensureUnusedCapacity(self.alloc, commands.len);
|
||||
for (commands) |cmd| self.command_queue.appendAssumeCapacity(cmd);
|
||||
|
||||
// Move into the command queue state
|
||||
self.state = .command_queue;
|
||||
|
|
@ -1128,6 +1166,9 @@ const Command = union(enum) {
|
|||
/// are part of the output so we can map it back to our panes.
|
||||
pane_state,
|
||||
|
||||
/// Get the tmux server version.
|
||||
tmux_version,
|
||||
|
||||
/// User command. This is a command provided by the user. Since
|
||||
/// this is user provided, we can't be sure what it is.
|
||||
user: []const u8,
|
||||
|
|
@ -1143,6 +1184,7 @@ const Command = union(enum) {
|
|||
.pane_history,
|
||||
.pane_visible,
|
||||
.pane_state,
|
||||
.tmux_version,
|
||||
=> {},
|
||||
.user => |v| alloc.free(v),
|
||||
};
|
||||
|
|
@ -1196,6 +1238,11 @@ const Command = union(enum) {
|
|||
.{comptime Format.list_panes.comptimeFormat()},
|
||||
)),
|
||||
|
||||
.tmux_version => try writer.writeAll(std.fmt.comptimePrint(
|
||||
"display-message -p '{s}'\n",
|
||||
.{comptime Format.tmux_version.comptimeFormat()},
|
||||
)),
|
||||
|
||||
.user => |v| try writer.writeAll(v),
|
||||
}
|
||||
}
|
||||
|
|
@ -1260,6 +1307,11 @@ const Format = struct {
|
|||
},
|
||||
};
|
||||
|
||||
const tmux_version: Format = .{
|
||||
.delim = ' ',
|
||||
.vars = &.{.version},
|
||||
};
|
||||
|
||||
/// The format string, available at comptime.
|
||||
pub fn comptimeFormat(comptime self: Format) []const u8 {
|
||||
return output.comptimeFormat(self.vars, self.delim);
|
||||
|
|
@ -1378,6 +1430,11 @@ test "session changed resets state" {
|
|||
.id = 1,
|
||||
.name = "first",
|
||||
} } },
|
||||
.contains_command = "display-message",
|
||||
},
|
||||
// Receive version response, which triggers list-windows
|
||||
.{
|
||||
.input = .{ .tmux = .{ .block_end = "3.5a" } },
|
||||
.contains_command = "list-windows",
|
||||
},
|
||||
// Receive window layout with two panes (same format as "initial flow" test)
|
||||
|
|
@ -1393,10 +1450,11 @@ test "session changed resets state" {
|
|||
try testing.expectEqual(1, v.session_id);
|
||||
try testing.expectEqual(1, v.windows.items.len);
|
||||
try testing.expectEqual(2, v.panes.count());
|
||||
try testing.expectEqualStrings("3.5a", v.tmux_version);
|
||||
}
|
||||
}).check,
|
||||
},
|
||||
// Now session changes - should reset everything
|
||||
// Now session changes - should reset everything but keep version
|
||||
.{
|
||||
.input = .{ .tmux = .{ .session_changed = .{
|
||||
.id = 2,
|
||||
|
|
@ -1420,6 +1478,8 @@ test "session changed resets state" {
|
|||
try testing.expectEqual(0, v.windows.items.len);
|
||||
// Old panes should be cleared
|
||||
try testing.expectEqual(0, v.panes.count());
|
||||
// Version should still be preserved
|
||||
try testing.expectEqualStrings("3.5a", v.tmux_version);
|
||||
}
|
||||
}).check,
|
||||
},
|
||||
|
|
@ -1460,13 +1520,23 @@ test "initial flow" {
|
|||
.id = 42,
|
||||
.name = "main",
|
||||
} } },
|
||||
.contains_command = "list-windows",
|
||||
.contains_command = "display-message",
|
||||
.check = (struct {
|
||||
fn check(v: *Viewer, _: []const Viewer.Action) anyerror!void {
|
||||
try testing.expectEqual(42, v.session_id);
|
||||
}
|
||||
}).check,
|
||||
},
|
||||
// Receive version response, which triggers list-windows
|
||||
.{
|
||||
.input = .{ .tmux = .{ .block_end = "3.5a" } },
|
||||
.contains_command = "list-windows",
|
||||
.check = (struct {
|
||||
fn check(v: *Viewer, _: []const Viewer.Action) anyerror!void {
|
||||
try testing.expectEqualStrings("3.5a", v.tmux_version);
|
||||
}
|
||||
}).check,
|
||||
},
|
||||
.{
|
||||
.input = .{ .tmux = .{
|
||||
.block_end =
|
||||
|
|
@ -1632,6 +1702,11 @@ test "layout change" {
|
|||
.id = 1,
|
||||
.name = "test",
|
||||
} } },
|
||||
.contains_command = "display-message",
|
||||
},
|
||||
// Receive version response, which triggers list-windows
|
||||
.{
|
||||
.input = .{ .tmux = .{ .block_end = "3.5a" } },
|
||||
.contains_command = "list-windows",
|
||||
},
|
||||
// Receive initial window layout with one pane
|
||||
|
|
@ -1698,6 +1773,11 @@ test "layout_change does not return command when queue not empty" {
|
|||
.id = 1,
|
||||
.name = "test",
|
||||
} } },
|
||||
.contains_command = "display-message",
|
||||
},
|
||||
// Receive version response, which triggers list-windows
|
||||
.{
|
||||
.input = .{ .tmux = .{ .block_end = "3.5a" } },
|
||||
.contains_command = "list-windows",
|
||||
},
|
||||
// Receive initial window layout with one pane
|
||||
|
|
@ -1754,6 +1834,11 @@ test "layout_change returns command when queue was empty" {
|
|||
.id = 1,
|
||||
.name = "test",
|
||||
} } },
|
||||
.contains_command = "display-message",
|
||||
},
|
||||
// Receive version response, which triggers list-windows
|
||||
.{
|
||||
.input = .{ .tmux = .{ .block_end = "3.5a" } },
|
||||
.contains_command = "list-windows",
|
||||
},
|
||||
// Receive initial window layout with one pane
|
||||
|
|
@ -1816,6 +1901,11 @@ test "window_add queues list_windows when queue empty" {
|
|||
.id = 1,
|
||||
.name = "test",
|
||||
} } },
|
||||
.contains_command = "display-message",
|
||||
},
|
||||
// Receive version response, which triggers list-windows
|
||||
.{
|
||||
.input = .{ .tmux = .{ .block_end = "3.5a" } },
|
||||
.contains_command = "list-windows",
|
||||
},
|
||||
// Receive initial window layout with one pane
|
||||
|
|
@ -1872,6 +1962,11 @@ test "window_add queues list_windows when queue not empty" {
|
|||
.id = 1,
|
||||
.name = "test",
|
||||
} } },
|
||||
.contains_command = "display-message",
|
||||
},
|
||||
// Receive version response, which triggers list-windows
|
||||
.{
|
||||
.input = .{ .tmux = .{ .block_end = "3.5a" } },
|
||||
.contains_command = "list-windows",
|
||||
},
|
||||
// Receive initial window layout with one pane
|
||||
|
|
@ -1924,13 +2019,18 @@ test "two pane flow with pane state" {
|
|||
.id = 0,
|
||||
.name = "0",
|
||||
} } },
|
||||
.contains_command = "list-windows",
|
||||
.contains_command = "display-message",
|
||||
.check = (struct {
|
||||
fn check(v: *Viewer, _: []const Viewer.Action) anyerror!void {
|
||||
try testing.expectEqual(0, v.session_id);
|
||||
}
|
||||
}).check,
|
||||
},
|
||||
// Receive version response, which triggers list-windows
|
||||
.{
|
||||
.input = .{ .tmux = .{ .block_end = "3.5a" } },
|
||||
.contains_command = "list-windows",
|
||||
},
|
||||
// list-windows output with 2 panes in a vertical split
|
||||
.{
|
||||
.input = .{ .tmux = .{
|
||||
|
|
|
|||
Loading…
Reference in New Issue