diff --git a/crates/agent_ui/src/mention_set.rs b/crates/agent_ui/src/mention_set.rs index 58e7e4cdfc196862bb3b8936f8582ba1ad54bda5..792bfc11a63471e02b22835823fa8c59cdfc9bcf 100644 --- a/crates/agent_ui/src/mention_set.rs +++ b/crates/agent_ui/src/mention_set.rs @@ -234,6 +234,8 @@ impl MentionSet { mention_uri.name().into(), IconName::Image.path().into(), mention_uri.tooltip_text(), + Some(mention_uri.clone()), + Some(workspace.downgrade()), Some(image), editor.clone(), window, @@ -247,6 +249,8 @@ impl MentionSet { crease_text, mention_uri.icon_path(cx), mention_uri.tooltip_text(), + Some(mention_uri.clone()), + Some(workspace.downgrade()), None, editor.clone(), window, @@ -699,6 +703,8 @@ pub(crate) async fn insert_images_as_context( MentionUri::PastedImage.name().into(), IconName::Image.path().into(), None, + None, + None, Some(Task::ready(Ok(image.clone())).shared()), editor.clone(), window, @@ -810,6 +816,8 @@ pub(crate) fn insert_crease_for_mention( crease_label: SharedString, crease_icon: SharedString, crease_tooltip: Option, + mention_uri: Option, + workspace: Option>, image: Option, String>>>>, editor: Entity, window: &mut Window, @@ -830,6 +838,8 @@ pub(crate) fn insert_crease_for_mention( crease_label.clone(), crease_icon.clone(), crease_tooltip, + mention_uri.clone(), + workspace.clone(), start..end, rx, image, @@ -1029,6 +1039,8 @@ fn render_mention_fold_button( label: SharedString, icon: SharedString, tooltip: Option, + mention_uri: Option, + workspace: Option>, range: Range, mut loading_finished: postage::barrier::Receiver, image_task: Option, String>>>>, @@ -1049,6 +1061,8 @@ fn render_mention_fold_button( label, icon, tooltip, + mention_uri: mention_uri.clone(), + workspace: workspace.clone(), range, editor, loading: Some(loading), @@ -1063,6 +1077,8 @@ struct LoadingContext { label: SharedString, icon: SharedString, tooltip: Option, + mention_uri: Option, + workspace: Option>, range: Range, editor: WeakEntity, loading: Option>, @@ -1079,6 +1095,8 @@ impl Render for LoadingContext { let id = ElementId::from(("loading_context", self.id)); MentionCrease::new(id, self.icon.clone(), self.label.clone()) + .mention_uri(self.mention_uri.clone()) + .workspace(self.workspace.clone()) .is_toggled(is_in_text_selection) .is_loading(self.loading.is_some()) .when_some(self.tooltip.clone(), |this, tooltip_text| { diff --git a/crates/agent_ui/src/message_editor.rs b/crates/agent_ui/src/message_editor.rs index 50b297847b43e4d147978fbcf14dce492fc572d0..36d18a5843dac6d7ae52b591a2e5a402093ac118 100644 --- a/crates/agent_ui/src/message_editor.rs +++ b/crates/agent_ui/src/message_editor.rs @@ -722,6 +722,8 @@ impl MessageEditor { crease_text.into(), mention_uri.icon_path(cx), mention_uri.tooltip_text(), + Some(mention_uri.clone()), + Some(self.workspace.clone()), None, self.editor.clone(), window, @@ -833,6 +835,8 @@ impl MessageEditor { mention_uri.name().into(), mention_uri.icon_path(cx), mention_uri.tooltip_text(), + Some(mention_uri.clone()), + Some(self.workspace.clone()), None, self.editor.clone(), window, @@ -1014,6 +1018,8 @@ impl MessageEditor { mention_uri.name().into(), mention_uri.icon_path(cx), mention_uri.tooltip_text(), + Some(mention_uri.clone()), + Some(self.workspace.clone()), None, self.editor.clone(), window, @@ -1370,6 +1376,8 @@ impl MessageEditor { mention_uri.name().into(), mention_uri.icon_path(cx), mention_uri.tooltip_text(), + Some(mention_uri.clone()), + Some(self.workspace.clone()), None, self.editor.clone(), window, diff --git a/crates/agent_ui/src/ui/mention_crease.rs b/crates/agent_ui/src/ui/mention_crease.rs index 2d464039dc552203ad76979239673ec27d5568c7..0a61b8e4ef2ec69714f158a72f83cc0528cc8a8f 100644 --- a/crates/agent_ui/src/ui/mention_crease.rs +++ b/crates/agent_ui/src/ui/mention_crease.rs @@ -1,15 +1,25 @@ -use std::time::Duration; +use std::{ops::RangeInclusive, path::PathBuf, time::Duration}; -use gpui::{Animation, AnimationExt, AnyView, IntoElement, Window, pulsating_between}; +use acp_thread::MentionUri; +use agent_client_protocol as acp; +use editor::{Editor, SelectionEffects, scroll::Autoscroll}; +use gpui::{ + Animation, AnimationExt, AnyView, Context, IntoElement, WeakEntity, Window, pulsating_between, +}; +use prompt_store::PromptId; +use rope::Point; use settings::Settings; use theme::ThemeSettings; use ui::{ButtonLike, TintColor, Tooltip, prelude::*}; +use workspace::{OpenOptions, Workspace}; #[derive(IntoElement)] pub struct MentionCrease { id: ElementId, icon: SharedString, label: SharedString, + mention_uri: Option, + workspace: Option>, is_toggled: bool, is_loading: bool, tooltip: Option, @@ -26,6 +36,8 @@ impl MentionCrease { id: id.into(), icon: icon.into(), label: label.into(), + mention_uri: None, + workspace: None, is_toggled: false, is_loading: false, tooltip: None, @@ -33,6 +45,16 @@ impl MentionCrease { } } + pub fn mention_uri(mut self, mention_uri: Option) -> Self { + self.mention_uri = mention_uri; + self + } + + pub fn workspace(mut self, workspace: Option>) -> Self { + self.workspace = workspace; + self + } + pub fn is_toggled(mut self, is_toggled: bool) -> Self { self.is_toggled = is_toggled; self @@ -76,6 +98,14 @@ impl RenderOnce for MentionCrease { .height(button_height) .selected_style(ButtonStyle::Tinted(TintColor::Accent)) .toggle_state(self.is_toggled) + .when_some( + self.mention_uri.clone().zip(self.workspace.clone()), + |this, (mention_uri, workspace)| { + this.on_click(move |_event, window, cx| { + open_mention_uri(mention_uri.clone(), &workspace, window, cx); + }) + }, + ) .child( h_flex() .pb_px() @@ -114,3 +144,168 @@ impl RenderOnce for MentionCrease { }) } } + +fn open_mention_uri( + mention_uri: MentionUri, + workspace: &WeakEntity, + window: &mut Window, + cx: &mut App, +) { + let Some(workspace) = workspace.upgrade() else { + return; + }; + + workspace.update(cx, |workspace, cx| match mention_uri { + MentionUri::File { abs_path } => { + open_file(workspace, abs_path, None, window, cx); + } + MentionUri::Symbol { + abs_path, + line_range, + .. + } + | MentionUri::Selection { + abs_path: Some(abs_path), + line_range, + } => { + open_file(workspace, abs_path, Some(line_range), window, cx); + } + MentionUri::Directory { abs_path } => { + reveal_in_project_panel(workspace, abs_path, cx); + } + MentionUri::Thread { id, name } => { + open_thread(workspace, id, name, window, cx); + } + MentionUri::TextThread { .. } => {} + MentionUri::Rule { id, .. } => { + open_rule(workspace, id, window, cx); + } + MentionUri::Fetch { url } => { + cx.open_url(url.as_str()); + } + MentionUri::PastedImage + | MentionUri::Selection { abs_path: None, .. } + | MentionUri::Diagnostics { .. } + | MentionUri::TerminalSelection { .. } + | MentionUri::GitDiff { .. } => {} + }); +} + +fn open_file( + workspace: &mut Workspace, + abs_path: PathBuf, + line_range: Option>, + window: &mut Window, + cx: &mut Context, +) { + let project = workspace.project(); + + if let Some(project_path) = + project.update(cx, |project, cx| project.find_project_path(&abs_path, cx)) + { + let item = workspace.open_path(project_path, None, true, window, cx); + if let Some(line_range) = line_range { + window + .spawn(cx, async move |cx| { + let Some(editor) = item.await?.downcast::() else { + return Ok(()); + }; + editor + .update_in(cx, |editor, window, cx| { + let range = Point::new(*line_range.start(), 0) + ..Point::new(*line_range.start(), 0); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::center()), + window, + cx, + |selections| selections.select_ranges(vec![range]), + ); + }) + .ok(); + anyhow::Ok(()) + }) + .detach_and_log_err(cx); + } else { + item.detach_and_log_err(cx); + } + } else if abs_path.exists() { + workspace + .open_abs_path( + abs_path, + OpenOptions { + focus: Some(true), + ..Default::default() + }, + window, + cx, + ) + .detach_and_log_err(cx); + } +} + +fn reveal_in_project_panel( + workspace: &mut Workspace, + abs_path: PathBuf, + cx: &mut Context, +) { + let project = workspace.project(); + let Some(entry_id) = project.update(cx, |project, cx| { + let path = project.find_project_path(&abs_path, cx)?; + project.entry_for_path(&path, cx).map(|entry| entry.id) + }) else { + return; + }; + + project.update(cx, |_, cx| { + cx.emit(project::Event::RevealInProjectPanel(entry_id)); + }); +} + +fn open_thread( + workspace: &mut Workspace, + id: acp::SessionId, + name: String, + window: &mut Window, + cx: &mut Context, +) { + use crate::AgentPanel; + use acp_thread::AgentSessionInfo; + + let Some(panel) = workspace.panel::(cx) else { + return; + }; + + panel.update(cx, |panel, cx| { + panel.load_agent_thread( + AgentSessionInfo { + session_id: id, + cwd: None, + title: Some(name.into()), + updated_at: None, + meta: None, + }, + window, + cx, + ) + }); +} + +fn open_rule( + _workspace: &mut Workspace, + id: PromptId, + window: &mut Window, + cx: &mut Context, +) { + use zed_actions::assistant::OpenRulesLibrary; + + let PromptId::User { uuid } = id else { + return; + }; + + window.dispatch_action( + Box::new(OpenRulesLibrary { + prompt_to_select: Some(uuid.0), + }), + cx, + ); +}