Omit `tsdk_path` from the servers' options if it does not exist

Kirill Bulatov created

Change summary

Cargo.lock                                             |  1 
crates/language/src/language.rs                        |  4 +
crates/language_extension/Cargo.toml                   |  1 
crates/language_extension/src/extension_lsp_adapter.rs |  3 
crates/languages/src/css.rs                            |  2 
crates/languages/src/go.rs                             |  2 
crates/languages/src/json.rs                           |  4 
crates/languages/src/python.rs                         |  3 
crates/languages/src/tailwind.rs                       |  4 
crates/languages/src/typescript.rs                     | 20 ++++-
crates/languages/src/vtsls.rs                          | 18 +++-
crates/languages/src/yaml.rs                           |  3 
crates/project/src/lsp_store.rs                        | 45 +++++++++--
crates/project/src/project.rs                          |  2 
14 files changed, 92 insertions(+), 20 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -6663,6 +6663,7 @@ dependencies = [
  "async-trait",
  "collections",
  "extension",
+ "fs",
  "futures 0.3.31",
  "gpui",
  "language",

crates/language/src/language.rs 🔗

@@ -25,6 +25,7 @@ use crate::language_settings::SoftWrap;
 use anyhow::{anyhow, Context, Result};
 use async_trait::async_trait;
 use collections::{HashMap, HashSet};
+use fs::Fs;
 use futures::Future;
 use gpui::{AppContext, AsyncAppContext, Model, SharedString, Task};
 pub use highlight_map::HighlightMap;
@@ -464,6 +465,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::InitializeParams`]
     async fn initialization_options(
         self: Arc<Self>,
+        _: &dyn Fs,
         _: &Arc<dyn LspAdapterDelegate>,
     ) -> Result<Option<Value>> {
         Ok(None)
@@ -471,6 +473,7 @@ pub trait LspAdapter: 'static + Send + Sync {
 
     async fn workspace_configuration(
         self: Arc<Self>,
+        _: &dyn Fs,
         _: &Arc<dyn LspAdapterDelegate>,
         _: Arc<dyn LanguageToolchainStore>,
         _cx: &mut AsyncAppContext,
@@ -1854,6 +1857,7 @@ impl LspAdapter for FakeLspAdapter {
 
     async fn initialization_options(
         self: Arc<Self>,
+        _: &dyn Fs,
         _: &Arc<dyn LspAdapterDelegate>,
     ) -> Result<Option<Value>> {
         Ok(self.initialization_options.clone())

crates/language_extension/Cargo.toml 🔗

@@ -17,6 +17,7 @@ async-trait.workspace = true
 collections.workspace = true
 extension.workspace = true
 futures.workspace = true
+fs.workspace = true
 gpui.workspace = true
 language.workspace = true
 lsp.workspace = true

crates/language_extension/src/extension_lsp_adapter.rs 🔗

@@ -8,6 +8,7 @@ use anyhow::{Context, Result};
 use async_trait::async_trait;
 use collections::HashMap;
 use extension::{Extension, ExtensionLanguageServerProxy, WorktreeDelegate};
+use fs::Fs;
 use futures::{Future, FutureExt};
 use gpui::AsyncAppContext;
 use language::{
@@ -224,6 +225,7 @@ impl LspAdapter for ExtensionLspAdapter {
 
     async fn initialization_options(
         self: Arc<Self>,
+        _: &dyn Fs,
         delegate: &Arc<dyn LspAdapterDelegate>,
     ) -> Result<Option<serde_json::Value>> {
         let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _;
@@ -246,6 +248,7 @@ impl LspAdapter for ExtensionLspAdapter {
 
     async fn workspace_configuration(
         self: Arc<Self>,
+        _: &dyn Fs,
         delegate: &Arc<dyn LspAdapterDelegate>,
         _: Arc<dyn LanguageToolchainStore>,
         _cx: &mut AsyncAppContext,

crates/languages/src/css.rs 🔗

@@ -4,6 +4,7 @@ use futures::StreamExt;
 use language::{LspAdapter, LspAdapterDelegate};
 use lsp::{LanguageServerBinary, LanguageServerName};
 use node_runtime::NodeRuntime;
+use project::Fs;
 use serde_json::json;
 use smol::fs;
 use std::{
@@ -107,6 +108,7 @@ impl LspAdapter for CssLspAdapter {
 
     async fn initialization_options(
         self: Arc<Self>,
+        _: &dyn Fs,
         _: &Arc<dyn LspAdapterDelegate>,
     ) -> Result<Option<serde_json::Value>> {
         Ok(Some(json!({

crates/languages/src/go.rs 🔗

@@ -6,6 +6,7 @@ use gpui::{AppContext, AsyncAppContext, Task};
 use http_client::github::latest_github_release;
 pub use language::*;
 use lsp::{LanguageServerBinary, LanguageServerName};
+use project::Fs;
 use regex::Regex;
 use serde_json::json;
 use smol::fs;
@@ -197,6 +198,7 @@ impl super::LspAdapter for GoLspAdapter {
 
     async fn initialization_options(
         self: Arc<Self>,
+        _: &dyn Fs,
         _: &Arc<dyn LspAdapterDelegate>,
     ) -> Result<Option<serde_json::Value>> {
         Ok(Some(json!({

crates/languages/src/json.rs 🔗

@@ -9,7 +9,7 @@ use http_client::github::{latest_github_release, GitHubLspBinaryVersion};
 use language::{LanguageRegistry, LanguageToolchainStore, LspAdapter, LspAdapterDelegate};
 use lsp::{LanguageServerBinary, LanguageServerName};
 use node_runtime::NodeRuntime;
-use project::{lsp_store::language_server_settings, ContextProviderWithTasks};
+use project::{lsp_store::language_server_settings, ContextProviderWithTasks, Fs};
 use serde_json::{json, Value};
 use settings::{KeymapFile, SettingsJsonSchemaParams, SettingsStore};
 use smol::{
@@ -208,6 +208,7 @@ impl LspAdapter for JsonLspAdapter {
 
     async fn initialization_options(
         self: Arc<Self>,
+        _: &dyn Fs,
         _: &Arc<dyn LspAdapterDelegate>,
     ) -> Result<Option<serde_json::Value>> {
         Ok(Some(json!({
@@ -217,6 +218,7 @@ impl LspAdapter for JsonLspAdapter {
 
     async fn workspace_configuration(
         self: Arc<Self>,
+        _: &dyn Fs,
         delegate: &Arc<dyn LspAdapterDelegate>,
         _: Arc<dyn LanguageToolchainStore>,
         cx: &mut AsyncAppContext,

crates/languages/src/python.rs 🔗

@@ -18,6 +18,7 @@ use pet_core::os_environment::Environment;
 use pet_core::python_environment::PythonEnvironmentKind;
 use pet_core::Configuration;
 use project::lsp_store::language_server_settings;
+use project::Fs;
 use serde_json::{json, Value};
 use smol::lock::OnceCell;
 use std::cmp::Ordering;
@@ -250,6 +251,7 @@ impl LspAdapter for PythonLspAdapter {
 
     async fn workspace_configuration(
         self: Arc<Self>,
+        _: &dyn Fs,
         adapter: &Arc<dyn LspAdapterDelegate>,
         toolchains: Arc<dyn LanguageToolchainStore>,
         cx: &mut AsyncAppContext,
@@ -931,6 +933,7 @@ impl LspAdapter for PyLspAdapter {
 
     async fn workspace_configuration(
         self: Arc<Self>,
+        _: &dyn Fs,
         adapter: &Arc<dyn LspAdapterDelegate>,
         toolchains: Arc<dyn LanguageToolchainStore>,
         cx: &mut AsyncAppContext,

crates/languages/src/tailwind.rs 🔗

@@ -6,7 +6,7 @@ use gpui::AsyncAppContext;
 use language::{LanguageToolchainStore, LspAdapter, LspAdapterDelegate};
 use lsp::{LanguageServerBinary, LanguageServerName};
 use node_runtime::NodeRuntime;
-use project::lsp_store::language_server_settings;
+use project::{lsp_store::language_server_settings, Fs};
 use serde_json::{json, Value};
 use smol::fs;
 use std::{
@@ -116,6 +116,7 @@ impl LspAdapter for TailwindLspAdapter {
 
     async fn initialization_options(
         self: Arc<Self>,
+        _: &dyn Fs,
         _: &Arc<dyn LspAdapterDelegate>,
     ) -> Result<Option<serde_json::Value>> {
         Ok(Some(json!({
@@ -131,6 +132,7 @@ impl LspAdapter for TailwindLspAdapter {
 
     async fn workspace_configuration(
         self: Arc<Self>,
+        _: &dyn Fs,
         delegate: &Arc<dyn LspAdapterDelegate>,
         _: Arc<dyn LanguageToolchainStore>,
         cx: &mut AsyncAppContext,

crates/languages/src/typescript.rs 🔗

@@ -8,8 +8,8 @@ use http_client::github::{build_asset_url, AssetKind, GitHubLspBinaryVersion};
 use language::{LanguageToolchainStore, LspAdapter, LspAdapterDelegate};
 use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerName};
 use node_runtime::NodeRuntime;
-use project::lsp_store::language_server_settings;
 use project::ContextProviderWithTasks;
+use project::{lsp_store::language_server_settings, Fs};
 use serde_json::{json, Value};
 use smol::{fs, io::BufReader, stream::StreamExt};
 use std::{
@@ -77,16 +77,25 @@ impl TypeScriptLspAdapter {
     pub fn new(node: NodeRuntime) -> Self {
         TypeScriptLspAdapter { node }
     }
-    async fn tsdk_path(adapter: &Arc<dyn LspAdapterDelegate>) -> &'static str {
+    async fn tsdk_path(fs: &dyn Fs, adapter: &Arc<dyn LspAdapterDelegate>) -> Option<&'static str> {
         let is_yarn = adapter
             .read_text_file(PathBuf::from(".yarn/sdks/typescript/lib/typescript.js"))
             .await
             .is_ok();
 
-        if is_yarn {
+        let tsdk_path = if is_yarn {
             ".yarn/sdks/typescript/lib"
         } else {
             "node_modules/typescript/lib"
+        };
+
+        if fs
+            .is_dir(&adapter.worktree_root_path().join(tsdk_path))
+            .await
+        {
+            Some(tsdk_path)
+        } else {
+            None
         }
     }
 }
@@ -233,9 +242,10 @@ impl LspAdapter for TypeScriptLspAdapter {
 
     async fn initialization_options(
         self: Arc<Self>,
+        fs: &dyn Fs,
         adapter: &Arc<dyn LspAdapterDelegate>,
     ) -> Result<Option<serde_json::Value>> {
-        let tsdk_path = Self::tsdk_path(adapter).await;
+        let tsdk_path = Self::tsdk_path(fs, adapter).await;
         Ok(Some(json!({
             "provideFormatter": true,
             "hostInfo": "zed",
@@ -257,6 +267,7 @@ impl LspAdapter for TypeScriptLspAdapter {
 
     async fn workspace_configuration(
         self: Arc<Self>,
+        _: &dyn Fs,
         delegate: &Arc<dyn LspAdapterDelegate>,
         _: Arc<dyn LanguageToolchainStore>,
         cx: &mut AsyncAppContext,
@@ -353,6 +364,7 @@ impl LspAdapter for EsLintLspAdapter {
 
     async fn workspace_configuration(
         self: Arc<Self>,
+        _: &dyn Fs,
         delegate: &Arc<dyn LspAdapterDelegate>,
         _: Arc<dyn LanguageToolchainStore>,
         cx: &mut AsyncAppContext,

crates/languages/src/vtsls.rs 🔗

@@ -5,7 +5,7 @@ use gpui::AsyncAppContext;
 use language::{LanguageToolchainStore, LspAdapter, LspAdapterDelegate};
 use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerName};
 use node_runtime::NodeRuntime;
-use project::lsp_store::language_server_settings;
+use project::{lsp_store::language_server_settings, Fs};
 use serde_json::Value;
 use std::{
     any::Any,
@@ -34,16 +34,25 @@ impl VtslsLspAdapter {
         VtslsLspAdapter { node }
     }
 
-    async fn tsdk_path(adapter: &Arc<dyn LspAdapterDelegate>) -> &'static str {
+    async fn tsdk_path(fs: &dyn Fs, adapter: &Arc<dyn LspAdapterDelegate>) -> Option<&'static str> {
         let is_yarn = adapter
             .read_text_file(PathBuf::from(".yarn/sdks/typescript/lib/typescript.js"))
             .await
             .is_ok();
 
-        if is_yarn {
+        let tsdk_path = if is_yarn {
             ".yarn/sdks/typescript/lib"
         } else {
             Self::TYPESCRIPT_TSDK_PATH
+        };
+
+        if fs
+            .is_dir(&adapter.worktree_root_path().join(tsdk_path))
+            .await
+        {
+            Some(tsdk_path)
+        } else {
+            None
         }
     }
 }
@@ -196,11 +205,12 @@ impl LspAdapter for VtslsLspAdapter {
 
     async fn workspace_configuration(
         self: Arc<Self>,
+        fs: &dyn Fs,
         delegate: &Arc<dyn LspAdapterDelegate>,
         _: Arc<dyn LanguageToolchainStore>,
         cx: &mut AsyncAppContext,
     ) -> Result<Value> {
-        let tsdk_path = Self::tsdk_path(delegate).await;
+        let tsdk_path = Self::tsdk_path(fs, delegate).await;
         let config = serde_json::json!({
             "tsdk": tsdk_path,
             "suggest": {

crates/languages/src/yaml.rs 🔗

@@ -7,7 +7,7 @@ use language::{
 };
 use lsp::{LanguageServerBinary, LanguageServerName};
 use node_runtime::NodeRuntime;
-use project::lsp_store::language_server_settings;
+use project::{lsp_store::language_server_settings, Fs};
 use serde_json::Value;
 use settings::{Settings, SettingsLocation};
 use smol::fs;
@@ -128,6 +128,7 @@ impl LspAdapter for YamlLspAdapter {
 
     async fn workspace_configuration(
         self: Arc<Self>,
+        _: &dyn Fs,
         delegate: &Arc<dyn LspAdapterDelegate>,
         _: Arc<dyn LanguageToolchainStore>,
         cx: &mut AsyncAppContext,

crates/project/src/lsp_store.rs 🔗

@@ -245,7 +245,7 @@ impl LocalLspStore {
             let language = language.clone();
             let key = key.clone();
             let adapter = adapter.clone();
-
+            let fs = self.fs.clone();
             cx.spawn(move |this, mut cx| async move {
                 let result = {
                     let delegate = delegate.clone();
@@ -261,13 +261,18 @@ impl LocalLspStore {
                         let workspace_config = adapter
                             .adapter
                             .clone()
-                            .workspace_configuration(&delegate, toolchains.clone(), &mut cx)
+                            .workspace_configuration(
+                                fs.as_ref(),
+                                &delegate,
+                                toolchains.clone(),
+                                &mut cx,
+                            )
                             .await?;
 
                         let mut initialization_options = adapter
                             .adapter
                             .clone()
-                            .initialization_options(&(delegate))
+                            .initialization_options(fs.as_ref(), &(delegate))
                             .await?;
 
                         match (&mut initialization_options, override_options) {
@@ -284,7 +289,13 @@ impl LocalLspStore {
                             adapter.adapter.prepare_initialize_params(params)
                         })??;
 
-                        Self::setup_lsp_messages(this.clone(), &language_server, delegate, adapter);
+                        Self::setup_lsp_messages(
+                            this.clone(),
+                            fs,
+                            &language_server,
+                            delegate,
+                            adapter,
+                        );
 
                         let did_change_configuration_params =
                             Arc::new(lsp::DidChangeConfigurationParams {
@@ -496,6 +507,7 @@ impl LocalLspStore {
 
     fn setup_lsp_messages(
         this: WeakModel<LspStore>,
+        fs: Arc<dyn Fs>,
         language_server: &LanguageServer,
         delegate: Arc<dyn LspAdapterDelegate>,
         adapter: Arc<CachedLspAdapter>,
@@ -529,15 +541,17 @@ impl LocalLspStore {
                 let adapter = adapter.adapter.clone();
                 let delegate = delegate.clone();
                 let this = this.clone();
+                let fs = fs.clone();
                 move |params, mut cx| {
                     let adapter = adapter.clone();
                     let delegate = delegate.clone();
                     let this = this.clone();
+                    let fs = fs.clone();
                     async move {
                         let toolchains =
                             this.update(&mut cx, |this, cx| this.toolchain_store(cx))?;
                         let workspace_config = adapter
-                            .workspace_configuration(&delegate, toolchains, &mut cx)
+                            .workspace_configuration(fs.as_ref(), &delegate, toolchains, &mut cx)
                             .await?;
                         Ok(params
                             .items
@@ -2954,7 +2968,10 @@ impl LspStore {
 
         let _maintain_workspace_config = {
             let (sender, receiver) = watch::channel();
-            (Self::maintain_workspace_config(receiver, cx), sender)
+            (
+                Self::maintain_workspace_config(fs.clone(), receiver, cx),
+                sender,
+            )
         };
         Self {
             mode: LspStoreMode::Local(LocalLspStore {
@@ -3017,6 +3034,7 @@ impl LspStore {
         })
     }
 
+    #[allow(clippy::too_many_arguments)]
     pub(super) fn new_remote(
         buffer_store: Model<BufferStore>,
         worktree_store: Model<WorktreeStore>,
@@ -3024,6 +3042,7 @@ impl LspStore {
         languages: Arc<LanguageRegistry>,
         upstream_client: AnyProtoClient,
         project_id: u64,
+        fs: Arc<dyn Fs>,
         cx: &mut ModelContext<Self>,
     ) -> Self {
         cx.subscribe(&buffer_store, Self::on_buffer_store_event)
@@ -3032,7 +3051,7 @@ impl LspStore {
             .detach();
         let _maintain_workspace_config = {
             let (sender, receiver) = watch::channel();
-            (Self::maintain_workspace_config(receiver, cx), sender)
+            (Self::maintain_workspace_config(fs, receiver, cx), sender)
         };
         Self {
             mode: LspStoreMode::Remote(RemoteLspStore {
@@ -5125,6 +5144,7 @@ impl LspStore {
 
     pub(crate) async fn refresh_workspace_configurations(
         this: &WeakModel<Self>,
+        fs: Arc<dyn Fs>,
         mut cx: AsyncAppContext,
     ) {
         maybe!(async move {
@@ -5171,7 +5191,12 @@ impl LspStore {
                 .ok()?;
             for (adapter, server, delegate) in servers {
                 let settings = adapter
-                    .workspace_configuration(&delegate, toolchain_store.clone(), &mut cx)
+                    .workspace_configuration(
+                        fs.as_ref(),
+                        &delegate,
+                        toolchain_store.clone(),
+                        &mut cx,
+                    )
                     .await
                     .ok()?;
 
@@ -5194,6 +5219,7 @@ impl LspStore {
         }
     }
     fn maintain_workspace_config(
+        fs: Arc<dyn Fs>,
         external_refresh_requests: watch::Receiver<()>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<()>> {
@@ -5208,7 +5234,7 @@ impl LspStore {
             futures::stream::select(settings_changed_rx, external_refresh_requests);
         cx.spawn(move |this, cx| async move {
             while let Some(()) = joint_future.next().await {
-                Self::refresh_workspace_configurations(&this, cx.clone()).await;
+                Self::refresh_workspace_configurations(&this, fs.clone(), cx.clone()).await;
             }
 
             drop(settings_observation);
@@ -8370,6 +8396,7 @@ impl LspAdapter for SshLspAdapter {
 
     async fn initialization_options(
         self: Arc<Self>,
+        _: &dyn Fs,
         _: &Arc<dyn LspAdapterDelegate>,
     ) -> Result<Option<serde_json::Value>> {
         let Some(options) = &self.initialization_options else {

crates/project/src/project.rs 🔗

@@ -800,6 +800,7 @@ impl Project {
                     languages.clone(),
                     ssh_proto.clone(),
                     SSH_PROJECT_ID,
+                    fs.clone(),
                     cx,
                 )
             });
@@ -972,6 +973,7 @@ impl Project {
                 languages.clone(),
                 client.clone().into(),
                 remote_id,
+                fs.clone(),
                 cx,
             );
             lsp_store.set_language_server_statuses_from_proto(response.payload.language_servers);