Add installation test binaries for all remaining adapters

Julia created

Change summary

crates/language/src/language.rs        |  18 +++-
crates/project/src/project.rs          |   9 ++
crates/zed/src/languages/elixir.rs     |  39 ++++++---
crates/zed/src/languages/go.rs         |  66 +++++++++++------
crates/zed/src/languages/html.rs       |  72 +++++++++++--------
crates/zed/src/languages/json.rs       |  64 ++++++++++------
crates/zed/src/languages/lua.rs        |  66 +++++++++++------
crates/zed/src/languages/python.rs     |  70 +++++++++++-------
crates/zed/src/languages/ruby.rs       |   8 ++
crates/zed/src/languages/rust.rs       |  39 +++++++---
crates/zed/src/languages/typescript.rs | 101 ++++++++++++++++++---------
crates/zed/src/languages/yaml.rs       |  69 +++++++++++-------
12 files changed, 395 insertions(+), 226 deletions(-)

Detailed changes

crates/language/src/language.rs 🔗

@@ -160,6 +160,10 @@ impl CachedLspAdapter {
             .await
     }
 
+    pub fn can_be_reinstalled(&self) -> bool {
+        self.adapter.can_be_reinstalled()
+    }
+
     pub async fn installation_test_binary(
         &self,
         container_dir: PathBuf,
@@ -249,12 +253,14 @@ pub trait LspAdapter: 'static + Send + Sync {
         delegate: &dyn LspAdapterDelegate,
     ) -> Option<LanguageServerBinary>;
 
+    fn can_be_reinstalled(&self) -> bool {
+        true
+    }
+
     async fn installation_test_binary(
         &self,
-        _container_dir: PathBuf,
-    ) -> Option<LanguageServerBinary> {
-        unimplemented!();
-    }
+        container_dir: PathBuf,
+    ) -> Option<LanguageServerBinary>;
 
     async fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
 
@@ -1667,6 +1673,10 @@ impl LspAdapter for Arc<FakeLspAdapter> {
         unreachable!();
     }
 
+    async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
+        unreachable!();
+    }
+
     async fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
 
     async fn disk_based_diagnostic_sources(&self) -> Vec<String> {

crates/project/src/project.rs 🔗

@@ -3057,6 +3057,14 @@ impl Project {
         installation_test_binary: Option<LanguageServerBinary>,
         cx: &mut ModelContext<Self>,
     ) {
+        if !adapter.can_be_reinstalled() {
+            log::info!(
+                "Validation check requested for {:?} but it cannot be reinstalled",
+                adapter.name.0
+            );
+            return;
+        }
+
         cx.spawn(|this, mut cx| async move {
             log::info!("About to spawn test binary");
 
@@ -3086,6 +3094,7 @@ impl Project {
 
                     _ = timeout => {
                         log::info!("test binary time-ed out, this counts as a success");
+                        _ = process.kill();
                     }
                 }
             } else {

crates/zed/src/languages/elixir.rs 🔗

@@ -140,20 +140,14 @@ impl LspAdapter for ElixirLspAdapter {
         container_dir: PathBuf,
         _: &dyn LspAdapterDelegate,
     ) -> Option<LanguageServerBinary> {
-        (|| async move {
-            let mut last = None;
-            let mut entries = fs::read_dir(&container_dir).await?;
-            while let Some(entry) = entries.next().await {
-                last = Some(entry?.path());
-            }
-            last.map(|path| LanguageServerBinary {
-                path,
-                arguments: vec![],
-            })
-            .ok_or_else(|| anyhow!("no cached binary"))
-        })()
-        .await
-        .log_err()
+        get_cached_server_binary(container_dir).await
+    }
+
+    async fn installation_test_binary(
+        &self,
+        container_dir: PathBuf,
+    ) -> Option<LanguageServerBinary> {
+        get_cached_server_binary(container_dir).await
     }
 
     async fn label_for_completion(
@@ -239,3 +233,20 @@ impl LspAdapter for ElixirLspAdapter {
         })
     }
 }
+
+async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
+    (|| async move {
+        let mut last = None;
+        let mut entries = fs::read_dir(&container_dir).await?;
+        while let Some(entry) = entries.next().await {
+            last = Some(entry?.path());
+        }
+        last.map(|path| LanguageServerBinary {
+            path,
+            arguments: vec![],
+        })
+        .ok_or_else(|| anyhow!("no cached binary"))
+    })()
+    .await
+    .log_err()
+}

crates/zed/src/languages/go.rs 🔗

@@ -149,32 +149,19 @@ impl super::LspAdapter for GoLspAdapter {
         container_dir: PathBuf,
         _: &dyn LspAdapterDelegate,
     ) -> Option<LanguageServerBinary> {
-        (|| async move {
-            let mut last_binary_path = None;
-            let mut entries = fs::read_dir(&container_dir).await?;
-            while let Some(entry) = entries.next().await {
-                let entry = entry?;
-                if entry.file_type().await?.is_file()
-                    && entry
-                        .file_name()
-                        .to_str()
-                        .map_or(false, |name| name.starts_with("gopls_"))
-                {
-                    last_binary_path = Some(entry.path());
-                }
-            }
+        get_cached_server_binary(container_dir).await
+    }
 
-            if let Some(path) = last_binary_path {
-                Ok(LanguageServerBinary {
-                    path,
-                    arguments: server_binary_arguments(),
-                })
-            } else {
-                Err(anyhow!("no cached binary"))
-            }
-        })()
-        .await
-        .log_err()
+    async fn installation_test_binary(
+        &self,
+        container_dir: PathBuf,
+    ) -> Option<LanguageServerBinary> {
+        get_cached_server_binary(container_dir)
+            .await
+            .map(|mut binary| {
+                binary.arguments = vec!["--help".into()];
+                binary
+            })
     }
 
     async fn label_for_completion(
@@ -337,6 +324,35 @@ impl super::LspAdapter for GoLspAdapter {
     }
 }
 
+async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
+    (|| async move {
+        let mut last_binary_path = None;
+        let mut entries = fs::read_dir(&container_dir).await?;
+        while let Some(entry) = entries.next().await {
+            let entry = entry?;
+            if entry.file_type().await?.is_file()
+                && entry
+                    .file_name()
+                    .to_str()
+                    .map_or(false, |name| name.starts_with("gopls_"))
+            {
+                last_binary_path = Some(entry.path());
+            }
+        }
+
+        if let Some(path) = last_binary_path {
+            Ok(LanguageServerBinary {
+                path,
+                arguments: server_binary_arguments(),
+            })
+        } else {
+            Err(anyhow!("no cached binary"))
+        }
+    })()
+    .await
+    .log_err()
+}
+
 fn adjust_runs(
     delta: usize,
     mut runs: Vec<(Range<usize>, HighlightId)>,

crates/zed/src/languages/html.rs 🔗

@@ -14,6 +14,9 @@ use std::{
 };
 use util::ResultExt;
 
+const SERVER_PATH: &'static str =
+    "node_modules/vscode-langservers-extracted/bin/vscode-html-language-server";
+
 fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
     vec![server_path.into(), "--stdio".into()]
 }
@@ -23,9 +26,6 @@ pub struct HtmlLspAdapter {
 }
 
 impl HtmlLspAdapter {
-    const SERVER_PATH: &'static str =
-        "node_modules/vscode-langservers-extracted/bin/vscode-html-language-server";
-
     pub fn new(node: Arc<NodeRuntime>) -> Self {
         HtmlLspAdapter { node }
     }
@@ -55,7 +55,7 @@ impl LspAdapter for HtmlLspAdapter {
         _: &dyn LspAdapterDelegate,
     ) -> Result<LanguageServerBinary> {
         let version = version.downcast::<String>().unwrap();
-        let server_path = container_dir.join(Self::SERVER_PATH);
+        let server_path = container_dir.join(SERVER_PATH);
 
         if fs::metadata(&server_path).await.is_err() {
             self.node
@@ -77,31 +77,14 @@ impl LspAdapter for HtmlLspAdapter {
         container_dir: PathBuf,
         _: &dyn LspAdapterDelegate,
     ) -> Option<LanguageServerBinary> {
-        (|| async move {
-            let mut last_version_dir = None;
-            let mut entries = fs::read_dir(&container_dir).await?;
-            while let Some(entry) = entries.next().await {
-                let entry = entry?;
-                if entry.file_type().await?.is_dir() {
-                    last_version_dir = Some(entry.path());
-                }
-            }
-            let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
-            let server_path = last_version_dir.join(Self::SERVER_PATH);
-            if server_path.exists() {
-                Ok(LanguageServerBinary {
-                    path: self.node.binary_path().await?,
-                    arguments: server_binary_arguments(&server_path),
-                })
-            } else {
-                Err(anyhow!(
-                    "missing executable in directory {:?}",
-                    last_version_dir
-                ))
-            }
-        })()
-        .await
-        .log_err()
+        get_cached_server_binary(container_dir, &self.node).await
+    }
+
+    async fn installation_test_binary(
+        &self,
+        container_dir: PathBuf,
+    ) -> Option<LanguageServerBinary> {
+        get_cached_server_binary(container_dir, &self.node).await
     }
 
     async fn initialization_options(&self) -> Option<serde_json::Value> {
@@ -110,3 +93,34 @@ impl LspAdapter for HtmlLspAdapter {
         }))
     }
 }
+
+async fn get_cached_server_binary(
+    container_dir: PathBuf,
+    node: &NodeRuntime,
+) -> Option<LanguageServerBinary> {
+    (|| async move {
+        let mut last_version_dir = None;
+        let mut entries = fs::read_dir(&container_dir).await?;
+        while let Some(entry) = entries.next().await {
+            let entry = entry?;
+            if entry.file_type().await?.is_dir() {
+                last_version_dir = Some(entry.path());
+            }
+        }
+        let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
+        let server_path = last_version_dir.join(SERVER_PATH);
+        if server_path.exists() {
+            Ok(LanguageServerBinary {
+                path: node.binary_path().await?,
+                arguments: server_binary_arguments(&server_path),
+            })
+        } else {
+            Err(anyhow!(
+                "missing executable in directory {:?}",
+                last_version_dir
+            ))
+        }
+    })()
+    .await
+    .log_err()
+}

crates/zed/src/languages/json.rs 🔗

@@ -83,32 +83,14 @@ impl LspAdapter for JsonLspAdapter {
         container_dir: PathBuf,
         _: &dyn LspAdapterDelegate,
     ) -> Option<LanguageServerBinary> {
-        (|| async move {
-            let mut last_version_dir = None;
-            let mut entries = fs::read_dir(&container_dir).await?;
-            while let Some(entry) = entries.next().await {
-                let entry = entry?;
-                if entry.file_type().await?.is_dir() {
-                    last_version_dir = Some(entry.path());
-                }
-            }
+        get_cached_server_binary(container_dir, &self.node).await
+    }
 
-            let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
-            let server_path = last_version_dir.join(SERVER_PATH);
-            if server_path.exists() {
-                Ok(LanguageServerBinary {
-                    path: self.node.binary_path().await?,
-                    arguments: server_binary_arguments(&server_path),
-                })
-            } else {
-                Err(anyhow!(
-                    "missing executable in directory {:?}",
-                    last_version_dir
-                ))
-            }
-        })()
-        .await
-        .log_err()
+    async fn installation_test_binary(
+        &self,
+        container_dir: PathBuf,
+    ) -> Option<LanguageServerBinary> {
+        get_cached_server_binary(container_dir, &self.node).await
     }
 
     async fn initialization_options(&self) -> Option<serde_json::Value> {
@@ -161,6 +143,38 @@ impl LspAdapter for JsonLspAdapter {
     }
 }
 
+async fn get_cached_server_binary(
+    container_dir: PathBuf,
+    node: &NodeRuntime,
+) -> Option<LanguageServerBinary> {
+    (|| async move {
+        let mut last_version_dir = None;
+        let mut entries = fs::read_dir(&container_dir).await?;
+        while let Some(entry) = entries.next().await {
+            let entry = entry?;
+            if entry.file_type().await?.is_dir() {
+                last_version_dir = Some(entry.path());
+            }
+        }
+
+        let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
+        let server_path = last_version_dir.join(SERVER_PATH);
+        if server_path.exists() {
+            Ok(LanguageServerBinary {
+                path: node.binary_path().await?,
+                arguments: server_binary_arguments(&server_path),
+            })
+        } else {
+            Err(anyhow!(
+                "missing executable in directory {:?}",
+                last_version_dir
+            ))
+        }
+    })()
+    .await
+    .log_err()
+}
+
 fn schema_file_match(path: &Path) -> &Path {
     path.strip_prefix(path.parent().unwrap().parent().unwrap())
         .unwrap()

crates/zed/src/languages/lua.rs 🔗

@@ -92,31 +92,47 @@ impl super::LspAdapter for LuaLspAdapter {
         container_dir: PathBuf,
         _: &dyn LspAdapterDelegate,
     ) -> Option<LanguageServerBinary> {
-        async_iife!({
-            let mut last_binary_path = None;
-            let mut entries = fs::read_dir(&container_dir).await?;
-            while let Some(entry) = entries.next().await {
-                let entry = entry?;
-                if entry.file_type().await?.is_file()
-                    && entry
-                        .file_name()
-                        .to_str()
-                        .map_or(false, |name| name == "lua-language-server")
-                {
-                    last_binary_path = Some(entry.path());
-                }
-            }
+        get_cached_server_binary(container_dir).await
+    }
 
-            if let Some(path) = last_binary_path {
-                Ok(LanguageServerBinary {
-                    path,
-                    arguments: server_binary_arguments(),
-                })
-            } else {
-                Err(anyhow!("no cached binary"))
-            }
-        })
-        .await
-        .log_err()
+    async fn installation_test_binary(
+        &self,
+        container_dir: PathBuf,
+    ) -> Option<LanguageServerBinary> {
+        get_cached_server_binary(container_dir)
+            .await
+            .map(|mut binary| {
+                binary.arguments = vec!["--version".into()];
+                binary
+            })
     }
 }
+
+async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
+    async_iife!({
+        let mut last_binary_path = None;
+        let mut entries = fs::read_dir(&container_dir).await?;
+        while let Some(entry) = entries.next().await {
+            let entry = entry?;
+            if entry.file_type().await?.is_file()
+                && entry
+                    .file_name()
+                    .to_str()
+                    .map_or(false, |name| name == "lua-language-server")
+            {
+                last_binary_path = Some(entry.path());
+            }
+        }
+
+        if let Some(path) = last_binary_path {
+            Ok(LanguageServerBinary {
+                path,
+                arguments: server_binary_arguments(),
+            })
+        } else {
+            Err(anyhow!("no cached binary"))
+        }
+    })
+    .await
+    .log_err()
+}

crates/zed/src/languages/python.rs 🔗

@@ -13,6 +13,8 @@ use std::{
 };
 use util::ResultExt;
 
+const SERVER_PATH: &'static str = "node_modules/pyright/langserver.index.js";
+
 fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
     vec![server_path.into(), "--stdio".into()]
 }
@@ -22,8 +24,6 @@ pub struct PythonLspAdapter {
 }
 
 impl PythonLspAdapter {
-    const SERVER_PATH: &'static str = "node_modules/pyright/langserver.index.js";
-
     pub fn new(node: Arc<NodeRuntime>) -> Self {
         PythonLspAdapter { node }
     }
@@ -49,7 +49,7 @@ impl LspAdapter for PythonLspAdapter {
         _: &dyn LspAdapterDelegate,
     ) -> Result<LanguageServerBinary> {
         let version = version.downcast::<String>().unwrap();
-        let server_path = container_dir.join(Self::SERVER_PATH);
+        let server_path = container_dir.join(SERVER_PATH);
 
         if fs::metadata(&server_path).await.is_err() {
             self.node
@@ -68,31 +68,14 @@ impl LspAdapter for PythonLspAdapter {
         container_dir: PathBuf,
         _: &dyn LspAdapterDelegate,
     ) -> Option<LanguageServerBinary> {
-        (|| async move {
-            let mut last_version_dir = None;
-            let mut entries = fs::read_dir(&container_dir).await?;
-            while let Some(entry) = entries.next().await {
-                let entry = entry?;
-                if entry.file_type().await?.is_dir() {
-                    last_version_dir = Some(entry.path());
-                }
-            }
-            let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
-            let server_path = last_version_dir.join(Self::SERVER_PATH);
-            if server_path.exists() {
-                Ok(LanguageServerBinary {
-                    path: self.node.binary_path().await?,
-                    arguments: server_binary_arguments(&server_path),
-                })
-            } else {
-                Err(anyhow!(
-                    "missing executable in directory {:?}",
-                    last_version_dir
-                ))
-            }
-        })()
-        .await
-        .log_err()
+        get_cached_server_binary(container_dir, &self.node).await
+    }
+
+    async fn installation_test_binary(
+        &self,
+        container_dir: PathBuf,
+    ) -> Option<LanguageServerBinary> {
+        get_cached_server_binary(container_dir, &self.node).await
     }
 
     async fn process_completion(&self, item: &mut lsp::CompletionItem) {
@@ -171,6 +154,37 @@ impl LspAdapter for PythonLspAdapter {
     }
 }
 
+async fn get_cached_server_binary(
+    container_dir: PathBuf,
+    node: &NodeRuntime,
+) -> Option<LanguageServerBinary> {
+    (|| async move {
+        let mut last_version_dir = None;
+        let mut entries = fs::read_dir(&container_dir).await?;
+        while let Some(entry) = entries.next().await {
+            let entry = entry?;
+            if entry.file_type().await?.is_dir() {
+                last_version_dir = Some(entry.path());
+            }
+        }
+        let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
+        let server_path = last_version_dir.join(SERVER_PATH);
+        if server_path.exists() {
+            Ok(LanguageServerBinary {
+                path: node.binary_path().await?,
+                arguments: server_binary_arguments(&server_path),
+            })
+        } else {
+            Err(anyhow!(
+                "missing executable in directory {:?}",
+                last_version_dir
+            ))
+        }
+    })()
+    .await
+    .log_err()
+}
+
 #[cfg(test)]
 mod tests {
     use gpui::{ModelContext, TestAppContext};

crates/zed/src/languages/ruby.rs 🔗

@@ -39,6 +39,14 @@ impl LspAdapter for RubyLanguageServer {
         })
     }
 
+    fn can_be_reinstalled(&self) -> bool {
+        false
+    }
+
+    async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
+        None
+    }
+
     async fn label_for_completion(
         &self,
         item: &lsp::CompletionItem,

crates/zed/src/languages/rust.rs 🔗

@@ -79,20 +79,19 @@ impl LspAdapter for RustLspAdapter {
         container_dir: PathBuf,
         _: &dyn LspAdapterDelegate,
     ) -> Option<LanguageServerBinary> {
-        (|| async move {
-            let mut last = None;
-            let mut entries = fs::read_dir(&container_dir).await?;
-            while let Some(entry) = entries.next().await {
-                last = Some(entry?.path());
-            }
+        get_cached_server_binary(container_dir).await
+    }
 
-            anyhow::Ok(LanguageServerBinary {
-                path: last.ok_or_else(|| anyhow!("no cached binary"))?,
-                arguments: Default::default(),
+    async fn installation_test_binary(
+        &self,
+        container_dir: PathBuf,
+    ) -> Option<LanguageServerBinary> {
+        get_cached_server_binary(container_dir)
+            .await
+            .map(|mut binary| {
+                binary.arguments = vec!["--help".into()];
+                binary
             })
-        })()
-        .await
-        .log_err()
     }
 
     async fn disk_based_diagnostic_sources(&self) -> Vec<String> {
@@ -259,6 +258,22 @@ impl LspAdapter for RustLspAdapter {
         })
     }
 }
+async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
+    (|| async move {
+        let mut last = None;
+        let mut entries = fs::read_dir(&container_dir).await?;
+        while let Some(entry) = entries.next().await {
+            last = Some(entry?.path());
+        }
+
+        anyhow::Ok(LanguageServerBinary {
+            path: last.ok_or_else(|| anyhow!("no cached binary"))?,
+            arguments: Default::default(),
+        })
+    })()
+    .await
+    .log_err()
+}
 
 #[cfg(test)]
 mod tests {

crates/zed/src/languages/typescript.rs 🔗

@@ -104,28 +104,14 @@ impl LspAdapter for TypeScriptLspAdapter {
         container_dir: PathBuf,
         _: &dyn LspAdapterDelegate,
     ) -> Option<LanguageServerBinary> {
-        (|| async move {
-            let old_server_path = container_dir.join(Self::OLD_SERVER_PATH);
-            let new_server_path = container_dir.join(Self::NEW_SERVER_PATH);
-            if new_server_path.exists() {
-                Ok(LanguageServerBinary {
-                    path: self.node.binary_path().await?,
-                    arguments: typescript_server_binary_arguments(&new_server_path),
-                })
-            } else if old_server_path.exists() {
-                Ok(LanguageServerBinary {
-                    path: self.node.binary_path().await?,
-                    arguments: typescript_server_binary_arguments(&old_server_path),
-                })
-            } else {
-                Err(anyhow!(
-                    "missing executable in directory {:?}",
-                    container_dir
-                ))
-            }
-        })()
-        .await
-        .log_err()
+        get_cached_ts_server_binary(container_dir, &self.node).await
+    }
+
+    async fn installation_test_binary(
+        &self,
+        container_dir: PathBuf,
+    ) -> Option<LanguageServerBinary> {
+        get_cached_ts_server_binary(container_dir, &self.node).await
     }
 
     fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> {
@@ -173,6 +159,34 @@ impl LspAdapter for TypeScriptLspAdapter {
     }
 }
 
+async fn get_cached_ts_server_binary(
+    container_dir: PathBuf,
+    node: &NodeRuntime,
+) -> Option<LanguageServerBinary> {
+    (|| async move {
+        let old_server_path = container_dir.join(TypeScriptLspAdapter::OLD_SERVER_PATH);
+        let new_server_path = container_dir.join(TypeScriptLspAdapter::NEW_SERVER_PATH);
+        if new_server_path.exists() {
+            Ok(LanguageServerBinary {
+                path: node.binary_path().await?,
+                arguments: typescript_server_binary_arguments(&new_server_path),
+            })
+        } else if old_server_path.exists() {
+            Ok(LanguageServerBinary {
+                path: node.binary_path().await?,
+                arguments: typescript_server_binary_arguments(&old_server_path),
+            })
+        } else {
+            Err(anyhow!(
+                "missing executable in directory {:?}",
+                container_dir
+            ))
+        }
+    })()
+    .await
+    .log_err()
+}
+
 pub struct EsLintLspAdapter {
     node: Arc<NodeRuntime>,
 }
@@ -268,21 +282,14 @@ impl LspAdapter for EsLintLspAdapter {
         container_dir: PathBuf,
         _: &dyn LspAdapterDelegate,
     ) -> Option<LanguageServerBinary> {
-        (|| async move {
-            // This is unfortunate but we don't know what the version is to build a path directly
-            let mut dir = fs::read_dir(&container_dir).await?;
-            let first = dir.next().await.ok_or(anyhow!("missing first file"))??;
-            if !first.file_type().await?.is_dir() {
-                return Err(anyhow!("First entry is not a directory"));
-            }
+        get_cached_eslint_server_binary(container_dir, &self.node).await
+    }
 
-            Ok(LanguageServerBinary {
-                path: first.path().join(Self::SERVER_PATH),
-                arguments: Default::default(),
-            })
-        })()
-        .await
-        .log_err()
+    async fn installation_test_binary(
+        &self,
+        container_dir: PathBuf,
+    ) -> Option<LanguageServerBinary> {
+        get_cached_eslint_server_binary(container_dir, &self.node).await
     }
 
     async fn label_for_completion(
@@ -298,6 +305,28 @@ impl LspAdapter for EsLintLspAdapter {
     }
 }
 
+async fn get_cached_eslint_server_binary(
+    container_dir: PathBuf,
+    node: &NodeRuntime,
+) -> Option<LanguageServerBinary> {
+    (|| async move {
+        // This is unfortunate but we don't know what the version is to build a path directly
+        let mut dir = fs::read_dir(&container_dir).await?;
+        let first = dir.next().await.ok_or(anyhow!("missing first file"))??;
+        if !first.file_type().await?.is_dir() {
+            return Err(anyhow!("First entry is not a directory"));
+        }
+        let server_path = first.path().join(EsLintLspAdapter::SERVER_PATH);
+
+        Ok(LanguageServerBinary {
+            path: node.binary_path().await?,
+            arguments: eslint_server_binary_arguments(&server_path),
+        })
+    })()
+    .await
+    .log_err()
+}
+
 #[cfg(test)]
 mod tests {
     use gpui::TestAppContext;

crates/zed/src/languages/yaml.rs 🔗

@@ -18,6 +18,8 @@ use std::{
 };
 use util::ResultExt;
 
+const SERVER_PATH: &'static str = "node_modules/yaml-language-server/bin/yaml-language-server";
+
 fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
     vec![server_path.into(), "--stdio".into()]
 }
@@ -27,8 +29,6 @@ pub struct YamlLspAdapter {
 }
 
 impl YamlLspAdapter {
-    const SERVER_PATH: &'static str = "node_modules/yaml-language-server/bin/yaml-language-server";
-
     pub fn new(node: Arc<NodeRuntime>) -> Self {
         YamlLspAdapter { node }
     }
@@ -58,7 +58,7 @@ impl LspAdapter for YamlLspAdapter {
         _: &dyn LspAdapterDelegate,
     ) -> Result<LanguageServerBinary> {
         let version = version.downcast::<String>().unwrap();
-        let server_path = container_dir.join(Self::SERVER_PATH);
+        let server_path = container_dir.join(SERVER_PATH);
 
         if fs::metadata(&server_path).await.is_err() {
             self.node
@@ -77,33 +77,15 @@ impl LspAdapter for YamlLspAdapter {
         container_dir: PathBuf,
         _: &dyn LspAdapterDelegate,
     ) -> Option<LanguageServerBinary> {
-        (|| async move {
-            let mut last_version_dir = None;
-            let mut entries = fs::read_dir(&container_dir).await?;
-            while let Some(entry) = entries.next().await {
-                let entry = entry?;
-                if entry.file_type().await?.is_dir() {
-                    last_version_dir = Some(entry.path());
-                }
-            }
-            let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
-            let server_path = last_version_dir.join(Self::SERVER_PATH);
-            if server_path.exists() {
-                Ok(LanguageServerBinary {
-                    path: self.node.binary_path().await?,
-                    arguments: server_binary_arguments(&server_path),
-                })
-            } else {
-                Err(anyhow!(
-                    "missing executable in directory {:?}",
-                    last_version_dir
-                ))
-            }
-        })()
-        .await
-        .log_err()
+        get_cached_server_binary(container_dir, &self.node).await
     }
 
+    async fn installation_test_binary(
+        &self,
+        container_dir: PathBuf,
+    ) -> Option<LanguageServerBinary> {
+        get_cached_server_binary(container_dir, &self.node).await
+    }
     fn workspace_configuration(&self, cx: &mut AppContext) -> Option<BoxFuture<'static, Value>> {
         let tab_size = all_language_settings(None, cx)
             .language(Some("YAML"))
@@ -121,3 +103,34 @@ impl LspAdapter for YamlLspAdapter {
         )
     }
 }
+
+async fn get_cached_server_binary(
+    container_dir: PathBuf,
+    node: &NodeRuntime,
+) -> Option<LanguageServerBinary> {
+    (|| async move {
+        let mut last_version_dir = None;
+        let mut entries = fs::read_dir(&container_dir).await?;
+        while let Some(entry) = entries.next().await {
+            let entry = entry?;
+            if entry.file_type().await?.is_dir() {
+                last_version_dir = Some(entry.path());
+            }
+        }
+        let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
+        let server_path = last_version_dir.join(SERVER_PATH);
+        if server_path.exists() {
+            Ok(LanguageServerBinary {
+                path: node.binary_path().await?,
+                arguments: server_binary_arguments(&server_path),
+            })
+        } else {
+            Err(anyhow!(
+                "missing executable in directory {:?}",
+                last_version_dir
+            ))
+        }
+    })()
+    .await
+    .log_err()
+}