Detailed changes
@@ -4645,7 +4645,6 @@ dependencies = [
"collections",
"db",
"editor",
- "extension",
"extension_host",
"feature_flags",
"fs",
@@ -10308,6 +10307,7 @@ dependencies = [
"clock",
"collections",
"env_logger 0.11.6",
+ "extension",
"fancy-regex 0.14.0",
"fs",
"futures 0.3.31",
@@ -1,4 +1,4 @@
-use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Global, ReadGlobal as _};
+use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Global};
pub fn init(cx: &mut App) {
let extension_events = cx.new(ExtensionEvents::new);
@@ -14,8 +14,10 @@ pub struct ExtensionEvents;
impl ExtensionEvents {
/// Returns the global [`ExtensionEvents`].
- pub fn global(cx: &App) -> Entity<Self> {
- GlobalExtensionEvents::global(cx).0.clone()
+ pub fn try_global(cx: &App) -> Option<Entity<Self>> {
+ return cx
+ .try_global::<GlobalExtensionEvents>()
+ .map(|g| g.0.clone());
}
fn new(_cx: &mut Context<Self>) -> Self {
@@ -29,7 +31,7 @@ impl ExtensionEvents {
#[derive(Clone)]
pub enum Event {
- ExtensionsUpdated,
+ ExtensionsInstalledChanged,
}
impl EventEmitter<Event> for ExtensionEvents {}
@@ -127,6 +127,7 @@ pub enum ExtensionOperation {
#[derive(Clone)]
pub enum Event {
+ ExtensionsUpdated,
StartedReloading,
ExtensionInstalled(Arc<str>),
ExtensionFailedToLoad(Arc<str>),
@@ -1213,9 +1214,7 @@ impl ExtensionStore {
self.extension_index = new_index;
cx.notify();
- ExtensionEvents::global(cx).update(cx, |this, cx| {
- this.emit(extension::Event::ExtensionsUpdated, cx)
- });
+ cx.emit(Event::ExtensionsUpdated);
cx.spawn(|this, mut cx| async move {
cx.background_spawn({
@@ -1317,6 +1316,12 @@ impl ExtensionStore {
this.proxy.set_extensions_loaded();
this.proxy.reload_current_theme(cx);
this.proxy.reload_current_icon_theme(cx);
+
+ if let Some(events) = ExtensionEvents::try_global(cx) {
+ events.update(cx, |this, cx| {
+ this.emit(extension::Event::ExtensionsInstalledChanged, cx)
+ });
+ }
})
.ok();
})
@@ -17,7 +17,6 @@ client.workspace = true
collections.workspace = true
db.workspace = true
editor.workspace = true
-extension.workspace = true
extension_host.workspace = true
feature_flags.workspace = true
fs.workspace = true
@@ -9,7 +9,6 @@ use std::{ops::Range, sync::Arc};
use client::{ExtensionMetadata, ExtensionProvides};
use collections::{BTreeMap, BTreeSet};
use editor::{Editor, EditorElement, EditorStyle};
-use extension::ExtensionEvents;
use extension_host::{ExtensionManifest, ExtensionOperation, ExtensionStore};
use feature_flags::FeatureFlagAppExt as _;
use fuzzy::{match_strings, StringMatchCandidate};
@@ -213,7 +212,7 @@ pub struct ExtensionsPage {
query_editor: Entity<Editor>,
query_contains_error: bool,
provides_filter: Option<ExtensionProvides>,
- _subscriptions: Vec<gpui::Subscription>,
+ _subscriptions: [gpui::Subscription; 2],
extension_fetch_task: Option<Task<()>>,
upsells: BTreeSet<Feature>,
}
@@ -227,12 +226,15 @@ impl ExtensionsPage {
cx.new(|cx| {
let store = ExtensionStore::global(cx);
let workspace_handle = workspace.weak_handle();
- let subscriptions = vec![
+ let subscriptions = [
cx.observe(&store, |_: &mut Self, _, cx| cx.notify()),
cx.subscribe_in(
&store,
window,
move |this, _, event, window, cx| match event {
+ extension_host::Event::ExtensionsUpdated => {
+ this.fetch_extensions_debounced(cx)
+ }
extension_host::Event::ExtensionInstalled(extension_id) => this
.on_extension_installed(
workspace_handle.clone(),
@@ -243,15 +245,6 @@ impl ExtensionsPage {
_ => {}
},
),
- cx.subscribe_in(
- &ExtensionEvents::global(cx),
- window,
- move |this, _, event, _window, cx| match event {
- extension::Event::ExtensionsUpdated => {
- this.fetch_extensions_debounced(cx);
- }
- },
- ),
];
let query_editor = cx.new(|cx| {
@@ -555,6 +555,23 @@ pub trait LspAdapter: 'static + Send + Sync {
// By default all language servers are rooted at the root of the worktree.
Some(Arc::from("".as_ref()))
}
+
+ /// Method only implemented by the default JSON language server adapter.
+ /// Used to provide dynamic reloading of the JSON schemas used to
+ /// provide autocompletion and diagnostics in Zed setting and keybind
+ /// files
+ fn is_primary_zed_json_schema_adapter(&self) -> bool {
+ false
+ }
+
+ /// Method only implemented by the default JSON language server adapter.
+ /// Used to clear the cache of JSON schemas that are used to provide
+ /// autocompletion and diagnostics in Zed settings and keybinds files.
+ /// Should not be called unless the callee is sure that
+ /// `Self::is_primary_zed_json_schema_adapter` returns `true`
+ async fn clear_zed_json_schema_cache(&self) {
+ unreachable!("Not implemented for this adapter. This method should only be called on the default JSON language server adapter");
+ }
}
async fn try_fetch_server_binary<L: LspAdapter + 'static + Send + Sync + ?Sized>(
@@ -15,6 +15,7 @@ use settings::{KeymapFile, SettingsJsonSchemaParams, SettingsStore};
use smol::{
fs::{self},
io::BufReader,
+ lock::RwLock,
};
use std::{
any::Any,
@@ -22,7 +23,7 @@ use std::{
ffi::OsString,
path::{Path, PathBuf},
str::FromStr,
- sync::{Arc, OnceLock},
+ sync::Arc,
};
use task::{TaskTemplate, TaskTemplates, VariableName};
use util::{fs::remove_matching, maybe, merge_json_value_into, ResultExt};
@@ -60,7 +61,7 @@ fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
pub struct JsonLspAdapter {
node: NodeRuntime,
languages: Arc<LanguageRegistry>,
- workspace_config: OnceLock<Value>,
+ workspace_config: RwLock<Option<Value>>,
}
impl JsonLspAdapter {
@@ -141,6 +142,20 @@ impl JsonLspAdapter {
}
})
}
+
+ async fn get_or_init_workspace_config(&self, cx: &mut AsyncApp) -> Result<Value> {
+ {
+ let reader = self.workspace_config.read().await;
+ if let Some(config) = reader.as_ref() {
+ return Ok(config.clone());
+ }
+ }
+ let mut writer = self.workspace_config.write().await;
+ let config =
+ cx.update(|cx| Self::get_workspace_config(self.languages.language_names(), cx))?;
+ writer.replace(config.clone());
+ return Ok(config);
+ }
}
#[async_trait(?Send)]
@@ -251,11 +266,7 @@ impl LspAdapter for JsonLspAdapter {
_: Arc<dyn LanguageToolchainStore>,
cx: &mut AsyncApp,
) -> Result<Value> {
- let mut config = cx.update(|cx| {
- self.workspace_config
- .get_or_init(|| Self::get_workspace_config(self.languages.language_names(), cx))
- .clone()
- })?;
+ let mut config = self.get_or_init_workspace_config(cx).await?;
let project_options = cx.update(|cx| {
language_server_settings(delegate.as_ref(), &self.name(), cx)
@@ -277,6 +288,14 @@ impl LspAdapter for JsonLspAdapter {
.into_iter()
.collect()
}
+
+ fn is_primary_zed_json_schema_adapter(&self) -> bool {
+ true
+ }
+
+ async fn clear_zed_json_schema_cache(&self) {
+ self.workspace_config.write().await.take();
+ }
}
async fn get_cached_server_binary(
@@ -33,6 +33,7 @@ buffer_diff.workspace = true
client.workspace = true
clock.workspace = true
collections.workspace = true
+extension.workspace = true
fancy-regex.workspace = true
fs.workspace = true
futures.workspace = true
@@ -3017,6 +3017,15 @@ impl LspStore {
.detach();
cx.subscribe(&toolchain_store, Self::on_toolchain_store_event)
.detach();
+ if let Some(extension_events) = extension::ExtensionEvents::try_global(cx).as_ref() {
+ cx.subscribe(
+ extension_events,
+ Self::reload_zed_json_schemas_on_extensions_changed,
+ )
+ .detach();
+ } else {
+ log::info!("No extension events global found. Skipping JSON schema auto-reload setup");
+ }
cx.observe_global::<SettingsStore>(Self::on_settings_changed)
.detach();
@@ -3277,6 +3286,109 @@ impl LspStore {
Ok(())
}
+ pub fn reload_zed_json_schemas_on_extensions_changed(
+ &mut self,
+ _: Entity<extension::ExtensionEvents>,
+ evt: &extension::Event,
+ cx: &mut Context<Self>,
+ ) {
+ #[expect(
+ irrefutable_let_patterns,
+ reason = "Make sure to handle new event types in extension properly"
+ )]
+ let extension::Event::ExtensionsInstalledChanged = evt
+ else {
+ return;
+ };
+ if self.as_local().is_none() {
+ return;
+ }
+ cx.spawn(async move |this, mut cx| {
+ let weak_ref = this.clone();
+ let servers = this
+ .update(&mut cx, |this, cx| {
+ let local = this.as_local()?;
+
+ let mut servers = Vec::new();
+ for ((worktree_id, _), server_ids) in &local.language_server_ids {
+ for server_id in server_ids {
+ let Some(states) = local.language_servers.get(server_id) else {
+ continue;
+ };
+ let (json_adapter, json_server) = match states {
+ LanguageServerState::Running {
+ adapter, server, ..
+ } if adapter.adapter.is_primary_zed_json_schema_adapter() => {
+ (adapter.adapter.clone(), server.clone())
+ }
+ _ => continue,
+ };
+
+ let Some(worktree) = this
+ .worktree_store
+ .read(cx)
+ .worktree_for_id(*worktree_id, cx)
+ else {
+ continue;
+ };
+ let json_delegate: Arc<dyn LspAdapterDelegate> =
+ LocalLspAdapterDelegate::new(
+ local.languages.clone(),
+ &local.environment,
+ weak_ref.clone(),
+ &worktree,
+ local.http_client.clone(),
+ local.fs.clone(),
+ cx,
+ );
+
+ servers.push((json_adapter, json_server, json_delegate));
+ }
+ }
+ return Some(servers);
+ })
+ .ok()
+ .flatten();
+
+ let Some(servers) = servers else {
+ return;
+ };
+
+ let Ok(Some((fs, toolchain_store))) = this.read_with(&cx, |this, cx| {
+ let local = this.as_local()?;
+ let toolchain_store = this.toolchain_store(cx);
+ return Some((local.fs.clone(), toolchain_store));
+ }) else {
+ return;
+ };
+ for (adapter, server, delegate) in servers {
+ adapter.clear_zed_json_schema_cache().await;
+
+ let Some(json_workspace_config) = adapter
+ .workspace_configuration(
+ fs.as_ref(),
+ &delegate,
+ toolchain_store.clone(),
+ &mut cx,
+ )
+ .await
+ .context("generate new workspace configuration for JSON language server while trying to refresh JSON Schemas")
+ .ok()
+ else {
+ continue;
+ };
+ server
+ .notify::<lsp::notification::DidChangeConfiguration>(
+ &lsp::DidChangeConfigurationParams {
+ settings: json_workspace_config,
+ },
+ )
+ .ok();
+ }
+ })
+ .detach();
+ }
+
pub(crate) fn register_buffer_with_language_servers(
&mut self,
buffer: &Entity<Buffer>,
@@ -95,6 +95,10 @@ use util::{
ResultExt as _,
};
use worktree::{CreatedEntry, Snapshot, Traversal};
+pub use worktree::{
+ Entry, EntryKind, File, LocalWorktree, PathChange, ProjectEntryId, UpdatedEntriesSet,
+ UpdatedGitRepositoriesSet, Worktree, WorktreeId, WorktreeSettings, FS_WATCH_LATENCY,
+};
use worktree_store::{WorktreeStore, WorktreeStoreEvent};
pub use fs::*;
@@ -104,10 +108,6 @@ pub use prettier::FORMAT_SUFFIX as TEST_PRETTIER_FORMAT_SUFFIX;
pub use task_inventory::{
BasicContextProvider, ContextProviderWithTasks, Inventory, TaskContexts, TaskSourceKind,
};
-pub use worktree::{
- Entry, EntryKind, File, LocalWorktree, PathChange, ProjectEntryId, UpdatedEntriesSet,
- UpdatedGitRepositoriesSet, Worktree, WorktreeId, WorktreeSettings, FS_WATCH_LATENCY,
-};
pub use buffer_store::ProjectTransaction;
pub use lsp_store::{