fix(windows): force UTF-8 encoding for all spawned child processes
- Set UTF-8 environment variables (PYTHONUTF8, PYTHONIOENCODING, LANG, LC_ALL) on all child processes via process.rs helpers - Configure PTY terminals per shell flavor: chcp 65001 for cmd.exe, [Console]::OutputEncoding for PowerShell, LANG=C.UTF-8 for Git Bash - Use /K and -NoExit for interactive shells to avoid nested processes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@ pub fn configure_std_command(command: &mut Command) -> &mut Command {
|
|||||||
{
|
{
|
||||||
use std::os::windows::process::CommandExt;
|
use std::os::windows::process::CommandExt;
|
||||||
command.creation_flags(CREATE_NO_WINDOW);
|
command.creation_flags(CREATE_NO_WINDOW);
|
||||||
|
set_utf8_env(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
command
|
command
|
||||||
@@ -33,11 +34,47 @@ pub fn configure_tokio_command(
|
|||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
command.creation_flags(CREATE_NO_WINDOW);
|
command.creation_flags(CREATE_NO_WINDOW);
|
||||||
|
set_utf8_env(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
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<C: SetEnv>(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)]
|
#[cfg(windows)]
|
||||||
fn maybe_windows_cmd_shim(program: &OsStr) -> Option<OsString> {
|
fn maybe_windows_cmd_shim(program: &OsStr) -> Option<OsString> {
|
||||||
let path = Path::new(program);
|
let path = Path::new(program);
|
||||||
|
|||||||
@@ -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>) {
|
fn configure_shell_command(cmd: &mut CommandBuilder, shell: &str, initial_command: Option<&str>) {
|
||||||
#[cfg(target_os = "windows")]
|
#[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) {
|
match detect_windows_shell_flavor(shell) {
|
||||||
WindowsShellFlavor::Cmd => {
|
WindowsShellFlavor::Cmd => {
|
||||||
if let Some(command) = initial_command {
|
if let Some(command) = initial_command {
|
||||||
cmd.env("CODEG_CMD", 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 => {
|
WindowsShellFlavor::PowerShell => {
|
||||||
@@ -106,16 +114,24 @@ fn configure_shell_command(cmd: &mut CommandBuilder, shell: &str, initial_comman
|
|||||||
"-NoLogo",
|
"-NoLogo",
|
||||||
"-NoProfile",
|
"-NoProfile",
|
||||||
"-Command",
|
"-Command",
|
||||||
"$ErrorActionPreference = 'Stop'; Invoke-Expression $env:CODEG_CMD",
|
"[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; $ErrorActionPreference = 'Stop'; Invoke-Expression $env:CODEG_CMD",
|
||||||
]);
|
]);
|
||||||
} else {
|
} 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 => {
|
WindowsShellFlavor::Posix => {
|
||||||
cmd.env("TERM", "xterm-256color");
|
cmd.env("TERM", "xterm-256color");
|
||||||
cmd.env("COLORTERM", "truecolor");
|
cmd.env("COLORTERM", "truecolor");
|
||||||
cmd.env("TERM_PROGRAM", "codeg");
|
cmd.env("TERM_PROGRAM", "codeg");
|
||||||
|
cmd.env("LANG", "C.UTF-8");
|
||||||
if let Some(command) = initial_command {
|
if let Some(command) = initial_command {
|
||||||
cmd.env("CODEG_CMD", command);
|
cmd.env("CODEG_CMD", command);
|
||||||
cmd.args(["-l", "-i", "-c", "eval \"$CODEG_CMD\""]);
|
cmd.args(["-l", "-i", "-c", "eval \"$CODEG_CMD\""]);
|
||||||
|
|||||||
Reference in New Issue
Block a user