diff --git a/assets/icons/at_sign.svg b/assets/icons/at_sign.svg
new file mode 100644
index 0000000000000000000000000000000000000000..531c10c8dc151fb27f2a53d424ab57acecd7d03c
--- /dev/null
+++ b/assets/icons/at_sign.svg
@@ -0,0 +1,4 @@
+
diff --git a/crates/agent_ui/src/acp/completion_provider.rs b/crates/agent_ui/src/acp/completion_provider.rs
index 84d75ebe4133b3145b892eec659867b137bce2f0..408dbedcfdd4998ca8d2e094aab4799bad168629 100644
--- a/crates/agent_ui/src/acp/completion_provider.rs
+++ b/crates/agent_ui/src/acp/completion_provider.rs
@@ -694,14 +694,18 @@ fn build_symbol_label(symbol_name: &str, file_name: &str, line: u32, cx: &App) -
}
fn build_code_label_for_full_path(file_name: &str, directory: Option<&str>, cx: &App) -> CodeLabel {
- let comment_id = cx.theme().syntax().highlight_id("comment").map(HighlightId);
+ let path = cx
+ .theme()
+ .syntax()
+ .highlight_id("variable")
+ .map(HighlightId);
let mut label = CodeLabelBuilder::default();
label.push_str(file_name, None);
label.push_str(" ", None);
if let Some(directory) = directory {
- label.push_str(directory, comment_id);
+ label.push_str(directory, path);
}
label.build()
diff --git a/crates/agent_ui/src/acp/message_editor.rs b/crates/agent_ui/src/acp/message_editor.rs
index 4f919a6c0425e48575d09380339730d7ddb26172..b7037a6413d93fb4ee538af7062049df9f58e818 100644
--- a/crates/agent_ui/src/acp/message_editor.rs
+++ b/crates/agent_ui/src/acp/message_editor.rs
@@ -15,6 +15,7 @@ use editor::{
EditorEvent, EditorMode, EditorSnapshot, EditorStyle, ExcerptId, FoldPlaceholder, Inlay,
MultiBuffer, ToOffset,
actions::Paste,
+ code_context_menus::CodeContextMenu,
display_map::{Crease, CreaseId, FoldId},
scroll::Autoscroll,
};
@@ -272,6 +273,15 @@ impl MessageEditor {
self.editor.read(cx).is_empty(cx)
}
+ pub fn is_completions_menu_visible(&self, cx: &App) -> bool {
+ self.editor
+ .read(cx)
+ .context_menu()
+ .borrow()
+ .as_ref()
+ .is_some_and(|menu| matches!(menu, CodeContextMenu::Completions(_)) && menu.visible())
+ }
+
pub fn mentions(&self) -> HashSet {
self.mention_set
.mentions
@@ -836,6 +846,45 @@ impl MessageEditor {
cx.emit(MessageEditorEvent::Send)
}
+ pub fn trigger_completion_menu(&mut self, window: &mut Window, cx: &mut Context) {
+ let editor = self.editor.clone();
+
+ cx.spawn_in(window, async move |_, cx| {
+ editor
+ .update_in(cx, |editor, window, cx| {
+ let menu_is_open =
+ editor.context_menu().borrow().as_ref().is_some_and(|menu| {
+ matches!(menu, CodeContextMenu::Completions(_)) && menu.visible()
+ });
+
+ let has_at_sign = {
+ let snapshot = editor.display_snapshot(cx);
+ let cursor = editor.selections.newest::(&snapshot).head();
+ let offset = cursor.to_offset(&snapshot);
+ if offset > 0 {
+ snapshot
+ .buffer_snapshot()
+ .reversed_chars_at(offset)
+ .next()
+ .map(|sign| sign == '@')
+ .unwrap_or(false)
+ } else {
+ false
+ }
+ };
+
+ if menu_is_open && has_at_sign {
+ return;
+ }
+
+ editor.insert("@", window, cx);
+ editor.show_completions(&editor::actions::ShowCompletions, window, cx);
+ })
+ .log_err();
+ })
+ .detach();
+ }
+
fn chat(&mut self, _: &Chat, _: &mut Window, cx: &mut Context) {
self.send(cx);
}
diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs
index 17daf5a18e97829d5e4d64d30d266b5d5d271e7b..4f3bbe718d3c6265f54f3cc4a949256b81c25572 100644
--- a/crates/agent_ui/src/acp/thread_view.rs
+++ b/crates/agent_ui/src/acp/thread_view.rs
@@ -4188,6 +4188,8 @@ impl AcpThreadView {
.justify_between()
.child(
h_flex()
+ .gap_0p5()
+ .child(self.render_add_context_button(cx))
.child(self.render_follow_toggle(cx))
.children(self.render_burn_mode_toggle(cx)),
)
@@ -4502,6 +4504,29 @@ impl AcpThreadView {
}))
}
+ fn render_add_context_button(&self, cx: &mut Context) -> impl IntoElement {
+ let message_editor = self.message_editor.clone();
+ let menu_visible = message_editor.read(cx).is_completions_menu_visible(cx);
+
+ IconButton::new("add-context", IconName::AtSign)
+ .icon_size(IconSize::Small)
+ .icon_color(Color::Muted)
+ .when(!menu_visible, |this| {
+ this.tooltip(move |_window, cx| {
+ Tooltip::with_meta("Add Context", None, "Or type @ to include context", cx)
+ })
+ })
+ .on_click(cx.listener(move |_this, _, window, cx| {
+ let message_editor_clone = message_editor.clone();
+
+ window.defer(cx, move |window, cx| {
+ message_editor_clone.update(cx, |message_editor, cx| {
+ message_editor.trigger_completion_menu(window, cx);
+ });
+ });
+ }))
+ }
+
fn render_markdown(&self, markdown: Entity, style: MarkdownStyle) -> MarkdownElement {
let workspace = self.workspace.clone();
MarkdownElement::new(markdown, style).on_url_click(move |text, window, cx| {
diff --git a/crates/icons/src/icons.rs b/crates/icons/src/icons.rs
index fb45ca1eb5f8334190c11ad811a31128396ba23a..a0865773ac394722c113a43fe323de218b2f145a 100644
--- a/crates/icons/src/icons.rs
+++ b/crates/icons/src/icons.rs
@@ -35,6 +35,7 @@ pub enum IconName {
ArrowUp,
ArrowUpRight,
Attach,
+ AtSign,
AudioOff,
AudioOn,
Backspace,