language: Move language server update to background when it takes too long (#43164)

Closes https://github.com/zed-industries/zed/issues/42360

If updating a language server takes longer than 10 seconds, we now fall
back to launching the currently installed version (if exists) and
continue downloading the update in the background.

Release Notes:

- Improved language server updates for slow connection, now Zed launches
existing server if the update is taking too long.

---------

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
This commit is contained in:
Smit Barmase
2025-11-21 14:19:55 +05:30
committed by GitHub
co-authored by Piotr Osiewicz
parent bb514c158e
commit e2f6422b3e
7 changed files with 161 additions and 157 deletions
@@ -1,17 +1,16 @@
use std::ops::Range;
use std::path::PathBuf;
use std::pin::Pin;
use std::sync::Arc;
use anyhow::{Context as _, Result};
use async_trait::async_trait;
use collections::{HashMap, HashSet};
use extension::{Extension, ExtensionLanguageServerProxy, WorktreeDelegate};
use futures::{Future, FutureExt, future::join_all};
use futures::{FutureExt, future::join_all, lock::OwnedMutexGuard};
use gpui::{App, AppContext, AsyncApp, Task};
use language::{
BinaryStatus, CodeLabel, DynLspInstaller, HighlightId, Language, LanguageName, LspAdapter,
LspAdapterDelegate, Toolchain,
BinaryStatus, CodeLabel, DynLspInstaller, HighlightId, Language, LanguageName,
LanguageServerBinaryLocations, LspAdapter, LspAdapterDelegate, Toolchain,
};
use lsp::{
CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerName,
@@ -155,47 +154,51 @@ impl ExtensionLspAdapter {
#[async_trait(?Send)]
impl DynLspInstaller for ExtensionLspAdapter {
fn get_language_server_command<'a>(
fn get_language_server_command(
self: Arc<Self>,
delegate: Arc<dyn LspAdapterDelegate>,
_: Option<Toolchain>,
_: LanguageServerBinaryOptions,
_: &'a mut Option<(bool, LanguageServerBinary)>,
_: &'a mut AsyncApp,
) -> Pin<Box<dyn 'a + Future<Output = Result<LanguageServerBinary>>>> {
_: OwnedMutexGuard<Option<(bool, LanguageServerBinary)>>,
_: AsyncApp,
) -> LanguageServerBinaryLocations {
async move {
let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _;
let command = self
.extension
.language_server_command(
self.language_server_id.clone(),
self.language_name.clone(),
delegate,
)
.await?;
let ret = maybe!(async move {
let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _;
let command = self
.extension
.language_server_command(
self.language_server_id.clone(),
self.language_name.clone(),
delegate,
)
.await?;
let path = self.extension.path_from_extension(command.command.as_ref());
let path = self.extension.path_from_extension(command.command.as_ref());
// TODO: This should now be done via the `zed::make_file_executable` function in
// Zed extension API, but we're leaving these existing usages in place temporarily
// to avoid any compatibility issues between Zed and the extension versions.
//
// We can remove once the following extension versions no longer see any use:
// - toml@0.0.2
// - zig@0.0.1
if ["toml", "zig"].contains(&self.extension.manifest().id.as_ref())
&& path.starts_with(&self.extension.work_dir())
{
make_file_executable(&path)
.await
.context("failed to set file permissions")?;
}
// TODO: This should now be done via the `zed::make_file_executable` function in
// Zed extension API, but we're leaving these existing usages in place temporarily
// to avoid any compatibility issues between Zed and the extension versions.
//
// We can remove once the following extension versions no longer see any use:
// - toml@0.0.2
// - zig@0.0.1
if ["toml", "zig"].contains(&self.extension.manifest().id.as_ref())
&& path.starts_with(&self.extension.work_dir())
{
make_file_executable(&path)
.await
.context("failed to set file permissions")?;
}
Ok(LanguageServerBinary {
path,
arguments: command.args.into_iter().map(|arg| arg.into()).collect(),
env: Some(command.env.into_iter().collect()),
Ok(LanguageServerBinary {
path,
arguments: command.args.into_iter().map(|arg| arg.into()).collect(),
env: Some(command.env.into_iter().collect()),
})
})
.await;
(ret, None)
}
.boxed_local()
}