mod configure_context_server_modal;mod manage_profiles_modal;mod tool_picker; use std::{sync::Arc, time::Duration}; use agent_settings::AgentSettings;use assistant_tool::{ToolSource, ToolWorkingSet};use collections::HashMap;use context_server::ContextServerId;use extension::ExtensionManifest;use extension_host::ExtensionStore;use fs::Fs;use gpui::{ Action, Animation, AnimationExt as _, AnyView, App, Corner, Entity, EventEmitter, FocusHandle, Focusable, ScrollHandle, Subscription, Task, Transformation, WeakEntity, percentage,};use language::LanguageRegistry;use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};use notifications::status_toast::{StatusToast, ToastIcon};use project::{ context_server_store::{ContextServerConfiguration, ContextServerStatus, ContextServerStore}, project_settings::{ContextServerSettings, ProjectSettings},};use settings::{Settings, update_settings_file};use ui::{ ContextMenu, Disclosure, ElevationIndex, Indicator, PopoverMenu, Scrollbar, ScrollbarState, Switch, SwitchColor, Tooltip, prelude::*,};use util::ResultExt as _;use workspace::Workspace;use zed_actions::ExtensionCategoryFilter; pub(crate) use configure_context_server_modal::ConfigureContextServerModal;pub(crate) use manage_profiles_modal::ManageProfilesModal; use crate::AddContextServer; pub struct AgentConfiguration { fs: Arc<dyn Fs>, language_registry: Arc<LanguageRegistry>, workspace: WeakEntity<Workspace>, focus_handle: FocusHandle, configuration_views_by_provider: HashMap<LanguageModelProviderId, AnyView>, context_server_store: Entity<ContextServerStore>, expanded_context_server_tools: HashMap<ContextServerId, bool>, expanded_provider_configurations: HashMap<LanguageModelProviderId, bool>, tools: Entity<ToolWorkingSet>, _registry_subscription: Subscription, scroll_handle: ScrollHandle, scrollbar_state: ScrollbarState,} impl AgentConfiguration { pub fn new( fs: Arc<dyn Fs>, context_server_store: Entity<ContextServerStore>, tools: Entity<ToolWorkingSet>, language_registry: Arc<LanguageRegistry>, workspace: WeakEntity<Workspace>, 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_provider_configuration_view(&provider, window, cx); } } language_model::Event::RemovedProvider(provider_id) => { this.remove_provider_configuration_view(provider_id); } _ => {} }, ); cx.subscribe(&context_server_store, |_, _, _, cx| cx.notify()) .detach(); let scroll_handle = ScrollHandle::new(); let scrollbar_state = ScrollbarState::new(scroll_handle.clone()); let mut this = Self { fs, language_registry, workspace, focus_handle, configuration_views_by_provider: HashMap::default(), context_server_store, expanded_context_server_tools: HashMap::default(), expanded_provider_configurations: HashMap::default(), tools, _registry_subscription: registry_subscription, scroll_handle, scrollbar_state, }; this.build_provider_configuration_views(window, cx); this } fn build_provider_configuration_views(&mut self, window: &mut Window, cx: &mut Context<Self>) { let providers = LanguageModelRegistry::read_global(cx).providers(); for provider in providers { self.add_provider_configuration_view(&provider, window, cx); } } fn remove_provider_configuration_view(&mut self, provider_id: &LanguageModelProviderId) { self.configuration_views_by_provider.remove(provider_id); self.expanded_provider_configurations.remove(provider_id); } fn add_provider_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_by_provider .insert(provider.id(), configuration_view); }} impl Focusable for AgentConfiguration { fn focus_handle(&self, _: &App) -> FocusHandle { self.focus_handle.clone() }} pub enum AssistantConfigurationEvent { NewThread(Arc<dyn LanguageModelProvider>),} impl EventEmitter<AssistantConfigurationEvent> for AgentConfiguration {} impl AgentConfiguration { fn render_provider_configuration_block( &mut self, provider: &Arc<dyn LanguageModelProvider>, cx: &mut Context<Self>, ) -> impl IntoElement + use<> { let provider_id = provider.id().0.clone(); let provider_name = provider.name().0.clone(); let provider_id_string = SharedString::from(format!("provider-disclosure-{provider_id}")); let configuration_view = self .configuration_views_by_provider .get(&provider.id()) .cloned(); let is_expanded = self .expanded_provider_configurations .get(&provider.id()) .copied() .unwrap_or(false); v_flex() .py_2() .gap_1p5() .border_t_1() .border_color(cx.theme().colors().border.opacity(0.6)) .child( h_flex() .w_full() .gap_1() .justify_between() .child( h_flex() .id(provider_id_string.clone()) .cursor_pointer() .py_0p5() .w_full() .justify_between() .rounded_sm() .hover(|hover| hover.bg(cx.theme().colors().element_hover)) .child( h_flex() .gap_2() .child( Icon::new(provider.icon()) .size(IconSize::Small) .color(Color::Muted), ) .child(Label::new(provider_name.clone()).size(LabelSize::Large)) .when( provider.is_authenticated(cx) && !is_expanded, |parent| { parent.child( Icon::new(IconName::Check).color(Color::Success), ) }, ), ) .child( Disclosure::new(provider_id_string, is_expanded) .opened_icon(IconName::ChevronUp) .closed_icon(IconName::ChevronDown), ) .on_click(cx.listener({ let provider_id = provider.id().clone(); move |this, _event, _window, _cx| { let is_expanded = this .expanded_provider_configurations .entry(provider_id.clone()) .or_insert(false); *is_expanded = !*is_expanded; } })), ) .when(provider.is_authenticated(cx), |parent| { parent.child( Button::new( SharedString::from(format!("new-thread-{provider_id}")), "Start New Thread", ) .icon_position(IconPosition::Start) .icon(IconName::Plus) .icon_size(IconSize::Small) .icon_color(Color::Muted) .label_size(LabelSize::Small) .on_click(cx.listener({ let provider = provider.clone(); move |_this, _event, _window, cx| { cx.emit(AssistantConfigurationEvent::NewThread( provider.clone(), )) } })), ) }), ) .when(is_expanded, |parent| match configuration_view { Some(configuration_view) => parent.child(configuration_view), None => parent.child(Label::new(format!( "No configuration view for {provider_name}", ))), }) } fn render_provider_configuration_section( &mut self, cx: &mut Context<Self>, ) -> impl IntoElement { let providers = LanguageModelRegistry::read_global(cx).providers(); v_flex() .p(DynamicSpacing::Base16.rems(cx)) .pr(DynamicSpacing::Base20.rems(cx)) .border_b_1() .border_color(cx.theme().colors().border) .child( v_flex() .mb_2p5() .gap_0p5() .child(Headline::new("LLM Providers")) .child( Label::new("Add at least one provider to use AI-powered features.") .color(Color::Muted), ), ) .children( providers .into_iter() .map(|provider| self.render_provider_configuration_block(&provider, cx)), ) } fn render_command_permission(&mut self, cx: &mut Context<Self>) -> impl IntoElement { let always_allow_tool_actions = AgentSettings::get_global(cx).always_allow_tool_actions; h_flex() .gap_4() .justify_between() .flex_wrap() .child( v_flex() .gap_0p5() .max_w_5_6() .child(Label::new("Allow running editing tools without asking for confirmation")) .child( Label::new( "The agent can perform potentially destructive actions without asking for your confirmation.", ) .color(Color::Muted), ), ) .child( Switch::new( "always-allow-tool-actions-switch", always_allow_tool_actions.into(), ) .color(SwitchColor::Accent) .on_click({ let fs = self.fs.clone(); move |state, _window, cx| { let allow = state == &ToggleState::Selected; update_settings_file::<AgentSettings>( fs.clone(), cx, move |settings, _| { settings.set_always_allow_tool_actions(allow); }, ); } }), ) } fn render_single_file_review(&mut self, cx: &mut Context<Self>) -> impl IntoElement { let single_file_review = AgentSettings::get_global(cx).single_file_review; h_flex() .gap_4() .justify_between() .flex_wrap() .child( v_flex() .gap_0p5() .max_w_5_6() .child(Label::new("Enable single-file agent reviews")) .child( Label::new( "Agent edits are also displayed in single-file editors for review.", ) .color(Color::Muted), ), ) .child( Switch::new("single-file-review-switch", single_file_review.into()) .color(SwitchColor::Accent) .on_click({ let fs = self.fs.clone(); move |state, _window, cx| { let allow = state == &ToggleState::Selected; update_settings_file::<AgentSettings>( fs.clone(), cx, move |settings, _| { settings.set_single_file_review(allow); }, ); } }), ) } fn render_sound_notification(&mut self, cx: &mut Context<Self>) -> impl IntoElement { let play_sound_when_agent_done = AgentSettings::get_global(cx).play_sound_when_agent_done; h_flex() .gap_4() .justify_between() .flex_wrap() .child( v_flex() .gap_0p5() .max_w_5_6() .child(Label::new("Play sound when finished generating")) .child( Label::new( "Hear a notification sound when the agent is done generating changes or needs your input.", ) .color(Color::Muted), ), ) .child( Switch::new("play-sound-notification-switch", play_sound_when_agent_done.into()) .color(SwitchColor::Accent) .on_click({ let fs = self.fs.clone(); move |state, _window, cx| { let allow = state == &ToggleState::Selected; update_settings_file::<AgentSettings>( fs.clone(), cx, move |settings, _| { settings.set_play_sound_when_agent_done(allow); }, ); } }), ) } fn render_general_settings_section(&mut self, cx: &mut Context<Self>) -> impl IntoElement { v_flex() .p(DynamicSpacing::Base16.rems(cx)) .pr(DynamicSpacing::Base20.rems(cx)) .gap_2p5() .border_b_1() .border_color(cx.theme().colors().border) .child(Headline::new("General Settings")) .child(self.render_command_permission(cx)) .child(self.render_single_file_review(cx)) .child(self.render_sound_notification(cx)) } fn render_context_servers_section( &mut self, window: &mut Window, cx: &mut Context<Self>, ) -> impl IntoElement { let context_server_ids = self.context_server_store.read(cx).all_server_ids().clone(); v_flex() .p(DynamicSpacing::Base16.rems(cx)) .pr(DynamicSpacing::Base20.rems(cx)) .gap_2() .border_b_1() .border_color(cx.theme().colors().border) .child( v_flex() .gap_0p5() .child(Headline::new("Model Context Protocol (MCP) Servers")) .child(Label::new("Connect to context servers via the Model Context Protocol either via Zed extensions or directly.").color(Color::Muted)), ) .children( context_server_ids.into_iter().map(|context_server_id| { self.render_context_server(context_server_id, window, cx) }), ) .child( h_flex() .justify_between() .gap_2() .child( h_flex().w_full().child( Button::new("add-context-server", "Add Custom Server") .style(ButtonStyle::Filled) .layer(ElevationIndex::ModalSurface) .full_width() .icon(IconName::Plus) .icon_size(IconSize::Small) .icon_position(IconPosition::Start) .on_click(|_event, window, cx| { window.dispatch_action(AddContextServer.boxed_clone(), cx) }), ), ) .child( h_flex().w_full().child( Button::new( "install-context-server-extensions", "Install MCP Extensions", ) .style(ButtonStyle::Filled) .layer(ElevationIndex::ModalSurface) .full_width() .icon(IconName::Hammer) .icon_size(IconSize::Small) .icon_position(IconPosition::Start) .on_click(|_event, window, cx| { window.dispatch_action( zed_actions::Extensions { category_filter: Some( ExtensionCategoryFilter::ContextServers, ), } .boxed_clone(), cx, ) }), ), ), ) } fn render_context_server( &self, context_server_id: ContextServerId, window: &mut Window, cx: &mut Context<Self>, ) -> impl use<> + IntoElement { let tools_by_source = self.tools.read(cx).tools_by_source(cx); let server_status = self .context_server_store .read(cx) .status_for_server(&context_server_id) .unwrap_or(ContextServerStatus::Stopped); let server_configuration = self .context_server_store .read(cx) .configuration_for_server(&context_server_id); let is_running = matches!(server_status, ContextServerStatus::Running); let item_id = SharedString::from(context_server_id.0.clone()); let is_from_extension = server_configuration .as_ref() .map(|config| { matches!( config.as_ref(), ContextServerConfiguration::Extension { .. }Lines 1–500 of 997. Load more
mod configure_context_server_modal; mod manage_profiles_modal; mod tool_picker; use std::{sync::Arc, time::Duration}; use agent_settings::AgentSettings; use assistant_tool::{ToolSource, ToolWorkingSet}; use collections::HashMap; use context_server::ContextServerId; use extension::ExtensionManifest; use extension_host::ExtensionStore; use fs::Fs; use gpui::{ Action, Animation, AnimationExt as _, AnyView, App, Corner, Entity, EventEmitter, FocusHandle, Focusable, ScrollHandle, Subscription, Task, Transformation, WeakEntity, percentage, }; use language::LanguageRegistry; use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry}; use notifications::status_toast::{StatusToast, ToastIcon}; use project::{ context_server_store::{ContextServerConfiguration, ContextServerStatus, ContextServerStore}, project_settings::{ContextServerSettings, ProjectSettings}, }; use settings::{Settings, update_settings_file}; use ui::{ ContextMenu, Disclosure, ElevationIndex, Indicator, PopoverMenu, Scrollbar, ScrollbarState, Switch, SwitchColor, Tooltip, prelude::*, }; use util::ResultExt as _; use workspace::Workspace; use zed_actions::ExtensionCategoryFilter; pub(crate) use configure_context_server_modal::ConfigureContextServerModal; pub(crate) use manage_profiles_modal::ManageProfilesModal; use crate::AddContextServer; pub struct AgentConfiguration { fs: Arc<dyn Fs>, language_registry: Arc<LanguageRegistry>, workspace: WeakEntity<Workspace>, focus_handle: FocusHandle, configuration_views_by_provider: HashMap<LanguageModelProviderId, AnyView>, context_server_store: Entity<ContextServerStore>, expanded_context_server_tools: HashMap<ContextServerId, bool>, expanded_provider_configurations: HashMap<LanguageModelProviderId, bool>, tools: Entity<ToolWorkingSet>, _registry_subscription: Subscription, scroll_handle: ScrollHandle, scrollbar_state: ScrollbarState, } impl AgentConfiguration { pub fn new( fs: Arc<dyn Fs>, context_server_store: Entity<ContextServerStore>, tools: Entity<ToolWorkingSet>, language_registry: Arc<LanguageRegistry>, workspace: WeakEntity<Workspace>, 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_provider_configuration_view(&provider, window, cx); } } language_model::Event::RemovedProvider(provider_id) => { this.remove_provider_configuration_view(provider_id); } _ => {} }, ); cx.subscribe(&context_server_store, |_, _, _, cx| cx.notify()) .detach(); let scroll_handle = ScrollHandle::new(); let scrollbar_state = ScrollbarState::new(scroll_handle.clone()); let mut this = Self { fs, language_registry, workspace, focus_handle, configuration_views_by_provider: HashMap::default(), context_server_store, expanded_context_server_tools: HashMap::default(), expanded_provider_configurations: HashMap::default(), tools, _registry_subscription: registry_subscription, scroll_handle, scrollbar_state, }; this.build_provider_configuration_views(window, cx); this } fn build_provider_configuration_views(&mut self, window: &mut Window, cx: &mut Context<Self>) { let providers = LanguageModelRegistry::read_global(cx).providers(); for provider in providers { self.add_provider_configuration_view(&provider, window, cx); } } fn remove_provider_configuration_view(&mut self, provider_id: &LanguageModelProviderId) { self.configuration_views_by_provider.remove(provider_id); self.expanded_provider_configurations.remove(provider_id); } fn add_provider_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_by_provider .insert(provider.id(), configuration_view); } } impl Focusable for AgentConfiguration { fn focus_handle(&self, _: &App) -> FocusHandle { self.focus_handle.clone() } } pub enum AssistantConfigurationEvent { NewThread(Arc<dyn LanguageModelProvider>), } impl EventEmitter<AssistantConfigurationEvent> for AgentConfiguration {} impl AgentConfiguration { fn render_provider_configuration_block( &mut self, provider: &Arc<dyn LanguageModelProvider>, cx: &mut Context<Self>, ) -> impl IntoElement + use<> { let provider_id = provider.id().0.clone(); let provider_name = provider.name().0.clone(); let provider_id_string = SharedString::from(format!("provider-disclosure-{provider_id}")); let configuration_view = self .configuration_views_by_provider .get(&provider.id()) .cloned(); let is_expanded = self .expanded_provider_configurations .get(&provider.id()) .copied() .unwrap_or(false); v_flex() .py_2() .gap_1p5() .border_t_1() .border_color(cx.theme().colors().border.opacity(0.6)) .child( h_flex() .w_full() .gap_1() .justify_between() .child( h_flex() .id(provider_id_string.clone()) .cursor_pointer() .py_0p5() .w_full() .justify_between() .rounded_sm() .hover(|hover| hover.bg(cx.theme().colors().element_hover)) .child( h_flex() .gap_2() .child( Icon::new(provider.icon()) .size(IconSize::Small) .color(Color::Muted), ) .child(Label::new(provider_name.clone()).size(LabelSize::Large)) .when( provider.is_authenticated(cx) && !is_expanded, |parent| { parent.child( Icon::new(IconName::Check).color(Color::Success), ) }, ), ) .child( Disclosure::new(provider_id_string, is_expanded) .opened_icon(IconName::ChevronUp) .closed_icon(IconName::ChevronDown), ) .on_click(cx.listener({ let provider_id = provider.id().clone(); move |this, _event, _window, _cx| { let is_expanded = this .expanded_provider_configurations .entry(provider_id.clone()) .or_insert(false); *is_expanded = !*is_expanded; } })), ) .when(provider.is_authenticated(cx), |parent| { parent.child( Button::new( SharedString::from(format!("new-thread-{provider_id}")), "Start New Thread", ) .icon_position(IconPosition::Start) .icon(IconName::Plus) .icon_size(IconSize::Small) .icon_color(Color::Muted) .label_size(LabelSize::Small) .on_click(cx.listener({ let provider = provider.clone(); move |_this, _event, _window, cx| { cx.emit(AssistantConfigurationEvent::NewThread( provider.clone(), )) } })), ) }), ) .when(is_expanded, |parent| match configuration_view { Some(configuration_view) => parent.child(configuration_view), None => parent.child(Label::new(format!( "No configuration view for {provider_name}", ))), }) } fn render_provider_configuration_section( &mut self, cx: &mut Context<Self>, ) -> impl IntoElement { let providers = LanguageModelRegistry::read_global(cx).providers(); v_flex() .p(DynamicSpacing::Base16.rems(cx)) .pr(DynamicSpacing::Base20.rems(cx)) .border_b_1() .border_color(cx.theme().colors().border) .child( v_flex() .mb_2p5() .gap_0p5() .child(Headline::new("LLM Providers")) .child( Label::new("Add at least one provider to use AI-powered features.") .color(Color::Muted), ), ) .children( providers .into_iter() .map(|provider| self.render_provider_configuration_block(&provider, cx)), ) } fn render_command_permission(&mut self, cx: &mut Context<Self>) -> impl IntoElement { let always_allow_tool_actions = AgentSettings::get_global(cx).always_allow_tool_actions; h_flex() .gap_4() .justify_between() .flex_wrap() .child( v_flex() .gap_0p5() .max_w_5_6() .child(Label::new("Allow running editing tools without asking for confirmation")) .child( Label::new( "The agent can perform potentially destructive actions without asking for your confirmation.", ) .color(Color::Muted), ), ) .child( Switch::new( "always-allow-tool-actions-switch", always_allow_tool_actions.into(), ) .color(SwitchColor::Accent) .on_click({ let fs = self.fs.clone(); move |state, _window, cx| { let allow = state == &ToggleState::Selected; update_settings_file::<AgentSettings>( fs.clone(), cx, move |settings, _| { settings.set_always_allow_tool_actions(allow); }, ); } }), ) } fn render_single_file_review(&mut self, cx: &mut Context<Self>) -> impl IntoElement { let single_file_review = AgentSettings::get_global(cx).single_file_review; h_flex() .gap_4() .justify_between() .flex_wrap() .child( v_flex() .gap_0p5() .max_w_5_6() .child(Label::new("Enable single-file agent reviews")) .child( Label::new( "Agent edits are also displayed in single-file editors for review.", ) .color(Color::Muted), ), ) .child( Switch::new("single-file-review-switch", single_file_review.into()) .color(SwitchColor::Accent) .on_click({ let fs = self.fs.clone(); move |state, _window, cx| { let allow = state == &ToggleState::Selected; update_settings_file::<AgentSettings>( fs.clone(), cx, move |settings, _| { settings.set_single_file_review(allow); }, ); } }), ) } fn render_sound_notification(&mut self, cx: &mut Context<Self>) -> impl IntoElement { let play_sound_when_agent_done = AgentSettings::get_global(cx).play_sound_when_agent_done; h_flex() .gap_4() .justify_between() .flex_wrap() .child( v_flex() .gap_0p5() .max_w_5_6() .child(Label::new("Play sound when finished generating")) .child( Label::new( "Hear a notification sound when the agent is done generating changes or needs your input.", ) .color(Color::Muted), ), ) .child( Switch::new("play-sound-notification-switch", play_sound_when_agent_done.into()) .color(SwitchColor::Accent) .on_click({ let fs = self.fs.clone(); move |state, _window, cx| { let allow = state == &ToggleState::Selected; update_settings_file::<AgentSettings>( fs.clone(), cx, move |settings, _| { settings.set_play_sound_when_agent_done(allow); }, ); } }), ) } fn render_general_settings_section(&mut self, cx: &mut Context<Self>) -> impl IntoElement { v_flex() .p(DynamicSpacing::Base16.rems(cx)) .pr(DynamicSpacing::Base20.rems(cx)) .gap_2p5() .border_b_1() .border_color(cx.theme().colors().border) .child(Headline::new("General Settings")) .child(self.render_command_permission(cx)) .child(self.render_single_file_review(cx)) .child(self.render_sound_notification(cx)) } fn render_context_servers_section( &mut self, window: &mut Window, cx: &mut Context<Self>, ) -> impl IntoElement { let context_server_ids = self.context_server_store.read(cx).all_server_ids().clone(); v_flex() .p(DynamicSpacing::Base16.rems(cx)) .pr(DynamicSpacing::Base20.rems(cx)) .gap_2() .border_b_1() .border_color(cx.theme().colors().border) .child( v_flex() .gap_0p5() .child(Headline::new("Model Context Protocol (MCP) Servers")) .child(Label::new("Connect to context servers via the Model Context Protocol either via Zed extensions or directly.").color(Color::Muted)), ) .children( context_server_ids.into_iter().map(|context_server_id| { self.render_context_server(context_server_id, window, cx) }), ) .child( h_flex() .justify_between() .gap_2() .child( h_flex().w_full().child( Button::new("add-context-server", "Add Custom Server") .style(ButtonStyle::Filled) .layer(ElevationIndex::ModalSurface) .full_width() .icon(IconName::Plus) .icon_size(IconSize::Small) .icon_position(IconPosition::Start) .on_click(|_event, window, cx| { window.dispatch_action(AddContextServer.boxed_clone(), cx) }), ), ) .child( h_flex().w_full().child( Button::new( "install-context-server-extensions", "Install MCP Extensions", ) .style(ButtonStyle::Filled) .layer(ElevationIndex::ModalSurface) .full_width() .icon(IconName::Hammer) .icon_size(IconSize::Small) .icon_position(IconPosition::Start) .on_click(|_event, window, cx| { window.dispatch_action( zed_actions::Extensions { category_filter: Some( ExtensionCategoryFilter::ContextServers, ), } .boxed_clone(), cx, ) }), ), ), ) } fn render_context_server( &self, context_server_id: ContextServerId, window: &mut Window, cx: &mut Context<Self>, ) -> impl use<> + IntoElement { let tools_by_source = self.tools.read(cx).tools_by_source(cx); let server_status = self .context_server_store .read(cx) .status_for_server(&context_server_id) .unwrap_or(ContextServerStatus::Stopped); let server_configuration = self .context_server_store .read(cx) .configuration_for_server(&context_server_id); let is_running = matches!(server_status, ContextServerStatus::Running); let item_id = SharedString::from(context_server_id.0.clone()); let is_from_extension = server_configuration .as_ref() .map(|config| { matches!( config.as_ref(), ContextServerConfiguration::Extension { .. }