From 38909fd7698b6357865716b6a5e194b6a6a3258a Mon Sep 17 00:00:00 2001 From: realfakenerd Date: Fri, 31 Oct 2025 12:52:09 -0300 Subject: [PATCH] feat: introduce runtime abstraction to support Bun and Node.js This commit introduces a new `Runtime` abstraction to manage execution environments for the language server and package installation. The `Runtime` enum currently supports `Bun` (if found on the system path) and falls back to Zed's built-in `Node.js` runtime. This change allows the extension to leverage Bun for potentially faster package management and server execution when available, while maintaining compatibility with the standard Node.js environment. The following changes were made: - Add `src/runtime.rs` defining the `Runtime` enum and its methods (`new`, `server_command`, `install_package`, `latest_package_version`, `installed_package_version`). - Update `Cargo.toml` to include `serde` and `which` dependencies. - Refactor `src/svelte.rs` to use the new `Runtime` for package installation checks, version lookups, and starting the language server process. --- Cargo.lock | 93 +++++++++++++++++++++++++++++++++++++++++++++++--- Cargo.toml | 2 ++ src/runtime.rs | 77 +++++++++++++++++++++++++++++++++++++++++ src/svelte.rs | 38 +++++++++++---------- 4 files changed, 188 insertions(+), 22 deletions(-) create mode 100644 src/runtime.rs diff --git a/Cargo.lock b/Cargo.lock index a6ceaa7..5cf1902 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -64,12 +64,28 @@ dependencies = [ "syn", ] +[[package]] +name = "env_home" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" + [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + [[package]] name = "flate2" version = "1.1.1" @@ -335,6 +351,18 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + [[package]] name = "litemap" version = "0.8.0" @@ -423,6 +451,19 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "ryu" version = "1.0.20" @@ -440,18 +481,28 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -608,6 +659,38 @@ dependencies = [ "semver", ] +[[package]] +name = "which" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fabb953106c3c8eea8306e4393700d7657561cb43122571b172bbfb7c7ba1d" +dependencies = [ + "env_home", + "rustix", + "winsafe", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + [[package]] name = "wit-bindgen" version = "0.41.0" @@ -753,6 +836,8 @@ dependencies = [ name = "zed_svelte" version = "0.2.10" dependencies = [ + "serde", + "which", "zed_extension_api", ] diff --git a/Cargo.toml b/Cargo.toml index 43bcf83..c147ff5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,4 +10,6 @@ path = "src/svelte.rs" crate-type = ["cdylib"] [dependencies] +serde = "1.0.228" +which = "8.0.0" zed_extension_api = "0.7.0" diff --git a/src/runtime.rs b/src/runtime.rs new file mode 100644 index 0000000..db2fff0 --- /dev/null +++ b/src/runtime.rs @@ -0,0 +1,77 @@ +use std::path::PathBuf; +use std::process::Command as SystemCommand; +use zed_extension_api::{self as zed, Result}; +use which::which; + +pub enum Runtime { + Bun(PathBuf), + Node +} + +impl Runtime { + pub fn new() -> Self { + match which("bun") { + Ok(path) => { + println!("Bun detected at {:?}, using it as the runtime.", path); + Self::Bun(path) + } + Err(_) => { + println!("Bun not found. Falling back to Zed's built-in Node.js runtime."); + Self::Node + } + } + } + + pub fn server_command(&self, server_path: &str) -> Result { + let command = match self { + Runtime::Bun(path) => path.to_string_lossy().to_string(), + Runtime::Node => zed::node_binary_path()? + }; + + Ok(zed::Command { + command, + args: vec![server_path.to_string(), "--stdio".to_string()], + env: Default::default() + }) + } + + pub fn install_package(&self, package_name: &str, version: &str) -> Result<()> { + println!("Installing {}@{} using {:?}...", package_name, version, self); + match self { + Runtime::Bun(path) => { + let exit_status = SystemCommand::new(path) + .arg("add") + .arg(format!("{}@{}", package_name, version)) + .status() + .map_err(|e| format!("Failed to execute bun: {}", e))?; + + if !exit_status.success() { + return Err(format!("'bun add' failed with status: {}", exit_status).into()) + } + } + Runtime::Node => { + zed::npm_install_package(package_name, version)?; + } + }; + Ok(()) + } + + pub fn latest_package_version(&self, package_name: &str) -> Result{ + match self { + Runtime::Bun(_) | Runtime::Node => zed::npm_package_latest_version(package_name) + } + } + + pub fn installed_package_version(&self, package_name: &str) -> Result> { + zed::npm_package_installed_version(package_name) + } +} + +impl std::fmt::Debug for Runtime { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Bun(path) => write!(f, "Bun({:?})", path), + Self::Node => write!(f, "Node"), + } + } +} diff --git a/src/svelte.rs b/src/svelte.rs index a620724..99a8444 100644 --- a/src/svelte.rs +++ b/src/svelte.rs @@ -1,8 +1,12 @@ +mod runtime; + use std::{collections::HashSet, env}; -use zed_extension_api::{self as zed, serde_json, Result}; +use zed_extension_api::{self as zed, Result, serde_json}; +use runtime::Runtime; struct SvelteExtension { installed: HashSet, + runtime: Runtime } const PACKAGE_NAME: &str = "svelte-language-server"; @@ -14,7 +18,7 @@ impl SvelteExtension { id: &zed::LanguageServerId, package_name: &str, ) -> Result<()> { - let installed_version = zed::npm_package_installed_version(package_name)?; + let installed_version = self.runtime.installed_package_version(package_name)?; // If package is already installed in this session, then we won't reinstall it if installed_version.is_some() && self.installed.contains(package_name) { @@ -26,7 +30,7 @@ impl SvelteExtension { &zed::LanguageServerInstallationStatus::CheckingForUpdate, ); - let latest_version = zed::npm_package_latest_version(package_name)?; + let latest_version = self.runtime.latest_package_version(package_name)?; if installed_version.as_ref() != Some(&latest_version) { println!("Installing {package_name}@{latest_version}..."); @@ -36,7 +40,7 @@ impl SvelteExtension { &zed::LanguageServerInstallationStatus::Downloading, ); - if let Err(error) = zed::npm_install_package(package_name, &latest_version) { + if let Err(error) = self.runtime.install_package(package_name, &latest_version){ // If installation failed, but we don't want to error but rather reuse existing version if installed_version.is_none() { Err(error)?; @@ -55,6 +59,7 @@ impl zed::Extension for SvelteExtension { fn new() -> Self { Self { installed: HashSet::new(), + runtime: Runtime::new() } } @@ -66,20 +71,16 @@ impl zed::Extension for SvelteExtension { self.install_package_if_needed(id, PACKAGE_NAME)?; self.install_package_if_needed(id, TS_PLUGIN_PACKAGE_NAME)?; - Ok(zed::Command { - command: zed::node_binary_path()?, - args: vec![ - env::current_dir() - .unwrap() - .join("node_modules") - .join(PACKAGE_NAME) - .join("bin/server.js") - .to_string_lossy() - .to_string(), - "--stdio".to_string(), - ], - env: Default::default(), - }) + let server_path = env::current_dir() + .unwrap() + .join("node_modules") + .join(PACKAGE_NAME) + .join("bin/server.js") + .to_string_lossy() + .to_string(); + + self.runtime.server_command(&server_path) + } fn language_server_initialization_options( @@ -87,6 +88,7 @@ impl zed::Extension for SvelteExtension { _: &zed::LanguageServerId, _: &zed::Worktree, ) -> Result> { + let config = serde_json::json!({ "inlayHints": { "parameterNames": {