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