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,