agent2: Support directories in @file mentions (#36416)
Release Notes: - N/A
This commit is contained in:
parent
821e97a392
commit
7bcea7dc2c
5 changed files with 325 additions and 168 deletions
|
@ -15,7 +15,9 @@ use url::Url;
|
|||
pub enum MentionUri {
|
||||
File {
|
||||
abs_path: PathBuf,
|
||||
is_directory: bool,
|
||||
},
|
||||
Directory {
|
||||
abs_path: PathBuf,
|
||||
},
|
||||
Symbol {
|
||||
path: PathBuf,
|
||||
|
@ -79,14 +81,14 @@ impl MentionUri {
|
|||
})
|
||||
}
|
||||
} else {
|
||||
let file_path =
|
||||
let abs_path =
|
||||
PathBuf::from(format!("{}{}", url.host_str().unwrap_or(""), path));
|
||||
let is_directory = input.ends_with("/");
|
||||
|
||||
Ok(Self::File {
|
||||
abs_path: file_path,
|
||||
is_directory,
|
||||
})
|
||||
if input.ends_with("/") {
|
||||
Ok(Self::Directory { abs_path })
|
||||
} else {
|
||||
Ok(Self::File { abs_path })
|
||||
}
|
||||
}
|
||||
}
|
||||
"zed" => {
|
||||
|
@ -120,7 +122,7 @@ impl MentionUri {
|
|||
|
||||
pub fn name(&self) -> String {
|
||||
match self {
|
||||
MentionUri::File { abs_path, .. } => abs_path
|
||||
MentionUri::File { abs_path, .. } | MentionUri::Directory { abs_path, .. } => abs_path
|
||||
.file_name()
|
||||
.unwrap_or_default()
|
||||
.to_string_lossy()
|
||||
|
@ -138,18 +140,11 @@ 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::File { abs_path } => {
|
||||
FileIcons::get_icon(abs_path, cx).unwrap_or_else(|| IconName::File.path().into())
|
||||
}
|
||||
MentionUri::Directory { .. } => FileIcons::get_folder_icon(false, cx)
|
||||
.unwrap_or_else(|| IconName::Folder.path().into()),
|
||||
MentionUri::Symbol { .. } => IconName::Code.path().into(),
|
||||
MentionUri::Thread { .. } => IconName::Thread.path().into(),
|
||||
MentionUri::TextThread { .. } => IconName::Thread.path().into(),
|
||||
|
@ -165,13 +160,16 @@ impl MentionUri {
|
|||
|
||||
pub fn to_uri(&self) -> Url {
|
||||
match self {
|
||||
MentionUri::File {
|
||||
abs_path,
|
||||
is_directory,
|
||||
} => {
|
||||
MentionUri::File { abs_path } => {
|
||||
let mut url = Url::parse("file:///").unwrap();
|
||||
let path = abs_path.to_string_lossy();
|
||||
url.set_path(&path);
|
||||
url
|
||||
}
|
||||
MentionUri::Directory { abs_path } => {
|
||||
let mut url = Url::parse("file:///").unwrap();
|
||||
let mut path = abs_path.to_string_lossy().to_string();
|
||||
if *is_directory && !path.ends_with("/") {
|
||||
if !path.ends_with("/") {
|
||||
path.push_str("/");
|
||||
}
|
||||
url.set_path(&path);
|
||||
|
@ -274,12 +272,8 @@ mod tests {
|
|||
let file_uri = "file:///path/to/file.rs";
|
||||
let parsed = MentionUri::parse(file_uri).unwrap();
|
||||
match &parsed {
|
||||
MentionUri::File {
|
||||
abs_path,
|
||||
is_directory,
|
||||
} => {
|
||||
MentionUri::File { abs_path } => {
|
||||
assert_eq!(abs_path.to_str().unwrap(), "/path/to/file.rs");
|
||||
assert!(!is_directory);
|
||||
}
|
||||
_ => panic!("Expected File variant"),
|
||||
}
|
||||
|
@ -291,32 +285,26 @@ mod tests {
|
|||
let file_uri = "file:///path/to/dir/";
|
||||
let parsed = MentionUri::parse(file_uri).unwrap();
|
||||
match &parsed {
|
||||
MentionUri::File {
|
||||
abs_path,
|
||||
is_directory,
|
||||
} => {
|
||||
MentionUri::Directory { abs_path } => {
|
||||
assert_eq!(abs_path.to_str().unwrap(), "/path/to/dir/");
|
||||
assert!(is_directory);
|
||||
}
|
||||
_ => panic!("Expected File variant"),
|
||||
_ => panic!("Expected Directory variant"),
|
||||
}
|
||||
assert_eq!(parsed.to_uri().to_string(), file_uri);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_directory_uri_with_slash() {
|
||||
let uri = MentionUri::File {
|
||||
let uri = MentionUri::Directory {
|
||||
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 {
|
||||
let uri = MentionUri::Directory {
|
||||
abs_path: PathBuf::from("/path/to/dir"),
|
||||
is_directory: true,
|
||||
};
|
||||
assert_eq!(uri.to_uri().to_string(), "file:///path/to/dir/");
|
||||
}
|
||||
|
|
|
@ -146,6 +146,7 @@ impl UserMessage {
|
|||
They are up-to-date and don't need to be re-read.\n\n";
|
||||
|
||||
const OPEN_FILES_TAG: &str = "<files>";
|
||||
const OPEN_DIRECTORIES_TAG: &str = "<directories>";
|
||||
const OPEN_SYMBOLS_TAG: &str = "<symbols>";
|
||||
const OPEN_THREADS_TAG: &str = "<threads>";
|
||||
const OPEN_FETCH_TAG: &str = "<fetched_urls>";
|
||||
|
@ -153,6 +154,7 @@ impl UserMessage {
|
|||
"<rules>\nThe user has specified the following rules that should be applied:\n";
|
||||
|
||||
let mut file_context = OPEN_FILES_TAG.to_string();
|
||||
let mut directory_context = OPEN_DIRECTORIES_TAG.to_string();
|
||||
let mut symbol_context = OPEN_SYMBOLS_TAG.to_string();
|
||||
let mut thread_context = OPEN_THREADS_TAG.to_string();
|
||||
let mut fetch_context = OPEN_FETCH_TAG.to_string();
|
||||
|
@ -168,7 +170,7 @@ impl UserMessage {
|
|||
}
|
||||
UserMessageContent::Mention { uri, content } => {
|
||||
match uri {
|
||||
MentionUri::File { abs_path, .. } => {
|
||||
MentionUri::File { abs_path } => {
|
||||
write!(
|
||||
&mut symbol_context,
|
||||
"\n{}",
|
||||
|
@ -179,6 +181,9 @@ impl UserMessage {
|
|||
)
|
||||
.ok();
|
||||
}
|
||||
MentionUri::Directory { .. } => {
|
||||
write!(&mut directory_context, "\n{}\n", content).ok();
|
||||
}
|
||||
MentionUri::Symbol {
|
||||
path, line_range, ..
|
||||
}
|
||||
|
@ -233,6 +238,13 @@ impl UserMessage {
|
|||
.push(language_model::MessageContent::Text(file_context));
|
||||
}
|
||||
|
||||
if directory_context.len() > OPEN_DIRECTORIES_TAG.len() {
|
||||
directory_context.push_str("</directories>\n");
|
||||
message
|
||||
.content
|
||||
.push(language_model::MessageContent::Text(directory_context));
|
||||
}
|
||||
|
||||
if symbol_context.len() > OPEN_SYMBOLS_TAG.len() {
|
||||
symbol_context.push_str("</symbols>\n");
|
||||
message
|
||||
|
|
|
@ -445,19 +445,20 @@ impl ContextPickerCompletionProvider {
|
|||
|
||||
let abs_path = project.read(cx).absolute_path(&project_path, cx)?;
|
||||
|
||||
let file_uri = MentionUri::File {
|
||||
abs_path,
|
||||
is_directory,
|
||||
let uri = if is_directory {
|
||||
MentionUri::Directory { abs_path }
|
||||
} else {
|
||||
MentionUri::File { abs_path }
|
||||
};
|
||||
|
||||
let crease_icon_path = file_uri.icon_path(cx);
|
||||
let crease_icon_path = uri.icon_path(cx);
|
||||
let completion_icon_path = if is_recent {
|
||||
IconName::HistoryRerun.path().into()
|
||||
} else {
|
||||
crease_icon_path.clone()
|
||||
};
|
||||
|
||||
let new_text = format!("{} ", file_uri.as_link());
|
||||
let new_text = format!("{} ", uri.as_link());
|
||||
let new_text_len = new_text.len();
|
||||
Some(Completion {
|
||||
replace_range: source_range.clone(),
|
||||
|
@ -472,7 +473,7 @@ impl ContextPickerCompletionProvider {
|
|||
source_range.start,
|
||||
new_text_len - 1,
|
||||
message_editor,
|
||||
file_uri,
|
||||
uri,
|
||||
)),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ use acp_thread::{MentionUri, selection_name};
|
|||
use agent::{TextThreadStore, ThreadId, ThreadStore};
|
||||
use agent_client_protocol as acp;
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_slash_commands::codeblock_fence_for_path;
|
||||
use collections::{HashMap, HashSet};
|
||||
use editor::{
|
||||
Anchor, AnchorRangeExt, ContextMenuOptions, ContextMenuPlacement, Editor, EditorElement,
|
||||
|
@ -15,7 +16,7 @@ use editor::{
|
|||
};
|
||||
use futures::{
|
||||
FutureExt as _, TryFutureExt as _,
|
||||
future::{Shared, try_join_all},
|
||||
future::{Shared, join_all, try_join_all},
|
||||
};
|
||||
use gpui::{
|
||||
AppContext, ClipboardEntry, Context, Entity, EventEmitter, FocusHandle, Focusable, Image,
|
||||
|
@ -23,12 +24,12 @@ use gpui::{
|
|||
};
|
||||
use language::{Buffer, Language};
|
||||
use language_model::LanguageModelImage;
|
||||
use project::{CompletionIntent, Project};
|
||||
use project::{CompletionIntent, Project, ProjectPath, Worktree};
|
||||
use rope::Point;
|
||||
use settings::Settings;
|
||||
use std::{
|
||||
ffi::OsStr,
|
||||
fmt::Write,
|
||||
fmt::{Display, Write},
|
||||
ops::Range,
|
||||
path::{Path, PathBuf},
|
||||
rc::Rc,
|
||||
|
@ -245,6 +246,9 @@ impl MessageEditor {
|
|||
MentionUri::Fetch { url } => {
|
||||
self.confirm_mention_for_fetch(crease_id, anchor, url, window, cx);
|
||||
}
|
||||
MentionUri::Directory { abs_path } => {
|
||||
self.confirm_mention_for_directory(crease_id, anchor, abs_path, window, cx);
|
||||
}
|
||||
MentionUri::Thread { id, name } => {
|
||||
self.confirm_mention_for_thread(crease_id, anchor, id, name, window, cx);
|
||||
}
|
||||
|
@ -260,6 +264,124 @@ impl MessageEditor {
|
|||
}
|
||||
}
|
||||
|
||||
fn confirm_mention_for_directory(
|
||||
&mut self,
|
||||
crease_id: CreaseId,
|
||||
anchor: Anchor,
|
||||
abs_path: PathBuf,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
fn collect_files_in_path(worktree: &Worktree, path: &Path) -> Vec<(Arc<Path>, PathBuf)> {
|
||||
let mut files = Vec::new();
|
||||
|
||||
for entry in worktree.child_entries(path) {
|
||||
if entry.is_dir() {
|
||||
files.extend(collect_files_in_path(worktree, &entry.path));
|
||||
} else if entry.is_file() {
|
||||
files.push((entry.path.clone(), worktree.full_path(&entry.path)));
|
||||
}
|
||||
}
|
||||
|
||||
files
|
||||
}
|
||||
|
||||
let uri = MentionUri::Directory {
|
||||
abs_path: abs_path.clone(),
|
||||
};
|
||||
let Some(project_path) = self
|
||||
.project
|
||||
.read(cx)
|
||||
.project_path_for_absolute_path(&abs_path, cx)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some(entry) = self.project.read(cx).entry_for_path(&project_path, cx) else {
|
||||
return;
|
||||
};
|
||||
let Some(worktree) = self.project.read(cx).worktree_for_entry(entry.id, cx) else {
|
||||
return;
|
||||
};
|
||||
let project = self.project.clone();
|
||||
let task = cx.spawn(async move |_, cx| {
|
||||
let directory_path = entry.path.clone();
|
||||
|
||||
let worktree_id = worktree.read_with(cx, |worktree, _| worktree.id())?;
|
||||
let file_paths = worktree.read_with(cx, |worktree, _cx| {
|
||||
collect_files_in_path(worktree, &directory_path)
|
||||
})?;
|
||||
let descendants_future = cx.update(|cx| {
|
||||
join_all(file_paths.into_iter().map(|(worktree_path, full_path)| {
|
||||
let rel_path = worktree_path
|
||||
.strip_prefix(&directory_path)
|
||||
.log_err()
|
||||
.map_or_else(|| worktree_path.clone(), |rel_path| rel_path.into());
|
||||
|
||||
let open_task = project.update(cx, |project, cx| {
|
||||
project.buffer_store().update(cx, |buffer_store, cx| {
|
||||
let project_path = ProjectPath {
|
||||
worktree_id,
|
||||
path: worktree_path,
|
||||
};
|
||||
buffer_store.open_buffer(project_path, cx)
|
||||
})
|
||||
});
|
||||
|
||||
// TODO: report load errors instead of just logging
|
||||
let rope_task = cx.spawn(async move |cx| {
|
||||
let buffer = open_task.await.log_err()?;
|
||||
let rope = buffer
|
||||
.read_with(cx, |buffer, _cx| buffer.as_rope().clone())
|
||||
.log_err()?;
|
||||
Some(rope)
|
||||
});
|
||||
|
||||
cx.background_spawn(async move {
|
||||
let rope = rope_task.await?;
|
||||
Some((rel_path, full_path, rope.to_string()))
|
||||
})
|
||||
}))
|
||||
})?;
|
||||
|
||||
let contents = cx
|
||||
.background_spawn(async move {
|
||||
let contents = descendants_future.await.into_iter().flatten();
|
||||
contents.collect()
|
||||
})
|
||||
.await;
|
||||
anyhow::Ok(contents)
|
||||
});
|
||||
let task = cx
|
||||
.spawn(async move |_, _| {
|
||||
task.await
|
||||
.map(|contents| DirectoryContents(contents).to_string())
|
||||
.map_err(|e| e.to_string())
|
||||
})
|
||||
.shared();
|
||||
|
||||
self.mention_set.directories.insert(abs_path, task.clone());
|
||||
|
||||
let editor = self.editor.clone();
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
if task.await.notify_async_err(cx).is_some() {
|
||||
this.update(cx, |this, _| {
|
||||
this.mention_set.insert_uri(crease_id, uri);
|
||||
})
|
||||
.ok();
|
||||
} else {
|
||||
editor
|
||||
.update(cx, |editor, cx| {
|
||||
editor.display_map.update(cx, |display_map, cx| {
|
||||
display_map.unfold_intersecting(vec![anchor..anchor], true, cx);
|
||||
});
|
||||
editor.remove_creases([crease_id], cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn confirm_mention_for_fetch(
|
||||
&mut self,
|
||||
crease_id: CreaseId,
|
||||
|
@ -361,6 +483,104 @@ impl MessageEditor {
|
|||
}
|
||||
}
|
||||
|
||||
fn confirm_mention_for_thread(
|
||||
&mut self,
|
||||
crease_id: CreaseId,
|
||||
anchor: Anchor,
|
||||
id: ThreadId,
|
||||
name: String,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let uri = MentionUri::Thread {
|
||||
id: id.clone(),
|
||||
name,
|
||||
};
|
||||
let open_task = self.thread_store.update(cx, |thread_store, cx| {
|
||||
thread_store.open_thread(&id, window, cx)
|
||||
});
|
||||
let task = cx
|
||||
.spawn(async move |_, cx| {
|
||||
let thread = open_task.await.map_err(|e| e.to_string())?;
|
||||
let content = thread
|
||||
.read_with(cx, |thread, _cx| thread.latest_detailed_summary_or_text())
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(content)
|
||||
})
|
||||
.shared();
|
||||
|
||||
self.mention_set.insert_thread(id, task.clone());
|
||||
|
||||
let editor = self.editor.clone();
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
if task.await.notify_async_err(cx).is_some() {
|
||||
this.update(cx, |this, _| {
|
||||
this.mention_set.insert_uri(crease_id, uri);
|
||||
})
|
||||
.ok();
|
||||
} else {
|
||||
editor
|
||||
.update(cx, |editor, cx| {
|
||||
editor.display_map.update(cx, |display_map, cx| {
|
||||
display_map.unfold_intersecting(vec![anchor..anchor], true, cx);
|
||||
});
|
||||
editor.remove_creases([crease_id], cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn confirm_mention_for_text_thread(
|
||||
&mut self,
|
||||
crease_id: CreaseId,
|
||||
anchor: Anchor,
|
||||
path: PathBuf,
|
||||
name: String,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let uri = MentionUri::TextThread {
|
||||
path: path.clone(),
|
||||
name,
|
||||
};
|
||||
let context = self.text_thread_store.update(cx, |text_thread_store, cx| {
|
||||
text_thread_store.open_local_context(path.as_path().into(), cx)
|
||||
});
|
||||
let task = cx
|
||||
.spawn(async move |_, cx| {
|
||||
let context = context.await.map_err(|e| e.to_string())?;
|
||||
let xml = context
|
||||
.update(cx, |context, cx| context.to_xml(cx))
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(xml)
|
||||
})
|
||||
.shared();
|
||||
|
||||
self.mention_set.insert_text_thread(path, task.clone());
|
||||
|
||||
let editor = self.editor.clone();
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
if task.await.notify_async_err(cx).is_some() {
|
||||
this.update(cx, |this, _| {
|
||||
this.mention_set.insert_uri(crease_id, uri);
|
||||
})
|
||||
.ok();
|
||||
} else {
|
||||
editor
|
||||
.update(cx, |editor, cx| {
|
||||
editor.display_map.update(cx, |display_map, cx| {
|
||||
display_map.unfold_intersecting(vec![anchor..anchor], true, cx);
|
||||
});
|
||||
editor.remove_creases([crease_id], cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub fn contents(
|
||||
&self,
|
||||
window: &mut Window,
|
||||
|
@ -613,13 +833,8 @@ impl MessageEditor {
|
|||
if task.await.notify_async_err(cx).is_some() {
|
||||
if let Some(abs_path) = abs_path.clone() {
|
||||
this.update(cx, |this, _cx| {
|
||||
this.mention_set.insert_uri(
|
||||
crease_id,
|
||||
MentionUri::File {
|
||||
abs_path,
|
||||
is_directory: false,
|
||||
},
|
||||
);
|
||||
this.mention_set
|
||||
.insert_uri(crease_id, MentionUri::File { abs_path });
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
@ -637,104 +852,6 @@ impl MessageEditor {
|
|||
.detach();
|
||||
}
|
||||
|
||||
fn confirm_mention_for_thread(
|
||||
&mut self,
|
||||
crease_id: CreaseId,
|
||||
anchor: Anchor,
|
||||
id: ThreadId,
|
||||
name: String,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let uri = MentionUri::Thread {
|
||||
id: id.clone(),
|
||||
name,
|
||||
};
|
||||
let open_task = self.thread_store.update(cx, |thread_store, cx| {
|
||||
thread_store.open_thread(&id, window, cx)
|
||||
});
|
||||
let task = cx
|
||||
.spawn(async move |_, cx| {
|
||||
let thread = open_task.await.map_err(|e| e.to_string())?;
|
||||
let content = thread
|
||||
.read_with(cx, |thread, _cx| thread.latest_detailed_summary_or_text())
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(content)
|
||||
})
|
||||
.shared();
|
||||
|
||||
self.mention_set.insert_thread(id, task.clone());
|
||||
|
||||
let editor = self.editor.clone();
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
if task.await.notify_async_err(cx).is_some() {
|
||||
this.update(cx, |this, _| {
|
||||
this.mention_set.insert_uri(crease_id, uri);
|
||||
})
|
||||
.ok();
|
||||
} else {
|
||||
editor
|
||||
.update(cx, |editor, cx| {
|
||||
editor.display_map.update(cx, |display_map, cx| {
|
||||
display_map.unfold_intersecting(vec![anchor..anchor], true, cx);
|
||||
});
|
||||
editor.remove_creases([crease_id], cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn confirm_mention_for_text_thread(
|
||||
&mut self,
|
||||
crease_id: CreaseId,
|
||||
anchor: Anchor,
|
||||
path: PathBuf,
|
||||
name: String,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let uri = MentionUri::TextThread {
|
||||
path: path.clone(),
|
||||
name,
|
||||
};
|
||||
let context = self.text_thread_store.update(cx, |text_thread_store, cx| {
|
||||
text_thread_store.open_local_context(path.as_path().into(), cx)
|
||||
});
|
||||
let task = cx
|
||||
.spawn(async move |_, cx| {
|
||||
let context = context.await.map_err(|e| e.to_string())?;
|
||||
let xml = context
|
||||
.update(cx, |context, cx| context.to_xml(cx))
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(xml)
|
||||
})
|
||||
.shared();
|
||||
|
||||
self.mention_set.insert_text_thread(path, task.clone());
|
||||
|
||||
let editor = self.editor.clone();
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
if task.await.notify_async_err(cx).is_some() {
|
||||
this.update(cx, |this, _| {
|
||||
this.mention_set.insert_uri(crease_id, uri);
|
||||
})
|
||||
.ok();
|
||||
} else {
|
||||
editor
|
||||
.update(cx, |editor, cx| {
|
||||
editor.display_map.update(cx, |display_map, cx| {
|
||||
display_map.unfold_intersecting(vec![anchor..anchor], true, cx);
|
||||
});
|
||||
editor.remove_creases([crease_id], cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub fn set_mode(&mut self, mode: EditorMode, cx: &mut Context<Self>) {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.set_mode(mode);
|
||||
|
@ -817,6 +934,10 @@ impl MessageEditor {
|
|||
self.mention_set
|
||||
.add_fetch_result(url, Task::ready(Ok(text)).shared());
|
||||
}
|
||||
MentionUri::Directory { abs_path } => {
|
||||
let task = Task::ready(Ok(text)).shared();
|
||||
self.mention_set.directories.insert(abs_path, task);
|
||||
}
|
||||
MentionUri::File { .. }
|
||||
| MentionUri::Symbol { .. }
|
||||
| MentionUri::Rule { .. }
|
||||
|
@ -882,6 +1003,18 @@ impl MessageEditor {
|
|||
}
|
||||
}
|
||||
|
||||
struct DirectoryContents(Arc<[(Arc<Path>, PathBuf, String)]>);
|
||||
|
||||
impl Display for DirectoryContents {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
for (_relative_path, full_path, content) in self.0.iter() {
|
||||
let fence = codeblock_fence_for_path(Some(full_path), None);
|
||||
write!(f, "\n{fence}\n{content}\n```")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Focusable for MessageEditor {
|
||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||
self.editor.focus_handle(cx)
|
||||
|
@ -1064,6 +1197,7 @@ pub struct MentionSet {
|
|||
images: HashMap<CreaseId, Shared<Task<Result<MentionImage, String>>>>,
|
||||
thread_summaries: HashMap<ThreadId, Shared<Task<Result<SharedString, String>>>>,
|
||||
text_thread_summaries: HashMap<PathBuf, Shared<Task<Result<String, String>>>>,
|
||||
directories: HashMap<PathBuf, Shared<Task<Result<String, String>>>>,
|
||||
}
|
||||
|
||||
impl MentionSet {
|
||||
|
@ -1116,7 +1250,6 @@ impl MentionSet {
|
|||
.map(|(&crease_id, uri)| {
|
||||
match uri {
|
||||
MentionUri::File { abs_path, .. } => {
|
||||
// TODO directories
|
||||
let uri = uri.clone();
|
||||
let abs_path = abs_path.to_path_buf();
|
||||
|
||||
|
@ -1141,6 +1274,24 @@ impl MentionSet {
|
|||
anyhow::Ok((crease_id, Mention::Text { uri, content }))
|
||||
})
|
||||
}
|
||||
MentionUri::Directory { abs_path } => {
|
||||
let Some(content) = self.directories.get(abs_path).cloned() else {
|
||||
return Task::ready(Err(anyhow!("missing directory load task")));
|
||||
};
|
||||
let uri = uri.clone();
|
||||
cx.spawn(async move |_| {
|
||||
Ok((
|
||||
crease_id,
|
||||
Mention::Text {
|
||||
uri,
|
||||
content: content
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!("{e}"))?
|
||||
.to_string(),
|
||||
},
|
||||
))
|
||||
})
|
||||
}
|
||||
MentionUri::Symbol {
|
||||
path, line_range, ..
|
||||
}
|
||||
|
|
|
@ -2790,25 +2790,30 @@ impl AcpThreadView {
|
|||
|
||||
if let Some(mention) = MentionUri::parse(&url).log_err() {
|
||||
workspace.update(cx, |workspace, cx| match mention {
|
||||
MentionUri::File { abs_path, .. } => {
|
||||
MentionUri::File { abs_path } => {
|
||||
let project = workspace.project();
|
||||
let Some((path, entry)) = project.update(cx, |project, cx| {
|
||||
let Some(path) =
|
||||
project.update(cx, |project, cx| project.find_project_path(abs_path, cx))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
workspace
|
||||
.open_path(path, None, true, window, cx)
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
MentionUri::Directory { abs_path } => {
|
||||
let project = workspace.project();
|
||||
let Some(entry) = project.update(cx, |project, cx| {
|
||||
let path = project.find_project_path(abs_path, cx)?;
|
||||
let entry = project.entry_for_path(&path, cx)?;
|
||||
Some((path, entry))
|
||||
project.entry_for_path(&path, cx)
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if entry.is_dir() {
|
||||
project.update(cx, |_, cx| {
|
||||
cx.emit(project::Event::RevealInProjectPanel(entry.id));
|
||||
});
|
||||
} else {
|
||||
workspace
|
||||
.open_path(path, None, true, window, cx)
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
project.update(cx, |_, cx| {
|
||||
cx.emit(project::Event::RevealInProjectPanel(entry.id));
|
||||
});
|
||||
}
|
||||
MentionUri::Symbol {
|
||||
path, line_range, ..
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue