Fixed TUI regression related to image paste in WSL (#9473)
This affects quoted paths in WSL. The fix is to normalize quoted Windows paths before WSL conversion. This addresses #9456
This commit is contained in:
parent
a5e5d7a384
commit
a803467f52
2 changed files with 148 additions and 58 deletions
|
|
@ -244,9 +244,14 @@ pub fn paste_image_to_temp_png() -> Result<(PathBuf, PastedImageInfo), PasteImag
|
|||
/// - shell-escaped single paths (via `shlex`)
|
||||
pub fn normalize_pasted_path(pasted: &str) -> Option<PathBuf> {
|
||||
let pasted = pasted.trim();
|
||||
let unquoted = pasted
|
||||
.strip_prefix('"')
|
||||
.and_then(|s| s.strip_suffix('"'))
|
||||
.or_else(|| pasted.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')))
|
||||
.unwrap_or(pasted);
|
||||
|
||||
// file:// URL → filesystem path
|
||||
if let Ok(url) = url::Url::parse(pasted)
|
||||
if let Ok(url) = url::Url::parse(unquoted)
|
||||
&& url.scheme() == "file"
|
||||
{
|
||||
return url.to_file_path().ok();
|
||||
|
|
@ -258,38 +263,18 @@ pub fn normalize_pasted_path(pasted: &str) -> Option<PathBuf> {
|
|||
// Detect unquoted Windows paths and bypass POSIX shlex which
|
||||
// treats backslashes as escapes (e.g., C:\Users\Alice\file.png).
|
||||
// Also handles UNC paths (\\server\share\path).
|
||||
let looks_like_windows_path = {
|
||||
// Drive letter path: C:\ or C:/
|
||||
let drive = pasted
|
||||
.chars()
|
||||
.next()
|
||||
.map(|c| c.is_ascii_alphabetic())
|
||||
.unwrap_or(false)
|
||||
&& pasted.get(1..2) == Some(":")
|
||||
&& pasted
|
||||
.get(2..3)
|
||||
.map(|s| s == "\\" || s == "/")
|
||||
.unwrap_or(false);
|
||||
// UNC path: \\server\share
|
||||
let unc = pasted.starts_with("\\\\");
|
||||
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));
|
||||
if let Some(path) = normalize_windows_path(unquoted) {
|
||||
return Some(path);
|
||||
}
|
||||
|
||||
// shell-escaped single path → unescaped
|
||||
let parts: Vec<String> = shlex::Shlex::new(pasted).collect();
|
||||
if parts.len() == 1 {
|
||||
return parts.into_iter().next().map(PathBuf::from);
|
||||
let part = parts.into_iter().next()?;
|
||||
if let Some(path) = normalize_windows_path(&part) {
|
||||
return Some(path);
|
||||
}
|
||||
return Some(PathBuf::from(part));
|
||||
}
|
||||
|
||||
None
|
||||
|
|
@ -339,6 +324,36 @@ fn convert_windows_path_to_wsl(input: &str) -> Option<PathBuf> {
|
|||
Some(result)
|
||||
}
|
||||
|
||||
fn normalize_windows_path(input: &str) -> Option<PathBuf> {
|
||||
// Drive letter path: C:\ or C:/
|
||||
let drive = input
|
||||
.chars()
|
||||
.next()
|
||||
.map(|c| c.is_ascii_alphabetic())
|
||||
.unwrap_or(false)
|
||||
&& input.get(1..2) == Some(":")
|
||||
&& input
|
||||
.get(2..3)
|
||||
.map(|s| s == "\\" || s == "/")
|
||||
.unwrap_or(false);
|
||||
// UNC path: \\server\share
|
||||
let unc = input.starts_with("\\\\");
|
||||
if !drive && !unc {
|
||||
return None;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if is_probably_wsl()
|
||||
&& let Some(converted) = convert_windows_path_to_wsl(input)
|
||||
{
|
||||
return Some(converted);
|
||||
}
|
||||
}
|
||||
|
||||
Some(PathBuf::from(input))
|
||||
}
|
||||
|
||||
/// Infer an image format for the provided path based on its extension.
|
||||
pub fn pasted_image_format(path: &Path) -> EncodedImageFormat {
|
||||
match path
|
||||
|
|
@ -438,9 +453,39 @@ mod pasted_paths_tests {
|
|||
#[test]
|
||||
fn normalize_single_quoted_windows_path() {
|
||||
let input = r"'C:\\Users\\Alice\\My File.jpeg'";
|
||||
let unquoted = r"C:\\Users\\Alice\\My File.jpeg";
|
||||
let result =
|
||||
normalize_pasted_path(input).expect("should trim single quotes on windows path");
|
||||
assert_eq!(result, PathBuf::from(r"C:\\Users\\Alice\\My File.jpeg"));
|
||||
#[cfg(target_os = "linux")]
|
||||
let expected = if is_probably_wsl()
|
||||
&& let Some(converted) = convert_windows_path_to_wsl(unquoted)
|
||||
{
|
||||
converted
|
||||
} else {
|
||||
PathBuf::from(unquoted)
|
||||
};
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let expected = PathBuf::from(unquoted);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_double_quoted_windows_path() {
|
||||
let input = r#""C:\\Users\\Alice\\My File.jpeg""#;
|
||||
let unquoted = r"C:\\Users\\Alice\\My File.jpeg";
|
||||
let result =
|
||||
normalize_pasted_path(input).expect("should trim double quotes on windows path");
|
||||
#[cfg(target_os = "linux")]
|
||||
let expected = if is_probably_wsl()
|
||||
&& let Some(converted) = convert_windows_path_to_wsl(unquoted)
|
||||
{
|
||||
converted
|
||||
} else {
|
||||
PathBuf::from(unquoted)
|
||||
};
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let expected = PathBuf::from(unquoted);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -244,9 +244,14 @@ pub fn paste_image_to_temp_png() -> Result<(PathBuf, PastedImageInfo), PasteImag
|
|||
/// - shell-escaped single paths (via `shlex`)
|
||||
pub fn normalize_pasted_path(pasted: &str) -> Option<PathBuf> {
|
||||
let pasted = pasted.trim();
|
||||
let unquoted = pasted
|
||||
.strip_prefix('"')
|
||||
.and_then(|s| s.strip_suffix('"'))
|
||||
.or_else(|| pasted.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')))
|
||||
.unwrap_or(pasted);
|
||||
|
||||
// file:// URL → filesystem path
|
||||
if let Ok(url) = url::Url::parse(pasted)
|
||||
if let Ok(url) = url::Url::parse(unquoted)
|
||||
&& url.scheme() == "file"
|
||||
{
|
||||
return url.to_file_path().ok();
|
||||
|
|
@ -258,38 +263,18 @@ pub fn normalize_pasted_path(pasted: &str) -> Option<PathBuf> {
|
|||
// Detect unquoted Windows paths and bypass POSIX shlex which
|
||||
// treats backslashes as escapes (e.g., C:\Users\Alice\file.png).
|
||||
// Also handles UNC paths (\\server\share\path).
|
||||
let looks_like_windows_path = {
|
||||
// Drive letter path: C:\ or C:/
|
||||
let drive = pasted
|
||||
.chars()
|
||||
.next()
|
||||
.map(|c| c.is_ascii_alphabetic())
|
||||
.unwrap_or(false)
|
||||
&& pasted.get(1..2) == Some(":")
|
||||
&& pasted
|
||||
.get(2..3)
|
||||
.map(|s| s == "\\" || s == "/")
|
||||
.unwrap_or(false);
|
||||
// UNC path: \\server\share
|
||||
let unc = pasted.starts_with("\\\\");
|
||||
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));
|
||||
if let Some(path) = normalize_windows_path(unquoted) {
|
||||
return Some(path);
|
||||
}
|
||||
|
||||
// shell-escaped single path → unescaped
|
||||
let parts: Vec<String> = shlex::Shlex::new(pasted).collect();
|
||||
if parts.len() == 1 {
|
||||
return parts.into_iter().next().map(PathBuf::from);
|
||||
let part = parts.into_iter().next()?;
|
||||
if let Some(path) = normalize_windows_path(&part) {
|
||||
return Some(path);
|
||||
}
|
||||
return Some(PathBuf::from(part));
|
||||
}
|
||||
|
||||
None
|
||||
|
|
@ -339,6 +324,36 @@ fn convert_windows_path_to_wsl(input: &str) -> Option<PathBuf> {
|
|||
Some(result)
|
||||
}
|
||||
|
||||
fn normalize_windows_path(input: &str) -> Option<PathBuf> {
|
||||
// Drive letter path: C:\ or C:/
|
||||
let drive = input
|
||||
.chars()
|
||||
.next()
|
||||
.map(|c| c.is_ascii_alphabetic())
|
||||
.unwrap_or(false)
|
||||
&& input.get(1..2) == Some(":")
|
||||
&& input
|
||||
.get(2..3)
|
||||
.map(|s| s == "\\" || s == "/")
|
||||
.unwrap_or(false);
|
||||
// UNC path: \\server\share
|
||||
let unc = input.starts_with("\\\\");
|
||||
if !drive && !unc {
|
||||
return None;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if is_probably_wsl()
|
||||
&& let Some(converted) = convert_windows_path_to_wsl(input)
|
||||
{
|
||||
return Some(converted);
|
||||
}
|
||||
}
|
||||
|
||||
Some(PathBuf::from(input))
|
||||
}
|
||||
|
||||
/// Infer an image format for the provided path based on its extension.
|
||||
pub fn pasted_image_format(path: &Path) -> EncodedImageFormat {
|
||||
match path
|
||||
|
|
@ -438,9 +453,39 @@ mod pasted_paths_tests {
|
|||
#[test]
|
||||
fn normalize_single_quoted_windows_path() {
|
||||
let input = r"'C:\\Users\\Alice\\My File.jpeg'";
|
||||
let unquoted = r"C:\\Users\\Alice\\My File.jpeg";
|
||||
let result =
|
||||
normalize_pasted_path(input).expect("should trim single quotes on windows path");
|
||||
assert_eq!(result, PathBuf::from(r"C:\\Users\\Alice\\My File.jpeg"));
|
||||
#[cfg(target_os = "linux")]
|
||||
let expected = if is_probably_wsl()
|
||||
&& let Some(converted) = convert_windows_path_to_wsl(unquoted)
|
||||
{
|
||||
converted
|
||||
} else {
|
||||
PathBuf::from(unquoted)
|
||||
};
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let expected = PathBuf::from(unquoted);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_double_quoted_windows_path() {
|
||||
let input = r#""C:\\Users\\Alice\\My File.jpeg""#;
|
||||
let unquoted = r"C:\\Users\\Alice\\My File.jpeg";
|
||||
let result =
|
||||
normalize_pasted_path(input).expect("should trim double quotes on windows path");
|
||||
#[cfg(target_os = "linux")]
|
||||
let expected = if is_probably_wsl()
|
||||
&& let Some(converted) = convert_windows_path_to_wsl(unquoted)
|
||||
{
|
||||
converted
|
||||
} else {
|
||||
PathBuf::from(unquoted)
|
||||
};
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let expected = PathBuf::from(unquoted);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue