Allow editing Agent2 messages (#36155)

Release Notes:

- N/A

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Agus Zubiaga <agus@zed.dev>
This commit is contained in:
Conrad Irwin 2025-08-14 07:39:33 -06:00 committed by GitHub
parent ffac8c5128
commit e5402d5464
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 956 additions and 621 deletions

View file

@ -23,6 +23,7 @@ anyhow.workspace = true
buffer_diff.workspace = true
collections.workspace = true
editor.workspace = true
file_icons.workspace = true
futures.workspace = true
gpui.workspace = true
itertools.workspace = true

View file

@ -32,6 +32,7 @@ use util::ResultExt;
pub struct UserMessage {
pub id: Option<UserMessageId>,
pub content: ContentBlock,
pub chunks: Vec<acp::ContentBlock>,
pub checkpoint: Option<GitStoreCheckpoint>,
}
@ -804,18 +805,25 @@ impl AcpThread {
let entries_len = self.entries.len();
if let Some(last_entry) = self.entries.last_mut()
&& let AgentThreadEntry::UserMessage(UserMessage { id, content, .. }) = last_entry
&& let AgentThreadEntry::UserMessage(UserMessage {
id,
content,
chunks,
..
}) = last_entry
{
*id = message_id.or(id.take());
content.append(chunk, &language_registry, cx);
content.append(chunk.clone(), &language_registry, cx);
chunks.push(chunk);
let idx = entries_len - 1;
cx.emit(AcpThreadEvent::EntryUpdated(idx));
} else {
let content = ContentBlock::new(chunk, &language_registry, cx);
let content = ContentBlock::new(chunk.clone(), &language_registry, cx);
self.push_entry(
AgentThreadEntry::UserMessage(UserMessage {
id: message_id,
content,
chunks: vec![chunk],
checkpoint: None,
}),
cx,
@ -1150,6 +1158,7 @@ impl AcpThread {
AgentThreadEntry::UserMessage(UserMessage {
id: message_id.clone(),
content: block,
chunks: message.clone(),
checkpoint: None,
}),
cx,

View file

@ -1,16 +1,21 @@
use agent::ThreadId;
use anyhow::{Context as _, Result, bail};
use file_icons::FileIcons;
use prompt_store::{PromptId, UserPromptId};
use std::{
fmt,
ops::Range,
path::{Path, PathBuf},
};
use ui::{App, IconName, SharedString};
use url::Url;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum MentionUri {
File(PathBuf),
File {
abs_path: PathBuf,
is_directory: bool,
},
Symbol {
path: PathBuf,
name: String,
@ -75,8 +80,12 @@ impl MentionUri {
} else {
let file_path =
PathBuf::from(format!("{}{}", url.host_str().unwrap_or(""), path));
let is_directory = input.ends_with("/");
Ok(Self::File(file_path))
Ok(Self::File {
abs_path: file_path,
is_directory,
})
}
}
"zed" => {
@ -108,9 +117,9 @@ impl MentionUri {
}
}
fn name(&self) -> String {
pub fn name(&self) -> String {
match self {
MentionUri::File(path) => path
MentionUri::File { abs_path, .. } => abs_path
.file_name()
.unwrap_or_default()
.to_string_lossy()
@ -126,15 +135,45 @@ impl MentionUri {
}
}
pub fn icon_path(&self, cx: &mut App) -> SharedString {
match self {
MentionUri::File {
abs_path,
is_directory,
} => {
if *is_directory {
FileIcons::get_folder_icon(false, cx)
.unwrap_or_else(|| IconName::Folder.path().into())
} else {
FileIcons::get_icon(&abs_path, cx)
.unwrap_or_else(|| IconName::File.path().into())
}
}
MentionUri::Symbol { .. } => IconName::Code.path().into(),
MentionUri::Thread { .. } => IconName::Thread.path().into(),
MentionUri::TextThread { .. } => IconName::Thread.path().into(),
MentionUri::Rule { .. } => IconName::Reader.path().into(),
MentionUri::Selection { .. } => IconName::Reader.path().into(),
MentionUri::Fetch { .. } => IconName::ToolWeb.path().into(),
}
}
pub fn as_link<'a>(&'a self) -> MentionLink<'a> {
MentionLink(self)
}
pub fn to_uri(&self) -> Url {
match self {
MentionUri::File(path) => {
MentionUri::File {
abs_path,
is_directory,
} => {
let mut url = Url::parse("file:///").unwrap();
url.set_path(&path.to_string_lossy());
let mut path = abs_path.to_string_lossy().to_string();
if *is_directory && !path.ends_with("/") {
path.push_str("/");
}
url.set_path(&path);
url
}
MentionUri::Symbol {
@ -226,12 +265,53 @@ mod tests {
let file_uri = "file:///path/to/file.rs";
let parsed = MentionUri::parse(file_uri).unwrap();
match &parsed {
MentionUri::File(path) => assert_eq!(path.to_str().unwrap(), "/path/to/file.rs"),
MentionUri::File {
abs_path,
is_directory,
} => {
assert_eq!(abs_path.to_str().unwrap(), "/path/to/file.rs");
assert!(!is_directory);
}
_ => panic!("Expected File variant"),
}
assert_eq!(parsed.to_uri().to_string(), file_uri);
}
#[test]
fn test_parse_directory_uri() {
let file_uri = "file:///path/to/dir/";
let parsed = MentionUri::parse(file_uri).unwrap();
match &parsed {
MentionUri::File {
abs_path,
is_directory,
} => {
assert_eq!(abs_path.to_str().unwrap(), "/path/to/dir/");
assert!(is_directory);
}
_ => panic!("Expected File variant"),
}
assert_eq!(parsed.to_uri().to_string(), file_uri);
}
#[test]
fn test_to_directory_uri_with_slash() {
let uri = MentionUri::File {
abs_path: PathBuf::from("/path/to/dir/"),
is_directory: true,
};
assert_eq!(uri.to_uri().to_string(), "file:///path/to/dir/");
}
#[test]
fn test_to_directory_uri_without_slash() {
let uri = MentionUri::File {
abs_path: PathBuf::from("/path/to/dir"),
is_directory: true,
};
assert_eq!(uri.to_uri().to_string(), "file:///path/to/dir/");
}
#[test]
fn test_parse_symbol_uri() {
let symbol_uri = "file:///path/to/file.rs?symbol=MySymbol#L10:20";