Simplify language server startup (#6449)

Max Brunsfeld created

These are just some small refactorings of our language-server-starting
code, motivated by another change that I decided to bail on:
https://github.com/zed-industries/zed/pull/6448.

Change summary

crates/language/src/language.rs        |  27 +++---
crates/project/src/project.rs          | 109 ++++++++++-----------------
crates/zed/src/languages/c.rs          |   2 
crates/zed/src/languages/css.rs        |   4 
crates/zed/src/languages/elixir.rs     |   6 
crates/zed/src/languages/go.rs         |   2 
crates/zed/src/languages/html.rs       |   4 
crates/zed/src/languages/json.rs       |   6 
crates/zed/src/languages/lua.rs        |   2 
crates/zed/src/languages/nu.rs         |   2 
crates/zed/src/languages/php.rs        |   6 
crates/zed/src/languages/python.rs     |   2 
crates/zed/src/languages/ruby.rs       |   2 
crates/zed/src/languages/rust.rs       |   6 
crates/zed/src/languages/svelte.rs     |   4 
crates/zed/src/languages/tailwind.rs   |   6 
crates/zed/src/languages/typescript.rs |  10 +-
crates/zed/src/languages/uiua.rs       |   2 
crates/zed/src/languages/vue.rs        |   4 
crates/zed/src/languages/yaml.rs       |   2 
20 files changed, 91 insertions(+), 117 deletions(-)

Detailed changes

crates/language/src/language.rs 🔗

@@ -139,12 +139,11 @@ pub struct CachedLspAdapter {
 
 impl CachedLspAdapter {
     pub async fn new(adapter: Arc<dyn LspAdapter>) -> Arc<Self> {
-        let name = adapter.name().await;
+        let name = adapter.name();
         let short_name = adapter.short_name();
-        let disk_based_diagnostic_sources = adapter.disk_based_diagnostic_sources().await;
-        let disk_based_diagnostics_progress_token =
-            adapter.disk_based_diagnostics_progress_token().await;
-        let language_ids = adapter.language_ids().await;
+        let disk_based_diagnostic_sources = adapter.disk_based_diagnostic_sources();
+        let disk_based_diagnostics_progress_token = adapter.disk_based_diagnostics_progress_token();
+        let language_ids = adapter.language_ids();
 
         Arc::new(CachedLspAdapter {
             name,
@@ -261,7 +260,7 @@ pub trait LspAdapterDelegate: Send + Sync {
 
 #[async_trait]
 pub trait LspAdapter: 'static + Send + Sync {
-    async fn name(&self) -> LanguageServerName;
+    fn name(&self) -> LanguageServerName;
 
     fn short_name(&self) -> &'static str;
 
@@ -337,7 +336,7 @@ pub trait LspAdapter: 'static + Send + Sync {
     }
 
     /// Returns initialization options that are going to be sent to a LSP server as a part of [`lsp_types::InitializeParams`]
-    async fn initialization_options(&self) -> Option<Value> {
+    fn initialization_options(&self) -> Option<Value> {
         None
     }
 
@@ -356,15 +355,15 @@ pub trait LspAdapter: 'static + Send + Sync {
         ])
     }
 
-    async fn disk_based_diagnostic_sources(&self) -> Vec<String> {
+    fn disk_based_diagnostic_sources(&self) -> Vec<String> {
         Default::default()
     }
 
-    async fn disk_based_diagnostics_progress_token(&self) -> Option<String> {
+    fn disk_based_diagnostics_progress_token(&self) -> Option<String> {
         None
     }
 
-    async fn language_ids(&self) -> HashMap<String, String> {
+    fn language_ids(&self) -> HashMap<String, String> {
         Default::default()
     }
 
@@ -1881,7 +1880,7 @@ impl Default for FakeLspAdapter {
 #[cfg(any(test, feature = "test-support"))]
 #[async_trait]
 impl LspAdapter for Arc<FakeLspAdapter> {
-    async fn name(&self) -> LanguageServerName {
+    fn name(&self) -> LanguageServerName {
         LanguageServerName(self.name.into())
     }
 
@@ -1919,15 +1918,15 @@ impl LspAdapter for Arc<FakeLspAdapter> {
 
     fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
 
-    async fn disk_based_diagnostic_sources(&self) -> Vec<String> {
+    fn disk_based_diagnostic_sources(&self) -> Vec<String> {
         self.disk_based_diagnostics_sources.clone()
     }
 
-    async fn disk_based_diagnostics_progress_token(&self) -> Option<String> {
+    fn disk_based_diagnostics_progress_token(&self) -> Option<String> {
         self.disk_based_diagnostics_progress_token.clone()
     }
 
-    async fn initialization_options(&self) -> Option<Value> {
+    fn initialization_options(&self) -> Option<Value> {
         self.initialization_options.clone()
     }
 

crates/project/src/project.rs 🔗

@@ -974,8 +974,7 @@ impl Project {
 
         // Start all the newly-enabled language servers.
         for (worktree, language) in language_servers_to_start {
-            let worktree_path = worktree.read(cx).abs_path();
-            self.start_language_servers(&worktree, worktree_path, language, cx);
+            self.start_language_servers(&worktree, language, cx);
         }
 
         // Restart all language servers with changed initialization options.
@@ -2774,8 +2773,8 @@ impl Project {
         };
         if let Some(file) = buffer_file {
             let worktree = file.worktree.clone();
-            if let Some(tree) = worktree.read(cx).as_local() {
-                self.start_language_servers(&worktree, tree.abs_path().clone(), new_language, cx);
+            if worktree.read(cx).is_local() {
+                self.start_language_servers(&worktree, new_language, cx);
             }
         }
     }
@@ -2783,7 +2782,6 @@ impl Project {
     fn start_language_servers(
         &mut self,
         worktree: &Model<Worktree>,
-        worktree_path: Arc<Path>,
         language: Arc<Language>,
         cx: &mut ModelContext<Self>,
     ) {
@@ -2793,22 +2791,14 @@ impl Project {
             return;
         }
 
-        let worktree_id = worktree.read(cx).id();
         for adapter in language.lsp_adapters() {
-            self.start_language_server(
-                worktree_id,
-                worktree_path.clone(),
-                adapter.clone(),
-                language.clone(),
-                cx,
-            );
+            self.start_language_server(worktree, adapter.clone(), language.clone(), cx);
         }
     }
 
     fn start_language_server(
         &mut self,
-        worktree_id: WorktreeId,
-        worktree_path: Arc<Path>,
+        worktree: &Model<Worktree>,
         adapter: Arc<CachedLspAdapter>,
         language: Arc<Language>,
         cx: &mut ModelContext<Self>,
@@ -2817,6 +2807,9 @@ impl Project {
             return;
         }
 
+        let worktree = worktree.read(cx);
+        let worktree_id = worktree.id();
+        let worktree_path = worktree.abs_path();
         let key = (worktree_id, adapter.name.clone());
         if self.language_server_ids.contains_key(&key) {
             return;
@@ -2949,20 +2942,14 @@ impl Project {
             this.update(&mut cx, |this, cx| {
                 let worktrees = this.worktrees.clone();
                 for worktree in worktrees {
-                    let worktree = match worktree.upgrade() {
-                        Some(worktree) => worktree.read(cx),
-                        None => continue,
-                    };
-                    let worktree_id = worktree.id();
-                    let root_path = worktree.abs_path();
-
-                    this.start_language_server(
-                        worktree_id,
-                        root_path,
-                        adapter.clone(),
-                        language.clone(),
-                        cx,
-                    );
+                    if let Some(worktree) = worktree.upgrade() {
+                        this.start_language_server(
+                            &worktree,
+                            adapter.clone(),
+                            language.clone(),
+                            cx,
+                        );
+                    }
                 }
             })
             .ok();
@@ -3176,7 +3163,7 @@ impl Project {
                 }
             })
             .detach();
-        let mut initialization_options = adapter.adapter.initialization_options().await;
+        let mut initialization_options = adapter.adapter.initialization_options();
         match (&mut initialization_options, override_options) {
             (Some(initialization_options), Some(override_options)) => {
                 merge_json_value_into(override_options, initialization_options);
@@ -3332,7 +3319,7 @@ impl Project {
         worktree_id: WorktreeId,
         adapter_name: LanguageServerName,
         cx: &mut ModelContext<Self>,
-    ) -> Task<(Option<PathBuf>, Vec<WorktreeId>)> {
+    ) -> Task<Vec<WorktreeId>> {
         let key = (worktree_id, adapter_name);
         if let Some(server_id) = self.language_server_ids.remove(&key) {
             log::info!("stopping language server {}", key.1 .0);
@@ -3370,8 +3357,6 @@ impl Project {
             let server_state = self.language_servers.remove(&server_id);
             cx.emit(Event::LanguageServerRemoved(server_id));
             cx.spawn(move |this, mut cx| async move {
-                let mut root_path = None;
-
                 let server = match server_state {
                     Some(LanguageServerState::Starting(task)) => task.await,
                     Some(LanguageServerState::Running { server, .. }) => Some(server),
@@ -3379,7 +3364,6 @@ impl Project {
                 };
 
                 if let Some(server) = server {
-                    root_path = Some(server.root_path().clone());
                     if let Some(shutdown) = server.shutdown() {
                         shutdown.await;
                     }
@@ -3393,10 +3377,10 @@ impl Project {
                     .ok();
                 }
 
-                (root_path, orphaned_worktrees)
+                orphaned_worktrees
             })
         } else {
-            Task::ready((None, Vec::new()))
+            Task::ready(Vec::new())
         }
     }
 
@@ -3426,7 +3410,6 @@ impl Project {
         None
     }
 
-    // TODO This will break in the case where the adapter's root paths and worktrees are not equal
     fn restart_language_servers(
         &mut self,
         worktree: Model<Worktree>,
@@ -3434,50 +3417,42 @@ impl Project {
         cx: &mut ModelContext<Self>,
     ) {
         let worktree_id = worktree.read(cx).id();
-        let fallback_path = worktree.read(cx).abs_path();
-
-        let mut stops = Vec::new();
-        for adapter in language.lsp_adapters() {
-            stops.push(self.stop_language_server(worktree_id, adapter.name.clone(), cx));
-        }
 
-        if stops.is_empty() {
+        let stop_tasks = language
+            .lsp_adapters()
+            .iter()
+            .map(|adapter| {
+                let stop_task = self.stop_language_server(worktree_id, adapter.name.clone(), cx);
+                (stop_task, adapter.name.clone())
+            })
+            .collect::<Vec<_>>();
+        if stop_tasks.is_empty() {
             return;
         }
-        let mut stops = stops.into_iter();
 
         cx.spawn(move |this, mut cx| async move {
-            let (original_root_path, mut orphaned_worktrees) = stops.next().unwrap().await;
-            for stop in stops {
-                let (_, worktrees) = stop.await;
-                orphaned_worktrees.extend_from_slice(&worktrees);
+            // For each stopped language server, record all of the worktrees with which
+            // it was associated.
+            let mut affected_worktrees = Vec::new();
+            for (stop_task, language_server_name) in stop_tasks {
+                for affected_worktree_id in stop_task.await {
+                    affected_worktrees.push((affected_worktree_id, language_server_name.clone()));
+                }
             }
 
-            let this = match this.upgrade() {
-                Some(this) => this,
-                None => return,
-            };
-
             this.update(&mut cx, |this, cx| {
-                // Attempt to restart using original server path. Fallback to passed in
-                // path if we could not retrieve the root path
-                let root_path = original_root_path
-                    .map(|path_buf| Arc::from(path_buf.as_path()))
-                    .unwrap_or(fallback_path);
-
-                this.start_language_servers(&worktree, root_path, language.clone(), cx);
+                // Restart the language server for the given worktree.
+                this.start_language_servers(&worktree, language.clone(), cx);
 
                 // Lookup new server ids and set them for each of the orphaned worktrees
-                for adapter in language.lsp_adapters() {
+                for (affected_worktree_id, language_server_name) in affected_worktrees {
                     if let Some(new_server_id) = this
                         .language_server_ids
-                        .get(&(worktree_id, adapter.name.clone()))
+                        .get(&(worktree_id, language_server_name.clone()))
                         .cloned()
                     {
-                        for &orphaned_worktree in &orphaned_worktrees {
-                            this.language_server_ids
-                                .insert((orphaned_worktree, adapter.name.clone()), new_server_id);
-                        }
+                        this.language_server_ids
+                            .insert((affected_worktree_id, language_server_name), new_server_id);
                     }
                 }
             })

crates/zed/src/languages/c.rs 🔗

@@ -15,7 +15,7 @@ pub struct CLspAdapter;
 
 #[async_trait]
 impl super::LspAdapter for CLspAdapter {
-    async fn name(&self) -> LanguageServerName {
+    fn name(&self) -> LanguageServerName {
         LanguageServerName("clangd".into())
     }
 

crates/zed/src/languages/css.rs 🔗

@@ -33,7 +33,7 @@ impl CssLspAdapter {
 
 #[async_trait]
 impl LspAdapter for CssLspAdapter {
-    async fn name(&self) -> LanguageServerName {
+    fn name(&self) -> LanguageServerName {
         LanguageServerName("vscode-css-language-server".into())
     }
 
@@ -91,7 +91,7 @@ impl LspAdapter for CssLspAdapter {
         get_cached_server_binary(container_dir, &*self.node).await
     }
 
-    async fn initialization_options(&self) -> Option<serde_json::Value> {
+    fn initialization_options(&self) -> Option<serde_json::Value> {
         Some(json!({
             "provideFormatter": true
         }))

crates/zed/src/languages/elixir.rs 🔗

@@ -67,7 +67,7 @@ pub struct ElixirLspAdapter;
 
 #[async_trait]
 impl LspAdapter for ElixirLspAdapter {
-    async fn name(&self) -> LanguageServerName {
+    fn name(&self) -> LanguageServerName {
         LanguageServerName("elixir-ls".into())
     }
 
@@ -301,7 +301,7 @@ pub struct NextLspAdapter;
 
 #[async_trait]
 impl LspAdapter for NextLspAdapter {
-    async fn name(&self) -> LanguageServerName {
+    fn name(&self) -> LanguageServerName {
         LanguageServerName("next-ls".into())
     }
 
@@ -452,7 +452,7 @@ pub struct LocalLspAdapter {
 
 #[async_trait]
 impl LspAdapter for LocalLspAdapter {
-    async fn name(&self) -> LanguageServerName {
+    fn name(&self) -> LanguageServerName {
         LanguageServerName("local-ls".into())
     }
 

crates/zed/src/languages/go.rs 🔗

@@ -33,7 +33,7 @@ lazy_static! {
 
 #[async_trait]
 impl super::LspAdapter for GoLspAdapter {
-    async fn name(&self) -> LanguageServerName {
+    fn name(&self) -> LanguageServerName {
         LanguageServerName("gopls".into())
     }
 

crates/zed/src/languages/html.rs 🔗

@@ -33,7 +33,7 @@ impl HtmlLspAdapter {
 
 #[async_trait]
 impl LspAdapter for HtmlLspAdapter {
-    async fn name(&self) -> LanguageServerName {
+    fn name(&self) -> LanguageServerName {
         LanguageServerName("vscode-html-language-server".into())
     }
 
@@ -91,7 +91,7 @@ impl LspAdapter for HtmlLspAdapter {
         get_cached_server_binary(container_dir, &*self.node).await
     }
 
-    async fn initialization_options(&self) -> Option<serde_json::Value> {
+    fn initialization_options(&self) -> Option<serde_json::Value> {
         Some(json!({
             "provideFormatter": true
         }))

crates/zed/src/languages/json.rs 🔗

@@ -38,7 +38,7 @@ impl JsonLspAdapter {
 
 #[async_trait]
 impl LspAdapter for JsonLspAdapter {
-    async fn name(&self) -> LanguageServerName {
+    fn name(&self) -> LanguageServerName {
         LanguageServerName("json-language-server".into())
     }
 
@@ -96,7 +96,7 @@ impl LspAdapter for JsonLspAdapter {
         get_cached_server_binary(container_dir, &*self.node).await
     }
 
-    async fn initialization_options(&self) -> Option<serde_json::Value> {
+    fn initialization_options(&self) -> Option<serde_json::Value> {
         Some(json!({
             "provideFormatter": true
         }))
@@ -140,7 +140,7 @@ impl LspAdapter for JsonLspAdapter {
         })
     }
 
-    async fn language_ids(&self) -> HashMap<String, String> {
+    fn language_ids(&self) -> HashMap<String, String> {
         [("JSON".into(), "jsonc".into())].into_iter().collect()
     }
 }

crates/zed/src/languages/lua.rs 🔗

@@ -18,7 +18,7 @@ pub struct LuaLspAdapter;
 
 #[async_trait]
 impl super::LspAdapter for LuaLspAdapter {
-    async fn name(&self) -> LanguageServerName {
+    fn name(&self) -> LanguageServerName {
         LanguageServerName("lua-language-server".into())
     }
 

crates/zed/src/languages/nu.rs 🔗

@@ -8,7 +8,7 @@ pub struct NuLanguageServer;
 
 #[async_trait]
 impl LspAdapter for NuLanguageServer {
-    async fn name(&self) -> LanguageServerName {
+    fn name(&self) -> LanguageServerName {
         LanguageServerName("nu".into())
     }
 

crates/zed/src/languages/php.rs 🔗

@@ -36,7 +36,7 @@ impl IntelephenseLspAdapter {
 
 #[async_trait]
 impl LspAdapter for IntelephenseLspAdapter {
-    async fn name(&self) -> LanguageServerName {
+    fn name(&self) -> LanguageServerName {
         LanguageServerName("intelephense".into())
     }
 
@@ -96,10 +96,10 @@ impl LspAdapter for IntelephenseLspAdapter {
         None
     }
 
-    async fn initialization_options(&self) -> Option<serde_json::Value> {
+    fn initialization_options(&self) -> Option<serde_json::Value> {
         None
     }
-    async fn language_ids(&self) -> HashMap<String, String> {
+    fn language_ids(&self) -> HashMap<String, String> {
         HashMap::from_iter([("PHP".into(), "php".into())])
     }
 }

crates/zed/src/languages/python.rs 🔗

@@ -30,7 +30,7 @@ impl PythonLspAdapter {
 
 #[async_trait]
 impl LspAdapter for PythonLspAdapter {
-    async fn name(&self) -> LanguageServerName {
+    fn name(&self) -> LanguageServerName {
         LanguageServerName("pyright".into())
     }
 

crates/zed/src/languages/ruby.rs 🔗

@@ -8,7 +8,7 @@ pub struct RubyLanguageServer;
 
 #[async_trait]
 impl LspAdapter for RubyLanguageServer {
-    async fn name(&self) -> LanguageServerName {
+    fn name(&self) -> LanguageServerName {
         LanguageServerName("solargraph".into())
     }
 

crates/zed/src/languages/rust.rs 🔗

@@ -18,7 +18,7 @@ pub struct RustLspAdapter;
 
 #[async_trait]
 impl LspAdapter for RustLspAdapter {
-    async fn name(&self) -> LanguageServerName {
+    fn name(&self) -> LanguageServerName {
         LanguageServerName("rust-analyzer".into())
     }
 
@@ -98,11 +98,11 @@ impl LspAdapter for RustLspAdapter {
             })
     }
 
-    async fn disk_based_diagnostic_sources(&self) -> Vec<String> {
+    fn disk_based_diagnostic_sources(&self) -> Vec<String> {
         vec!["rustc".into()]
     }
 
-    async fn disk_based_diagnostics_progress_token(&self) -> Option<String> {
+    fn disk_based_diagnostics_progress_token(&self) -> Option<String> {
         Some("rust-analyzer/flycheck".into())
     }
 

crates/zed/src/languages/svelte.rs 🔗

@@ -32,7 +32,7 @@ impl SvelteLspAdapter {
 
 #[async_trait]
 impl LspAdapter for SvelteLspAdapter {
-    async fn name(&self) -> LanguageServerName {
+    fn name(&self) -> LanguageServerName {
         LanguageServerName("svelte-language-server".into())
     }
 
@@ -90,7 +90,7 @@ impl LspAdapter for SvelteLspAdapter {
         get_cached_server_binary(container_dir, &*self.node).await
     }
 
-    async fn initialization_options(&self) -> Option<serde_json::Value> {
+    fn initialization_options(&self) -> Option<serde_json::Value> {
         Some(json!({
             "provideFormatter": true
         }))

crates/zed/src/languages/tailwind.rs 🔗

@@ -34,7 +34,7 @@ impl TailwindLspAdapter {
 
 #[async_trait]
 impl LspAdapter for TailwindLspAdapter {
-    async fn name(&self) -> LanguageServerName {
+    fn name(&self) -> LanguageServerName {
         LanguageServerName("tailwindcss-language-server".into())
     }
 
@@ -92,7 +92,7 @@ impl LspAdapter for TailwindLspAdapter {
         get_cached_server_binary(container_dir, &*self.node).await
     }
 
-    async fn initialization_options(&self) -> Option<serde_json::Value> {
+    fn initialization_options(&self) -> Option<serde_json::Value> {
         Some(json!({
             "provideFormatter": true,
             "userLanguages": {
@@ -112,7 +112,7 @@ impl LspAdapter for TailwindLspAdapter {
         })
     }
 
-    async fn language_ids(&self) -> HashMap<String, String> {
+    fn language_ids(&self) -> HashMap<String, String> {
         HashMap::from_iter([
             ("HTML".to_string(), "html".to_string()),
             ("CSS".to_string(), "css".to_string()),

crates/zed/src/languages/typescript.rs 🔗

@@ -46,7 +46,7 @@ struct TypeScriptVersions {
 
 #[async_trait]
 impl LspAdapter for TypeScriptLspAdapter {
-    async fn name(&self) -> LanguageServerName {
+    fn name(&self) -> LanguageServerName {
         LanguageServerName("typescript-language-server".into())
     }
 
@@ -150,7 +150,7 @@ impl LspAdapter for TypeScriptLspAdapter {
         })
     }
 
-    async fn initialization_options(&self) -> Option<serde_json::Value> {
+    fn initialization_options(&self) -> Option<serde_json::Value> {
         Some(json!({
             "provideFormatter": true,
             "tsserver": {
@@ -159,7 +159,7 @@ impl LspAdapter for TypeScriptLspAdapter {
         }))
     }
 
-    async fn language_ids(&self) -> HashMap<String, String> {
+    fn language_ids(&self) -> HashMap<String, String> {
         HashMap::from_iter([
             ("TypeScript".into(), "typescript".into()),
             ("JavaScript".into(), "javascript".into()),
@@ -227,7 +227,7 @@ impl LspAdapter for EsLintLspAdapter {
         })
     }
 
-    async fn name(&self) -> LanguageServerName {
+    fn name(&self) -> LanguageServerName {
         LanguageServerName("eslint".into())
     }
 
@@ -315,7 +315,7 @@ impl LspAdapter for EsLintLspAdapter {
         None
     }
 
-    async fn initialization_options(&self) -> Option<serde_json::Value> {
+    fn initialization_options(&self) -> Option<serde_json::Value> {
         None
     }
 }

crates/zed/src/languages/uiua.rs 🔗

@@ -8,7 +8,7 @@ pub struct UiuaLanguageServer;
 
 #[async_trait]
 impl LspAdapter for UiuaLanguageServer {
-    async fn name(&self) -> LanguageServerName {
+    fn name(&self) -> LanguageServerName {
         LanguageServerName("uiua".into())
     }
 

crates/zed/src/languages/vue.rs 🔗

@@ -40,7 +40,7 @@ impl VueLspAdapter {
 }
 #[async_trait]
 impl super::LspAdapter for VueLspAdapter {
-    async fn name(&self) -> LanguageServerName {
+    fn name(&self) -> LanguageServerName {
         LanguageServerName("vue-language-server".into())
     }
 
@@ -60,7 +60,7 @@ impl super::LspAdapter for VueLspAdapter {
             ts_version: self.node.npm_package_latest_version("typescript").await?,
         }) as Box<_>)
     }
-    async fn initialization_options(&self) -> Option<Value> {
+    fn initialization_options(&self) -> Option<Value> {
         let typescript_sdk_path = self.typescript_install_path.lock();
         let typescript_sdk_path = typescript_sdk_path
             .as_ref()

crates/zed/src/languages/yaml.rs 🔗

@@ -35,7 +35,7 @@ impl YamlLspAdapter {
 
 #[async_trait]
 impl LspAdapter for YamlLspAdapter {
-    async fn name(&self) -> LanguageServerName {
+    fn name(&self) -> LanguageServerName {
         LanguageServerName("yaml-language-server".into())
     }