diff --git a/Cargo.lock b/Cargo.lock index 3e9639c..c617da0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -950,6 +950,7 @@ dependencies = [ "tokio-util", "unicode-segmentation", "unicode-width 0.2.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/crates/deep-code-tui/Cargo.toml b/crates/deep-code-tui/Cargo.toml index 25b256a..e99ab4c 100644 --- a/crates/deep-code-tui/Cargo.toml +++ b/crates/deep-code-tui/Cargo.toml @@ -26,5 +26,13 @@ unicode-width = "0.2" tokio = { version = "1", features = ["macros", "rt-multi-thread", "sync", "time"] } tokio-util = { version = "0.7", features = ["rt"] } +# Redirect stderr to the log file on Windows (SetStdHandle), mirroring the +# unix `dup2` path so stray writes can't paint over the alternate-screen TUI. +[target.'cfg(windows)'.dependencies] +windows-sys = { version = "0.59", features = [ + "Win32_Foundation", + "Win32_System_Console", +] } + [dev-dependencies] tempfile = "3" diff --git a/crates/deep-code-tui/src/ui.rs b/crates/deep-code-tui/src/ui.rs index 52489a6..b38794c 100644 --- a/crates/deep-code-tui/src/ui.rs +++ b/crates/deep-code-tui/src/ui.rs @@ -108,7 +108,41 @@ fn redirect_stderr_to_log() { } } -#[cfg(not(unix))] +/// Windows equivalent of the unix path: point the process's `STD_ERROR_HANDLE` +/// at the log file via `SetStdHandle`, so LSP/persistence `eprintln!`s land in +/// the log instead of corrupting the alternate-screen TUI. +#[cfg(windows)] +fn redirect_stderr_to_log() { + use std::os::windows::io::AsRawHandle; + + use windows_sys::Win32::Foundation::HANDLE; + use windows_sys::Win32::System::Console::{STD_ERROR_HANDLE, SetStdHandle}; + + let path = crate::cli::workspace_root() + .join(".deep-code") + .join("deep-code.log"); + if let Some(parent) = path.parent() { + let _ = std::fs::create_dir_all(parent); + } + let Ok(file) = std::fs::OpenOptions::new() + .create(true) + .append(true) + .open(&path) + else { + return; + }; + let handle: HANDLE = file.as_raw_handle(); + // SAFETY: `handle` is a valid, writable file handle. SetStdHandle just + // records it as the process's stderr; `forget` keeps it open for the + // process lifetime (the alternate-screen TUI runs until exit), mirroring how + // the unix `dup2` fd outlives the dropped `File`. + unsafe { + SetStdHandle(STD_ERROR_HANDLE, handle); + } + std::mem::forget(file); +} + +#[cfg(not(any(unix, windows)))] fn redirect_stderr_to_log() {} fn run_loop(terminal: &mut AppTerminal, app: &mut App) -> Result<()> {