Support @-mentions in inline assists and when editing old agent panel messages (#29734)
Closes #ISSUE Co-authored-by: Bennet <bennet@zed.dev> Release Notes: - Added support for context `@mentions` in the inline prompt editor and when editing past messages in the agent panel. --------- Co-authored-by: Bennet Bo Fenner <bennet@zed.dev> Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
This commit is contained in:
parent
c918f6cde1
commit
9547d42b15
18 changed files with 499 additions and 156 deletions
|
@ -5,7 +5,7 @@ use std::sync::Arc;
|
|||
use anyhow::{Result, anyhow};
|
||||
use collections::{HashSet, IndexSet};
|
||||
use futures::{self, FutureExt};
|
||||
use gpui::{App, Context, Entity, Image, SharedString, Task, WeakEntity};
|
||||
use gpui::{App, Context, Entity, EventEmitter, Image, SharedString, Task, WeakEntity};
|
||||
use language::Buffer;
|
||||
use language_model::LanguageModelImage;
|
||||
use project::image_store::is_image_file;
|
||||
|
@ -31,6 +31,12 @@ pub struct ContextStore {
|
|||
context_thread_ids: HashSet<ThreadId>,
|
||||
}
|
||||
|
||||
pub enum ContextStoreEvent {
|
||||
ContextRemoved(AgentContextKey),
|
||||
}
|
||||
|
||||
impl EventEmitter<ContextStoreEvent> for ContextStore {}
|
||||
|
||||
impl ContextStore {
|
||||
pub fn new(
|
||||
project: WeakEntity<Project>,
|
||||
|
@ -82,7 +88,7 @@ impl ContextStore {
|
|||
project_path: ProjectPath,
|
||||
remove_if_exists: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
) -> Task<Result<Option<AgentContextHandle>>> {
|
||||
let Some(project) = self.project.upgrade() else {
|
||||
return Task::ready(Err(anyhow!("failed to read project")));
|
||||
};
|
||||
|
@ -108,21 +114,22 @@ impl ContextStore {
|
|||
buffer: Entity<Buffer>,
|
||||
remove_if_exists: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
) -> Option<AgentContextHandle> {
|
||||
let context_id = self.next_context_id.post_inc();
|
||||
let context = AgentContextHandle::File(FileContextHandle { buffer, context_id });
|
||||
|
||||
let already_included = if self.has_context(&context) {
|
||||
if let Some(key) = self.context_set.get(AgentContextKey::ref_cast(&context)) {
|
||||
if remove_if_exists {
|
||||
self.remove_context(&context, cx);
|
||||
None
|
||||
} else {
|
||||
Some(key.as_ref().clone())
|
||||
}
|
||||
true
|
||||
} else if self.path_included_in_directory(project_path, cx).is_some() {
|
||||
None
|
||||
} else {
|
||||
self.path_included_in_directory(project_path, cx).is_some()
|
||||
};
|
||||
|
||||
if !already_included {
|
||||
self.insert_context(context, cx);
|
||||
self.insert_context(context.clone(), cx);
|
||||
Some(context)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,7 +138,7 @@ impl ContextStore {
|
|||
project_path: &ProjectPath,
|
||||
remove_if_exists: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Result<()> {
|
||||
) -> Result<Option<AgentContextHandle>> {
|
||||
let Some(project) = self.project.upgrade() else {
|
||||
return Err(anyhow!("failed to read project"));
|
||||
};
|
||||
|
@ -150,15 +157,20 @@ impl ContextStore {
|
|||
context_id,
|
||||
});
|
||||
|
||||
if self.has_context(&context) {
|
||||
if remove_if_exists {
|
||||
self.remove_context(&context, cx);
|
||||
}
|
||||
} else if self.path_included_in_directory(project_path, cx).is_none() {
|
||||
self.insert_context(context, cx);
|
||||
}
|
||||
let context =
|
||||
if let Some(existing) = self.context_set.get(AgentContextKey::ref_cast(&context)) {
|
||||
if remove_if_exists {
|
||||
self.remove_context(&context, cx);
|
||||
None
|
||||
} else {
|
||||
Some(existing.as_ref().clone())
|
||||
}
|
||||
} else {
|
||||
self.insert_context(context.clone(), cx);
|
||||
Some(context)
|
||||
};
|
||||
|
||||
anyhow::Ok(())
|
||||
anyhow::Ok(context)
|
||||
}
|
||||
|
||||
pub fn add_symbol(
|
||||
|
@ -169,7 +181,7 @@ impl ContextStore {
|
|||
enclosing_range: Range<Anchor>,
|
||||
remove_if_exists: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) -> bool {
|
||||
) -> (Option<AgentContextHandle>, bool) {
|
||||
let context_id = self.next_context_id.post_inc();
|
||||
let context = AgentContextHandle::Symbol(SymbolContextHandle {
|
||||
buffer,
|
||||
|
@ -179,14 +191,18 @@ impl ContextStore {
|
|||
context_id,
|
||||
});
|
||||
|
||||
if self.has_context(&context) {
|
||||
if remove_if_exists {
|
||||
if let Some(key) = self.context_set.get(AgentContextKey::ref_cast(&context)) {
|
||||
let handle = if remove_if_exists {
|
||||
self.remove_context(&context, cx);
|
||||
}
|
||||
return false;
|
||||
None
|
||||
} else {
|
||||
Some(key.as_ref().clone())
|
||||
};
|
||||
return (handle, false);
|
||||
}
|
||||
|
||||
self.insert_context(context, cx)
|
||||
let included = self.insert_context(context.clone(), cx);
|
||||
(Some(context), included)
|
||||
}
|
||||
|
||||
pub fn add_thread(
|
||||
|
@ -194,16 +210,20 @@ impl ContextStore {
|
|||
thread: Entity<Thread>,
|
||||
remove_if_exists: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
) -> Option<AgentContextHandle> {
|
||||
let context_id = self.next_context_id.post_inc();
|
||||
let context = AgentContextHandle::Thread(ThreadContextHandle { thread, context_id });
|
||||
|
||||
if self.has_context(&context) {
|
||||
if let Some(existing) = self.context_set.get(AgentContextKey::ref_cast(&context)) {
|
||||
if remove_if_exists {
|
||||
self.remove_context(&context, cx);
|
||||
None
|
||||
} else {
|
||||
Some(existing.as_ref().clone())
|
||||
}
|
||||
} else {
|
||||
self.insert_context(context, cx);
|
||||
self.insert_context(context.clone(), cx);
|
||||
Some(context)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -212,19 +232,23 @@ impl ContextStore {
|
|||
prompt_id: UserPromptId,
|
||||
remove_if_exists: bool,
|
||||
cx: &mut Context<ContextStore>,
|
||||
) {
|
||||
) -> Option<AgentContextHandle> {
|
||||
let context_id = self.next_context_id.post_inc();
|
||||
let context = AgentContextHandle::Rules(RulesContextHandle {
|
||||
prompt_id,
|
||||
context_id,
|
||||
});
|
||||
|
||||
if self.has_context(&context) {
|
||||
if let Some(existing) = self.context_set.get(AgentContextKey::ref_cast(&context)) {
|
||||
if remove_if_exists {
|
||||
self.remove_context(&context, cx);
|
||||
None
|
||||
} else {
|
||||
Some(existing.as_ref().clone())
|
||||
}
|
||||
} else {
|
||||
self.insert_context(context, cx);
|
||||
self.insert_context(context.clone(), cx);
|
||||
Some(context)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -233,14 +257,15 @@ impl ContextStore {
|
|||
url: String,
|
||||
text: impl Into<SharedString>,
|
||||
cx: &mut Context<ContextStore>,
|
||||
) {
|
||||
) -> AgentContextHandle {
|
||||
let context = AgentContextHandle::FetchedUrl(FetchedUrlContext {
|
||||
url: url.into(),
|
||||
text: text.into(),
|
||||
context_id: self.next_context_id.post_inc(),
|
||||
});
|
||||
|
||||
self.insert_context(context, cx);
|
||||
self.insert_context(context.clone(), cx);
|
||||
context
|
||||
}
|
||||
|
||||
pub fn add_image_from_path(
|
||||
|
@ -248,7 +273,7 @@ impl ContextStore {
|
|||
project_path: ProjectPath,
|
||||
remove_if_exists: bool,
|
||||
cx: &mut Context<ContextStore>,
|
||||
) -> Task<Result<()>> {
|
||||
) -> Task<Result<Option<AgentContextHandle>>> {
|
||||
let project = self.project.clone();
|
||||
cx.spawn(async move |this, cx| {
|
||||
let open_image_task = project.update(cx, |project, cx| {
|
||||
|
@ -262,7 +287,7 @@ impl ContextStore {
|
|||
image,
|
||||
remove_if_exists,
|
||||
cx,
|
||||
);
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -277,7 +302,7 @@ impl ContextStore {
|
|||
image: Arc<Image>,
|
||||
remove_if_exists: bool,
|
||||
cx: &mut Context<ContextStore>,
|
||||
) {
|
||||
) -> Option<AgentContextHandle> {
|
||||
let image_task = LanguageModelImage::from_image(image.clone(), cx).shared();
|
||||
let context = AgentContextHandle::Image(ImageContext {
|
||||
project_path,
|
||||
|
@ -288,11 +313,12 @@ impl ContextStore {
|
|||
if self.has_context(&context) {
|
||||
if remove_if_exists {
|
||||
self.remove_context(&context, cx);
|
||||
return;
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
self.insert_context(context, cx);
|
||||
self.insert_context(context.clone(), cx);
|
||||
Some(context)
|
||||
}
|
||||
|
||||
pub fn add_selection(
|
||||
|
@ -364,9 +390,9 @@ impl ContextStore {
|
|||
}
|
||||
|
||||
pub fn remove_context(&mut self, context: &AgentContextHandle, cx: &mut Context<Self>) {
|
||||
if self
|
||||
if let Some((_, key)) = self
|
||||
.context_set
|
||||
.shift_remove(AgentContextKey::ref_cast(context))
|
||||
.shift_remove_full(AgentContextKey::ref_cast(context))
|
||||
{
|
||||
match context {
|
||||
AgentContextHandle::Thread(thread_context) => {
|
||||
|
@ -375,6 +401,7 @@ impl ContextStore {
|
|||
}
|
||||
_ => {}
|
||||
}
|
||||
cx.emit(ContextStoreEvent::ContextRemoved(key));
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
@ -451,6 +478,12 @@ impl ContextStore {
|
|||
.contains(&FetchedUrlContext::lookup_key(url.into()))
|
||||
}
|
||||
|
||||
pub fn get_url_context(&self, url: SharedString) -> Option<AgentContextHandle> {
|
||||
self.context_set
|
||||
.get(&FetchedUrlContext::lookup_key(url))
|
||||
.map(|key| key.as_ref().clone())
|
||||
}
|
||||
|
||||
pub fn file_paths(&self, cx: &App) -> HashSet<ProjectPath> {
|
||||
self.context()
|
||||
.filter_map(|context| match context {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue