Commit 48d9a17
Vishal
·
2026-03-19 08:35:37 -0400 EDT
parent 27bd9b8
fix: avoid replaying synchronized output on reattach (#95)
1 files changed,
+49,
-0
+49,
-0
| ... | ... | @@ -212,6 +212,17 @@ pub fn serializeTerminalState(alloc: std.mem.Allocator, term: *ghostty_vt.Termin | |
| 212 | 212 | var builder: std.Io.Writer.Allocating = .init(alloc); | |
| 213 | 213 | defer builder.deinit(); | |
| 214 | 214 | ||
| 215 | + | // Synchronized output (DECSET 2026) is a transient rendering handshake | |
| 216 | + | // between a program and its current terminal client. Replaying it to a | |
| 217 | + | // newly attached client can leave that client deferring renders until its | |
| 218 | + | // local timeout fires, so temporarily exclude it from restored state and | |
| 219 | + | // restore the original mode before returning. | |
| 220 | + | const had_synchronized_output = term.modes.get(.synchronized_output); | |
| 221 | + | if (had_synchronized_output) { | |
| 222 | + | term.modes.set(.synchronized_output, false); | |
| 223 | + | defer term.modes.set(.synchronized_output, true); | |
| 224 | + | } | |
| 225 | + | ||
| 215 | 226 | var term_formatter = ghostty_vt.formatter.TerminalFormatter.init(term, .vt); | |
| 216 | 227 | term_formatter.content = .{ .selection = null }; | |
| 217 | 228 | term_formatter.extra = .{ |
| ... | ... | @@ -528,3 +539,41 @@ test "isKittyCtrlBackslash" { | |
| 528 | 539 | try std.testing.expect(!isKittyCtrlBackslash("\x1b[92;1u")); | |
| 529 | 540 | try std.testing.expect(!isKittyCtrlBackslash("garbage")); | |
| 530 | 541 | } | |
| 542 | + | ||
| 543 | + | test "serializeTerminalState excludes synchronized output replay" { | |
| 544 | + | const alloc = std.testing.allocator; | |
| 545 | + | ||
| 546 | + | var term = try ghostty_vt.Terminal.init(alloc, .{ | |
| 547 | + | .cols = 80, | |
| 548 | + | .rows = 24, | |
| 549 | + | }); | |
| 550 | + | defer term.deinit(alloc); | |
| 551 | + | ||
| 552 | + | var stream = term.vtStream(); | |
| 553 | + | defer stream.deinit(); | |
| 554 | + | ||
| 555 | + | stream.nextSlice("\x1b[?2004h"); // Bracketed paste | |
| 556 | + | stream.nextSlice("\x1b[?2026h"); // Synchronized output | |
| 557 | + | stream.nextSlice("hello"); | |
| 558 | + | ||
| 559 | + | try std.testing.expect(term.modes.get(.bracketed_paste)); | |
| 560 | + | try std.testing.expect(term.modes.get(.synchronized_output)); | |
| 561 | + | ||
| 562 | + | const output = serializeTerminalState(alloc, &term) orelse return error.TestUnexpectedNull; | |
| 563 | + | defer alloc.free(output); | |
| 564 | + | ||
| 565 | + | try std.testing.expect(term.modes.get(.synchronized_output)); | |
| 566 | + | ||
| 567 | + | var restored = try ghostty_vt.Terminal.init(alloc, .{ | |
| 568 | + | .cols = 80, | |
| 569 | + | .rows = 24, | |
| 570 | + | }); | |
| 571 | + | defer restored.deinit(alloc); | |
| 572 | + | ||
| 573 | + | var restored_stream = restored.vtStream(); | |
| 574 | + | defer restored_stream.deinit(); | |
| 575 | + | restored_stream.nextSlice(output); | |
| 576 | + | ||
| 577 | + | try std.testing.expect(restored.modes.get(.bracketed_paste)); | |
| 578 | + | try std.testing.expect(!restored.modes.get(.synchronized_output)); | |
| 579 | + | } |