mirror of
https://github.com/openai/codex.git
synced 2026-04-24 22:54:54 +00:00
fixing image paste
This commit is contained in:
@@ -52,6 +52,7 @@ use crate::ui_consts::LIVE_PREFIX_COLS;
|
||||
use codex_file_search::FileMatch;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::io::ErrorKind;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
@@ -61,6 +62,34 @@ use std::time::Instant;
|
||||
/// placeholder in the UI.
|
||||
const LARGE_PASTE_CHAR_THRESHOLD: usize = 1000;
|
||||
|
||||
fn maybe_prefix_root_like(path: &Path) -> Option<PathBuf> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let _ = path;
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
if path.has_root() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let path_str = path.to_string_lossy();
|
||||
const ROOT_PREFIXES: [&str; 5] =
|
||||
["Applications/", "Library/", "System/", "Users/", "Volumes/"];
|
||||
|
||||
if ROOT_PREFIXES
|
||||
.iter()
|
||||
.any(|prefix| path_str.starts_with(prefix))
|
||||
{
|
||||
return Some(PathBuf::from(format!("/{path_str}")));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Result returned when the user interacts with the text area.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum InputResult {
|
||||
@@ -275,11 +304,11 @@ impl ChatComposer {
|
||||
return false;
|
||||
};
|
||||
|
||||
match image::image_dimensions(&path_buf) {
|
||||
Ok((w, h)) => {
|
||||
match Self::resolve_image_path_with_fallback(path_buf) {
|
||||
Ok((resolved_path, w, h)) => {
|
||||
tracing::info!("OK: {pasted}");
|
||||
let format_label = pasted_image_format(&path_buf).label();
|
||||
self.attach_image(path_buf, w, h, format_label);
|
||||
let format_label = pasted_image_format(&resolved_path).label();
|
||||
self.attach_image(resolved_path, w, h, format_label);
|
||||
true
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -289,6 +318,34 @@ impl ChatComposer {
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_image_path_with_fallback(
|
||||
path: PathBuf,
|
||||
) -> Result<(PathBuf, u32, u32), image::ImageError> {
|
||||
match image::image_dimensions(&path) {
|
||||
Ok((w, h)) => Ok((path, w, h)),
|
||||
Err(err) => {
|
||||
if let image::ImageError::IoError(io_err) = &err
|
||||
&& io_err.kind() == ErrorKind::NotFound
|
||||
{
|
||||
if let Some(fallback) = maybe_prefix_root_like(&path) {
|
||||
match image::image_dimensions(&fallback) {
|
||||
Ok((w, h)) => return Ok((fallback, w, h)),
|
||||
Err(fallback_err) => {
|
||||
tracing::debug!(
|
||||
?fallback_err,
|
||||
original = %path.display(),
|
||||
fallback = %fallback.display(),
|
||||
"fallback_dimensions_failed",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_disable_paste_burst(&mut self, disabled: bool) {
|
||||
let was_disabled = self.disable_paste_burst;
|
||||
self.disable_paste_burst = disabled;
|
||||
@@ -3448,4 +3505,20 @@ mod tests {
|
||||
assert_eq!(composer.textarea.text(), "z".repeat(count));
|
||||
assert!(composer.pending_pastes.is_empty());
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[test]
|
||||
fn maybe_prefix_root_like_adds_leading_slash() {
|
||||
let input = PathBuf::from("Users/example/image.png");
|
||||
let result = maybe_prefix_root_like(&input);
|
||||
assert_eq!(result, Some(PathBuf::from("/Users/example/image.png")));
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[test]
|
||||
fn maybe_prefix_root_like_ignores_relative_dirs() {
|
||||
let input = PathBuf::from("project/assets/image.png");
|
||||
let result = maybe_prefix_root_like(&input);
|
||||
assert!(result.is_none());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,12 +187,36 @@ pub fn normalize_pasted_path(pasted: &str) -> Option<PathBuf> {
|
||||
// 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 mut path = parts.into_iter().next()?;
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
path = fixup_unix_root_relative_path(path);
|
||||
}
|
||||
|
||||
return Some(PathBuf::from(path));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn fixup_unix_root_relative_path(mut path: String) -> String {
|
||||
use std::path::Path;
|
||||
|
||||
if Path::new(&path).has_root() {
|
||||
return path;
|
||||
}
|
||||
|
||||
const ROOT_PREFIXES: [&str; 5] = ["Applications/", "Library/", "System/", "Users/", "Volumes/"];
|
||||
|
||||
if ROOT_PREFIXES.iter().any(|prefix| path.starts_with(prefix)) {
|
||||
path.insert(0, '/');
|
||||
}
|
||||
|
||||
path
|
||||
}
|
||||
|
||||
/// Infer an image format for the provided path based on its extension.
|
||||
pub fn pasted_image_format(path: &Path) -> EncodedImageFormat {
|
||||
match path
|
||||
@@ -255,6 +279,25 @@ mod pasted_paths_tests {
|
||||
assert!(result.is_none());
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[test]
|
||||
fn normalize_dragged_finder_users_path() {
|
||||
let input = "'Users/alice/Pictures/example.png'";
|
||||
let result = normalize_pasted_path(input).expect("should add leading slash for Users/");
|
||||
assert_eq!(result, PathBuf::from("/Users/alice/Pictures/example.png"));
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[test]
|
||||
fn normalize_dragged_finder_volumes_path() {
|
||||
let input = "'Volumes/ExternalDrive/photos/image.jpg'";
|
||||
let result = normalize_pasted_path(input).expect("should add leading slash for Volumes/");
|
||||
assert_eq!(
|
||||
result,
|
||||
PathBuf::from("/Volumes/ExternalDrive/photos/image.jpg")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pasted_image_format_png_jpeg_unknown() {
|
||||
assert_eq!(
|
||||
|
||||
Reference in New Issue
Block a user