diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index aa07524ea9a86d3d2279e28df7a1aeab3103adcd..4832e5e602b7a06fbb6f6fe88870f48ff3b8a17c 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -28,7 +28,7 @@ use anyhow::{Context as _, Result}; use async_trait::async_trait; use collections::{HashMap, HashSet, IndexSet}; use futures::Future; -use gpui::{App, AsyncApp, Entity, SharedString, Task}; +use gpui::{App, AsyncApp, Entity, SharedString}; pub use highlight_map::HighlightMap; use http_client::HttpClient; pub use language_registry::{ @@ -45,7 +45,6 @@ use settings::WorktreeId; use smol::future::FutureExt as _; use std::num::NonZeroU32; use std::{ - any::Any, ffi::OsStr, fmt::Debug, hash::Hash, @@ -156,6 +155,8 @@ pub struct Location { pub range: Range, } +type ServerBinaryCache = futures::lock::Mutex>; + /// Represents a Language Server, with certain cached sync properties. /// Uses [`LspAdapter`] under the hood, but calls all 'static' methods /// once at startup, and caches the results. @@ -166,7 +167,7 @@ pub struct CachedLspAdapter { language_ids: HashMap, pub adapter: Arc, pub reinstall_attempt_count: AtomicU64, - cached_binary: futures::lock::Mutex>, + cached_binary: ServerBinaryCache, } impl Debug for CachedLspAdapter { @@ -216,10 +217,16 @@ impl CachedLspAdapter { binary_options: LanguageServerBinaryOptions, cx: &mut AsyncApp, ) -> Result { - let cached_binary = self.cached_binary.lock().await; + let mut cached_binary = self.cached_binary.lock().await; self.adapter .clone() - .get_language_server_command(delegate, toolchains, binary_options, cached_binary, cx) + .get_language_server_command( + delegate, + toolchains, + binary_options, + &mut cached_binary, + cx, + ) .await } @@ -306,128 +313,9 @@ pub trait LspAdapterDelegate: Send + Sync { } #[async_trait(?Send)] -pub trait LspAdapter: 'static + Send + Sync { +pub trait LspAdapter: 'static + Send + Sync + DynLspInstaller { fn name(&self) -> LanguageServerName; - fn get_language_server_command<'a>( - self: Arc, - delegate: Arc, - toolchains: Option, - binary_options: LanguageServerBinaryOptions, - mut cached_binary: futures::lock::MutexGuard<'a, Option>, - cx: &'a mut AsyncApp, - ) -> Pin>>> { - async move { - // First we check whether the adapter can give us a user-installed binary. - // If so, we do *not* want to cache that, because each worktree might give us a different - // binary: - // - // worktree 1: user-installed at `.bin/gopls` - // worktree 2: user-installed at `~/bin/gopls` - // worktree 3: no gopls found in PATH -> fallback to Zed installation - // - // We only want to cache when we fall back to the global one, - // because we don't want to download and overwrite our global one - // for each worktree we might have open. - if binary_options.allow_path_lookup - && let Some(binary) = self.check_if_user_installed(delegate.as_ref(), toolchains, cx).await { - log::debug!( - "found user-installed language server for {}. path: {:?}, arguments: {:?}", - self.name().0, - binary.path, - binary.arguments - ); - return Ok(binary); - } - - anyhow::ensure!(binary_options.allow_binary_download, "downloading language servers disabled"); - - if let Some(cached_binary) = cached_binary.as_ref() { - return Ok(cached_binary.clone()); - } - - let Some(container_dir) = delegate.language_server_download_dir(&self.name()).await else { - anyhow::bail!("no language server download dir defined") - }; - - let mut binary = try_fetch_server_binary(self.as_ref(), &delegate, container_dir.to_path_buf(), cx).await; - - if let Err(error) = binary.as_ref() { - if let Some(prev_downloaded_binary) = self - .cached_server_binary(container_dir.to_path_buf(), delegate.as_ref()) - .await - { - log::info!( - "failed to fetch newest version of language server {:?}. error: {:?}, falling back to using {:?}", - self.name(), - error, - prev_downloaded_binary.path - ); - binary = Ok(prev_downloaded_binary); - } else { - delegate.update_status( - self.name(), - BinaryStatus::Failed { - error: format!("{error:?}"), - }, - ); - } - } - - if let Ok(binary) = &binary { - *cached_binary = Some(binary.clone()); - } - - binary - } - .boxed_local() - } - - async fn check_if_user_installed( - &self, - _: &dyn LspAdapterDelegate, - _: Option, - _: &AsyncApp, - ) -> Option { - None - } - - async fn fetch_latest_server_version( - &self, - delegate: &dyn LspAdapterDelegate, - cx: &AsyncApp, - ) -> Result>; - - fn will_fetch_server( - &self, - _: &Arc, - _: &mut AsyncApp, - ) -> Option>> { - None - } - - async fn check_if_version_installed( - &self, - _version: &(dyn 'static + Send + Any), - _container_dir: &PathBuf, - _delegate: &dyn LspAdapterDelegate, - ) -> Option { - None - } - - async fn fetch_server_binary( - &self, - latest_version: Box, - container_dir: PathBuf, - delegate: &dyn LspAdapterDelegate, - ) -> Result; - - async fn cached_server_binary( - &self, - container_dir: PathBuf, - delegate: &dyn LspAdapterDelegate, - ) -> Option; - fn process_diagnostics( &self, _: &mut lsp::PublishDiagnosticsParams, @@ -592,40 +480,194 @@ pub trait LspAdapter: 'static + Send + Sync { } } -async fn try_fetch_server_binary( - adapter: &L, - delegate: &Arc, - container_dir: PathBuf, - cx: &mut AsyncApp, -) -> Result { - if let Some(task) = adapter.will_fetch_server(delegate, cx) { - task.await?; +pub trait LspInstaller { + type BinaryVersion; + fn check_if_user_installed( + &self, + _: &dyn LspAdapterDelegate, + _: Option, + _: &AsyncApp, + ) -> impl Future> { + async { None } + } + + fn fetch_latest_server_version( + &self, + delegate: &dyn LspAdapterDelegate, + pre_release: bool, + cx: &mut AsyncApp, + ) -> impl Future>; + + fn check_if_version_installed( + &self, + _version: &Self::BinaryVersion, + _container_dir: &PathBuf, + _delegate: &dyn LspAdapterDelegate, + ) -> impl Future> { + async { None } } - let name = adapter.name(); - log::debug!("fetching latest version of language server {:?}", name.0); - delegate.update_status(name.clone(), BinaryStatus::CheckingForUpdate); + fn fetch_server_binary( + &self, + latest_version: Self::BinaryVersion, + container_dir: PathBuf, + delegate: &dyn LspAdapterDelegate, + ) -> impl Future>; - let latest_version = adapter - .fetch_latest_server_version(delegate.as_ref(), cx) - .await?; + fn cached_server_binary( + &self, + container_dir: PathBuf, + delegate: &dyn LspAdapterDelegate, + ) -> impl Future>; +} - if let Some(binary) = adapter - .check_if_version_installed(latest_version.as_ref(), &container_dir, delegate.as_ref()) - .await - { - log::debug!("language server {:?} is already installed", name.0); - delegate.update_status(name.clone(), BinaryStatus::None); - Ok(binary) - } else { - log::info!("downloading language server {:?}", name.0); - delegate.update_status(adapter.name(), BinaryStatus::Downloading); - let binary = adapter - .fetch_server_binary(latest_version, container_dir, delegate.as_ref()) - .await; +#[async_trait(?Send)] +pub trait DynLspInstaller { + async fn try_fetch_server_binary( + &self, + delegate: &Arc, + container_dir: PathBuf, + pre_release: bool, + cx: &mut AsyncApp, + ) -> Result; + fn get_language_server_command<'a>( + self: Arc, + delegate: Arc, + toolchains: Option, + binary_options: LanguageServerBinaryOptions, + cached_binary: &'a mut Option<(bool, LanguageServerBinary)>, + cx: &'a mut AsyncApp, + ) -> Pin>>>; +} - delegate.update_status(name.clone(), BinaryStatus::None); - binary +#[async_trait(?Send)] +impl DynLspInstaller for LI +where + LI: LspInstaller + LspAdapter, +{ + async fn try_fetch_server_binary( + &self, + delegate: &Arc, + container_dir: PathBuf, + pre_release: bool, + cx: &mut AsyncApp, + ) -> Result { + let name = self.name(); + + log::debug!("fetching latest version of language server {:?}", name.0); + delegate.update_status(name.clone(), BinaryStatus::CheckingForUpdate); + + let latest_version = self + .fetch_latest_server_version(delegate.as_ref(), pre_release, cx) + .await?; + + if let Some(binary) = self + .check_if_version_installed(&latest_version, &container_dir, delegate.as_ref()) + .await + { + log::debug!("language server {:?} is already installed", name.0); + delegate.update_status(name.clone(), BinaryStatus::None); + Ok(binary) + } else { + log::debug!("downloading language server {:?}", name.0); + delegate.update_status(name.clone(), BinaryStatus::Downloading); + let binary = self + .fetch_server_binary(latest_version, container_dir, delegate.as_ref()) + .await; + + delegate.update_status(name.clone(), BinaryStatus::None); + binary + } + } + fn get_language_server_command<'a>( + self: Arc, + delegate: Arc, + toolchain: Option, + binary_options: LanguageServerBinaryOptions, + cached_binary: &'a mut Option<(bool, LanguageServerBinary)>, + cx: &'a mut AsyncApp, + ) -> Pin>>> { + async move { + // First we check whether the adapter can give us a user-installed binary. + // If so, we do *not* want to cache that, because each worktree might give us a different + // binary: + // + // worktree 1: user-installed at `.bin/gopls` + // worktree 2: user-installed at `~/bin/gopls` + // worktree 3: no gopls found in PATH -> fallback to Zed installation + // + // We only want to cache when we fall back to the global one, + // because we don't want to download and overwrite our global one + // for each worktree we might have open. + if binary_options.allow_path_lookup + && let Some(binary) = self + .check_if_user_installed(delegate.as_ref(), toolchain, cx) + .await + { + log::info!( + "found user-installed language server for {}. path: {:?}, arguments: {:?}", + self.name().0, + binary.path, + binary.arguments + ); + return Ok(binary); + } + + anyhow::ensure!( + binary_options.allow_binary_download, + "downloading language servers disabled" + ); + + if let Some((pre_release, cached_binary)) = cached_binary + && *pre_release == binary_options.pre_release + { + return Ok(cached_binary.clone()); + } + + let Some(container_dir) = delegate.language_server_download_dir(&self.name()).await + else { + anyhow::bail!("no language server download dir defined") + }; + + let mut binary = self + .try_fetch_server_binary( + &delegate, + container_dir.to_path_buf(), + binary_options.pre_release, + cx, + ) + .await; + + if let Err(error) = binary.as_ref() { + if let Some(prev_downloaded_binary) = self + .cached_server_binary(container_dir.to_path_buf(), delegate.as_ref()) + .await + { + log::info!( + "failed to fetch newest version of language server {:?}. \ + error: {:?}, falling back to using {:?}", + self.name(), + error, + prev_downloaded_binary.path + ); + binary = Ok(prev_downloaded_binary); + } else { + delegate.update_status( + self.name(), + BinaryStatus::Failed { + error: format!("{error:?}"), + }, + ); + } + } + + if let Ok(binary) = &binary { + *cached_binary = Some((binary_options.pre_release, binary.clone())); + } + + binary + } + .boxed_local() } } @@ -2195,10 +2237,16 @@ impl Default for FakeLspAdapter { } #[cfg(any(test, feature = "test-support"))] -#[async_trait(?Send)] -impl LspAdapter for FakeLspAdapter { - fn name(&self) -> LanguageServerName { - LanguageServerName(self.name.into()) +impl LspInstaller for FakeLspAdapter { + type BinaryVersion = (); + + async fn fetch_latest_server_version( + &self, + _: &dyn LspAdapterDelegate, + _: bool, + _: &mut AsyncApp, + ) -> Result { + unreachable!() } async fn check_if_user_installed( @@ -2210,28 +2258,9 @@ impl LspAdapter for FakeLspAdapter { Some(self.language_server_binary.clone()) } - fn get_language_server_command<'a>( - self: Arc, - _: Arc, - _: Option, - _: LanguageServerBinaryOptions, - _: futures::lock::MutexGuard<'a, Option>, - _: &'a mut AsyncApp, - ) -> Pin>>> { - async move { Ok(self.language_server_binary.clone()) }.boxed_local() - } - - async fn fetch_latest_server_version( - &self, - _: &dyn LspAdapterDelegate, - _: &AsyncApp, - ) -> Result> { - unreachable!(); - } - async fn fetch_server_binary( &self, - _: Box, + _: (), _: PathBuf, _: &dyn LspAdapterDelegate, ) -> Result { @@ -2245,6 +2274,14 @@ impl LspAdapter for FakeLspAdapter { ) -> Option { unreachable!(); } +} + +#[cfg(any(test, feature = "test-support"))] +#[async_trait(?Send)] +impl LspAdapter for FakeLspAdapter { + fn name(&self) -> LanguageServerName { + LanguageServerName(self.name.into()) + } fn disk_based_diagnostic_sources(&self) -> Vec { self.disk_based_diagnostics_sources.clone() diff --git a/crates/language_extension/src/extension_lsp_adapter.rs b/crates/language_extension/src/extension_lsp_adapter.rs index 2c7cc9b7b1c73abf8daaceeefd53963c8ddf735b..dccd33ffb6b90381d7d86c38e80ec95effd4daf7 100644 --- a/crates/language_extension/src/extension_lsp_adapter.rs +++ b/crates/language_extension/src/extension_lsp_adapter.rs @@ -1,4 +1,3 @@ -use std::any::Any; use std::ops::Range; use std::path::PathBuf; use std::pin::Pin; @@ -11,8 +10,8 @@ use extension::{Extension, ExtensionLanguageServerProxy, WorktreeDelegate}; use futures::{Future, FutureExt, future::join_all}; use gpui::{App, AppContext, AsyncApp, Task}; use language::{ - BinaryStatus, CodeLabel, HighlightId, Language, LanguageName, LspAdapter, LspAdapterDelegate, - Toolchain, + BinaryStatus, CodeLabel, DynLspInstaller, HighlightId, Language, LanguageName, LspAdapter, + LspAdapterDelegate, Toolchain, }; use lsp::{ CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerName, @@ -155,17 +154,13 @@ impl ExtensionLspAdapter { } #[async_trait(?Send)] -impl LspAdapter for ExtensionLspAdapter { - fn name(&self) -> LanguageServerName { - self.language_server_id.clone() - } - +impl DynLspInstaller for ExtensionLspAdapter { fn get_language_server_command<'a>( self: Arc, delegate: Arc, _: Option, _: LanguageServerBinaryOptions, - _: futures::lock::MutexGuard<'a, Option>, + _: &'a mut Option<(bool, LanguageServerBinary)>, _: &'a mut AsyncApp, ) -> Pin>>> { async move { @@ -205,29 +200,21 @@ impl LspAdapter for ExtensionLspAdapter { .boxed_local() } - async fn fetch_latest_server_version( - &self, - _: &dyn LspAdapterDelegate, - _: &AsyncApp, - ) -> Result> { - unreachable!("get_language_server_command is overridden") - } - - async fn fetch_server_binary( + async fn try_fetch_server_binary( &self, - _: Box, + _: &Arc, _: PathBuf, - _: &dyn LspAdapterDelegate, + _: bool, + _: &mut AsyncApp, ) -> Result { unreachable!("get_language_server_command is overridden") } +} - async fn cached_server_binary( - &self, - _: PathBuf, - _: &dyn LspAdapterDelegate, - ) -> Option { - unreachable!("get_language_server_command is overridden") +#[async_trait(?Send)] +impl LspAdapter for ExtensionLspAdapter { + fn name(&self) -> LanguageServerName { + self.language_server_id.clone() } fn code_action_kinds(&self) -> Option> { diff --git a/crates/languages/src/c.rs b/crates/languages/src/c.rs index afdf49e66e59b78c82f234160a9c4bc1efa83574..1e3a1f805885e850884c2e5f5ec18ca71801301f 100644 --- a/crates/languages/src/c.rs +++ b/crates/languages/src/c.rs @@ -5,11 +5,10 @@ use gpui::{App, AsyncApp}; use http_client::github::{AssetKind, GitHubLspBinaryVersion, latest_github_release}; pub use language::*; use lsp::{InitializeParams, LanguageServerBinary, LanguageServerName}; -use project::{lsp_store::clangd_ext, project_settings::ProjectSettings}; +use project::lsp_store::clangd_ext; use serde_json::json; -use settings::Settings as _; use smol::fs; -use std::{any::Any, env::consts, path::PathBuf, sync::Arc}; +use std::{env::consts, path::PathBuf, sync::Arc}; use util::{ResultExt, fs::remove_matching, maybe, merge_json_value_into}; use crate::github_download::{GithubBinaryMetadata, download_server_binary}; @@ -20,42 +19,18 @@ impl CLspAdapter { const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("clangd"); } -#[async_trait(?Send)] -impl super::LspAdapter for CLspAdapter { - fn name(&self) -> LanguageServerName { - Self::SERVER_NAME - } - - async fn check_if_user_installed( - &self, - delegate: &dyn LspAdapterDelegate, - _: Option, - _: &AsyncApp, - ) -> Option { - let path = delegate.which(Self::SERVER_NAME.as_ref()).await?; - Some(LanguageServerBinary { - path, - arguments: Vec::new(), - env: None, - }) - } +impl LspInstaller for CLspAdapter { + type BinaryVersion = GitHubLspBinaryVersion; async fn fetch_latest_server_version( &self, delegate: &dyn LspAdapterDelegate, - cx: &AsyncApp, - ) -> Result> { - let release = latest_github_release( - "clangd/clangd", - true, - ProjectSettings::try_read_global(cx, |s| { - s.lsp.get(&Self::SERVER_NAME)?.fetch.as_ref()?.pre_release - }) - .flatten() - .unwrap_or(false), - delegate.http_client(), - ) - .await?; + pre_release: bool, + _: &mut AsyncApp, + ) -> Result { + let release = + latest_github_release("clangd/clangd", true, pre_release, delegate.http_client()) + .await?; let os_suffix = match consts::OS { "macos" => "mac", "linux" => "linux", @@ -73,12 +48,26 @@ impl super::LspAdapter for CLspAdapter { url: asset.browser_download_url.clone(), digest: asset.digest.clone(), }; - Ok(Box::new(version) as Box<_>) + Ok(version) + } + + async fn check_if_user_installed( + &self, + delegate: &dyn LspAdapterDelegate, + _: Option, + _: &AsyncApp, + ) -> Option { + let path = delegate.which(Self::SERVER_NAME.as_ref()).await?; + Some(LanguageServerBinary { + path, + arguments: Vec::new(), + env: None, + }) } async fn fetch_server_binary( &self, - version: Box, + version: GitHubLspBinaryVersion, container_dir: PathBuf, delegate: &dyn LspAdapterDelegate, ) -> Result { @@ -86,7 +75,7 @@ impl super::LspAdapter for CLspAdapter { name, url, digest: expected_digest, - } = *version.downcast::().unwrap(); + } = version; let version_dir = container_dir.join(format!("clangd_{name}")); let binary_path = version_dir.join("bin/clangd"); @@ -157,6 +146,13 @@ impl super::LspAdapter for CLspAdapter { ) -> Option { get_cached_server_binary(container_dir).await } +} + +#[async_trait(?Send)] +impl super::LspAdapter for CLspAdapter { + fn name(&self) -> LanguageServerName { + Self::SERVER_NAME + } async fn label_for_completion( &self, diff --git a/crates/languages/src/css.rs b/crates/languages/src/css.rs index 37955b31c5d111577a3ab36951df9edb09187aac..035a2c693dbbdceed38adc8ccc0510274205670f 100644 --- a/crates/languages/src/css.rs +++ b/crates/languages/src/css.rs @@ -2,14 +2,13 @@ use anyhow::{Context as _, Result}; use async_trait::async_trait; use futures::StreamExt; use gpui::AsyncApp; -use language::{LspAdapter, LspAdapterDelegate, Toolchain}; +use language::{LspAdapter, LspAdapterDelegate, LspInstaller, Toolchain}; use lsp::{LanguageServerBinary, LanguageServerName}; use node_runtime::{NodeRuntime, VersionStrategy}; use project::lsp_store::language_server_settings; use serde_json::json; use smol::fs; use std::{ - any::Any, ffi::OsString, path::{Path, PathBuf}, sync::Arc, @@ -34,10 +33,18 @@ impl CssLspAdapter { } } -#[async_trait(?Send)] -impl LspAdapter for CssLspAdapter { - fn name(&self) -> LanguageServerName { - LanguageServerName("vscode-css-language-server".into()) +impl LspInstaller for CssLspAdapter { + type BinaryVersion = String; + + async fn fetch_latest_server_version( + &self, + _: &dyn LspAdapterDelegate, + _: bool, + _: &mut AsyncApp, + ) -> Result { + self.node + .npm_package_latest_version("vscode-langservers-extracted") + .await } async fn check_if_user_installed( @@ -58,25 +65,12 @@ impl LspAdapter for CssLspAdapter { }) } - async fn fetch_latest_server_version( - &self, - _: &dyn LspAdapterDelegate, - _: &AsyncApp, - ) -> Result> { - Ok(Box::new( - self.node - .npm_package_latest_version("vscode-langservers-extracted") - .await?, - ) as Box<_>) - } - async fn fetch_server_binary( &self, - latest_version: Box, + latest_version: String, container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Result { - let latest_version = latest_version.downcast::().unwrap(); let server_path = container_dir.join(SERVER_PATH); self.node @@ -95,11 +89,10 @@ impl LspAdapter for CssLspAdapter { async fn check_if_version_installed( &self, - version: &(dyn 'static + Send + Any), + version: &String, container_dir: &PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { - let version = version.downcast_ref::().unwrap(); let server_path = container_dir.join(SERVER_PATH); let should_install_language_server = self @@ -130,6 +123,13 @@ impl LspAdapter for CssLspAdapter { ) -> Option { get_cached_server_binary(container_dir, &self.node).await } +} + +#[async_trait(?Send)] +impl LspAdapter for CssLspAdapter { + fn name(&self) -> LanguageServerName { + LanguageServerName("vscode-css-language-server".into()) + } async fn initialization_options( self: Arc, diff --git a/crates/languages/src/go.rs b/crates/languages/src/go.rs index ff98f7bc8689a197a9eca2f1ec8ba068ac7a4984..60c6d98d21f9b3273f3afdaeb2cbe544af15efe9 100644 --- a/crates/languages/src/go.rs +++ b/crates/languages/src/go.rs @@ -5,17 +5,17 @@ use futures::StreamExt; use gpui::{App, AsyncApp, Task}; use http_client::github::latest_github_release; pub use language::*; +use language::{LanguageToolchainStore, LspAdapterDelegate, LspInstaller}; use lsp::{LanguageServerBinary, LanguageServerName}; use regex::Regex; use serde_json::json; use smol::fs; use std::{ - any::Any, borrow::Cow, ffi::{OsStr, OsString}, ops::Range, - path::PathBuf, + path::{Path, PathBuf}, process::Output, str, sync::{ @@ -50,17 +50,32 @@ const BINARY: &str = if cfg!(target_os = "windows") { "gopls" }; -#[async_trait(?Send)] -impl super::LspAdapter for GoLspAdapter { - fn name(&self) -> LanguageServerName { - Self::SERVER_NAME - } +impl LspInstaller for GoLspAdapter { + type BinaryVersion = Option; async fn fetch_latest_server_version( &self, delegate: &dyn LspAdapterDelegate, - _: &AsyncApp, - ) -> Result> { + _: bool, + cx: &mut AsyncApp, + ) -> Result> { + static DID_SHOW_NOTIFICATION: AtomicBool = AtomicBool::new(false); + + const NOTIFICATION_MESSAGE: &str = + "Could not install the Go language server `gopls`, because `go` was not found."; + + if delegate.which("go".as_ref()).await.is_none() { + if DID_SHOW_NOTIFICATION + .compare_exchange(false, true, SeqCst, SeqCst) + .is_ok() + { + cx.update(|cx| { + delegate.show_notification(NOTIFICATION_MESSAGE, cx); + })? + } + anyhow::bail!("cannot install gopls"); + } + let release = latest_github_release("golang/tools", false, false, delegate.http_client()).await?; let version: Option = release.tag_name.strip_prefix("gopls/v").map(str::to_string); @@ -70,7 +85,7 @@ impl super::LspAdapter for GoLspAdapter { release.tag_name ); } - Ok(Box::new(version) as Box<_>) + Ok(version) } async fn check_if_user_installed( @@ -87,36 +102,9 @@ impl super::LspAdapter for GoLspAdapter { }) } - fn will_fetch_server( - &self, - delegate: &Arc, - cx: &mut AsyncApp, - ) -> Option>> { - static DID_SHOW_NOTIFICATION: AtomicBool = AtomicBool::new(false); - - const NOTIFICATION_MESSAGE: &str = - "Could not install the Go language server `gopls`, because `go` was not found."; - - let delegate = delegate.clone(); - Some(cx.spawn(async move |cx| { - if delegate.which("go".as_ref()).await.is_none() { - if DID_SHOW_NOTIFICATION - .compare_exchange(false, true, SeqCst, SeqCst) - .is_ok() - { - cx.update(|cx| { - delegate.show_notification(NOTIFICATION_MESSAGE, cx); - })? - } - anyhow::bail!("cannot install gopls"); - } - Ok(()) - })) - } - async fn fetch_server_binary( &self, - version: Box, + version: Option, container_dir: PathBuf, delegate: &dyn LspAdapterDelegate, ) -> Result { @@ -127,10 +115,8 @@ impl super::LspAdapter for GoLspAdapter { .await .context("failed to get go version via `go version` command`")?; let go_version = parse_version_output(&go_version_output)?; - let version = version.downcast::>().unwrap(); - let this = *self; - if let Some(version) = *version { + if let Some(version) = version { let binary_path = container_dir.join(format!("gopls_{version}_go_{go_version}")); if let Ok(metadata) = fs::metadata(&binary_path).await && metadata.is_file() @@ -146,10 +132,7 @@ impl super::LspAdapter for GoLspAdapter { env: None, }); } - } else if let Some(path) = this - .cached_server_binary(container_dir.clone(), delegate) - .await - { + } else if let Some(path) = get_cached_server_binary(&container_dir).await { return Ok(path); } @@ -195,7 +178,14 @@ impl super::LspAdapter for GoLspAdapter { container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { - get_cached_server_binary(container_dir).await + get_cached_server_binary(&container_dir).await + } +} + +#[async_trait(?Send)] +impl LspAdapter for GoLspAdapter { + fn name(&self) -> LanguageServerName { + Self::SERVER_NAME } async fn initialization_options( @@ -442,10 +432,10 @@ fn parse_version_output(output: &Output) -> Result<&str> { Ok(version) } -async fn get_cached_server_binary(container_dir: PathBuf) -> Option { +async fn get_cached_server_binary(container_dir: &Path) -> Option { maybe!(async { let mut last_binary_path = None; - let mut entries = fs::read_dir(&container_dir).await?; + let mut entries = fs::read_dir(container_dir).await?; while let Some(entry) = entries.next().await { let entry = entry?; if entry.file_type().await?.is_file() diff --git a/crates/languages/src/json.rs b/crates/languages/src/json.rs index 0d5d21de0b2e497f662551d45fd2a7c809b84291..df4a0d114611a2bab7a49417bb0a0bac79eaf734 100644 --- a/crates/languages/src/json.rs +++ b/crates/languages/src/json.rs @@ -9,7 +9,7 @@ use gpui::{App, AsyncApp, Task}; use http_client::github::{GitHubLspBinaryVersion, latest_github_release}; use language::{ ContextProvider, LanguageName, LanguageRegistry, LocalFile as _, LspAdapter, - LspAdapterDelegate, Toolchain, + LspAdapterDelegate, LspInstaller, Toolchain, }; use lsp::{LanguageServerBinary, LanguageServerName}; use node_runtime::{NodeRuntime, VersionStrategy}; @@ -22,7 +22,6 @@ use smol::{ lock::RwLock, }; use std::{ - any::Any, env::consts, ffi::OsString, path::{Path, PathBuf}, @@ -293,10 +292,18 @@ fn generate_inspector_style_schema() -> serde_json_lenient::Value { serde_json_lenient::to_value(schema).unwrap() } -#[async_trait(?Send)] -impl LspAdapter for JsonLspAdapter { - fn name(&self) -> LanguageServerName { - LanguageServerName("json-language-server".into()) +impl LspInstaller for JsonLspAdapter { + type BinaryVersion = String; + + async fn fetch_latest_server_version( + &self, + _: &dyn LspAdapterDelegate, + _: bool, + _: &mut AsyncApp, + ) -> Result { + self.node + .npm_package_latest_version(Self::PACKAGE_NAME) + .await } async fn check_if_user_installed( @@ -317,25 +324,12 @@ impl LspAdapter for JsonLspAdapter { }) } - async fn fetch_latest_server_version( - &self, - _: &dyn LspAdapterDelegate, - _: &AsyncApp, - ) -> Result> { - Ok(Box::new( - self.node - .npm_package_latest_version(Self::PACKAGE_NAME) - .await?, - ) as Box<_>) - } - async fn check_if_version_installed( &self, - version: &(dyn 'static + Send + Any), + version: &String, container_dir: &PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { - let version = version.downcast_ref::().unwrap(); let server_path = container_dir.join(SERVER_PATH); let should_install_language_server = self @@ -361,11 +355,10 @@ impl LspAdapter for JsonLspAdapter { async fn fetch_server_binary( &self, - latest_version: Box, + latest_version: String, container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Result { - let latest_version = latest_version.downcast::().unwrap(); let server_path = container_dir.join(SERVER_PATH); self.node @@ -389,6 +382,13 @@ impl LspAdapter for JsonLspAdapter { ) -> Option { get_cached_server_binary(container_dir, &self.node).await } +} + +#[async_trait(?Send)] +impl LspAdapter for JsonLspAdapter { + fn name(&self) -> LanguageServerName { + LanguageServerName("json-language-server".into()) + } async fn initialization_options( self: Arc, @@ -483,17 +483,15 @@ impl NodeVersionAdapter { LanguageServerName::new_static("package-version-server"); } -#[async_trait(?Send)] -impl LspAdapter for NodeVersionAdapter { - fn name(&self) -> LanguageServerName { - Self::SERVER_NAME - } +impl LspInstaller for NodeVersionAdapter { + type BinaryVersion = GitHubLspBinaryVersion; async fn fetch_latest_server_version( &self, delegate: &dyn LspAdapterDelegate, - _: &AsyncApp, - ) -> Result> { + _: bool, + _: &mut AsyncApp, + ) -> Result { let release = latest_github_release( "zed-industries/package-version-server", true, @@ -518,11 +516,11 @@ impl LspAdapter for NodeVersionAdapter { .iter() .find(|asset| asset.name == asset_name) .with_context(|| format!("no asset found matching `{asset_name:?}`"))?; - Ok(Box::new(GitHubLspBinaryVersion { + Ok(GitHubLspBinaryVersion { name: release.tag_name, url: asset.browser_download_url.clone(), digest: asset.digest.clone(), - })) + }) } async fn check_if_user_installed( @@ -541,11 +539,11 @@ impl LspAdapter for NodeVersionAdapter { async fn fetch_server_binary( &self, - latest_version: Box, + latest_version: GitHubLspBinaryVersion, container_dir: PathBuf, delegate: &dyn LspAdapterDelegate, ) -> Result { - let version = latest_version.downcast::().unwrap(); + let version = &latest_version; let destination_path = container_dir.join(format!( "{}-{}{}", Self::SERVER_NAME, @@ -595,6 +593,13 @@ impl LspAdapter for NodeVersionAdapter { } } +#[async_trait(?Send)] +impl LspAdapter for NodeVersionAdapter { + fn name(&self) -> LanguageServerName { + Self::SERVER_NAME + } +} + async fn get_cached_version_server_binary(container_dir: PathBuf) -> Option { maybe!(async { let mut last = None; diff --git a/crates/languages/src/python.rs b/crates/languages/src/python.rs index 71d847d9f2fc44aeb52611b92b774b49dc615293..eaf536d8b277e0b404d240c9505756db320868ea 100644 --- a/crates/languages/src/python.rs +++ b/crates/languages/src/python.rs @@ -6,7 +6,7 @@ use futures::{AsyncBufReadExt, StreamExt as _}; use gpui::{App, AsyncApp, SharedString, Task}; use http_client::github::{AssetKind, GitHubLspBinaryVersion, latest_github_release}; use language::language_settings::language_settings; -use language::{ContextLocation, LanguageToolchainStore}; +use language::{ContextLocation, LanguageToolchainStore, LspInstaller}; use language::{ContextProvider, LspAdapter, LspAdapterDelegate}; use language::{LanguageName, ManifestName, ManifestProvider, ManifestQuery}; use language::{Toolchain, ToolchainList, ToolchainLister, ToolchainMetadata}; @@ -27,7 +27,6 @@ use util::fs::{make_file_executable, remove_matching}; use parking_lot::Mutex; use std::str::FromStr; use std::{ - any::Any, borrow::Cow, ffi::OsString, fmt::Write, @@ -159,11 +158,44 @@ impl LspAdapter for TyLspAdapter { Self::SERVER_NAME } + async fn workspace_configuration( + self: Arc, + _: &Arc, + toolchain: Option, + _cx: &mut AsyncApp, + ) -> Result { + let mut ret = json!({}); + if let Some(toolchain) = toolchain.and_then(|toolchain| { + serde_json::from_value::(toolchain.as_json).ok() + }) { + _ = maybe!({ + let uri = url::Url::from_file_path(toolchain.executable?).ok()?; + let sys_prefix = toolchain.prefix.clone()?; + let environment = json!({ + "executable": { + "uri": uri, + "sysPrefix": sys_prefix + } + }); + ret.as_object_mut()?.insert( + "pythonExtension".into(), + json!({ "activeEnvironment": environment }), + ); + Some(()) + }); + } + Ok(json!({"ty": ret})) + } +} + +impl LspInstaller for TyLspAdapter { + type BinaryVersion = GitHubLspBinaryVersion; async fn fetch_latest_server_version( &self, delegate: &dyn LspAdapterDelegate, - _: &AsyncApp, - ) -> Result> { + _: bool, + _: &mut AsyncApp, + ) -> Result { let release = latest_github_release("astral-sh/ty", true, true, delegate.http_client()).await?; let (_, asset_name) = Self::build_asset_name()?; @@ -172,16 +204,16 @@ impl LspAdapter for TyLspAdapter { .into_iter() .find(|asset| asset.name == asset_name) .with_context(|| format!("no asset found matching `{asset_name:?}`"))?; - Ok(Box::new(GitHubLspBinaryVersion { + Ok(GitHubLspBinaryVersion { name: release.tag_name, url: asset.browser_download_url, digest: asset.digest, - })) + }) } async fn fetch_server_binary( &self, - latest_version: Box, + latest_version: Self::BinaryVersion, container_dir: PathBuf, delegate: &dyn LspAdapterDelegate, ) -> Result { @@ -189,7 +221,7 @@ impl LspAdapter for TyLspAdapter { name, url, digest: expected_digest, - } = *latest_version.downcast::().unwrap(); + } = latest_version; let destination_path = container_dir.join(format!("ty-{name}")); let server_path = match Self::GITHUB_ASSET_KIND { AssetKind::TarGz | AssetKind::Gz => destination_path.clone(), // Tar and gzip extract in place. @@ -293,35 +325,6 @@ impl LspAdapter for TyLspAdapter { .await .log_err() } - - async fn workspace_configuration( - self: Arc, - _: &Arc, - toolchain: Option, - _cx: &mut AsyncApp, - ) -> Result { - let mut ret = json!({}); - if let Some(toolchain) = toolchain.and_then(|toolchain| { - serde_json::from_value::(toolchain.as_json).ok() - }) { - _ = maybe!({ - let uri = url::Url::from_file_path(toolchain.executable?).ok()?; - let sys_prefix = toolchain.prefix.clone()?; - let environment = json!({ - "executable": { - "uri": uri, - "sysPrefix": sys_prefix - } - }); - ret.as_object_mut()?.insert( - "pythonExtension".into(), - json!({ "activeEnvironment": environment }), - ); - Some(()) - }); - } - Ok(json!({"ty": ret})) - } } pub struct PyrightLspAdapter { @@ -359,114 +362,6 @@ impl LspAdapter for PyrightLspAdapter { }))) } - async fn check_if_user_installed( - &self, - delegate: &dyn LspAdapterDelegate, - _: Option, - _: &AsyncApp, - ) -> Option { - if let Some(pyright_bin) = delegate.which("pyright-langserver".as_ref()).await { - let env = delegate.shell_env().await; - Some(LanguageServerBinary { - path: pyright_bin, - env: Some(env), - arguments: vec!["--stdio".into()], - }) - } else { - let node = delegate.which("node".as_ref()).await?; - let (node_modules_path, _) = delegate - .npm_package_installed_version(Self::SERVER_NAME.as_ref()) - .await - .log_err()??; - - let path = node_modules_path.join(NODE_MODULE_RELATIVE_SERVER_PATH); - - let env = delegate.shell_env().await; - Some(LanguageServerBinary { - path: node, - env: Some(env), - arguments: server_binary_arguments(&path), - }) - } - } - - async fn fetch_latest_server_version( - &self, - _: &dyn LspAdapterDelegate, - _: &AsyncApp, - ) -> Result> { - Ok(Box::new( - self.node - .npm_package_latest_version(Self::SERVER_NAME.as_ref()) - .await?, - ) as Box<_>) - } - - async fn fetch_server_binary( - &self, - latest_version: Box, - container_dir: PathBuf, - delegate: &dyn LspAdapterDelegate, - ) -> Result { - let latest_version = latest_version.downcast::().unwrap(); - let server_path = container_dir.join(SERVER_PATH); - - self.node - .npm_install_packages( - &container_dir, - &[(Self::SERVER_NAME.as_ref(), latest_version.as_str())], - ) - .await?; - - let env = delegate.shell_env().await; - Ok(LanguageServerBinary { - path: self.node.binary_path().await?, - env: Some(env), - arguments: server_binary_arguments(&server_path), - }) - } - - async fn check_if_version_installed( - &self, - version: &(dyn 'static + Send + Any), - container_dir: &PathBuf, - delegate: &dyn LspAdapterDelegate, - ) -> Option { - let version = version.downcast_ref::().unwrap(); - let server_path = container_dir.join(SERVER_PATH); - - let should_install_language_server = self - .node - .should_install_npm_package( - Self::SERVER_NAME.as_ref(), - &server_path, - container_dir, - VersionStrategy::Latest(version), - ) - .await; - - if should_install_language_server { - None - } else { - let env = delegate.shell_env().await; - Some(LanguageServerBinary { - path: self.node.binary_path().await.ok()?, - env: Some(env), - arguments: server_binary_arguments(&server_path), - }) - } - } - - async fn cached_server_binary( - &self, - container_dir: PathBuf, - delegate: &dyn LspAdapterDelegate, - ) -> Option { - let mut binary = get_cached_server_binary(container_dir, &self.node).await?; - binary.env = Some(delegate.shell_env().await); - Some(binary) - } - async fn process_completions(&self, items: &mut [lsp::CompletionItem]) { process_pyright_completions(items); } @@ -616,6 +511,115 @@ impl LspAdapter for PyrightLspAdapter { } } +impl LspInstaller for PyrightLspAdapter { + type BinaryVersion = String; + + async fn fetch_latest_server_version( + &self, + _: &dyn LspAdapterDelegate, + _: bool, + _: &mut AsyncApp, + ) -> Result { + self.node + .npm_package_latest_version(Self::SERVER_NAME.as_ref()) + .await + } + + async fn check_if_user_installed( + &self, + delegate: &dyn LspAdapterDelegate, + _: Option, + _: &AsyncApp, + ) -> Option { + if let Some(pyright_bin) = delegate.which("pyright-langserver".as_ref()).await { + let env = delegate.shell_env().await; + Some(LanguageServerBinary { + path: pyright_bin, + env: Some(env), + arguments: vec!["--stdio".into()], + }) + } else { + let node = delegate.which("node".as_ref()).await?; + let (node_modules_path, _) = delegate + .npm_package_installed_version(Self::SERVER_NAME.as_ref()) + .await + .log_err()??; + + let path = node_modules_path.join(NODE_MODULE_RELATIVE_SERVER_PATH); + + let env = delegate.shell_env().await; + Some(LanguageServerBinary { + path: node, + env: Some(env), + arguments: server_binary_arguments(&path), + }) + } + } + + async fn fetch_server_binary( + &self, + latest_version: Self::BinaryVersion, + container_dir: PathBuf, + delegate: &dyn LspAdapterDelegate, + ) -> Result { + let server_path = container_dir.join(SERVER_PATH); + + self.node + .npm_install_packages( + &container_dir, + &[(Self::SERVER_NAME.as_ref(), latest_version.as_str())], + ) + .await?; + + let env = delegate.shell_env().await; + Ok(LanguageServerBinary { + path: self.node.binary_path().await?, + env: Some(env), + arguments: server_binary_arguments(&server_path), + }) + } + + async fn check_if_version_installed( + &self, + version: &Self::BinaryVersion, + container_dir: &PathBuf, + delegate: &dyn LspAdapterDelegate, + ) -> Option { + let server_path = container_dir.join(SERVER_PATH); + + let should_install_language_server = self + .node + .should_install_npm_package( + Self::SERVER_NAME.as_ref(), + &server_path, + container_dir, + VersionStrategy::Latest(version), + ) + .await; + + if should_install_language_server { + None + } else { + let env = delegate.shell_env().await; + Some(LanguageServerBinary { + path: self.node.binary_path().await.ok()?, + env: Some(env), + arguments: server_binary_arguments(&server_path), + }) + } + } + + async fn cached_server_binary( + &self, + container_dir: PathBuf, + delegate: &dyn LspAdapterDelegate, + ) -> Option { + let mut binary = get_cached_server_binary(container_dir, &self.node).await?; + binary.env = Some(delegate.shell_env().await); + Some(binary) + } +} + async fn get_cached_server_binary( container_dir: PathBuf, node: &NodeRuntime, @@ -1324,108 +1328,13 @@ impl PyLspAdapter { const BINARY_DIR: &str = if cfg!(target_os = "windows") { "Scripts" } else { - "bin" -}; - -#[async_trait(?Send)] -impl LspAdapter for PyLspAdapter { - fn name(&self) -> LanguageServerName { - Self::SERVER_NAME - } - - async fn check_if_user_installed( - &self, - delegate: &dyn LspAdapterDelegate, - toolchain: Option, - _: &AsyncApp, - ) -> Option { - if let Some(pylsp_bin) = delegate.which(Self::SERVER_NAME.as_ref()).await { - let env = delegate.shell_env().await; - Some(LanguageServerBinary { - path: pylsp_bin, - env: Some(env), - arguments: vec![], - }) - } else { - let toolchain = toolchain?; - let pylsp_path = Path::new(toolchain.path.as_ref()).parent()?.join("pylsp"); - pylsp_path.exists().then(|| LanguageServerBinary { - path: toolchain.path.to_string().into(), - arguments: vec![pylsp_path.into()], - env: None, - }) - } - } - - async fn fetch_latest_server_version( - &self, - _: &dyn LspAdapterDelegate, - _: &AsyncApp, - ) -> Result> { - Ok(Box::new(()) as Box<_>) - } - - async fn fetch_server_binary( - &self, - _: Box, - _: PathBuf, - delegate: &dyn LspAdapterDelegate, - ) -> Result { - let venv = self.base_venv(delegate).await.map_err(|e| anyhow!(e))?; - let pip_path = venv.join(BINARY_DIR).join("pip3"); - ensure!( - util::command::new_smol_command(pip_path.as_path()) - .arg("install") - .arg("python-lsp-server") - .arg("-U") - .output() - .await? - .status - .success(), - "python-lsp-server installation failed" - ); - ensure!( - util::command::new_smol_command(pip_path.as_path()) - .arg("install") - .arg("python-lsp-server[all]") - .arg("-U") - .output() - .await? - .status - .success(), - "python-lsp-server[all] installation failed" - ); - ensure!( - util::command::new_smol_command(pip_path) - .arg("install") - .arg("pylsp-mypy") - .arg("-U") - .output() - .await? - .status - .success(), - "pylsp-mypy installation failed" - ); - let pylsp = venv.join(BINARY_DIR).join("pylsp"); - Ok(LanguageServerBinary { - path: pylsp, - env: None, - arguments: vec![], - }) - } + "bin" +}; - async fn cached_server_binary( - &self, - _: PathBuf, - delegate: &dyn LspAdapterDelegate, - ) -> Option { - let venv = self.base_venv(delegate).await.ok()?; - let pylsp = venv.join(BINARY_DIR).join("pylsp"); - Some(LanguageServerBinary { - path: pylsp, - env: None, - arguments: vec![], - }) +#[async_trait(?Send)] +impl LspAdapter for PyLspAdapter { + fn name(&self) -> LanguageServerName { + Self::SERVER_NAME } async fn process_completions(&self, _items: &mut [lsp::CompletionItem]) {} @@ -1559,6 +1468,105 @@ impl LspAdapter for PyLspAdapter { } } +impl LspInstaller for PyLspAdapter { + type BinaryVersion = (); + async fn check_if_user_installed( + &self, + delegate: &dyn LspAdapterDelegate, + toolchain: Option, + _: &AsyncApp, + ) -> Option { + if let Some(pylsp_bin) = delegate.which(Self::SERVER_NAME.as_ref()).await { + let env = delegate.shell_env().await; + Some(LanguageServerBinary { + path: pylsp_bin, + env: Some(env), + arguments: vec![], + }) + } else { + let toolchain = toolchain?; + let pylsp_path = Path::new(toolchain.path.as_ref()).parent()?.join("pylsp"); + pylsp_path.exists().then(|| LanguageServerBinary { + path: toolchain.path.to_string().into(), + arguments: vec![pylsp_path.into()], + env: None, + }) + } + } + + async fn fetch_latest_server_version( + &self, + _: &dyn LspAdapterDelegate, + _: bool, + _: &mut AsyncApp, + ) -> Result<()> { + Ok(()) + } + + async fn fetch_server_binary( + &self, + _: (), + _: PathBuf, + delegate: &dyn LspAdapterDelegate, + ) -> Result { + let venv = self.base_venv(delegate).await.map_err(|e| anyhow!(e))?; + let pip_path = venv.join(BINARY_DIR).join("pip3"); + ensure!( + util::command::new_smol_command(pip_path.as_path()) + .arg("install") + .arg("python-lsp-server") + .arg("-U") + .output() + .await? + .status + .success(), + "python-lsp-server installation failed" + ); + ensure!( + util::command::new_smol_command(pip_path.as_path()) + .arg("install") + .arg("python-lsp-server[all]") + .arg("-U") + .output() + .await? + .status + .success(), + "python-lsp-server[all] installation failed" + ); + ensure!( + util::command::new_smol_command(pip_path) + .arg("install") + .arg("pylsp-mypy") + .arg("-U") + .output() + .await? + .status + .success(), + "pylsp-mypy installation failed" + ); + let pylsp = venv.join(BINARY_DIR).join("pylsp"); + Ok(LanguageServerBinary { + path: pylsp, + env: None, + arguments: vec![], + }) + } + + async fn cached_server_binary( + &self, + _: PathBuf, + delegate: &dyn LspAdapterDelegate, + ) -> Option { + let venv = self.base_venv(delegate).await.ok()?; + let pylsp = venv.join(BINARY_DIR).join("pylsp"); + Some(LanguageServerBinary { + path: pylsp, + env: None, + arguments: vec![], + }) + } +} + pub(crate) struct BasedPyrightLspAdapter { python_venv_base: OnceCell, String>>, } @@ -1642,80 +1650,6 @@ impl LspAdapter for BasedPyrightLspAdapter { }))) } - async fn check_if_user_installed( - &self, - delegate: &dyn LspAdapterDelegate, - toolchain: Option, - _: &AsyncApp, - ) -> Option { - if let Some(bin) = delegate.which(Self::BINARY_NAME.as_ref()).await { - let env = delegate.shell_env().await; - Some(LanguageServerBinary { - path: bin, - env: Some(env), - arguments: vec!["--stdio".into()], - }) - } else { - let path = Path::new(toolchain?.path.as_ref()) - .parent()? - .join(Self::BINARY_NAME); - path.exists().then(|| LanguageServerBinary { - path, - arguments: vec!["--stdio".into()], - env: None, - }) - } - } - - async fn fetch_latest_server_version( - &self, - _: &dyn LspAdapterDelegate, - _: &AsyncApp, - ) -> Result> { - Ok(Box::new(()) as Box<_>) - } - - async fn fetch_server_binary( - &self, - _latest_version: Box, - _container_dir: PathBuf, - delegate: &dyn LspAdapterDelegate, - ) -> Result { - let venv = self.base_venv(delegate).await.map_err(|e| anyhow!(e))?; - let pip_path = venv.join(BINARY_DIR).join("pip3"); - ensure!( - util::command::new_smol_command(pip_path.as_path()) - .arg("install") - .arg("basedpyright") - .arg("-U") - .output() - .await? - .status - .success(), - "basedpyright installation failed" - ); - let pylsp = venv.join(BINARY_DIR).join(Self::BINARY_NAME); - Ok(LanguageServerBinary { - path: pylsp, - env: None, - arguments: vec!["--stdio".into()], - }) - } - - async fn cached_server_binary( - &self, - _container_dir: PathBuf, - delegate: &dyn LspAdapterDelegate, - ) -> Option { - let venv = self.base_venv(delegate).await.ok()?; - let pylsp = venv.join(BINARY_DIR).join(Self::BINARY_NAME); - Some(LanguageServerBinary { - path: pylsp, - env: None, - arguments: vec!["--stdio".into()], - }) - } - async fn process_completions(&self, items: &mut [lsp::CompletionItem]) { process_pyright_completions(items); } @@ -1878,6 +1812,85 @@ impl LspAdapter for BasedPyrightLspAdapter { } } +impl LspInstaller for BasedPyrightLspAdapter { + type BinaryVersion = (); + + async fn fetch_latest_server_version( + &self, + _: &dyn LspAdapterDelegate, + _: bool, + _: &mut AsyncApp, + ) -> Result<()> { + Ok(()) + } + + async fn check_if_user_installed( + &self, + delegate: &dyn LspAdapterDelegate, + toolchain: Option, + _: &AsyncApp, + ) -> Option { + if let Some(bin) = delegate.which(Self::BINARY_NAME.as_ref()).await { + let env = delegate.shell_env().await; + Some(LanguageServerBinary { + path: bin, + env: Some(env), + arguments: vec!["--stdio".into()], + }) + } else { + let path = Path::new(toolchain?.path.as_ref()) + .parent()? + .join(Self::BINARY_NAME); + path.exists().then(|| LanguageServerBinary { + path, + arguments: vec!["--stdio".into()], + env: None, + }) + } + } + + async fn fetch_server_binary( + &self, + _latest_version: (), + _container_dir: PathBuf, + delegate: &dyn LspAdapterDelegate, + ) -> Result { + let venv = self.base_venv(delegate).await.map_err(|e| anyhow!(e))?; + let pip_path = venv.join(BINARY_DIR).join("pip3"); + ensure!( + util::command::new_smol_command(pip_path.as_path()) + .arg("install") + .arg("basedpyright") + .arg("-U") + .output() + .await? + .status + .success(), + "basedpyright installation failed" + ); + let pylsp = venv.join(BINARY_DIR).join(Self::BINARY_NAME); + Ok(LanguageServerBinary { + path: pylsp, + env: None, + arguments: vec!["--stdio".into()], + }) + } + + async fn cached_server_binary( + &self, + _container_dir: PathBuf, + delegate: &dyn LspAdapterDelegate, + ) -> Option { + let venv = self.base_venv(delegate).await.ok()?; + let pylsp = venv.join(BINARY_DIR).join(Self::BINARY_NAME); + Some(LanguageServerBinary { + path: pylsp, + env: None, + arguments: vec!["--stdio".into()], + }) + } +} + pub(crate) struct RuffLspAdapter { fs: Arc, } @@ -1934,7 +1947,10 @@ impl LspAdapter for RuffLspAdapter { fn name(&self) -> LanguageServerName { Self::SERVER_NAME } +} +impl LspInstaller for RuffLspAdapter { + type BinaryVersion = GitHubLspBinaryVersion; async fn check_if_user_installed( &self, delegate: &dyn LspAdapterDelegate, @@ -1968,8 +1984,9 @@ impl LspAdapter for RuffLspAdapter { async fn fetch_latest_server_version( &self, delegate: &dyn LspAdapterDelegate, - _: &AsyncApp, - ) -> Result> { + _: bool, + _: &mut AsyncApp, + ) -> Result { let release = latest_github_release("astral-sh/ruff", true, false, delegate.http_client()).await?; let (_, asset_name) = Self::build_asset_name()?; @@ -1978,16 +1995,16 @@ impl LspAdapter for RuffLspAdapter { .into_iter() .find(|asset| asset.name == asset_name) .with_context(|| format!("no asset found matching `{asset_name:?}`"))?; - Ok(Box::new(GitHubLspBinaryVersion { + Ok(GitHubLspBinaryVersion { name: release.tag_name, url: asset.browser_download_url, digest: asset.digest, - })) + }) } async fn fetch_server_binary( &self, - latest_version: Box, + latest_version: GitHubLspBinaryVersion, container_dir: PathBuf, delegate: &dyn LspAdapterDelegate, ) -> Result { @@ -1995,7 +2012,7 @@ impl LspAdapter for RuffLspAdapter { name, url, digest: expected_digest, - } = *latest_version.downcast::().unwrap(); + } = latest_version; let destination_path = container_dir.join(format!("ruff-{name}")); let server_path = match Self::GITHUB_ASSET_KIND { AssetKind::TarGz | AssetKind::Gz => destination_path diff --git a/crates/languages/src/rust.rs b/crates/languages/src/rust.rs index ce73c70c0ceec4bdca4b19d7155a01e1f0940dcf..e35d870610669f0e09cb28e15841d1105d129cca 100644 --- a/crates/languages/src/rust.rs +++ b/crates/languages/src/rust.rs @@ -16,7 +16,6 @@ use smol::fs::{self}; use std::fmt::Display; use std::ops::Range; use std::{ - any::Any, borrow::Cow, path::{Path, PathBuf}, sync::{Arc, LazyLock}, @@ -108,161 +107,6 @@ impl LspAdapter for RustLspAdapter { SERVER_NAME } - async fn check_if_user_installed( - &self, - delegate: &dyn LspAdapterDelegate, - _: Option, - _: &AsyncApp, - ) -> Option { - let path = delegate.which("rust-analyzer".as_ref()).await?; - let env = delegate.shell_env().await; - - // It is surprisingly common for ~/.cargo/bin/rust-analyzer to be a symlink to - // /usr/bin/rust-analyzer that fails when you run it; so we need to test it. - log::info!("found rust-analyzer in PATH. trying to run `rust-analyzer --help`"); - let result = delegate - .try_exec(LanguageServerBinary { - path: path.clone(), - arguments: vec!["--help".into()], - env: Some(env.clone()), - }) - .await; - if let Err(err) = result { - log::debug!( - "failed to run rust-analyzer after detecting it in PATH: binary: {:?}: {}", - path, - err - ); - return None; - } - - Some(LanguageServerBinary { - path, - env: Some(env), - arguments: vec![], - }) - } - - async fn fetch_latest_server_version( - &self, - delegate: &dyn LspAdapterDelegate, - cx: &AsyncApp, - ) -> Result> { - let release = latest_github_release( - "rust-lang/rust-analyzer", - true, - ProjectSettings::try_read_global(cx, |s| { - s.lsp.get(&SERVER_NAME)?.fetch.as_ref()?.pre_release - }) - .flatten() - .unwrap_or(false), - delegate.http_client(), - ) - .await?; - let asset_name = Self::build_asset_name(); - let asset = release - .assets - .into_iter() - .find(|asset| asset.name == asset_name) - .with_context(|| format!("no asset found matching `{asset_name:?}`"))?; - Ok(Box::new(GitHubLspBinaryVersion { - name: release.tag_name, - url: asset.browser_download_url, - digest: asset.digest, - })) - } - - async fn fetch_server_binary( - &self, - version: Box, - container_dir: PathBuf, - delegate: &dyn LspAdapterDelegate, - ) -> Result { - let GitHubLspBinaryVersion { - name, - url, - digest: expected_digest, - } = *version.downcast::().unwrap(); - let destination_path = container_dir.join(format!("rust-analyzer-{name}")); - let server_path = match Self::GITHUB_ASSET_KIND { - AssetKind::TarGz | AssetKind::Gz => destination_path.clone(), // Tar and gzip extract in place. - AssetKind::Zip => destination_path.clone().join("rust-analyzer.exe"), // zip contains a .exe - }; - - let binary = LanguageServerBinary { - path: server_path.clone(), - env: None, - arguments: Default::default(), - }; - - let metadata_path = destination_path.with_extension("metadata"); - let metadata = GithubBinaryMetadata::read_from_file(&metadata_path) - .await - .ok(); - if let Some(metadata) = metadata { - let validity_check = async || { - delegate - .try_exec(LanguageServerBinary { - path: server_path.clone(), - arguments: vec!["--version".into()], - env: None, - }) - .await - .inspect_err(|err| { - log::warn!("Unable to run {server_path:?} asset, redownloading: {err}",) - }) - }; - if let (Some(actual_digest), Some(expected_digest)) = - (&metadata.digest, &expected_digest) - { - if actual_digest == expected_digest { - if validity_check().await.is_ok() { - return Ok(binary); - } - } else { - log::info!( - "SHA-256 mismatch for {destination_path:?} asset, downloading new asset. Expected: {expected_digest}, Got: {actual_digest}" - ); - } - } else if validity_check().await.is_ok() { - return Ok(binary); - } - } - - download_server_binary( - delegate, - &url, - expected_digest.as_deref(), - &destination_path, - Self::GITHUB_ASSET_KIND, - ) - .await?; - make_file_executable(&server_path).await?; - remove_matching(&container_dir, |path| path != destination_path).await; - GithubBinaryMetadata::write_to_file( - &GithubBinaryMetadata { - metadata_version: 1, - digest: expected_digest, - }, - &metadata_path, - ) - .await?; - - Ok(LanguageServerBinary { - path: server_path, - env: None, - arguments: Default::default(), - }) - } - - async fn cached_server_binary( - &self, - container_dir: PathBuf, - _: &dyn LspAdapterDelegate, - ) -> Option { - get_cached_server_binary(container_dir).await - } - fn disk_based_diagnostic_sources(&self) -> Vec { vec![CARGO_DIAGNOSTICS_SOURCE_NAME.to_owned()] } @@ -518,6 +362,161 @@ impl LspAdapter for RustLspAdapter { } } +impl LspInstaller for RustLspAdapter { + type BinaryVersion = GitHubLspBinaryVersion; + async fn check_if_user_installed( + &self, + delegate: &dyn LspAdapterDelegate, + _: Option, + _: &AsyncApp, + ) -> Option { + let path = delegate.which("rust-analyzer".as_ref()).await?; + let env = delegate.shell_env().await; + + // It is surprisingly common for ~/.cargo/bin/rust-analyzer to be a symlink to + // /usr/bin/rust-analyzer that fails when you run it; so we need to test it. + log::info!("found rust-analyzer in PATH. trying to run `rust-analyzer --help`"); + let result = delegate + .try_exec(LanguageServerBinary { + path: path.clone(), + arguments: vec!["--help".into()], + env: Some(env.clone()), + }) + .await; + if let Err(err) = result { + log::debug!( + "failed to run rust-analyzer after detecting it in PATH: binary: {:?}: {}", + path, + err + ); + return None; + } + + Some(LanguageServerBinary { + path, + env: Some(env), + arguments: vec![], + }) + } + + async fn fetch_latest_server_version( + &self, + delegate: &dyn LspAdapterDelegate, + pre_release: bool, + _: &mut AsyncApp, + ) -> Result { + let release = latest_github_release( + "rust-lang/rust-analyzer", + true, + pre_release, + delegate.http_client(), + ) + .await?; + let asset_name = Self::build_asset_name(); + let asset = release + .assets + .into_iter() + .find(|asset| asset.name == asset_name) + .with_context(|| format!("no asset found matching `{asset_name:?}`"))?; + Ok(GitHubLspBinaryVersion { + name: release.tag_name, + url: asset.browser_download_url, + digest: asset.digest, + }) + } + + async fn fetch_server_binary( + &self, + version: GitHubLspBinaryVersion, + container_dir: PathBuf, + delegate: &dyn LspAdapterDelegate, + ) -> Result { + let GitHubLspBinaryVersion { + name, + url, + digest: expected_digest, + } = version; + let destination_path = container_dir.join(format!("rust-analyzer-{name}")); + let server_path = match Self::GITHUB_ASSET_KIND { + AssetKind::TarGz | AssetKind::Gz => destination_path.clone(), // Tar and gzip extract in place. + AssetKind::Zip => destination_path.clone().join("rust-analyzer.exe"), // zip contains a .exe + }; + + let binary = LanguageServerBinary { + path: server_path.clone(), + env: None, + arguments: Default::default(), + }; + + let metadata_path = destination_path.with_extension("metadata"); + let metadata = GithubBinaryMetadata::read_from_file(&metadata_path) + .await + .ok(); + if let Some(metadata) = metadata { + let validity_check = async || { + delegate + .try_exec(LanguageServerBinary { + path: server_path.clone(), + arguments: vec!["--version".into()], + env: None, + }) + .await + .inspect_err(|err| { + log::warn!("Unable to run {server_path:?} asset, redownloading: {err}",) + }) + }; + if let (Some(actual_digest), Some(expected_digest)) = + (&metadata.digest, &expected_digest) + { + if actual_digest == expected_digest { + if validity_check().await.is_ok() { + return Ok(binary); + } + } else { + log::info!( + "SHA-256 mismatch for {destination_path:?} asset, downloading new asset. Expected: {expected_digest}, Got: {actual_digest}" + ); + } + } else if validity_check().await.is_ok() { + return Ok(binary); + } + } + + download_server_binary( + delegate, + &url, + expected_digest.as_deref(), + &destination_path, + Self::GITHUB_ASSET_KIND, + ) + .await?; + make_file_executable(&server_path).await?; + remove_matching(&container_dir, |path| path != destination_path).await; + GithubBinaryMetadata::write_to_file( + &GithubBinaryMetadata { + metadata_version: 1, + digest: expected_digest, + }, + &metadata_path, + ) + .await?; + + Ok(LanguageServerBinary { + path: server_path, + env: None, + arguments: Default::default(), + }) + } + + async fn cached_server_binary( + &self, + container_dir: PathBuf, + _: &dyn LspAdapterDelegate, + ) -> Option { + get_cached_server_binary(container_dir).await + } +} + pub(crate) struct RustContextProvider; const RUST_PACKAGE_TASK_VARIABLE: VariableName = diff --git a/crates/languages/src/tailwind.rs b/crates/languages/src/tailwind.rs index 1d8b3db212b62367354a333fa9f30a99efb8be1d..db539edabb6c10253f2fd0d688eafa46082d427a 100644 --- a/crates/languages/src/tailwind.rs +++ b/crates/languages/src/tailwind.rs @@ -3,14 +3,13 @@ use async_trait::async_trait; use collections::HashMap; use futures::StreamExt; use gpui::AsyncApp; -use language::{LanguageName, LspAdapter, LspAdapterDelegate, Toolchain}; +use language::{LanguageName, LspAdapter, LspAdapterDelegate, LspInstaller, Toolchain}; use lsp::{LanguageServerBinary, LanguageServerName}; use node_runtime::{NodeRuntime, VersionStrategy}; use project::lsp_store::language_server_settings; use serde_json::{Value, json}; use smol::fs; use std::{ - any::Any, ffi::OsString, path::{Path, PathBuf}, sync::Arc, @@ -41,10 +40,18 @@ impl TailwindLspAdapter { } } -#[async_trait(?Send)] -impl LspAdapter for TailwindLspAdapter { - fn name(&self) -> LanguageServerName { - Self::SERVER_NAME +impl LspInstaller for TailwindLspAdapter { + type BinaryVersion = String; + + async fn fetch_latest_server_version( + &self, + _: &dyn LspAdapterDelegate, + _: bool, + _: &mut AsyncApp, + ) -> Result { + self.node + .npm_package_latest_version(Self::PACKAGE_NAME) + .await } async fn check_if_user_installed( @@ -63,25 +70,12 @@ impl LspAdapter for TailwindLspAdapter { }) } - async fn fetch_latest_server_version( - &self, - _: &dyn LspAdapterDelegate, - _: &AsyncApp, - ) -> Result> { - Ok(Box::new( - self.node - .npm_package_latest_version(Self::PACKAGE_NAME) - .await?, - ) as Box<_>) - } - async fn fetch_server_binary( &self, - latest_version: Box, + latest_version: String, container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Result { - let latest_version = latest_version.downcast::().unwrap(); let server_path = container_dir.join(SERVER_PATH); self.node @@ -100,11 +94,10 @@ impl LspAdapter for TailwindLspAdapter { async fn check_if_version_installed( &self, - version: &(dyn 'static + Send + Any), + version: &String, container_dir: &PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { - let version = version.downcast_ref::().unwrap(); let server_path = container_dir.join(SERVER_PATH); let should_install_language_server = self @@ -135,6 +128,13 @@ impl LspAdapter for TailwindLspAdapter { ) -> Option { get_cached_server_binary(container_dir, &self.node).await } +} + +#[async_trait(?Send)] +impl LspAdapter for TailwindLspAdapter { + fn name(&self) -> LanguageServerName { + Self::SERVER_NAME + } async fn initialization_options( self: Arc, diff --git a/crates/languages/src/typescript.rs b/crates/languages/src/typescript.rs index 8f9d362c0dff0bdb8e001155264f1e75f86f5172..bc939f0b727f5ca60a851be99aaf0f3e61ee3a93 100644 --- a/crates/languages/src/typescript.rs +++ b/crates/languages/src/typescript.rs @@ -7,7 +7,7 @@ use gpui::{App, AppContext, AsyncApp, Task}; use http_client::github::{AssetKind, GitHubLspBinaryVersion, build_asset_url}; use language::{ ContextLocation, ContextProvider, File, LanguageName, LanguageToolchainStore, LspAdapter, - LspAdapterDelegate, Toolchain, + LspAdapterDelegate, LspInstaller, Toolchain, }; use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerName}; use node_runtime::{NodeRuntime, VersionStrategy}; @@ -15,7 +15,6 @@ use project::{Fs, lsp_store::language_server_settings}; use serde_json::{Value, json}; use smol::{fs, lock::RwLock, stream::StreamExt}; use std::{ - any::Any, borrow::Cow, ffi::OsString, path::{Path, PathBuf}, @@ -555,38 +554,35 @@ impl TypeScriptLspAdapter { } } -struct TypeScriptVersions { +pub struct TypeScriptVersions { typescript_version: String, server_version: String, } -#[async_trait(?Send)] -impl LspAdapter for TypeScriptLspAdapter { - fn name(&self) -> LanguageServerName { - Self::SERVER_NAME - } +impl LspInstaller for TypeScriptLspAdapter { + type BinaryVersion = TypeScriptVersions; async fn fetch_latest_server_version( &self, _: &dyn LspAdapterDelegate, - _: &AsyncApp, - ) -> Result> { - Ok(Box::new(TypeScriptVersions { + _: bool, + _: &mut AsyncApp, + ) -> Result { + Ok(TypeScriptVersions { typescript_version: self.node.npm_package_latest_version("typescript").await?, server_version: self .node .npm_package_latest_version("typescript-language-server") .await?, - }) as Box<_>) + }) } async fn check_if_version_installed( &self, - version: &(dyn 'static + Send + Any), + version: &TypeScriptVersions, container_dir: &PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { - let version = version.downcast_ref::().unwrap(); let server_path = container_dir.join(Self::NEW_SERVER_PATH); let should_install_language_server = self @@ -612,11 +608,10 @@ impl LspAdapter for TypeScriptLspAdapter { async fn fetch_server_binary( &self, - latest_version: Box, + latest_version: TypeScriptVersions, container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Result { - let latest_version = latest_version.downcast::().unwrap(); let server_path = container_dir.join(Self::NEW_SERVER_PATH); self.node @@ -649,6 +644,13 @@ impl LspAdapter for TypeScriptLspAdapter { ) -> Option { get_cached_ts_server_binary(container_dir, &self.node).await } +} + +#[async_trait(?Send)] +impl LspAdapter for TypeScriptLspAdapter { + fn name(&self) -> LanguageServerName { + Self::SERVER_NAME + } fn code_action_kinds(&self) -> Option> { Some(vec![ @@ -815,103 +817,34 @@ impl EsLintLspAdapter { } } -#[async_trait(?Send)] -impl LspAdapter for EsLintLspAdapter { - fn code_action_kinds(&self) -> Option> { - Some(vec![ - CodeActionKind::QUICKFIX, - CodeActionKind::new("source.fixAll.eslint"), - ]) - } - - async fn workspace_configuration( - self: Arc, - delegate: &Arc, - _: Option, - cx: &mut AsyncApp, - ) -> Result { - let workspace_root = delegate.worktree_root_path(); - let use_flat_config = Self::FLAT_CONFIG_FILE_NAMES - .iter() - .any(|file| workspace_root.join(file).is_file()); - - let mut default_workspace_configuration = json!({ - "validate": "on", - "rulesCustomizations": [], - "run": "onType", - "nodePath": null, - "workingDirectory": { - "mode": "auto" - }, - "workspaceFolder": { - "uri": workspace_root, - "name": workspace_root.file_name() - .unwrap_or(workspace_root.as_os_str()) - .to_string_lossy(), - }, - "problems": {}, - "codeActionOnSave": { - // We enable this, but without also configuring code_actions_on_format - // in the Zed configuration, it doesn't have an effect. - "enable": true, - }, - "codeAction": { - "disableRuleComment": { - "enable": true, - "location": "separateLine", - }, - "showDocumentation": { - "enable": true - } - }, - "experimental": { - "useFlatConfig": use_flat_config, - } - }); - - let override_options = cx.update(|cx| { - language_server_settings(delegate.as_ref(), &Self::SERVER_NAME, cx) - .and_then(|s| s.settings.clone()) - })?; - - if let Some(override_options) = override_options { - merge_json_value_into(override_options, &mut default_workspace_configuration); - } - - Ok(json!({ - "": default_workspace_configuration - })) - } - - fn name(&self) -> LanguageServerName { - Self::SERVER_NAME - } +impl LspInstaller for EsLintLspAdapter { + type BinaryVersion = GitHubLspBinaryVersion; async fn fetch_latest_server_version( &self, _delegate: &dyn LspAdapterDelegate, - _: &AsyncApp, - ) -> Result> { + _: bool, + _: &mut AsyncApp, + ) -> Result { let url = build_asset_url( "zed-industries/vscode-eslint", Self::CURRENT_VERSION_TAG_NAME, Self::GITHUB_ASSET_KIND, )?; - Ok(Box::new(GitHubLspBinaryVersion { + Ok(GitHubLspBinaryVersion { name: Self::CURRENT_VERSION.into(), digest: None, url, - })) + }) } async fn fetch_server_binary( &self, - version: Box, + version: GitHubLspBinaryVersion, container_dir: PathBuf, delegate: &dyn LspAdapterDelegate, ) -> Result { - let version = version.downcast::().unwrap(); let destination_path = Self::build_destination_path(&container_dir); let server_path = destination_path.join(Self::SERVER_PATH); @@ -977,6 +910,79 @@ impl LspAdapter for EsLintLspAdapter { } } +#[async_trait(?Send)] +impl LspAdapter for EsLintLspAdapter { + fn code_action_kinds(&self) -> Option> { + Some(vec![ + CodeActionKind::QUICKFIX, + CodeActionKind::new("source.fixAll.eslint"), + ]) + } + + async fn workspace_configuration( + self: Arc, + delegate: &Arc, + _: Option, + cx: &mut AsyncApp, + ) -> Result { + let workspace_root = delegate.worktree_root_path(); + let use_flat_config = Self::FLAT_CONFIG_FILE_NAMES + .iter() + .any(|file| workspace_root.join(file).is_file()); + + let mut default_workspace_configuration = json!({ + "validate": "on", + "rulesCustomizations": [], + "run": "onType", + "nodePath": null, + "workingDirectory": { + "mode": "auto" + }, + "workspaceFolder": { + "uri": workspace_root, + "name": workspace_root.file_name() + .unwrap_or(workspace_root.as_os_str()) + .to_string_lossy(), + }, + "problems": {}, + "codeActionOnSave": { + // We enable this, but without also configuring code_actions_on_format + // in the Zed configuration, it doesn't have an effect. + "enable": true, + }, + "codeAction": { + "disableRuleComment": { + "enable": true, + "location": "separateLine", + }, + "showDocumentation": { + "enable": true + } + }, + "experimental": { + "useFlatConfig": use_flat_config, + } + }); + + let override_options = cx.update(|cx| { + language_server_settings(delegate.as_ref(), &Self::SERVER_NAME, cx) + .and_then(|s| s.settings.clone()) + })?; + + if let Some(override_options) = override_options { + merge_json_value_into(override_options, &mut default_workspace_configuration); + } + + Ok(json!({ + "": default_workspace_configuration + })) + } + + fn name(&self) -> LanguageServerName { + Self::SERVER_NAME + } +} + #[cfg(target_os = "windows")] async fn handle_symlink(src_dir: PathBuf, dest_dir: PathBuf) -> Result<()> { anyhow::ensure!( diff --git a/crates/languages/src/vtsls.rs b/crates/languages/src/vtsls.rs index 32554fe651860a1db3732565fe163b702734e11c..763a0d5a5ed961d916e3deea44963b0aa9340cb9 100644 --- a/crates/languages/src/vtsls.rs +++ b/crates/languages/src/vtsls.rs @@ -2,13 +2,12 @@ use anyhow::Result; use async_trait::async_trait; use collections::HashMap; use gpui::AsyncApp; -use language::{LanguageName, LspAdapter, LspAdapterDelegate, Toolchain}; +use language::{LanguageName, LspAdapter, LspAdapterDelegate, LspInstaller, Toolchain}; use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerName}; use node_runtime::{NodeRuntime, VersionStrategy}; use project::{Fs, lsp_store::language_server_settings}; use serde_json::Value; use std::{ - any::Any, ffi::OsString, path::{Path, PathBuf}, sync::Arc, @@ -59,31 +58,29 @@ impl VtslsLspAdapter { } } -struct TypeScriptVersions { +pub struct TypeScriptVersions { typescript_version: String, server_version: String, } const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("vtsls"); -#[async_trait(?Send)] -impl LspAdapter for VtslsLspAdapter { - fn name(&self) -> LanguageServerName { - SERVER_NAME - } +impl LspInstaller for VtslsLspAdapter { + type BinaryVersion = TypeScriptVersions; async fn fetch_latest_server_version( &self, _: &dyn LspAdapterDelegate, - _: &AsyncApp, - ) -> Result> { - Ok(Box::new(TypeScriptVersions { + _: bool, + _: &mut AsyncApp, + ) -> Result { + Ok(TypeScriptVersions { typescript_version: self.node.npm_package_latest_version("typescript").await?, server_version: self .node .npm_package_latest_version("@vtsls/language-server") .await?, - }) as Box<_>) + }) } async fn check_if_user_installed( @@ -103,11 +100,10 @@ impl LspAdapter for VtslsLspAdapter { async fn fetch_server_binary( &self, - latest_version: Box, + latest_version: TypeScriptVersions, container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Result { - let latest_version = latest_version.downcast::().unwrap(); let server_path = container_dir.join(Self::SERVER_PATH); let mut packages_to_install = Vec::new(); @@ -159,6 +155,13 @@ impl LspAdapter for VtslsLspAdapter { ) -> Option { get_cached_ts_server_binary(container_dir, &self.node).await } +} + +#[async_trait(?Send)] +impl LspAdapter for VtslsLspAdapter { + fn name(&self) -> LanguageServerName { + SERVER_NAME + } fn code_action_kinds(&self) -> Option> { Some(vec![ diff --git a/crates/languages/src/yaml.rs b/crates/languages/src/yaml.rs index 39c30a3714332044eff137fd735fa53e07160070..c629756324cbded19485e7ba9d420db3fd4bd093 100644 --- a/crates/languages/src/yaml.rs +++ b/crates/languages/src/yaml.rs @@ -2,7 +2,9 @@ use anyhow::{Context as _, Result}; use async_trait::async_trait; use futures::StreamExt; use gpui::AsyncApp; -use language::{LspAdapter, LspAdapterDelegate, Toolchain, language_settings::AllLanguageSettings}; +use language::{ + LspAdapter, LspAdapterDelegate, LspInstaller, Toolchain, language_settings::AllLanguageSettings, +}; use lsp::{LanguageServerBinary, LanguageServerName}; use node_runtime::{NodeRuntime, VersionStrategy}; use project::lsp_store::language_server_settings; @@ -10,7 +12,6 @@ use serde_json::Value; use settings::{Settings, SettingsLocation}; use smol::fs; use std::{ - any::Any, ffi::OsString, path::{Path, PathBuf}, sync::Arc, @@ -35,22 +36,18 @@ impl YamlLspAdapter { } } -#[async_trait(?Send)] -impl LspAdapter for YamlLspAdapter { - fn name(&self) -> LanguageServerName { - Self::SERVER_NAME - } +impl LspInstaller for YamlLspAdapter { + type BinaryVersion = String; async fn fetch_latest_server_version( &self, _: &dyn LspAdapterDelegate, - _: &AsyncApp, - ) -> Result> { - Ok(Box::new( - self.node - .npm_package_latest_version("yaml-language-server") - .await?, - ) as Box<_>) + _: bool, + _: &mut AsyncApp, + ) -> Result { + self.node + .npm_package_latest_version("yaml-language-server") + .await } async fn check_if_user_installed( @@ -71,11 +68,10 @@ impl LspAdapter for YamlLspAdapter { async fn fetch_server_binary( &self, - latest_version: Box, + latest_version: String, container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Result { - let latest_version = latest_version.downcast::().unwrap(); let server_path = container_dir.join(SERVER_PATH); self.node @@ -94,11 +90,10 @@ impl LspAdapter for YamlLspAdapter { async fn check_if_version_installed( &self, - version: &(dyn 'static + Send + Any), + version: &String, container_dir: &PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { - let version = version.downcast_ref::().unwrap(); let server_path = container_dir.join(SERVER_PATH); let should_install_language_server = self @@ -129,6 +124,13 @@ impl LspAdapter for YamlLspAdapter { ) -> Option { get_cached_server_binary(container_dir, &self.node).await } +} + +#[async_trait(?Send)] +impl LspAdapter for YamlLspAdapter { + fn name(&self) -> LanguageServerName { + Self::SERVER_NAME + } async fn workspace_configuration( self: Arc, diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 7af51ef6fff8bddefac993fb5eb40e10d054977c..fda188be8bec8b8c8a48b888243290b31c10f149 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -62,7 +62,7 @@ pub enum IoKind { /// Represents a launchable language server. This can either be a standalone binary or the path /// to a runtime with arguments to instruct it to launch the actual language server file. -#[derive(Clone, Deserialize)] +#[derive(Clone)] pub struct LanguageServerBinary { pub path: PathBuf, pub arguments: Vec, @@ -70,12 +70,14 @@ pub struct LanguageServerBinary { } /// Configures the search (and installation) of language servers. -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Clone)] pub struct LanguageServerBinaryOptions { /// Whether the adapter should look at the users system pub allow_path_lookup: bool, /// Whether the adapter should download its own version pub allow_binary_download: bool, + /// Whether the adapter should download a pre-release version + pub pre_release: bool, } /// A running language server process. diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index e719c9e4fb1aee3d6b20e0a46f2bb28b252795d4..2040b10bb39ceff6d9d6d06a5dbfb0f68f41a459 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -59,9 +59,9 @@ use itertools::Itertools as _; use language::{ Bias, BinaryStatus, Buffer, BufferSnapshot, CachedLspAdapter, CodeLabel, Diagnostic, DiagnosticEntry, DiagnosticSet, DiagnosticSourceKind, Diff, File as _, Language, LanguageName, - LanguageRegistry, LocalFile, LspAdapter, LspAdapterDelegate, ManifestDelegate, ManifestName, - Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToPoint, ToPointUtf16, Toolchain, Transaction, - Unclipped, + LanguageRegistry, LocalFile, LspAdapter, LspAdapterDelegate, LspInstaller, ManifestDelegate, + ManifestName, Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Toolchain, + Transaction, Unclipped, language_settings::{ FormatOnSave, Formatter, LanguageSettings, SelectedFormatter, language_settings, }, @@ -96,7 +96,7 @@ use sha2::{Digest, Sha256}; use smol::channel::Sender; use snippet::Snippet; use std::{ - any::{Any, TypeId}, + any::TypeId, borrow::Cow, cell::RefCell, cmp::{Ordering, Reverse}, @@ -112,7 +112,7 @@ use std::{ time::{Duration, Instant}, }; use sum_tree::Dimensions; -use text::{Anchor, BufferId, LineEnding, OffsetRangeExt}; +use text::{Anchor, BufferId, LineEnding, OffsetRangeExt, ToPoint as _}; use util::{ ConnectionResult, ResultExt as _, debug_panic, defer, maybe, merge_json_value_into, @@ -534,6 +534,11 @@ impl LocalLspStore { .and_then(|b| b.ignore_system_version) .unwrap_or_default(), allow_binary_download, + pre_release: settings + .fetch + .as_ref() + .and_then(|f| f.pre_release) + .unwrap_or(false), }; cx.spawn(async move |cx| { @@ -12579,27 +12584,8 @@ impl SshLspAdapter { } } -#[async_trait(?Send)] -impl LspAdapter for SshLspAdapter { - fn name(&self) -> LanguageServerName { - self.name.clone() - } - - async fn initialization_options( - self: Arc, - _: &Arc, - ) -> Result> { - let Some(options) = &self.initialization_options else { - return Ok(None); - }; - let result = serde_json::from_str(options)?; - Ok(result) - } - - fn code_action_kinds(&self) -> Option> { - self.code_action_kinds.clone() - } - +impl LspInstaller for SshLspAdapter { + type BinaryVersion = (); async fn check_if_user_installed( &self, _: &dyn LspAdapterDelegate, @@ -12620,14 +12606,15 @@ impl LspAdapter for SshLspAdapter { async fn fetch_latest_server_version( &self, _: &dyn LspAdapterDelegate, - _: &AsyncApp, - ) -> Result> { + _: bool, + _: &mut AsyncApp, + ) -> Result<()> { anyhow::bail!("SshLspAdapter does not support fetch_latest_server_version") } async fn fetch_server_binary( &self, - _: Box, + _: (), _: PathBuf, _: &dyn LspAdapterDelegate, ) -> Result { @@ -12635,6 +12622,28 @@ impl LspAdapter for SshLspAdapter { } } +#[async_trait(?Send)] +impl LspAdapter for SshLspAdapter { + fn name(&self) -> LanguageServerName { + self.name.clone() + } + + async fn initialization_options( + self: Arc, + _: &Arc, + ) -> Result> { + let Some(options) = &self.initialization_options else { + return Ok(None); + }; + let result = serde_json::from_str(options)?; + Ok(result) + } + + fn code_action_kinds(&self) -> Option> { + self.code_action_kinds.clone() + } +} + pub fn language_server_settings<'a>( delegate: &'a dyn LspAdapterDelegate, language: &LanguageServerName,