intent_engine/
windows_console.rs

1//! Windows Console UTF-8 Support
2//!
3//! This module provides utilities for handling console encoding on Windows.
4//! It automatically configures the console to use UTF-8 encoding for proper
5//! display and input of Chinese and other non-ASCII characters.
6
7#[cfg(windows)]
8use windows::Win32::System::Console::{
9    GetConsoleMode, GetStdHandle, SetConsoleCP, SetConsoleMode, SetConsoleOutputCP, CONSOLE_MODE,
10    ENABLE_VIRTUAL_TERMINAL_PROCESSING, STD_OUTPUT_HANDLE,
11};
12
13/// Setup Windows console for UTF-8 input and output
14///
15/// This function:
16/// 1. Sets the console input code page to UTF-8 (65001)
17/// 2. Sets the console output code page to UTF-8 (65001)
18/// 3. Enables virtual terminal processing for ANSI escape sequences
19///
20/// # Returns
21///
22/// Returns `Ok(())` if successful, or an error message if it fails.
23///
24/// # Platform-specific
25///
26/// This function only affects Windows systems. On other platforms, it's a no-op.
27///
28/// # Example
29///
30/// ```no_run
31/// # use intent_engine::windows_console::setup_windows_console;
32/// if let Err(e) = setup_windows_console() {
33///     eprintln!("Warning: Failed to setup UTF-8 console: {}", e);
34/// }
35/// ```
36#[cfg(windows)]
37pub fn setup_windows_console() -> Result<(), String> {
38    unsafe {
39        // Set console INPUT code page to UTF-8 (65001)
40        // This is CRITICAL for reading UTF-8 from stdin (pipes, redirects)
41        SetConsoleCP(65001)
42            .map_err(|e| format!("Failed to set console input code page to UTF-8: {}", e))?;
43
44        // Set console OUTPUT code page to UTF-8 (65001)
45        // This ensures that our UTF-8 output is correctly interpreted
46        SetConsoleOutputCP(65001)
47            .map_err(|e| format!("Failed to set console output code page to UTF-8: {}", e))?;
48
49        // Get the standard output handle
50        let handle = match GetStdHandle(STD_OUTPUT_HANDLE) {
51            Ok(h) => h,
52            Err(e) => return Err(format!("Failed to get stdout handle: {}", e)),
53        };
54
55        // Enable virtual terminal processing
56        // This allows ANSI escape sequences to work properly
57        let mut mode = CONSOLE_MODE(0);
58        if GetConsoleMode(handle, &mut mode).is_ok() {
59            let new_mode = mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING;
60            if SetConsoleMode(handle, new_mode).is_err() {
61                // This is non-critical, so we just log a warning
62                eprintln!(
63                    "Warning: Could not enable virtual terminal processing. ANSI colors may not work."
64                );
65            }
66        }
67    }
68
69    Ok(())
70}
71
72/// Setup Windows console for UTF-8 output (no-op on non-Windows platforms)
73#[cfg(not(windows))]
74pub fn setup_windows_console() -> Result<(), String> {
75    Ok(())
76}
77
78/// Check if the current console output is using UTF-8 encoding
79///
80/// # Returns
81///
82/// Returns `true` if the console output is using UTF-8 (code page 65001),
83/// `false` otherwise.
84///
85/// # Platform-specific
86///
87/// On non-Windows platforms, this always returns `true`.
88#[cfg(windows)]
89pub fn is_console_utf8() -> bool {
90    use windows::Win32::System::Console::GetConsoleOutputCP;
91
92    unsafe {
93        let cp = GetConsoleOutputCP();
94        cp == 65001 // UTF-8 code page
95    }
96}
97
98#[cfg(not(windows))]
99pub fn is_console_utf8() -> bool {
100    true
101}
102
103/// Check if the current console input is using UTF-8 encoding
104///
105/// # Returns
106///
107/// Returns `true` if the console input is using UTF-8 (code page 65001),
108/// `false` otherwise.
109///
110/// # Platform-specific
111///
112/// On non-Windows platforms, this always returns `true`.
113#[cfg(windows)]
114pub fn is_console_input_utf8() -> bool {
115    use windows::Win32::System::Console::GetConsoleCP;
116
117    unsafe {
118        let cp = GetConsoleCP();
119        cp == 65001 // UTF-8 code page
120    }
121}
122
123#[cfg(not(windows))]
124pub fn is_console_input_utf8() -> bool {
125    true
126}
127
128/// Detect the current console output code page
129///
130/// # Returns
131///
132/// Returns the current output code page number (e.g., 936 for GBK, 65001 for UTF-8)
133///
134/// # Platform-specific
135///
136/// On non-Windows platforms, this returns 65001 (UTF-8).
137#[cfg(windows)]
138pub fn get_console_code_page() -> u32 {
139    use windows::Win32::System::Console::GetConsoleOutputCP;
140
141    unsafe { GetConsoleOutputCP() }
142}
143
144#[cfg(not(windows))]
145pub fn get_console_code_page() -> u32 {
146    65001 // UTF-8
147}
148
149/// Detect the current console input code page
150///
151/// # Returns
152///
153/// Returns the current input code page number (e.g., 936 for GBK, 65001 for UTF-8)
154///
155/// # Platform-specific
156///
157/// On non-Windows platforms, this returns 65001 (UTF-8).
158#[cfg(windows)]
159pub fn get_console_input_code_page() -> u32 {
160    use windows::Win32::System::Console::GetConsoleCP;
161
162    unsafe { GetConsoleCP() }
163}
164
165#[cfg(not(windows))]
166pub fn get_console_input_code_page() -> u32 {
167    65001 // UTF-8
168}
169
170/// Get a user-friendly name for a Windows code page
171///
172/// # Arguments
173///
174/// * `code_page` - The code page number
175///
176/// # Returns
177///
178/// A string describing the code page (e.g., "UTF-8", "GBK", "Unknown")
179pub fn code_page_name(code_page: u32) -> &'static str {
180    match code_page {
181        65001 => "UTF-8",
182        936 => "GBK (Simplified Chinese)",
183        950 => "Big5 (Traditional Chinese)",
184        932 => "Shift-JIS (Japanese)",
185        949 => "EUC-KR (Korean)",
186        437 => "OEM United States",
187        1252 => "Western European (Windows)",
188        _ => "Unknown",
189    }
190}
191
192#[cfg(test)]
193mod tests {
194    use super::*;
195
196    #[test]
197    fn test_setup_console() {
198        // Should not panic
199        let result = setup_windows_console();
200        assert!(result.is_ok());
201    }
202
203    #[test]
204    fn test_is_console_utf8() {
205        // After setup, console should be UTF-8 (on Windows)
206        let _ = setup_windows_console();
207
208        #[cfg(windows)]
209        {
210            let is_utf8 = is_console_utf8();
211            // This might fail in some CI environments, so we just check it doesn't panic
212            let _ = is_utf8;
213        }
214
215        #[cfg(not(windows))]
216        {
217            assert!(is_console_utf8());
218        }
219    }
220
221    #[test]
222    fn test_code_page_names() {
223        assert_eq!(code_page_name(65001), "UTF-8");
224        assert_eq!(code_page_name(936), "GBK (Simplified Chinese)");
225        assert_eq!(code_page_name(950), "Big5 (Traditional Chinese)");
226        assert_eq!(code_page_name(12345), "Unknown");
227    }
228
229    #[test]
230    fn test_get_code_page() {
231        let cp = get_console_code_page();
232
233        #[cfg(windows)]
234        {
235            // After setup, should be UTF-8
236            let _ = setup_windows_console();
237            let cp_after = get_console_code_page();
238            assert_eq!(cp_after, 65001);
239        }
240
241        #[cfg(not(windows))]
242        {
243            assert_eq!(cp, 65001);
244        }
245    }
246}