Make the branch picker in the commit modal a popover (#25697)

Release Notes:

- N/A

---------

Co-authored-by: Nate Butler <iamnbutler@gmail.com>
This commit is contained in:
Mikayla Maki 2025-02-26 17:56:07 -08:00 committed by GitHub
parent 11838cf89e
commit 8ba7b349a5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 455 additions and 334 deletions

View file

@ -21,3 +21,4 @@ proto.workspace = true
ui.workspace = true
workspace.workspace = true
zed_actions.workspace = true
popover_button.workspace = true

View file

@ -2,17 +2,26 @@ use std::sync::Arc;
use feature_flags::ZedPro;
use gpui::{
Action, AnyElement, AnyView, App, Corner, DismissEvent, Entity, EventEmitter, FocusHandle,
Focusable, Subscription, Task, WeakEntity,
action_with_deprecated_aliases, Action, AnyElement, App, Corner, DismissEvent, Entity,
EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity,
};
use language_model::{
AuthenticateError, LanguageModel, LanguageModelAvailability, LanguageModelRegistry,
};
use picker::{Picker, PickerDelegate};
use popover_button::{PopoverButton, TriggerablePopover};
use proto::Plan;
use ui::{prelude::*, ListItem, ListItemSpacing, PopoverMenu, PopoverMenuHandle, PopoverTrigger};
use ui::{
prelude::*, ButtonLike, IconButtonShape, ListItem, ListItemSpacing, PopoverMenuHandle, Tooltip,
};
use workspace::ShowConfiguration;
action_with_deprecated_aliases!(
assistant,
ToggleModelSelector,
["assistant2::ToggleModelSelector"]
);
const TRY_ZED_PRO_URL: &str = "https://zed.dev/pro";
type OnModelChanged = Arc<dyn Fn(Arc<dyn LanguageModel>, &App) + 'static>;
@ -22,6 +31,7 @@ pub struct LanguageModelSelector {
/// The task used to update the picker's matches when there is a change to
/// the language model registry.
update_matches_task: Option<Task<()>>,
popover_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
_authenticate_all_providers_task: Task<()>,
_subscriptions: Vec<Subscription>,
}
@ -53,6 +63,7 @@ impl LanguageModelSelector {
LanguageModelSelector {
picker,
update_matches_task: None,
popover_menu_handle: PopoverMenuHandle::default(),
_authenticate_all_providers_task: Self::authenticate_all_providers(cx),
_subscriptions: vec![cx.subscribe_in(
&LanguageModelRegistry::global(cx),
@ -62,6 +73,15 @@ impl LanguageModelSelector {
}
}
pub fn toggle_model_selector(
&mut self,
_: &ToggleModelSelector,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.popover_menu_handle.toggle(window, cx);
}
fn handle_language_model_registry_event(
&mut self,
_registry: &Entity<LanguageModelRegistry>,
@ -181,62 +201,13 @@ impl Render for LanguageModelSelector {
}
}
#[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),
})
impl TriggerablePopover for LanguageModelSelector {
fn menu_handle(
&mut self,
_window: &mut Window,
_cx: &mut gpui::Context<Self>,
) -> PopoverMenuHandle<Self> {
self.popover_menu_handle.clone()
}
}
@ -521,3 +492,98 @@ impl PickerDelegate for LanguageModelPickerDelegate {
)
}
}
pub struct InlineLanguageModelSelector {
selector: Entity<LanguageModelSelector>,
}
impl InlineLanguageModelSelector {
pub fn new(selector: Entity<LanguageModelSelector>) -> Self {
Self { selector }
}
}
impl RenderOnce for InlineLanguageModelSelector {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
PopoverButton::new(
self.selector,
gpui::Corner::TopRight,
IconButton::new("context", IconName::SettingsAlt)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.icon_color(Color::Muted),
move |window, cx| {
Tooltip::with_meta(
format!(
"Using {}",
LanguageModelRegistry::read_global(cx)
.active_model()
.map(|model| model.name().0)
.unwrap_or_else(|| "No model selected".into()),
),
None,
"Change Model",
window,
cx,
)
},
)
.render(window, cx)
}
}
pub struct AssistantLanguageModelSelector {
focus_handle: FocusHandle,
selector: Entity<LanguageModelSelector>,
}
impl AssistantLanguageModelSelector {
pub fn new(focus_handle: FocusHandle, selector: Entity<LanguageModelSelector>) -> Self {
Self {
focus_handle,
selector,
}
}
}
impl RenderOnce for AssistantLanguageModelSelector {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let active_model = LanguageModelRegistry::read_global(cx).active_model();
let focus_handle = self.focus_handle.clone();
let model_name = match active_model {
Some(model) => model.name().0,
_ => SharedString::from("No model selected"),
};
popover_button::PopoverButton::new(
self.selector.clone(),
Corner::BottomRight,
ButtonLike::new("active-model")
.style(ButtonStyle::Subtle)
.child(
h_flex()
.gap_0p5()
.child(
Label::new(model_name)
.size(LabelSize::Small)
.color(Color::Muted),
)
.child(
Icon::new(IconName::ChevronDown)
.color(Color::Muted)
.size(IconSize::XSmall),
),
),
move |window, cx| {
Tooltip::for_action_in(
"Change Model",
&ToggleModelSelector,
&focus_handle,
window,
cx,
)
},
)
.render(window, cx)
}
}