Allow to reuse PickerPopoverMenu outside of the model selector (#31684)
LSP button preparation step: move out the component that will be used to build the button's context menu. Release Notes: - N/A
This commit is contained in:
parent
45f9edcbb9
commit
f792827a01
5 changed files with 243 additions and 235 deletions
|
@ -1,10 +1,11 @@
|
||||||
use agent_settings::AgentSettings;
|
use agent_settings::AgentSettings;
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{Entity, FocusHandle, SharedString};
|
use gpui::{Entity, FocusHandle, SharedString};
|
||||||
|
use picker::popover_menu::PickerPopoverMenu;
|
||||||
|
|
||||||
use crate::Thread;
|
use crate::Thread;
|
||||||
use assistant_context_editor::language_model_selector::{
|
use assistant_context_editor::language_model_selector::{
|
||||||
LanguageModelSelector, LanguageModelSelectorPopoverMenu, ToggleModelSelector,
|
LanguageModelSelector, ToggleModelSelector, language_model_selector,
|
||||||
};
|
};
|
||||||
use language_model::{ConfiguredModel, LanguageModelRegistry};
|
use language_model::{ConfiguredModel, LanguageModelRegistry};
|
||||||
use settings::update_settings_file;
|
use settings::update_settings_file;
|
||||||
|
@ -35,7 +36,7 @@ impl AgentModelSelector {
|
||||||
Self {
|
Self {
|
||||||
selector: cx.new(move |cx| {
|
selector: cx.new(move |cx| {
|
||||||
let fs = fs.clone();
|
let fs = fs.clone();
|
||||||
LanguageModelSelector::new(
|
language_model_selector(
|
||||||
{
|
{
|
||||||
let model_type = model_type.clone();
|
let model_type = model_type.clone();
|
||||||
move |cx| match &model_type {
|
move |cx| match &model_type {
|
||||||
|
@ -100,15 +101,14 @@ impl AgentModelSelector {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for AgentModelSelector {
|
impl Render for AgentModelSelector {
|
||||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
let focus_handle = self.focus_handle.clone();
|
let focus_handle = self.focus_handle.clone();
|
||||||
|
|
||||||
let model = self.selector.read(cx).active_model(cx);
|
let model = self.selector.read(cx).delegate.active_model(cx);
|
||||||
let model_name = model
|
let model_name = model
|
||||||
.map(|model| model.model.name().0)
|
.map(|model| model.model.name().0)
|
||||||
.unwrap_or_else(|| SharedString::from("No model selected"));
|
.unwrap_or_else(|| SharedString::from("No model selected"));
|
||||||
|
PickerPopoverMenu::new(
|
||||||
LanguageModelSelectorPopoverMenu::new(
|
|
||||||
self.selector.clone(),
|
self.selector.clone(),
|
||||||
Button::new("active-model", model_name)
|
Button::new("active-model", model_name)
|
||||||
.label_size(LabelSize::Small)
|
.label_size(LabelSize::Small)
|
||||||
|
@ -127,7 +127,9 @@ impl Render for AgentModelSelector {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
gpui::Corner::BottomRight,
|
gpui::Corner::BottomRight,
|
||||||
|
cx,
|
||||||
)
|
)
|
||||||
.with_handle(self.menu_handle.clone())
|
.with_handle(self.menu_handle.clone())
|
||||||
|
.render(window, cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
language_model_selector::{
|
language_model_selector::{
|
||||||
LanguageModelSelector, LanguageModelSelectorPopoverMenu, ToggleModelSelector,
|
LanguageModelSelector, ToggleModelSelector, language_model_selector,
|
||||||
},
|
},
|
||||||
max_mode_tooltip::MaxModeTooltip,
|
max_mode_tooltip::MaxModeTooltip,
|
||||||
};
|
};
|
||||||
|
@ -43,7 +43,7 @@ use language_model::{
|
||||||
Role,
|
Role,
|
||||||
};
|
};
|
||||||
use multi_buffer::MultiBufferRow;
|
use multi_buffer::MultiBufferRow;
|
||||||
use picker::Picker;
|
use picker::{Picker, popover_menu::PickerPopoverMenu};
|
||||||
use project::{Project, Worktree};
|
use project::{Project, Worktree};
|
||||||
use project::{ProjectPath, lsp_store::LocalLspAdapterDelegate};
|
use project::{ProjectPath, lsp_store::LocalLspAdapterDelegate};
|
||||||
use rope::Point;
|
use rope::Point;
|
||||||
|
@ -283,7 +283,7 @@ impl ContextEditor {
|
||||||
slash_menu_handle: Default::default(),
|
slash_menu_handle: Default::default(),
|
||||||
dragged_file_worktrees: Vec::new(),
|
dragged_file_worktrees: Vec::new(),
|
||||||
language_model_selector: cx.new(|cx| {
|
language_model_selector: cx.new(|cx| {
|
||||||
LanguageModelSelector::new(
|
language_model_selector(
|
||||||
|cx| LanguageModelRegistry::read_global(cx).default_model(),
|
|cx| LanguageModelRegistry::read_global(cx).default_model(),
|
||||||
move |model, cx| {
|
move |model, cx| {
|
||||||
update_settings_file::<AgentSettings>(
|
update_settings_file::<AgentSettings>(
|
||||||
|
@ -2100,7 +2100,11 @@ impl ContextEditor {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_language_model_selector(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render_language_model_selector(
|
||||||
|
&self,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> impl IntoElement {
|
||||||
let active_model = LanguageModelRegistry::read_global(cx)
|
let active_model = LanguageModelRegistry::read_global(cx)
|
||||||
.default_model()
|
.default_model()
|
||||||
.map(|default| default.model);
|
.map(|default| default.model);
|
||||||
|
@ -2110,7 +2114,7 @@ impl ContextEditor {
|
||||||
None => SharedString::from("No model selected"),
|
None => SharedString::from("No model selected"),
|
||||||
};
|
};
|
||||||
|
|
||||||
LanguageModelSelectorPopoverMenu::new(
|
PickerPopoverMenu::new(
|
||||||
self.language_model_selector.clone(),
|
self.language_model_selector.clone(),
|
||||||
ButtonLike::new("active-model")
|
ButtonLike::new("active-model")
|
||||||
.style(ButtonStyle::Subtle)
|
.style(ButtonStyle::Subtle)
|
||||||
|
@ -2138,8 +2142,10 @@ impl ContextEditor {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
gpui::Corner::BottomLeft,
|
gpui::Corner::BottomLeft,
|
||||||
|
cx,
|
||||||
)
|
)
|
||||||
.with_handle(self.language_model_selector_menu_handle.clone())
|
.with_handle(self.language_model_selector_menu_handle.clone())
|
||||||
|
.render(window, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_last_error(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
|
fn render_last_error(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
|
||||||
|
@ -2615,7 +2621,7 @@ impl Render for ContextEditor {
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.child(self.render_language_model_selector(cx))
|
.child(self.render_language_model_selector(window, cx))
|
||||||
.child(self.render_send_button(window, cx)),
|
.child(self.render_send_button(window, cx)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,8 +4,7 @@ use collections::{HashSet, IndexMap};
|
||||||
use feature_flags::ZedProFeatureFlag;
|
use feature_flags::ZedProFeatureFlag;
|
||||||
use fuzzy::{StringMatch, StringMatchCandidate, match_strings};
|
use fuzzy::{StringMatch, StringMatchCandidate, match_strings};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
Action, AnyElement, AnyView, App, BackgroundExecutor, Corner, DismissEvent, Entity,
|
Action, AnyElement, App, BackgroundExecutor, DismissEvent, Subscription, Task,
|
||||||
EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity,
|
|
||||||
action_with_deprecated_aliases,
|
action_with_deprecated_aliases,
|
||||||
};
|
};
|
||||||
use language_model::{
|
use language_model::{
|
||||||
|
@ -15,7 +14,7 @@ use language_model::{
|
||||||
use ordered_float::OrderedFloat;
|
use ordered_float::OrderedFloat;
|
||||||
use picker::{Picker, PickerDelegate};
|
use picker::{Picker, PickerDelegate};
|
||||||
use proto::Plan;
|
use proto::Plan;
|
||||||
use ui::{ListItem, ListItemSpacing, PopoverMenu, PopoverMenuHandle, PopoverTrigger, prelude::*};
|
use ui::{ListItem, ListItemSpacing, prelude::*};
|
||||||
|
|
||||||
action_with_deprecated_aliases!(
|
action_with_deprecated_aliases!(
|
||||||
agent,
|
agent,
|
||||||
|
@ -31,77 +30,146 @@ const TRY_ZED_PRO_URL: &str = "https://zed.dev/pro";
|
||||||
type OnModelChanged = Arc<dyn Fn(Arc<dyn LanguageModel>, &mut App) + 'static>;
|
type OnModelChanged = Arc<dyn Fn(Arc<dyn LanguageModel>, &mut App) + 'static>;
|
||||||
type GetActiveModel = Arc<dyn Fn(&App) -> Option<ConfiguredModel> + 'static>;
|
type GetActiveModel = Arc<dyn Fn(&App) -> Option<ConfiguredModel> + 'static>;
|
||||||
|
|
||||||
pub struct LanguageModelSelector {
|
pub type LanguageModelSelector = Picker<LanguageModelPickerDelegate>;
|
||||||
picker: Entity<Picker<LanguageModelPickerDelegate>>,
|
|
||||||
|
pub fn language_model_selector(
|
||||||
|
get_active_model: impl Fn(&App) -> Option<ConfiguredModel> + 'static,
|
||||||
|
on_model_changed: impl Fn(Arc<dyn LanguageModel>, &mut App) + 'static,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<LanguageModelSelector>,
|
||||||
|
) -> LanguageModelSelector {
|
||||||
|
let delegate = LanguageModelPickerDelegate::new(get_active_model, on_model_changed, window, cx);
|
||||||
|
Picker::list(delegate, window, cx)
|
||||||
|
.show_scrollbar(true)
|
||||||
|
.width(rems(20.))
|
||||||
|
.max_height(Some(rems(20.).into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn all_models(cx: &App) -> GroupedModels {
|
||||||
|
let mut recommended = Vec::new();
|
||||||
|
let mut recommended_set = HashSet::default();
|
||||||
|
for provider in LanguageModelRegistry::global(cx)
|
||||||
|
.read(cx)
|
||||||
|
.providers()
|
||||||
|
.iter()
|
||||||
|
{
|
||||||
|
let models = provider.recommended_models(cx);
|
||||||
|
recommended_set.extend(models.iter().map(|model| (model.provider_id(), model.id())));
|
||||||
|
recommended.extend(
|
||||||
|
provider
|
||||||
|
.recommended_models(cx)
|
||||||
|
.into_iter()
|
||||||
|
.map(move |model| ModelInfo {
|
||||||
|
model: model.clone(),
|
||||||
|
icon: provider.icon(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let other_models = LanguageModelRegistry::global(cx)
|
||||||
|
.read(cx)
|
||||||
|
.providers()
|
||||||
|
.iter()
|
||||||
|
.map(|provider| {
|
||||||
|
(
|
||||||
|
provider.id(),
|
||||||
|
provider
|
||||||
|
.provided_models(cx)
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|model| {
|
||||||
|
let not_included =
|
||||||
|
!recommended_set.contains(&(model.provider_id(), model.id()));
|
||||||
|
not_included.then(|| ModelInfo {
|
||||||
|
model: model.clone(),
|
||||||
|
icon: provider.icon(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<IndexMap<_, _>>();
|
||||||
|
|
||||||
|
GroupedModels {
|
||||||
|
recommended,
|
||||||
|
other: other_models,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct ModelInfo {
|
||||||
|
model: Arc<dyn LanguageModel>,
|
||||||
|
icon: IconName,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct LanguageModelPickerDelegate {
|
||||||
|
on_model_changed: OnModelChanged,
|
||||||
|
get_active_model: GetActiveModel,
|
||||||
|
all_models: Arc<GroupedModels>,
|
||||||
|
filtered_entries: Vec<LanguageModelPickerEntry>,
|
||||||
|
selected_index: usize,
|
||||||
_authenticate_all_providers_task: Task<()>,
|
_authenticate_all_providers_task: Task<()>,
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LanguageModelSelector {
|
impl LanguageModelPickerDelegate {
|
||||||
pub fn new(
|
fn new(
|
||||||
get_active_model: impl Fn(&App) -> Option<ConfiguredModel> + 'static,
|
get_active_model: impl Fn(&App) -> Option<ConfiguredModel> + 'static,
|
||||||
on_model_changed: impl Fn(Arc<dyn LanguageModel>, &mut App) + 'static,
|
on_model_changed: impl Fn(Arc<dyn LanguageModel>, &mut App) + 'static,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Picker<Self>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let on_model_changed = Arc::new(on_model_changed);
|
let on_model_changed = Arc::new(on_model_changed);
|
||||||
|
let models = all_models(cx);
|
||||||
|
let entries = models.entries();
|
||||||
|
|
||||||
let all_models = Self::all_models(cx);
|
Self {
|
||||||
let entries = all_models.entries();
|
|
||||||
|
|
||||||
let delegate = LanguageModelPickerDelegate {
|
|
||||||
language_model_selector: cx.entity().downgrade(),
|
|
||||||
on_model_changed: on_model_changed.clone(),
|
on_model_changed: on_model_changed.clone(),
|
||||||
all_models: Arc::new(all_models),
|
all_models: Arc::new(models),
|
||||||
selected_index: Self::get_active_model_index(&entries, get_active_model(cx)),
|
selected_index: Self::get_active_model_index(&entries, get_active_model(cx)),
|
||||||
filtered_entries: entries,
|
filtered_entries: entries,
|
||||||
get_active_model: Arc::new(get_active_model),
|
get_active_model: Arc::new(get_active_model),
|
||||||
};
|
|
||||||
|
|
||||||
let picker = cx.new(|cx| {
|
|
||||||
Picker::list(delegate, window, cx)
|
|
||||||
.show_scrollbar(true)
|
|
||||||
.width(rems(20.))
|
|
||||||
.max_height(Some(rems(20.).into()))
|
|
||||||
});
|
|
||||||
|
|
||||||
let subscription = cx.subscribe(&picker, |_, _, _, cx| cx.emit(DismissEvent));
|
|
||||||
|
|
||||||
LanguageModelSelector {
|
|
||||||
picker,
|
|
||||||
_authenticate_all_providers_task: Self::authenticate_all_providers(cx),
|
_authenticate_all_providers_task: Self::authenticate_all_providers(cx),
|
||||||
_subscriptions: vec![
|
_subscriptions: vec![cx.subscribe_in(
|
||||||
cx.subscribe_in(
|
&LanguageModelRegistry::global(cx),
|
||||||
&LanguageModelRegistry::global(cx),
|
window,
|
||||||
window,
|
|picker, _, event, window, cx| {
|
||||||
Self::handle_language_model_registry_event,
|
match event {
|
||||||
),
|
language_model::Event::ProviderStateChanged
|
||||||
subscription,
|
| language_model::Event::AddedProvider(_)
|
||||||
],
|
| language_model::Event::RemovedProvider(_) => {
|
||||||
|
let query = picker.query(cx);
|
||||||
|
picker.delegate.all_models = Arc::new(all_models(cx));
|
||||||
|
// Update matches will automatically drop the previous task
|
||||||
|
// if we get a provider event again
|
||||||
|
picker.update_matches(query, window, cx)
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_language_model_registry_event(
|
fn get_active_model_index(
|
||||||
&mut self,
|
entries: &[LanguageModelPickerEntry],
|
||||||
_registry: &Entity<LanguageModelRegistry>,
|
active_model: Option<ConfiguredModel>,
|
||||||
event: &language_model::Event,
|
) -> usize {
|
||||||
window: &mut Window,
|
entries
|
||||||
cx: &mut Context<Self>,
|
.iter()
|
||||||
) {
|
.position(|entry| {
|
||||||
match event {
|
if let LanguageModelPickerEntry::Model(model) = entry {
|
||||||
language_model::Event::ProviderStateChanged
|
active_model
|
||||||
| language_model::Event::AddedProvider(_)
|
.as_ref()
|
||||||
| language_model::Event::RemovedProvider(_) => {
|
.map(|active_model| {
|
||||||
self.picker.update(cx, |this, cx| {
|
active_model.model.id() == model.model.id()
|
||||||
let query = this.query(cx);
|
&& active_model.provider.id() == model.model.provider_id()
|
||||||
this.delegate.all_models = Arc::new(Self::all_models(cx));
|
})
|
||||||
// Update matches will automatically drop the previous task
|
.unwrap_or_default()
|
||||||
// if we get a provider event again
|
} else {
|
||||||
this.update_matches(query, window, cx)
|
false
|
||||||
});
|
}
|
||||||
}
|
})
|
||||||
_ => {}
|
.unwrap_or(0)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Authenticates all providers in the [`LanguageModelRegistry`].
|
/// Authenticates all providers in the [`LanguageModelRegistry`].
|
||||||
|
@ -154,169 +222,9 @@ impl LanguageModelSelector {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn all_models(cx: &App) -> GroupedModels {
|
|
||||||
let mut recommended = Vec::new();
|
|
||||||
let mut recommended_set = HashSet::default();
|
|
||||||
for provider in LanguageModelRegistry::global(cx)
|
|
||||||
.read(cx)
|
|
||||||
.providers()
|
|
||||||
.iter()
|
|
||||||
{
|
|
||||||
let models = provider.recommended_models(cx);
|
|
||||||
recommended_set.extend(models.iter().map(|model| (model.provider_id(), model.id())));
|
|
||||||
recommended.extend(
|
|
||||||
provider
|
|
||||||
.recommended_models(cx)
|
|
||||||
.into_iter()
|
|
||||||
.map(move |model| ModelInfo {
|
|
||||||
model: model.clone(),
|
|
||||||
icon: provider.icon(),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let other_models = LanguageModelRegistry::global(cx)
|
|
||||||
.read(cx)
|
|
||||||
.providers()
|
|
||||||
.iter()
|
|
||||||
.map(|provider| {
|
|
||||||
(
|
|
||||||
provider.id(),
|
|
||||||
provider
|
|
||||||
.provided_models(cx)
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|model| {
|
|
||||||
let not_included =
|
|
||||||
!recommended_set.contains(&(model.provider_id(), model.id()));
|
|
||||||
not_included.then(|| ModelInfo {
|
|
||||||
model: model.clone(),
|
|
||||||
icon: provider.icon(),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<IndexMap<_, _>>();
|
|
||||||
|
|
||||||
GroupedModels {
|
|
||||||
recommended,
|
|
||||||
other: other_models,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn active_model(&self, cx: &App) -> Option<ConfiguredModel> {
|
pub fn active_model(&self, cx: &App) -> Option<ConfiguredModel> {
|
||||||
(self.picker.read(cx).delegate.get_active_model)(cx)
|
(self.get_active_model)(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_active_model_index(
|
|
||||||
entries: &[LanguageModelPickerEntry],
|
|
||||||
active_model: Option<ConfiguredModel>,
|
|
||||||
) -> usize {
|
|
||||||
entries
|
|
||||||
.iter()
|
|
||||||
.position(|entry| {
|
|
||||||
if let LanguageModelPickerEntry::Model(model) = entry {
|
|
||||||
active_model
|
|
||||||
.as_ref()
|
|
||||||
.map(|active_model| {
|
|
||||||
active_model.model.id() == model.model.id()
|
|
||||||
&& active_model.provider.id() == model.model.provider_id()
|
|
||||||
})
|
|
||||||
.unwrap_or_default()
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap_or(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EventEmitter<DismissEvent> for LanguageModelSelector {}
|
|
||||||
|
|
||||||
impl Focusable for LanguageModelSelector {
|
|
||||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
|
||||||
self.picker.focus_handle(cx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Render for LanguageModelSelector {
|
|
||||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
|
||||||
self.picker.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
|
||||||
pub struct LanguageModelSelectorPopoverMenu<T, TT>
|
|
||||||
where
|
|
||||||
T: PopoverTrigger + ButtonCommon,
|
|
||||||
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
|
|
||||||
{
|
|
||||||
language_model_selector: Entity<LanguageModelSelector>,
|
|
||||||
trigger: T,
|
|
||||||
tooltip: TT,
|
|
||||||
handle: Option<PopoverMenuHandle<LanguageModelSelector>>,
|
|
||||||
anchor: Corner,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, TT> LanguageModelSelectorPopoverMenu<T, TT>
|
|
||||||
where
|
|
||||||
T: PopoverTrigger + ButtonCommon,
|
|
||||||
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
|
|
||||||
{
|
|
||||||
pub fn new(
|
|
||||||
language_model_selector: Entity<LanguageModelSelector>,
|
|
||||||
trigger: T,
|
|
||||||
tooltip: TT,
|
|
||||||
anchor: Corner,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
language_model_selector,
|
|
||||||
trigger,
|
|
||||||
tooltip,
|
|
||||||
handle: None,
|
|
||||||
anchor,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_handle(mut self, handle: PopoverMenuHandle<LanguageModelSelector>) -> Self {
|
|
||||||
self.handle = Some(handle);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, TT> RenderOnce for LanguageModelSelectorPopoverMenu<T, TT>
|
|
||||||
where
|
|
||||||
T: PopoverTrigger + ButtonCommon,
|
|
||||||
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
|
|
||||||
{
|
|
||||||
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
|
|
||||||
let language_model_selector = self.language_model_selector.clone();
|
|
||||||
|
|
||||||
PopoverMenu::new("model-switcher")
|
|
||||||
.menu(move |_window, _cx| Some(language_model_selector.clone()))
|
|
||||||
.trigger_with_tooltip(self.trigger, self.tooltip)
|
|
||||||
.anchor(self.anchor)
|
|
||||||
.when_some(self.handle.clone(), |menu, handle| menu.with_handle(handle))
|
|
||||||
.offset(gpui::Point {
|
|
||||||
x: px(0.0),
|
|
||||||
y: px(-2.0),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct ModelInfo {
|
|
||||||
model: Arc<dyn LanguageModel>,
|
|
||||||
icon: IconName,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct LanguageModelPickerDelegate {
|
|
||||||
language_model_selector: WeakEntity<LanguageModelSelector>,
|
|
||||||
on_model_changed: OnModelChanged,
|
|
||||||
get_active_model: GetActiveModel,
|
|
||||||
all_models: Arc<GroupedModels>,
|
|
||||||
filtered_entries: Vec<LanguageModelPickerEntry>,
|
|
||||||
selected_index: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct GroupedModels {
|
struct GroupedModels {
|
||||||
|
@ -577,9 +485,7 @@ impl PickerDelegate for LanguageModelPickerDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dismissed(&mut self, _: &mut Window, cx: &mut Context<Picker<Self>>) {
|
fn dismissed(&mut self, _: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||||
self.language_model_selector
|
cx.emit(DismissEvent);
|
||||||
.update(cx, |_this, cx| cx.emit(DismissEvent))
|
|
||||||
.ok();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_match(
|
fn render_match(
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
mod head;
|
||||||
|
pub mod highlighted_match_with_paths;
|
||||||
|
pub mod popover_menu;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use editor::{
|
use editor::{
|
||||||
Editor,
|
Editor,
|
||||||
|
@ -20,9 +24,6 @@ use ui::{
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::ModalView;
|
use workspace::ModalView;
|
||||||
|
|
||||||
mod head;
|
|
||||||
pub mod highlighted_match_with_paths;
|
|
||||||
|
|
||||||
enum ElementContainer {
|
enum ElementContainer {
|
||||||
List(ListState),
|
List(ListState),
|
||||||
UniformList(UniformListScrollHandle),
|
UniformList(UniformListScrollHandle),
|
||||||
|
|
93
crates/picker/src/popover_menu.rs
Normal file
93
crates/picker/src/popover_menu.rs
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
use gpui::{
|
||||||
|
AnyView, Corner, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Subscription,
|
||||||
|
};
|
||||||
|
use ui::{
|
||||||
|
App, ButtonCommon, FluentBuilder as _, IntoElement, PopoverMenu, PopoverMenuHandle,
|
||||||
|
PopoverTrigger, RenderOnce, Window, px,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{Picker, PickerDelegate};
|
||||||
|
|
||||||
|
pub struct PickerPopoverMenu<T, TT, P>
|
||||||
|
where
|
||||||
|
T: PopoverTrigger + ButtonCommon,
|
||||||
|
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
|
||||||
|
P: PickerDelegate,
|
||||||
|
{
|
||||||
|
picker: Entity<Picker<P>>,
|
||||||
|
trigger: T,
|
||||||
|
tooltip: TT,
|
||||||
|
handle: Option<PopoverMenuHandle<Picker<P>>>,
|
||||||
|
anchor: Corner,
|
||||||
|
_subscriptions: Vec<Subscription>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, TT, P> PickerPopoverMenu<T, TT, P>
|
||||||
|
where
|
||||||
|
T: PopoverTrigger + ButtonCommon,
|
||||||
|
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
|
||||||
|
P: PickerDelegate,
|
||||||
|
{
|
||||||
|
pub fn new(
|
||||||
|
picker: Entity<Picker<P>>,
|
||||||
|
trigger: T,
|
||||||
|
tooltip: TT,
|
||||||
|
anchor: Corner,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
_subscriptions: vec![cx.subscribe(&picker, |picker, &DismissEvent, cx| {
|
||||||
|
picker.update(cx, |_, cx| cx.emit(DismissEvent));
|
||||||
|
})],
|
||||||
|
picker,
|
||||||
|
trigger,
|
||||||
|
tooltip,
|
||||||
|
handle: None,
|
||||||
|
anchor,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_handle(mut self, handle: PopoverMenuHandle<Picker<P>>) -> Self {
|
||||||
|
self.handle = Some(handle);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, TT, P> EventEmitter<DismissEvent> for PickerPopoverMenu<T, TT, P>
|
||||||
|
where
|
||||||
|
T: PopoverTrigger + ButtonCommon,
|
||||||
|
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
|
||||||
|
P: PickerDelegate,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, TT, P> Focusable for PickerPopoverMenu<T, TT, P>
|
||||||
|
where
|
||||||
|
T: PopoverTrigger + ButtonCommon,
|
||||||
|
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
|
||||||
|
P: PickerDelegate,
|
||||||
|
{
|
||||||
|
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||||
|
self.picker.focus_handle(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, TT, P> RenderOnce for PickerPopoverMenu<T, TT, P>
|
||||||
|
where
|
||||||
|
T: PopoverTrigger + ButtonCommon,
|
||||||
|
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
|
||||||
|
P: PickerDelegate,
|
||||||
|
{
|
||||||
|
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
|
||||||
|
let picker = self.picker.clone();
|
||||||
|
PopoverMenu::new("popover-menu")
|
||||||
|
.menu(move |_window, _cx| Some(picker.clone()))
|
||||||
|
.trigger_with_tooltip(self.trigger, self.tooltip)
|
||||||
|
.anchor(self.anchor)
|
||||||
|
.when_some(self.handle.clone(), |menu, handle| menu.with_handle(handle))
|
||||||
|
.offset(gpui::Point {
|
||||||
|
x: px(0.0),
|
||||||
|
y: px(-2.0),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue