- commit
- 79fce5d
- parent
- a32b80d
- author
- Paul Smith
- date
- 2026-01-24 19:37:56 -0500 EST
feat: indicate current session in listing This change adds a visual indicator (a prefixed arrow) of the current session, if any, to session listings with `zmx [l]ist`. Example: ``` $ zmx l session_name=o.quux pid=38719 clients=0 started_in=/home/example → session_name=o.foo.bash pid=60775 clients=2 started_in=/home/example session_name=o.foo.quux pid=50707 clients=1 started_in=/home/example session_name=o.zmx pid=21531 clients=2 started_in=/home/example session_name=o.zmx.bash pid=17772 clients=1 started_in=/home/example $ zmx l --short o.quux → o.foo.bash o.foo.quux o.zmx o.zmx.bash ``` If there is no current session, the output is unchanged. Fixes #42.
1 files changed,
+113,
-15
+113,
-15
1@@ -503,11 +503,57 @@ const SessionEntry = struct {
2 }
3 };
4
5+const current_arrow = "→";
6+
7+/// Formats a session entry for list output (only the name when `short` is
8+/// true), adding a prefix to indicate the current session, if there is one.
9+fn writeSessionLine(writer: *std.Io.Writer, session: SessionEntry, short: bool, current_session: ?[]const u8) !void {
10+ const prefix = if (current_session) |current|
11+ if (std.mem.eql(u8, current, session.name)) current_arrow ++ " " else " "
12+ else
13+ "";
14+
15+ if (short) {
16+ if (session.is_error) return;
17+ try writer.print("{s}{s}\n", .{ prefix, session.name });
18+ return;
19+ }
20+
21+ if (session.is_error) {
22+ try writer.print("{s}session_name={s}\tstatus={s}\t(cleaning up)\n", .{
23+ prefix,
24+ session.name,
25+ session.error_name.?,
26+ });
27+ return;
28+ }
29+
30+ try writer.print("{s}session_name={s}\tpid={d}\tclients={d}", .{
31+ prefix,
32+ session.name,
33+ session.pid.?,
34+ session.clients_len.?,
35+ });
36+ if (session.cwd) |cwd| {
37+ try writer.print("\tstarted_in={s}", .{cwd});
38+ }
39+ if (session.cmd) |cmd| {
40+ try writer.print("\tcmd={s}", .{cmd});
41+ }
42+ try writer.print("\n", .{});
43+}
44+
45 fn list(cfg: *Cfg, short: bool) !void {
46 var gpa = std.heap.GeneralPurposeAllocator(.{}){};
47 defer _ = gpa.deinit();
48 const alloc = gpa.allocator();
49
50+ const current_session = std.process.getEnvVarOwned(alloc, "ZMX_SESSION") catch |err| switch (err) {
51+ error.EnvironmentVariableNotFound => null,
52+ else => return err,
53+ };
54+ defer if (current_session) |name| alloc.free(name);
55+
56 var dir = try std.fs.openDirAbsolute(cfg.socket_dir, .{ .iterate = true });
57 defer dir.close();
58 var iter = dir.iterate();
59@@ -578,21 +624,7 @@ fn list(cfg: *Cfg, short: bool) !void {
60 std.mem.sort(SessionEntry, sessions.items, {}, SessionEntry.lessThan);
61
62 for (sessions.items) |session| {
63- if (short) {
64- if (session.is_error) continue;
65- try w.interface.print("{s}\n", .{session.name});
66- } else if (session.is_error) {
67- try w.interface.print("session_name={s}\tstatus={s}\t(cleaning up)\n", .{ session.name, session.error_name.? });
68- } else {
69- try w.interface.print("session_name={s}\tpid={d}\tclients={d}", .{ session.name, session.pid.?, session.clients_len.? });
70- if (session.cwd) |cwd| {
71- try w.interface.print("\tstarted_in={s}", .{cwd});
72- }
73- if (session.cmd) |cmd| {
74- try w.interface.print("\tcmd={s}", .{cmd});
75- }
76- try w.interface.print("\n", .{});
77- }
78+ try writeSessionLine(&w.interface, session, short, current_session);
79 try w.interface.flush();
80 }
81 }
82@@ -1478,6 +1510,72 @@ test "isKittyCtrlBackslash" {
83 try std.testing.expect(!isKittyCtrlBackslash("garbage"));
84 }
85
86+test "writeSessionLine formats output for current session and short output" {
87+ const Case = struct {
88+ session: SessionEntry,
89+ short: bool,
90+ current_session: ?[]const u8,
91+ expected: []const u8,
92+ };
93+
94+ const session = SessionEntry{
95+ .name = "dev",
96+ .pid = 123,
97+ .clients_len = 2,
98+ .is_error = false,
99+ .error_name = null,
100+ .cmd = null,
101+ .cwd = null,
102+ };
103+
104+ const cases = [_]Case{
105+ .{
106+ .session = session,
107+ .short = false,
108+ .current_session = "dev",
109+ .expected = "→ session_name=dev\tpid=123\tclients=2\n",
110+ },
111+ .{
112+ .session = session,
113+ .short = false,
114+ .current_session = "other",
115+ .expected = " session_name=dev\tpid=123\tclients=2\n",
116+ },
117+ .{
118+ .session = session,
119+ .short = false,
120+ .current_session = null,
121+ .expected = "session_name=dev\tpid=123\tclients=2\n",
122+ },
123+ .{
124+ .session = session,
125+ .short = true,
126+ .current_session = "dev",
127+ .expected = "→ dev\n",
128+ },
129+ .{
130+ .session = session,
131+ .short = true,
132+ .current_session = "other",
133+ .expected = " dev\n",
134+ },
135+ .{
136+ .session = session,
137+ .short = true,
138+ .current_session = null,
139+ .expected = "dev\n",
140+ },
141+ };
142+
143+ for (cases) |case| {
144+ var builder: std.Io.Writer.Allocating = .init(std.testing.allocator);
145+ defer builder.deinit();
146+
147+ try writeSessionLine(&builder.writer, case.session, case.short, case.current_session);
148+ try std.testing.expectEqualStrings(case.expected, builder.writer.buffered());
149+ }
150+}
151+
152 fn serializeTerminalState(alloc: std.mem.Allocator, term: *ghostty_vt.Terminal) ?[]const u8 {
153 var builder: std.Io.Writer.Allocating = .init(alloc);
154 defer builder.deinit();