Detailed changes
@@ -80,6 +80,18 @@ pub trait Extension: Send + Sync + 'static {
worktree: Arc<dyn WorktreeDelegate>,
) -> Result<Option<String>>;
+ async fn language_server_initialization_options_schema(
+ &self,
+ language_server_id: LanguageServerName,
+ worktree: Arc<dyn WorktreeDelegate>,
+ ) -> Result<Option<String>>;
+
+ async fn language_server_workspace_configuration_schema(
+ &self,
+ language_server_id: LanguageServerName,
+ worktree: Arc<dyn WorktreeDelegate>,
+ ) -> Result<Option<String>>;
+
async fn language_server_additional_initialization_options(
&self,
language_server_id: LanguageServerName,
@@ -100,6 +100,28 @@ pub trait Extension: Send + Sync {
Ok(None)
}
+ /// Returns the JSON schema for the initialization options.
+ ///
+ /// The schema must conform to the JSON Schema speification.
+ fn language_server_initialization_options_schema(
+ &mut self,
+ _language_server_id: &LanguageServerId,
+ _worktree: &Worktree,
+ ) -> Option<serde_json::Value> {
+ None
+ }
+
+ /// Returns the JSON schema for the workspace configuration.
+ ///
+ /// The schema must conform to the JSON Schema specification.
+ fn language_server_workspace_configuration_schema(
+ &mut self,
+ _language_server_id: &LanguageServerId,
+ _worktree: &Worktree,
+ ) -> Option<serde_json::Value> {
+ None
+ }
+
/// Returns the initialization options to pass to the other language server.
fn language_server_additional_initialization_options(
&mut self,
@@ -370,6 +392,26 @@ impl wit::Guest for Component {
.and_then(|value| serde_json::to_string(&value).ok()))
}
+ fn language_server_initialization_options_schema(
+ language_server_id: String,
+ worktree: &Worktree,
+ ) -> Option<String> {
+ let language_server_id = LanguageServerId(language_server_id);
+ extension()
+ .language_server_initialization_options_schema(&language_server_id, worktree)
+ .and_then(|value| serde_json::to_string(&value).ok())
+ }
+
+ fn language_server_workspace_configuration_schema(
+ language_server_id: String,
+ worktree: &Worktree,
+ ) -> Option<String> {
+ let language_server_id = LanguageServerId(language_server_id);
+ extension()
+ .language_server_workspace_configuration_schema(&language_server_id, worktree)
+ .and_then(|value| serde_json::to_string(&value).ok())
+ }
+
fn language_server_additional_initialization_options(
language_server_id: String,
target_language_server_id: String,
@@ -101,6 +101,16 @@ world extension {
/// Returns the workspace configuration options to pass to the language server.
export language-server-workspace-configuration: func(language-server-id: string, worktree: borrow<worktree>) -> result<option<string>, string>;
+ /// Returns the JSON schema for the initialization options.
+ ///
+ /// The schema is represented as a JSON string conforming to the JSON Schema specification.
+ export language-server-initialization-options-schema: func(language-server-id: string, worktree: borrow<worktree>) -> option<string>;
+
+ /// Returns the JSON schema for the workspace configuration.
+ ///
+ /// The schema is represented as a JSON string conforming to the JSON Schema specification.
+ export language-server-workspace-configuration-schema: func(language-server-id: string, worktree: borrow<worktree>) -> option<string>;
+
/// Returns the initialization options to pass to the other language server.
export language-server-additional-initialization-options: func(language-server-id: string, target-language-server-id: string, worktree: borrow<worktree>) -> result<option<string>, string>;
@@ -159,6 +159,48 @@ impl extension::Extension for WasmExtension {
.await?
}
+ async fn language_server_initialization_options_schema(
+ &self,
+ language_server_id: LanguageServerName,
+ worktree: Arc<dyn WorktreeDelegate>,
+ ) -> Result<Option<String>> {
+ self.call(|extension, store| {
+ async move {
+ let resource = store.data_mut().table().push(worktree)?;
+ extension
+ .call_language_server_initialization_options_schema(
+ store,
+ &language_server_id,
+ resource,
+ )
+ .await
+ }
+ .boxed()
+ })
+ .await?
+ }
+
+ async fn language_server_workspace_configuration_schema(
+ &self,
+ language_server_id: LanguageServerName,
+ worktree: Arc<dyn WorktreeDelegate>,
+ ) -> Result<Option<String>> {
+ self.call(|extension, store| {
+ async move {
+ let resource = store.data_mut().table().push(worktree)?;
+ extension
+ .call_language_server_workspace_configuration_schema(
+ store,
+ &language_server_id,
+ resource,
+ )
+ .await
+ }
+ .boxed()
+ })
+ .await?
+ }
+
async fn language_server_additional_initialization_options(
&self,
language_server_id: LanguageServerName,
@@ -465,6 +465,60 @@ impl Extension {
}
}
+ pub async fn call_language_server_initialization_options_schema(
+ &self,
+ store: &mut Store<WasmState>,
+ language_server_id: &LanguageServerName,
+ resource: Resource<Arc<dyn WorktreeDelegate>>,
+ ) -> Result<Option<String>> {
+ match self {
+ Extension::V0_8_0(ext) => {
+ ext.call_language_server_initialization_options_schema(
+ store,
+ &language_server_id.0,
+ resource,
+ )
+ .await
+ }
+ Extension::V0_6_0(_)
+ | Extension::V0_5_0(_)
+ | Extension::V0_4_0(_)
+ | Extension::V0_3_0(_)
+ | Extension::V0_2_0(_)
+ | Extension::V0_1_0(_)
+ | Extension::V0_0_6(_)
+ | Extension::V0_0_4(_)
+ | Extension::V0_0_1(_) => Ok(None),
+ }
+ }
+
+ pub async fn call_language_server_workspace_configuration_schema(
+ &self,
+ store: &mut Store<WasmState>,
+ language_server_id: &LanguageServerName,
+ resource: Resource<Arc<dyn WorktreeDelegate>>,
+ ) -> Result<Option<String>> {
+ match self {
+ Extension::V0_8_0(ext) => {
+ ext.call_language_server_workspace_configuration_schema(
+ store,
+ &language_server_id.0,
+ resource,
+ )
+ .await
+ }
+ Extension::V0_6_0(_)
+ | Extension::V0_5_0(_)
+ | Extension::V0_4_0(_)
+ | Extension::V0_3_0(_)
+ | Extension::V0_2_0(_)
+ | Extension::V0_1_0(_)
+ | Extension::V0_0_6(_)
+ | Extension::V0_0_4(_)
+ | Extension::V0_0_1(_) => Ok(None),
+ }
+ }
+
pub async fn call_language_server_additional_initialization_options(
&self,
store: &mut Store<WasmState>,
@@ -67,25 +67,22 @@ pub fn init(cx: &mut App) {
.detach();
if let Some(extension_events) = extension::ExtensionEvents::try_global(cx) {
- cx.subscribe(&extension_events, move |_, evt, cx| {
- match evt {
- extension::Event::ExtensionInstalled(_)
- | extension::Event::ExtensionUninstalled(_)
- | extension::Event::ConfigureExtensionRequested(_) => return,
- extension::Event::ExtensionsInstalledChanged => {}
+ cx.subscribe(&extension_events, move |_, evt, cx| match evt {
+ extension::Event::ExtensionsInstalledChanged => {
+ cx.update_global::<SchemaStore, _>(|schema_store, cx| {
+ schema_store.notify_schema_changed(ChangedSchemas::Settings, cx);
+ });
}
- cx.update_global::<SchemaStore, _>(|schema_store, cx| {
- schema_store.notify_schema_changed(&format!("{SCHEMA_URI_PREFIX}settings"), cx);
- schema_store
- .notify_schema_changed(&format!("{SCHEMA_URI_PREFIX}project_settings"), cx);
- });
+ extension::Event::ExtensionUninstalled(_)
+ | extension::Event::ExtensionInstalled(_)
+ | extension::Event::ConfigureExtensionRequested(_) => {}
})
.detach();
}
cx.observe_global::<dap::DapRegistry>(move |cx| {
cx.update_global::<SchemaStore, _>(|schema_store, cx| {
- schema_store.notify_schema_changed(&format!("{SCHEMA_URI_PREFIX}debug_tasks"), cx);
+ schema_store.notify_schema_changed(ChangedSchemas::DebugTasks, cx);
});
})
.detach();
@@ -98,18 +95,42 @@ pub struct SchemaStore {
impl gpui::Global for SchemaStore {}
+enum ChangedSchemas {
+ Settings,
+ DebugTasks,
+}
+
impl SchemaStore {
- fn notify_schema_changed(&mut self, uri: &str, cx: &mut App) {
- DYNAMIC_SCHEMA_CACHE.write().remove(uri);
+ fn notify_schema_changed(&mut self, changed_schemas: ChangedSchemas, cx: &mut App) {
+ let uris_to_invalidate = match changed_schemas {
+ ChangedSchemas::Settings => {
+ let settings_uri_prefix = &format!("{SCHEMA_URI_PREFIX}settings");
+ let project_settings_uri = &format!("{SCHEMA_URI_PREFIX}project_settings");
+ DYNAMIC_SCHEMA_CACHE
+ .write()
+ .extract_if(|uri, _| {
+ uri == project_settings_uri || uri.starts_with(settings_uri_prefix)
+ })
+ .map(|(url, _)| url)
+ .collect()
+ }
+ ChangedSchemas::DebugTasks => DYNAMIC_SCHEMA_CACHE
+ .write()
+ .remove_entry(&format!("{SCHEMA_URI_PREFIX}debug_tasks"))
+ .map_or_else(Vec::new, |(uri, _)| vec![uri]),
+ };
+
+ if uris_to_invalidate.is_empty() {
+ return;
+ }
- let uri = uri.to_string();
self.lsp_stores.retain(|lsp_store| {
let Some(lsp_store) = lsp_store.upgrade() else {
return false;
};
- project::lsp_store::json_language_server_ext::notify_schema_changed(
+ project::lsp_store::json_language_server_ext::notify_schemas_changed(
lsp_store,
- uri.clone(),
+ &uris_to_invalidate,
cx,
);
true
@@ -238,7 +259,8 @@ async fn resolve_dynamic_schema(
(adapter_name, LspSchemaKind::Settings)
} else {
anyhow::bail!(
- "Invalid LSP schema path: expected '{{adapter}}/initialization_options' or '{{adapter}}/settings', got '{}'",
+ "Invalid LSP schema path: \
+ Expected '{{adapter}}/initialization_options' or '{{adapter}}/settings', got '{}'",
lsp_path
);
};
@@ -484,7 +506,7 @@ pub fn all_schema_file_associations(
let file_name = normalized_action_name_to_file_name(normalized_name.clone());
serde_json::json!({
"fileMatch": [file_name],
- "url": format!("{}action/{normalized_name}", SCHEMA_URI_PREFIX)
+ "url": format!("{SCHEMA_URI_PREFIX}action/{normalized_name}")
})
}));
@@ -350,6 +350,44 @@ impl LspAdapter for ExtensionLspAdapter {
})
}
+ async fn initialization_options_schema(
+ self: Arc<Self>,
+ delegate: &Arc<dyn LspAdapterDelegate>,
+ _cached_binary: OwnedMutexGuard<Option<(bool, LanguageServerBinary)>>,
+ _cx: &mut AsyncApp,
+ ) -> Option<serde_json::Value> {
+ let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _;
+ let json_schema: Option<String> = self
+ .extension
+ .language_server_initialization_options_schema(
+ self.language_server_id.clone(),
+ delegate,
+ )
+ .await
+ .ok()
+ .flatten();
+ json_schema.and_then(|s| serde_json::from_str(&s).ok())
+ }
+
+ async fn settings_schema(
+ self: Arc<Self>,
+ delegate: &Arc<dyn LspAdapterDelegate>,
+ _cached_binary: OwnedMutexGuard<Option<(bool, LanguageServerBinary)>>,
+ _cx: &mut AsyncApp,
+ ) -> Option<serde_json::Value> {
+ let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _;
+ let json_schema: Option<String> = self
+ .extension
+ .language_server_workspace_configuration_schema(
+ self.language_server_id.clone(),
+ delegate,
+ )
+ .await
+ .ok()
+ .flatten();
+ json_schema.and_then(|s| serde_json::from_str(&s).ok())
+ }
+
async fn additional_initialization_options(
self: Arc<Self>,
target_language_server_id: LanguageServerName,
@@ -42,8 +42,8 @@ impl lsp::notification::Notification for SchemaContentsChanged {
type Params = String;
}
-pub fn notify_schema_changed(lsp_store: Entity<LspStore>, uri: String, cx: &App) {
- zlog::trace!(LOGGER => "Notifying schema changed for URI: {:?}", uri);
+pub fn notify_schemas_changed(lsp_store: Entity<LspStore>, uris: &[String], cx: &App) {
+ zlog::trace!(LOGGER => "Notifying schema changes for URIs: {:?}", uris);
let servers = lsp_store.read_with(cx, |lsp_store, _| {
let mut servers = Vec::new();
let Some(local) = lsp_store.as_local() else {
@@ -63,16 +63,18 @@ pub fn notify_schema_changed(lsp_store: Entity<LspStore>, uri: String, cx: &App)
servers
});
for server in servers {
- zlog::trace!(LOGGER => "Notifying server {NAME} (id {ID:?}) of schema change for URI: {uri:?}",
- NAME = server.name(),
- ID = server.server_id()
- );
- if let Err(error) = server.notify::<SchemaContentsChanged>(uri.clone()) {
- zlog::error!(
- LOGGER => "Failed to notify server {NAME} (id {ID:?}) of schema change for URI {uri:?}: {error:#}",
- NAME = server.name(),
- ID = server.server_id(),
+ for uri in uris {
+ zlog::trace!(LOGGER => "Notifying server {NAME} (id {ID:?}) of schema change for URI: {uri:?}",
+ NAME = server.name(),
+ ID = server.server_id()
);
+ if let Err(error) = server.notify::<SchemaContentsChanged>(uri.clone()) {
+ zlog::error!(
+ LOGGER => "Failed to notify server {NAME} (id {ID:?}) of schema change for URI {uri:?}: {error:#}",
+ NAME = server.name(),
+ ID = server.server_id(),
+ );
+ }
}
}
}