Skip to content

fix(runtime): real isatty/columns/rows/setRawMode + VT output on Windows - #6630

Merged
proggeramlug merged 3 commits into
mainfrom
fix/win-tty
Jul 20, 2026
Merged

proggeramlug merged 3 commits into
mainfrom
fix/win-tty

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jul 18, 2026 •

Copy link
Copy Markdown
Contributor

Fixes the 2026-07-18 audit defects in crates/perry-runtime/src/tty.rs: the #[cfg(not(unix))] arms hardwired isatty to false and columns/rows to None (the TODO admitted it), and setRawMode was a no-op returning false. Net effect on Windows: process.stdout.isTTY was always false (every color library disabled colors), columns/rows were always undefined, and interactive stdin (inquirer-style prompts) was dead. The irony being fixed: builtins/console.rs:1414 already used std's is_terminal() — which works on Windows via the same GetConsoleMode probe — while tty.rs never got its Windows arms.

What each piece does

New shared module crates/perry-runtime/src/win_console.rs (#[cfg(windows)], windows-sys 0.61 — already a dependency with Win32_System_Console/Win32_Foundation, so no manifest change):

  • is_console_fd(fd) — GetStdHandle(STD_{INPUT,OUTPUT,ERROR}_HANDLE) + GetConsoleMode success ⇒ console. Piped/redirected handles fail the probe ⇒ false, matching Node (libuv classifies a std handle as UV_TTY by the same criterion).
  • window_size(fd) — GetConsoleScreenBufferInfo srWindow extents (Right-Left+1, Bottom-Top+1) for fd 1 (stdout) / fd 2 (stderr). Window, not scrollback buffer — matches uv_tty_get_winsize.
  • set_stdin_raw(enable) — clear ENABLE_{LINE,ECHO,PROCESSED}_INPUT, set ENABLE_VIRTUAL_TERMINAL_INPUT (arrow keys arrive as ANSI \x1b[A..D, matching the Unix parsers); cooked mode saved on first enable, restored on disable; "disable without prior enable" is a successful no-op — the exact contract of the Unix termios arm. The flag handling mirrors the working precedent in perry-stdlib/src/readline.rs:969-1047 (tty.setRawMode + perry/tui input on Windows: SetConsoleMode plumbing #406) and perry-runtime/src/tui/input.rs.
  • enable_vt_output() — sets ENABLE_VIRTUAL_TERMINAL_PROCESSING on console stdout/stderr. Deliberately does NOT set DISABLE_NEWLINE_AUTO_RETURN: the runtime writes bare \n via Rust std I/O throughout, so it relies on the console's automatic NL→CRNL.

tty.rs — real #[cfg(windows)] arms for isatty_impl / winsize_impl / set_fd_raw_mode delegating to the module above (setRawMode accepts fd 0 only — the only fd GetStdHandle can map to the console input handle). Non-unix non-windows targets keep the old stub behavior under #[cfg(not(any(unix, windows)))]. This flows through to tty.isatty(fd), process.std{in,out,err}.isTTY (Node parity: true or undefined, never false), process.stdout.columns/.rows, getWindowSize, and ReadStream.setRawMode.

Startup VT enable — js_gc_init (the once-per-program bootstrap every compiled binary calls first) now calls win_console::enable_vt_output() on Windows, so runtime-emitted escapes (console.clear, tty cursor ops, chalk-style color output unlocked by isTTY now being true) render instead of printing literally. No-op for pipes/redirects (probe fails ⇒ untouched), a failing SetConsoleMode (legacy conhost) is ignored — it can never fail program startup. Idempotent.

Dedup — tui/input.rs's Windows termios_impl (a copy of the readline.rs implementation) now delegates to win_console, so the flag math and save/restore live in one place inside perry-runtime. perry-stdlib/src/readline.rs is intentionally untouched: its enable() couples stdin raw mode with stdout VT in one saved pair, and consolidating it is a separate cleanup (no runtime→stdlib dependency was added; none was needed).

Out of scope

resize events: Windows has no SIGWINCH — delivering process.stdout.on('resize') needs event-loop polling of the console size (or a console input record reader), i.e. event-loop integration. The module doc now says so explicitly. columns/rows reads are live (re-queried per access), so consumers that poll get fresh values.

Tests + verification

  • win_console::tests::pipe_handle_is_not_a_console — a fresh anonymous pipe (std::io::pipe) must fail the GetConsoleMode probe and have no screen-buffer info. Deterministic everywhere.
  • win_console::tests::raw_input_mode_clears_cooked_flags_and_sets_vt_input — pure flag math, deterministic.
  • win_console::tests::stdin_raw_mode_round_trip_when_console_available — save/restore round-trip on a real console handle, gated: when GetConsoleMode on stdin fails (CI runners and piped invocations have no console) it asserts the enable-path failure and skips the round-trip with a message. Not flaky by construction.
  • tty::tests::isatty_and_winsize_report_false_for_piped_std_handles — re-invokes the test binary with all three std streams piped and asserts in the child: isatty(0/1/2) false, isTTY undefined (not false — Node parity), columns/rows undefined, setRawMode(true) fails. Deterministic whether or not the environment has a console.

Ran on Windows 11 x64 / MSVC: cargo check -p perry-runtime (warning set byte-identical to the origin/main baseline modulo line shifts — zero new warnings), the 11 tty+win_console tests (all pass; the console round-trip skipped in this headless session as designed), and the touched-module sweep (tty:: win_console:: tui:: gc:: process:: — 538 passed; the 3 failures are gc::tests timing flakes that fail identically, with run-to-run set shifts, on an unmodified origin/main baseline). Caveats: the full perry-runtime Windows unit suite aborts in dyn_eval::tests (non-unwinding panic) on unmodified origin/main too — pre-existing, unrelated; and interactive-console behavior (real colors/dimensions/raw prompts in a live terminal) was not exercisable from this headless session — the console-dependent paths are covered by the probe/flag tests above. Note origin/main currently doesn't compile on Windows at all (ExitProcess missing -> ! in process/env_misc.rs, pre-existing, fix belongs to PR #6609); verification used that one-line local overlay, which is NOT part of this PR.

No version bump/changelog per maintainer instruction (external-contributor PR flow: maintainer folds in metadata at merge).

Summary by CodeRabbit

  • New Features

    • Improved Windows terminal support, including detection of console handles and terminal dimensions.
    • Added Windows raw input mode support for interactive terminal applications.
    • Enabled ANSI/VT escape sequences for Windows console output.
  • Bug Fixes

    • Corrected terminal capability reporting for Windows console and piped input/output.
    • Improved restoration of normal console input behavior after raw mode is disabled.

@coderabbitai

coderabbitai Bot commented Jul 18, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 282c3016-ba3e-4560-a626-cb6320a7ea25

📥 Commits

Reviewing files that changed from the base of the PR and between cca6e54 and 8508317.

📒 Files selected for processing (5)
  • crates/perry-runtime/src/gc/mod.rs
  • crates/perry-runtime/src/lib.rs
  • crates/perry-runtime/src/tty.rs
  • crates/perry-runtime/src/tui/input.rs
  • crates/perry-runtime/src/win_console.rs

📝 Walkthrough

Walkthrough

Windows runtime console handling is centralized in a new Windows-only module. TTY detection, window sizing, raw input, and VT output setup now use shared helpers across GC startup, TUI input, and tty operations, with tests for console and piped handles.

Changes

Windows console integration

Layer / File(s) Summary
Shared console helpers
crates/perry-runtime/src/lib.rs, crates/perry-runtime/src/win_console.rs
Adds the Windows console module with handle probing, raw-mode save/restore, output sizing, VT output setup, and unit tests.
TTY detection and raw mode
crates/perry-runtime/src/tty.rs
Windows TTY checks, window sizing, and stdin raw-mode operations delegate to win_console, with piped-stdio behavior covered by a child-process test.
Runtime startup and TUI wiring
crates/perry-runtime/src/gc/mod.rs, crates/perry-runtime/src/tui/input.rs
GC startup enables VT output on Windows, while the TUI input backend uses shared raw-mode and VT helpers.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant js_gc_init
  participant termios_impl
  participant win_console
  participant WindowsConsole
  js_gc_init->>win_console: enable_vt_output()
  termios_impl->>win_console: set_stdin_raw(true)
  termios_impl->>win_console: enable_vt_output()
  win_console->>WindowsConsole: GetConsoleMode / SetConsoleMode
Loading

Suggested reviewers: andrewtdiz, thehypnoo

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the Windows TTY/raw-mode and VT-output changes in this PR.
Description check ✅ Passed The description covers the summary, concrete changes, and test/verification details, with only non-critical template sections omitted.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/win-tty

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@proggeramlug

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 19, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@proggeramlug
proggeramlug merged commit efb5120 into main Jul 20, 2026
27 checks passed
@proggeramlug
proggeramlug deleted the fix/win-tty branch July 20, 2026 22:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant