agent: Improve the UX around interacting with MCP servers (#32622)
Still a work in progress! Todos before merging: - [x] Allow to delete (not just turn off) an MCP server from the panel's settings view - [x] Also uninstall the extension upon deleting the server (check if the extension just provides MCPs) - [x] Resolve repository URL again - [x] Add a button to open the configuration modal from the panel's settings view - [x] Improve modal UX to install and configure a non-extension MCP Release Notes: - N/A --------- Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de> Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
This commit is contained in:
1 parent
2661450754
commit
3389726d6e
16 files changed
+926
-728
No files matched your search
Generated
+1
-1
@@ -71,6 +71,7 @@ dependencies = [
|
||||
"db",
|
||||
"editor",
|
||||
"extension",
|
||||
"extension_host",
|
||||
"feature_flags",
|
||||
"file_icons",
|
||||
"fs",
|
||||
@@ -127,7 +128,6 @@ dependencies = [
|
||||
"time",
|
||||
"time_format",
|
||||
"ui",
|
||||
"ui_input",
|
||||
"urlencoding",
|
||||
"util",
|
||||
"uuid",
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect opacity="0.3" x="2" y="2" width="12" height="12" rx="2" stroke="black" stroke-width="1.5"/>
|
||||
<path d="M8.17871 4.15039C9.57718 4.15047 10.7699 4.84416 11.25 6.00879L11.292 6.125C11.3291 6.2423 11.3496 6.36246 11.3496 6.47656C11.3496 6.67403 11.2827 6.86644 11.1357 7.00879C10.99 7.14999 10.797 7.21191 10.6035 7.21191C10.4311 7.21184 10.263 7.16459 10.1172 7.05469C9.97557 6.94786 9.88031 6.80045 9.81543 6.6416V6.64062C9.54483 5.99474 8.92921 5.62898 8.17871 5.62891C7.58712 5.62891 7.11895 5.85535 6.79297 6.24512C6.46181 6.64107 6.25488 7.2348 6.25488 7.99805C6.25495 8.76969 6.4603 9.36343 6.79004 9.75781C7.11426 10.1456 7.5828 10.3711 8.18359 10.3711C9.00554 10.371 9.57612 10.0476 9.83789 9.44922L9.83984 9.44531C9.90093 9.30959 9.98754 9.17654 10.1211 9.08008C10.2598 8.97992 10.4154 8.94341 10.5693 8.94336C10.9809 8.94336 11.3203 9.24379 11.3203 9.67383C11.3203 9.89213 11.2593 10.0749 11.1641 10.2578L11.1631 10.2568C10.6246 11.2962 9.56725 11.8495 8.17871 11.8496C7.12036 11.8496 6.22667 11.4786 5.60059 10.7891C4.97793 10.1031 4.65047 9.13626 4.65039 7.99805C4.65039 6.86743 4.98182 5.90203 5.60547 5.21582C6.23235 4.52617 7.12584 4.15039 8.17871 4.15039ZM7.67773 11.4678C7.68226 11.4684 7.68688 11.4682 7.69141 11.4688C7.68688 11.4682 7.68226 11.4684 7.67773 11.4678ZM7.35059 11.4082C7.37261 11.4133 7.39471 11.4182 7.41699 11.4229C7.39471 11.4182 7.37261 11.4133 7.35059 11.4082ZM7.07617 11.333C7.08849 11.337 7.10087 11.3408 7.11328 11.3447C7.10087 11.3408 7.08849 11.337 7.07617 11.333ZM6.80469 11.2295C6.81416 11.2336 6.82348 11.2381 6.83301 11.2422C6.82347 11.2381 6.81416 11.2336 6.80469 11.2295ZM6.32812 10.9648C6.33354 10.9686 6.33929 10.9719 6.34473 10.9756C6.33928 10.9719 6.33354 10.9686 6.32812 10.9648ZM6.10156 10.7891C6.11103 10.7972 6.12128 10.8045 6.13086 10.8125C6.12128 10.8045 6.11103 10.7972 6.10156 10.7891ZM10.5449 10.5586V10.5596V10.5586ZM5.90234 10.5986C5.909 10.6056 5.91613 10.6122 5.92285 10.6191C5.91612 10.6122 5.909 10.6056 5.90234 10.5986ZM5.71094 10.374C5.71943 10.3851 5.72769 10.3963 5.73633 10.4072C5.72769 10.3963 5.71944 10.3851 5.71094 10.374ZM5.55469 10.1504C5.55922 10.1576 5.56378 10.1647 5.56836 10.1719C5.56377 10.1647 5.55922 10.1576 5.55469 10.1504ZM10.9131 9.95898C10.9213 9.9374 10.9302 9.91676 10.9365 9.89551C10.9301 9.91674 10.9214 9.93742 10.9131 9.95898ZM10.21 9.49219C10.192 9.52099 10.1752 9.55324 10.1592 9.58887L10.1953 9.51758C10.2002 9.50891 10.2049 9.50031 10.21 9.49219ZM10.499 9.29688C10.4547 9.30262 10.4145 9.31428 10.3779 9.33203C10.4145 9.31437 10.4547 9.30259 10.499 9.29688ZM5.18652 9.33203C5.18988 9.34309 5.19284 9.35425 5.19629 9.36523C5.19284 9.35424 5.18988 9.34309 5.18652 9.33203ZM10.5693 9.29297C10.628 9.29297 10.6829 9.3029 10.7314 9.32129C10.6827 9.30276 10.6281 9.29297 10.5693 9.29297ZM5.10352 9.01172C5.1083 9.03397 5.11306 9.05613 5.11816 9.07812C5.11306 9.05612 5.1083 9.03397 5.10352 9.01172ZM5.01465 8.39062C5.02368 8.51203 5.03633 8.63062 5.05371 8.74609C5.03633 8.63061 5.02368 8.51204 5.01465 8.39062ZM5.01465 7.60742V7.6084V7.60742ZM10.6035 6.86133C10.6321 6.86133 10.6594 6.85865 10.6855 6.85449C10.6596 6.85862 10.632 6.86133 10.6035 6.86133ZM10.7002 6.85059C10.7213 6.84652 10.7415 6.84169 10.7607 6.83496C10.7415 6.8417 10.7213 6.84652 10.7002 6.85059ZM10.7773 6.82812C10.7929 6.82186 10.8081 6.81563 10.8223 6.80762C10.8081 6.81566 10.7929 6.82185 10.7773 6.82812ZM10.8516 6.78809C10.8602 6.782 10.868 6.77535 10.876 6.76855C10.8681 6.77537 10.8602 6.78198 10.8516 6.78809ZM10.8994 6.74707C10.9093 6.73683 10.9173 6.72535 10.9258 6.71387C10.9173 6.72537 10.9092 6.73681 10.8994 6.74707ZM10.9912 6.37305C10.9893 6.3598 10.9861 6.34659 10.9834 6.33301C10.9861 6.34658 10.9892 6.35981 10.9912 6.37305ZM10.9102 6.10645C10.8889 6.05715 10.867 6.00836 10.8428 5.96094C10.867 6.00837 10.8889 6.05714 10.9102 6.10645ZM10.7871 5.86328C10.7743 5.84084 10.7625 5.81787 10.749 5.7959C10.7625 5.81788 10.7743 5.84084 10.7871 5.86328ZM10.6973 5.7168C10.6775 5.68719 10.6578 5.65759 10.6367 5.62891C10.6578 5.65759 10.6775 5.68719 10.6973 5.7168ZM10.584 5.56152C10.563 5.53497 10.5425 5.5081 10.5205 5.48242C10.5425 5.50811 10.563 5.53497 10.584 5.56152ZM10.4492 5.4043C10.4285 5.3821 10.4083 5.35937 10.3867 5.33789C10.4083 5.35938 10.4285 5.38209 10.4492 5.4043ZM7.68457 5.32812H7.68555H7.68457ZM10.3096 5.26562C10.2895 5.24727 10.2697 5.2287 10.249 5.21094C10.2697 5.22871 10.2895 5.24726 10.3096 5.26562ZM10.1553 5.13477C10.1356 5.11933 10.1159 5.10379 10.0957 5.08887C10.1159 5.10379 10.1356 5.11933 10.1553 5.13477ZM10.0078 5.02734C9.9783 5.00732 9.94843 4.98772 9.91797 4.96875C9.94843 4.98773 9.9783 5.00731 10.0078 5.02734ZM9.83887 4.9209C9.81069 4.90462 9.78183 4.88942 9.75293 4.87402C9.78184 4.88943 9.81069 4.90461 9.83887 4.9209ZM9.65039 4.82129C9.62853 4.81074 9.60622 4.80108 9.58398 4.79102C9.60622 4.80108 9.62853 4.81073 9.65039 4.82129ZM9.45703 4.73633C9.43574 4.72785 9.41417 4.71996 9.39258 4.71191C9.41418 4.71996 9.43574 4.72784 9.45703 4.73633ZM9.27441 4.66895C9.23013 4.65444 9.18506 4.64161 9.13965 4.62891C9.18506 4.64161 9.23013 4.65444 9.27441 4.66895ZM9.08008 4.6123C9Line truncated
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.5 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect opacity="0.3" x="2" y="2" width="12" height="12" rx="2" stroke="black" stroke-width="1.5"/>
|
||||
<path d="M9.62109 4.65039C9.80393 4.65039 9.98678 4.70637 10.1279 4.83203C10.2722 4.96055 10.3496 5.14167 10.3496 5.33691C10.3496 5.53215 10.2723 5.71329 10.1279 5.8418C9.98678 5.96744 9.80392 6.02344 9.62109 6.02344H7.1416V7.24902H9.46289C9.6423 7.24902 9.82303 7.30362 9.96387 7.42773C10.1085 7.55531 10.1875 7.73591 10.1875 7.93164C10.1874 8.12674 10.1072 8.30592 9.96484 8.43262C9.82536 8.55656 9.64499 8.61426 9.46289 8.61426H7.1416V9.97656H9.62109C9.80392 9.97656 9.98678 10.0326 10.1279 10.1582C10.2723 10.2867 10.3496 10.4678 10.3496 10.6631C10.3496 10.8583 10.2722 11.0394 10.1279 11.168C9.98678 11.2936 9.80393 11.3496 9.62109 11.3496H6.39648C6.20308 11.3496 6.00965 11.289 5.86328 11.1465C5.7159 11.0028 5.65039 10.8095 5.65039 10.6133V5.38672C5.65039 5.19054 5.7159 4.99723 5.86328 4.85352C6.00965 4.71101 6.20308 4.65039 6.39648 4.65039H9.62109ZM9.70215 10.9941C9.72618 10.9903 9.74882 10.9838 9.77051 10.9766C9.74884 10.9838 9.72616 10.9903 9.70215 10.9941ZM9.78809 10.9688C9.80381 10.9626 9.81877 10.9561 9.83301 10.9482C9.81877 10.9561 9.80381 10.9626 9.78809 10.9688ZM9.85059 10.9375C9.86364 10.9292 9.87617 10.9209 9.8877 10.9111C9.87616 10.9209 9.86365 10.9293 9.85059 10.9375ZM6.1123 10.8994C6.12346 10.9099 6.1358 10.9188 6.14844 10.9277C6.1358 10.9188 6.12346 10.9099 6.1123 10.8994ZM9.90039 10.8984C9.91122 10.8882 9.92149 10.8778 9.93066 10.8662C9.92147 10.8778 9.91123 10.8882 9.90039 10.8984ZM6.07129 10.8516C6.0792 10.8626 6.08754 10.8729 6.09668 10.8828C6.08754 10.8729 6.0792 10.8626 6.07129 10.8516ZM9.94434 10.8477C9.95104 10.8377 9.95638 10.8272 9.96191 10.8164C9.95637 10.8271 9.95106 10.8377 9.94434 10.8477ZM6.03418 10.7842C6.04066 10.7995 6.04735 10.8143 6.05566 10.8281C6.04735 10.8143 6.04066 10.7995 6.03418 10.7842ZM9.97559 10.7891C9.97984 10.7783 9.98321 10.7673 9.98633 10.7559C9.98321 10.7673 9.97984 10.7783 9.97559 10.7891ZM6.01367 10.7236C6.0173 10.7388 6.02121 10.7535 6.02637 10.7676C6.02121 10.7535 6.0173 10.7388 6.01367 10.7236ZM9.62109 10.3262C9.67716 10.3262 9.72942 10.3346 9.77539 10.3506C9.72927 10.3345 9.67733 10.3262 9.62109 10.3262ZM9.56641 8.25098C9.5802 8.24792 9.59348 8.24449 9.60645 8.24023C9.59348 8.2445 9.58021 8.24792 9.56641 8.25098ZM9.625 8.2334C9.64073 8.22735 9.65566 8.2207 9.66992 8.21289C9.65565 8.22072 9.64074 8.22733 9.625 8.2334ZM9.68848 8.20117C9.69832 8.19495 9.70781 8.18872 9.7168 8.18164C9.70781 8.18874 9.69832 8.19493 9.68848 8.20117ZM9.74121 8.16016C9.7511 8.15059 9.76008 8.14059 9.76855 8.12988C9.76009 8.1406 9.75109 8.15057 9.74121 8.16016ZM9.77734 8.11914C9.78753 8.10499 9.79605 8.09004 9.80371 8.07422C9.79606 8.09005 9.78751 8.10498 9.77734 8.11914ZM9.79883 5.6377C9.80686 5.6343 9.81463 5.63082 9.82227 5.62695C9.81463 5.63084 9.80687 5.63429 9.79883 5.6377ZM9.93457 5.53516C9.91124 5.56582 9.88273 5.59214 9.84863 5.61328C9.88277 5.5922 9.91121 5.56576 9.93457 5.53516ZM9.94434 5.52246C9.95363 5.50886 9.96171 5.4946 9.96875 5.47949C9.96172 5.4946 9.9536 5.50885 9.94434 5.52246ZM6.02637 5.23145C6.02118 5.24552 6.01733 5.26024 6.01367 5.27539C6.01734 5.26023 6.02118 5.24552 6.02637 5.23145ZM6.05566 5.17188C6.04766 5.18525 6.04049 5.19914 6.03418 5.21387C6.04049 5.19914 6.04765 5.18525 6.05566 5.17188ZM6.09961 5.11328C6.08938 5.124 6.08003 5.13538 6.07129 5.14746C6.08003 5.13538 6.08938 5.12399 6.09961 5.11328ZM6.14844 5.07129C6.13601 5.08003 6.12428 5.08938 6.11328 5.09961C6.12428 5.08938 6.13601 5.08003 6.14844 5.07129ZM6.23242 5.02637C6.21195 5.03382 6.19287 5.04334 6.1748 5.05371C6.19287 5.04335 6.21195 5.03382 6.23242 5.02637ZM6.1748 10.9453C6.19195 10.9552 6.21017 10.9635 6.22949 10.9707C6.21017 10.9635 6.19195 10.9552 6.1748 10.9453ZM6.30957 5.00684C6.28239 5.01136 6.25651 5.01759 6.23242 5.02637C6.25651 5.01759 6.28239 5.01136 6.30957 5.00684Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
@@ -36,6 +36,7 @@ convert_case.workspace = true
|
||||
db.workspace = true
|
||||
editor.workspace = true
|
||||
extension.workspace = true
|
||||
extension_host.workspace = true
|
||||
feature_flags.workspace = true
|
||||
file_icons.workspace = true
|
||||
fs.workspace = true
|
||||
@@ -90,7 +91,6 @@ thiserror.workspace = true
|
||||
time.workspace = true
|
||||
time_format.workspace = true
|
||||
ui.workspace = true
|
||||
ui_input.workspace = true
|
||||
urlencoding.workspace = true
|
||||
util.workspace = true
|
||||
uuid.workspace = true
|
||||
|
||||
@@ -46,7 +46,7 @@ use settings::{Settings as _, SettingsStore};
|
||||
use thread::ThreadId;
|
||||
|
||||
pub use crate::active_thread::ActiveThread;
|
||||
use crate::agent_configuration::{AddContextServerModal, ManageProfilesModal};
|
||||
use crate::agent_configuration::{ConfigureContextServerModal, ManageProfilesModal};
|
||||
pub use crate::agent_panel::{AgentPanel, ConcreteAssistantPanelDelegate};
|
||||
pub use crate::context::{ContextLoadResult, LoadedContext};
|
||||
pub use crate::inline_assistant::InlineAssistant;
|
||||
@@ -162,7 +162,7 @@ pub fn init(
|
||||
assistant_slash_command::init(cx);
|
||||
thread_store::init(cx);
|
||||
agent_panel::init(cx);
|
||||
context_server_configuration::init(language_registry, fs.clone(), cx);
|
||||
context_server_configuration::init(language_registry.clone(), fs.clone(), cx);
|
||||
|
||||
register_slash_commands(cx);
|
||||
inline_assistant::init(
|
||||
@@ -178,7 +178,10 @@ pub fn init(
|
||||
cx,
|
||||
);
|
||||
indexed_docs::init(cx);
|
||||
cx.observe_new(AddContextServerModal::register).detach();
|
||||
cx.observe_new(move |workspace, window, cx| {
|
||||
ConfigureContextServerModal::register(workspace, language_registry.clone(), window, cx)
|
||||
})
|
||||
.detach();
|
||||
cx.observe_new(ManageProfilesModal::register).detach();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
mod add_context_server_modal;
|
||||
mod configure_context_server_modal;
|
||||
mod manage_profiles_modal;
|
||||
mod tool_picker;
|
||||
@@ -9,22 +8,29 @@ 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, Entity, EventEmitter, FocusHandle,
|
||||
Focusable, ScrollHandle, Subscription, Transformation, percentage,
|
||||
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 project::context_server_store::{ContextServerStatus, ContextServerStore};
|
||||
use notifications::status_toast::{StatusToast, ToastIcon};
|
||||
use project::{
|
||||
context_server_store::{ContextServerConfiguration, ContextServerStatus, ContextServerStore},
|
||||
project_settings::ProjectSettings,
|
||||
};
|
||||
use settings::{Settings, update_settings_file};
|
||||
use ui::{
|
||||
Disclosure, ElevationIndex, Indicator, Scrollbar, ScrollbarState, Switch, SwitchColor, Tooltip,
|
||||
prelude::*,
|
||||
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 add_context_server_modal::AddContextServerModal;
|
||||
pub(crate) use configure_context_server_modal::ConfigureContextServerModal;
|
||||
pub(crate) use manage_profiles_modal::ManageProfilesModal;
|
||||
|
||||
@@ -32,6 +38,8 @@ 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>,
|
||||
@@ -48,6 +56,8 @@ impl AgentConfiguration {
|
||||
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 {
|
||||
@@ -70,11 +80,16 @@ impl AgentConfiguration {
|
||||
},
|
||||
);
|
||||
|
||||
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,
|
||||
@@ -460,9 +475,22 @@ impl AgentConfiguration {
|
||||
.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 { .. }
|
||||
)
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
let error = if let ContextServerStatus::Error(error) = server_status.clone() {
|
||||
Some(error)
|
||||
@@ -484,6 +512,18 @@ impl AgentConfiguration {
|
||||
|
||||
let border_color = cx.theme().colors().border.opacity(0.6);
|
||||
|
||||
let (source_icon, source_tooltip) = if is_from_extension {
|
||||
(
|
||||
IconName::ZedMcpExtension,
|
||||
"This MCP server was installed from an extension.",
|
||||
)
|
||||
} else {
|
||||
(
|
||||
IconName::ZedMcpCustom,
|
||||
"This custom MCP server was installed directly.",
|
||||
)
|
||||
};
|
||||
|
||||
let (status_indicator, tooltip_text) = match server_status {
|
||||
ContextServerStatus::Starting => (
|
||||
Icon::new(IconName::LoadCircle)
|
||||
@@ -511,6 +551,105 @@ impl AgentConfiguration {
|
||||
),
|
||||
};
|
||||
|
||||
let context_server_configuration_menu = PopoverMenu::new("context-server-config-menu")
|
||||
.trigger_with_tooltip(
|
||||
IconButton::new("context-server-config-menu", IconName::Settings)
|
||||
.icon_color(Color::Muted)
|
||||
.icon_size(IconSize::Small),
|
||||
Tooltip::text("Open MCP server options"),
|
||||
)
|
||||
.anchor(Corner::TopRight)
|
||||
.menu({
|
||||
let fs = self.fs.clone();
|
||||
let context_server_id = context_server_id.clone();
|
||||
let language_registry = self.language_registry.clone();
|
||||
let context_server_store = self.context_server_store.clone();
|
||||
let workspace = self.workspace.clone();
|
||||
move |window, cx| {
|
||||
Some(ContextMenu::build(window, cx, |menu, _window, _cx| {
|
||||
menu.entry("Configure Server", None, {
|
||||
let context_server_id = context_server_id.clone();
|
||||
let language_registry = language_registry.clone();
|
||||
let workspace = workspace.clone();
|
||||
move |window, cx| {
|
||||
ConfigureContextServerModal::show_modal_for_existing_server(
|
||||
context_server_id.clone(),
|
||||
language_registry.clone(),
|
||||
workspace.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
})
|
||||
.separator()
|
||||
.entry("Delete", None, {
|
||||
let fs = fs.clone();
|
||||
let context_server_id = context_server_id.clone();
|
||||
let context_server_store = context_server_store.clone();
|
||||
let workspace = workspace.clone();
|
||||
move |_, cx| {
|
||||
let is_provided_by_extension = context_server_store
|
||||
.read(cx)
|
||||
.configuration_for_server(&context_server_id)
|
||||
.as_ref()
|
||||
.map(|config| {
|
||||
matches!(
|
||||
config.as_ref(),
|
||||
ContextServerConfiguration::Extension { .. }
|
||||
)
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
let uninstall_extension_task = match (
|
||||
is_provided_by_extension,
|
||||
resolve_extension_for_context_server(&context_server_id, cx),
|
||||
) {
|
||||
(true, Some((id, manifest))) => {
|
||||
if extension_only_provides_context_server(manifest.as_ref())
|
||||
{
|
||||
ExtensionStore::global(cx).update(cx, |store, cx| {
|
||||
store.uninstall_extension(id, cx)
|
||||
})
|
||||
} else {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
show_unable_to_uninstall_extension_with_context_server(workspace, context_server_id.clone(), cx);
|
||||
}).log_err();
|
||||
Task::ready(Ok(()))
|
||||
}
|
||||
}
|
||||
_ => Task::ready(Ok(())),
|
||||
};
|
||||
|
||||
cx.spawn({
|
||||
let fs = fs.clone();
|
||||
let context_server_id = context_server_id.clone();
|
||||
async move |cx| {
|
||||
uninstall_extension_task.await?;
|
||||
cx.update(|cx| {
|
||||
update_settings_file::<ProjectSettings>(
|
||||
fs.clone(),
|
||||
cx,
|
||||
{
|
||||
let context_server_id =
|
||||
context_server_id.clone();
|
||||
move |settings, _| {
|
||||
settings
|
||||
.context_servers
|
||||
.remove(&context_server_id.0);
|
||||
}
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
})
|
||||
}))
|
||||
}
|
||||
});
|
||||
|
||||
v_flex()
|
||||
.id(item_id.clone())
|
||||
.border_1()
|
||||
@@ -556,7 +695,19 @@ impl AgentConfiguration {
|
||||
.tooltip(Tooltip::text(tooltip_text))
|
||||
.child(status_indicator),
|
||||
)
|
||||
.child(Label::new(item_id).ml_0p5().mr_1p5())
|
||||
.child(Label::new(item_id).ml_0p5())
|
||||
.child(
|
||||
div()
|
||||
.id("extension-source")
|
||||
.mt_0p5()
|
||||
.mx_1()
|
||||
.tooltip(Tooltip::text(source_tooltip))
|
||||
.child(
|
||||
Icon::new(source_icon)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
.when(is_running, |this| {
|
||||
this.child(
|
||||
Label::new(if tool_count == 1 {
|
||||
@@ -570,28 +721,37 @@ impl AgentConfiguration {
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
Switch::new("context-server-switch", is_running.into())
|
||||
.color(SwitchColor::Accent)
|
||||
.on_click({
|
||||
let context_server_manager = self.context_server_store.clone();
|
||||
let context_server_id = context_server_id.clone();
|
||||
move |state, _window, cx| match state {
|
||||
ToggleState::Unselected | ToggleState::Indeterminate => {
|
||||
context_server_manager.update(cx, |this, cx| {
|
||||
this.stop_server(&context_server_id, cx).log_err();
|
||||
});
|
||||
}
|
||||
ToggleState::Selected => {
|
||||
context_server_manager.update(cx, |this, cx| {
|
||||
if let Some(server) =
|
||||
this.get_server(&context_server_id)
|
||||
{
|
||||
this.start_server(server, cx);
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(context_server_configuration_menu)
|
||||
.child(
|
||||
Switch::new("context-server-switch", is_running.into())
|
||||
.color(SwitchColor::Accent)
|
||||
.on_click({
|
||||
let context_server_manager =
|
||||
self.context_server_store.clone();
|
||||
let context_server_id = context_server_id.clone();
|
||||
|
||||
move |state, _window, cx| match state {
|
||||
ToggleState::Unselected
|
||||
| ToggleState::Indeterminate => {
|
||||
context_server_manager.update(cx, |this, cx| {
|
||||
this.stop_server(&context_server_id, cx)
|
||||
.log_err();
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}),
|
||||
ToggleState::Selected => {
|
||||
context_server_manager.update(cx, |this, cx| {
|
||||
if let Some(server) =
|
||||
this.get_server(&context_server_id)
|
||||
{
|
||||
this.start_server(server, cx);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
.map(|parent| {
|
||||
@@ -701,3 +861,51 @@ impl Render for AgentConfiguration {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn extension_only_provides_context_server(manifest: &ExtensionManifest) -> bool {
|
||||
manifest.context_servers.len() == 1
|
||||
&& manifest.themes.is_empty()
|
||||
&& manifest.icon_themes.is_empty()
|
||||
&& manifest.languages.is_empty()
|
||||
&& manifest.grammars.is_empty()
|
||||
&& manifest.language_servers.is_empty()
|
||||
&& manifest.slash_commands.is_empty()
|
||||
&& manifest.indexed_docs_providers.is_empty()
|
||||
&& manifest.snippets.is_none()
|
||||
&& manifest.debug_locators.is_empty()
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_extension_for_context_server(
|
||||
id: &ContextServerId,
|
||||
cx: &App,
|
||||
) -> Option<(Arc<str>, Arc<ExtensionManifest>)> {
|
||||
ExtensionStore::global(cx)
|
||||
.read(cx)
|
||||
.installed_extensions()
|
||||
.iter()
|
||||
.find(|(_, entry)| entry.manifest.context_servers.contains_key(&id.0))
|
||||
.map(|(id, entry)| (id.clone(), entry.manifest.clone()))
|
||||
}
|
||||
|
||||
// This notification appears when trying to delete
|
||||
// an MCP server extension that not only provides
|
||||
// the server, but other things, too, like language servers and more.
|
||||
fn show_unable_to_uninstall_extension_with_context_server(
|
||||
workspace: &mut Workspace,
|
||||
id: ContextServerId,
|
||||
cx: &mut App,
|
||||
) {
|
||||
let status_toast = StatusToast::new(
|
||||
format!(
|
||||
"Unable to uninstall the {} extension, as it provides more than just the MCP server.",
|
||||
id.0
|
||||
),
|
||||
cx,
|
||||
|this, _cx| {
|
||||
this.icon(ToastIcon::new(IconName::Warning).color(Color::Warning))
|
||||
.action("Dismiss", |_, _| {})
|
||||
},
|
||||
);
|
||||
|
||||
workspace.toggle_status_toast(status_toast, cx);
|
||||
}
|
||||
@@ -1,195 +0,0 @@
|
||||
use context_server::ContextServerCommand;
|
||||
use gpui::{DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, WeakEntity, prelude::*};
|
||||
use project::project_settings::{ContextServerSettings, ProjectSettings};
|
||||
use settings::update_settings_file;
|
||||
use ui::{KeyBinding, Modal, ModalFooter, ModalHeader, Section, Tooltip, prelude::*};
|
||||
use ui_input::SingleLineInput;
|
||||
use workspace::{ModalView, Workspace};
|
||||
|
||||
use crate::AddContextServer;
|
||||
|
||||
pub struct AddContextServerModal {
|
||||
workspace: WeakEntity<Workspace>,
|
||||
name_editor: Entity<SingleLineInput>,
|
||||
command_editor: Entity<SingleLineInput>,
|
||||
}
|
||||
|
||||
impl AddContextServerModal {
|
||||
pub fn register(
|
||||
workspace: &mut Workspace,
|
||||
_window: Option<&mut Window>,
|
||||
_cx: &mut Context<Workspace>,
|
||||
) {
|
||||
workspace.register_action(|workspace, _: &AddContextServer, window, cx| {
|
||||
let workspace_handle = cx.entity().downgrade();
|
||||
workspace.toggle_modal(window, cx, |window, cx| {
|
||||
Self::new(workspace_handle, window, cx)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
workspace: WeakEntity<Workspace>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let name_editor =
|
||||
cx.new(|cx| SingleLineInput::new(window, cx, "my-custom-server").label("Name"));
|
||||
let command_editor = cx.new(|cx| {
|
||||
SingleLineInput::new(window, cx, "Command").label("Command to run the MCP server")
|
||||
});
|
||||
|
||||
Self {
|
||||
name_editor,
|
||||
command_editor,
|
||||
workspace,
|
||||
}
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _: &menu::Confirm, cx: &mut Context<Self>) {
|
||||
let name = self
|
||||
.name_editor
|
||||
.read(cx)
|
||||
.editor()
|
||||
.read(cx)
|
||||
.text(cx)
|
||||
.trim()
|
||||
.to_string();
|
||||
let command = self
|
||||
.command_editor
|
||||
.read(cx)
|
||||
.editor()
|
||||
.read(cx)
|
||||
.text(cx)
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
if name.is_empty() || command.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut command_parts = command.split(' ').map(|part| part.trim().to_string());
|
||||
let Some(path) = command_parts.next() else {
|
||||
return;
|
||||
};
|
||||
let args = command_parts.collect::<Vec<_>>();
|
||||
|
||||
if let Some(workspace) = self.workspace.upgrade() {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
let fs = workspace.app_state().fs.clone();
|
||||
update_settings_file::<ProjectSettings>(fs.clone(), cx, |settings, _| {
|
||||
settings.context_servers.insert(
|
||||
name.into(),
|
||||
ContextServerSettings::Custom {
|
||||
command: ContextServerCommand {
|
||||
path,
|
||||
args,
|
||||
env: None,
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
|
||||
fn cancel(&mut self, _: &menu::Cancel, cx: &mut Context<Self>) {
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
}
|
||||
|
||||
impl ModalView for AddContextServerModal {}
|
||||
|
||||
impl Focusable for AddContextServerModal {
|
||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||
self.name_editor.focus_handle(cx).clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<DismissEvent> for AddContextServerModal {}
|
||||
|
||||
impl Render for AddContextServerModal {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let is_name_empty = self.name_editor.read(cx).is_empty(cx);
|
||||
let is_command_empty = self.command_editor.read(cx).is_empty(cx);
|
||||
|
||||
let focus_handle = self.focus_handle(cx);
|
||||
|
||||
div()
|
||||
.elevation_3(cx)
|
||||
.w(rems(34.))
|
||||
.key_context("AddContextServerModal")
|
||||
.on_action(
|
||||
cx.listener(|this, _: &menu::Cancel, _window, cx| this.cancel(&menu::Cancel, cx)),
|
||||
)
|
||||
.on_action(
|
||||
cx.listener(|this, _: &menu::Confirm, _window, cx| {
|
||||
this.confirm(&menu::Confirm, cx)
|
||||
}),
|
||||
)
|
||||
.capture_any_mouse_down(cx.listener(|this, _, window, cx| {
|
||||
this.focus_handle(cx).focus(window);
|
||||
}))
|
||||
.on_mouse_down_out(cx.listener(|_this, _, _, cx| cx.emit(DismissEvent)))
|
||||
.child(
|
||||
Modal::new("add-context-server", None)
|
||||
.header(ModalHeader::new().headline("Add MCP Server"))
|
||||
.section(
|
||||
Section::new().child(
|
||||
v_flex()
|
||||
.gap_2()
|
||||
.child(self.name_editor.clone())
|
||||
.child(self.command_editor.clone()),
|
||||
),
|
||||
)
|
||||
.footer(
|
||||
ModalFooter::new().end_slot(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
Button::new("cancel", "Cancel")
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(
|
||||
&menu::Cancel,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
)
|
||||
.on_click(cx.listener(|this, _event, _window, cx| {
|
||||
this.cancel(&menu::Cancel, cx)
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
Button::new("add-server", "Add Server")
|
||||
.disabled(is_name_empty || is_command_empty)
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(
|
||||
&menu::Confirm,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
)
|
||||
.map(|button| {
|
||||
if is_name_empty {
|
||||
button.tooltip(Tooltip::text("Name is required"))
|
||||
} else if is_command_empty {
|
||||
button.tooltip(Tooltip::text("Command is required"))
|
||||
} else {
|
||||
button
|
||||
}
|
||||
})
|
||||
.on_click(cx.listener(|this, _event, _window, cx| {
|
||||
this.confirm(&menu::Confirm, cx)
|
||||
})),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1184,8 +1184,17 @@ impl AgentPanel {
|
||||
let fs = self.fs.clone();
|
||||
|
||||
self.set_active_view(ActiveView::Configuration, window, cx);
|
||||
self.configuration =
|
||||
Some(cx.new(|cx| AgentConfiguration::new(fs, context_server_store, tools, window, cx)));
|
||||
self.configuration = Some(cx.new(|cx| {
|
||||
AgentConfiguration::new(
|
||||
fs,
|
||||
context_server_store,
|
||||
tools,
|
||||
self.language_registry.clone(),
|
||||
self.workspace.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}));
|
||||
|
||||
if let Some(configuration) = self.configuration.as_ref() {
|
||||
self.configuration_subscription = Some(cx.subscribe_in(
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Context as _;
|
||||
use context_server::ContextServerId;
|
||||
use extension::{ContextServerConfiguration, ExtensionManifest};
|
||||
use extension::ExtensionManifest;
|
||||
use fs::Fs;
|
||||
use gpui::Task;
|
||||
use gpui::WeakEntity;
|
||||
use language::LanguageRegistry;
|
||||
use project::{
|
||||
context_server_store::registry::ContextServerDescriptorRegistry,
|
||||
project_settings::ProjectSettings,
|
||||
};
|
||||
use project::project_settings::ProjectSettings;
|
||||
use settings::update_settings_file;
|
||||
use ui::prelude::*;
|
||||
use util::ResultExt;
|
||||
@@ -27,12 +23,12 @@ pub(crate) fn init(language_registry: Arc<LanguageRegistry>, fs: Arc<dyn Fs>, cx
|
||||
cx.subscribe_in(extension_events, window, {
|
||||
let language_registry = language_registry.clone();
|
||||
let fs = fs.clone();
|
||||
move |workspace, _, event, window, cx| match event {
|
||||
move |_, _, event, window, cx| match event {
|
||||
extension::Event::ExtensionInstalled(manifest) => {
|
||||
show_configure_mcp_modal(
|
||||
language_registry.clone(),
|
||||
manifest,
|
||||
workspace,
|
||||
cx.weak_entity(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
@@ -49,7 +45,7 @@ pub(crate) fn init(language_registry: Arc<LanguageRegistry>, fs: Arc<dyn Fs>, cx
|
||||
show_configure_mcp_modal(
|
||||
language_registry.clone(),
|
||||
manifest,
|
||||
workspace,
|
||||
cx.weak_entity(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
@@ -80,19 +76,10 @@ fn remove_context_server_settings(
|
||||
});
|
||||
}
|
||||
|
||||
pub enum Configuration {
|
||||
NotAvailable(ContextServerId, Option<SharedString>),
|
||||
Required(
|
||||
ContextServerId,
|
||||
Option<SharedString>,
|
||||
ContextServerConfiguration,
|
||||
),
|
||||
}
|
||||
|
||||
fn show_configure_mcp_modal(
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
manifest: &Arc<ExtensionManifest>,
|
||||
workspace: &mut Workspace,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<'_, Workspace>,
|
||||
) {
|
||||
@@ -100,70 +87,30 @@ fn show_configure_mcp_modal(
|
||||
return;
|
||||
}
|
||||
|
||||
let context_server_store = workspace.project().read(cx).context_server_store();
|
||||
let repository: Option<SharedString> = manifest.repository.as_ref().map(|s| s.clone().into());
|
||||
let ids = manifest.context_servers.keys().cloned().collect::<Vec<_>>();
|
||||
if ids.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let registry = ContextServerDescriptorRegistry::default_global(cx).read(cx);
|
||||
let worktree_store = workspace.project().read(cx).worktree_store();
|
||||
let configuration_tasks = manifest
|
||||
.context_servers
|
||||
.keys()
|
||||
.cloned()
|
||||
.map({
|
||||
|key| {
|
||||
let Some(descriptor) = registry.context_server_descriptor(&key) else {
|
||||
return Task::ready(Configuration::NotAvailable(
|
||||
ContextServerId(key),
|
||||
repository.clone(),
|
||||
));
|
||||
window
|
||||
.spawn(cx, async move |cx| {
|
||||
for id in ids {
|
||||
let Some(task) = cx
|
||||
.update(|window, cx| {
|
||||
ConfigureContextServerModal::show_modal_for_existing_server(
|
||||
ContextServerId(id.clone()),
|
||||
language_registry.clone(),
|
||||
workspace.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.ok()
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
cx.spawn({
|
||||
let repository_url = repository.clone();
|
||||
let worktree_store = worktree_store.clone();
|
||||
async move |_, cx| {
|
||||
let configuration = descriptor
|
||||
.configuration(worktree_store.clone(), &cx)
|
||||
.await
|
||||
.context("Failed to resolve context server configuration")
|
||||
.log_err()
|
||||
.flatten();
|
||||
|
||||
match configuration {
|
||||
Some(config) => Configuration::Required(
|
||||
ContextServerId(key),
|
||||
repository_url,
|
||||
config,
|
||||
),
|
||||
None => {
|
||||
Configuration::NotAvailable(ContextServerId(key), repository_url)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
task.await.log_err();
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let jsonc_language = language_registry.language_for_name("jsonc");
|
||||
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
let configurations = futures::future::join_all(configuration_tasks).await;
|
||||
let jsonc_language = jsonc_language.await.ok();
|
||||
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
let workspace = cx.entity().downgrade();
|
||||
this.toggle_modal(window, cx, |window, cx| {
|
||||
ConfigureContextServerModal::new(
|
||||
configurations.into_iter(),
|
||||
context_server_store,
|
||||
jsonc_language,
|
||||
language_registry,
|
||||
workspace,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
})
|
||||
})
|
||||
.detach();
|
||||
.detach();
|
||||
}
|
||||
@@ -838,7 +838,11 @@ impl ExtensionStore {
|
||||
self.install_or_upgrade_extension_at_endpoint(extension_id, url, operation, cx)
|
||||
}
|
||||
|
||||
pub fn uninstall_extension(&mut self, extension_id: Arc<str>, cx: &mut Context<Self>) {
|
||||
pub fn uninstall_extension(
|
||||
&mut self,
|
||||
extension_id: Arc<str>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let extension_dir = self.installed_dir.join(extension_id.as_ref());
|
||||
let work_dir = self.wasm_host.work_dir.join(extension_id.as_ref());
|
||||
let fs = self.fs.clone();
|
||||
@@ -846,7 +850,7 @@ impl ExtensionStore {
|
||||
let extension_manifest = self.extension_manifest_for_id(&extension_id).cloned();
|
||||
|
||||
match self.outstanding_operations.entry(extension_id.clone()) {
|
||||
btree_map::Entry::Occupied(_) => return,
|
||||
btree_map::Entry::Occupied(_) => return Task::ready(Ok(())),
|
||||
btree_map::Entry::Vacant(e) => e.insert(ExtensionOperation::Remove),
|
||||
};
|
||||
|
||||
@@ -894,7 +898,6 @@ impl ExtensionStore {
|
||||
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx)
|
||||
}
|
||||
|
||||
pub fn install_dev_extension(
|
||||
|
||||
@@ -482,7 +482,9 @@ async fn test_extension_store(cx: &mut TestAppContext) {
|
||||
});
|
||||
|
||||
store.update(cx, |store, cx| {
|
||||
store.uninstall_extension("zed-ruby".into(), cx)
|
||||
store
|
||||
.uninstall_extension("zed-ruby".into(), cx)
|
||||
.detach_and_log_err(cx);
|
||||
});
|
||||
|
||||
cx.executor().advance_clock(RELOAD_DEBOUNCE_DURATION);
|
||||
|
||||
@@ -583,7 +583,7 @@ impl ExtensionsPage {
|
||||
let extension_id = extension.id.clone();
|
||||
move |_, _, cx| {
|
||||
ExtensionStore::global(cx).update(cx, |store, cx| {
|
||||
store.uninstall_extension(extension_id.clone(), cx)
|
||||
store.uninstall_extension(extension_id.clone(), cx).detach_and_log_err(cx);
|
||||
});
|
||||
}
|
||||
}),
|
||||
@@ -983,7 +983,9 @@ impl ExtensionsPage {
|
||||
move |_, _, cx| {
|
||||
telemetry::event!("Extension Uninstalled", extension_id);
|
||||
ExtensionStore::global(cx).update(cx, |store, cx| {
|
||||
store.uninstall_extension(extension_id.clone(), cx)
|
||||
store
|
||||
.uninstall_extension(extension_id.clone(), cx)
|
||||
.detach_and_log_err(cx);
|
||||
});
|
||||
}
|
||||
}),
|
||||
|
||||
@@ -263,6 +263,8 @@ pub enum IconName {
|
||||
ZedAssistantFilled,
|
||||
ZedBurnMode,
|
||||
ZedBurnModeOn,
|
||||
ZedMcpCustom,
|
||||
ZedMcpExtension,
|
||||
ZedPredict,
|
||||
ZedPredictDisabled,
|
||||
ZedPredictDown,
|
||||
|
||||
@@ -235,6 +235,13 @@ impl ContextServerStore {
|
||||
self.servers.get(id).map(ContextServerStatus::from_state)
|
||||
}
|
||||
|
||||
pub fn configuration_for_server(
|
||||
&self,
|
||||
id: &ContextServerId,
|
||||
) -> Option<Arc<ContextServerConfiguration>> {
|
||||
self.servers.get(id).map(|state| state.configuration())
|
||||
}
|
||||
|
||||
pub fn all_server_ids(&self) -> Vec<ContextServerId> {
|
||||
self.servers.keys().cloned().collect()
|
||||
}
|
||||
|
||||
@@ -1496,25 +1496,24 @@ fn replace_value_in_json_text(
|
||||
if between_comma_and_key.trim().is_empty() {
|
||||
removal_start = comma_pos;
|
||||
}
|
||||
} else {
|
||||
// No preceding comma, check for trailing comma
|
||||
if let Some(remaining_text) = text.get(existing_value_range.end..) {
|
||||
let mut chars = remaining_text.char_indices();
|
||||
while let Some((offset, ch)) = chars.next() {
|
||||
if ch == ',' {
|
||||
removal_end = existing_value_range.end + offset + 1;
|
||||
// Also consume whitespace after the comma
|
||||
while let Some((_, next_ch)) = chars.next() {
|
||||
if next_ch.is_whitespace() {
|
||||
removal_end += next_ch.len_utf8();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(remaining_text) = text.get(existing_value_range.end..) {
|
||||
let mut chars = remaining_text.char_indices();
|
||||
while let Some((offset, ch)) = chars.next() {
|
||||
if ch == ',' {
|
||||
removal_end = existing_value_range.end + offset + 1;
|
||||
// Also consume whitespace after the comma
|
||||
while let Some((_, next_ch)) = chars.next() {
|
||||
if next_ch.is_whitespace() {
|
||||
removal_end += next_ch.len_utf8();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
break;
|
||||
} else if !ch.is_whitespace() {
|
||||
break;
|
||||
}
|
||||
break;
|
||||
} else if !ch.is_whitespace() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user