Fully support all mention kinds (#36134)
Feature parity with the agent1 @mention kinds: - File - Symbols - Selections - Threads - Rules - Fetch Release Notes: - N/A --------- Co-authored-by: Cole Miller <cole@zed.dev>
This commit is contained in:
parent
389d382f42
commit
389d24d7e5
17 changed files with 1787 additions and 324 deletions
|
@ -4,15 +4,17 @@ use acp_thread::{
|
|||
};
|
||||
use acp_thread::{AgentConnection, Plan};
|
||||
use action_log::ActionLog;
|
||||
use agent::{TextThreadStore, ThreadStore};
|
||||
use agent_client_protocol as acp;
|
||||
use agent_servers::AgentServer;
|
||||
use agent_settings::{AgentSettings, NotifyWhenAgentWaiting};
|
||||
use audio::{Audio, Sound};
|
||||
use buffer_diff::BufferDiff;
|
||||
use collections::{HashMap, HashSet};
|
||||
use editor::scroll::Autoscroll;
|
||||
use editor::{
|
||||
AnchorRangeExt, ContextMenuOptions, ContextMenuPlacement, Editor, EditorElement, EditorMode,
|
||||
EditorStyle, MinimapVisibility, MultiBuffer, PathKey,
|
||||
EditorStyle, MinimapVisibility, MultiBuffer, PathKey, SelectionEffects,
|
||||
};
|
||||
use file_icons::FileIcons;
|
||||
use gpui::{
|
||||
|
@ -27,8 +29,10 @@ use language::{Buffer, Language};
|
|||
use markdown::{HeadingLevelStyles, Markdown, MarkdownElement, MarkdownStyle};
|
||||
use parking_lot::Mutex;
|
||||
use project::{CompletionIntent, Project};
|
||||
use prompt_store::PromptId;
|
||||
use rope::Point;
|
||||
use settings::{Settings as _, SettingsStore};
|
||||
use std::fmt::Write as _;
|
||||
use std::path::PathBuf;
|
||||
use std::{
|
||||
cell::RefCell, collections::BTreeMap, path::Path, process::ExitStatus, rc::Rc, sync::Arc,
|
||||
|
@ -44,6 +48,7 @@ use ui::{
|
|||
use util::{ResultExt, size::format_file_size, time::duration_alt_display};
|
||||
use workspace::{CollaboratorId, Workspace};
|
||||
use zed_actions::agent::{Chat, NextHistoryMessage, PreviousHistoryMessage, ToggleModelSelector};
|
||||
use zed_actions::assistant::OpenRulesLibrary;
|
||||
|
||||
use crate::acp::AcpModelSelectorPopover;
|
||||
use crate::acp::completion_provider::{ContextPickerCompletionProvider, MentionSet};
|
||||
|
@ -61,6 +66,8 @@ pub struct AcpThreadView {
|
|||
agent: Rc<dyn AgentServer>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
project: Entity<Project>,
|
||||
thread_store: Entity<ThreadStore>,
|
||||
text_thread_store: Entity<TextThreadStore>,
|
||||
thread_state: ThreadState,
|
||||
diff_editors: HashMap<EntityId, Entity<Editor>>,
|
||||
terminal_views: HashMap<EntityId, Entity<TerminalView>>,
|
||||
|
@ -108,6 +115,8 @@ impl AcpThreadView {
|
|||
agent: Rc<dyn AgentServer>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
project: Entity<Project>,
|
||||
thread_store: Entity<ThreadStore>,
|
||||
text_thread_store: Entity<TextThreadStore>,
|
||||
message_history: Rc<RefCell<MessageHistory<Vec<acp::ContentBlock>>>>,
|
||||
min_lines: usize,
|
||||
max_lines: Option<usize>,
|
||||
|
@ -145,6 +154,8 @@ impl AcpThreadView {
|
|||
editor.set_completion_provider(Some(Rc::new(ContextPickerCompletionProvider::new(
|
||||
mention_set.clone(),
|
||||
workspace.clone(),
|
||||
thread_store.downgrade(),
|
||||
text_thread_store.downgrade(),
|
||||
cx.weak_entity(),
|
||||
))));
|
||||
editor.set_context_menu_options(ContextMenuOptions {
|
||||
|
@ -188,6 +199,8 @@ impl AcpThreadView {
|
|||
agent: agent.clone(),
|
||||
workspace: workspace.clone(),
|
||||
project: project.clone(),
|
||||
thread_store,
|
||||
text_thread_store,
|
||||
thread_state: Self::initial_state(agent, workspace, project, window, cx),
|
||||
message_editor,
|
||||
model_selector: None,
|
||||
|
@ -401,7 +414,13 @@ impl AcpThreadView {
|
|||
let mut chunks: Vec<acp::ContentBlock> = Vec::new();
|
||||
let project = self.project.clone();
|
||||
|
||||
let contents = self.mention_set.lock().contents(project, cx);
|
||||
let thread_store = self.thread_store.clone();
|
||||
let text_thread_store = self.text_thread_store.clone();
|
||||
|
||||
let contents =
|
||||
self.mention_set
|
||||
.lock()
|
||||
.contents(project, thread_store, text_thread_store, window, cx);
|
||||
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
let contents = match contents.await {
|
||||
|
@ -439,7 +458,7 @@ impl AcpThreadView {
|
|||
acp::TextResourceContents {
|
||||
mime_type: None,
|
||||
text: mention.content.clone(),
|
||||
uri: mention.uri.to_uri(),
|
||||
uri: mention.uri.to_uri().to_string(),
|
||||
},
|
||||
),
|
||||
}));
|
||||
|
@ -614,8 +633,7 @@ impl AcpThreadView {
|
|||
let path = PathBuf::from(&resource.uri);
|
||||
let project_path = project.read(cx).project_path_for_absolute_path(&path, cx);
|
||||
let start = text.len();
|
||||
let content = MentionUri::File(path).to_uri();
|
||||
text.push_str(&content);
|
||||
let _ = write!(&mut text, "{}", MentionUri::File(path).to_uri());
|
||||
let end = text.len();
|
||||
if let Some(project_path) = project_path {
|
||||
let filename: SharedString = project_path
|
||||
|
@ -663,7 +681,9 @@ impl AcpThreadView {
|
|||
);
|
||||
|
||||
if let Some(crease_id) = crease_id {
|
||||
mention_set.lock().insert(crease_id, project_path);
|
||||
mention_set
|
||||
.lock()
|
||||
.insert(crease_id, MentionUri::File(project_path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2698,9 +2718,72 @@ impl AcpThreadView {
|
|||
.detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// TODO
|
||||
unimplemented!()
|
||||
MentionUri::Symbol {
|
||||
path, line_range, ..
|
||||
}
|
||||
| MentionUri::Selection { path, line_range } => {
|
||||
let project = workspace.project();
|
||||
let Some((path, _)) = project.update(cx, |project, cx| {
|
||||
let path = project.find_project_path(path, cx)?;
|
||||
let entry = project.entry_for_path(&path, cx)?;
|
||||
Some((path, entry))
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let item = workspace.open_path(path, None, true, window, cx);
|
||||
window
|
||||
.spawn(cx, async move |cx| {
|
||||
let Some(editor) = item.await?.downcast::<Editor>() else {
|
||||
return Ok(());
|
||||
};
|
||||
let range =
|
||||
Point::new(line_range.start, 0)..Point::new(line_range.start, 0);
|
||||
editor
|
||||
.update_in(cx, |editor, window, cx| {
|
||||
editor.change_selections(
|
||||
SelectionEffects::scroll(Autoscroll::center()),
|
||||
window,
|
||||
cx,
|
||||
|s| s.select_ranges(vec![range]),
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
MentionUri::Thread { id, .. } => {
|
||||
if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel
|
||||
.open_thread_by_id(&id, window, cx)
|
||||
.detach_and_log_err(cx)
|
||||
});
|
||||
}
|
||||
}
|
||||
MentionUri::TextThread { path, .. } => {
|
||||
if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel
|
||||
.open_saved_prompt_editor(path.as_path().into(), window, cx)
|
||||
.detach_and_log_err(cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
MentionUri::Rule { id, .. } => {
|
||||
let PromptId::User { uuid } = id else {
|
||||
return;
|
||||
};
|
||||
window.dispatch_action(
|
||||
Box::new(OpenRulesLibrary {
|
||||
prompt_to_select: Some(uuid.0),
|
||||
}),
|
||||
cx,
|
||||
)
|
||||
}
|
||||
MentionUri::Fetch { url } => {
|
||||
cx.open_url(url.as_str());
|
||||
}
|
||||
})
|
||||
} else {
|
||||
|
@ -3090,7 +3173,7 @@ impl AcpThreadView {
|
|||
.unwrap_or(path.path.as_os_str())
|
||||
.display()
|
||||
.to_string();
|
||||
let completion = ContextPickerCompletionProvider::completion_for_path(
|
||||
let Some(completion) = ContextPickerCompletionProvider::completion_for_path(
|
||||
path,
|
||||
&path_prefix,
|
||||
false,
|
||||
|
@ -3101,7 +3184,9 @@ impl AcpThreadView {
|
|||
self.mention_set.clone(),
|
||||
self.project.clone(),
|
||||
cx,
|
||||
);
|
||||
) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
self.message_editor.update(cx, |message_editor, cx| {
|
||||
message_editor.edit(
|
||||
|
@ -3431,17 +3516,14 @@ fn terminal_command_markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use agent::{TextThreadStore, ThreadStore};
|
||||
use agent_client_protocol::SessionId;
|
||||
use editor::EditorSettings;
|
||||
use fs::FakeFs;
|
||||
use futures::future::try_join_all;
|
||||
use gpui::{SemanticVersion, TestAppContext, VisualTestContext};
|
||||
use lsp::{CompletionContext, CompletionTriggerKind};
|
||||
use project::CompletionIntent;
|
||||
use rand::Rng;
|
||||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
use util::path;
|
||||
|
||||
use super::*;
|
||||
|
||||
|
@ -3554,109 +3636,6 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_crease_removal(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree("/project", json!({"file": ""})).await;
|
||||
let project = Project::test(fs, [Path::new(path!("/project"))], cx).await;
|
||||
let agent = StubAgentServer::default();
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let thread_view = cx.update(|window, cx| {
|
||||
cx.new(|cx| {
|
||||
AcpThreadView::new(
|
||||
Rc::new(agent),
|
||||
workspace.downgrade(),
|
||||
project,
|
||||
Rc::new(RefCell::new(MessageHistory::default())),
|
||||
1,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
});
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
let message_editor = cx.read(|cx| thread_view.read(cx).message_editor.clone());
|
||||
let excerpt_id = message_editor.update(cx, |editor, cx| {
|
||||
editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.excerpt_ids()
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap()
|
||||
});
|
||||
let completions = message_editor.update_in(cx, |editor, window, cx| {
|
||||
editor.set_text("Hello @", window, cx);
|
||||
let buffer = editor.buffer().read(cx).as_singleton().unwrap();
|
||||
let completion_provider = editor.completion_provider().unwrap();
|
||||
completion_provider.completions(
|
||||
excerpt_id,
|
||||
&buffer,
|
||||
Anchor::MAX,
|
||||
CompletionContext {
|
||||
trigger_kind: CompletionTriggerKind::TRIGGER_CHARACTER,
|
||||
trigger_character: Some("@".into()),
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let [_, completion]: [_; 2] = completions
|
||||
.await
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.flat_map(|response| response.completions)
|
||||
.collect::<Vec<_>>()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
message_editor.update_in(cx, |editor, window, cx| {
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
let start = snapshot
|
||||
.anchor_in_excerpt(excerpt_id, completion.replace_range.start)
|
||||
.unwrap();
|
||||
let end = snapshot
|
||||
.anchor_in_excerpt(excerpt_id, completion.replace_range.end)
|
||||
.unwrap();
|
||||
editor.edit([(start..end, completion.new_text)], cx);
|
||||
(completion.confirm.unwrap())(CompletionIntent::Complete, window, cx);
|
||||
});
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
// Backspace over the inserted crease (and the following space).
|
||||
message_editor.update_in(cx, |editor, window, cx| {
|
||||
editor.backspace(&Default::default(), window, cx);
|
||||
editor.backspace(&Default::default(), window, cx);
|
||||
});
|
||||
|
||||
thread_view.update_in(cx, |thread_view, window, cx| {
|
||||
thread_view.chat(&Chat, window, cx);
|
||||
});
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
let content = thread_view.update_in(cx, |thread_view, _window, _cx| {
|
||||
thread_view
|
||||
.message_history
|
||||
.borrow()
|
||||
.items()
|
||||
.iter()
|
||||
.flatten()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
// We don't send a resource link for the deleted crease.
|
||||
pretty_assertions::assert_matches!(content.as_slice(), [acp::ContentBlock::Text { .. }]);
|
||||
}
|
||||
|
||||
async fn setup_thread_view(
|
||||
agent: impl AgentServer + 'static,
|
||||
cx: &mut TestAppContext,
|
||||
|
@ -3666,12 +3645,19 @@ mod tests {
|
|||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
|
||||
let thread_store =
|
||||
cx.update(|_window, cx| cx.new(|cx| ThreadStore::fake(project.clone(), cx)));
|
||||
let text_thread_store =
|
||||
cx.update(|_window, cx| cx.new(|cx| TextThreadStore::fake(project.clone(), cx)));
|
||||
|
||||
let thread_view = cx.update(|window, cx| {
|
||||
cx.new(|cx| {
|
||||
AcpThreadView::new(
|
||||
Rc::new(agent),
|
||||
workspace.downgrade(),
|
||||
project,
|
||||
thread_store.clone(),
|
||||
text_thread_store.clone(),
|
||||
Rc::new(RefCell::new(MessageHistory::default())),
|
||||
1,
|
||||
None,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue