From 67763fbabfa873f506d4d2a498c08bf83e1fbac5 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Wed, 1 Jul 2026 17:26:20 -0300 Subject: sdl: main + tests additions --- main.zig | 15 +++++++++++++++ tests.zig | 22 ++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/main.zig b/main.zig index 3db83d94..732a4710 100644 --- a/main.zig +++ b/main.zig @@ -481,9 +481,24 @@ fn forkTerm(gpa: std.mem.Allocator, init_cols: u16, init_rows: u16, cwd: ?[*:0]c .rows = init_rows, }; 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. + t.stream.handler.effects.write_pty = ptyReport; 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. +fn ptyReport(handler: *ghostty_vt.TerminalStream.Handler, data: [:0]const u8) void { + const t: *Term = @fieldParentPtr("term", handler.terminal); + termWrite(t, data); +} + // 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 83e42dad..0a81feb6 100644 --- a/tests.zig +++ b/tests.zig @@ -589,4 +589,26 @@ pub fn main(init: std.process.Init) !void { return error.ColumnCollapseReflow; } } + + // 18. DEVICE-QUERY REPLIES: full-screen apps (nushell/reedline, helix, fzf — all + // crossterm) send a DSR cursor-position report (ESC[6n) at startup and BLOCK + // until the terminal answers. pardes must reply into the pty; without the + // write_pty effect the reply is dropped and the app spins forever ("nushell + // never opens"). Mode-independent. Run a command that (in raw mode, like a + // real app) emits ESC[6n and only echoes its marker AFTER it reads the reply + // — the marker appears only if pardes answered the query. + { + var hs8 = try Harness.init(gpa, exe.ptr, 24, 80); + defer hs8.deinit(); + try hs8.expectWaitFor("build.zig", 8000, "fresh terminal never auto-ls'd"); + try hs8.send("\x02"); // normal -> tty + try hs8.pump(600); + // -icanon so the reply's bytes reach `read` without a newline; read until the + // reply terminator 'R' (ESC[;R) with a timeout, and print the marker + // ONLY if the read SUCCEEDS (a reply arrived). The marker is split with '' + // so it appears solely in the command's OUTPUT, never in the echoed command + // line — otherwise the on-screen command text would false-match it. + 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)"); + } } -- cgit v1.3