diff --git a/src/bin/dosh-client.rs b/src/bin/dosh-client.rs index dfa9d7e..d33a873 100644 --- a/src/bin/dosh-client.rs +++ b/src/bin/dosh-client.rs @@ -10232,15 +10232,71 @@ fn save_cache(path: &std::path::Path, cred: &CachedCredential) -> Result<()> { Ok(()) } -struct RawMode; +struct RawMode { + #[cfg(windows)] + console_modes: WindowsConsoleModeGuard, +} impl RawMode { fn enter() -> Result { flush_local_terminal_input(); enable_raw_mode()?; + let raw = Self { + #[cfg(windows)] + console_modes: WindowsConsoleModeGuard::enter(), + }; flush_local_terminal_input(); drain_local_terminal_input(); - Ok(Self) + Ok(raw) + } +} + +#[cfg_attr(not(any(test, windows)), allow(dead_code))] +fn windows_vt_output_mode(mode: u32) -> u32 { + const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 0x0004; + mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING +} + +#[cfg(windows)] +struct WindowsConsoleModeGuard { + output: Option<(windows_sys::Win32::Foundation::HANDLE, u32)>, +} + +#[cfg(windows)] +impl WindowsConsoleModeGuard { + fn enter() -> Self { + unsafe { + use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE; + use windows_sys::Win32::System::Console::{ + GetConsoleMode, GetStdHandle, STD_OUTPUT_HANDLE, SetConsoleMode, + }; + + let handle = GetStdHandle(STD_OUTPUT_HANDLE); + if handle.is_null() || handle == INVALID_HANDLE_VALUE { + return Self { output: None }; + } + let mut original = 0u32; + if GetConsoleMode(handle, &mut original) == 0 { + return Self { output: None }; + } + let desired = windows_vt_output_mode(original); + if desired != original { + let _ = SetConsoleMode(handle, desired); + } + Self { + output: Some((handle, original)), + } + } + } + + fn restore(&self) { + if let Some((handle, mode)) = self.output { + unsafe { + use windows_sys::Win32::System::Console::SetConsoleMode; + + let _ = SetConsoleMode(handle, mode); + } + } } } @@ -10304,6 +10360,8 @@ impl Drop for RawMode { let mut stdout = std::io::stdout(); let _ = stdout.write_all(TERMINAL_CLEANUP); let _ = stdout.flush(); + #[cfg(windows)] + self.console_modes.restore(); let _ = disable_raw_mode(); } } @@ -10369,7 +10427,7 @@ mod tests { vscode_command_candidates, vscode_safe_alias, wake_repaint_retry_deadline, windows_command_word, windows_deferred_update_script, windows_effective_url_script, windows_mode_from_readonly, windows_readonly_from_mode, windows_update_script, - windows_url_reachable_script, + windows_url_reachable_script, windows_vt_output_mode, }; use dosh::config::{ClientConfig, CommandExtension, HostConfig}; use dosh::native::EnvVar; @@ -10478,6 +10536,13 @@ mod tests { assert!(!windows_readonly_from_mode(0o755)); } + #[test] + fn windows_vt_output_mode_enables_ansi_sequences() { + assert_eq!(windows_vt_output_mode(0), 0x0004); + assert_eq!(windows_vt_output_mode(0x0001), 0x0005); + assert_eq!(windows_vt_output_mode(0x0005), 0x0005); + } + #[test] fn cleanup_stream_state_removes_all_per_stream_state_only_for_target() { let stream_id = 42;