Split Lsp installation methods from LspAdapter
This commit is contained in:
parent
85865fc950
commit
aae59a2842
13 changed files with 719 additions and 717 deletions
|
@ -46,7 +46,6 @@ use settings::WorktreeId;
|
|||
use smol::future::FutureExt as _;
|
||||
use std::num::NonZeroU32;
|
||||
use std::{
|
||||
any::Any,
|
||||
ffi::OsStr,
|
||||
fmt::Debug,
|
||||
hash::Hash,
|
||||
|
@ -306,127 +305,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,
|
||||
) -> 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,
|
||||
|
@ -590,40 +471,182 @@ 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 }
|
||||
}
|
||||
|
||||
let name = adapter.name();
|
||||
log::debug!("fetching latest version of language server {:?}", name.0);
|
||||
delegate.update_status(name.clone(), BinaryStatus::CheckingForUpdate);
|
||||
fn fetch_latest_server_version(
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> impl Future<Output = Result<Self::BinaryVersion>>;
|
||||
|
||||
let latest_version = adapter
|
||||
.fetch_latest_server_version(delegate.as_ref())
|
||||
.await?;
|
||||
fn will_fetch_server(
|
||||
&self,
|
||||
_: &Arc<dyn LspAdapterDelegate>,
|
||||
_: &mut AsyncApp,
|
||||
) -> Option<Task<Result<()>>> {
|
||||
None
|
||||
}
|
||||
|
||||
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;
|
||||
fn check_if_version_installed(
|
||||
&self,
|
||||
_version: &Self::BinaryVersion,
|
||||
_container_dir: &PathBuf,
|
||||
_delegate: &dyn LspAdapterDelegate,
|
||||
) -> impl Future<Output = Option<LanguageServerBinary>> {
|
||||
async { None }
|
||||
}
|
||||
|
||||
delegate.update_status(name.clone(), BinaryStatus::None);
|
||||
binary
|
||||
fn fetch_server_binary(
|
||||
&self,
|
||||
latest_version: Self::BinaryVersion,
|
||||
container_dir: PathBuf,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> impl Future<Output = Result<LanguageServerBinary>>;
|
||||
|
||||
fn cached_server_binary(
|
||||
&self,
|
||||
container_dir: PathBuf,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> impl Future<Output = Option<LanguageServerBinary>>;
|
||||
}
|
||||
#[async_trait(?Send)]
|
||||
pub trait DynLspInstaller {
|
||||
async fn try_fetch_server_binary(
|
||||
&self,
|
||||
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||
container_dir: PathBuf,
|
||||
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: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,
|
||||
cx: &'a mut AsyncApp,
|
||||
) -> Pin<Box<dyn 'a + Future<Output = Result<LanguageServerBinary>>>>;
|
||||
}
|
||||
#[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,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<LanguageServerBinary> {
|
||||
if let Some(task) = self.will_fetch_server(delegate, cx) {
|
||||
task.await?;
|
||||
}
|
||||
|
||||
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()).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,
|
||||
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(), 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(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 = self.try_fetch_server_binary( &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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2176,10 +2199,14 @@ 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,
|
||||
) -> Result<Self::BinaryVersion> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
async fn check_if_user_installed(
|
||||
|
@ -2191,27 +2218,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,
|
||||
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
async fn fetch_server_binary(
|
||||
&self,
|
||||
_: Box<dyn 'static + Send + Any>,
|
||||
_: (),
|
||||
_: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Result<LanguageServerBinary> {
|
||||
|
@ -2225,6 +2234,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()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue