diff --git a/src-tauri/src/process.rs b/src-tauri/src/process.rs index a327227..3fab9a8 100644 --- a/src-tauri/src/process.rs +++ b/src-tauri/src/process.rs @@ -13,6 +13,7 @@ pub fn configure_std_command(command: &mut Command) -> &mut Command { { use std::os::windows::process::CommandExt; command.creation_flags(CREATE_NO_WINDOW); + set_utf8_env(command); } command @@ -33,11 +34,47 @@ pub fn configure_tokio_command( #[cfg(windows)] { command.creation_flags(CREATE_NO_WINDOW); + set_utf8_env(command); } command } +/// Hint child processes to produce UTF-8 output on Windows. +/// +/// Sets environment variables recognised by common runtimes (Python, MSYS2/Git +/// Bash, .NET console apps). Not all programs honour these, but they cover the +/// most frequent sources of mojibake in practice. +#[cfg(windows)] +fn set_utf8_env(command: &mut C) { + // Python + command.env("PYTHONUTF8", "1"); + command.env("PYTHONIOENCODING", "utf-8"); + // MSYS2 / Git-for-Windows / POSIX-layer tools + command.env("LANG", "C.UTF-8"); + command.env("LC_ALL", "C.UTF-8"); +} + +/// Abstraction over the `.env()` method shared by std and tokio Command types. +#[cfg(windows)] +trait SetEnv { + fn env(&mut self, key: &str, val: &str) -> &mut Self; +} + +#[cfg(windows)] +impl SetEnv for Command { + fn env(&mut self, key: &str, val: &str) -> &mut Self { + Command::env(self, key, val) + } +} + +#[cfg(windows)] +impl SetEnv for tokio::process::Command { + fn env(&mut self, key: &str, val: &str) -> &mut Self { + tokio::process::Command::env(self, key, val) + } +} + #[cfg(windows)] fn maybe_windows_cmd_shim(program: &OsStr) -> Option { let path = Path::new(program); diff --git a/src-tauri/src/terminal/manager.rs b/src-tauri/src/terminal/manager.rs index 7c875be..cbf91a9 100644 --- a/src-tauri/src/terminal/manager.rs +++ b/src-tauri/src/terminal/manager.rs @@ -92,11 +92,19 @@ fn detect_windows_shell_flavor(shell: &str) -> WindowsShellFlavor { fn configure_shell_command(cmd: &mut CommandBuilder, shell: &str, initial_command: Option<&str>) { #[cfg(target_os = "windows")] { + // Force UTF-8 output for all Windows shell flavors + cmd.env("PYTHONUTF8", "1"); + cmd.env("PYTHONIOENCODING", "utf-8"); + match detect_windows_shell_flavor(shell) { WindowsShellFlavor::Cmd => { if let Some(command) = initial_command { cmd.env("CODEG_CMD", command); - cmd.args(["/D", "/S", "/C", "%CODEG_CMD%"]); + // Set UTF-8 code page before running the actual command + cmd.args(["/D", "/S", "/C", "chcp 65001 >nul & %CODEG_CMD%"]); + } else { + // /K runs the command then stays open for interactive use + cmd.args(["/D", "/S", "/K", "chcp 65001 >nul"]); } } WindowsShellFlavor::PowerShell => { @@ -106,16 +114,24 @@ fn configure_shell_command(cmd: &mut CommandBuilder, shell: &str, initial_comman "-NoLogo", "-NoProfile", "-Command", - "$ErrorActionPreference = 'Stop'; Invoke-Expression $env:CODEG_CMD", + "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; $ErrorActionPreference = 'Stop'; Invoke-Expression $env:CODEG_CMD", ]); } else { - cmd.args(["-NoLogo", "-NoProfile"]); + // -NoExit runs the command then stays open for interactive use + cmd.args([ + "-NoLogo", + "-NoProfile", + "-NoExit", + "-Command", + "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; $host.UI.RawUI.WindowTitle = 'codeg'", + ]); } } WindowsShellFlavor::Posix => { cmd.env("TERM", "xterm-256color"); cmd.env("COLORTERM", "truecolor"); cmd.env("TERM_PROGRAM", "codeg"); + cmd.env("LANG", "C.UTF-8"); if let Some(command) = initial_command { cmd.env("CODEG_CMD", command); cmd.args(["-l", "-i", "-c", "eval \"$CODEG_CMD\""]);