Remove NodeRuntime static & add fake implementation for tests

Julia created

Change summary

Cargo.lock                                            |   1 
crates/copilot/src/copilot.rs                         |  13 
crates/live_kit_client/LiveKitBridge/Package.resolved |   4 
crates/node_runtime/Cargo.toml                        |   1 
crates/node_runtime/src/node_runtime.rs               | 151 ++++++++----
crates/zed/src/languages.rs                           |   2 
crates/zed/src/languages/css.rs                       |  12 
crates/zed/src/languages/html.rs                      |  12 
crates/zed/src/languages/json.rs                      |  12 
crates/zed/src/languages/php.rs                       |  12 
crates/zed/src/languages/python.rs                    |  12 
crates/zed/src/languages/svelte.rs                    |  12 
crates/zed/src/languages/tailwind.rs                  |  12 
crates/zed/src/languages/typescript.rs                |  22 
crates/zed/src/languages/yaml.rs                      |  15 
crates/zed/src/main.rs                                |   4 
crates/zed/src/zed.rs                                 |   5 
17 files changed, 175 insertions(+), 127 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -4582,6 +4582,7 @@ dependencies = [
  "anyhow",
  "async-compression",
  "async-tar",
+ "async-trait",
  "futures 0.3.28",
  "gpui",
  "log",

crates/copilot/src/copilot.rs 🔗

@@ -41,7 +41,7 @@ actions!(
     [Suggest, NextSuggestion, PreviousSuggestion, Reinstall]
 );
 
-pub fn init(http: Arc<dyn HttpClient>, node_runtime: Arc<NodeRuntime>, cx: &mut AppContext) {
+pub fn init(http: Arc<dyn HttpClient>, node_runtime: Arc<dyn NodeRuntime>, cx: &mut AppContext) {
     let copilot = cx.add_model({
         let node_runtime = node_runtime.clone();
         move |cx| Copilot::start(http, node_runtime, cx)
@@ -265,7 +265,7 @@ pub struct Completion {
 
 pub struct Copilot {
     http: Arc<dyn HttpClient>,
-    node_runtime: Arc<NodeRuntime>,
+    node_runtime: Arc<dyn NodeRuntime>,
     server: CopilotServer,
     buffers: HashSet<WeakModelHandle<Buffer>>,
 }
@@ -299,7 +299,7 @@ impl Copilot {
 
     fn start(
         http: Arc<dyn HttpClient>,
-        node_runtime: Arc<NodeRuntime>,
+        node_runtime: Arc<dyn NodeRuntime>,
         cx: &mut ModelContext<Self>,
     ) -> Self {
         let mut this = Self {
@@ -335,12 +335,15 @@ impl Copilot {
 
     #[cfg(any(test, feature = "test-support"))]
     pub fn fake(cx: &mut gpui::TestAppContext) -> (ModelHandle<Self>, lsp::FakeLanguageServer) {
+        use node_runtime::FakeNodeRuntime;
+
         let (server, fake_server) =
             LanguageServer::fake("copilot".into(), Default::default(), cx.to_async());
         let http = util::http::FakeHttpClient::create(|_| async { unreachable!() });
+        let node_runtime = FakeNodeRuntime::new();
         let this = cx.add_model(|_| Self {
             http: http.clone(),
-            node_runtime: NodeRuntime::instance(http),
+            node_runtime,
             server: CopilotServer::Running(RunningCopilotServer {
                 lsp: Arc::new(server),
                 sign_in_status: SignInStatus::Authorized,
@@ -353,7 +356,7 @@ impl Copilot {
 
     fn start_language_server(
         http: Arc<dyn HttpClient>,
-        node_runtime: Arc<NodeRuntime>,
+        node_runtime: Arc<dyn NodeRuntime>,
         this: ModelHandle<Self>,
         mut cx: AsyncAppContext,
     ) -> impl Future<Output = ()> {

crates/live_kit_client/LiveKitBridge/Package.resolved 🔗

@@ -42,8 +42,8 @@
         "repositoryURL": "https://github.com/apple/swift-protobuf.git",
         "state": {
           "branch": null,
-          "revision": "0af9125c4eae12a4973fb66574c53a54962a9e1e",
-          "version": "1.21.0"
+          "revision": "ce20dc083ee485524b802669890291c0d8090170",
+          "version": "1.22.1"
         }
       }
     ]

crates/node_runtime/Cargo.toml 🔗

@@ -14,6 +14,7 @@ util = { path = "../util" }
 async-compression = { version = "0.3", features = ["gzip", "futures-bufread"] }
 async-tar = "0.4.2"
 futures.workspace = true
+async-trait.workspace = true
 anyhow.workspace = true
 parking_lot.workspace = true
 serde.workspace = true

crates/node_runtime/src/node_runtime.rs 🔗

@@ -7,14 +7,12 @@ use std::process::{Output, Stdio};
 use std::{
     env::consts,
     path::{Path, PathBuf},
-    sync::{Arc, OnceLock},
+    sync::Arc,
 };
 use util::http::HttpClient;
 
 const VERSION: &str = "v18.15.0";
 
-static RUNTIME_INSTANCE: OnceLock<Arc<NodeRuntime>> = OnceLock::new();
-
 #[derive(Debug, Deserialize)]
 #[serde(rename_all = "kebab-case")]
 pub struct NpmInfo {
@@ -28,23 +26,88 @@ pub struct NpmInfoDistTags {
     latest: Option<String>,
 }
 
-pub struct NodeRuntime {
+#[async_trait::async_trait]
+pub trait NodeRuntime: Send + Sync {
+    async fn binary_path(&self) -> Result<PathBuf>;
+
+    async fn run_npm_subcommand(
+        &self,
+        directory: Option<&Path>,
+        subcommand: &str,
+        args: &[&str],
+    ) -> Result<Output>;
+
+    async fn npm_package_latest_version(&self, name: &str) -> Result<String>;
+
+    async fn npm_install_packages(&self, directory: &Path, packages: &[(&str, &str)])
+        -> Result<()>;
+}
+
+pub struct RealNodeRuntime {
     http: Arc<dyn HttpClient>,
 }
 
-impl NodeRuntime {
-    pub fn instance(http: Arc<dyn HttpClient>) -> Arc<NodeRuntime> {
-        RUNTIME_INSTANCE
-            .get_or_init(|| Arc::new(NodeRuntime { http }))
-            .clone()
+impl RealNodeRuntime {
+    pub fn new(http: Arc<dyn HttpClient>) -> Arc<dyn NodeRuntime> {
+        Arc::new(RealNodeRuntime { http })
+    }
+
+    async fn install_if_needed(&self) -> Result<PathBuf> {
+        log::info!("Node runtime install_if_needed");
+
+        let arch = match consts::ARCH {
+            "x86_64" => "x64",
+            "aarch64" => "arm64",
+            other => bail!("Running on unsupported platform: {other}"),
+        };
+
+        let folder_name = format!("node-{VERSION}-darwin-{arch}");
+        let node_containing_dir = util::paths::SUPPORT_DIR.join("node");
+        let node_dir = node_containing_dir.join(folder_name);
+        let node_binary = node_dir.join("bin/node");
+        let npm_file = node_dir.join("bin/npm");
+
+        let result = Command::new(&node_binary)
+            .arg(npm_file)
+            .arg("--version")
+            .stdin(Stdio::null())
+            .stdout(Stdio::null())
+            .stderr(Stdio::null())
+            .status()
+            .await;
+        let valid = matches!(result, Ok(status) if status.success());
+
+        if !valid {
+            _ = fs::remove_dir_all(&node_containing_dir).await;
+            fs::create_dir(&node_containing_dir)
+                .await
+                .context("error creating node containing dir")?;
+
+            let file_name = format!("node-{VERSION}-darwin-{arch}.tar.gz");
+            let url = format!("https://nodejs.org/dist/{VERSION}/{file_name}");
+            let mut response = self
+                .http
+                .get(&url, Default::default(), true)
+                .await
+                .context("error downloading Node binary tarball")?;
+
+            let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
+            let archive = Archive::new(decompressed_bytes);
+            archive.unpack(&node_containing_dir).await?;
+        }
+
+        anyhow::Ok(node_dir)
     }
+}
 
-    pub async fn binary_path(&self) -> Result<PathBuf> {
+#[async_trait::async_trait]
+impl NodeRuntime for RealNodeRuntime {
+    async fn binary_path(&self) -> Result<PathBuf> {
         let installation_path = self.install_if_needed().await?;
         Ok(installation_path.join("bin/node"))
     }
 
-    pub async fn run_npm_subcommand(
+    async fn run_npm_subcommand(
         &self,
         directory: Option<&Path>,
         subcommand: &str,
@@ -106,7 +169,7 @@ impl NodeRuntime {
         output.map_err(|e| anyhow!("{e}"))
     }
 
-    pub async fn npm_package_latest_version(&self, name: &str) -> Result<String> {
+    async fn npm_package_latest_version(&self, name: &str) -> Result<String> {
         let output = self
             .run_npm_subcommand(
                 None,
@@ -131,10 +194,10 @@ impl NodeRuntime {
             .ok_or_else(|| anyhow!("no version found for npm package {}", name))
     }
 
-    pub async fn npm_install_packages(
+    async fn npm_install_packages(
         &self,
         directory: &Path,
-        packages: impl IntoIterator<Item = (&str, &str)>,
+        packages: &[(&str, &str)],
     ) -> Result<()> {
         let packages: Vec<_> = packages
             .into_iter()
@@ -155,51 +218,31 @@ impl NodeRuntime {
             .await?;
         Ok(())
     }
+}
 
-    async fn install_if_needed(&self) -> Result<PathBuf> {
-        log::info!("Node runtime install_if_needed");
-
-        let arch = match consts::ARCH {
-            "x86_64" => "x64",
-            "aarch64" => "arm64",
-            other => bail!("Running on unsupported platform: {other}"),
-        };
-
-        let folder_name = format!("node-{VERSION}-darwin-{arch}");
-        let node_containing_dir = util::paths::SUPPORT_DIR.join("node");
-        let node_dir = node_containing_dir.join(folder_name);
-        let node_binary = node_dir.join("bin/node");
-        let npm_file = node_dir.join("bin/npm");
+pub struct FakeNodeRuntime;
 
-        let result = Command::new(&node_binary)
-            .arg(npm_file)
-            .arg("--version")
-            .stdin(Stdio::null())
-            .stdout(Stdio::null())
-            .stderr(Stdio::null())
-            .status()
-            .await;
-        let valid = matches!(result, Ok(status) if status.success());
+impl FakeNodeRuntime {
+    pub fn new() -> Arc<dyn NodeRuntime> {
+        Arc::new(FakeNodeRuntime)
+    }
+}
 
-        if !valid {
-            _ = fs::remove_dir_all(&node_containing_dir).await;
-            fs::create_dir(&node_containing_dir)
-                .await
-                .context("error creating node containing dir")?;
+#[async_trait::async_trait]
+impl NodeRuntime for FakeNodeRuntime {
+    async fn binary_path(&self) -> Result<PathBuf> {
+        unreachable!()
+    }
 
-            let file_name = format!("node-{VERSION}-darwin-{arch}.tar.gz");
-            let url = format!("https://nodejs.org/dist/{VERSION}/{file_name}");
-            let mut response = self
-                .http
-                .get(&url, Default::default(), true)
-                .await
-                .context("error downloading Node binary tarball")?;
+    async fn run_npm_subcommand(&self, _: Option<&Path>, _: &str, _: &[&str]) -> Result<Output> {
+        unreachable!()
+    }
 
-            let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
-            let archive = Archive::new(decompressed_bytes);
-            archive.unpack(&node_containing_dir).await?;
-        }
+    async fn npm_package_latest_version(&self, _: &str) -> Result<String> {
+        unreachable!()
+    }
 
-        anyhow::Ok(node_dir)
+    async fn npm_install_packages(&self, _: &Path, _: &[(&str, &str)]) -> Result<()> {
+        unreachable!()
     }
 }

crates/zed/src/languages.rs 🔗

@@ -37,7 +37,7 @@ mod yaml;
 #[exclude = "*.rs"]
 struct LanguageDir;
 
-pub fn init(languages: Arc<LanguageRegistry>, node_runtime: Arc<NodeRuntime>) {
+pub fn init(languages: Arc<LanguageRegistry>, node_runtime: Arc<dyn NodeRuntime>) {
     let language = |name, grammar, adapters| {
         languages.register(name, load_config(name), grammar, adapters, load_queries)
     };

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

@@ -22,11 +22,11 @@ fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
 }
 
 pub struct CssLspAdapter {
-    node: Arc<NodeRuntime>,
+    node: Arc<dyn NodeRuntime>,
 }
 
 impl CssLspAdapter {
-    pub fn new(node: Arc<NodeRuntime>) -> Self {
+    pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
         CssLspAdapter { node }
     }
 }
@@ -65,7 +65,7 @@ impl LspAdapter for CssLspAdapter {
             self.node
                 .npm_install_packages(
                     &container_dir,
-                    [("vscode-langservers-extracted", version.as_str())],
+                    &[("vscode-langservers-extracted", version.as_str())],
                 )
                 .await?;
         }
@@ -81,14 +81,14 @@ impl LspAdapter for CssLspAdapter {
         container_dir: PathBuf,
         _: &dyn LspAdapterDelegate,
     ) -> Option<LanguageServerBinary> {
-        get_cached_server_binary(container_dir, &self.node).await
+        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
+        get_cached_server_binary(container_dir, &*self.node).await
     }
 
     async fn initialization_options(&self) -> Option<serde_json::Value> {
@@ -100,7 +100,7 @@ impl LspAdapter for CssLspAdapter {
 
 async fn get_cached_server_binary(
     container_dir: PathBuf,
-    node: &NodeRuntime,
+    node: &dyn NodeRuntime,
 ) -> Option<LanguageServerBinary> {
     (|| async move {
         let mut last_version_dir = None;

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

@@ -22,11 +22,11 @@ fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
 }
 
 pub struct HtmlLspAdapter {
-    node: Arc<NodeRuntime>,
+    node: Arc<dyn NodeRuntime>,
 }
 
 impl HtmlLspAdapter {
-    pub fn new(node: Arc<NodeRuntime>) -> Self {
+    pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
         HtmlLspAdapter { node }
     }
 }
@@ -65,7 +65,7 @@ impl LspAdapter for HtmlLspAdapter {
             self.node
                 .npm_install_packages(
                     &container_dir,
-                    [("vscode-langservers-extracted", version.as_str())],
+                    &[("vscode-langservers-extracted", version.as_str())],
                 )
                 .await?;
         }
@@ -81,14 +81,14 @@ impl LspAdapter for HtmlLspAdapter {
         container_dir: PathBuf,
         _: &dyn LspAdapterDelegate,
     ) -> Option<LanguageServerBinary> {
-        get_cached_server_binary(container_dir, &self.node).await
+        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
+        get_cached_server_binary(container_dir, &*self.node).await
     }
 
     async fn initialization_options(&self) -> Option<serde_json::Value> {
@@ -100,7 +100,7 @@ impl LspAdapter for HtmlLspAdapter {
 
 async fn get_cached_server_binary(
     container_dir: PathBuf,
-    node: &NodeRuntime,
+    node: &dyn NodeRuntime,
 ) -> Option<LanguageServerBinary> {
     (|| async move {
         let mut last_version_dir = None;

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

@@ -27,12 +27,12 @@ fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
 }
 
 pub struct JsonLspAdapter {
-    node: Arc<NodeRuntime>,
+    node: Arc<dyn NodeRuntime>,
     languages: Arc<LanguageRegistry>,
 }
 
 impl JsonLspAdapter {
-    pub fn new(node: Arc<NodeRuntime>, languages: Arc<LanguageRegistry>) -> Self {
+    pub fn new(node: Arc<dyn NodeRuntime>, languages: Arc<LanguageRegistry>) -> Self {
         JsonLspAdapter { node, languages }
     }
 }
@@ -71,7 +71,7 @@ impl LspAdapter for JsonLspAdapter {
             self.node
                 .npm_install_packages(
                     &container_dir,
-                    [("vscode-json-languageserver", version.as_str())],
+                    &[("vscode-json-languageserver", version.as_str())],
                 )
                 .await?;
         }
@@ -87,14 +87,14 @@ impl LspAdapter for JsonLspAdapter {
         container_dir: PathBuf,
         _: &dyn LspAdapterDelegate,
     ) -> Option<LanguageServerBinary> {
-        get_cached_server_binary(container_dir, &self.node).await
+        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
+        get_cached_server_binary(container_dir, &*self.node).await
     }
 
     async fn initialization_options(&self) -> Option<serde_json::Value> {
@@ -148,7 +148,7 @@ impl LspAdapter for JsonLspAdapter {
 
 async fn get_cached_server_binary(
     container_dir: PathBuf,
-    node: &NodeRuntime,
+    node: &dyn NodeRuntime,
 ) -> Option<LanguageServerBinary> {
     (|| async move {
         let mut last_version_dir = None;

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

@@ -23,14 +23,14 @@ fn intelephense_server_binary_arguments(server_path: &Path) -> Vec<OsString> {
 pub struct IntelephenseVersion(String);
 
 pub struct IntelephenseLspAdapter {
-    node: Arc<NodeRuntime>,
+    node: Arc<dyn NodeRuntime>,
 }
 
 impl IntelephenseLspAdapter {
     const SERVER_PATH: &'static str = "node_modules/intelephense/lib/intelephense.js";
 
     #[allow(unused)]
-    pub fn new(node: Arc<NodeRuntime>) -> Self {
+    pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
         Self { node }
     }
 }
@@ -65,7 +65,7 @@ impl LspAdapter for IntelephenseLspAdapter {
 
         if fs::metadata(&server_path).await.is_err() {
             self.node
-                .npm_install_packages(&container_dir, [("intelephense", version.0.as_str())])
+                .npm_install_packages(&container_dir, &[("intelephense", version.0.as_str())])
                 .await?;
         }
         Ok(LanguageServerBinary {
@@ -79,14 +79,14 @@ impl LspAdapter for IntelephenseLspAdapter {
         container_dir: PathBuf,
         _: &dyn LspAdapterDelegate,
     ) -> Option<LanguageServerBinary> {
-        get_cached_server_binary(container_dir, &self.node).await
+        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
+        get_cached_server_binary(container_dir, &*self.node).await
     }
 
     async fn label_for_completion(
@@ -107,7 +107,7 @@ impl LspAdapter for IntelephenseLspAdapter {
 
 async fn get_cached_server_binary(
     container_dir: PathBuf,
-    node: &NodeRuntime,
+    node: &dyn NodeRuntime,
 ) -> Option<LanguageServerBinary> {
     (|| async move {
         let mut last_version_dir = None;

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

@@ -20,11 +20,11 @@ fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
 }
 
 pub struct PythonLspAdapter {
-    node: Arc<NodeRuntime>,
+    node: Arc<dyn NodeRuntime>,
 }
 
 impl PythonLspAdapter {
-    pub fn new(node: Arc<NodeRuntime>) -> Self {
+    pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
         PythonLspAdapter { node }
     }
 }
@@ -57,7 +57,7 @@ impl LspAdapter for PythonLspAdapter {
 
         if fs::metadata(&server_path).await.is_err() {
             self.node
-                .npm_install_packages(&container_dir, [("pyright", version.as_str())])
+                .npm_install_packages(&container_dir, &[("pyright", version.as_str())])
                 .await?;
         }
 
@@ -72,14 +72,14 @@ impl LspAdapter for PythonLspAdapter {
         container_dir: PathBuf,
         _: &dyn LspAdapterDelegate,
     ) -> Option<LanguageServerBinary> {
-        get_cached_server_binary(container_dir, &self.node).await
+        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
+        get_cached_server_binary(container_dir, &*self.node).await
     }
 
     async fn process_completion(&self, item: &mut lsp::CompletionItem) {
@@ -162,7 +162,7 @@ impl LspAdapter for PythonLspAdapter {
 
 async fn get_cached_server_binary(
     container_dir: PathBuf,
-    node: &NodeRuntime,
+    node: &dyn NodeRuntime,
 ) -> Option<LanguageServerBinary> {
     (|| async move {
         let mut last_version_dir = None;

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

@@ -21,11 +21,11 @@ fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
 }
 
 pub struct SvelteLspAdapter {
-    node: Arc<NodeRuntime>,
+    node: Arc<dyn NodeRuntime>,
 }
 
 impl SvelteLspAdapter {
-    pub fn new(node: Arc<NodeRuntime>) -> Self {
+    pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
         SvelteLspAdapter { node }
     }
 }
@@ -64,7 +64,7 @@ impl LspAdapter for SvelteLspAdapter {
             self.node
                 .npm_install_packages(
                     &container_dir,
-                    [("svelte-language-server", version.as_str())],
+                    &[("svelte-language-server", version.as_str())],
                 )
                 .await?;
         }
@@ -80,14 +80,14 @@ impl LspAdapter for SvelteLspAdapter {
         container_dir: PathBuf,
         _: &dyn LspAdapterDelegate,
     ) -> Option<LanguageServerBinary> {
-        get_cached_server_binary(container_dir, &self.node).await
+        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
+        get_cached_server_binary(container_dir, &*self.node).await
     }
 
     async fn initialization_options(&self) -> Option<serde_json::Value> {
@@ -99,7 +99,7 @@ impl LspAdapter for SvelteLspAdapter {
 
 async fn get_cached_server_binary(
     container_dir: PathBuf,
-    node: &NodeRuntime,
+    node: &dyn NodeRuntime,
 ) -> Option<LanguageServerBinary> {
     (|| async move {
         let mut last_version_dir = None;

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

@@ -26,11 +26,11 @@ fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
 }
 
 pub struct TailwindLspAdapter {
-    node: Arc<NodeRuntime>,
+    node: Arc<dyn NodeRuntime>,
 }
 
 impl TailwindLspAdapter {
-    pub fn new(node: Arc<NodeRuntime>) -> Self {
+    pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
         TailwindLspAdapter { node }
     }
 }
@@ -69,7 +69,7 @@ impl LspAdapter for TailwindLspAdapter {
             self.node
                 .npm_install_packages(
                     &container_dir,
-                    [("@tailwindcss/language-server", version.as_str())],
+                    &[("@tailwindcss/language-server", version.as_str())],
                 )
                 .await?;
         }
@@ -85,14 +85,14 @@ impl LspAdapter for TailwindLspAdapter {
         container_dir: PathBuf,
         _: &dyn LspAdapterDelegate,
     ) -> Option<LanguageServerBinary> {
-        get_cached_server_binary(container_dir, &self.node).await
+        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
+        get_cached_server_binary(container_dir, &*self.node).await
     }
 
     async fn initialization_options(&self) -> Option<serde_json::Value> {
@@ -131,7 +131,7 @@ impl LspAdapter for TailwindLspAdapter {
 
 async fn get_cached_server_binary(
     container_dir: PathBuf,
-    node: &NodeRuntime,
+    node: &dyn NodeRuntime,
 ) -> Option<LanguageServerBinary> {
     (|| async move {
         let mut last_version_dir = None;

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

@@ -33,14 +33,14 @@ fn eslint_server_binary_arguments(server_path: &Path) -> Vec<OsString> {
 }
 
 pub struct TypeScriptLspAdapter {
-    node: Arc<NodeRuntime>,
+    node: Arc<dyn NodeRuntime>,
 }
 
 impl TypeScriptLspAdapter {
     const OLD_SERVER_PATH: &'static str = "node_modules/typescript-language-server/lib/cli.js";
     const NEW_SERVER_PATH: &'static str = "node_modules/typescript-language-server/lib/cli.mjs";
 
-    pub fn new(node: Arc<NodeRuntime>) -> Self {
+    pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
         TypeScriptLspAdapter { node }
     }
 }
@@ -86,7 +86,7 @@ impl LspAdapter for TypeScriptLspAdapter {
             self.node
                 .npm_install_packages(
                     &container_dir,
-                    [
+                    &[
                         ("typescript", version.typescript_version.as_str()),
                         (
                             "typescript-language-server",
@@ -108,14 +108,14 @@ impl LspAdapter for TypeScriptLspAdapter {
         container_dir: PathBuf,
         _: &dyn LspAdapterDelegate,
     ) -> Option<LanguageServerBinary> {
-        get_cached_ts_server_binary(container_dir, &self.node).await
+        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
+        get_cached_ts_server_binary(container_dir, &*self.node).await
     }
 
     fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> {
@@ -165,7 +165,7 @@ impl LspAdapter for TypeScriptLspAdapter {
 
 async fn get_cached_ts_server_binary(
     container_dir: PathBuf,
-    node: &NodeRuntime,
+    node: &dyn NodeRuntime,
 ) -> Option<LanguageServerBinary> {
     (|| async move {
         let old_server_path = container_dir.join(TypeScriptLspAdapter::OLD_SERVER_PATH);
@@ -192,14 +192,14 @@ async fn get_cached_ts_server_binary(
 }
 
 pub struct EsLintLspAdapter {
-    node: Arc<NodeRuntime>,
+    node: Arc<dyn NodeRuntime>,
 }
 
 impl EsLintLspAdapter {
     const SERVER_PATH: &'static str = "vscode-eslint/server/out/eslintServer.js";
 
     #[allow(unused)]
-    pub fn new(node: Arc<NodeRuntime>) -> Self {
+    pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
         EsLintLspAdapter { node }
     }
 }
@@ -288,14 +288,14 @@ impl LspAdapter for EsLintLspAdapter {
         container_dir: PathBuf,
         _: &dyn LspAdapterDelegate,
     ) -> Option<LanguageServerBinary> {
-        get_cached_eslint_server_binary(container_dir, &self.node).await
+        get_cached_eslint_server_binary(container_dir, &*self.node).await
     }
 
     async fn installation_test_binary(
         &self,
         container_dir: PathBuf,
     ) -> Option<LanguageServerBinary> {
-        get_cached_eslint_server_binary(container_dir, &self.node).await
+        get_cached_eslint_server_binary(container_dir, &*self.node).await
     }
 
     async fn label_for_completion(
@@ -313,7 +313,7 @@ impl LspAdapter for EsLintLspAdapter {
 
 async fn get_cached_eslint_server_binary(
     container_dir: PathBuf,
-    node: &NodeRuntime,
+    node: &dyn NodeRuntime,
 ) -> Option<LanguageServerBinary> {
     (|| async move {
         // This is unfortunate but we don't know what the version is to build a path directly

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

@@ -25,11 +25,11 @@ fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
 }
 
 pub struct YamlLspAdapter {
-    node: Arc<NodeRuntime>,
+    node: Arc<dyn NodeRuntime>,
 }
 
 impl YamlLspAdapter {
-    pub fn new(node: Arc<NodeRuntime>) -> Self {
+    pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
         YamlLspAdapter { node }
     }
 }
@@ -66,7 +66,10 @@ impl LspAdapter for YamlLspAdapter {
 
         if fs::metadata(&server_path).await.is_err() {
             self.node
-                .npm_install_packages(&container_dir, [("yaml-language-server", version.as_str())])
+                .npm_install_packages(
+                    &container_dir,
+                    &[("yaml-language-server", version.as_str())],
+                )
                 .await?;
         }
 
@@ -81,14 +84,14 @@ impl LspAdapter for YamlLspAdapter {
         container_dir: PathBuf,
         _: &dyn LspAdapterDelegate,
     ) -> Option<LanguageServerBinary> {
-        get_cached_server_binary(container_dir, &self.node).await
+        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
+        get_cached_server_binary(container_dir, &*self.node).await
     }
     fn workspace_configuration(&self, cx: &mut AppContext) -> BoxFuture<'static, Value> {
         let tab_size = all_language_settings(None, cx)
@@ -109,7 +112,7 @@ impl LspAdapter for YamlLspAdapter {
 
 async fn get_cached_server_binary(
     container_dir: PathBuf,
-    node: &NodeRuntime,
+    node: &dyn NodeRuntime,
 ) -> Option<LanguageServerBinary> {
     (|| async move {
         let mut last_version_dir = None;

crates/zed/src/main.rs 🔗

@@ -19,7 +19,7 @@ use gpui::{Action, App, AppContext, AssetSource, AsyncAppContext, Task};
 use isahc::{config::Configurable, Request};
 use language::{LanguageRegistry, Point};
 use log::LevelFilter;
-use node_runtime::NodeRuntime;
+use node_runtime::RealNodeRuntime;
 use parking_lot::Mutex;
 use project::Fs;
 use serde::{Deserialize, Serialize};
@@ -138,7 +138,7 @@ fn main() {
         languages.set_executor(cx.background().clone());
         languages.set_language_server_download_dir(paths::LANGUAGES_DIR.clone());
         let languages = Arc::new(languages);
-        let node_runtime = NodeRuntime::instance(http.clone());
+        let node_runtime = RealNodeRuntime::new(http.clone());
 
         languages::init(languages.clone(), node_runtime.clone());
         let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx));

crates/zed/src/zed.rs 🔗

@@ -723,7 +723,6 @@ mod tests {
         AppContext, AssetSource, Element, Entity, TestAppContext, View, ViewHandle,
     };
     use language::LanguageRegistry;
-    use node_runtime::NodeRuntime;
     use project::{Project, ProjectPath};
     use serde_json::json;
     use settings::{handle_settings_file_changes, watch_config_file, SettingsStore};
@@ -732,7 +731,6 @@ mod tests {
         path::{Path, PathBuf},
     };
     use theme::{ThemeRegistry, ThemeSettings};
-    use util::http::FakeHttpClient;
     use workspace::{
         item::{Item, ItemHandle},
         open_new, open_paths, pane, NewFile, SplitDirection, WorkspaceHandle,
@@ -2364,8 +2362,7 @@ mod tests {
         let mut languages = LanguageRegistry::test();
         languages.set_executor(cx.background().clone());
         let languages = Arc::new(languages);
-        let http = FakeHttpClient::with_404_response();
-        let node_runtime = NodeRuntime::instance(http);
+        let node_runtime = node_runtime::FakeNodeRuntime::new();
         languages::init(languages.clone(), node_runtime);
         for name in languages.language_names() {
             languages.language_for_name(&name);