gpui: Remove unsound await_on_background helper (#56132)

Lukas Wirth created

The function is unsound due to the classic fact that one can leak tasks,
sidestepping the blocking drop behavior resulting in a use after free.

Release Notes:

- N/A or Added/Fixed/Improved ...

Change summary

crates/diagnostics/src/diagnostics.rs |  70 +-
crates/gpui/src/executor.rs           |  54 --
crates/language/src/language.rs       |  30 
crates/languages/src/bash.rs          |  93 ++--
crates/languages/src/c.rs             | 140 +++---
crates/languages/src/css.rs           |  75 ++-
crates/languages/src/eslint.rs        |  96 ++--
crates/languages/src/go.rs            | 129 +++---
crates/languages/src/json.rs          | 163 ++++---
crates/languages/src/python.rs        | 555 +++++++++++++++-------------
crates/languages/src/rust.rs          | 147 ++++---
crates/languages/src/tailwind.rs      |  79 ++-
crates/languages/src/tailwindcss.rs   |  79 ++-
crates/languages/src/typescript.rs    | 109 ++--
crates/languages/src/vtsls.rs         |  85 ++--
crates/languages/src/yaml.rs          |  80 ++-
crates/project/src/lsp_store.rs       |   8 
17 files changed, 1,027 insertions(+), 965 deletions(-)

Detailed changes

crates/diagnostics/src/diagnostics.rs 🔗

@@ -1050,47 +1050,41 @@ async fn heuristic_syntactic_expand(
         let node_range = node_start..node_end;
         let row_count = node_end.row - node_start.row + 1;
         let mut ancestor_range = None;
-        cx.background_executor()
-            .await_on_background(async {
-                // Stop if we've exceeded the row count or reached an outline node. Then, find the interval
-                // of node children which contains the query range. For example, this allows just returning
-                // the header of a declaration rather than the entire declaration.
-                if row_count > max_row_count || outline_range == Some(node_range.clone()) {
-                    let mut cursor = node.walk();
-                    let mut included_child_start = None;
-                    let mut included_child_end = None;
-                    let mut previous_end = node_start;
-                    if cursor.goto_first_child() {
-                        loop {
-                            let child_node = cursor.node();
-                            let child_range =
-                                previous_end..Point::from_ts_point(child_node.end_position());
-                            if included_child_start.is_none()
-                                && child_range.contains(&input_range.start)
-                            {
-                                included_child_start = Some(child_range.start);
-                            }
-                            if child_range.contains(&input_range.end) {
-                                included_child_end = Some(child_range.end);
-                            }
-                            previous_end = child_range.end;
-                            if !cursor.goto_next_sibling() {
-                                break;
-                            }
-                        }
+        // Stop if we've exceeded the row count or reached an outline node. Then, find the interval
+        // of node children which contains the query range. For example, this allows just returning
+        // the header of a declaration rather than the entire declaration.
+        if row_count > max_row_count || outline_range == Some(node_range.clone()) {
+            let mut cursor = node.walk();
+            let mut included_child_start = None;
+            let mut included_child_end = None;
+            let mut previous_end = node_start;
+            if cursor.goto_first_child() {
+                loop {
+                    let child_node = cursor.node();
+                    let child_range = previous_end..Point::from_ts_point(child_node.end_position());
+                    if included_child_start.is_none() && child_range.contains(&input_range.start) {
+                        included_child_start = Some(child_range.start);
                     }
-                    let end = included_child_end.unwrap_or(node_range.end);
-                    if let Some(start) = included_child_start {
-                        let row_count = end.row - start.row;
-                        if row_count < max_row_count {
-                            ancestor_range = Some(Some(RangeInclusive::new(start.row, end.row)));
-                            return;
-                        }
+                    if child_range.contains(&input_range.end) {
+                        included_child_end = Some(child_range.end);
+                    }
+                    previous_end = child_range.end;
+                    if !cursor.goto_next_sibling() {
+                        break;
                     }
-                    ancestor_range = Some(None);
                 }
-            })
-            .await;
+            }
+            let end = included_child_end.unwrap_or(node_range.end);
+            if let Some(start) = included_child_start {
+                let row_count = end.row - start.row;
+                if row_count < max_row_count {
+                    ancestor_range = Some(Some(RangeInclusive::new(start.row, end.row)));
+                }
+            }
+            if ancestor_range.is_none() {
+                ancestor_range = Some(None);
+            }
+        }
         if let Some(node) = ancestor_range {
             return node;
         }

crates/gpui/src/executor.rs 🔗

@@ -115,60 +115,6 @@ impl BackgroundExecutor {
         }
     }
 
-    /// Enqueues the given future to be run to completion on a background thread and blocking the current task on it.
-    ///
-    /// This allows to spawn background work that borrows from its scope. Note that the supplied future will run to
-    /// completion before the current task is resumed, even if the current task is slated for cancellation.
-    pub async fn await_on_background<R>(&self, future: impl Future<Output = R> + Send) -> R
-    where
-        R: Send,
-    {
-        use crate::RunnableMeta;
-        use parking_lot::{Condvar, Mutex};
-
-        struct NotifyOnDrop<'a>(&'a (Condvar, Mutex<bool>));
-
-        impl Drop for NotifyOnDrop<'_> {
-            fn drop(&mut self) {
-                *self.0.1.lock() = true;
-                self.0.0.notify_all();
-            }
-        }
-
-        struct WaitOnDrop<'a>(&'a (Condvar, Mutex<bool>));
-
-        impl Drop for WaitOnDrop<'_> {
-            fn drop(&mut self) {
-                let mut done = self.0.1.lock();
-                if !*done {
-                    self.0.0.wait(&mut done);
-                }
-            }
-        }
-
-        let dispatcher = self.dispatcher.clone();
-        let location = core::panic::Location::caller();
-
-        let pair = &(Condvar::new(), Mutex::new(false));
-        let _wait_guard = WaitOnDrop(pair);
-
-        let (runnable, task) = unsafe {
-            async_task::Builder::new()
-                .metadata(RunnableMeta { location })
-                .spawn_unchecked(
-                    move |_| async {
-                        let _notify_guard = NotifyOnDrop(pair);
-                        future.await
-                    },
-                    move |runnable| {
-                        dispatcher.dispatch(runnable, Priority::default());
-                    },
-                )
-        };
-        runnable.schedule();
-        task.await
-    }
-
     /// Scoped lets you start a number of tasks and waits
     /// for all of them to complete before returning.
     pub async fn scoped<'scope, F>(&self, scheduler: F)

crates/language/src/language.rs 🔗

@@ -623,8 +623,8 @@ pub trait LspInstaller {
         &self,
         _version: &Self::BinaryVersion,
         _container_dir: &PathBuf,
-        _delegate: &dyn LspAdapterDelegate,
-    ) -> impl Send + Future<Output = Option<LanguageServerBinary>> {
+        _delegate: &Arc<dyn LspAdapterDelegate>,
+    ) -> impl Send + Future<Output = Option<LanguageServerBinary>> + use<Self> {
         async { None }
     }
 
@@ -632,8 +632,8 @@ pub trait LspInstaller {
         &self,
         latest_version: Self::BinaryVersion,
         container_dir: PathBuf,
-        delegate: &dyn LspAdapterDelegate,
-    ) -> impl Send + Future<Output = Result<LanguageServerBinary>>;
+        _delegate: &Arc<dyn LspAdapterDelegate>,
+    ) -> impl Send + Future<Output = Result<LanguageServerBinary>> + use<Self>;
 
     fn cached_server_binary(
         &self,
@@ -686,11 +686,7 @@ where
 
         if let Some(binary) = cx
             .background_executor()
-            .await_on_background(self.check_if_version_installed(
-                &latest_version,
-                &container_dir,
-                delegate.as_ref(),
-            ))
+            .spawn(self.check_if_version_installed(&latest_version, &container_dir, &delegate))
             .await
         {
             log::debug!("language server {:?} is already installed", name.0);
@@ -701,11 +697,7 @@ where
             delegate.update_status(name.clone(), BinaryStatus::Downloading);
             let binary = cx
                 .background_executor()
-                .await_on_background(self.fetch_server_binary(
-                    latest_version,
-                    container_dir,
-                    delegate.as_ref(),
-                ))
+                .spawn(self.fetch_server_binary(latest_version, container_dir, delegate))
                 .await;
 
             delegate.update_status(name.clone(), BinaryStatus::None);
@@ -1421,13 +1413,15 @@ impl LspInstaller for FakeLspAdapter {
         Some(self.language_server_binary.clone())
     }
 
-    async fn fetch_server_binary(
+    fn fetch_server_binary(
         &self,
         _: (),
         _: PathBuf,
-        _: &dyn LspAdapterDelegate,
-    ) -> Result<LanguageServerBinary> {
-        unreachable!();
+        _: &Arc<dyn LspAdapterDelegate>,
+    ) -> impl Send + Future<Output = Result<LanguageServerBinary>> + use<> {
+        async {
+            unreachable!();
+        }
     }
 
     async fn cached_server_binary(

crates/languages/src/bash.rs 🔗

@@ -6,7 +6,7 @@ use lsp::LanguageServerBinary;
 use node_runtime::{NodeRuntime, VersionStrategy};
 use project::ContextProviderWithTasks;
 use semver::Version;
-use std::{path::PathBuf, vec};
+use std::{future::Future, path::PathBuf, sync::Arc, vec};
 use task::{TaskTemplate, TaskTemplates, VariableName};
 use util::{ResultExt, maybe};
 
@@ -90,35 +90,41 @@ impl LspInstaller for BashLspAdapter {
         })
     }
 
-    async fn check_if_version_installed(
+    fn check_if_version_installed(
         &self,
         version: &Self::BinaryVersion,
         container_dir: &PathBuf,
-        delegate: &dyn LspAdapterDelegate,
-    ) -> Option<lsp::LanguageServerBinary> {
-        let server_path = container_dir
-            .join("node_modules")
-            .join(Self::NODE_MODULE_RELATIVE_SERVER_PATH);
-
-        let should_install_language_server = self
-            .node
-            .should_install_npm_package(
-                Self::PACKAGE_NAME,
-                &server_path,
-                container_dir,
-                VersionStrategy::Latest(version),
-            )
-            .await;
+        delegate: &Arc<dyn LspAdapterDelegate>,
+    ) -> impl Send + Future<Output = Option<lsp::LanguageServerBinary>> + use<> {
+        let node = self.node.clone();
+        let version = version.clone();
+        let container_dir = container_dir.clone();
+        let delegate = delegate.clone();
+
+        async move {
+            let server_path = container_dir
+                .join("node_modules")
+                .join(Self::NODE_MODULE_RELATIVE_SERVER_PATH);
 
-        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: vec![server_path.into(), "start".into()],
-            })
+            let should_install_language_server = node
+                .should_install_npm_package(
+                    Self::PACKAGE_NAME,
+                    &server_path,
+                    &container_dir,
+                    VersionStrategy::Latest(&version),
+                )
+                .await;
+
+            if should_install_language_server {
+                None
+            } else {
+                let env = delegate.shell_env().await;
+                Some(LanguageServerBinary {
+                    path: node.binary_path().await.ok()?,
+                    env: Some(env),
+                    arguments: vec![server_path.into(), "start".into()],
+                })
+            }
         }
     }
 
@@ -133,29 +139,34 @@ impl LspInstaller for BashLspAdapter {
             .await
     }
 
-    async fn fetch_server_binary(
+    fn fetch_server_binary(
         &self,
         latest_version: Self::BinaryVersion,
         container_dir: std::path::PathBuf,
-        delegate: &dyn LspAdapterDelegate,
-    ) -> Result<lsp::LanguageServerBinary> {
-        let server_path = container_dir
-            .join("node_modules")
-            .join(Self::NODE_MODULE_RELATIVE_SERVER_PATH);
+        delegate: &Arc<dyn LspAdapterDelegate>,
+    ) -> impl Send + Future<Output = Result<lsp::LanguageServerBinary>> + use<> {
+        let node = self.node.clone();
+        let delegate = delegate.clone();
 
-        self.node
-            .npm_install_packages(
+        async move {
+            let server_path = container_dir
+                .join("node_modules")
+                .join(Self::NODE_MODULE_RELATIVE_SERVER_PATH);
+            let latest_version = latest_version.to_string();
+
+            node.npm_install_packages(
                 &container_dir,
-                &[(Self::PACKAGE_NAME, &latest_version.to_string())],
+                &[(Self::PACKAGE_NAME, latest_version.as_str())],
             )
             .await?;
 
-        let env = delegate.shell_env().await;
-        Ok(LanguageServerBinary {
-            path: self.node.binary_path().await?,
-            env: Some(env),
-            arguments: vec![server_path.into(), "start".into()],
-        })
+            let env = delegate.shell_env().await;
+            Ok(LanguageServerBinary {
+                path: node.binary_path().await?,
+                env: Some(env),
+                arguments: vec![server_path.into(), "start".into()],
+            })
+        }
     }
 }
 

crates/languages/src/c.rs 🔗

@@ -9,7 +9,7 @@ use lsp::{InitializeParams, LanguageServerBinary, LanguageServerName};
 use project::lsp_store::clangd_ext;
 use serde_json::json;
 use smol::fs;
-use std::{env::consts, path::PathBuf, sync::Arc};
+use std::{env::consts, future::Future, path::PathBuf, sync::Arc};
 use util::{ResultExt, fs::remove_matching, maybe, merge_json_value_into};
 
 pub struct CLspAdapter;
@@ -66,82 +66,88 @@ impl LspInstaller for CLspAdapter {
         })
     }
 
-    async fn fetch_server_binary(
+    fn fetch_server_binary(
         &self,
         version: GitHubLspBinaryVersion,
         container_dir: PathBuf,
-        delegate: &dyn LspAdapterDelegate,
-    ) -> Result<LanguageServerBinary> {
-        ensure_arch_compatibility()?;
+        delegate: &Arc<dyn LspAdapterDelegate>,
+    ) -> impl Send + Future<Output = Result<LanguageServerBinary>> + use<> {
+        let delegate = delegate.clone();
 
-        let GitHubLspBinaryVersion {
-            name,
-            url,
-            digest: expected_digest,
-        } = version;
-        let version_dir = container_dir.join(format!("clangd_{name}"));
-        let binary_path = version_dir
-            .join("bin")
-            .join(format!("clangd{}", consts::EXE_SUFFIX));
+        async move {
+            ensure_arch_compatibility()?;
 
-        let binary = LanguageServerBinary {
-            path: binary_path.clone(),
-            env: None,
-            arguments: Default::default(),
-        };
-
-        let metadata_path = version_dir.join("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: binary_path.clone(),
-                        arguments: vec!["--version".into()],
-                        env: None,
-                    })
-                    .await
-                    .inspect_err(|err| {
-                        log::warn!("Unable to run {binary_path:?} asset, redownloading: {err:#}",)
-                    })
+            let GitHubLspBinaryVersion {
+                name,
+                url,
+                digest: expected_digest,
+            } = version;
+            let version_dir = container_dir.join(format!("clangd_{name}"));
+            let binary_path = version_dir
+                .join("bin")
+                .join(format!("clangd{}", consts::EXE_SUFFIX));
+
+            let binary = LanguageServerBinary {
+                path: binary_path.clone(),
+                env: None,
+                arguments: Default::default(),
             };
-            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);
+
+            let metadata_path = version_dir.join("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: binary_path.clone(),
+                            arguments: vec!["--version".into()],
+                            env: None,
+                        })
+                        .await
+                        .inspect_err(|err| {
+                            log::warn!(
+                                "Unable to run {binary_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 {binary_path:?} asset, downloading new asset. Expected: {expected_digest}, Got: {actual_digest}"
+                        );
                     }
-                } else {
-                    log::info!(
-                        "SHA-256 mismatch for {binary_path:?} asset, downloading new asset. Expected: {expected_digest}, Got: {actual_digest}"
-                    );
+                } else if validity_check().await.is_ok() {
+                    return Ok(binary);
                 }
-            } else if validity_check().await.is_ok() {
-                return Ok(binary);
             }
-        }
-        download_server_binary(
-            &*delegate.http_client(),
-            &url,
-            expected_digest.as_deref(),
-            &container_dir,
-            AssetKind::Zip,
-        )
-        .await?;
-        remove_matching(&container_dir, |entry| entry != version_dir).await;
-        GithubBinaryMetadata::write_to_file(
-            &GithubBinaryMetadata {
-                metadata_version: 1,
-                digest: expected_digest,
-            },
-            &metadata_path,
-        )
-        .await?;
+            download_server_binary(
+                &*delegate.http_client(),
+                &url,
+                expected_digest.as_deref(),
+                &container_dir,
+                AssetKind::Zip,
+            )
+            .await?;
+            remove_matching(&container_dir, |entry| entry != version_dir).await;
+            GithubBinaryMetadata::write_to_file(
+                &GithubBinaryMetadata {
+                    metadata_version: 1,
+                    digest: expected_digest,
+                },
+                &metadata_path,
+            )
+            .await?;
 
-        Ok(binary)
+            Ok(binary)
+        }
     }
 
     async fn cached_server_binary(

crates/languages/src/css.rs 🔗

@@ -9,6 +9,7 @@ use semver::Version;
 use serde_json::json;
 use std::{
     ffi::OsString,
+    future::Future,
     path::{Path, PathBuf},
     sync::Arc,
 };
@@ -64,55 +65,63 @@ impl LspInstaller for CssLspAdapter {
         })
     }
 
-    async fn fetch_server_binary(
+    fn fetch_server_binary(
         &self,
         latest_version: Self::BinaryVersion,
         container_dir: PathBuf,
-        _: &dyn LspAdapterDelegate,
-    ) -> Result<LanguageServerBinary> {
-        let server_path = container_dir.join(SERVER_PATH);
-        let latest_version = latest_version.to_string();
+        _: &Arc<dyn LspAdapterDelegate>,
+    ) -> impl Send + Future<Output = Result<LanguageServerBinary>> + use<> {
+        let node = self.node.clone();
 
-        self.node
-            .npm_install_packages(
+        async move {
+            let server_path = container_dir.join(SERVER_PATH);
+            let latest_version = latest_version.to_string();
+
+            node.npm_install_packages(
                 &container_dir,
                 &[(Self::PACKAGE_NAME, latest_version.as_str())],
             )
             .await?;
 
-        Ok(LanguageServerBinary {
-            path: self.node.binary_path().await?,
-            env: None,
-            arguments: server_binary_arguments(&server_path),
-        })
+            Ok(LanguageServerBinary {
+                path: node.binary_path().await?,
+                env: None,
+                arguments: server_binary_arguments(&server_path),
+            })
+        }
     }
 
-    async fn check_if_version_installed(
+    fn check_if_version_installed(
         &self,
         version: &Self::BinaryVersion,
         container_dir: &PathBuf,
-        _: &dyn LspAdapterDelegate,
-    ) -> Option<LanguageServerBinary> {
-        let server_path = container_dir.join(SERVER_PATH);
+        _: &Arc<dyn LspAdapterDelegate>,
+    ) -> impl Send + Future<Output = Option<LanguageServerBinary>> + use<> {
+        let node = self.node.clone();
+        let version = version.clone();
+        let container_dir = container_dir.clone();
 
-        let should_install_language_server = self
-            .node
-            .should_install_npm_package(
-                Self::PACKAGE_NAME,
-                &server_path,
-                container_dir,
-                VersionStrategy::Latest(version),
-            )
-            .await;
+        async move {
+            let server_path = container_dir.join(SERVER_PATH);
 
-        if should_install_language_server {
-            None
-        } else {
-            Some(LanguageServerBinary {
-                path: self.node.binary_path().await.ok()?,
-                env: None,
-                arguments: server_binary_arguments(&server_path),
-            })
+            let should_install_language_server = node
+                .should_install_npm_package(
+                    Self::PACKAGE_NAME,
+                    &server_path,
+                    &container_dir,
+                    VersionStrategy::Latest(&version),
+                )
+                .await;
+
+            if should_install_language_server {
+                None
+            } else {
+                Some(LanguageServerBinary {
+                    path: node.binary_path().await.ok()?,
+                    env: None,
+                    arguments: server_binary_arguments(&server_path),
+                })
+            }
         }
     }
 

crates/languages/src/eslint.rs 🔗

@@ -17,6 +17,7 @@ use settings::SettingsLocation;
 use smol::{fs, stream::StreamExt};
 use std::{
     ffi::OsString,
+    future::Future,
     path::{Path, PathBuf},
     sync::Arc,
 };
@@ -99,60 +100,63 @@ impl LspInstaller for EsLintLspAdapter {
         })
     }
 
-    async fn fetch_server_binary(
+    fn fetch_server_binary(
         &self,
         version: GitHubLspBinaryVersion,
         container_dir: PathBuf,
-        delegate: &dyn LspAdapterDelegate,
-    ) -> Result<LanguageServerBinary> {
-        let destination_path = Self::build_destination_path(&container_dir);
-        let server_path = destination_path.join(Self::SERVER_PATH);
-
-        if fs::metadata(&server_path).await.is_err() {
-            remove_matching(&container_dir, |_| true).await;
-
-            download_server_binary(
-                &*delegate.http_client(),
-                &version.url,
-                None,
-                &destination_path,
-                Self::GITHUB_ASSET_KIND,
-            )
-            .await?;
-
-            let mut dir = fs::read_dir(&destination_path).await?;
-            let first = dir.next().await.context("missing first file")??;
-            let repo_root = destination_path.join("vscode-eslint");
-            fs::rename(first.path(), &repo_root).await?;
-
-            #[cfg(target_os = "windows")]
-            {
-                handle_symlink(
-                    repo_root.join("$shared"),
-                    repo_root.join("client").join("src").join("shared"),
-                )
-                .await?;
-                handle_symlink(
-                    repo_root.join("$shared"),
-                    repo_root.join("server").join("src").join("shared"),
+        delegate: &Arc<dyn LspAdapterDelegate>,
+    ) -> impl Send + Future<Output = Result<LanguageServerBinary>> + use<> {
+        let delegate = delegate.clone();
+        let node = self.node.clone();
+
+        async move {
+            let destination_path = Self::build_destination_path(&container_dir);
+            let server_path = destination_path.join(Self::SERVER_PATH);
+
+            if fs::metadata(&server_path).await.is_err() {
+                remove_matching(&container_dir, |_| true).await;
+
+                download_server_binary(
+                    &*delegate.http_client(),
+                    &version.url,
+                    None,
+                    &destination_path,
+                    Self::GITHUB_ASSET_KIND,
                 )
                 .await?;
-            }
 
-            self.node
-                .run_npm_subcommand(Some(&repo_root), "install", &[])
-                .await?;
+                let mut dir = fs::read_dir(&destination_path).await?;
+                let first = dir.next().await.context("missing first file")??;
+                let repo_root = destination_path.join("vscode-eslint");
+                fs::rename(first.path(), &repo_root).await?;
 
-            self.node
-                .run_npm_subcommand(Some(&repo_root), "run-script", &["compile"])
-                .await?;
-        }
+                #[cfg(target_os = "windows")]
+                {
+                    handle_symlink(
+                        repo_root.join("$shared"),
+                        repo_root.join("client").join("src").join("shared"),
+                    )
+                    .await?;
+                    handle_symlink(
+                        repo_root.join("$shared"),
+                        repo_root.join("server").join("src").join("shared"),
+                    )
+                    .await?;
+                }
 
-        Ok(LanguageServerBinary {
-            path: self.node.binary_path().await?,
-            env: None,
-            arguments: eslint_server_binary_arguments(&server_path),
-        })
+                node.run_npm_subcommand(Some(&repo_root), "install", &[])
+                    .await?;
+
+                node.run_npm_subcommand(Some(&repo_root), "run-script", &["compile"])
+                    .await?;
+            }
+
+            Ok(LanguageServerBinary {
+                path: node.binary_path().await?,
+                env: None,
+                arguments: eslint_server_binary_arguments(&server_path),
+            })
+        }
     }
 
     async fn cached_server_binary(

crates/languages/src/go.rs 🔗

@@ -19,6 +19,7 @@ use smol::fs;
 use std::{
     borrow::Cow,
     ffi::{OsStr, OsString},
+    future::Future,
     ops::Range,
     path::{Path, PathBuf},
     process::Output,
@@ -117,75 +118,79 @@ impl LspInstaller for GoLspAdapter {
         })
     }
 
-    async fn fetch_server_binary(
+    fn fetch_server_binary(
         &self,
         version: Option<String>,
         container_dir: PathBuf,
-        delegate: &dyn LspAdapterDelegate,
-    ) -> Result<LanguageServerBinary> {
-        let go = delegate.which("go".as_ref()).await.unwrap_or("go".into());
-        let go_version_output = util::command::new_command(&go)
-            .args(["version"])
-            .output()
-            .await
-            .context("failed to get go version via `go version` command`")?;
-        let go_version = parse_version_output(&go_version_output)?;
-
-        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()
-            {
-                remove_matching(&container_dir, |entry| {
-                    entry != binary_path && entry.file_name() != Some(OsStr::new("gobin"))
-                })
-                .await;
+        delegate: &Arc<dyn LspAdapterDelegate>,
+    ) -> impl Send + Future<Output = Result<LanguageServerBinary>> + use<> {
+        let delegate = delegate.clone();
+
+        async move {
+            let go = delegate.which("go".as_ref()).await.unwrap_or("go".into());
+            let go_version_output = util::command::new_command(&go)
+                .args(["version"])
+                .output()
+                .await
+                .context("failed to get go version via `go version` command`")?;
+            let go_version = parse_version_output(&go_version_output)?;
+
+            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()
+                {
+                    remove_matching(&container_dir, |entry| {
+                        entry != binary_path && entry.file_name() != Some(OsStr::new("gobin"))
+                    })
+                    .await;
 
-                return Ok(LanguageServerBinary {
-                    path: binary_path.to_path_buf(),
-                    arguments: server_binary_arguments(),
-                    env: None,
-                });
+                    return Ok(LanguageServerBinary {
+                        path: binary_path.to_path_buf(),
+                        arguments: server_binary_arguments(),
+                        env: None,
+                    });
+                }
+            } else if let Some(path) = get_cached_server_binary(&container_dir).await {
+                return Ok(path);
             }
-        } else if let Some(path) = get_cached_server_binary(&container_dir).await {
-            return Ok(path);
-        }
 
-        let gobin_dir = container_dir.join("gobin");
-        fs::create_dir_all(&gobin_dir).await?;
-        let install_output = util::command::new_command(go)
-            .env("GO111MODULE", "on")
-            .env("GOBIN", &gobin_dir)
-            .args(["install", "golang.org/x/tools/gopls@latest"])
-            .output()
-            .await?;
-
-        if !install_output.status.success() {
-            log::error!(
-                "failed to install gopls via `go install`. stdout: {:?}, stderr: {:?}",
-                String::from_utf8_lossy(&install_output.stdout),
-                String::from_utf8_lossy(&install_output.stderr)
-            );
-            anyhow::bail!(
-                "failed to install gopls with `go install`. Is `go` installed and in the PATH? Check logs for more information."
-            );
-        }
+            let gobin_dir = container_dir.join("gobin");
+            fs::create_dir_all(&gobin_dir).await?;
+            let install_output = util::command::new_command(go)
+                .env("GO111MODULE", "on")
+                .env("GOBIN", &gobin_dir)
+                .args(["install", "golang.org/x/tools/gopls@latest"])
+                .output()
+                .await?;
+
+            if !install_output.status.success() {
+                log::error!(
+                    "failed to install gopls via `go install`. stdout: {:?}, stderr: {:?}",
+                    String::from_utf8_lossy(&install_output.stdout),
+                    String::from_utf8_lossy(&install_output.stderr)
+                );
+                anyhow::bail!(
+                    "failed to install gopls with `go install`. Is `go` installed and in the PATH? Check logs for more information."
+                );
+            }
 
-        let installed_binary_path = gobin_dir.join(BINARY);
-        let version_output = util::command::new_command(&installed_binary_path)
-            .arg("version")
-            .output()
-            .await
-            .context("failed to run installed gopls binary")?;
-        let gopls_version = parse_version_output(&version_output)?;
-        let binary_path = container_dir.join(format!("gopls_{gopls_version}_go_{go_version}"));
-        fs::rename(&installed_binary_path, &binary_path).await?;
-
-        Ok(LanguageServerBinary {
-            path: binary_path.to_path_buf(),
-            arguments: server_binary_arguments(),
-            env: None,
-        })
+            let installed_binary_path = gobin_dir.join(BINARY);
+            let version_output = util::command::new_command(&installed_binary_path)
+                .arg("version")
+                .output()
+                .await
+                .context("failed to run installed gopls binary")?;
+            let gopls_version = parse_version_output(&version_output)?;
+            let binary_path = container_dir.join(format!("gopls_{gopls_version}_go_{go_version}"));
+            fs::rename(&installed_binary_path, &binary_path).await?;
+
+            Ok(LanguageServerBinary {
+                path: binary_path.to_path_buf(),
+                arguments: server_binary_arguments(),
+                env: None,
+            })
+        }
     }
 
     async fn cached_server_binary(

crates/languages/src/json.rs 🔗

@@ -24,6 +24,7 @@ use std::{
     borrow::Cow,
     env::consts,
     ffi::OsString,
+    future::Future,
     path::{Path, PathBuf},
     str::FromStr,
     sync::Arc,
@@ -176,56 +177,64 @@ impl LspInstaller for JsonLspAdapter {
         })
     }
 
-    async fn check_if_version_installed(
+    fn check_if_version_installed(
         &self,
         version: &Self::BinaryVersion,
         container_dir: &PathBuf,
-        _: &dyn LspAdapterDelegate,
-    ) -> Option<LanguageServerBinary> {
-        let server_path = container_dir.join(SERVER_PATH);
-
-        let should_install_language_server = self
-            .node
-            .should_install_npm_package(
-                Self::PACKAGE_NAME,
-                &server_path,
-                container_dir,
-                VersionStrategy::Latest(version),
-            )
-            .await;
-
-        if should_install_language_server {
-            None
-        } else {
-            Some(LanguageServerBinary {
-                path: self.node.binary_path().await.ok()?,
-                env: None,
-                arguments: server_binary_arguments(&server_path),
-            })
+        _: &Arc<dyn LspAdapterDelegate>,
+    ) -> impl Send + Future<Output = Option<LanguageServerBinary>> + use<> {
+        let node = self.node.clone();
+        let version = version.clone();
+        let container_dir = container_dir.clone();
+
+        async move {
+            let server_path = container_dir.join(SERVER_PATH);
+
+            let should_install_language_server = node
+                .should_install_npm_package(
+                    Self::PACKAGE_NAME,
+                    &server_path,
+                    &container_dir,
+                    VersionStrategy::Latest(&version),
+                )
+                .await;
+
+            if should_install_language_server {
+                None
+            } else {
+                Some(LanguageServerBinary {
+                    path: node.binary_path().await.ok()?,
+                    env: None,
+                    arguments: server_binary_arguments(&server_path),
+                })
+            }
         }
     }
 
-    async fn fetch_server_binary(
+    fn fetch_server_binary(
         &self,
         latest_version: Self::BinaryVersion,
         container_dir: PathBuf,
-        _: &dyn LspAdapterDelegate,
-    ) -> Result<LanguageServerBinary> {
-        let server_path = container_dir.join(SERVER_PATH);
-        let latest_version = latest_version.to_string();
+        _: &Arc<dyn LspAdapterDelegate>,
+    ) -> impl Send + Future<Output = Result<LanguageServerBinary>> + use<> {
+        let node = self.node.clone();
 
-        self.node
-            .npm_install_packages(
+        async move {
+            let server_path = container_dir.join(SERVER_PATH);
+            let latest_version = latest_version.to_string();
+
+            node.npm_install_packages(
                 &container_dir,
                 &[(Self::PACKAGE_NAME, latest_version.as_str())],
             )
             .await?;
 
-        Ok(LanguageServerBinary {
-            path: self.node.binary_path().await?,
-            env: None,
-            arguments: server_binary_arguments(&server_path),
-        })
+            Ok(LanguageServerBinary {
+                path: node.binary_path().await?,
+                env: None,
+                arguments: server_binary_arguments(&server_path),
+            })
+        }
     }
 
     async fn cached_server_binary(
@@ -478,51 +487,55 @@ impl LspInstaller for NodeVersionAdapter {
         })
     }
 
-    async fn fetch_server_binary(
+    fn fetch_server_binary(
         &self,
         latest_version: GitHubLspBinaryVersion,
         container_dir: PathBuf,
-        delegate: &dyn LspAdapterDelegate,
-    ) -> Result<LanguageServerBinary> {
-        let version = &latest_version;
-        let destination_path = container_dir.join(format!(
-            "{}-{}{}",
-            Self::SERVER_NAME,
-            version.name,
-            std::env::consts::EXE_SUFFIX
-        ));
-        let destination_container_path =
-            container_dir.join(format!("{}-{}-tmp", Self::SERVER_NAME, version.name));
-        if fs::metadata(&destination_path).await.is_err() {
-            let mut response = delegate
-                .http_client()
-                .get(&version.url, Default::default(), true)
-                .await
-                .context("downloading release")?;
-            if version.url.ends_with(".zip") {
-                extract_zip(&destination_container_path, response.body_mut()).await?;
-            } else if version.url.ends_with(".tar.gz") {
-                let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
-                let archive = Archive::new(decompressed_bytes);
-                archive.unpack(&destination_container_path).await?;
-            }
+        delegate: &Arc<dyn LspAdapterDelegate>,
+    ) -> impl Send + Future<Output = Result<LanguageServerBinary>> + use<> {
+        let delegate = delegate.clone();
+
+        async move {
+            let version = &latest_version;
+            let destination_path = container_dir.join(format!(
+                "{}-{}{}",
+                Self::SERVER_NAME,
+                version.name,
+                std::env::consts::EXE_SUFFIX
+            ));
+            let destination_container_path =
+                container_dir.join(format!("{}-{}-tmp", Self::SERVER_NAME, version.name));
+            if fs::metadata(&destination_path).await.is_err() {
+                let mut response = delegate
+                    .http_client()
+                    .get(&version.url, Default::default(), true)
+                    .await
+                    .context("downloading release")?;
+                if version.url.ends_with(".zip") {
+                    extract_zip(&destination_container_path, response.body_mut()).await?;
+                } else if version.url.ends_with(".tar.gz") {
+                    let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
+                    let archive = Archive::new(decompressed_bytes);
+                    archive.unpack(&destination_container_path).await?;
+                }
 
-            fs::copy(
-                destination_container_path.join(format!(
-                    "{}{}",
-                    Self::SERVER_NAME,
-                    std::env::consts::EXE_SUFFIX
-                )),
-                &destination_path,
-            )
-            .await?;
-            remove_matching(&container_dir, |entry| entry != destination_path).await;
+                fs::copy(
+                    destination_container_path.join(format!(
+                        "{}{}",
+                        Self::SERVER_NAME,
+                        std::env::consts::EXE_SUFFIX
+                    )),
+                    &destination_path,
+                )
+                .await?;
+                remove_matching(&container_dir, |entry| entry != destination_path).await;
+            }
+            Ok(LanguageServerBinary {
+                path: destination_path,
+                env: None,
+                arguments: Default::default(),
+            })
         }
-        Ok(LanguageServerBinary {
-            path: destination_path,
-            env: None,
-            arguments: Default::default(),
-        })
     }
 
     async fn cached_server_binary(

crates/languages/src/python.rs 🔗

@@ -1,5 +1,5 @@
+use anyhow::Result;
 use anyhow::{Context as _, ensure};
-use anyhow::{Result, anyhow};
 use async_trait::async_trait;
 use collections::HashMap;
 use futures::future::BoxFuture;
@@ -45,6 +45,7 @@ use std::str::FromStr;
 use std::{
     borrow::Cow,
     fmt::Write,
+    future::Future,
     path::{Path, PathBuf},
     sync::Arc,
 };
@@ -447,92 +448,98 @@ impl LspInstaller for TyLspAdapter {
         None
     }
 
-    async fn fetch_server_binary(
+    fn fetch_server_binary(
         &self,
         latest_version: Self::BinaryVersion,
         container_dir: PathBuf,
-        delegate: &dyn LspAdapterDelegate,
-    ) -> Result<LanguageServerBinary> {
-        let GitHubLspBinaryVersion {
-            name,
-            url,
-            digest: expected_digest,
-        } = latest_version;
-        let destination_path = container_dir.join(format!("ty-{name}"));
-
-        async_fs::create_dir_all(&destination_path).await?;
-
-        let server_path = match Self::GITHUB_ASSET_KIND {
-            AssetKind::TarGz | AssetKind::TarBz2 | AssetKind::Gz => destination_path
-                .join(Self::build_asset_name()?.0)
-                .join("ty"),
-            AssetKind::Zip => destination_path.clone().join("ty.exe"),
-        };
+        delegate: &Arc<dyn LspAdapterDelegate>,
+    ) -> impl Send + Future<Output = Result<LanguageServerBinary>> + use<> {
+        let delegate = delegate.clone();
 
-        let binary = LanguageServerBinary {
-            path: server_path.clone(),
-            env: None,
-            arguments: vec!["server".into()],
-        };
+        async move {
+            let GitHubLspBinaryVersion {
+                name,
+                url,
+                digest: expected_digest,
+            } = latest_version;
+            let destination_path = container_dir.join(format!("ty-{name}"));
 
-        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:#}",)
-                    })
+            async_fs::create_dir_all(&destination_path).await?;
+
+            let server_path = match Self::GITHUB_ASSET_KIND {
+                AssetKind::TarGz | AssetKind::TarBz2 | AssetKind::Gz => destination_path
+                    .join(Self::build_asset_name()?.0)
+                    .join("ty"),
+                AssetKind::Zip => destination_path.clone().join("ty.exe"),
             };
-            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);
+
+            let binary = LanguageServerBinary {
+                path: server_path.clone(),
+                env: None,
+                arguments: vec!["server".into()],
+            };
+
+            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 {
-                    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);
                 }
-            } else if validity_check().await.is_ok() {
-                return Ok(binary);
             }
-        }
 
-        download_server_binary(
-            &*delegate.http_client(),
-            &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?;
+            download_server_binary(
+                &*delegate.http_client(),
+                &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: vec!["server".into()],
-        })
+            Ok(LanguageServerBinary {
+                path: server_path,
+                env: None,
+                arguments: vec!["server".into()],
+            })
+        }
     }
 
     async fn cached_server_binary(
@@ -777,57 +784,67 @@ impl LspInstaller for PyrightLspAdapter {
         }
     }
 
-    async fn fetch_server_binary(
+    fn fetch_server_binary(
         &self,
         latest_version: Self::BinaryVersion,
         container_dir: PathBuf,
-        delegate: &dyn LspAdapterDelegate,
-    ) -> Result<LanguageServerBinary> {
-        let server_path = container_dir.join(Self::SERVER_PATH);
-        let latest_version = latest_version.to_string();
+        delegate: &Arc<dyn LspAdapterDelegate>,
+    ) -> impl Send + Future<Output = Result<LanguageServerBinary>> + use<> {
+        let delegate = delegate.clone();
+        let node = self.node.clone();
 
-        self.node
-            .npm_install_packages(
+        async move {
+            let server_path = container_dir.join(Self::SERVER_PATH);
+            let latest_version = latest_version.to_string();
+
+            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: vec![server_path.into(), "--stdio".into()],
-        })
+            let env = delegate.shell_env().await;
+            Ok(LanguageServerBinary {
+                path: node.binary_path().await?,
+                env: Some(env),
+                arguments: vec![server_path.into(), "--stdio".into()],
+            })
+        }
     }
 
-    async fn check_if_version_installed(
+    fn check_if_version_installed(
         &self,
         version: &Self::BinaryVersion,
         container_dir: &PathBuf,
-        delegate: &dyn LspAdapterDelegate,
-    ) -> Option<LanguageServerBinary> {
-        let server_path = container_dir.join(Self::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: vec![server_path.into(), "--stdio".into()],
-            })
+        delegate: &Arc<dyn LspAdapterDelegate>,
+    ) -> impl Send + Future<Output = Option<LanguageServerBinary>> + use<> {
+        let delegate = delegate.clone();
+        let node = self.node.clone();
+        let version = version.clone();
+        let container_dir = container_dir.clone();
+
+        async move {
+            let server_path = container_dir.join(Self::SERVER_PATH);
+
+            let should_install_language_server = 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: node.binary_path().await.ok()?,
+                    env: Some(env),
+                    arguments: vec![server_path.into(), "--stdio".into()],
+                })
+            }
         }
     }
 
@@ -1949,46 +1966,50 @@ impl LspInstaller for PyLspAdapter {
         Ok(())
     }
 
-    async fn fetch_server_binary(
+    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_command(pip_path.as_path())
-                .arg("install")
-                .arg("python-lsp-server[all]")
-                .arg("--upgrade")
-                .output()
-                .await?
-                .status
-                .success(),
-            "python-lsp-server[all] installation failed"
-        );
-        ensure!(
-            util::command::new_command(pip_path)
-                .arg("install")
-                .arg("pylsp-mypy")
-                .arg("--upgrade")
-                .output()
-                .await?
-                .status
-                .success(),
-            "pylsp-mypy installation failed"
-        );
-        let pylsp = venv.join(BINARY_DIR).join("pylsp");
-        ensure!(
-            delegate.which(pylsp.as_os_str()).await.is_some(),
-            "pylsp installation was incomplete"
-        );
-        Ok(LanguageServerBinary {
-            path: pylsp,
-            env: None,
-            arguments: vec![],
-        })
+        delegate: &Arc<dyn LspAdapterDelegate>,
+    ) -> impl Send + Future<Output = Result<LanguageServerBinary>> + use<> {
+        let delegate = delegate.clone();
+
+        async move {
+            let venv = Self::ensure_venv(delegate.as_ref()).await?;
+            let pip_path = venv.join(BINARY_DIR).join("pip3");
+            ensure!(
+                util::command::new_command(pip_path.as_path())
+                    .arg("install")
+                    .arg("python-lsp-server[all]")
+                    .arg("--upgrade")
+                    .output()
+                    .await?
+                    .status
+                    .success(),
+                "python-lsp-server[all] installation failed"
+            );
+            ensure!(
+                util::command::new_command(pip_path)
+                    .arg("install")
+                    .arg("pylsp-mypy")
+                    .arg("--upgrade")
+                    .output()
+                    .await?
+                    .status
+                    .success(),
+                "pylsp-mypy installation failed"
+            );
+            let pylsp = venv.join(BINARY_DIR).join("pylsp");
+            ensure!(
+                delegate.which(pylsp.as_os_str()).await.is_some(),
+                "pylsp installation was incomplete"
+            );
+            Ok(LanguageServerBinary {
+                path: pylsp,
+                env: None,
+                arguments: vec![],
+            })
+        }
     }
 
     async fn cached_server_binary(
@@ -2229,57 +2250,67 @@ impl LspInstaller for BasedPyrightLspAdapter {
         }
     }
 
-    async fn fetch_server_binary(
+    fn fetch_server_binary(
         &self,
         latest_version: Self::BinaryVersion,
         container_dir: PathBuf,
-        delegate: &dyn LspAdapterDelegate,
-    ) -> Result<LanguageServerBinary> {
-        let server_path = container_dir.join(Self::SERVER_PATH);
-        let latest_version = latest_version.to_string();
+        delegate: &Arc<dyn LspAdapterDelegate>,
+    ) -> impl Send + Future<Output = Result<LanguageServerBinary>> + use<> {
+        let delegate = delegate.clone();
+        let node = self.node.clone();
 
-        self.node
-            .npm_install_packages(
+        async move {
+            let server_path = container_dir.join(Self::SERVER_PATH);
+            let latest_version = latest_version.to_string();
+
+            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: vec![server_path.into(), "--stdio".into()],
-        })
+            let env = delegate.shell_env().await;
+            Ok(LanguageServerBinary {
+                path: node.binary_path().await?,
+                env: Some(env),
+                arguments: vec![server_path.into(), "--stdio".into()],
+            })
+        }
     }
 
-    async fn check_if_version_installed(
+    fn check_if_version_installed(
         &self,
         version: &Self::BinaryVersion,
         container_dir: &PathBuf,
-        delegate: &dyn LspAdapterDelegate,
-    ) -> Option<LanguageServerBinary> {
-        let server_path = container_dir.join(Self::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: vec![server_path.into(), "--stdio".into()],
-            })
+        delegate: &Arc<dyn LspAdapterDelegate>,
+    ) -> impl Send + Future<Output = Option<LanguageServerBinary>> + use<> {
+        let delegate = delegate.clone();
+        let node = self.node.clone();
+        let version = version.clone();
+        let container_dir = container_dir.clone();
+
+        async move {
+            let server_path = container_dir.join(Self::SERVER_PATH);
+
+            let should_install_language_server = 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: node.binary_path().await.ok()?,
+                    env: Some(env),
+                    arguments: vec![server_path.into(), "--stdio".into()],
+                })
+            }
         }
     }
 
@@ -2566,89 +2597,95 @@ impl LspInstaller for RuffLspAdapter {
         })
     }
 
-    async fn fetch_server_binary(
+    fn fetch_server_binary(
         &self,
         latest_version: GitHubLspBinaryVersion,
         container_dir: PathBuf,
-        delegate: &dyn LspAdapterDelegate,
-    ) -> Result<LanguageServerBinary> {
-        let GitHubLspBinaryVersion {
-            name,
-            url,
-            digest: expected_digest,
-        } = latest_version;
-        let destination_path = container_dir.join(format!("ruff-{name}"));
-        let server_path = match Self::GITHUB_ASSET_KIND {
-            AssetKind::TarGz | AssetKind::TarBz2 | AssetKind::Gz => destination_path
-                .join(Self::build_asset_name()?.0)
-                .join("ruff"),
-            AssetKind::Zip => destination_path.clone().join("ruff.exe"),
-        };
+        delegate: &Arc<dyn LspAdapterDelegate>,
+    ) -> impl Send + Future<Output = Result<LanguageServerBinary>> + use<> {
+        let delegate = delegate.clone();
 
-        let binary = LanguageServerBinary {
-            path: server_path.clone(),
-            env: None,
-            arguments: vec!["server".into()],
-        };
+        async move {
+            let GitHubLspBinaryVersion {
+                name,
+                url,
+                digest: expected_digest,
+            } = latest_version;
+            let destination_path = container_dir.join(format!("ruff-{name}"));
+            let server_path = match Self::GITHUB_ASSET_KIND {
+                AssetKind::TarGz | AssetKind::TarBz2 | AssetKind::Gz => destination_path
+                    .join(Self::build_asset_name()?.0)
+                    .join("ruff"),
+                AssetKind::Zip => destination_path.clone().join("ruff.exe"),
+            };
 
-        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:#}",)
-                    })
+            let binary = LanguageServerBinary {
+                path: server_path.clone(),
+                env: None,
+                arguments: vec!["server".into()],
             };
-            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);
+
+            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 {
-                    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);
                 }
-            } else if validity_check().await.is_ok() {
-                return Ok(binary);
             }
-        }
 
-        download_server_binary(
-            &*delegate.http_client(),
-            &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?;
+            download_server_binary(
+                &*delegate.http_client(),
+                &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: vec!["server".into()],
-        })
+            Ok(LanguageServerBinary {
+                path: server_path,
+                env: None,
+                arguments: vec!["server".into()],
+            })
+        }
     }
 
     async fn cached_server_binary(

crates/languages/src/rust.rs 🔗

@@ -19,6 +19,7 @@ use smallvec::SmallVec;
 use smol::fs::{self};
 use std::cmp::Reverse;
 use std::fmt::Display;
+use std::future::Future;
 use std::ops::Range;
 use std::{
     borrow::Cow,
@@ -729,87 +730,93 @@ impl LspInstaller for RustLspAdapter {
         })
     }
 
-    async fn fetch_server_binary(
+    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::TarBz2 | AssetKind::Gz => destination_path.clone(), // Tar and gzip extract in place.
-            AssetKind::Zip => destination_path.clone().join("rust-analyzer.exe"), // zip contains a .exe
-        };
+        delegate: &Arc<dyn LspAdapterDelegate>,
+    ) -> impl Send + Future<Output = Result<LanguageServerBinary>> + use<> {
+        let delegate = delegate.clone();
 
-        let binary = LanguageServerBinary {
-            path: server_path.clone(),
-            env: None,
-            arguments: Default::default(),
-        };
+        async move {
+            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::TarBz2 | 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 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:#}",)
-                    })
+            let binary = LanguageServerBinary {
+                path: server_path.clone(),
+                env: None,
+                arguments: Default::default(),
             };
-            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);
+
+            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 {
-                    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);
                 }
-            } else if validity_check().await.is_ok() {
-                return Ok(binary);
             }
-        }
 
-        download_server_binary(
-            &*delegate.http_client(),
-            &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?;
+            download_server_binary(
+                &*delegate.http_client(),
+                &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(),
-        })
+            Ok(LanguageServerBinary {
+                path: server_path,
+                env: None,
+                arguments: Default::default(),
+            })
+        }
     }
 
     async fn cached_server_binary(

crates/languages/src/tailwind.rs 🔗

@@ -10,6 +10,7 @@ use semver::Version;
 use serde_json::{Value, json};
 use std::{
     ffi::OsString,
+    future::Future,
     path::{Path, PathBuf},
     sync::Arc,
 };
@@ -69,55 +70,63 @@ impl LspInstaller for TailwindLspAdapter {
         })
     }
 
-    async fn fetch_server_binary(
+    fn fetch_server_binary(
         &self,
         latest_version: Self::BinaryVersion,
         container_dir: PathBuf,
-        _: &dyn LspAdapterDelegate,
-    ) -> Result<LanguageServerBinary> {
-        let server_path = container_dir.join(SERVER_PATH);
-        let latest_version = latest_version.to_string();
+        _: &Arc<dyn LspAdapterDelegate>,
+    ) -> impl Send + Future<Output = Result<LanguageServerBinary>> + use<> {
+        let node = self.node.clone();
 
-        self.node
-            .npm_install_packages(
+        async move {
+            let server_path = container_dir.join(SERVER_PATH);
+            let latest_version = latest_version.to_string();
+
+            node.npm_install_packages(
                 &container_dir,
                 &[(Self::PACKAGE_NAME, latest_version.as_str())],
             )
             .await?;
 
-        Ok(LanguageServerBinary {
-            path: self.node.binary_path().await?,
-            env: None,
-            arguments: server_binary_arguments(&server_path),
-        })
+            Ok(LanguageServerBinary {
+                path: node.binary_path().await?,
+                env: None,
+                arguments: server_binary_arguments(&server_path),
+            })
+        }
     }
 
-    async fn check_if_version_installed(
+    fn check_if_version_installed(
         &self,
         version: &Self::BinaryVersion,
         container_dir: &PathBuf,
-        _: &dyn LspAdapterDelegate,
-    ) -> Option<LanguageServerBinary> {
-        let server_path = container_dir.join(SERVER_PATH);
-
-        let should_install_language_server = self
-            .node
-            .should_install_npm_package(
-                Self::PACKAGE_NAME,
-                &server_path,
-                container_dir,
-                VersionStrategy::Latest(version),
-            )
-            .await;
-
-        if should_install_language_server {
-            None
-        } else {
-            Some(LanguageServerBinary {
-                path: self.node.binary_path().await.ok()?,
-                env: None,
-                arguments: server_binary_arguments(&server_path),
-            })
+        _: &Arc<dyn LspAdapterDelegate>,
+    ) -> impl Send + Future<Output = Option<LanguageServerBinary>> + use<> {
+        let node = self.node.clone();
+        let version = version.clone();
+        let container_dir = container_dir.clone();
+
+        async move {
+            let server_path = container_dir.join(SERVER_PATH);
+
+            let should_install_language_server = node
+                .should_install_npm_package(
+                    Self::PACKAGE_NAME,
+                    &server_path,
+                    &container_dir,
+                    VersionStrategy::Latest(&version),
+                )
+                .await;
+
+            if should_install_language_server {
+                None
+            } else {
+                Some(LanguageServerBinary {
+                    path: node.binary_path().await.ok()?,
+                    env: None,
+                    arguments: server_binary_arguments(&server_path),
+                })
+            }
         }
     }
 

crates/languages/src/tailwindcss.rs 🔗

@@ -9,6 +9,7 @@ use semver::Version;
 use serde_json::json;
 use std::{
     ffi::OsString,
+    future::Future,
     path::{Path, PathBuf},
     sync::Arc,
 };
@@ -65,55 +66,63 @@ impl LspInstaller for TailwindCssLspAdapter {
         })
     }
 
-    async fn fetch_server_binary(
+    fn fetch_server_binary(
         &self,
         latest_version: Self::BinaryVersion,
         container_dir: PathBuf,
-        _: &dyn LspAdapterDelegate,
-    ) -> Result<LanguageServerBinary> {
-        let server_path = container_dir.join(SERVER_PATH);
-        let latest_version = latest_version.to_string();
+        _: &Arc<dyn LspAdapterDelegate>,
+    ) -> impl Send + Future<Output = Result<LanguageServerBinary>> + use<> {
+        let node = self.node.clone();
 
-        self.node
-            .npm_install_packages(
+        async move {
+            let server_path = container_dir.join(SERVER_PATH);
+            let latest_version = latest_version.to_string();
+
+            node.npm_install_packages(
                 &container_dir,
                 &[(Self::PACKAGE_NAME, latest_version.as_str())],
             )
             .await?;
 
-        Ok(LanguageServerBinary {
-            path: self.node.binary_path().await?,
-            env: None,
-            arguments: server_binary_arguments(&server_path),
-        })
+            Ok(LanguageServerBinary {
+                path: node.binary_path().await?,
+                env: None,
+                arguments: server_binary_arguments(&server_path),
+            })
+        }
     }
 
-    async fn check_if_version_installed(
+    fn check_if_version_installed(
         &self,
         version: &Self::BinaryVersion,
         container_dir: &PathBuf,
-        _: &dyn LspAdapterDelegate,
-    ) -> Option<LanguageServerBinary> {
-        let server_path = container_dir.join(SERVER_PATH);
-
-        let should_install_language_server = self
-            .node
-            .should_install_npm_package(
-                Self::PACKAGE_NAME,
-                &server_path,
-                container_dir,
-                VersionStrategy::Latest(version),
-            )
-            .await;
-
-        if should_install_language_server {
-            None
-        } else {
-            Some(LanguageServerBinary {
-                path: self.node.binary_path().await.ok()?,
-                env: None,
-                arguments: server_binary_arguments(&server_path),
-            })
+        _: &Arc<dyn LspAdapterDelegate>,
+    ) -> impl Send + Future<Output = Option<LanguageServerBinary>> + use<> {
+        let node = self.node.clone();
+        let version = version.clone();
+        let container_dir = container_dir.clone();
+
+        async move {
+            let server_path = container_dir.join(SERVER_PATH);
+
+            let should_install_language_server = node
+                .should_install_npm_package(
+                    Self::PACKAGE_NAME,
+                    &server_path,
+                    &container_dir,
+                    VersionStrategy::Latest(&version),
+                )
+                .await;
+
+            if should_install_language_server {
+                None
+            } else {
+                Some(LanguageServerBinary {
+                    path: node.binary_path().await.ok()?,
+                    env: None,
+                    arguments: server_binary_arguments(&server_path),
+                })
+            }
         }
     }
 

crates/languages/src/typescript.rs 🔗

@@ -18,6 +18,7 @@ use smol::lock::RwLock;
 use std::{
     borrow::Cow,
     ffi::OsString,
+    future::Future,
     path::{Path, PathBuf},
     sync::{Arc, LazyLock},
 };
@@ -669,76 +670,80 @@ impl LspInstaller for TypeScriptLspAdapter {
         })
     }
 
-    async fn check_if_version_installed(
+    fn check_if_version_installed(
         &self,
         version: &Self::BinaryVersion,
         container_dir: &PathBuf,
-        _: &dyn LspAdapterDelegate,
-    ) -> Option<LanguageServerBinary> {
-        let server_path = container_dir.join(Self::NEW_SERVER_PATH);
+        _: &Arc<dyn LspAdapterDelegate>,
+    ) -> impl Send + Future<Output = Option<LanguageServerBinary>> + use<> {
+        let node = self.node.clone();
+        let typescript_version = version.typescript_version.clone();
+        let server_version = version.server_version.clone();
+        let container_dir = container_dir.clone();
+
+        async move {
+            let server_path = container_dir.join(Self::NEW_SERVER_PATH);
+
+            if node
+                .should_install_npm_package(
+                    Self::PACKAGE_NAME,
+                    &server_path,
+                    &container_dir,
+                    VersionStrategy::Latest(&typescript_version),
+                )
+                .await
+            {
+                return None;
+            }
 
-        if self
-            .node
-            .should_install_npm_package(
-                Self::PACKAGE_NAME,
-                &server_path,
-                container_dir,
-                VersionStrategy::Latest(&version.typescript_version),
-            )
-            .await
-        {
-            return None;
-        }
+            if node
+                .should_install_npm_package(
+                    Self::SERVER_PACKAGE_NAME,
+                    &server_path,
+                    &container_dir,
+                    VersionStrategy::Latest(&server_version),
+                )
+                .await
+            {
+                return None;
+            }
 
-        if self
-            .node
-            .should_install_npm_package(
-                Self::SERVER_PACKAGE_NAME,
-                &server_path,
-                container_dir,
-                VersionStrategy::Latest(&version.server_version),
-            )
-            .await
-        {
-            return None;
+            Some(LanguageServerBinary {
+                path: node.binary_path().await.ok()?,
+                env: None,
+                arguments: typescript_server_binary_arguments(&server_path),
+            })
         }
-
-        Some(LanguageServerBinary {
-            path: self.node.binary_path().await.ok()?,
-            env: None,
-            arguments: typescript_server_binary_arguments(&server_path),
-        })
     }
 
-    async fn fetch_server_binary(
+    fn fetch_server_binary(
         &self,
         latest_version: Self::BinaryVersion,
         container_dir: PathBuf,
-        _: &dyn LspAdapterDelegate,
-    ) -> Result<LanguageServerBinary> {
-        let server_path = container_dir.join(Self::NEW_SERVER_PATH);
+        _: &Arc<dyn LspAdapterDelegate>,
+    ) -> impl Send + Future<Output = Result<LanguageServerBinary>> + use<> {
+        let node = self.node.clone();
+
+        async move {
+            let server_path = container_dir.join(Self::NEW_SERVER_PATH);
+            let typescript_version = latest_version.typescript_version.to_string();
+            let server_version = latest_version.server_version.to_string();
 
-        self.node
-            .npm_install_packages(
+            node.npm_install_packages(
                 &container_dir,
                 &[
-                    (
-                        Self::PACKAGE_NAME,
-                        &latest_version.typescript_version.to_string(),
-                    ),
-                    (
-                        Self::SERVER_PACKAGE_NAME,
-                        &latest_version.server_version.to_string(),
-                    ),
+                    (Self::PACKAGE_NAME, typescript_version.as_str()),
+                    (Self::SERVER_PACKAGE_NAME, server_version.as_str()),
                 ],
             )
             .await?;
 
-        Ok(LanguageServerBinary {
-            path: self.node.binary_path().await?,
-            env: None,
-            arguments: typescript_server_binary_arguments(&server_path),
-        })
+            Ok(LanguageServerBinary {
+                path: node.binary_path().await?,
+                env: None,
+                arguments: typescript_server_binary_arguments(&server_path),
+            })
+        }
     }
 
     async fn cached_server_binary(

crates/languages/src/vtsls.rs 🔗

@@ -15,6 +15,7 @@ use serde_json::json;
 use settings::update_settings_file;
 use std::{
     ffi::OsString,
+    future::Future,
     path::{Path, PathBuf},
     sync::{Arc, LazyLock},
 };
@@ -123,54 +124,56 @@ impl LspInstaller for VtslsLspAdapter {
         })
     }
 
-    async fn fetch_server_binary(
+    fn fetch_server_binary(
         &self,
         latest_version: Self::BinaryVersion,
         container_dir: PathBuf,
-        _: &dyn LspAdapterDelegate,
-    ) -> Result<LanguageServerBinary> {
-        let server_path = container_dir.join(Self::SERVER_PATH);
+        _: &Arc<dyn LspAdapterDelegate>,
+    ) -> impl Send + Future<Output = Result<LanguageServerBinary>> + use<> {
+        let node = self.node.clone();
+
+        async move {
+            let server_path = container_dir.join(Self::SERVER_PATH);
+
+            let typescript_version = latest_version.typescript_version.to_string();
+            let server_version = latest_version.server_version.to_string();
+
+            let mut packages_to_install = Vec::new();
+
+            if node
+                .should_install_npm_package(
+                    Self::PACKAGE_NAME,
+                    &server_path,
+                    &container_dir,
+                    VersionStrategy::Latest(&latest_version.server_version),
+                )
+                .await
+            {
+                packages_to_install.push((Self::PACKAGE_NAME, server_version.as_str()));
+            }
 
-        let typescript_version = latest_version.typescript_version.to_string();
-        let server_version = latest_version.server_version.to_string();
+            if node
+                .should_install_npm_package(
+                    Self::TYPESCRIPT_PACKAGE_NAME,
+                    &container_dir.join(Self::TYPESCRIPT_TSDK_PATH),
+                    &container_dir,
+                    VersionStrategy::Latest(&latest_version.typescript_version),
+                )
+                .await
+            {
+                packages_to_install
+                    .push((Self::TYPESCRIPT_PACKAGE_NAME, typescript_version.as_str()));
+            }
 
-        let mut packages_to_install = Vec::new();
+            node.npm_install_packages(&container_dir, &packages_to_install)
+                .await?;
 
-        if self
-            .node
-            .should_install_npm_package(
-                Self::PACKAGE_NAME,
-                &server_path,
-                &container_dir,
-                VersionStrategy::Latest(&latest_version.server_version),
-            )
-            .await
-        {
-            packages_to_install.push((Self::PACKAGE_NAME, server_version.as_str()));
+            Ok(LanguageServerBinary {
+                path: node.binary_path().await?,
+                env: None,
+                arguments: typescript_server_binary_arguments(&server_path),
+            })
         }
-
-        if self
-            .node
-            .should_install_npm_package(
-                Self::TYPESCRIPT_PACKAGE_NAME,
-                &container_dir.join(Self::TYPESCRIPT_TSDK_PATH),
-                &container_dir,
-                VersionStrategy::Latest(&latest_version.typescript_version),
-            )
-            .await
-        {
-            packages_to_install.push((Self::TYPESCRIPT_PACKAGE_NAME, typescript_version.as_str()));
-        }
-
-        self.node
-            .npm_install_packages(&container_dir, &packages_to_install)
-            .await?;
-
-        Ok(LanguageServerBinary {
-            path: self.node.binary_path().await?,
-            env: None,
-            arguments: typescript_server_binary_arguments(&server_path),
-        })
     }
 
     async fn cached_server_binary(

crates/languages/src/yaml.rs 🔗

@@ -12,6 +12,7 @@ use serde_json::Value;
 use settings::{Settings, SettingsLocation};
 use std::{
     ffi::OsString,
+    future::Future,
     path::{Path, PathBuf},
     sync::Arc,
 };
@@ -65,54 +66,63 @@ impl LspInstaller for YamlLspAdapter {
         })
     }
 
-    async fn fetch_server_binary(
+    fn fetch_server_binary(
         &self,
         latest_version: Self::BinaryVersion,
         container_dir: PathBuf,
-        _: &dyn LspAdapterDelegate,
-    ) -> Result<LanguageServerBinary> {
-        let server_path = container_dir.join(SERVER_PATH);
+        _: &Arc<dyn LspAdapterDelegate>,
+    ) -> impl Send + Future<Output = Result<LanguageServerBinary>> + use<> {
+        let node = self.node.clone();
 
-        self.node
-            .npm_install_packages(
+        async move {
+            let server_path = container_dir.join(SERVER_PATH);
+            let latest_version = latest_version.to_string();
+
+            node.npm_install_packages(
                 &container_dir,
-                &[(Self::PACKAGE_NAME, &latest_version.to_string())],
+                &[(Self::PACKAGE_NAME, latest_version.as_str())],
             )
             .await?;
 
-        Ok(LanguageServerBinary {
-            path: self.node.binary_path().await?,
-            env: None,
-            arguments: server_binary_arguments(&server_path),
-        })
+            Ok(LanguageServerBinary {
+                path: node.binary_path().await?,
+                env: None,
+                arguments: server_binary_arguments(&server_path),
+            })
+        }
     }
 
-    async fn check_if_version_installed(
+    fn check_if_version_installed(
         &self,
         version: &Self::BinaryVersion,
         container_dir: &PathBuf,
-        _: &dyn LspAdapterDelegate,
-    ) -> Option<LanguageServerBinary> {
-        let server_path = container_dir.join(SERVER_PATH);
-
-        let should_install_language_server = self
-            .node
-            .should_install_npm_package(
-                Self::PACKAGE_NAME,
-                &server_path,
-                container_dir,
-                VersionStrategy::Latest(version),
-            )
-            .await;
-
-        if should_install_language_server {
-            None
-        } else {
-            Some(LanguageServerBinary {
-                path: self.node.binary_path().await.ok()?,
-                env: None,
-                arguments: server_binary_arguments(&server_path),
-            })
+        _: &Arc<dyn LspAdapterDelegate>,
+    ) -> impl Send + Future<Output = Option<LanguageServerBinary>> + use<> {
+        let node = self.node.clone();
+        let version = version.clone();
+        let container_dir = container_dir.clone();
+
+        async move {
+            let server_path = container_dir.join(SERVER_PATH);
+
+            let should_install_language_server = node
+                .should_install_npm_package(
+                    Self::PACKAGE_NAME,
+                    &server_path,
+                    &container_dir,
+                    VersionStrategy::Latest(&version),
+                )
+                .await;
+
+            if should_install_language_server {
+                None
+            } else {
+                Some(LanguageServerBinary {
+                    path: node.binary_path().await.ok()?,
+                    env: None,
+                    arguments: server_binary_arguments(&server_path),
+                })
+            }
         }
     }
 

crates/project/src/lsp_store.rs 🔗

@@ -14349,13 +14349,13 @@ impl LspInstaller for SshLspAdapter {
         anyhow::bail!("SshLspAdapter does not support fetch_latest_server_version")
     }
 
-    async fn fetch_server_binary(
+    fn fetch_server_binary(
         &self,
         _: (),
         _: PathBuf,
-        _: &dyn LspAdapterDelegate,
-    ) -> Result<LanguageServerBinary> {
-        anyhow::bail!("SshLspAdapter does not support fetch_server_binary")
+        _: &Arc<dyn LspAdapterDelegate>,
+    ) -> impl Send + Future<Output = Result<LanguageServerBinary>> + use<> {
+        async { anyhow::bail!("SshLspAdapter does not support fetch_server_binary") }
     }
 }