diff --git a/src/bundler.rs b/src/bundler.rs index ed95a60..9fd9a30 100644 --- a/src/bundler.rs +++ b/src/bundler.rs @@ -4,7 +4,6 @@ use std::path::Path; /// A simple wrapper around the `bundle` command. pub struct Bundler { pub working_dir: String, - envs: Vec<(String, String)>, command_executor: Box, } @@ -14,14 +13,9 @@ impl Bundler { /// # Arguments /// * `working_dir` - The working directory where `bundle` commands should be executed. /// * `command_executor` - An executor for `bundle` commands. - pub fn new( - working_dir: String, - envs: Vec<(String, String)>, - command_executor: Box, - ) -> Self { + pub fn new(working_dir: String, command_executor: Box) -> Self { Bundler { working_dir, - envs, command_executor, } } @@ -33,31 +27,36 @@ impl Bundler { /// /// # Returns /// A `Result` containing the version string if successful, or an error message. - pub fn installed_gem_version(&self, name: &str) -> Result { - let args = vec!["--version".into(), name.into()]; - - self.execute_bundle_command("info".into(), args) + pub fn installed_gem_version( + &self, + name: &str, + envs: &[(&str, &str)], + ) -> Result { + let args = &["--version", name]; + + self.execute_bundle_command("info", args, envs) } - fn execute_bundle_command(&self, cmd: String, args: Vec) -> Result { + fn execute_bundle_command( + &self, + cmd: &str, + args: &[&str], + envs: &[(&str, &str)], + ) -> Result { let bundle_gemfile_path = Path::new(&self.working_dir).join("Gemfile"); let bundle_gemfile = bundle_gemfile_path .to_str() .ok_or_else(|| "Invalid path to Gemfile".to_string())?; - let full_args: Vec = std::iter::once(cmd).chain(args).collect(); - let command_envs: Vec<(String, String)> = self - .envs + let full_args: Vec<&str> = std::iter::once(cmd).chain(args.iter().copied()).collect(); + let command_envs: Vec<(&str, &str)> = envs .iter() .cloned() - .chain(std::iter::once(( - "BUNDLE_GEMFILE".to_string(), - bundle_gemfile.to_string(), - ))) + .chain(std::iter::once(("BUNDLE_GEMFILE", bundle_gemfile))) .collect(); self.command_executor - .execute("bundle", full_args, command_envs) + .execute("bundle", &full_args, &command_envs) .and_then(|output| match output.status { Some(0) => Ok(String::from_utf8_lossy(&output.stdout).to_string()), Some(status) => { @@ -128,8 +127,8 @@ mod tests { fn execute( &self, command_name: &str, - args: Vec, - envs: Vec<(String, String)>, + args: &[&str], + envs: &[(&str, &str)], ) -> Result { let mut config = self.config.borrow_mut(); @@ -140,6 +139,10 @@ mod tests { assert_eq!(&args, expected_args, "Mock: Args mismatch"); } if let Some(expected_envs) = &config.expected_envs { + let envs: Vec<(String, String)> = envs + .iter() + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect(); assert_eq!(&envs, expected_envs, "Mock: Env mismatch"); } @@ -172,9 +175,9 @@ mod tests { #[test] fn test_installed_gem_version_success() { let mock_executor = create_mock_executor_for_success("8.0.0", "test_dir", "rails"); - let bundler = Bundler::new("test_dir".into(), vec![], Box::new(mock_executor)); + let bundler = Bundler::new("test_dir".into(), Box::new(mock_executor)); let version = bundler - .installed_gem_version("rails") + .installed_gem_version("rails", &[]) .expect("Expected successful version"); assert_eq!(version, "8.0.0", "Installed gem version should match"); } @@ -197,8 +200,8 @@ mod tests { }), ); - let bundler = Bundler::new("test_dir".into(), vec![], Box::new(mock_executor)); - let result = bundler.installed_gem_version(gem_name); + let bundler = Bundler::new("test_dir".into(), Box::new(mock_executor)); + let result = bundler.installed_gem_version(gem_name, &[]); assert!( result.is_err(), @@ -229,8 +232,8 @@ mod tests { Err(specific_error_msg.to_string()), ); - let bundler = Bundler::new("test_dir".into(), vec![], Box::new(mock_executor)); - let result = bundler.installed_gem_version(gem_name); + let bundler = Bundler::new("test_dir".into(), Box::new(mock_executor)); + let result = bundler.installed_gem_version(gem_name, &[]); assert!(result.is_err(), "Expected error from executor failure"); assert_eq!( diff --git a/src/command_executor.rs b/src/command_executor.rs index 9345769..8bcad66 100644 --- a/src/command_executor.rs +++ b/src/command_executor.rs @@ -19,22 +19,26 @@ pub trait CommandExecutor { fn execute( &self, cmd: &str, - args: Vec, - envs: Vec<(String, String)>, + args: &[&str], + envs: &[(&str, &str)], ) -> zed::Result; } /// An implementation of `CommandExecutor` that executes commands /// using the `zed_extension_api::Command`. +#[derive(Clone)] pub struct RealCommandExecutor; impl CommandExecutor for RealCommandExecutor { fn execute( &self, cmd: &str, - args: Vec, - envs: Vec<(String, String)>, + args: &[&str], + envs: &[(&str, &str)], ) -> zed::Result { - zed::Command::new(cmd).args(args).envs(envs).output() + zed::Command::new(cmd) + .args(args.iter().copied()) + .envs(envs.iter().copied()) + .output() } } diff --git a/src/gemset.rs b/src/gemset.rs index d8d6a96..a302b46 100644 --- a/src/gemset.rs +++ b/src/gemset.rs @@ -16,11 +16,10 @@ impl Gemset { } /// Returns the full path to a gem binary executable. - pub fn gem_bin_path(&self, bin_name: impl Into) -> Result { - let bin_name = bin_name.into(); + pub fn gem_bin_path(&self, bin_name: &str) -> Result { let path = std::path::Path::new(&self.gem_home) .join("bin") - .join(&bin_name); + .join(bin_name); path.to_str() .map(ToString::to_string) @@ -35,21 +34,21 @@ impl Gemset { } pub fn install_gem(&self, name: &str) -> Result<(), String> { - let args = vec![ - "--no-user-install".to_string(), - "--no-format-executable".to_string(), - "--no-document".to_string(), - name.into(), + let args = &[ + "--no-user-install", + "--no-format-executable", + "--no-document", + name, ]; - self.execute_gem_command("install".into(), args) + self.execute_gem_command("install", args) .map_err(|e| format!("Failed to install gem '{name}': {e}"))?; Ok(()) } pub fn update_gem(&self, name: &str) -> Result<(), String> { - self.execute_gem_command("update".into(), vec![name.into()]) + self.execute_gem_command("update", &[name]) .map_err(|e| format!("Failed to update gem '{name}': {e}"))?; Ok(()) } @@ -58,8 +57,8 @@ impl Gemset { let re = Regex::new(r"^(\S+) \((.+)\)$").map_err(|e| format!("Failed to compile regex: {e}"))?; - let args = vec!["--exact".to_string(), name.into()]; - let output_str = self.execute_gem_command("list".into(), args)?; + let args = &["--exact", name]; + let output_str = self.execute_gem_command("list", args)?; for line in output_str.lines() { let captures = match re.captures(line) { @@ -78,23 +77,22 @@ impl Gemset { } pub fn is_outdated_gem(&self, name: &str) -> Result { - self.execute_gem_command("outdated".into(), vec![]) - .map(|output| { - output - .lines() - .any(|line| line.split_whitespace().next().is_some_and(|n| n == name)) - }) + self.execute_gem_command("outdated", &[]).map(|output| { + output + .lines() + .any(|line| line.split_whitespace().next().is_some_and(|n| n == name)) + }) } - fn execute_gem_command(&self, cmd: String, args: Vec) -> Result { - let full_args: Vec = std::iter::once(cmd) - .chain(std::iter::once("--norc".to_string())) - .chain(args) + fn execute_gem_command(&self, cmd: &str, args: &[&str]) -> Result { + let full_args: Vec<&str> = std::iter::once(cmd) + .chain(std::iter::once("--norc")) + .chain(args.iter().copied()) .collect(); - let command_envs = vec![("GEM_HOME".to_string(), self.gem_home.clone())]; + let command_envs = &[("GEM_HOME", self.gem_home.as_str())]; self.command_executor - .execute("gem", full_args, command_envs) + .execute("gem", &full_args, command_envs) .and_then(|output| match output.status { Some(0) => Ok(String::from_utf8_lossy(&output.stdout).to_string()), Some(status) => { @@ -165,8 +163,8 @@ mod tests { fn execute( &self, command_name: &str, - args: Vec, - envs: Vec<(String, String)>, + args: &[&str], + envs: &[(&str, &str)], ) -> Result { let mut config = self.config.borrow_mut(); @@ -177,6 +175,10 @@ mod tests { assert_eq!(&args, expected_args, "Mock: Args mismatch"); } if let Some(expected_envs) = &config.expected_envs { + let envs: Vec<(String, String)> = envs + .iter() + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect(); assert_eq!(&envs, expected_envs, "Mock: Env mismatch"); } diff --git a/src/language_servers/language_server.rs b/src/language_servers/language_server.rs index 4c65b59..d784764 100644 --- a/src/language_servers/language_server.rs +++ b/src/language_servers/language_server.rs @@ -164,12 +164,14 @@ pub trait LanguageServer { return self.try_find_on_path_or_extension_gemset(language_server_id, worktree); } - let bundler = Bundler::new( - worktree.root_path(), - worktree.shell_env(), - Box::new(RealCommandExecutor), - ); - match bundler.installed_gem_version(Self::GEM_NAME) { + let bundler = Bundler::new(worktree.root_path(), Box::new(RealCommandExecutor)); + let shell_env = worktree.shell_env(); + let env_vars: Vec<(&str, &str)> = shell_env + .iter() + .map(|(key, value)| (key.as_str(), value.as_str())) + .collect(); + + match bundler.installed_gem_version(Self::GEM_NAME, &env_vars) { Ok(_version) => { let bundle_path = worktree .which("bundle") @@ -183,7 +185,7 @@ pub trait LanguageServer { .chain(self.get_executable_args(worktree)) .collect(), ), - env: Some(worktree.shell_env()), + env: Some(shell_env), }) } Err(_e) => self.try_find_on_path_or_extension_gemset(language_server_id, worktree), diff --git a/src/language_servers/sorbet.rs b/src/language_servers/sorbet.rs index ca0b579..a6c7cdc 100644 --- a/src/language_servers/sorbet.rs +++ b/src/language_servers/sorbet.rs @@ -12,19 +12,19 @@ impl LanguageServer for Sorbet { .lsp_binary_settings(Self::SERVER_ID) .unwrap_or_default(); - let default_args = vec![ - "tc".to_string(), - "--lsp".to_string(), - "--enable-experimental-lsp-document-highlight".to_string(), - ]; - // test if sorbet/config is present match worktree.read_text_file("sorbet/config") { Ok(_) => { // Config file exists, prefer custom arguments if available. binary_settings .and_then(|bs| bs.arguments) - .unwrap_or(default_args) + .unwrap_or_else(|| { + vec![ + "tc".to_string(), + "--lsp".to_string(), + "--enable-experimental-lsp-document-highlight".to_string(), + ] + }) } Err(_) => { // gross, but avoid sorbet errors in a non-sorbet diff --git a/src/ruby.rs b/src/ruby.rs index cfcbce7..ab917fb 100644 --- a/src/ruby.rs +++ b/src/ruby.rs @@ -122,6 +122,12 @@ impl zed::Extension for RubyExtension { _: Option, worktree: &Worktree, ) -> Result { + let shell_env = worktree.shell_env(); + let env_vars: Vec<(&str, &str)> = shell_env + .iter() + .map(|(key, value)| (key.as_str(), value.as_str())) + .collect(); + let mut rdbg_path = Path::new(&adapter_name) .join("rdbg") .to_string_lossy() @@ -129,12 +135,8 @@ impl zed::Extension for RubyExtension { let mut use_bundler = false; if worktree.which(&rdbg_path).is_none() { - let bundler = Bundler::new( - worktree.root_path(), - worktree.shell_env(), - Box::new(RealCommandExecutor), - ); - match bundler.installed_gem_version("debug") { + let bundler = Bundler::new(worktree.root_path(), Box::new(RealCommandExecutor)); + match bundler.installed_gem_version("debug", &env_vars) { Ok(_version) => { rdbg_path = worktree .which("bundle")