feat(context_server): Add HTTP/SSE transports and settings
This commit introduces HTTP and SSE transport implementations for the `context_server` crate, allowing Zed to communicate with remote context providers. The new transports are built on the existing `http_client` and `gpui` executor, adhering to the project's architectural patterns. To support configuration of these new transports, the settings system has been updated: - A `Remote` variant has been added to the `ContextServerSettings` enum, allowing users to configure remote servers in `settings.json` with a URL. - The project logic has been updated to initialize remote context servers using the new `ContextServer::from_url` constructor. - The agent configuration UI now includes an "Add Remote Server" option and a dedicated modal for adding and editing remote server configurations. Unit tests have been added for the new transports and for the logic that handles remote server configurations.
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -3648,7 +3648,6 @@ dependencies = [
|
||||
"settings",
|
||||
"smol",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"url",
|
||||
"util",
|
||||
"workspace-hack",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
mod add_llm_provider_modal;
|
||||
mod configure_context_server_modal;
|
||||
mod configure_remote_context_server_modal;
|
||||
mod configure_context_server_tools_modal;
|
||||
mod manage_profiles_modal;
|
||||
mod tool_picker;
|
||||
@@ -43,9 +44,12 @@ pub(crate) use configure_context_server_tools_modal::ConfigureContextServerTools
|
||||
pub(crate) use manage_profiles_modal::ManageProfilesModal;
|
||||
|
||||
use crate::{
|
||||
AddContextServer,
|
||||
agent_configuration::add_llm_provider_modal::{AddLlmProviderModal, LlmCompatibleProvider},
|
||||
agent_configuration::configure_remote_context_server_modal::ConfigureRemoteContextServerModal,
|
||||
};
|
||||
use gpui::actions;
|
||||
|
||||
actions!(agent_ui, [AddRemoteContextServer]);
|
||||
|
||||
pub struct AgentConfiguration {
|
||||
fs: Arc<dyn Fs>,
|
||||
@@ -581,6 +585,9 @@ impl AgentConfiguration {
|
||||
menu.entry("Add Custom Server", None, {
|
||||
|window, cx| window.dispatch_action(AddContextServer.boxed_clone(), cx)
|
||||
})
|
||||
.entry("Add Remote Server", None, {
|
||||
|window, cx| window.dispatch_action(AddRemoteContextServer.boxed_clone(), cx)
|
||||
})
|
||||
.entry("Install from Extensions", None, {
|
||||
|window, cx| {
|
||||
window.dispatch_action(
|
||||
@@ -704,11 +711,26 @@ impl AgentConfiguration {
|
||||
.map_or([].as_slice(), |tools| tools.as_slice());
|
||||
let tool_count = tools.len();
|
||||
|
||||
let is_remote = server_configuration
|
||||
.as_ref()
|
||||
.map(|config| {
|
||||
matches!(
|
||||
config.as_ref(),
|
||||
ContextServerConfiguration::Remote { .. }
|
||||
)
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
let (source_icon, source_tooltip) = if is_from_extension {
|
||||
(
|
||||
IconName::ZedMcpExtension,
|
||||
"This MCP server was installed from an extension.",
|
||||
)
|
||||
} else if is_remote {
|
||||
(
|
||||
IconName::Server,
|
||||
"This is a remote MCP server.",
|
||||
)
|
||||
} else {
|
||||
(
|
||||
IconName::ZedMcpCustom,
|
||||
@@ -764,15 +786,26 @@ impl AgentConfiguration {
|
||||
let context_server_id = context_server_id.clone();
|
||||
let language_registry = language_registry.clone();
|
||||
let workspace = workspace.clone();
|
||||
let is_remote = is_remote;
|
||||
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);
|
||||
if is_remote {
|
||||
ConfigureRemoteContextServerModal::show_modal_for_existing_server(
|
||||
context_server_id.clone(),
|
||||
workspace.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.detach_and_log_err(cx);
|
||||
} else {
|
||||
ConfigureContextServerModal::show_modal_for_existing_server(
|
||||
context_server_id.clone(),
|
||||
language_registry.clone(),
|
||||
workspace.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
}).when(tool_count >= 1, |this| this.entry("View Tools", None, {
|
||||
let context_server_id = context_server_id.clone();
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
use crate::agent_configuration::AddContextServer;
|
||||
use gpui::{
|
||||
actions, App, AppContext, AsyncWindowContext, Context, Entity, EventEmitter, FocusHandle,
|
||||
Focusable, Global, Subscription, Task, WeakEntity, Window,
|
||||
};
|
||||
use language::LanguageRegistry;
|
||||
use project::{
|
||||
context_server_store::ContextServerStore,
|
||||
project_settings::{ContextServerSettings, ProjectSettings},
|
||||
Project,
|
||||
};
|
||||
use settings::{Settings, SettingsStore, update_settings_file};
|
||||
use std::sync::Arc;
|
||||
use ui::{
|
||||
prelude::*, Button, ButtonStyle, Checkbox, Divider, Editor, EditorWithAction, Icon,
|
||||
IconButton, IconName, Label, Select, Switch, SwitchColor, TextField,
|
||||
};
|
||||
use workspace::Workspace;
|
||||
|
||||
pub struct ConfigureRemoteContextServerModal {
|
||||
fs: Arc<fs::Fs>,
|
||||
server_id: Option<ContextServerId>,
|
||||
server_name_editor: Entity<Editor>,
|
||||
server_url_editor: Entity<Editor>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
focus_handle: FocusHandle,
|
||||
}
|
||||
|
||||
actions!(
|
||||
remote_context_server_modal,
|
||||
[Submit, Dismiss, AddContextServer]
|
||||
);
|
||||
|
||||
impl ConfigureRemoteContextServerModal {
|
||||
pub fn new(
|
||||
workspace: WeakEntity<Workspace>,
|
||||
server_id: Option<ContextServerId>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let fs = workspace.read(cx).app_state().fs.clone();
|
||||
let server_name_editor = cx.new(|cx| Editor::single_line(Some(cx.text_style()), cx));
|
||||
let server_url_editor = cx.new(|cx| Editor::single_line(Some(cx.text_style()), cx));
|
||||
let focus_handle = cx.focus_handle();
|
||||
|
||||
if let Some(server_id) = &server_id {
|
||||
server_name_editor.update(cx, |editor, cx| {
|
||||
editor.set_text(server_id.0.to_string(), cx);
|
||||
});
|
||||
|
||||
if let Some(project) = workspace.read(cx).project().as_ref() {
|
||||
let settings = ProjectSettings::get_global(cx);
|
||||
if let Some(ContextServerSettings::Remote { url, .. }) =
|
||||
settings.context_servers.get(&server_id.0)
|
||||
{
|
||||
server_url_editor.update(cx, |editor, cx| {
|
||||
editor.set_text(url.clone(), cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
fs,
|
||||
server_id,
|
||||
server_name_editor,
|
||||
server_url_editor,
|
||||
workspace,
|
||||
focus_handle,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle(
|
||||
workspace: &mut Workspace,
|
||||
_: &super::AddRemoteContextServer,
|
||||
window: &mut Window,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
window.toggle_modal(cx, |cx| {
|
||||
Self::new(workspace.weak_handle(), None, cx)
|
||||
});
|
||||
}
|
||||
|
||||
pub fn show_modal_for_existing_server(
|
||||
server_id: ContextServerId,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
window: &mut Window,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<()> {
|
||||
let task = window
|
||||
.new_modal(cx, |cx| Self::new(workspace, Some(server_id), cx))
|
||||
.log_err();
|
||||
cx.spawn(|_, _| async move {
|
||||
task.await;
|
||||
})
|
||||
}
|
||||
|
||||
fn submit(&mut self, _: &Submit, window: &mut Window, cx: &mut AppContext) {
|
||||
let server_name = self.server_name_editor.read(cx).text(cx);
|
||||
let server_url = self.server_url_editor.read(cx).text(cx);
|
||||
|
||||
if server_name.is_empty() || server_url.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let settings_path = SettingsStore::global(cx).read(cx).user_settings_file_path();
|
||||
let fs = self.fs.clone();
|
||||
let server_id = self.server_id.clone();
|
||||
cx.spawn(|_cx| async move {
|
||||
update_settings_file::<ProjectSettings>(fs, settings_path, |settings| {
|
||||
if let Some(server_id) = server_id {
|
||||
if server_id.0.as_ref() != server_name {
|
||||
settings.context_servers.remove(&server_id.0);
|
||||
}
|
||||
}
|
||||
|
||||
settings
|
||||
.context_servers
|
||||
.insert(server_name.into(), ContextServerSettings::Remote {
|
||||
enabled: true,
|
||||
url: server_url,
|
||||
});
|
||||
})
|
||||
.await
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
|
||||
self.dismiss(&Dismiss, window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
impl Focusable for ConfigureRemoteContextServerModal {
|
||||
fn focus_handle(&self, _: &AppContext) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ConfigureRemoteContextServerModal {
|
||||
fn render(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
v_flex()
|
||||
.key_context("ConfigureRemoteContextServerModal")
|
||||
.on_action(cx.listener(Self::submit))
|
||||
.on_action(cx.listener(Self::dismiss))
|
||||
.w_96()
|
||||
.gap_4()
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_2()
|
||||
.child(Label::new("Server Name"))
|
||||
.child(TextField::new(self.server_name_editor.clone())),
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_2()
|
||||
.child(Label::new("Server URL"))
|
||||
.child(TextField::new(self.server_url_editor.clone())),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_end()
|
||||
.gap_2()
|
||||
.child(Button::new("cancel", "Cancel").on_click(cx.listener(Self::dismiss)))
|
||||
.child(Button::new("submit", "Add Server").on_click(cx.listener(Self::submit))),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Modal for ConfigureRemoteContextServerModal {
|
||||
fn on_before_dismiss(&mut self, _cx: &mut AppContext) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn on_after_dismiss(&mut self, _action: &Self::Action, _cx: &mut AppContext) {}
|
||||
}
|
||||
@@ -42,7 +42,9 @@ use serde::{Deserialize, Serialize};
|
||||
use settings::{LanguageModelSelection, Settings as _, SettingsStore};
|
||||
use std::any::TypeId;
|
||||
|
||||
use crate::agent_configuration::{ConfigureContextServerModal, ManageProfilesModal};
|
||||
use crate::agent_configuration::{
|
||||
AddRemoteContextServer, ConfigureContextServerModal, ManageProfilesModal,
|
||||
};
|
||||
pub use crate::agent_panel::{AgentPanel, ConcreteAssistantPanelDelegate};
|
||||
pub use crate::inline_assistant::InlineAssistant;
|
||||
pub use agent_diff::{AgentDiffPane, AgentDiffToolbar};
|
||||
@@ -281,6 +283,24 @@ pub fn init(
|
||||
ConfigureContextServerModal::register(workspace, language_registry.clone(), window, cx)
|
||||
})
|
||||
.detach();
|
||||
cx.observe_new(move |workspace, window, cx| {
|
||||
window.on_action({
|
||||
let workspace = workspace.weak_handle();
|
||||
move |_: &AddRemoteContextServer, cx| {
|
||||
if let Some(workspace) = workspace.upgrade(cx) {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
agent_configuration::configure_remote_context_server_modal::ConfigureRemoteContextServerModal::toggle(
|
||||
workspace,
|
||||
&AddRemoteContextServer,
|
||||
cx.window(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
cx.observe_new(ManageProfilesModal::register).detach();
|
||||
|
||||
// Update command palette filter based on AI settings
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use anyhow::{Result, anyhow};
|
||||
use async_trait::async_trait;
|
||||
use futures::{io::AsyncReadExt, stream, Stream, StreamExt};
|
||||
use futures::{io::AsyncReadExt, stream, Stream};
|
||||
use http_client::{
|
||||
http::Method,
|
||||
AsyncBody, HttpClient, Request,
|
||||
};
|
||||
use postage::prelude::Sink;
|
||||
use postage::prelude::{Sink, Stream as _};
|
||||
use std::{
|
||||
pin::Pin,
|
||||
sync::{Arc, Mutex},
|
||||
@@ -43,7 +43,7 @@ impl Transport for HttpTransport {
|
||||
|
||||
let http_client = self.http_client.clone();
|
||||
let mut tx = self.tx.lock().unwrap().take().ok_or_else(|| anyhow!("transport already used"))?;
|
||||
gpui::spawn(async move {
|
||||
smol::spawn(async move {
|
||||
let res = async {
|
||||
let mut response = http_client.send(request).await?;
|
||||
|
||||
@@ -67,7 +67,10 @@ impl Transport for HttpTransport {
|
||||
fn receive(&self) -> Pin<Box<dyn Stream<Item = String> + Send>> {
|
||||
if let Some(mut rx) = self.rx.lock().unwrap().take() {
|
||||
Box::pin(stream::once(async move {
|
||||
rx.recv().await.unwrap_or_else(|_| Ok("".to_string())).unwrap_or_default()
|
||||
match rx.recv().await {
|
||||
Some(Ok(response)) => response,
|
||||
_ => "".to_string(),
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
Box::pin(stream::empty())
|
||||
|
||||
@@ -53,7 +53,7 @@ impl Transport for SseTransport {
|
||||
let mut tx = self.tx.lock().unwrap().take().ok_or_else(|| anyhow!("transport already used"))?;
|
||||
let mut err_tx = self.err_tx.lock().unwrap().take().ok_or_else(|| anyhow!("transport already used"))?;
|
||||
|
||||
gpui::spawn(async move {
|
||||
smol::spawn(async move {
|
||||
let response = http_client.send(request).await;
|
||||
match response {
|
||||
Ok(response) => {
|
||||
|
||||
@@ -99,13 +99,17 @@ pub enum ContextServerConfiguration {
|
||||
command: ContextServerCommand,
|
||||
settings: serde_json::Value,
|
||||
},
|
||||
Remote {
|
||||
url: url::Url,
|
||||
},
|
||||
}
|
||||
|
||||
impl ContextServerConfiguration {
|
||||
pub fn command(&self) -> &ContextServerCommand {
|
||||
pub fn command(&self) -> Option<&ContextServerCommand> {
|
||||
match self {
|
||||
ContextServerConfiguration::Custom { command } => command,
|
||||
ContextServerConfiguration::Extension { command, .. } => command,
|
||||
ContextServerConfiguration::Custom { command } => Some(command),
|
||||
ContextServerConfiguration::Extension { command, .. } => Some(command),
|
||||
ContextServerConfiguration::Remote { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,6 +138,10 @@ impl ContextServerConfiguration {
|
||||
|
||||
Some(ContextServerConfiguration::Extension { command, settings })
|
||||
}
|
||||
ContextServerSettings::Remote { enabled: _, url } => {
|
||||
let url = url::Url::parse(&url).log_err()?;
|
||||
Some(ContextServerConfiguration::Remote { url })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -472,31 +480,37 @@ impl ContextServerStore {
|
||||
configuration: Arc<ContextServerConfiguration>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Arc<ContextServer> {
|
||||
let root_path = self
|
||||
.project
|
||||
.read_with(cx, |project, cx| project.active_project_directory(cx))
|
||||
.ok()
|
||||
.flatten()
|
||||
.or_else(|| {
|
||||
self.worktree_store.read_with(cx, |store, cx| {
|
||||
store.visible_worktrees(cx).fold(None, |acc, item| {
|
||||
if acc.is_none() {
|
||||
item.read(cx).root_dir()
|
||||
} else {
|
||||
acc
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
if let Some(factory) = self.context_server_factory.as_ref() {
|
||||
factory(id, configuration)
|
||||
} else {
|
||||
Arc::new(ContextServer::stdio(
|
||||
id,
|
||||
configuration.command().clone(),
|
||||
root_path,
|
||||
))
|
||||
return factory(id, configuration);
|
||||
}
|
||||
|
||||
match configuration.as_ref() {
|
||||
ContextServerConfiguration::Remote { url } => {
|
||||
Arc::new(ContextServer::from_url(id, url, cx).unwrap())
|
||||
}
|
||||
_ => {
|
||||
let root_path = self
|
||||
.project
|
||||
.read_with(cx, |project, cx| project.active_project_directory(cx))
|
||||
.ok()
|
||||
.flatten()
|
||||
.or_else(|| {
|
||||
self.worktree_store.read_with(cx, |store, cx| {
|
||||
store.visible_worktrees(cx).fold(None, |acc, item| {
|
||||
if acc.is_none() {
|
||||
item.read(cx).root_dir()
|
||||
} else {
|
||||
acc
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
Arc::new(ContextServer::stdio(
|
||||
id,
|
||||
configuration.command().unwrap().clone(),
|
||||
root_path,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1219,6 +1233,60 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_remote_context_server(cx: &mut TestAppContext) {
|
||||
const SERVER_ID: &str = "remote-server";
|
||||
let server_id = ContextServerId(SERVER_ID.into());
|
||||
let server_url = "http://example.com/api";
|
||||
|
||||
let (_fs, project) = setup_context_server_test(
|
||||
cx,
|
||||
json!({ "code.rs": "" }),
|
||||
vec![(
|
||||
SERVER_ID.into(),
|
||||
ContextServerSettings::Remote {
|
||||
enabled: true,
|
||||
url: server_url.to_string(),
|
||||
},
|
||||
)],
|
||||
)
|
||||
.await;
|
||||
|
||||
let executor = cx.executor();
|
||||
let registry = cx.new(|_| ContextServerDescriptorRegistry::new());
|
||||
let store = cx.new(|cx| {
|
||||
ContextServerStore::test_maintain_server_loop(
|
||||
Box::new(move |id, config| {
|
||||
assert_eq!(id.0.as_ref(), SERVER_ID);
|
||||
match config.as_ref() {
|
||||
ContextServerConfiguration::Remote { url } => {
|
||||
assert_eq!(url.as_str(), server_url);
|
||||
}
|
||||
_ => panic!("Expected remote configuration"),
|
||||
}
|
||||
Arc::new(ContextServer::new(
|
||||
id.clone(),
|
||||
Arc::new(create_fake_transport(id.0.to_string(), executor.clone())),
|
||||
))
|
||||
}),
|
||||
registry.clone(),
|
||||
project.read(cx).worktree_store(),
|
||||
project.downgrade(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let _server_events = assert_server_events(
|
||||
&store,
|
||||
vec![
|
||||
(server_id.clone(), ContextServerStatus::Starting),
|
||||
(server_id.clone(), ContextServerStatus::Running),
|
||||
],
|
||||
cx,
|
||||
);
|
||||
cx.run_until_parked();
|
||||
}
|
||||
|
||||
struct ServerEvents {
|
||||
received_event_count: Rc<RefCell<usize>>,
|
||||
expected_event_count: usize,
|
||||
|
||||
@@ -139,6 +139,13 @@ pub enum ContextServerSettings {
|
||||
/// are supported.
|
||||
settings: serde_json::Value,
|
||||
},
|
||||
Remote {
|
||||
/// Whether the context server is enabled.
|
||||
#[serde(default = "default_true")]
|
||||
enabled: bool,
|
||||
/// The URL of the remote context server.
|
||||
url: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<settings::ContextServerSettingsContent> for ContextServerSettings {
|
||||
@@ -150,6 +157,9 @@ impl From<settings::ContextServerSettingsContent> for ContextServerSettings {
|
||||
settings::ContextServerSettingsContent::Extension { enabled, settings } => {
|
||||
ContextServerSettings::Extension { enabled, settings }
|
||||
}
|
||||
settings::ContextServerSettingsContent::Remote { enabled, url } => {
|
||||
ContextServerSettings::Remote { enabled, url }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,6 +172,9 @@ impl Into<settings::ContextServerSettingsContent> for ContextServerSettings {
|
||||
ContextServerSettings::Extension { enabled, settings } => {
|
||||
settings::ContextServerSettingsContent::Extension { enabled, settings }
|
||||
}
|
||||
ContextServerSettings::Remote { enabled, url } => {
|
||||
settings::ContextServerSettingsContent::Remote { enabled, url }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -178,6 +191,7 @@ impl ContextServerSettings {
|
||||
match self {
|
||||
ContextServerSettings::Custom { enabled, .. } => *enabled,
|
||||
ContextServerSettings::Extension { enabled, .. } => *enabled,
|
||||
ContextServerSettings::Remote { enabled, .. } => *enabled,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,6 +199,7 @@ impl ContextServerSettings {
|
||||
match self {
|
||||
ContextServerSettings::Custom { enabled: e, .. } => *e = enabled,
|
||||
ContextServerSettings::Extension { enabled: e, .. } => *e = enabled,
|
||||
ContextServerSettings::Remote { enabled: e, .. } => *e = enabled,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,20 +186,31 @@ pub enum ContextServerSettingsContent {
|
||||
/// are supported.
|
||||
settings: serde_json::Value,
|
||||
},
|
||||
Remote {
|
||||
/// Whether the context server is enabled.
|
||||
#[serde(default = "default_true")]
|
||||
enabled: bool,
|
||||
/// The URL of the remote context server.
|
||||
url: String,
|
||||
},
|
||||
}
|
||||
impl ContextServerSettingsContent {
|
||||
pub fn set_enabled(&mut self, enabled: bool) {
|
||||
match self {
|
||||
ContextServerSettingsContent::Custom {
|
||||
enabled: custom_enabled,
|
||||
command: _,
|
||||
..
|
||||
} => {
|
||||
*custom_enabled = enabled;
|
||||
}
|
||||
ContextServerSettingsContent::Extension {
|
||||
enabled: ext_enabled,
|
||||
settings: _,
|
||||
..
|
||||
} => *ext_enabled = enabled,
|
||||
ContextServerSettingsContent::Remote {
|
||||
enabled: remote_enabled,
|
||||
..
|
||||
} => *remote_enabled = enabled,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user