Detailed changes
@@ -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<Anchor>,
}
+type ServerBinaryCache = futures::lock::Mutex<Option<(bool, LanguageServerBinary)>>;
+
/// 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<LanguageName, String>,
pub adapter: Arc<dyn LspAdapter>,
pub reinstall_attempt_count: AtomicU64,
- cached_binary: futures::lock::Mutex<Option<LanguageServerBinary>>,
+ cached_binary: ServerBinaryCache,
}
impl Debug for CachedLspAdapter {
@@ -216,10 +217,16 @@ impl CachedLspAdapter {
binary_options: LanguageServerBinaryOptions,
cx: &mut AsyncApp,
) -> Result<LanguageServerBinary> {
- 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<Self>,
- delegate: Arc<dyn LspAdapterDelegate>,
- toolchains: Option<Toolchain>,
- binary_options: LanguageServerBinaryOptions,
- mut cached_binary: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,
- cx: &'a mut AsyncApp,
- ) -> Pin<Box<dyn 'a + Future<Output = Result<LanguageServerBinary>>>> {
- 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<Toolchain>,
- _: &AsyncApp,
- ) -> Option<LanguageServerBinary> {
- None
- }
-
- async fn fetch_latest_server_version(
- &self,
- delegate: &dyn LspAdapterDelegate,
- cx: &AsyncApp,
- ) -> Result<Box<dyn 'static + Send + Any>>;
-
- fn will_fetch_server(
- &self,
- _: &Arc<dyn LspAdapterDelegate>,
- _: &mut AsyncApp,
- ) -> Option<Task<Result<()>>> {
- None
- }
-
- async fn check_if_version_installed(
- &self,
- _version: &(dyn 'static + Send + Any),
- _container_dir: &PathBuf,
- _delegate: &dyn LspAdapterDelegate,
- ) -> Option<LanguageServerBinary> {
- None
- }
-
- async fn fetch_server_binary(
- &self,
- latest_version: Box<dyn 'static + Send + Any>,
- container_dir: PathBuf,
- delegate: &dyn LspAdapterDelegate,
- ) -> Result<LanguageServerBinary>;
-
- async fn cached_server_binary(
- &self,
- container_dir: PathBuf,
- delegate: &dyn LspAdapterDelegate,
- ) -> Option<LanguageServerBinary>;
-
fn process_diagnostics(
&self,
_: &mut lsp::PublishDiagnosticsParams,
@@ -592,40 +480,194 @@ pub trait LspAdapter: 'static + Send + Sync {
}
}
-async fn try_fetch_server_binary<L: LspAdapter + 'static + Send + Sync + ?Sized>(
- adapter: &L,
- delegate: &Arc<dyn LspAdapterDelegate>,
- container_dir: PathBuf,
- cx: &mut AsyncApp,
-) -> Result<LanguageServerBinary> {
- 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<Toolchain>,
+ _: &AsyncApp,
+ ) -> impl Future<Output = Option<LanguageServerBinary>> {
+ async { None }
+ }
+
+ fn fetch_latest_server_version(
+ &self,
+ delegate: &dyn LspAdapterDelegate,
+ pre_release: bool,
+ cx: &mut AsyncApp,
+ ) -> impl Future<Output = Result<Self::BinaryVersion>>;
+
+ fn check_if_version_installed(
+ &self,
+ _version: &Self::BinaryVersion,
+ _container_dir: &PathBuf,
+ _delegate: &dyn LspAdapterDelegate,
+ ) -> impl Future<Output = Option<LanguageServerBinary>> {
+ 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<Output = Result<LanguageServerBinary>>;
- 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<Output = Option<LanguageServerBinary>>;
+}
- 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<dyn LspAdapterDelegate>,
+ container_dir: PathBuf,
+ pre_release: bool,
+ cx: &mut AsyncApp,
+ ) -> Result<LanguageServerBinary>;
+ fn get_language_server_command<'a>(
+ self: Arc<Self>,
+ delegate: Arc<dyn LspAdapterDelegate>,
+ toolchains: Option<Toolchain>,
+ binary_options: LanguageServerBinaryOptions,
+ cached_binary: &'a mut Option<(bool, LanguageServerBinary)>,
+ cx: &'a mut AsyncApp,
+ ) -> Pin<Box<dyn 'a + Future<Output = Result<LanguageServerBinary>>>>;
+}
- delegate.update_status(name.clone(), BinaryStatus::None);
- binary
+#[async_trait(?Send)]
+impl<LI, BinaryVersion> DynLspInstaller for LI
+where
+ LI: LspInstaller<BinaryVersion = BinaryVersion> + LspAdapter,
+{
+ async fn try_fetch_server_binary(
+ &self,
+ delegate: &Arc<dyn LspAdapterDelegate>,
+ container_dir: PathBuf,
+ pre_release: bool,
+ cx: &mut AsyncApp,
+ ) -> Result<LanguageServerBinary> {
+ 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<Self>,
+ delegate: Arc<dyn LspAdapterDelegate>,
+ toolchain: Option<Toolchain>,
+ binary_options: LanguageServerBinaryOptions,
+ cached_binary: &'a mut Option<(bool, LanguageServerBinary)>,
+ cx: &'a mut AsyncApp,
+ ) -> Pin<Box<dyn 'a + Future<Output = Result<LanguageServerBinary>>>> {
+ 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<Self::BinaryVersion> {
+ 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<Self>,
- _: Arc<dyn LspAdapterDelegate>,
- _: Option<Toolchain>,
- _: LanguageServerBinaryOptions,
- _: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,
- _: &'a mut AsyncApp,
- ) -> Pin<Box<dyn 'a + Future<Output = Result<LanguageServerBinary>>>> {
- async move { Ok(self.language_server_binary.clone()) }.boxed_local()
- }
-
- async fn fetch_latest_server_version(
- &self,
- _: &dyn LspAdapterDelegate,
- _: &AsyncApp,
- ) -> Result<Box<dyn 'static + Send + Any>> {
- unreachable!();
- }
-
async fn fetch_server_binary(
&self,
- _: Box<dyn 'static + Send + Any>,
+ _: (),
_: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
@@ -2245,6 +2274,14 @@ impl LspAdapter for FakeLspAdapter {
) -> Option<LanguageServerBinary> {
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<String> {
self.disk_based_diagnostics_sources.clone()
@@ -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<Self>,
delegate: Arc<dyn LspAdapterDelegate>,
_: Option<Toolchain>,
_: LanguageServerBinaryOptions,
- _: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,
+ _: &'a mut Option<(bool, LanguageServerBinary)>,
_: &'a mut AsyncApp,
) -> Pin<Box<dyn 'a + Future<Output = Result<LanguageServerBinary>>>> {
async move {
@@ -205,29 +200,21 @@ impl LspAdapter for ExtensionLspAdapter {
.boxed_local()
}
- async fn fetch_latest_server_version(
- &self,
- _: &dyn LspAdapterDelegate,
- _: &AsyncApp,
- ) -> Result<Box<dyn 'static + Send + Any>> {
- unreachable!("get_language_server_command is overridden")
- }
-
- async fn fetch_server_binary(
+ async fn try_fetch_server_binary(
&self,
- _: Box<dyn 'static + Send + Any>,
+ _: &Arc<dyn LspAdapterDelegate>,
_: PathBuf,
- _: &dyn LspAdapterDelegate,
+ _: bool,
+ _: &mut AsyncApp,
) -> Result<LanguageServerBinary> {
unreachable!("get_language_server_command is overridden")
}
+}
- async fn cached_server_binary(
- &self,
- _: PathBuf,
- _: &dyn LspAdapterDelegate,
- ) -> Option<LanguageServerBinary> {
- 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<Vec<CodeActionKind>> {
@@ -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<Toolchain>,
- _: &AsyncApp,
- ) -> Option<LanguageServerBinary> {
- 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<Box<dyn 'static + Send + Any>> {
- 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<GitHubLspBinaryVersion> {
+ 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<Toolchain>,
+ _: &AsyncApp,
+ ) -> Option<LanguageServerBinary> {
+ 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<dyn 'static + Send + Any>,
+ version: GitHubLspBinaryVersion,
container_dir: PathBuf,
delegate: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
@@ -86,7 +75,7 @@ impl super::LspAdapter for CLspAdapter {
name,
url,
digest: expected_digest,
- } = *version.downcast::<GitHubLspBinaryVersion>().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<LanguageServerBinary> {
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,
@@ -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<String> {
+ 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<Box<dyn 'static + Any + Send>> {
- Ok(Box::new(
- self.node
- .npm_package_latest_version("vscode-langservers-extracted")
- .await?,
- ) as Box<_>)
- }
-
async fn fetch_server_binary(
&self,
- latest_version: Box<dyn 'static + Send + Any>,
+ latest_version: String,
container_dir: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
- let latest_version = latest_version.downcast::<String>().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<LanguageServerBinary> {
- let version = version.downcast_ref::<String>().unwrap();
let server_path = container_dir.join(SERVER_PATH);
let should_install_language_server = self
@@ -130,6 +123,13 @@ impl LspAdapter for CssLspAdapter {
) -> Option<LanguageServerBinary> {
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<Self>,
@@ -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<String>;
async fn fetch_latest_server_version(
&self,
delegate: &dyn LspAdapterDelegate,
- _: &AsyncApp,
- ) -> Result<Box<dyn 'static + Send + Any>> {
+ _: bool,
+ cx: &mut AsyncApp,
+ ) -> Result<Option<String>> {
+ 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<String> = 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<dyn LspAdapterDelegate>,
- cx: &mut AsyncApp,
- ) -> Option<Task<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.";
-
- 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<dyn 'static + Send + Any>,
+ version: Option<String>,
container_dir: PathBuf,
delegate: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
@@ -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::<Option<String>>().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<LanguageServerBinary> {
- 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<LanguageServerBinary> {
+async fn get_cached_server_binary(container_dir: &Path) -> Option<LanguageServerBinary> {
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()
@@ -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<String> {
+ 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<Box<dyn 'static + Send + Any>> {
- 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<LanguageServerBinary> {
- let version = version.downcast_ref::<String>().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<dyn 'static + Send + Any>,
+ latest_version: String,
container_dir: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
- let latest_version = latest_version.downcast::<String>().unwrap();
let server_path = container_dir.join(SERVER_PATH);
self.node
@@ -389,6 +382,13 @@ impl LspAdapter for JsonLspAdapter {
) -> Option<LanguageServerBinary> {
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<Self>,
@@ -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<Box<dyn 'static + Send + Any>> {
+ _: bool,
+ _: &mut AsyncApp,
+ ) -> Result<GitHubLspBinaryVersion> {
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<dyn 'static + Send + Any>,
+ latest_version: GitHubLspBinaryVersion,
container_dir: PathBuf,
delegate: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
- let version = latest_version.downcast::<GitHubLspBinaryVersion>().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<LanguageServerBinary> {
maybe!(async {
let mut last = None;
@@ -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<Self>,
+ _: &Arc<dyn LspAdapterDelegate>,
+ toolchain: Option<Toolchain>,
+ _cx: &mut AsyncApp,
+ ) -> Result<Value> {
+ let mut ret = json!({});
+ if let Some(toolchain) = toolchain.and_then(|toolchain| {
+ serde_json::from_value::<PythonEnvironment>(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<Box<dyn 'static + Send + Any>> {
+ _: bool,
+ _: &mut AsyncApp,
+ ) -> Result<Self::BinaryVersion> {
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<dyn 'static + Send + Any>,
+ latest_version: Self::BinaryVersion,
container_dir: PathBuf,
delegate: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
@@ -189,7 +221,7 @@ impl LspAdapter for TyLspAdapter {
name,
url,
digest: expected_digest,
- } = *latest_version.downcast::<GitHubLspBinaryVersion>().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<Self>,
- _: &Arc<dyn LspAdapterDelegate>,
- toolchain: Option<Toolchain>,
- _cx: &mut AsyncApp,
- ) -> Result<Value> {
- let mut ret = json!({});
- if let Some(toolchain) = toolchain.and_then(|toolchain| {
- serde_json::from_value::<PythonEnvironment>(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<Toolchain>,
- _: &AsyncApp,
- ) -> Option<LanguageServerBinary> {
- 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<Box<dyn 'static + Any + Send>> {
- 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<dyn 'static + Send + Any>,
- container_dir: PathBuf,
- delegate: &dyn LspAdapterDelegate,
- ) -> Result<LanguageServerBinary> {
- let latest_version = latest_version.downcast::<String>().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<LanguageServerBinary> {
- let version = version.downcast_ref::<String>().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<LanguageServerBinary> {
- 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<String> {
+ self.node
+ .npm_package_latest_version(Self::SERVER_NAME.as_ref())
+ .await
+ }
+
+ async fn check_if_user_installed(
+ &self,
+ delegate: &dyn LspAdapterDelegate,
+ _: Option<Toolchain>,
+ _: &AsyncApp,
+ ) -> Option<LanguageServerBinary> {
+ 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<LanguageServerBinary> {
+ 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<LanguageServerBinary> {
+ 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<LanguageServerBinary> {
+ 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<Toolchain>,
- _: &AsyncApp,
- ) -> Option<LanguageServerBinary> {
- 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<Box<dyn 'static + Any + Send>> {
- Ok(Box::new(()) as Box<_>)
- }
-
- async fn fetch_server_binary(
- &self,
- _: Box<dyn 'static + Send + Any>,
- _: PathBuf,
- delegate: &dyn LspAdapterDelegate,
- ) -> Result<LanguageServerBinary> {
- 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<LanguageServerBinary> {
- 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<Toolchain>,
+ _: &AsyncApp,
+ ) -> Option<LanguageServerBinary> {
+ 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<LanguageServerBinary> {
+ 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<LanguageServerBinary> {
+ 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<Result<Arc<Path>, String>>,
}
@@ -1642,80 +1650,6 @@ impl LspAdapter for BasedPyrightLspAdapter {
})))
}
- async fn check_if_user_installed(
- &self,
- delegate: &dyn LspAdapterDelegate,
- toolchain: Option<Toolchain>,
- _: &AsyncApp,
- ) -> Option<LanguageServerBinary> {
- 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<Box<dyn 'static + Any + Send>> {
- Ok(Box::new(()) as Box<_>)
- }
-
- async fn fetch_server_binary(
- &self,
- _latest_version: Box<dyn 'static + Send + Any>,
- _container_dir: PathBuf,
- delegate: &dyn LspAdapterDelegate,
- ) -> Result<LanguageServerBinary> {
- 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<LanguageServerBinary> {
- 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<Toolchain>,
+ _: &AsyncApp,
+ ) -> Option<LanguageServerBinary> {
+ 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<LanguageServerBinary> {
+ 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<LanguageServerBinary> {
+ 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<dyn Fs>,
}
@@ -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<Box<dyn 'static + Send + Any>> {
+ _: bool,
+ _: &mut AsyncApp,
+ ) -> Result<GitHubLspBinaryVersion> {
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<dyn 'static + Send + Any>,
+ latest_version: GitHubLspBinaryVersion,
container_dir: PathBuf,
delegate: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
@@ -1995,7 +2012,7 @@ impl LspAdapter for RuffLspAdapter {
name,
url,
digest: expected_digest,
- } = *latest_version.downcast::<GitHubLspBinaryVersion>().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
@@ -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<Toolchain>,
- _: &AsyncApp,
- ) -> Option<LanguageServerBinary> {
- 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<Box<dyn 'static + Send + Any>> {
- 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<dyn 'static + Send + Any>,
- container_dir: PathBuf,
- delegate: &dyn LspAdapterDelegate,
- ) -> Result<LanguageServerBinary> {
- let GitHubLspBinaryVersion {
- name,
- url,
- digest: expected_digest,
- } = *version.downcast::<GitHubLspBinaryVersion>().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<LanguageServerBinary> {
- get_cached_server_binary(container_dir).await
- }
-
fn disk_based_diagnostic_sources(&self) -> Vec<String> {
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<Toolchain>,
+ _: &AsyncApp,
+ ) -> Option<LanguageServerBinary> {
+ 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<GitHubLspBinaryVersion> {
+ 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<LanguageServerBinary> {
+ 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<LanguageServerBinary> {
+ get_cached_server_binary(container_dir).await
+ }
+}
+
pub(crate) struct RustContextProvider;
const RUST_PACKAGE_TASK_VARIABLE: VariableName =
@@ -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<String> {
+ 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<Box<dyn 'static + Any + Send>> {
- Ok(Box::new(
- self.node
- .npm_package_latest_version(Self::PACKAGE_NAME)
- .await?,
- ) as Box<_>)
- }
-
async fn fetch_server_binary(
&self,
- latest_version: Box<dyn 'static + Send + Any>,
+ latest_version: String,
container_dir: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
- let latest_version = latest_version.downcast::<String>().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<LanguageServerBinary> {
- let version = version.downcast_ref::<String>().unwrap();
let server_path = container_dir.join(SERVER_PATH);
let should_install_language_server = self
@@ -135,6 +128,13 @@ impl LspAdapter for TailwindLspAdapter {
) -> Option<LanguageServerBinary> {
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<Self>,
@@ -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<Box<dyn 'static + Send + Any>> {
- Ok(Box::new(TypeScriptVersions {
+ _: bool,
+ _: &mut AsyncApp,
+ ) -> Result<TypeScriptVersions> {
+ 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<LanguageServerBinary> {
- let version = version.downcast_ref::<TypeScriptVersions>().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<dyn 'static + Send + Any>,
+ latest_version: TypeScriptVersions,
container_dir: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
- let latest_version = latest_version.downcast::<TypeScriptVersions>().unwrap();
let server_path = container_dir.join(Self::NEW_SERVER_PATH);
self.node
@@ -649,6 +644,13 @@ impl LspAdapter for TypeScriptLspAdapter {
) -> Option<LanguageServerBinary> {
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<Vec<CodeActionKind>> {
Some(vec![
@@ -815,103 +817,34 @@ impl EsLintLspAdapter {
}
}
-#[async_trait(?Send)]
-impl LspAdapter for EsLintLspAdapter {
- fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> {
- Some(vec![
- CodeActionKind::QUICKFIX,
- CodeActionKind::new("source.fixAll.eslint"),
- ])
- }
-
- async fn workspace_configuration(
- self: Arc<Self>,
- delegate: &Arc<dyn LspAdapterDelegate>,
- _: Option<Toolchain>,
- cx: &mut AsyncApp,
- ) -> Result<Value> {
- 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<Box<dyn 'static + Send + Any>> {
+ _: bool,
+ _: &mut AsyncApp,
+ ) -> Result<GitHubLspBinaryVersion> {
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<dyn 'static + Send + Any>,
+ version: GitHubLspBinaryVersion,
container_dir: PathBuf,
delegate: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
- let version = version.downcast::<GitHubLspBinaryVersion>().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<Vec<CodeActionKind>> {
+ Some(vec![
+ CodeActionKind::QUICKFIX,
+ CodeActionKind::new("source.fixAll.eslint"),
+ ])
+ }
+
+ async fn workspace_configuration(
+ self: Arc<Self>,
+ delegate: &Arc<dyn LspAdapterDelegate>,
+ _: Option<Toolchain>,
+ cx: &mut AsyncApp,
+ ) -> Result<Value> {
+ 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!(
@@ -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<Box<dyn 'static + Send + Any>> {
- Ok(Box::new(TypeScriptVersions {
+ _: bool,
+ _: &mut AsyncApp,
+ ) -> Result<TypeScriptVersions> {
+ 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<dyn 'static + Send + Any>,
+ latest_version: TypeScriptVersions,
container_dir: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
- let latest_version = latest_version.downcast::<TypeScriptVersions>().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<LanguageServerBinary> {
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<Vec<CodeActionKind>> {
Some(vec![
@@ -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<Box<dyn 'static + Any + Send>> {
- Ok(Box::new(
- self.node
- .npm_package_latest_version("yaml-language-server")
- .await?,
- ) as Box<_>)
+ _: bool,
+ _: &mut AsyncApp,
+ ) -> Result<String> {
+ 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<dyn 'static + Send + Any>,
+ latest_version: String,
container_dir: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
- let latest_version = latest_version.downcast::<String>().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<LanguageServerBinary> {
- let version = version.downcast_ref::<String>().unwrap();
let server_path = container_dir.join(SERVER_PATH);
let should_install_language_server = self
@@ -129,6 +124,13 @@ impl LspAdapter for YamlLspAdapter {
) -> Option<LanguageServerBinary> {
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<Self>,
@@ -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<OsString>,
@@ -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.
@@ -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<Self>,
- _: &Arc<dyn LspAdapterDelegate>,
- ) -> Result<Option<serde_json::Value>> {
- 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<Vec<CodeActionKind>> {
- 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<Box<dyn 'static + Send + Any>> {
+ _: bool,
+ _: &mut AsyncApp,
+ ) -> Result<()> {
anyhow::bail!("SshLspAdapter does not support fetch_latest_server_version")
}
async fn fetch_server_binary(
&self,
- _: Box<dyn 'static + Send + Any>,
+ _: (),
_: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
@@ -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<Self>,
+ _: &Arc<dyn LspAdapterDelegate>,
+ ) -> Result<Option<serde_json::Value>> {
+ 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<Vec<CodeActionKind>> {
+ self.code_action_kinds.clone()
+ }
+}
+
pub fn language_server_settings<'a>(
delegate: &'a dyn LspAdapterDelegate,
language: &LanguageServerName,