Skip to content

feat: add ANSI parsing support on Windows (VT input hybrid)#1030

Open
eitsupi wants to merge 10 commits intocrossterm-rs:masterfrom
eitsupi:feature/windows-vt-input
Open

feat: add ANSI parsing support on Windows (VT input hybrid)#1030
eitsupi wants to merge 10 commits intocrossterm-rs:masterfrom
eitsupi:feature/windows-vt-input

Conversation

@eitsupi
Copy link
Copy Markdown

@eitsupi eitsupi commented Feb 6, 2026

Fix #737
Fix #962

Summary

  • Enable ENABLE_VIRTUAL_TERMINAL_INPUT on Windows to receive ANSI escape sequences (e.g. bracketed paste ESC[200~...ESC[201~) from modern terminals like Windows Terminal
  • Share the ANSI parser between Unix and Windows by moving it from sys/unix/parse to sys/parse
  • Hybrid approach: VT-capable terminals use the ANSI parser path; legacy consoles gracefully fall back to existing ReadConsoleInput VK code handling

Changes

Refactoring (no behavior change)

  • Move ANSI parser from sys/unix/parse.rs to shared sys/parse.rs
  • Deduplicate Parser struct (was duplicated in mio.rs and tty.rs)
  • Remove #[cfg(unix)] guards from InternalEvent variants and filters

Windows VT input

  • Add try_enable_vt_input() to enable ENABLE_VIRTUAL_TERMINAL_INPUT with graceful fallback
  • Fix enable_mouse_capture() to OR flags instead of replacing (preserves VT input)
  • Implement hybrid WindowsEventSource: batch-read all available input records, feed unicode chars to
    ANSI parser (VT path), fall back to VK codes (non-VT path)

Tested

  • cargo test on Linux
  • cargo test on Windows (1 pre-existing failure unrelated to changes)
  • Manual test cargo run --example event-read --features "events,bracketed-paste" on Windows Terminal: bracketed paste produces Event::Paste(...) correctly
  • Legacy fallback test: with VT disabled, behavior matches main (individual Key events per character)

eitsupi and others added 9 commits February 6, 2026 14:38
Move the ANSI escape sequence parser to a platform-shared module so it
can be reused on Windows. Replace internal is_raw_mode_enabled() calls
with the public API (crate::terminal::is_raw_mode_enabled()) which has
a uniform Result<bool> signature on all platforms.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Make CursorPosition, KeyboardEnhancementFlags, and PrimaryDeviceAttributes
variants available on all platforms. Unify EventFilter::eval to use the
matches! pattern for all platforms. Remove cfg gates from filter structs,
impls, and test functions.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Move the identical Parser struct from mio.rs and tty.rs into
sys/parse.rs. Add push_event() method for Windows hybrid source to
enqueue non-ANSI events alongside parsed events.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Enable ENABLE_VIRTUAL_TERMINAL_INPUT on Windows console input to receive
ANSI escape sequences (including bracketed paste). The hybrid approach
feeds KEY_EVENT unicode characters through the shared ANSI parser when
VT input is available, and falls back to VK code handling for keys
without character data or when VT input is unsupported (legacy conhost).

Also fix enable_mouse_capture() to OR flags instead of replacing, which
would clobber the VT input flag.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Add comment about init_original_console_mode ordering dependency
- Add comment about potential stale event count in batch reading
- Add comment about surrogate buffer invariant across event types
- Clarify VT key-up skip comment re: behavioral consistency
- Extract decode_utf16_char to shared free function with unit tests
  (BMP chars, surrogate pairs, orphaned high/low surrogates)

Co-Authored-By: Claude Opus 4.6 <[email protected]>
…arnings

- Split surrogate_buffer into vt_surrogate and legacy_surrogate to
  prevent interference between VT and non-VT code paths within a
  single batch of input events
- Add comment explaining unwrap_or(false) rationale for
  is_raw_mode_enabled on Windows
- Add #[allow(dead_code)] to push_event and decode_utf16_char (only
  used on Windows, but defined in shared module for testability)
- Expand try_enable_vt_input comment explaining why all set_mode
  errors are treated as "VT not supported"

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Re-add comment explaining that non-key events don't touch vt_surrogate,
so interleaved events between surrogate pair halves are harmless.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
After removing #[cfg(unix)] from InternalEvent variants (CursorPosition,
KeyboardEnhancementFlags, PrimaryDeviceAttributes), the wildcard match
arms in event.rs and stream.rs also need their #[cfg(unix)] guards
removed to remain exhaustive on Windows.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
CursorPositionFilter, KeyboardEnhancementFlagsFilter, and
PrimaryDeviceAttributesFilter are only used on Unix (for terminal
queries). Use #[cfg_attr(windows, allow(dead_code))] to suppress
warnings specifically on Windows without hiding them on Unix.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@soerennielsen
Copy link
Copy Markdown

soerennielsen commented Feb 12, 2026

I verified this with Forge code -> reedline -> crossterm.
Patched cargo.toml with:

// Patches to enable Windows VT input and bracketed paste support
// See: #870
[patch.crates-io]
crossterm = { git = "https://github.com/eitsupi/crossterm", branch = "feature/windows-vt-input" }

It works, THANKS!

I just hope the maintainers will merge and release a new version soon 🙏

sinelaw pushed a commit to sinelaw/fresh that referenced this pull request Mar 7, 2026
Crossterm's Windows backend uses ReadConsoleInput which delivers
structured INPUT_RECORD events. The console strips VT escape sequences
(including bracketed paste markers) from these events. This is a known
crossterm limitation (crossterm-rs/crossterm#737).

Fix by enabling ENABLE_VIRTUAL_TERMINAL_INPUT on the console input
handle and reading VT sequences from KEY_EVENT_RECORD.uChar. This is
the hybrid approach recommended by Microsoft and used by Cygwin, MSYS2,
and OpenSSH (see crossterm-rs/crossterm#1030).

Changes:
- New win_vt_input module: enables VT input mode, reads INPUT_RECORD
  events, extracts raw VT bytes from key events, handles resize/focus
  as structured events, supports UTF-16 surrogate pairs
- relay_windows.rs: forwards raw VT bytes to server (matching Unix
  relay behavior) with crossterm fallback for legacy Windows
- main.rs: Windows direct mode uses InputParser to parse VT bytes into
  crossterm Events, with crossterm fallback if VT input unavailable

https://claude.ai/code/session_01NRqCzMiQM41Low1HvSNaLc
sinelaw pushed a commit to sinelaw/fresh that referenced this pull request Mar 7, 2026
Crossterm's Windows backend uses ReadConsoleInput which delivers
structured INPUT_RECORD events. The console strips VT escape sequences
(including bracketed paste markers) from these events. This is a known
crossterm limitation (crossterm-rs/crossterm#737).

Fix by enabling ENABLE_VIRTUAL_TERMINAL_INPUT on the console input
handle and reading VT sequences from KEY_EVENT_RECORD.uChar. This is
the hybrid approach recommended by Microsoft and used by Cygwin, MSYS2,
and OpenSSH (see crossterm-rs/crossterm#1030).

Changes:
- New win_vt_input module: enables VT input mode, reads INPUT_RECORD
  events, extracts raw VT bytes from key events, handles resize/focus
  as structured events, supports UTF-16 surrogate pairs
- relay_windows.rs: forwards raw VT bytes to server (matching Unix
  relay behavior) with crossterm fallback for legacy Windows
- main.rs: Windows direct mode uses InputParser to parse VT bytes into
  crossterm Events, with crossterm fallback if VT input unavailable

https://claude.ai/code/session_01NRqCzMiQM41Low1HvSNaLc
sinelaw pushed a commit to sinelaw/fresh that referenced this pull request Mar 7, 2026
Crossterm's Windows backend uses ReadConsoleInput which delivers
structured INPUT_RECORD events. The console strips VT escape sequences
(including bracketed paste markers) from these events. This is a known
crossterm limitation (crossterm-rs/crossterm#737).

Fix by enabling ENABLE_VIRTUAL_TERMINAL_INPUT on the console input
handle and reading VT sequences from KEY_EVENT_RECORD.uChar. This is
the hybrid approach recommended by Microsoft and used by Cygwin, MSYS2,
and OpenSSH (see crossterm-rs/crossterm#1030).

Changes:
- New win_vt_input module: enables VT input mode, reads INPUT_RECORD
  events, extracts raw VT bytes from key events, handles resize/focus
  as structured events, supports UTF-16 surrogate pairs
- relay_windows.rs: forwards raw VT bytes to server (matching Unix
  relay behavior) with crossterm fallback for legacy Windows
- main.rs: Windows direct mode uses InputParser to parse VT bytes into
  crossterm Events, with crossterm fallback if VT input unavailable

https://claude.ai/code/session_01NRqCzMiQM41Low1HvSNaLc
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.

Bracketed paste interference on Windows Bracketed paste doesn't work on windows

2 participants