diff --git a/crates/assistant2/src/assistant_configuration.rs b/crates/assistant2/src/assistant_configuration.rs index 81036c1915..64d024f68d 100644 --- a/crates/assistant2/src/assistant_configuration.rs +++ b/crates/assistant2/src/assistant_configuration.rs @@ -1,6 +1,5 @@ mod add_context_server_modal; mod manage_profiles_modal; -mod profile_picker; mod tool_picker; use std::sync::Arc; diff --git a/crates/assistant2/src/assistant_configuration/manage_profiles_modal.rs b/crates/assistant2/src/assistant_configuration/manage_profiles_modal.rs index dd226a3fc4..38377e27ff 100644 --- a/crates/assistant2/src/assistant_configuration/manage_profiles_modal.rs +++ b/crates/assistant2/src/assistant_configuration/manage_profiles_modal.rs @@ -15,19 +15,17 @@ use gpui::{ WeakEntity, }; use settings::{update_settings_file, Settings as _}; -use ui::{prelude::*, ListItem, ListItemSpacing, ListSeparator, Navigable, NavigableEntry}; +use ui::{ + prelude::*, KeyBinding, ListItem, ListItemSpacing, ListSeparator, Navigable, NavigableEntry, +}; use workspace::{ModalView, Workspace}; use crate::assistant_configuration::manage_profiles_modal::profile_modal_header::ProfileModalHeader; -use crate::assistant_configuration::profile_picker::{ProfilePicker, ProfilePickerDelegate}; use crate::assistant_configuration::tool_picker::{ToolPicker, ToolPickerDelegate}; use crate::{AssistantPanel, ManageProfiles, ThreadStore}; enum Mode { - ChooseProfile { - profile_picker: Entity, - _subscription: Subscription, - }, + ChooseProfile(ChooseProfileMode), NewProfile(NewProfileMode), ViewProfile(ViewProfileMode), ConfigureTools { @@ -38,35 +36,41 @@ enum Mode { } impl Mode { - pub fn choose_profile(window: &mut Window, cx: &mut Context) -> Self { - let this = cx.entity(); + pub fn choose_profile(_window: &mut Window, cx: &mut Context) -> Self { + let settings = AssistantSettings::get_global(cx); - let profile_picker = cx.new(|cx| { - let delegate = ProfilePickerDelegate::new( - move |profile_id, window, cx| { - this.update(cx, |this, cx| { - this.view_profile(profile_id.clone(), window, cx); - }) - }, - cx, - ); - ProfilePicker::new(delegate, window, cx) - }); - let dismiss_subscription = cx.subscribe_in( - &profile_picker, - window, - |_this, _profile_picker, _: &DismissEvent, _window, cx| { - cx.emit(DismissEvent); - }, - ); + let mut profiles = settings.profiles.clone(); + profiles.sort_unstable_by(|_, a, _, b| a.name.cmp(&b.name)); - Self::ChooseProfile { - profile_picker, - _subscription: dismiss_subscription, - } + let profiles = profiles + .into_iter() + .map(|(id, profile)| ProfileEntry { + id, + name: profile.name, + navigation: NavigableEntry::focusable(cx), + }) + .collect::>(); + + Self::ChooseProfile(ChooseProfileMode { + profiles, + add_new_profile: NavigableEntry::focusable(cx), + }) } } +#[derive(Clone)] +struct ProfileEntry { + pub id: Arc, + pub name: SharedString, + pub navigation: NavigableEntry, +} + +#[derive(Clone)] +pub struct ChooseProfileMode { + profiles: Vec, + add_new_profile: NavigableEntry, +} + #[derive(Clone)] pub struct ViewProfileMode { profile_id: Arc, @@ -234,7 +238,9 @@ impl ManageProfilesModal { fn cancel(&mut self, window: &mut Window, cx: &mut Context) { match &self.mode { - Mode::ChooseProfile { .. } => {} + Mode::ChooseProfile { .. } => { + cx.emit(DismissEvent); + } Mode::NewProfile(mode) => { if let Some(profile_id) = mode.base_profile_id.clone() { self.view_profile(profile_id, window, cx); @@ -290,7 +296,7 @@ impl ModalView for ManageProfilesModal {} impl Focusable for ManageProfilesModal { fn focus_handle(&self, cx: &App) -> FocusHandle { match &self.mode { - Mode::ChooseProfile { profile_picker, .. } => profile_picker.focus_handle(cx), + Mode::ChooseProfile(_) => self.focus_handle.clone(), Mode::NewProfile(mode) => mode.name_editor.focus_handle(cx), Mode::ViewProfile(_) => self.focus_handle.clone(), Mode::ConfigureTools { tool_picker, .. } => tool_picker.focus_handle(cx), @@ -301,6 +307,106 @@ impl Focusable for ManageProfilesModal { impl EventEmitter for ManageProfilesModal {} impl ManageProfilesModal { + fn render_choose_profile( + &mut self, + mode: ChooseProfileMode, + window: &mut Window, + cx: &mut Context, + ) -> impl IntoElement { + Navigable::new( + div() + .track_focus(&self.focus_handle(cx)) + .size_full() + .child(ProfileModalHeader::new( + "Agent Profiles", + IconName::ZedAssistant, + )) + .child( + v_flex() + .pb_1() + .child(ListSeparator) + .children(mode.profiles.iter().map(|profile| { + div() + .id(SharedString::from(format!("profile-{}", profile.id))) + .track_focus(&profile.navigation.focus_handle) + .on_action({ + let profile_id = profile.id.clone(); + cx.listener(move |this, _: &menu::Confirm, window, cx| { + this.view_profile(profile_id.clone(), window, cx); + }) + }) + .child( + ListItem::new(SharedString::from(format!( + "profile-{}", + profile.id + ))) + .toggle_state( + profile + .navigation + .focus_handle + .contains_focused(window, cx), + ) + .inset(true) + .spacing(ListItemSpacing::Sparse) + .child(Label::new(profile.name.clone())) + .end_slot( + h_flex() + .gap_1() + .child(Label::new("Customize").size(LabelSize::Small)) + .children(KeyBinding::for_action_in( + &menu::Confirm, + &self.focus_handle, + window, + cx, + )), + ) + .on_click({ + let profile_id = profile.id.clone(); + cx.listener(move |this, _, window, cx| { + this.new_profile(Some(profile_id.clone()), window, cx); + }) + }), + ) + })) + .child(ListSeparator) + .child( + div() + .id("new-profile") + .track_focus(&mode.add_new_profile.focus_handle) + .on_action(cx.listener(|this, _: &menu::Confirm, window, cx| { + this.new_profile(None, window, cx); + })) + .child( + ListItem::new("new-profile") + .toggle_state( + mode.add_new_profile + .focus_handle + .contains_focused(window, cx), + ) + .inset(true) + .spacing(ListItemSpacing::Sparse) + .start_slot(Icon::new(IconName::Plus)) + .child(Label::new("Add New Profile")) + .on_click({ + cx.listener(move |this, _, window, cx| { + this.new_profile(None, window, cx); + }) + }), + ), + ), + ) + .into_any_element(), + ) + .map(|mut navigable| { + for profile in mode.profiles { + navigable = navigable.entry(profile.navigation); + } + + navigable + }) + .entry(mode.add_new_profile) + } + fn render_new_profile( &mut self, mode: NewProfileMode, @@ -446,10 +552,8 @@ impl Render for ManageProfilesModal { })) .on_mouse_down_out(cx.listener(|_this, _, _, cx| cx.emit(DismissEvent))) .child(match &self.mode { - Mode::ChooseProfile { profile_picker, .. } => div() - .child(ProfileModalHeader::new("Profiles", IconName::ZedAssistant)) - .child(ListSeparator) - .child(profile_picker.clone()) + Mode::ChooseProfile(mode) => self + .render_choose_profile(mode.clone(), window, cx) .into_any_element(), Mode::NewProfile(mode) => self .render_new_profile(mode.clone(), window, cx) diff --git a/crates/assistant2/src/assistant_configuration/profile_picker.rs b/crates/assistant2/src/assistant_configuration/profile_picker.rs deleted file mode 100644 index 6ab480df17..0000000000 --- a/crates/assistant2/src/assistant_configuration/profile_picker.rs +++ /dev/null @@ -1,194 +0,0 @@ -use std::sync::Arc; - -use assistant_settings::AssistantSettings; -use fuzzy::{match_strings, StringMatch, StringMatchCandidate}; -use gpui::{ - App, Context, DismissEvent, Entity, EventEmitter, Focusable, SharedString, Task, WeakEntity, - Window, -}; -use picker::{Picker, PickerDelegate}; -use settings::Settings; -use ui::{prelude::*, HighlightedLabel, ListItem, ListItemSpacing}; -use util::ResultExt as _; - -pub struct ProfilePicker { - picker: Entity>, -} - -impl ProfilePicker { - pub fn new( - delegate: ProfilePickerDelegate, - window: &mut Window, - cx: &mut Context, - ) -> Self { - let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx).modal(false)); - Self { picker } - } -} - -impl EventEmitter for ProfilePicker {} - -impl Focusable for ProfilePicker { - fn focus_handle(&self, cx: &App) -> gpui::FocusHandle { - self.picker.focus_handle(cx) - } -} - -impl Render for ProfilePicker { - fn render(&mut self, _window: &mut Window, _cx: &mut Context) -> impl IntoElement { - v_flex().w(rems(34.)).child(self.picker.clone()) - } -} - -#[derive(Debug)] -pub struct ProfileEntry { - pub id: Arc, - pub name: SharedString, -} - -pub struct ProfilePickerDelegate { - profile_picker: WeakEntity, - profiles: Vec, - matches: Vec, - selected_index: usize, - on_confirm: Arc, &mut Window, &mut App) + 'static>, -} - -impl ProfilePickerDelegate { - pub fn new( - on_confirm: impl Fn(&Arc, &mut Window, &mut App) + 'static, - cx: &mut Context, - ) -> Self { - let settings = AssistantSettings::get_global(cx); - - let profiles = settings - .profiles - .iter() - .map(|(id, profile)| ProfileEntry { - id: id.clone(), - name: profile.name.clone(), - }) - .collect::>(); - - Self { - profile_picker: cx.entity().downgrade(), - profiles, - matches: Vec::new(), - selected_index: 0, - on_confirm: Arc::new(on_confirm), - } - } -} - -impl PickerDelegate for ProfilePickerDelegate { - type ListItem = ListItem; - - fn match_count(&self) -> usize { - self.matches.len() - } - - fn selected_index(&self) -> usize { - self.selected_index - } - - fn set_selected_index( - &mut self, - ix: usize, - _window: &mut Window, - _cx: &mut Context>, - ) { - self.selected_index = ix; - } - - fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc { - "Search profiles…".into() - } - - fn update_matches( - &mut self, - query: String, - window: &mut Window, - cx: &mut Context>, - ) -> Task<()> { - let background = cx.background_executor().clone(); - let candidates = self - .profiles - .iter() - .enumerate() - .map(|(id, profile)| StringMatchCandidate::new(id, profile.name.as_ref())) - .collect::>(); - - cx.spawn_in(window, async move |this, cx| { - let matches = if query.is_empty() { - candidates - .into_iter() - .enumerate() - .map(|(index, candidate)| StringMatch { - candidate_id: index, - string: candidate.string, - positions: Vec::new(), - score: 0., - }) - .collect() - } else { - match_strings( - &candidates, - &query, - false, - 100, - &Default::default(), - background, - ) - .await - }; - - this.update(cx, |this, _cx| { - this.delegate.matches = matches; - this.delegate.selected_index = this - .delegate - .selected_index - .min(this.delegate.matches.len().saturating_sub(1)); - }) - .log_err(); - }) - } - - fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context>) { - if self.matches.is_empty() { - self.dismissed(window, cx); - return; - } - - let candidate_id = self.matches[self.selected_index].candidate_id; - let profile = &self.profiles[candidate_id]; - - (self.on_confirm)(&profile.id, window, cx); - } - - fn dismissed(&mut self, _window: &mut Window, cx: &mut Context>) { - self.profile_picker - .update(cx, |_this, cx| cx.emit(DismissEvent)) - .log_err(); - } - - fn render_match( - &self, - ix: usize, - selected: bool, - _window: &mut Window, - _cx: &mut Context>, - ) -> Option { - let profile_match = &self.matches[ix]; - - Some( - ListItem::new(ix) - .inset(true) - .spacing(ListItemSpacing::Sparse) - .toggle_state(selected) - .child(HighlightedLabel::new( - profile_match.string.clone(), - profile_match.positions.clone(), - )), - ) - } -}