mirror of
https://github.com/openai/codex.git
synced 2026-04-24 22:54:54 +00:00
fix(tui): handle WSL clipboard image paths (#3990)
Fixes #3939 Fixes #2803 ## Summary - convert Windows clipboard file paths into their `/mnt/<drive>` equivalents when running inside WSL so pasted images resolve correctly - add WSL detection helpers and share them with unit tests to cover both native Windows and WSL clipboard normalization cases - improve the test suite by exercising Windows path handling plus a dedicated WSL conversion scenario and keeping the code path guarded by targeted cfgs ## Testing - just fmt - cargo test -p codex-tui - cargo clippy -p codex-tui --tests - just fix -p codex-tui ## Screenshots _Codex TUI screenshot:_ <img width="1880" height="848" alt="describe this copied image" src="https://github.com/user-attachments/assets/c620d43c-f45c-451e-8893-e56ae85a5eea" /> _GitHub docs directory screenshot:_ <img width="1064" height="478" alt="image-copied" src="https://github.com/user-attachments/assets/eb5eef6c-eb43-45a0-8bfe-25c35bcae753" /> Co-authored-by: Eric Traut <etraut@openai.com>
This commit is contained in:
@@ -181,6 +181,14 @@ pub fn normalize_pasted_path(pasted: &str) -> Option<PathBuf> {
|
||||
drive || unc
|
||||
};
|
||||
if looks_like_windows_path {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if is_probably_wsl()
|
||||
&& let Some(converted) = convert_windows_path_to_wsl(pasted)
|
||||
{
|
||||
return Some(converted);
|
||||
}
|
||||
}
|
||||
return Some(PathBuf::from(pasted));
|
||||
}
|
||||
|
||||
@@ -193,6 +201,41 @@ pub fn normalize_pasted_path(pasted: &str) -> Option<PathBuf> {
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn is_probably_wsl() -> bool {
|
||||
std::env::var_os("WSL_DISTRO_NAME").is_some()
|
||||
|| std::env::var_os("WSL_INTEROP").is_some()
|
||||
|| std::env::var_os("WSLENV").is_some()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn convert_windows_path_to_wsl(input: &str) -> Option<PathBuf> {
|
||||
if input.starts_with("\\\\") {
|
||||
return None;
|
||||
}
|
||||
|
||||
let drive_letter = input.chars().next()?.to_ascii_lowercase();
|
||||
if !drive_letter.is_ascii_lowercase() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if input.get(1..2) != Some(":") {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut result = PathBuf::from(format!("/mnt/{drive_letter}"));
|
||||
for component in input
|
||||
.get(2..)?
|
||||
.trim_start_matches(['\\', '/'])
|
||||
.split(['\\', '/'])
|
||||
.filter(|component| !component.is_empty())
|
||||
{
|
||||
result.push(component);
|
||||
}
|
||||
|
||||
Some(result)
|
||||
}
|
||||
|
||||
/// Infer an image format for the provided path based on its extension.
|
||||
pub fn pasted_image_format(path: &Path) -> EncodedImageFormat {
|
||||
match path
|
||||
@@ -210,6 +253,40 @@ pub fn pasted_image_format(path: &Path) -> EncodedImageFormat {
|
||||
#[cfg(test)]
|
||||
mod pasted_paths_tests {
|
||||
use super::*;
|
||||
#[cfg(target_os = "linux")]
|
||||
use std::ffi::OsString;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
struct EnvVarGuard {
|
||||
key: &'static str,
|
||||
original: Option<OsString>,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
impl EnvVarGuard {
|
||||
fn set(key: &'static str, value: &str) -> Self {
|
||||
let original = std::env::var_os(key);
|
||||
unsafe {
|
||||
std::env::set_var(key, value);
|
||||
}
|
||||
Self { key, original }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
impl Drop for EnvVarGuard {
|
||||
fn drop(&mut self) {
|
||||
if let Some(original) = &self.original {
|
||||
unsafe {
|
||||
std::env::set_var(self.key, original);
|
||||
}
|
||||
} else {
|
||||
unsafe {
|
||||
std::env::remove_var(self.key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[test]
|
||||
@@ -223,7 +300,17 @@ mod pasted_paths_tests {
|
||||
fn normalize_file_url_windows() {
|
||||
let input = r"C:\Temp\example.png";
|
||||
let result = normalize_pasted_path(input).expect("should parse file URL");
|
||||
assert_eq!(result, PathBuf::from(r"C:\Temp\example.png"));
|
||||
#[cfg(target_os = "linux")]
|
||||
let expected = if is_probably_wsl()
|
||||
&& let Some(converted) = convert_windows_path_to_wsl(input)
|
||||
{
|
||||
converted
|
||||
} else {
|
||||
PathBuf::from(r"C:\Temp\example.png")
|
||||
};
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let expected = PathBuf::from(r"C:\Temp\example.png");
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -291,10 +378,17 @@ mod pasted_paths_tests {
|
||||
fn normalize_unquoted_windows_path_with_spaces() {
|
||||
let input = r"C:\\Users\\Alice\\My Pictures\\example image.png";
|
||||
let result = normalize_pasted_path(input).expect("should accept unquoted windows path");
|
||||
assert_eq!(
|
||||
result,
|
||||
#[cfg(target_os = "linux")]
|
||||
let expected = if is_probably_wsl()
|
||||
&& let Some(converted) = convert_windows_path_to_wsl(input)
|
||||
{
|
||||
converted
|
||||
} else {
|
||||
PathBuf::from(r"C:\\Users\\Alice\\My Pictures\\example image.png")
|
||||
);
|
||||
};
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let expected = PathBuf::from(r"C:\\Users\\Alice\\My Pictures\\example image.png");
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -322,4 +416,16 @@ mod pasted_paths_tests {
|
||||
EncodedImageFormat::Other
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[test]
|
||||
fn normalize_windows_path_in_wsl() {
|
||||
let _guard = EnvVarGuard::set("WSL_DISTRO_NAME", "Ubuntu-24.04");
|
||||
let input = r"C:\\Users\\Alice\\Pictures\\example image.png";
|
||||
let result = normalize_pasted_path(input).expect("should convert windows path on wsl");
|
||||
assert_eq!(
|
||||
result,
|
||||
PathBuf::from("/mnt/c/Users/Alice/Pictures/example image.png")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user