Detailed changes
@@ -19,7 +19,9 @@ pub enum MentionUri {
File {
abs_path: PathBuf,
},
- PastedImage,
+ PastedImage {
+ name: String,
+ },
Directory {
abs_path: PathBuf,
},
@@ -155,7 +157,9 @@ impl MentionUri {
include_warnings,
})
} else if path.starts_with("/agent/pasted-image") {
- Ok(Self::PastedImage)
+ let name =
+ single_query_param(&url, "name")?.unwrap_or_else(|| "Image".to_string());
+ Ok(Self::PastedImage { name })
} else if path.starts_with("/agent/untitled-buffer") {
let fragment = url
.fragment()
@@ -227,7 +231,7 @@ impl MentionUri {
.unwrap_or_default()
.to_string_lossy()
.into_owned(),
- MentionUri::PastedImage => "Image".to_string(),
+ MentionUri::PastedImage { name } => name.clone(),
MentionUri::Symbol { name, .. } => name.clone(),
MentionUri::Thread { name, .. } => name.clone(),
MentionUri::Rule { name, .. } => name.clone(),
@@ -296,7 +300,7 @@ impl MentionUri {
MentionUri::File { abs_path } => {
FileIcons::get_icon(abs_path, cx).unwrap_or_else(|| IconName::File.path().into())
}
- MentionUri::PastedImage => IconName::Image.path().into(),
+ MentionUri::PastedImage { .. } => IconName::Image.path().into(),
MentionUri::Directory { abs_path } => FileIcons::get_folder_icon(false, abs_path, cx)
.unwrap_or_else(|| IconName::Folder.path().into()),
MentionUri::Symbol { .. } => IconName::Code.path().into(),
@@ -322,10 +326,18 @@ impl MentionUri {
url.set_path(&abs_path.to_string_lossy());
url
}
- MentionUri::PastedImage => Url::parse("zed:///agent/pasted-image").unwrap(),
+ MentionUri::PastedImage { name } => {
+ let mut url = Url::parse("zed:///agent/pasted-image").unwrap();
+ url.query_pairs_mut().append_pair("name", name);
+ url
+ }
MentionUri::Directory { abs_path } => {
let mut url = Url::parse("file:///").unwrap();
- url.set_path(&abs_path.to_string_lossy());
+ let mut path = abs_path.to_string_lossy().into_owned();
+ if !path.ends_with('/') && !path.ends_with('\\') {
+ path.push('/');
+ }
+ url.set_path(&path);
url
}
MentionUri::Symbol {
@@ -490,6 +502,21 @@ mod tests {
assert_eq!(uri.to_uri().to_string(), expected);
}
+ #[test]
+ fn test_directory_uri_round_trip_without_trailing_slash() {
+ let uri = MentionUri::Directory {
+ abs_path: PathBuf::from(path!("/path/to/dir")),
+ };
+ let serialized = uri.to_uri().to_string();
+ assert!(serialized.ends_with('/'), "directory URI must end with /");
+ let parsed = MentionUri::parse(&serialized, PathStyle::local()).unwrap();
+ assert!(
+ matches!(parsed, MentionUri::Directory { .. }),
+ "expected Directory variant, got {:?}",
+ parsed
+ );
+ }
+
#[test]
fn test_parse_symbol_uri() {
let symbol_uri = uri!("file:///path/to/file.rs?symbol=MySymbol#L10:20");
@@ -253,7 +253,7 @@ impl UserMessage {
)
.ok();
}
- MentionUri::PastedImage => {
+ MentionUri::PastedImage { .. } => {
debug_panic!("pasted image URI should not be used in mention content")
}
MentionUri::Directory { .. } => {
@@ -8819,7 +8819,7 @@ pub(crate) fn open_link(
.open_path(path, None, true, window, cx)
.detach_and_log_err(cx);
}
- MentionUri::PastedImage => {}
+ MentionUri::PastedImage { .. } => {}
MentionUri::Directory { abs_path } => {
let project = workspace.project();
let Some(entry_id) = project.update(cx, |project, cx| {
@@ -154,7 +154,7 @@ impl MentionSet {
MentionUri::Selection { abs_path: None, .. } => Task::ready(Err(anyhow!(
"Untitled buffer selection mentions are not supported for paste"
))),
- MentionUri::PastedImage
+ MentionUri::PastedImage { .. }
| MentionUri::TerminalSelection { .. }
| MentionUri::MergeConflict { .. } => {
Task::ready(Err(anyhow!("Unsupported mention URI type for paste")))
@@ -283,7 +283,7 @@ impl MentionSet {
include_errors,
include_warnings,
} => self.confirm_mention_for_diagnostics(include_errors, include_warnings, cx),
- MentionUri::PastedImage => {
+ MentionUri::PastedImage { .. } => {
debug_panic!("pasted image URI should not be included in completions");
Task::ready(Err(anyhow!(
"pasted imaged URI should not be included in completions"
@@ -739,9 +739,11 @@ pub(crate) async fn insert_images_as_context(
return;
}
- let replacement_text = MentionUri::PastedImage.as_link().to_string();
-
for (image, name) in images {
+ let mention_uri = MentionUri::PastedImage {
+ name: name.to_string(),
+ };
+ let replacement_text = mention_uri.as_link().to_string();
let Some((text_anchor, multibuffer_anchor)) = editor
.update_in(cx, |editor, window, cx| {
let snapshot = editor.snapshot(window, cx);
@@ -804,7 +806,13 @@ pub(crate) async fn insert_images_as_context(
.shared();
mention_set.update(cx, |mention_set, _cx| {
- mention_set.insert_mention(crease_id, MentionUri::PastedImage, task.clone())
+ mention_set.insert_mention(
+ crease_id,
+ MentionUri::PastedImage {
+ name: name.to_string(),
+ },
+ task.clone(),
+ )
});
if task
@@ -873,7 +881,7 @@ pub(crate) fn paste_images_as_context(
Some(window.spawn(cx, async move |mut cx| {
use itertools::Itertools;
- let default_name: SharedString = MentionUri::PastedImage.name().into();
+ let default_name: SharedString = "Image".into();
let (mut images, paths): (Vec<(gpui::Image, SharedString)>, Vec<_>) = clipboard
.into_entries()
.filter_map(|entry| match entry {
@@ -261,7 +261,7 @@ async fn resolve_pasted_context_items(
) -> (Vec<ResolvedPastedContextItem>, Vec<Entity<Worktree>>) {
let mut items = Vec::new();
let mut added_worktrees = Vec::new();
- let default_image_name: SharedString = MentionUri::PastedImage.name().into();
+ let default_image_name: SharedString = "Image".into();
for entry in entries {
match entry {
@@ -812,7 +812,9 @@ impl MessageEditor {
)
.uri(match uri {
MentionUri::File { .. } => Some(uri.to_uri().to_string()),
- MentionUri::PastedImage => None,
+ MentionUri::PastedImage { .. } => {
+ Some(uri.to_uri().to_string())
+ }
other => {
debug_panic!(
"unexpected mention uri for image: {:?}",
@@ -1638,7 +1640,9 @@ impl MessageEditor {
let mention_uri = if let Some(uri) = uri {
MentionUri::parse(&uri, path_style)
} else {
- Ok(MentionUri::PastedImage)
+ Ok(MentionUri::PastedImage {
+ name: "Image".to_string(),
+ })
};
let Some(mention_uri) = mention_uri.log_err() else {
continue;
@@ -4074,6 +4078,11 @@ mod tests {
&mut cx,
);
+ let image_name = temporary_image_path
+ .file_name()
+ .and_then(|n| n.to_str())
+ .unwrap_or("Image")
+ .to_string();
std::fs::remove_file(&temporary_image_path).expect("remove temp png");
let expected_file_uri = MentionUri::File {
@@ -4081,12 +4090,16 @@ mod tests {
}
.to_uri()
.to_string();
- let expected_image_uri = MentionUri::PastedImage.to_uri().to_string();
+ let expected_image_uri = MentionUri::PastedImage {
+ name: image_name.clone(),
+ }
+ .to_uri()
+ .to_string();
editor.update(&mut cx, |editor, cx| {
assert_eq!(
editor.text(cx),
- format!("[@Image]({expected_image_uri}) [@file.txt]({expected_file_uri}) ")
+ format!("[@{image_name}]({expected_image_uri}) [@file.txt]({expected_file_uri}) ")
);
});
@@ -4094,7 +4107,7 @@ mod tests {
assert_eq!(contents.len(), 2);
assert!(contents.iter().any(|(uri, mention)| {
- *uri == MentionUri::PastedImage && matches!(mention, Mention::Image(_))
+ matches!(uri, MentionUri::PastedImage { .. }) && matches!(mention, Mention::Image(_))
}));
assert!(contents.iter().any(|(uri, mention)| {
*uri == MentionUri::File {
@@ -184,7 +184,7 @@ fn open_mention_uri(
MentionUri::Fetch { url } => {
cx.open_url(url.as_str());
}
- MentionUri::PastedImage
+ MentionUri::PastedImage { .. }
| MentionUri::Selection { abs_path: None, .. }
| MentionUri::Diagnostics { .. }
| MentionUri::TerminalSelection { .. }