From c6603e4fba9c917f354c1ce40c1f6eb1d1818971 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 10 Jul 2025 22:25:10 +0300 Subject: [PATCH] Stop extensions' servers and message loops before removing their files (#34208) Fixes an issue that caused Windows to fail when removing extension's directories, as Zed had never stop any related processes. Now: * Zed shuts down and waits until the end when the language servers are shut down * Adds `impl Drop for WasmExtension` where does `self.tx.close_channel();` to stop a receiver loop that holds the "lock" on the extension's work dir. The extension was dropped, but the channel was not closed for some reason. * Does more unregistration to ensure `Arc` with the `tx` does not leak further * Tidies up the related errors which had never reported a problematic path before Release Notes: - N/A --------- Co-authored-by: Smit Barmase Co-authored-by: Smit --- Cargo.lock | 1 + .../src/extension_slash_command.rs | 5 ++ crates/eval/src/eval.rs | 7 +- crates/extension/src/extension_host_proxy.rs | 30 +++++++- crates/extension_host/src/extension_host.rs | 76 +++++++++++++------ .../src/extension_store_test.rs | 10 ++- crates/extension_host/src/headless_host.rs | 27 +++++-- crates/extension_host/src/wasm_host.rs | 9 ++- .../src/extension_indexed_docs_provider.rs | 5 ++ crates/indexed_docs/src/registry.rs | 4 + crates/language_extension/Cargo.toml | 1 + .../src/extension_lsp_adapter.rs | 59 ++++++++++++-- .../src/language_extension.rs | 16 +++- crates/project/src/lsp_store.rs | 67 +++++++++------- crates/project/src/project.rs | 8 +- crates/remote_server/src/headless_project.rs | 6 +- crates/workspace/src/workspace.rs | 4 + crates/zed/src/main.rs | 22 +++++- 18 files changed, 273 insertions(+), 84 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6d15e873e1..111ed89b12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8978,6 +8978,7 @@ dependencies = [ "gpui", "language", "lsp", + "project", "serde", "serde_json", "util", diff --git a/crates/assistant_slash_command/src/extension_slash_command.rs b/crates/assistant_slash_command/src/extension_slash_command.rs index 6cc1f73c47..74c46ffb5f 100644 --- a/crates/assistant_slash_command/src/extension_slash_command.rs +++ b/crates/assistant_slash_command/src/extension_slash_command.rs @@ -34,6 +34,11 @@ impl ExtensionSlashCommandProxy for SlashCommandRegistryProxy { self.slash_command_registry .register_command(ExtensionSlashCommand::new(extension, command), false) } + + fn unregister_slash_command(&self, command_name: Arc) { + self.slash_command_registry + .unregister_command_by_name(&command_name) + } } /// An adapter that allows an [`LspAdapterDelegate`] to be used as a [`WorktreeDelegate`]. diff --git a/crates/eval/src/eval.rs b/crates/eval/src/eval.rs index 39121377bb..a02b4a7f0b 100644 --- a/crates/eval/src/eval.rs +++ b/crates/eval/src/eval.rs @@ -8,6 +8,7 @@ mod tool_metrics; use assertions::{AssertionsReport, display_error_row}; use instance::{ExampleInstance, JudgeOutput, RunOutput, run_git}; +use language_extension::LspAccess; pub(crate) use tool_metrics::*; use ::fs::RealFs; @@ -415,7 +416,11 @@ pub fn init(cx: &mut App) -> Arc { language::init(cx); debug_adapter_extension::init(extension_host_proxy.clone(), cx); - language_extension::init(extension_host_proxy.clone(), languages.clone()); + language_extension::init( + LspAccess::Noop, + extension_host_proxy.clone(), + languages.clone(), + ); language_model::init(client.clone(), cx); language_models::init(user_store.clone(), client.clone(), cx); languages::init(languages.clone(), node_runtime.clone(), cx); diff --git a/crates/extension/src/extension_host_proxy.rs b/crates/extension/src/extension_host_proxy.rs index f6ae9090fc..917739759f 100644 --- a/crates/extension/src/extension_host_proxy.rs +++ b/crates/extension/src/extension_host_proxy.rs @@ -286,7 +286,8 @@ pub trait ExtensionLanguageServerProxy: Send + Sync + 'static { &self, language: &LanguageName, language_server_id: &LanguageServerName, - ); + cx: &mut App, + ) -> Task>; fn update_language_server_status( &self, @@ -313,12 +314,13 @@ impl ExtensionLanguageServerProxy for ExtensionHostProxy { &self, language: &LanguageName, language_server_id: &LanguageServerName, - ) { + cx: &mut App, + ) -> Task> { let Some(proxy) = self.language_server_proxy.read().clone() else { - return; + return Task::ready(Ok(())); }; - proxy.remove_language_server(language, language_server_id) + proxy.remove_language_server(language, language_server_id, cx) } fn update_language_server_status( @@ -350,6 +352,8 @@ impl ExtensionSnippetProxy for ExtensionHostProxy { pub trait ExtensionSlashCommandProxy: Send + Sync + 'static { fn register_slash_command(&self, extension: Arc, command: SlashCommand); + + fn unregister_slash_command(&self, command_name: Arc); } impl ExtensionSlashCommandProxy for ExtensionHostProxy { @@ -360,6 +364,14 @@ impl ExtensionSlashCommandProxy for ExtensionHostProxy { proxy.register_slash_command(extension, command) } + + fn unregister_slash_command(&self, command_name: Arc) { + let Some(proxy) = self.slash_command_proxy.read().clone() else { + return; + }; + + proxy.unregister_slash_command(command_name) + } } pub trait ExtensionContextServerProxy: Send + Sync + 'static { @@ -398,6 +410,8 @@ impl ExtensionContextServerProxy for ExtensionHostProxy { pub trait ExtensionIndexedDocsProviderProxy: Send + Sync + 'static { fn register_indexed_docs_provider(&self, extension: Arc, provider_id: Arc); + + fn unregister_indexed_docs_provider(&self, provider_id: Arc); } impl ExtensionIndexedDocsProviderProxy for ExtensionHostProxy { @@ -408,6 +422,14 @@ impl ExtensionIndexedDocsProviderProxy for ExtensionHostProxy { proxy.register_indexed_docs_provider(extension, provider_id) } + + fn unregister_indexed_docs_provider(&self, provider_id: Arc) { + let Some(proxy) = self.indexed_docs_provider_proxy.read().clone() else { + return; + }; + + proxy.unregister_indexed_docs_provider(provider_id) + } } pub trait ExtensionDebugAdapterProviderProxy: Send + Sync + 'static { diff --git a/crates/extension_host/src/extension_host.rs b/crates/extension_host/src/extension_host.rs index dd753199a8..075c68d51a 100644 --- a/crates/extension_host/src/extension_host.rs +++ b/crates/extension_host/src/extension_host.rs @@ -20,6 +20,7 @@ use extension::{ ExtensionSnippetProxy, ExtensionThemeProxy, }; use fs::{Fs, RemoveOptions}; +use futures::future::join_all; use futures::{ AsyncReadExt as _, Future, FutureExt as _, StreamExt as _, channel::{ @@ -860,8 +861,8 @@ impl ExtensionStore { btree_map::Entry::Vacant(e) => e.insert(ExtensionOperation::Remove), }; - cx.spawn(async move |this, cx| { - let _finish = cx.on_drop(&this, { + cx.spawn(async move |extension_store, cx| { + let _finish = cx.on_drop(&extension_store, { let extension_id = extension_id.clone(); move |this, cx| { this.outstanding_operations.remove(extension_id.as_ref()); @@ -876,22 +877,39 @@ impl ExtensionStore { ignore_if_not_exists: true, }, ) - .await?; + .await + .with_context(|| format!("Removing extension dir {extension_dir:?}"))?; - // todo(windows) - // Stop the server here. - this.update(cx, |this, cx| this.reload(None, cx))?.await; + extension_store + .update(cx, |extension_store, cx| extension_store.reload(None, cx))? + .await; - fs.remove_dir( - &work_dir, - RemoveOptions { - recursive: true, - ignore_if_not_exists: true, - }, - ) - .await?; + // There's a race between wasm extension fully stopping and the directory removal. + // On Windows, it's impossible to remove a directory that has a process running in it. + for i in 0..3 { + cx.background_executor() + .timer(Duration::from_millis(i * 100)) + .await; + let removal_result = fs + .remove_dir( + &work_dir, + RemoveOptions { + recursive: true, + ignore_if_not_exists: true, + }, + ) + .await; + match removal_result { + Ok(()) => break, + Err(e) => { + if i == 2 { + log::error!("Failed to remove extension work dir {work_dir:?} : {e}"); + } + } + } + } - this.update(cx, |_, cx| { + extension_store.update(cx, |_, cx| { cx.emit(Event::ExtensionUninstalled(extension_id.clone())); if let Some(events) = ExtensionEvents::try_global(cx) { if let Some(manifest) = extension_manifest { @@ -1143,27 +1161,38 @@ impl ExtensionStore { }) .collect::>(); let mut grammars_to_remove = Vec::new(); + let mut server_removal_tasks = Vec::with_capacity(extensions_to_unload.len()); for extension_id in &extensions_to_unload { let Some(extension) = old_index.extensions.get(extension_id) else { continue; }; grammars_to_remove.extend(extension.manifest.grammars.keys().cloned()); - for (language_server_name, config) in extension.manifest.language_servers.iter() { + for (language_server_name, config) in &extension.manifest.language_servers { for language in config.languages() { - self.proxy - .remove_language_server(&language, language_server_name); + server_removal_tasks.push(self.proxy.remove_language_server( + &language, + language_server_name, + cx, + )); } } - for (server_id, _) in extension.manifest.context_servers.iter() { + for (server_id, _) in &extension.manifest.context_servers { self.proxy.unregister_context_server(server_id.clone(), cx); } - for (adapter, _) in extension.manifest.debug_adapters.iter() { + for (adapter, _) in &extension.manifest.debug_adapters { self.proxy.unregister_debug_adapter(adapter.clone()); } - for (locator, _) in extension.manifest.debug_locators.iter() { + for (locator, _) in &extension.manifest.debug_locators { self.proxy.unregister_debug_locator(locator.clone()); } + for (command_name, _) in &extension.manifest.slash_commands { + self.proxy.unregister_slash_command(command_name.clone()); + } + for (provider_id, _) in &extension.manifest.indexed_docs_providers { + self.proxy + .unregister_indexed_docs_provider(provider_id.clone()); + } } self.wasm_extensions @@ -1268,14 +1297,15 @@ impl ExtensionStore { cx.background_spawn({ let fs = fs.clone(); async move { - for theme_path in themes_to_add.into_iter() { + let _ = join_all(server_removal_tasks).await; + for theme_path in themes_to_add { proxy .load_user_theme(theme_path, fs.clone()) .await .log_err(); } - for (icon_theme_path, icons_root_path) in icon_themes_to_add.into_iter() { + for (icon_theme_path, icons_root_path) in icon_themes_to_add { proxy .load_icon_theme(icon_theme_path, icons_root_path, fs.clone()) .await diff --git a/crates/extension_host/src/extension_store_test.rs b/crates/extension_host/src/extension_store_test.rs index cfe97f1675..891ab91852 100644 --- a/crates/extension_host/src/extension_store_test.rs +++ b/crates/extension_host/src/extension_store_test.rs @@ -11,6 +11,7 @@ use futures::{AsyncReadExt, StreamExt, io::BufReader}; use gpui::{AppContext as _, SemanticVersion, TestAppContext}; use http_client::{FakeHttpClient, Response}; use language::{BinaryStatus, LanguageMatcher, LanguageRegistry}; +use language_extension::LspAccess; use lsp::LanguageServerName; use node_runtime::NodeRuntime; use parking_lot::Mutex; @@ -271,7 +272,7 @@ async fn test_extension_store(cx: &mut TestAppContext) { let theme_registry = Arc::new(ThemeRegistry::new(Box::new(()))); theme_extension::init(proxy.clone(), theme_registry.clone(), cx.executor()); let language_registry = Arc::new(LanguageRegistry::test(cx.executor())); - language_extension::init(proxy.clone(), language_registry.clone()); + language_extension::init(LspAccess::Noop, proxy.clone(), language_registry.clone()); let node_runtime = NodeRuntime::unavailable(); let store = cx.new(|cx| { @@ -554,7 +555,11 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) { let theme_registry = Arc::new(ThemeRegistry::new(Box::new(()))); theme_extension::init(proxy.clone(), theme_registry.clone(), cx.executor()); let language_registry = project.read_with(cx, |project, _cx| project.languages().clone()); - language_extension::init(proxy.clone(), language_registry.clone()); + language_extension::init( + LspAccess::ViaLspStore(project.update(cx, |project, _| project.lsp_store())), + proxy.clone(), + language_registry.clone(), + ); let node_runtime = NodeRuntime::unavailable(); let mut status_updates = language_registry.language_server_binary_statuses(); @@ -815,7 +820,6 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) { extension_store .update(cx, |store, cx| store.reload(Some("gleam".into()), cx)) .await; - cx.executor().run_until_parked(); project.update(cx, |project, cx| { project.restart_language_servers_for_buffers(vec![buffer.clone()], HashSet::default(), cx) diff --git a/crates/extension_host/src/headless_host.rs b/crates/extension_host/src/headless_host.rs index 1dfcf56e5d..dbc9bbfe13 100644 --- a/crates/extension_host/src/headless_host.rs +++ b/crates/extension_host/src/headless_host.rs @@ -11,6 +11,7 @@ use extension::{ ExtensionLanguageServerProxy, ExtensionManifest, }; use fs::{Fs, RemoveOptions, RenameOptions}; +use futures::future::join_all; use gpui::{App, AppContext as _, AsyncApp, Context, Entity, Task, WeakEntity}; use http_client::HttpClient; use language::{LanguageConfig, LanguageName, LanguageQueries, LoadedLanguage}; @@ -230,18 +231,27 @@ impl HeadlessExtensionStore { .unwrap_or_default(); self.proxy.remove_languages(&languages_to_remove, &[]); - for (language_server_name, language) in self + let servers_to_remove = self .loaded_language_servers .remove(extension_id) - .unwrap_or_default() - { - self.proxy - .remove_language_server(&language, &language_server_name); - } - + .unwrap_or_default(); + let proxy = self.proxy.clone(); let path = self.extension_dir.join(&extension_id.to_string()); let fs = self.fs.clone(); - cx.spawn(async move |_, _| { + cx.spawn(async move |_, cx| { + let mut removal_tasks = Vec::with_capacity(servers_to_remove.len()); + cx.update(|cx| { + for (language_server_name, language) in servers_to_remove { + removal_tasks.push(proxy.remove_language_server( + &language, + &language_server_name, + cx, + )); + } + }) + .ok(); + let _ = join_all(removal_tasks).await; + fs.remove_dir( &path, RemoveOptions { @@ -250,6 +260,7 @@ impl HeadlessExtensionStore { }, ) .await + .with_context(|| format!("Removing directory {path:?}")) }) } diff --git a/crates/extension_host/src/wasm_host.rs b/crates/extension_host/src/wasm_host.rs index 01931dfd6d..3971fa4263 100644 --- a/crates/extension_host/src/wasm_host.rs +++ b/crates/extension_host/src/wasm_host.rs @@ -54,7 +54,7 @@ pub struct WasmHost { main_thread_message_tx: mpsc::UnboundedSender, } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct WasmExtension { tx: UnboundedSender, pub manifest: Arc, @@ -63,6 +63,12 @@ pub struct WasmExtension { pub zed_api_version: SemanticVersion, } +impl Drop for WasmExtension { + fn drop(&mut self) { + self.tx.close_channel(); + } +} + #[async_trait] impl extension::Extension for WasmExtension { fn manifest(&self) -> Arc { @@ -742,7 +748,6 @@ impl WasmExtension { { let (return_tx, return_rx) = oneshot::channel(); self.tx - .clone() .unbounded_send(Box::new(move |extension, store| { async { let result = f(extension, store).await; diff --git a/crates/indexed_docs/src/extension_indexed_docs_provider.rs b/crates/indexed_docs/src/extension_indexed_docs_provider.rs index 1cfcd88122..c77ea4066d 100644 --- a/crates/indexed_docs/src/extension_indexed_docs_provider.rs +++ b/crates/indexed_docs/src/extension_indexed_docs_provider.rs @@ -29,6 +29,11 @@ impl ExtensionIndexedDocsProviderProxy for IndexedDocsRegistryProxy { ProviderId(provider_id), ))); } + + fn unregister_indexed_docs_provider(&self, provider_id: Arc) { + self.indexed_docs_registry + .unregister_provider(&ProviderId(provider_id)); + } } pub struct ExtensionIndexedDocsProvider { diff --git a/crates/indexed_docs/src/registry.rs b/crates/indexed_docs/src/registry.rs index 10389367cf..6757cd9c1a 100644 --- a/crates/indexed_docs/src/registry.rs +++ b/crates/indexed_docs/src/registry.rs @@ -52,6 +52,10 @@ impl IndexedDocsRegistry { ); } + pub fn unregister_provider(&self, provider_id: &ProviderId) { + self.stores_by_provider.write().remove(provider_id); + } + pub fn get_provider_store(&self, provider_id: ProviderId) -> Option> { self.stores_by_provider.read().get(&provider_id).cloned() } diff --git a/crates/language_extension/Cargo.toml b/crates/language_extension/Cargo.toml index 31b92d35cf..cc73b1f923 100644 --- a/crates/language_extension/Cargo.toml +++ b/crates/language_extension/Cargo.toml @@ -21,6 +21,7 @@ fs.workspace = true gpui.workspace = true language.workspace = true lsp.workspace = true +project.workspace = true serde.workspace = true serde_json.workspace = true util.workspace = true diff --git a/crates/language_extension/src/extension_lsp_adapter.rs b/crates/language_extension/src/extension_lsp_adapter.rs index d2eabf0a3e..58fbe6cda2 100644 --- a/crates/language_extension/src/extension_lsp_adapter.rs +++ b/crates/language_extension/src/extension_lsp_adapter.rs @@ -6,21 +6,24 @@ use std::sync::Arc; use anyhow::{Context as _, Result}; use async_trait::async_trait; -use collections::HashMap; +use collections::{HashMap, HashSet}; use extension::{Extension, ExtensionLanguageServerProxy, WorktreeDelegate}; use fs::Fs; -use futures::{Future, FutureExt}; -use gpui::AsyncApp; +use futures::{Future, FutureExt, future::join_all}; +use gpui::{App, AppContext, AsyncApp, Task}; use language::{ BinaryStatus, CodeLabel, HighlightId, Language, LanguageName, LanguageToolchainStore, LspAdapter, LspAdapterDelegate, }; -use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerName}; +use lsp::{ + CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerName, + LanguageServerSelector, +}; use serde::Serialize; use serde_json::Value; use util::{ResultExt, fs::make_file_executable, maybe}; -use crate::LanguageServerRegistryProxy; +use crate::{LanguageServerRegistryProxy, LspAccess}; /// An adapter that allows an [`LspAdapterDelegate`] to be used as a [`WorktreeDelegate`]. struct WorktreeDelegateAdapter(pub Arc); @@ -71,10 +74,50 @@ impl ExtensionLanguageServerProxy for LanguageServerRegistryProxy { fn remove_language_server( &self, language: &LanguageName, - language_server_id: &LanguageServerName, - ) { + language_server_name: &LanguageServerName, + cx: &mut App, + ) -> Task> { self.language_registry - .remove_lsp_adapter(language, language_server_id); + .remove_lsp_adapter(language, language_server_name); + + let mut tasks = Vec::new(); + match &self.lsp_access { + LspAccess::ViaLspStore(lsp_store) => lsp_store.update(cx, |lsp_store, cx| { + let stop_task = lsp_store.stop_language_servers_for_buffers( + Vec::new(), + HashSet::from_iter([LanguageServerSelector::Name( + language_server_name.clone(), + )]), + cx, + ); + tasks.push(stop_task); + }), + LspAccess::ViaWorkspaces(lsp_store_provider) => { + if let Ok(lsp_stores) = lsp_store_provider(cx) { + for lsp_store in lsp_stores { + lsp_store.update(cx, |lsp_store, cx| { + let stop_task = lsp_store.stop_language_servers_for_buffers( + Vec::new(), + HashSet::from_iter([LanguageServerSelector::Name( + language_server_name.clone(), + )]), + cx, + ); + tasks.push(stop_task); + }); + } + } + } + LspAccess::Noop => {} + } + + cx.background_spawn(async move { + let results = join_all(tasks).await; + for result in results { + result?; + } + Ok(()) + }) } fn update_language_server_status( diff --git a/crates/language_extension/src/language_extension.rs b/crates/language_extension/src/language_extension.rs index 59951c87e4..1915eae2d1 100644 --- a/crates/language_extension/src/language_extension.rs +++ b/crates/language_extension/src/language_extension.rs @@ -5,13 +5,26 @@ use std::sync::Arc; use anyhow::Result; use extension::{ExtensionGrammarProxy, ExtensionHostProxy, ExtensionLanguageProxy}; +use gpui::{App, Entity}; use language::{LanguageMatcher, LanguageName, LanguageRegistry, LoadedLanguage}; +use project::LspStore; + +#[derive(Clone)] +pub enum LspAccess { + ViaLspStore(Entity), + ViaWorkspaces(Arc Result>> + Send + Sync + 'static>), + Noop, +} pub fn init( + lsp_access: LspAccess, extension_host_proxy: Arc, language_registry: Arc, ) { - let language_server_registry_proxy = LanguageServerRegistryProxy { language_registry }; + let language_server_registry_proxy = LanguageServerRegistryProxy { + language_registry, + lsp_access, + }; extension_host_proxy.register_grammar_proxy(language_server_registry_proxy.clone()); extension_host_proxy.register_language_proxy(language_server_registry_proxy.clone()); extension_host_proxy.register_language_server_proxy(language_server_registry_proxy); @@ -20,6 +33,7 @@ pub fn init( #[derive(Clone)] struct LanguageServerRegistryProxy { language_registry: Arc, + lsp_access: LspAccess, } impl ExtensionGrammarProxy for LanguageServerRegistryProxy { diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 3e8917d91b..e7afac0689 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -9712,29 +9712,31 @@ impl LspStore { } else { let buffers = lsp_store.buffer_ids_to_buffers(envelope.payload.buffer_ids.into_iter(), cx); - lsp_store.stop_language_servers_for_buffers( - buffers, - envelope - .payload - .also_servers - .into_iter() - .filter_map(|selector| { - Some(match selector.selector? { - proto::language_server_selector::Selector::ServerId(server_id) => { - LanguageServerSelector::Id(LanguageServerId::from_proto( + lsp_store + .stop_language_servers_for_buffers( + buffers, + envelope + .payload + .also_servers + .into_iter() + .filter_map(|selector| { + Some(match selector.selector? { + proto::language_server_selector::Selector::ServerId( server_id, - )) - } - proto::language_server_selector::Selector::Name(name) => { - LanguageServerSelector::Name(LanguageServerName( - SharedString::from(name), - )) - } + ) => LanguageServerSelector::Id(LanguageServerId::from_proto( + server_id, + )), + proto::language_server_selector::Selector::Name(name) => { + LanguageServerSelector::Name(LanguageServerName( + SharedString::from(name), + )) + } + }) }) - }) - .collect(), - cx, - ); + .collect(), + cx, + ) + .detach_and_log_err(cx); } })?; @@ -10290,9 +10292,9 @@ impl LspStore { pub fn stop_language_servers_for_buffers( &mut self, buffers: Vec>, - also_restart_servers: HashSet, + also_stop_servers: HashSet, cx: &mut Context, - ) { + ) -> Task> { if let Some((client, project_id)) = self.upstream_client() { let request = client.request(proto::StopLanguageServers { project_id, @@ -10300,7 +10302,7 @@ impl LspStore { .into_iter() .map(|b| b.read(cx).remote_id().to_proto()) .collect(), - also_servers: also_restart_servers + also_servers: also_stop_servers .into_iter() .map(|selector| { let selector = match selector { @@ -10322,24 +10324,31 @@ impl LspStore { .collect(), all: false, }); - cx.background_spawn(request).detach_and_log_err(cx); + cx.background_spawn(async move { + let _ = request.await?; + Ok(()) + }) } else { - self.stop_local_language_servers_for_buffers(&buffers, also_restart_servers, cx) - .detach(); + let task = + self.stop_local_language_servers_for_buffers(&buffers, also_stop_servers, cx); + cx.background_spawn(async move { + task.await; + Ok(()) + }) } } fn stop_local_language_servers_for_buffers( &mut self, buffers: &[Entity], - also_restart_servers: HashSet, + also_stop_servers: HashSet, cx: &mut Context, ) -> Task<()> { let Some(local) = self.as_local_mut() else { return Task::ready(()); }; let mut language_server_names_to_stop = BTreeSet::default(); - let mut language_servers_to_stop = also_restart_servers + let mut language_servers_to_stop = also_stop_servers .into_iter() .flat_map(|selector| match selector { LanguageServerSelector::Id(id) => Some(id), diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 8e1026421e..22ec8438a2 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -3217,9 +3217,11 @@ impl Project { also_restart_servers: HashSet, cx: &mut Context, ) { - self.lsp_store.update(cx, |lsp_store, cx| { - lsp_store.stop_language_servers_for_buffers(buffers, also_restart_servers, cx) - }) + self.lsp_store + .update(cx, |lsp_store, cx| { + lsp_store.stop_language_servers_for_buffers(buffers, also_restart_servers, cx) + }) + .detach_and_log_err(cx); } pub fn cancel_language_server_work_for_buffers( diff --git a/crates/remote_server/src/headless_project.rs b/crates/remote_server/src/headless_project.rs index fa5f2617df..b4d3162641 100644 --- a/crates/remote_server/src/headless_project.rs +++ b/crates/remote_server/src/headless_project.rs @@ -77,7 +77,6 @@ impl HeadlessProject { cx: &mut Context, ) -> Self { debug_adapter_extension::init(proxy.clone(), cx); - language_extension::init(proxy.clone(), languages.clone()); languages::init(languages.clone(), node_runtime.clone(), cx); let worktree_store = cx.new(|cx| { @@ -185,6 +184,11 @@ impl HeadlessProject { }); cx.subscribe(&lsp_store, Self::on_lsp_store_event).detach(); + language_extension::init( + language_extension::LspAccess::ViaLspStore(lsp_store.clone()), + proxy.clone(), + languages.clone(), + ); cx.subscribe( &buffer_store, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 141cd36efd..125b5bb98a 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -6657,6 +6657,10 @@ impl WorkspaceStore { Ok(()) })? } + + pub fn workspaces(&self) -> &HashSet> { + &self.workspaces + } } impl ViewId { diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 34f0ea99cf..59f432faaf 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -441,11 +441,31 @@ pub fn main() { debug_adapter_extension::init(extension_host_proxy.clone(), cx); language::init(cx); - language_extension::init(extension_host_proxy.clone(), languages.clone()); languages::init(languages.clone(), node_runtime.clone(), cx); let user_store = cx.new(|cx| UserStore::new(client.clone(), cx)); let workspace_store = cx.new(|cx| WorkspaceStore::new(client.clone(), cx)); + language_extension::init( + language_extension::LspAccess::ViaWorkspaces({ + let workspace_store = workspace_store.clone(); + Arc::new(move |cx: &mut App| { + workspace_store.update(cx, |workspace_store, cx| { + workspace_store + .workspaces() + .iter() + .map(|workspace| { + workspace.update(cx, |workspace, _, cx| { + workspace.project().read(cx).lsp_store() + }) + }) + .collect() + }) + }) + }), + extension_host_proxy.clone(), + languages.clone(), + ); + Client::set_global(client.clone(), cx); zed::init(cx);