- commit
- 48d9a17
- parent
- 27bd9b8
- author
- Vishal
- date
- 2026-03-19 08:35:37 -0400 EDT
fix: avoid replaying synchronized output on reattach (#95)
1 files changed,
+49,
-0
+49,
-0
1@@ -212,6 +212,17 @@ pub fn serializeTerminalState(alloc: std.mem.Allocator, term: *ghostty_vt.Termin
2 var builder: std.Io.Writer.Allocating = .init(alloc);
3 defer builder.deinit();
4
5+ // Synchronized output (DECSET 2026) is a transient rendering handshake
6+ // between a program and its current terminal client. Replaying it to a
7+ // newly attached client can leave that client deferring renders until its
8+ // local timeout fires, so temporarily exclude it from restored state and
9+ // restore the original mode before returning.
10+ const had_synchronized_output = term.modes.get(.synchronized_output);
11+ if (had_synchronized_output) {
12+ term.modes.set(.synchronized_output, false);
13+ defer term.modes.set(.synchronized_output, true);
14+ }
15+
16 var term_formatter = ghostty_vt.formatter.TerminalFormatter.init(term, .vt);
17 term_formatter.content = .{ .selection = null };
18 term_formatter.extra = .{
19@@ -528,3 +539,41 @@ test "isKittyCtrlBackslash" {
20 try std.testing.expect(!isKittyCtrlBackslash("\x1b[92;1u"));
21 try std.testing.expect(!isKittyCtrlBackslash("garbage"));
22 }
23+
24+test "serializeTerminalState excludes synchronized output replay" {
25+ const alloc = std.testing.allocator;
26+
27+ var term = try ghostty_vt.Terminal.init(alloc, .{
28+ .cols = 80,
29+ .rows = 24,
30+ });
31+ defer term.deinit(alloc);
32+
33+ var stream = term.vtStream();
34+ defer stream.deinit();
35+
36+ stream.nextSlice("\x1b[?2004h"); // Bracketed paste
37+ stream.nextSlice("\x1b[?2026h"); // Synchronized output
38+ stream.nextSlice("hello");
39+
40+ try std.testing.expect(term.modes.get(.bracketed_paste));
41+ try std.testing.expect(term.modes.get(.synchronized_output));
42+
43+ const output = serializeTerminalState(alloc, &term) orelse return error.TestUnexpectedNull;
44+ defer alloc.free(output);
45+
46+ try std.testing.expect(term.modes.get(.synchronized_output));
47+
48+ var restored = try ghostty_vt.Terminal.init(alloc, .{
49+ .cols = 80,
50+ .rows = 24,
51+ });
52+ defer restored.deinit(alloc);
53+
54+ var restored_stream = restored.vtStream();
55+ defer restored_stream.deinit();
56+ restored_stream.nextSlice(output);
57+
58+ try std.testing.expect(restored.modes.get(.bracketed_paste));
59+ try std.testing.expect(!restored.modes.get(.synchronized_output));
60+}