Drag / Copy image files into chat

This commit is contained in:
Daniel Edrisian
2025-08-21 14:49:10 -07:00
parent 7e54c4885f
commit 2af06c2e6d
5 changed files with 70 additions and 3 deletions

8
codex-rs/Cargo.lock generated
View File

@@ -976,6 +976,7 @@ dependencies = [
"reqwest",
"serde",
"serde_json",
"shell-words",
"shlex",
"strum 0.27.2",
"strum_macros 0.27.2",
@@ -989,6 +990,7 @@ dependencies = [
"tui-markdown",
"unicode-segmentation",
"unicode-width 0.1.14",
"url",
"uuid",
"vt100",
]
@@ -4397,6 +4399,12 @@ dependencies = [
"lazy_static",
]
[[package]]
name = "shell-words"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
[[package]]
name = "shlex"
version = "1.3.0"

View File

@@ -34,10 +34,10 @@ codex-common = { path = "../common", features = [
"sandbox_summary",
] }
codex-core = { path = "../core" }
codex-protocol = { path = "../protocol" }
codex-file-search = { path = "../file-search" }
codex-login = { path = "../login" }
codex-ollama = { path = "../ollama" }
codex-protocol = { path = "../protocol" }
color-eyre = "0.6.3"
crossterm = { version = "0.28.1", features = ["bracketed-paste"] }
diffy = "0.4.2"
@@ -46,9 +46,10 @@ image = { version = "^0.25.6", default-features = false, features = [
"png",
] }
lazy_static = "1"
once_cell = "1"
mcp-types = { path = "../mcp-types" }
once_cell = "1"
path-clean = "1.0.1"
rand = "0.9"
ratatui = { version = "0.29.0", features = [
"scrolling-regions",
"unstable-rendered-line-info",
@@ -59,6 +60,7 @@ regex-lite = "0.1"
reqwest = { version = "0.12", features = ["json"] }
serde = { version = "1", features = ["derive"] }
serde_json = { version = "1", features = ["preserve_order"] }
shell-words = "1.1"
shlex = "1.3.0"
strum = "0.27.2"
strum_macros = "0.27.2"
@@ -78,8 +80,8 @@ tui-input = "0.14.0"
tui-markdown = "0.3.3"
unicode-segmentation = "1.12.0"
unicode-width = "0.1"
url = "2"
uuid = "1"
rand = "0.9"
[target.'cfg(unix)'.dependencies]
libc = "0.2"

View File

@@ -26,6 +26,8 @@ use super::file_search_popup::FileSearchPopup;
use crate::app_event::AppEvent;
use crate::app_event_sender::AppEventSender;
use crate::bottom_pane::string_utils::get_img_format_label;
use crate::bottom_pane::string_utils::normalize_pasted_path;
use crate::bottom_pane::textarea::TextArea;
use crate::bottom_pane::textarea::TextAreaState;
use codex_file_search::FileMatch;
@@ -189,6 +191,8 @@ impl ChatComposer {
let placeholder = format!("[Pasted Content {char_count} chars]");
self.textarea.insert_element(&placeholder);
self.pending_pastes.push((placeholder, pasted));
} else if self.handle_paste_image_paths(pasted.clone()) {
self.textarea.insert_str(" ");
} else {
self.textarea.insert_str(&pasted);
}
@@ -197,6 +201,25 @@ impl ChatComposer {
true
}
pub fn handle_paste_image_paths(&mut self, pasted: String) -> bool {
tracing::info!("pasted: {pasted}");
let Some(path_buf) = normalize_pasted_path(&pasted) else {
return false;
};
match image::image_dimensions(&path_buf) {
Ok((w, h)) => {
tracing::info!("OK: {pasted}");
let format_label = get_img_format_label(path_buf.clone());
self.attach_image(path_buf, w, h, &format_label)
}
Err(err) => {
tracing::info!("ERR: {err}");
false
}
}
}
pub fn attach_image(
&mut self,
path: std::path::PathBuf,

View File

@@ -21,6 +21,7 @@ mod popup_consts;
mod scroll_state;
mod selection_popup_common;
mod status_indicator_view;
mod string_utils;
mod textarea;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]

View File

@@ -0,0 +1,33 @@
use std::path::PathBuf;
pub fn normalize_pasted_path(pasted: &str) -> Option<PathBuf> {
// file:// URL → filesystem path
if let Ok(url) = url::Url::parse(pasted) {
if url.scheme() == "file" {
return url.to_file_path().ok();
}
}
// shell-escaped single path → unescaped
if let Ok(mut parts) = shell_words::split(pasted) {
if parts.len() == 1 {
return Some(PathBuf::from(parts.remove(0)));
}
}
// simple quoted path fallback
Some(PathBuf::from(pasted.trim_matches('"')))
}
pub fn get_img_format_label(path: PathBuf) -> String {
match path
.extension()
.and_then(|e| e.to_str())
.map(|s| s.to_ascii_lowercase())
{
Some(ext) if ext == "png" => "PNG",
Some(ext) if ext == "jpg" || ext == "jpeg" => "JPEG",
_ => "IMG",
}
.into()
}