From 91b0ca089508671569b6537b79c4654be408a133 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 23 Jan 2025 13:17:32 +0200 Subject: [PATCH] Omit `tsdk_path` from the servers' options if it does not exist (#23525) Part of https://github.com/zed-industries/zed/issues/22606 Before, `tsdk_path` for vtsls and typescript-language-server unconditionally set a `tsdk`/`tsserver` property for the corresponding language server, even if there were no such directory at all. Instead, make the corresponding code to omit such property if it was not found on the FS. Release Notes: - Fixed "The path /.../tsserver.js doesn't point to a valid tsserver install. Falling back to bundled TypeScript version." pop-up appearing --- Cargo.lock | 1 + crates/language/src/language.rs | 4 ++ crates/language_extension/Cargo.toml | 1 + .../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 | 44 +++++++++++++++---- crates/project/src/project.rs | 2 + 14 files changed, 92 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 489bf6989a719e24aa3c29e72069fb6414809623..e56e26deb9a4fad3efe6845bd0a1e0bd9a181491 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6788,6 +6788,7 @@ dependencies = [ "async-trait", "collections", "extension", + "fs", "futures 0.3.31", "gpui", "language", diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 2769e0fe0bc86356852db31a8409bcc9257d267f..d926d26b17f8c278e92f137e821fb0746e674dfb 100644 --- a/crates/language/src/language.rs +++ b/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; @@ -498,6 +499,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, + _: &dyn Fs, _: &Arc, ) -> Result> { Ok(None) @@ -505,6 +507,7 @@ pub trait LspAdapter: 'static + Send + Sync { async fn workspace_configuration( self: Arc, + _: &dyn Fs, _: &Arc, _: Arc, _cx: &mut AsyncAppContext, @@ -1901,6 +1904,7 @@ impl LspAdapter for FakeLspAdapter { async fn initialization_options( self: Arc, + _: &dyn Fs, _: &Arc, ) -> Result> { Ok(self.initialization_options.clone()) diff --git a/crates/language_extension/Cargo.toml b/crates/language_extension/Cargo.toml index ef561b884120f8ef766db85602f6be3703375665..6f9439fb5a3b226b2d851cd56501cedfadccba00 100644 --- a/crates/language_extension/Cargo.toml +++ b/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 diff --git a/crates/language_extension/src/extension_lsp_adapter.rs b/crates/language_extension/src/extension_lsp_adapter.rs index 3286e09e2d6ff621bf27978d09c00ff50691cd34..ec9ea1e0e5f028ef1fccaeed383b68231952f174 100644 --- a/crates/language_extension/src/extension_lsp_adapter.rs +++ b/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, + _: &dyn Fs, delegate: &Arc, ) -> Result> { let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _; @@ -246,6 +248,7 @@ impl LspAdapter for ExtensionLspAdapter { async fn workspace_configuration( self: Arc, + _: &dyn Fs, delegate: &Arc, _: Arc, _cx: &mut AsyncAppContext, diff --git a/crates/languages/src/css.rs b/crates/languages/src/css.rs index 148f6acced679fcb7f8d25cac741c47c559690d7..50e34cea40b572025362d8da7a4d3a9495877705 100644 --- a/crates/languages/src/css.rs +++ b/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, + _: &dyn Fs, _: &Arc, ) -> Result> { Ok(Some(json!({ diff --git a/crates/languages/src/go.rs b/crates/languages/src/go.rs index d56c720eb0ec3a93ab2a1237e06284913698016e..ca17f13e26ce838c00687692b1e10c701f6e2e3b 100644 --- a/crates/languages/src/go.rs +++ b/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, + _: &dyn Fs, _: &Arc, ) -> Result> { Ok(Some(json!({ diff --git a/crates/languages/src/json.rs b/crates/languages/src/json.rs index a783195db32bbdd4335e9420f879ffb10fb9926f..95aefd405aff2b98c6a13c37f1d291495f474631 100644 --- a/crates/languages/src/json.rs +++ b/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, + _: &dyn Fs, _: &Arc, ) -> Result> { Ok(Some(json!({ @@ -217,6 +218,7 @@ impl LspAdapter for JsonLspAdapter { async fn workspace_configuration( self: Arc, + _: &dyn Fs, delegate: &Arc, _: Arc, cx: &mut AsyncAppContext, diff --git a/crates/languages/src/python.rs b/crates/languages/src/python.rs index be9cef8651479a0fe524d241d2b7429d7c8c3b73..a3cf24cf57dae808dab7d4a7275288904f343fd0 100644 --- a/crates/languages/src/python.rs +++ b/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, + _: &dyn Fs, adapter: &Arc, toolchains: Arc, cx: &mut AsyncAppContext, @@ -931,6 +933,7 @@ impl LspAdapter for PyLspAdapter { async fn workspace_configuration( self: Arc, + _: &dyn Fs, adapter: &Arc, toolchains: Arc, cx: &mut AsyncAppContext, diff --git a/crates/languages/src/tailwind.rs b/crates/languages/src/tailwind.rs index 02c3dbefc2c1cae605b8a78ef6e51a6527203f8f..328cf51279e8464866b0572a366658d00d389a1f 100644 --- a/crates/languages/src/tailwind.rs +++ b/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, + _: &dyn Fs, _: &Arc, ) -> Result> { Ok(Some(json!({ @@ -131,6 +132,7 @@ impl LspAdapter for TailwindLspAdapter { async fn workspace_configuration( self: Arc, + _: &dyn Fs, delegate: &Arc, _: Arc, cx: &mut AsyncAppContext, diff --git a/crates/languages/src/typescript.rs b/crates/languages/src/typescript.rs index edfdbf5f55a28c833469fd92a6399cf130669e6c..b14f3dc2379ef80a96354c406b836aca13631f62 100644 --- a/crates/languages/src/typescript.rs +++ b/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) -> &'static str { + async fn tsdk_path(fs: &dyn Fs, adapter: &Arc) -> 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, + fs: &dyn Fs, adapter: &Arc, ) -> Result> { - 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, + _: &dyn Fs, delegate: &Arc, _: Arc, cx: &mut AsyncAppContext, @@ -353,6 +364,7 @@ impl LspAdapter for EsLintLspAdapter { async fn workspace_configuration( self: Arc, + _: &dyn Fs, delegate: &Arc, _: Arc, cx: &mut AsyncAppContext, diff --git a/crates/languages/src/vtsls.rs b/crates/languages/src/vtsls.rs index 22718e410c221e51f1eafd5361853a70401b9d4d..33dea775409a3e800d9e1389765f3494a2ca6e7c 100644 --- a/crates/languages/src/vtsls.rs +++ b/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) -> &'static str { + async fn tsdk_path(fs: &dyn Fs, adapter: &Arc) -> 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, + fs: &dyn Fs, delegate: &Arc, _: Arc, cx: &mut AsyncAppContext, ) -> Result { - 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": { diff --git a/crates/languages/src/yaml.rs b/crates/languages/src/yaml.rs index 12878b8f936031cc098e421d61457c8e086503a7..91f6e48aaef8aaf10f06f102c37e2d70e9e88508 100644 --- a/crates/languages/src/yaml.rs +++ b/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, + _: &dyn Fs, delegate: &Arc, _: Arc, cx: &mut AsyncAppContext, diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 9e3960c9254d17eb6990fbf04138eaeb67b1d157..d887853ef4f1f47823bda72690919581a684fcf6 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -239,6 +239,7 @@ impl LocalLspStore { let adapter = adapter.clone(); let this = self.weak.clone(); let pending_workspace_folders = pending_workspace_folders.clone(); + let fs = self.fs.clone(); cx.spawn(move |mut cx| async move { let result = { let delegate = delegate.clone(); @@ -254,13 +255,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) { @@ -277,7 +283,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 { @@ -425,6 +437,7 @@ impl LocalLspStore { fn setup_lsp_messages( this: WeakModel, + fs: Arc, language_server: &LanguageServer, delegate: Arc, adapter: Arc, @@ -458,15 +471,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 @@ -3029,7 +3044,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, + ) }; let project_tree = ProjectTree::new(worktree_store.clone(), cx); Self { @@ -3093,6 +3111,7 @@ impl LspStore { }) } + #[allow(clippy::too_many_arguments)] pub(super) fn new_remote( buffer_store: Model, worktree_store: Model, @@ -3100,6 +3119,7 @@ impl LspStore { languages: Arc, upstream_client: AnyProtoClient, project_id: u64, + fs: Arc, cx: &mut ModelContext, ) -> Self { cx.subscribe(&buffer_store, Self::on_buffer_store_event) @@ -3108,7 +3128,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 { @@ -5138,6 +5158,7 @@ impl LspStore { pub(crate) async fn refresh_workspace_configurations( this: &WeakModel, + fs: Arc, mut cx: AsyncAppContext, ) { maybe!(async move { @@ -5190,7 +5211,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()?; @@ -5213,6 +5239,7 @@ impl LspStore { } } fn maintain_workspace_config( + fs: Arc, external_refresh_requests: watch::Receiver<()>, cx: &mut ModelContext, ) -> Task> { @@ -5227,7 +5254,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); @@ -8454,6 +8481,7 @@ impl LspAdapter for SshLspAdapter { async fn initialization_options( self: Arc, + _: &dyn Fs, _: &Arc, ) -> Result> { let Some(options) = &self.initialization_options else { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index d4f78757d80da808e2b97cdfcdd0cfea27dabb2f..fe40280806a3a9167003d15297a9d80431a7741c 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -810,6 +810,7 @@ impl Project { languages.clone(), ssh_proto.clone(), SSH_PROJECT_ID, + fs.clone(), cx, ) }); @@ -983,6 +984,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);