resolves #24655 resolves #23945 I haven't yet added a default binding for the new command. #27797 added `:ls` and `:buffers` which in my opinion should use the global searchable version given that that matches the vim semantics of those commands better than just showing the tabs in the local pane. There's also a question of what to do when you select a tab from another pane, should the focus jump to that pane or should that tab move to the currently focused pane? For now I've implemented the former. Release Notes: - Added `tab_switcher::ToggleAll` to search open tabs from all panes and focus the selected one. --------- Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
200 lines
7.1 KiB
Rust
200 lines
7.1 KiB
Rust
use std::sync::Arc;
|
|
|
|
use collections::HashMap;
|
|
use gpui::{AnyView, App, EventEmitter, FocusHandle, Focusable, Subscription, canvas};
|
|
use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
|
|
use ui::{ElevationIndex, prelude::*};
|
|
use workspace::Item;
|
|
|
|
pub struct ConfigurationView {
|
|
focus_handle: FocusHandle,
|
|
configuration_views: HashMap<LanguageModelProviderId, AnyView>,
|
|
_registry_subscription: Subscription,
|
|
}
|
|
|
|
impl ConfigurationView {
|
|
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
|
let focus_handle = cx.focus_handle();
|
|
|
|
let registry_subscription = cx.subscribe_in(
|
|
&LanguageModelRegistry::global(cx),
|
|
window,
|
|
|this, _, event: &language_model::Event, window, cx| match event {
|
|
language_model::Event::AddedProvider(provider_id) => {
|
|
let provider = LanguageModelRegistry::read_global(cx).provider(provider_id);
|
|
if let Some(provider) = provider {
|
|
this.add_configuration_view(&provider, window, cx);
|
|
}
|
|
}
|
|
language_model::Event::RemovedProvider(provider_id) => {
|
|
this.remove_configuration_view(provider_id);
|
|
}
|
|
_ => {}
|
|
},
|
|
);
|
|
|
|
let mut this = Self {
|
|
focus_handle,
|
|
configuration_views: HashMap::default(),
|
|
_registry_subscription: registry_subscription,
|
|
};
|
|
this.build_configuration_views(window, cx);
|
|
this
|
|
}
|
|
|
|
fn build_configuration_views(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
|
let providers = LanguageModelRegistry::read_global(cx).providers();
|
|
for provider in providers {
|
|
self.add_configuration_view(&provider, window, cx);
|
|
}
|
|
}
|
|
|
|
fn remove_configuration_view(&mut self, provider_id: &LanguageModelProviderId) {
|
|
self.configuration_views.remove(provider_id);
|
|
}
|
|
|
|
fn add_configuration_view(
|
|
&mut self,
|
|
provider: &Arc<dyn LanguageModelProvider>,
|
|
window: &mut Window,
|
|
cx: &mut Context<Self>,
|
|
) {
|
|
let configuration_view = provider.configuration_view(window, cx);
|
|
self.configuration_views
|
|
.insert(provider.id(), configuration_view);
|
|
}
|
|
|
|
fn render_provider_view(
|
|
&mut self,
|
|
provider: &Arc<dyn LanguageModelProvider>,
|
|
cx: &mut Context<Self>,
|
|
) -> Div {
|
|
let provider_id = provider.id().0.clone();
|
|
let provider_name = provider.name().0.clone();
|
|
let configuration_view = self.configuration_views.get(&provider.id()).cloned();
|
|
|
|
let open_new_context = cx.listener({
|
|
let provider = provider.clone();
|
|
move |_, _, _window, cx| {
|
|
cx.emit(ConfigurationViewEvent::NewProviderContextEditor(
|
|
provider.clone(),
|
|
))
|
|
}
|
|
});
|
|
|
|
v_flex()
|
|
.gap_2()
|
|
.child(
|
|
h_flex()
|
|
.justify_between()
|
|
.child(Headline::new(provider_name.clone()).size(HeadlineSize::Small))
|
|
.when(provider.is_authenticated(cx), move |this| {
|
|
this.child(
|
|
h_flex().justify_end().child(
|
|
Button::new(
|
|
SharedString::from(format!("new-context-{provider_id}")),
|
|
"Open New Chat",
|
|
)
|
|
.icon_position(IconPosition::Start)
|
|
.icon(IconName::Plus)
|
|
.style(ButtonStyle::Filled)
|
|
.layer(ElevationIndex::ModalSurface)
|
|
.on_click(open_new_context),
|
|
),
|
|
)
|
|
}),
|
|
)
|
|
.child(
|
|
div()
|
|
.p(DynamicSpacing::Base08.rems(cx))
|
|
.bg(cx.theme().colors().surface_background)
|
|
.border_1()
|
|
.border_color(cx.theme().colors().border_variant)
|
|
.rounded_sm()
|
|
.when(configuration_view.is_none(), |this| {
|
|
this.child(div().child(Label::new(format!(
|
|
"No configuration view for {}",
|
|
provider_name
|
|
))))
|
|
})
|
|
.when_some(configuration_view, |this, configuration_view| {
|
|
this.child(configuration_view)
|
|
}),
|
|
)
|
|
}
|
|
}
|
|
|
|
impl Render for ConfigurationView {
|
|
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
|
let providers = LanguageModelRegistry::read_global(cx).providers();
|
|
let provider_views = providers
|
|
.into_iter()
|
|
.map(|provider| self.render_provider_view(&provider, cx))
|
|
.collect::<Vec<_>>();
|
|
|
|
let mut element = v_flex()
|
|
.id("assistant-configuration-view")
|
|
.track_focus(&self.focus_handle(cx))
|
|
.bg(cx.theme().colors().editor_background)
|
|
.size_full()
|
|
.overflow_y_scroll()
|
|
.child(
|
|
v_flex()
|
|
.p(DynamicSpacing::Base16.rems(cx))
|
|
.border_b_1()
|
|
.border_color(cx.theme().colors().border)
|
|
.gap_1()
|
|
.child(Headline::new("Configure your Assistant").size(HeadlineSize::Medium))
|
|
.child(
|
|
Label::new(
|
|
"At least one LLM provider must be configured to use the Assistant.",
|
|
)
|
|
.color(Color::Muted),
|
|
),
|
|
)
|
|
.child(
|
|
v_flex()
|
|
.p(DynamicSpacing::Base16.rems(cx))
|
|
.mt_1()
|
|
.gap_6()
|
|
.flex_1()
|
|
.children(provider_views),
|
|
)
|
|
.into_any();
|
|
|
|
// We use a canvas here to get scrolling to work in the ConfigurationView. It's a workaround
|
|
// because we couldn't the element to take up the size of the parent.
|
|
canvas(
|
|
move |bounds, window, cx| {
|
|
element.prepaint_as_root(bounds.origin, bounds.size.into(), window, cx);
|
|
element
|
|
},
|
|
|_, mut element, window, cx| {
|
|
element.paint(window, cx);
|
|
},
|
|
)
|
|
.flex_1()
|
|
.w_full()
|
|
}
|
|
}
|
|
|
|
pub enum ConfigurationViewEvent {
|
|
NewProviderContextEditor(Arc<dyn LanguageModelProvider>),
|
|
}
|
|
|
|
impl EventEmitter<ConfigurationViewEvent> for ConfigurationView {}
|
|
|
|
impl Focusable for ConfigurationView {
|
|
fn focus_handle(&self, _: &App) -> FocusHandle {
|
|
self.focus_handle.clone()
|
|
}
|
|
}
|
|
|
|
impl Item for ConfigurationView {
|
|
type Event = ConfigurationViewEvent;
|
|
|
|
fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
|
|
"Configuration".into()
|
|
}
|
|
}
|