summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--main.zig31
-rw-r--r--tests.zig18
2 files changed, 41 insertions, 8 deletions
diff --git a/main.zig b/main.zig
index 732a4710..5ec3bca6 100644
--- a/main.zig
+++ b/main.zig
@@ -482,23 +482,38 @@ fn forkTerm(gpa: std.mem.Allocator, init_cols: u16, init_rows: u16, cwd: ?[*:0]c
};
t.stream = t.term.vtStream();
// Answer terminal QUERIES back into the pty. Apps (nushell/reedline, helix, fzf
- // — anything on crossterm) send a DSR cursor-position report (ESC[6n) at startup
- // and BLOCK waiting for the reply. ghostty computes the reply but drops it unless
- // this effect is wired, so the app spins forever ("nushell never opens"). Only
- // real terminals need it (file/image stubs read no pty). See ptyReport.
+ // — anything on crossterm) query the terminal at startup and BLOCK on the reply.
+ // ghostty computes the reply but drops it unless these effects are wired, so the
+ // app spins forever. write_pty is the sink for DSR cursor-position (ESC[6n) and
+ // the kitty-keyboard report (ESC[?u); device_attributes answers primary DA
+ // (ESC[c), which crossterm sends as `ESC[?u ESC[c` and waits on the DA as the
+ // TERMINATOR — without it helix hangs after entering its alt screen (a blank
+ // pane). Only real terminals need this (file/image stubs read no pty).
t.stream.handler.effects.write_pty = ptyReport;
+ t.stream.handler.effects.device_attributes = ptyDeviceAttrs;
return t;
}
-// ghostty calls this with a reply (cursor-position report, operating status, ...)
-// to send back to the child. The handler's `terminal` is our Term.term field, so
-// recover the owning Term and echo the bytes into its pty as if the child typed
-// them. termWrite no-ops on a closed pty, so a late report after EOF is harmless.
+// ghostty calls this with a reply (cursor-position report, kitty-keyboard flags,
+// operating status, an encoded DA, ...) to send back to the child. The handler's
+// `terminal` is our Term.term field, so recover the owning Term and echo the bytes
+// into its pty as if the child typed them. termWrite no-ops on a closed pty, so a
+// late report after EOF is harmless.
fn ptyReport(handler: *ghostty_vt.TerminalStream.Handler, data: [:0]const u8) void {
const t: *Term = @fieldParentPtr("term", handler.terminal);
termWrite(t, data);
}
+// device_attributes.Attributes isn't re-exported by the ghostty-vt module, so name
+// its type by reflection off the effects field. The default (VT220 + ansi_color) is
+// a fine DA reply — its only job here is to TERMINATE crossterm's capability probe.
+const DeviceAttrs = @typeInfo(@typeInfo(@typeInfo(
+ @FieldType(ghostty_vt.TerminalStream.Handler.Effects, "device_attributes"),
+).optional.child).pointer.child).@"fn".return_type.?;
+fn ptyDeviceAttrs(_: *ghostty_vt.TerminalStream.Handler) DeviceAttrs {
+ return .{};
+}
+
// peel a trailing :NNN (or :NNN:) line suffix: main.zig:100 -> {main.zig, 100}.
fn parsePathLine(tok: []const u8) struct { path: []const u8, line: usize } {
var sep: usize = 0;
diff --git a/tests.zig b/tests.zig
index 0a81feb6..e0d982bd 100644
--- a/tests.zig
+++ b/tests.zig
@@ -611,4 +611,22 @@ pub fn main(init: std.process.Init) !void {
try hs8.send("stty -icanon -echo min 1 time 0; printf '\\033[6n'; read -r -d R -t 3 _c && printf 'ZZCPR''OK\\n'; stty sane\r");
try hs8.expectWaitFor("ZZCPROK", 8000, "app blocked on its ESC[6n query — pardes dropped the reply (device queries not answered)");
}
+
+ // 19. PRIMARY-DA TERMINATOR (helix regression). crossterm probes capabilities with
+ // `ESC[?u ESC[c` (kitty-keyboard query + primary DA) and waits on the DA reply
+ // `ESC[?...c` as the TERMINATOR of the whole probe. Answering ESC[6n alone (test
+ // 18) is not enough: without primary DA, helix enters its alt screen and then
+ // hangs on a BLANK pane. Send the real probe, read until the DA reply's 'c',
+ // and print the split marker only on success.
+ {
+ var hs9 = try Harness.init(gpa, exe.ptr, 24, 80);
+ defer hs9.deinit();
+ try hs9.expectWaitFor("build.zig", 8000, "fresh terminal never auto-ls'd");
+ try hs9.send("\x02"); // normal -> tty
+ try hs9.pump(600);
+ // read until 'c' (primary DA ends in 'c'); the kitty reply ESC[?0u has no 'c',
+ // so the read only completes once primary DA is answered.
+ try hs9.send("stty -icanon -echo min 1 time 0; printf '\\033[?u\\033[c'; read -r -d c -t 3 _r && printf 'ZZDA''OK\\n'; stty sane\r");
+ try hs9.expectWaitFor("ZZDAOK", 8000, "app blocked on its ESC[c device-attributes probe — pardes never answered primary DA (helix hangs on a blank pane)");
+ }
}