agent: Add full-path tooltips to chat mentions (#50087)

José Duarte and Danilo Leal created

In text box
<img width="662" height="160" alt="Screenshot 2026-02-25 at 14 32 01"
src="https://github.com/user-attachments/assets/c568036c-1445-46cd-b703-41af1b185d6f"
/>
In chat
<img width="661" height="245" alt="Screenshot 2026-02-25 at 14 32 15"
src="https://github.com/user-attachments/assets/9932799f-6580-4a70-b83f-d1c0fb0c55d9"
/>
In chat (light theme)
<img width="656" height="232" alt="Screenshot 2026-02-25 at 14 35 26"
src="https://github.com/user-attachments/assets/65c4ce56-c902-4dda-a1c4-9dfcea0e6012"
/>

Release Notes:

- N/A

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>

Change summary

crates/acp_thread/src/mention.rs            | 35 +++++++++++++++++++++++
crates/agent_ui/src/completion_provider.rs  |  1 
crates/agent_ui/src/inline_prompt_editor.rs |  1 
crates/agent_ui/src/mention_set.rs          | 20 +++++++++++-
crates/agent_ui/src/message_editor.rs       |  4 ++
crates/agent_ui/src/ui/mention_crease.rs    | 26 +++++++++++++---
6 files changed, 80 insertions(+), 7 deletions(-)

Detailed changes

crates/acp_thread/src/mention.rs 🔗

@@ -254,6 +254,41 @@ impl MentionUri {
         }
     }
 
+    pub fn tooltip_text(&self) -> Option<SharedString> {
+        match self {
+            MentionUri::File { abs_path } | MentionUri::Directory { abs_path } => {
+                Some(abs_path.to_string_lossy().into_owned().into())
+            }
+            MentionUri::Symbol {
+                abs_path,
+                line_range,
+                ..
+            } => Some(
+                format!(
+                    "{}:{}-{}",
+                    abs_path.display(),
+                    line_range.start(),
+                    line_range.end()
+                )
+                .into(),
+            ),
+            MentionUri::Selection {
+                abs_path: Some(path),
+                line_range,
+                ..
+            } => Some(
+                format!(
+                    "{}:{}-{}",
+                    path.display(),
+                    line_range.start(),
+                    line_range.end()
+                )
+                .into(),
+            ),
+            _ => None,
+        }
+    }
+
     pub fn icon_path(&self, cx: &mut App) -> SharedString {
         match self {
             MentionUri::File { abs_path } => {

crates/agent_ui/src/completion_provider.rs 🔗

@@ -617,6 +617,7 @@ impl<T: PromptCompletionProviderDelegate> PromptCompletionProvider<T> {
                                     let crease = crate::mention_set::crease_for_mention(
                                         mention_uri.name().into(),
                                         mention_uri.icon_path(cx),
+                                        None,
                                         range,
                                         editor.downgrade(),
                                     );

crates/agent_ui/src/mention_set.rs 🔗

@@ -233,6 +233,7 @@ impl MentionSet {
                 content_len,
                 mention_uri.name().into(),
                 IconName::Image.path().into(),
+                mention_uri.tooltip_text(),
                 Some(image),
                 editor.clone(),
                 window,
@@ -245,6 +246,7 @@ impl MentionSet {
                 content_len,
                 crease_text,
                 mention_uri.icon_path(cx),
+                mention_uri.tooltip_text(),
                 None,
                 editor.clone(),
                 window,
@@ -485,6 +487,7 @@ impl MentionSet {
             let crease = crease_for_mention(
                 selection_name(abs_path.as_deref(), &line_range).into(),
                 uri.icon_path(cx),
+                uri.tooltip_text(),
                 range,
                 editor.downgrade(),
             );
@@ -695,6 +698,7 @@ pub(crate) async fn insert_images_as_context(
                 content_len,
                 MentionUri::PastedImage.name().into(),
                 IconName::Image.path().into(),
+                None,
                 Some(Task::ready(Ok(image.clone())).shared()),
                 editor.clone(),
                 window,
@@ -805,7 +809,7 @@ pub(crate) fn insert_crease_for_mention(
     content_len: usize,
     crease_label: SharedString,
     crease_icon: SharedString,
-    // abs_path: Option<Arc<Path>>,
+    crease_tooltip: Option<SharedString>,
     image: Option<Shared<Task<Result<Arc<Image>, String>>>>,
     editor: Entity<Editor>,
     window: &mut Window,
@@ -825,6 +829,7 @@ pub(crate) fn insert_crease_for_mention(
             render: render_mention_fold_button(
                 crease_label.clone(),
                 crease_icon.clone(),
+                crease_tooltip,
                 start..end,
                 rx,
                 image,
@@ -858,11 +863,12 @@ pub(crate) fn insert_crease_for_mention(
 pub(crate) fn crease_for_mention(
     label: SharedString,
     icon_path: SharedString,
+    tooltip: Option<SharedString>,
     range: Range<Anchor>,
     editor_entity: WeakEntity<Editor>,
 ) -> Crease<Anchor> {
     let placeholder = FoldPlaceholder {
-        render: render_fold_icon_button(icon_path.clone(), label.clone(), editor_entity),
+        render: render_fold_icon_button(icon_path.clone(), label.clone(), tooltip, editor_entity),
         merge_adjacent: false,
         ..Default::default()
     };
@@ -876,6 +882,7 @@ pub(crate) fn crease_for_mention(
 fn render_fold_icon_button(
     icon_path: SharedString,
     label: SharedString,
+    tooltip: Option<SharedString>,
     editor: WeakEntity<Editor>,
 ) -> Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut App) -> AnyElement> {
     Arc::new({
@@ -886,6 +893,9 @@ fn render_fold_icon_button(
 
             MentionCrease::new(fold_id, icon_path.clone(), label.clone())
                 .is_toggled(is_in_text_selection)
+                .when_some(tooltip.clone(), |this, tooltip_text| {
+                    this.tooltip(tooltip_text)
+                })
                 .into_any_element()
         }
     })
@@ -1018,6 +1028,7 @@ fn render_directory_contents(entries: Vec<(Arc<RelPath>, String, String)>) -> St
 fn render_mention_fold_button(
     label: SharedString,
     icon: SharedString,
+    tooltip: Option<SharedString>,
     range: Range<Anchor>,
     mut loading_finished: postage::barrier::Receiver,
     image_task: Option<Shared<Task<Result<Arc<Image>, String>>>>,
@@ -1037,6 +1048,7 @@ fn render_mention_fold_button(
             id: cx.entity_id(),
             label,
             icon,
+            tooltip,
             range,
             editor,
             loading: Some(loading),
@@ -1050,6 +1062,7 @@ struct LoadingContext {
     id: EntityId,
     label: SharedString,
     icon: SharedString,
+    tooltip: Option<SharedString>,
     range: Range<Anchor>,
     editor: WeakEntity<Editor>,
     loading: Option<Task<()>>,
@@ -1068,6 +1081,9 @@ impl Render for LoadingContext {
         MentionCrease::new(id, self.icon.clone(), self.label.clone())
             .is_toggled(is_in_text_selection)
             .is_loading(self.loading.is_some())
+            .when_some(self.tooltip.clone(), |this, tooltip_text| {
+                this.tooltip(tooltip_text)
+            })
             .when_some(self.image.clone(), |this, image_task| {
                 this.image_preview(move |_, cx| {
                     let image = image_task.peek().cloned().transpose().ok().flatten();

crates/agent_ui/src/message_editor.rs 🔗

@@ -690,6 +690,7 @@ impl MessageEditor {
                         content_len,
                         crease_text.into(),
                         mention_uri.icon_path(cx),
+                        mention_uri.tooltip_text(),
                         None,
                         self.editor.clone(),
                         window,
@@ -800,6 +801,7 @@ impl MessageEditor {
                             content_len,
                             mention_uri.name().into(),
                             mention_uri.icon_path(cx),
+                            mention_uri.tooltip_text(),
                             None,
                             self.editor.clone(),
                             window,
@@ -980,6 +982,7 @@ impl MessageEditor {
             content_len,
             mention_uri.name().into(),
             mention_uri.icon_path(cx),
+            mention_uri.tooltip_text(),
             None,
             self.editor.clone(),
             window,
@@ -1285,6 +1288,7 @@ impl MessageEditor {
                 range.end - range.start,
                 mention_uri.name().into(),
                 mention_uri.icon_path(cx),
+                mention_uri.tooltip_text(),
                 None,
                 self.editor.clone(),
                 window,

crates/agent_ui/src/ui/mention_crease.rs 🔗

@@ -3,7 +3,7 @@ use std::time::Duration;
 use gpui::{Animation, AnimationExt, AnyView, IntoElement, Window, pulsating_between};
 use settings::Settings;
 use theme::ThemeSettings;
-use ui::{ButtonLike, TintColor, prelude::*};
+use ui::{ButtonLike, TintColor, Tooltip, prelude::*};
 
 #[derive(IntoElement)]
 pub struct MentionCrease {
@@ -12,6 +12,7 @@ pub struct MentionCrease {
     label: SharedString,
     is_toggled: bool,
     is_loading: bool,
+    tooltip: Option<SharedString>,
     image_preview: Option<Box<dyn Fn(&mut Window, &mut App) -> AnyView + 'static>>,
 }
 
@@ -27,6 +28,7 @@ impl MentionCrease {
             label: label.into(),
             is_toggled: false,
             is_loading: false,
+            tooltip: None,
             image_preview: None,
         }
     }
@@ -41,6 +43,11 @@ impl MentionCrease {
         self
     }
 
+    pub fn tooltip(mut self, tooltip: impl Into<SharedString>) -> Self {
+        self.tooltip = Some(tooltip.into());
+        self
+    }
+
     pub fn image_preview(
         mut self,
         builder: impl Fn(&mut Window, &mut App) -> AnyView + 'static,
@@ -55,6 +62,9 @@ impl RenderOnce for MentionCrease {
         let settings = ThemeSettings::get_global(cx);
         let font_size = settings.agent_buffer_font_size(cx);
         let buffer_font = settings.buffer_font.clone();
+        let is_loading = self.is_loading;
+        let tooltip = self.tooltip;
+        let image_preview = self.image_preview;
 
         let button_height = DefiniteLength::Absolute(AbsoluteLength::Pixels(
             px(window.line_height().into()) - px(1.),
@@ -66,9 +76,6 @@ impl RenderOnce for MentionCrease {
             .height(button_height)
             .selected_style(ButtonStyle::Tinted(TintColor::Accent))
             .toggle_state(self.is_toggled)
-            .when_some(self.image_preview, |this, image_preview| {
-                this.hoverable_tooltip(image_preview)
-            })
             .child(
                 h_flex()
                     .pb_px()
@@ -82,7 +89,7 @@ impl RenderOnce for MentionCrease {
                     )
                     .child(self.label.clone())
                     .map(|this| {
-                        if self.is_loading {
+                        if is_loading {
                             this.with_animation(
                                 "loading-context-crease",
                                 Animation::new(Duration::from_secs(2))
@@ -96,5 +103,14 @@ impl RenderOnce for MentionCrease {
                         }
                     }),
             )
+            .map(|button| {
+                if let Some(image_preview) = image_preview {
+                    button.hoverable_tooltip(image_preview)
+                } else {
+                    button.when_some(tooltip, |this, tooltip_text| {
+                        this.tooltip(Tooltip::text(tooltip_text))
+                    })
+                }
+            })
     }
 }