diff --git a/Cargo.toml b/Cargo.toml
index 9310ba8..ab15af6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -20,10 +20,12 @@ crossterm = "0.27"
futures = "0.3"
[dev-dependencies]
-rusty-testkit = { version = "0.1.1", path = "../rusty-testkit" }
tokio-test = "0.4"
rusty-bubbles = { version = "2.1.0", path = "../rusty-bubbles" }
+[target.'cfg(unix)'.dev-dependencies]
+rusty-testkit = { version = "0.1.1", path = "../rusty-testkit" }
+
[lib]
name = "rusty_bubbletea"
path = "src/lib.rs"
diff --git a/UPSTREAM_MAPPING.md b/UPSTREAM_MAPPING.md
index 9bf892b..afe1e76 100644
--- a/UPSTREAM_MAPPING.md
+++ b/UPSTREAM_MAPPING.md
@@ -48,9 +48,9 @@ upstream tag `v2.0.8`, checked out locally in `upstream-go/` (gitignored).
| `termios_other.go` | `src/termios_other.rs` | Non-POSIX fallback |
| `termios_unix.go` | `src/termios_unix.rs` | POSIX termios helper |
| `termios_windows.go` | `src/termios_windows.rs` | Windows console mode helper |
-| `tty.go` | `src/tty.rs` | `init_terminal` / `restore_terminal` |
-| `tty_unix.go` | `src/tty_unix.rs` | Unix TTY reader + raw mode |
-| `tty_windows.go` | `src/tty_windows.rs` | Windows VT console helper |
+| `tty.go` | `src/tty.rs` | Public terminal facade; dispatches raw mode and restore operations to the target platform while sharing window-size queries |
+| `tty_unix.go` | `src/tty_unix.rs` | Unix-only TTY state, termios, and raw-file-descriptor implementation |
+| `tty_windows.go` | `src/tty_windows.rs` | Windows-only safe crossterm console-mode implementation |
| `xterm.go` | `src/xterm.rs` | XTVERSION query, `TerminalVersionMsg` |
## Test Files (`*_test.go` -> `tests/`)
@@ -66,6 +66,7 @@ upstream tag `v2.0.8`, checked out locally in `upstream-go/` (gitignored).
| `mouse_test.go` | `tests/mouse_test.rs` | Mouse event suite |
| `options_test.go` | `tests/commands_test.rs` | Program option tests |
| `screen_test.go` | `tests/commands_test.rs` | Screen buffer tests |
+| Rust Windows regression | `tests/windows_tty.rs` | Native Windows compile and public terminal-surface regression for the platform split |
Golden files under `testdata/` are accounted for by the corresponding Rust test
assertions (values verified against upstream output): `testdata/TestClearMsg/*.golden`
@@ -160,6 +161,11 @@ byte-for-byte.
| `tutorials/basics/main.go` | `examples/tutorial_basics.rs` | Tutorial: counter; quits on 'q' |
| `tutorials/commands/main.go` | `examples/tutorial_commands.rs` | Tutorial: commands; quits on 'q' |
+Rust-only portability regression coverage is maintained in `tests/windows_platform.rs`; it
+exercises the Windows raw-mode and terminal-restoration boundary that has no standalone
+upstream Go test file. The PTY-driven `tests/interactive.rs` suite remains Unix-gated because
+its real pseudo-terminal dependency is intentionally not claimed as native Windows support.
+
Example support files (`examples/go.mod`, `examples/go.sum`, `examples/table/demo.tape`,
per-example `README.md`/`.gif` assets, `tutorials/go.mod`, `tutorials/go.sum`) are documented
in the Support Files section.
diff --git a/coverage.svg b/coverage.svg
index 14a01d2..e9340f8 100644
--- a/coverage.svg
+++ b/coverage.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/src/program.rs b/src/program.rs
index 844a6bd..04f4913 100644
--- a/src/program.rs
+++ b/src/program.rs
@@ -921,9 +921,10 @@ fn uv_mouse_to_mouse(m: rusty_ultraviolet::Mouse) -> crate::mouse::Mouse {
/// CheckOptimizedMovements reads the stdin termios and reports whether hard
/// tabs (TABDLY==TAB0) and backspace (BSDLY==BS0) optimizations are enabled.
fn check_optimized_movements() -> (bool, bool) {
- use std::os::fd::AsRawFd;
#[cfg(unix)]
{
+ use std::os::fd::AsRawFd;
+
let fd = std::io::stdin().as_raw_fd();
let mut t: libc::termios = unsafe { std::mem::zeroed() };
if unsafe { libc::tcgetattr(fd, &mut t) } != 0 {
diff --git a/src/tty.rs b/src/tty.rs
index 178b4a5..1879698 100644
--- a/src/tty.rs
+++ b/src/tty.rs
@@ -1,82 +1,60 @@
//! Cleanroom Rust port of upstream Go source file: `tty.go`
//! Upstream Target Tag / Version: `v2.0.8`
//!
-//!
+//!
//! # TTY Terminal Management
//!
-//! TTY initialization, input stream setup, raw mode toggles, and window dimension queries for Bubble Tea v2.0.8.
-//!
+//! TTY initialization, raw mode toggles, and window dimension queries for
+//! Bubble Tea v2.0.8. Unix targets use the upstream-compatible termios and
+//! raw-file-descriptor implementation. Windows targets use crossterm's safe
+//! console-mode API for enable, disable, initialize, and restore operations.
+//!
use crossterm::terminal::size as term_size;
-use std::sync::{Mutex, OnceLock};
-/// Saved terminal state for the raw mode toggle, mirroring the upstream
-/// `p.previousTtyInputState` (x/term `MakeRaw`/`Restore`).
-static SAVED_TERMIOS: OnceLock>> = OnceLock::new();
+#[cfg(unix)]
+use crate::tty_unix as platform;
+#[cfg(windows)]
+use crate::tty_windows as platform;
+#[cfg(not(any(unix, windows)))]
+use unsupported as platform;
-/// Initializes terminal raw mode, mirroring the upstream `initInput` ->
-/// `term.MakeRaw` path (`tty_unix.go`). Unlike a fully-zeroed `cfmakeraw`,
-/// only `OPOST` is cleared from the output flags, so `TABDLY` (and thus the
-/// hard-tab cursor optimization) behaves exactly as it does upstream.
-pub fn enable_raw_mode() -> std::io::Result<()> {
- use std::os::fd::AsRawFd;
- let fd = std::io::stdin().as_raw_fd();
- let mut t: libc::termios = unsafe { std::mem::zeroed() };
- if unsafe { libc::tcgetattr(fd, &mut t) } != 0 {
- return Err(std::io::Error::last_os_error());
+#[cfg(not(any(unix, windows)))]
+mod unsupported {
+ /// Reports that raw mode is unavailable on an unsupported target.
+ pub(super) fn enable_raw_mode() -> std::io::Result<()> {
+ Err(std::io::Error::new(
+ std::io::ErrorKind::Unsupported,
+ "raw terminal mode is unsupported on this target",
+ ))
}
- *SAVED_TERMIOS
- .get_or_init(|| Mutex::new(None))
- .lock()
- .unwrap() = Some(t);
- // This attempts to replicate the behaviour documented for cfmakeraw in
- // the termios(3) manpage, as x/term's `makeRaw` does.
- t.c_iflag &= !(libc::IGNBRK
- | libc::BRKINT
- | libc::PARMRK
- | libc::ISTRIP
- | libc::INLCR
- | libc::IGNCR
- | libc::ICRNL
- | libc::IXON);
- t.c_oflag &= !libc::OPOST;
- t.c_lflag &= !(libc::ECHO | libc::ECHONL | libc::ICANON | libc::ISIG | libc::IEXTEN);
- t.c_cflag &= !(libc::CSIZE | libc::PARENB);
- t.c_cflag |= libc::CS8;
- t.c_cc[libc::VMIN] = 1;
- t.c_cc[libc::VTIME] = 0;
- if unsafe { libc::tcsetattr(fd, libc::TCSANOW, &t) } != 0 {
- return Err(std::io::Error::last_os_error());
+ /// Reports that raw mode is unavailable on an unsupported target.
+ pub(super) fn disable_raw_mode() -> std::io::Result<()> {
+ Err(std::io::Error::new(
+ std::io::ErrorKind::Unsupported,
+ "raw terminal mode is unsupported on this target",
+ ))
}
- Ok(())
}
-/// Restores the terminal state saved by [`enable_raw_mode`], mirroring the
-/// upstream `term.Restore` path.
+/// Enables terminal raw mode through the target platform implementation.
+pub fn enable_raw_mode() -> std::io::Result<()> {
+ platform::enable_raw_mode()
+}
+
+/// Disables terminal raw mode through the target platform implementation.
pub fn disable_raw_mode() -> std::io::Result<()> {
- use std::os::fd::AsRawFd;
- let saved = SAVED_TERMIOS
- .get_or_init(|| Mutex::new(None))
- .lock()
- .unwrap()
- .take();
- if let Some(t) = saved {
- let fd = std::io::stdin().as_raw_fd();
- if unsafe { libc::tcsetattr(fd, libc::TCSANOW, &t) } != 0 {
- return Err(std::io::Error::last_os_error());
- }
- }
- Ok(())
+ platform::disable_raw_mode()
}
-/// Initializes terminal raw mode.
+/// Initializes terminal raw mode through the target platform implementation.
pub fn init_terminal() -> Result<(), Box> {
enable_raw_mode()?;
Ok(())
}
-/// Restores terminal raw mode.
+/// Restores terminal raw mode through the target platform implementation.
pub fn restore_terminal() -> Result<(), Box> {
disable_raw_mode()?;
Ok(())
diff --git a/src/tty_unix.rs b/src/tty_unix.rs
index 29c5e47..a3d9532 100644
--- a/src/tty_unix.rs
+++ b/src/tty_unix.rs
@@ -1,13 +1,86 @@
//! Cleanroom Rust port of upstream Go source file: `tty_unix.go`
//! Upstream Target Tag / Version: `v2.0.8`
//!
-//!
+//!
//! # TTY (Unix)
//!
-//! POSIX Unix TTY handle and raw mode initialization.
-//!
+//! POSIX Unix TTY handle and raw mode initialization. The implementation is
+//! compiled only for Unix targets so Windows never type-checks its termios or
+//! raw-file-descriptor operations.
+//!
/// Unix TTY initialization check.
pub fn is_unix_tty() -> bool {
- !cfg!(target_os = "windows")
+ cfg!(unix)
}
+
+#[cfg(unix)]
+mod unix {
+ use std::io;
+ use std::sync::{Mutex, OnceLock};
+
+ /// Saved terminal state for the raw mode toggle, mirroring the upstream
+ /// `p.previousTtyInputState` (`term.MakeRaw`/`term.Restore`).
+ static SAVED_TERMIOS: OnceLock>> = OnceLock::new();
+
+ fn saved_termios() -> &'static Mutex