assistant2: Add keybinding for profile selector (#27674)

Marshall Bowers created

This PR adds a keybinding to toggle the profile selector.

Defaults to `Cmd-I` on macOS and `ctrl-I` on Linux/Windows.

Release Notes:

- N/A

Change summary

assets/keymaps/default-linux.json         |  4 ++
assets/keymaps/default-macos.json         |  2 +
crates/assistant2/src/assistant.rs        |  1 
crates/assistant2/src/message_editor.rs   | 10 ++++++-
crates/assistant2/src/profile_selector.rs | 29 ++++++++++++++++++++----
5 files changed, 38 insertions(+), 8 deletions(-)

Detailed changes

assets/keymaps/default-linux.json 🔗

@@ -618,6 +618,7 @@
       "ctrl-n": "assistant2::NewThread",
       "new": "assistant2::NewThread",
       "ctrl-shift-h": "assistant2::OpenHistory",
+      "ctrl-i": "assistant2::ToggleProfileSelector",
       "ctrl-alt-/": "assistant::ToggleModelSelector",
       "ctrl-shift-a": "assistant2::ToggleContextPicker",
       "ctrl-e": "assistant2::ChatMode",
@@ -635,7 +636,8 @@
   {
     "context": "MessageEditor > Editor",
     "bindings": {
-      "enter": "assistant2::Chat"
+      "enter": "assistant2::Chat",
+      "ctrl-i": "assistant2::ToggleProfileSelector"
     }
   },
   {

assets/keymaps/default-macos.json 🔗

@@ -269,6 +269,7 @@
       "cmd-n": "assistant2::NewThread",
       "cmd-alt-p": "assistant2::NewPromptEditor",
       "cmd-shift-h": "assistant2::OpenHistory",
+      "cmd-i": "assistant2::ToggleProfileSelector",
       "cmd-alt-/": "assistant::ToggleModelSelector",
       "cmd-shift-a": "assistant2::ToggleContextPicker",
       "cmd-e": "assistant2::ChatMode",
@@ -288,6 +289,7 @@
     "use_key_equivalents": true,
     "bindings": {
       "enter": "assistant2::Chat",
+      "cmd-i": "assistant2::ToggleProfileSelector",
       "cmd-g d": "git::Diff",
       "shift-escape": "git::ExpandCommitEditor"
     }

crates/assistant2/src/assistant.rs 🔗

@@ -44,6 +44,7 @@ actions!(
         NewThread,
         NewPromptEditor,
         ToggleContextPicker,
+        ToggleProfileSelector,
         RemoveAllContext,
         OpenHistory,
         OpenConfiguration,

crates/assistant2/src/message_editor.rs 🔗

@@ -29,7 +29,9 @@ use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
 use crate::profile_selector::ProfileSelector;
 use crate::thread::{RequestKind, Thread};
 use crate::thread_store::ThreadStore;
-use crate::{Chat, ChatMode, RemoveAllContext, ThreadEvent, ToggleContextPicker};
+use crate::{
+    Chat, ChatMode, RemoveAllContext, ThreadEvent, ToggleContextPicker, ToggleProfileSelector,
+};
 
 pub struct MessageEditor {
     thread: Entity<Thread>,
@@ -135,7 +137,8 @@ impl MessageEditor {
                     cx,
                 )
             }),
-            profile_selector: cx.new(|cx| ProfileSelector::new(fs, thread_store, cx)),
+            profile_selector: cx
+                .new(|cx| ProfileSelector::new(fs, thread_store, editor.focus_handle(cx), cx)),
             _subscriptions: subscriptions,
         }
     }
@@ -561,6 +564,9 @@ impl Render for MessageEditor {
                 v_flex()
                     .key_context("MessageEditor")
                     .on_action(cx.listener(Self::chat))
+                    .on_action(cx.listener(|this, _: &ToggleProfileSelector, window, cx| {
+                        this.profile_selector.read(cx).menu_handle().toggle(window, cx);
+                    }))
                     .on_action(cx.listener(|this, _: &ToggleModelSelector, window, cx| {
                         this.model_selector
                             .update(cx, |model_selector, cx| model_selector.toggle(window, cx));

crates/assistant2/src/profile_selector.rs 🔗

@@ -2,18 +2,20 @@ use std::sync::Arc;
 
 use assistant_settings::{AgentProfile, AssistantSettings};
 use fs::Fs;
-use gpui::{prelude::*, Action, Entity, Subscription, WeakEntity};
+use gpui::{prelude::*, Action, Entity, FocusHandle, Subscription, WeakEntity};
 use indexmap::IndexMap;
 use settings::{update_settings_file, Settings as _, SettingsStore};
-use ui::{prelude::*, ContextMenu, ContextMenuEntry, PopoverMenu, Tooltip};
+use ui::{prelude::*, ContextMenu, ContextMenuEntry, PopoverMenu, PopoverMenuHandle, Tooltip};
 use util::ResultExt as _;
 
-use crate::{ManageProfiles, ThreadStore};
+use crate::{ManageProfiles, ThreadStore, ToggleProfileSelector};
 
 pub struct ProfileSelector {
     profiles: IndexMap<Arc<str>, AgentProfile>,
     fs: Arc<dyn Fs>,
     thread_store: WeakEntity<ThreadStore>,
+    focus_handle: FocusHandle,
+    menu_handle: PopoverMenuHandle<ContextMenu>,
     _subscriptions: Vec<Subscription>,
 }
 
@@ -21,6 +23,7 @@ impl ProfileSelector {
     pub fn new(
         fs: Arc<dyn Fs>,
         thread_store: WeakEntity<ThreadStore>,
+        focus_handle: FocusHandle,
         cx: &mut Context<Self>,
     ) -> Self {
         let settings_subscription = cx.observe_global::<SettingsStore>(move |this, cx| {
@@ -31,6 +34,8 @@ impl ProfileSelector {
             profiles: IndexMap::default(),
             fs,
             thread_store,
+            focus_handle,
+            menu_handle: PopoverMenuHandle::default(),
             _subscriptions: vec![settings_subscription],
         };
         this.refresh_profiles(cx);
@@ -38,6 +43,10 @@ impl ProfileSelector {
         this
     }
 
+    pub fn menu_handle(&self) -> PopoverMenuHandle<ContextMenu> {
+        self.menu_handle.clone()
+    }
+
     fn refresh_profiles(&mut self, cx: &mut Context<Self>) {
         let settings = AssistantSettings::get_global(cx);
 
@@ -106,7 +115,8 @@ impl Render for ProfileSelector {
             .unwrap_or_else(|| "Unknown".into());
 
         let this = cx.entity().clone();
-        PopoverMenu::new("tool-selector")
+        let focus_handle = self.focus_handle.clone();
+        PopoverMenu::new("profile-selector")
             .menu(move |window, cx| {
                 Some(this.update(cx, |this, cx| this.build_context_menu(window, cx)))
             })
@@ -114,8 +124,17 @@ impl Render for ProfileSelector {
                 Button::new("profile-selector-button", profile)
                     .style(ButtonStyle::Filled)
                     .label_size(LabelSize::Small),
-                Tooltip::text("Change Profile"),
+                move |window, cx| {
+                    Tooltip::for_action_in(
+                        "Change Profile",
+                        &ToggleProfileSelector,
+                        &focus_handle,
+                        window,
+                        cx,
+                    )
+                },
             )
             .anchor(gpui::Corner::BottomLeft)
+            .with_handle(self.menu_handle.clone())
     }
 }