repos / zmx

session persistence for terminal processes
git clone https://github.com/neurosnap/zmx.git

commit
af0e1f3
parent
3971f2d
author
Eric Bower
date
2026-04-08 23:05:34 -0400 EDT
chore: cleanup util formatting
1 files changed,  +32, -52
M src/util.zig
+32, -52
  1@@ -339,11 +339,13 @@ pub fn serializeTerminalState(alloc: std.mem.Allocator, term: *ghostty_vt.Termin
  2             sb_bottom.x = @intCast(pages.cols - 1);
  3 
  4             var scroll_fmt = ghostty_vt.formatter.TerminalFormatter.init(term, .vt);
  5-            scroll_fmt.content = .{ .selection = ghostty_vt.Selection.init(
  6-                screen_top,
  7-                sb_bottom,
  8-                false,
  9-            ) };
 10+            scroll_fmt.content = .{
 11+                .selection = ghostty_vt.Selection.init(
 12+                    screen_top,
 13+                    sb_bottom,
 14+                    false,
 15+                ),
 16+            };
 17             scroll_fmt.extra = .none; // no modes, cursor, keyboard — just content
 18             scroll_fmt.format(&builder.writer) catch |err| {
 19                 std.log.warn("failed to format scrollback err={s}", .{@errorName(err)});
 20@@ -361,17 +363,21 @@ pub fn serializeTerminalState(alloc: std.mem.Allocator, term: *ghostty_vt.Termin
 21 
 22     // Restrict content to the active viewport only
 23     const active_tl = pages.pin(.{ .active = .{ .x = 0, .y = 0 } });
 24-    const active_br = pages.pin(.{ .active = .{
 25-        .x = @intCast(pages.cols - 1),
 26-        .y = @intCast(pages.rows - 1),
 27-    } });
 28+    const active_br = pages.pin(.{
 29+        .active = .{
 30+            .x = @intCast(pages.cols - 1),
 31+            .y = @intCast(pages.rows - 1),
 32+        },
 33+    });
 34 
 35     if (active_tl != null and active_br != null) {
 36-        vis_fmt.content = .{ .selection = ghostty_vt.Selection.init(
 37-            active_tl.?,
 38-            active_br.?,
 39-            false,
 40-        ) };
 41+        vis_fmt.content = .{
 42+            .selection = ghostty_vt.Selection.init(
 43+                active_tl.?,
 44+                active_br.?,
 45+                false,
 46+            ),
 47+        };
 48     }
 49     // Fallback: if pins are somehow invalid, use null selection (all content)
 50 
 51@@ -815,20 +821,6 @@ test "serializeTerminalState excludes synchronized output replay" {
 52     try std.testing.expect(std.mem.indexOf(u8, output, "\x1b[?2026h") == null);
 53 }
 54 
 55-// ---------------------------------------------------------------------------
 56-// Integration tests: serializeTerminalState roundtrip verification
 57-//
 58-// These tests exercise the ghostty-vt roundtrip pattern:
 59-//   1. Create Terminal A, feed VT sequences (scrollback, markers, cursor)
 60-//   2. Serialize A via serializeTerminalState()
 61-//   3. Create Terminal B (same dimensions), feed serialized bytes
 62-//   4. Compare B's screen content and cursor with A's
 63-//
 64-// This verifies that what the user sees after re-attach matches what the
 65-// daemon's terminal state actually contains — including in nested sessions
 66-// (zmx→SSH→zmx) where serialized state flows through multiple layers.
 67-// ---------------------------------------------------------------------------
 68-
 69 fn testCreateTerminal(alloc: std.mem.Allocator, cols: u16, rows: u16, vt_data: []const u8) !ghostty_vt.Terminal {
 70     var term = try ghostty_vt.Terminal.init(alloc, .{
 71         .cols = cols,
 72@@ -892,8 +884,7 @@ fn expectMarkerAtRow(alloc: std.mem.Allocator, term: *ghostty_vt.Terminal, marke
 73 test "serializeTerminalState roundtrip preserves cursor position" {
 74     const alloc = std.testing.allocator;
 75 
 76-    var term = try testCreateTerminal(alloc, 80, 24,
 77-        "\x1b[2J" ++ // clear
 78+    var term = try testCreateTerminal(alloc, 80, 24, "\x1b[2J" ++ // clear
 79         "\x1b[10;20H" // cursor at row 10, col 20 (1-indexed)
 80     );
 81     defer term.deinit(alloc);
 82@@ -909,14 +900,12 @@ test "serializeTerminalState roundtrip preserves cursor position" {
 83 test "serializeTerminalState roundtrip preserves CUP-positioned markers" {
 84     const alloc = std.testing.allocator;
 85 
 86-    var term = try testCreateTerminal(alloc, 80, 24,
 87-        "\x1b[2J" ++
 88+    var term = try testCreateTerminal(alloc, 80, 24, "\x1b[2J" ++
 89         "\x1b[2;5HMARK_A" ++
 90         "\x1b[6;15HMARK_B" ++
 91         "\x1b[10;30HMARK_C" ++
 92         "\x1b[14;50HMARK_D" ++
 93-        "\x1b[16;20H"
 94-    );
 95+        "\x1b[16;20H");
 96     defer term.deinit(alloc);
 97 
 98     var client = try serializeRoundtrip(alloc, &term);
 99@@ -947,13 +936,11 @@ test "serializeTerminalState with scrollback preserves visible content" {
100     }
101 
102     // Clear screen and place markers at specific positions
103-    try stream.nextSlice(
104-        "\x1b[2J" ++
105+    try stream.nextSlice("\x1b[2J" ++
106         "\x1b[2;5HMARK_A" ++
107         "\x1b[6;15HMARK_B" ++
108         "\x1b[10;30HMARK_C" ++
109-        "\x1b[16;20H"
110-    );
111+        "\x1b[16;20H");
112 
113     // Verify source terminal has scrollback
114     const pages = &term.screens.active.pages;
115@@ -989,12 +976,10 @@ test "serializeTerminalState nested roundtrip preserves content" {
116             const line = std.fmt.bufPrint(&buf, "SCROLL_{d}\r\n", .{i}) catch unreachable;
117             try inner_stream.nextSlice(line);
118         }
119-        try inner_stream.nextSlice(
120-            "\x1b[2J" ++
121+        try inner_stream.nextSlice("\x1b[2J" ++
122             "\x1b[3;10HINNER_A" ++
123             "\x1b[12;25HINNER_B" ++
124-            "\x1b[20;5H"
125-        );
126+            "\x1b[20;5H");
127     }
128 
129     // Record inner's ground truth
130@@ -1030,8 +1015,7 @@ test "serializeTerminalState nested roundtrip preserves content" {
131 test "serializeTerminalState alternate screen not leaked" {
132     const alloc = std.testing.allocator;
133 
134-    var term = try testCreateTerminal(alloc, 80, 24,
135-        "\x1b[?1049h" ++ // enter alt screen
136+    var term = try testCreateTerminal(alloc, 80, 24, "\x1b[?1049h" ++ // enter alt screen
137         "\x1b[2J\x1b[3;10HALT_MARK" ++ // write on alt screen
138         "\x1b[?1049l" ++ // exit alt screen
139         "\x1b[2J\x1b[2;5HMAIN_MARK\x1b[8;20H" // write on main screen
140@@ -1052,13 +1036,11 @@ test "serializeTerminalState alternate screen not leaked" {
141 test "serializeTerminalState size mismatch roundtrip" {
142     const alloc = std.testing.allocator;
143 
144-    var term = try testCreateTerminal(alloc, 80, 30,
145-        "\x1b[2J" ++
146+    var term = try testCreateTerminal(alloc, 80, 30, "\x1b[2J" ++
147         "\x1b[3;10HSIZE_A" ++
148         "\x1b[12;20HSIZE_B" ++
149         "\x1b[20;40HSIZE_C" ++
150-        "\x1b[15;15H"
151-    );
152+        "\x1b[15;15H");
153     defer term.deinit(alloc);
154 
155     // Resize to 24 rows (simulates outer terminal being smaller)
156@@ -1085,12 +1067,10 @@ test "serializeTerminalState scrollback + size mismatch nested roundtrip" {
157             const line = std.fmt.bufPrint(&buf, "LINE_{d}\r\n", .{i}) catch unreachable;
158             try inner_stream.nextSlice(line);
159         }
160-        try inner_stream.nextSlice(
161-            "\x1b[2J" ++
162+        try inner_stream.nextSlice("\x1b[2J" ++
163             "\x1b[3;10HSTRESS_A" ++
164             "\x1b[12;25HSTRESS_B" ++
165-            "\x1b[16;20H"
166-        );
167+            "\x1b[16;20H");
168     }
169 
170     // Resize inner to 24 rows (outer terminal is smaller)