diff --git a/src/gemset.rs b/src/gemset.rs index 59fc688..7c63001 100644 --- a/src/gemset.rs +++ b/src/gemset.rs @@ -5,19 +5,23 @@ use std::path::PathBuf; /// A simple wrapper around the `gem` command. pub struct Gemset { gem_home: PathBuf, - working_dir: PathBuf, + envs: Vec<(String, String)>, command_executor: Box, } impl Gemset { pub fn new( gem_home: PathBuf, - working_dir: PathBuf, + envs: Option<&[(&str, &str)]>, command_executor: Box, ) -> Self { Self { gem_home, - working_dir, + envs: envs.map_or(Vec::new(), |envs| { + envs.iter() + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect() + }), command_executor, } } @@ -32,33 +36,18 @@ impl Gemset { } pub fn env(&self, envs: Option<&[(&str, &str)]>) -> Vec<(String, String)> { - let gem_bin_path = self.gem_home.join("bin"); - let gem_bin_str = gem_bin_path.display().to_string(); - let gem_path = ( + let mut env_map: std::collections::HashMap = envs + .unwrap_or(&[]) + .iter() + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect(); + + env_map.insert( "GEM_PATH".to_string(), format!("{}:$GEM_PATH", self.gem_home.display()), ); - let path_override = { - let existing_path = envs - .unwrap_or(&[]) - .iter() - .find(|(k, _)| *k == "PATH") - .map(|(_, v)| v) - .map_or("$PATH", |v| v); - - ( - "PATH".to_string(), - format!("{}:{}", gem_bin_str, existing_path), - ) - }; - - envs.unwrap_or(&[]) - .iter() - .filter(|(k, _)| *k != "GEM_PATH" || *k != "PATH") - .map(|(k, v)| (k.to_string(), v.to_string())) - .chain([gem_path, path_override]) - .collect() + env_map.into_iter().collect() } pub fn install_gem(&self, name: &str) -> Result<(), String> { @@ -129,19 +118,16 @@ impl Gemset { .gem_home .to_str() .ok_or("Failed to convert gem_home path to string")?; - let working_dir_str = self - .working_dir - .to_str() - .ok_or("Failed to convert working_dir path to string")?; - let command_envs = &[ - ("GEM_HOME", gem_home_str), - ("GEM_PATH", gem_home_str), - ("RBENV_DIR", working_dir_str), - ]; + let command_envs = vec![("GEM_HOME", gem_home_str), ("GEM_PATH", gem_home_str)]; + + let merged_envs: Vec<(&str, &str)> = command_envs + .into_iter() + .chain(self.envs.iter().map(|(k, v)| (k.as_str(), v.as_str()))) + .collect(); self.command_executor - .execute("gem", &full_args, command_envs) + .execute("gem", &full_args, &merged_envs) .and_then(|output| match output.status { Some(0) => Ok(String::from_utf8_lossy(&output.stdout).into_owned()), Some(status) => { @@ -239,21 +225,16 @@ mod tests { } const TEST_GEM_HOME: &str = "/test/gem_home"; - const TEST_WORKING_DIR: &str = "/test/my_project"; fn create_gemset(mock_executor: MockGemCommandExecutor) -> Gemset { - Gemset::new( - TEST_GEM_HOME.into(), - TEST_WORKING_DIR.into(), - Box::new(mock_executor), - ) + Gemset::new(TEST_GEM_HOME.into(), None, Box::new(mock_executor)) } #[test] fn test_gem_bin_path() { let gemset = Gemset::new( TEST_GEM_HOME.into(), - TEST_WORKING_DIR.into(), + None, Box::new(MockGemCommandExecutor::new()), ); let path = gemset.gem_bin_path("ruby-lsp").unwrap(); @@ -264,11 +245,11 @@ mod tests { fn test_gem_env() { let gemset = Gemset::new( TEST_GEM_HOME.into(), - TEST_WORKING_DIR.into(), + None, Box::new(MockGemCommandExecutor::new()), ); let env = gemset.env(None); - assert_eq!(env.len(), 2); + assert_eq!(env.len(), 1); assert_eq!(env[0].0, "GEM_PATH"); assert_eq!(env[0].1, "/test/gem_home:$GEM_PATH"); } @@ -277,32 +258,30 @@ mod tests { fn test_gem_env_with_env_vars() { let gemset = Gemset::new( TEST_GEM_HOME.into(), - TEST_WORKING_DIR.into(), + None, Box::new(MockGemCommandExecutor::new()), ); let env = gemset.env(Some(&[("GEM_HOME", "/home/user/.gem")])); - assert_eq!(env.len(), 3); + assert_eq!(env.len(), 2); let env_map: std::collections::HashMap = env.into_iter().collect(); assert_eq!(env_map.get("GEM_HOME").unwrap(), "/home/user/.gem"); assert_eq!(env_map.get("GEM_PATH").unwrap(), "/test/gem_home:$GEM_PATH"); - assert_eq!(env_map.get("PATH").unwrap(), "/test/gem_home/bin:$PATH"); } #[test] fn test_gem_env_with_env_vars_overwrite() { let gemset = Gemset::new( TEST_GEM_HOME.into(), - TEST_WORKING_DIR.into(), + None, Box::new(MockGemCommandExecutor::new()), ); let env = gemset.env(Some(&[("GEM_PATH", "/home/user/.gem")])); - assert_eq!(env.len(), 3); + assert_eq!(env.len(), 1); // GEM_PATH should be overwritten with our value let env_map: std::collections::HashMap = env.into_iter().collect(); assert_eq!(env_map.get("GEM_PATH").unwrap(), "/test/gem_home:$GEM_PATH"); - assert_eq!(env_map.get("PATH").unwrap(), "/test/gem_home/bin:$PATH"); } #[test] @@ -319,11 +298,7 @@ mod tests { "--no-document", gem_name, ], - &[ - ("GEM_HOME", TEST_GEM_HOME), - ("GEM_PATH", TEST_GEM_HOME), - ("RBENV_DIR", TEST_WORKING_DIR), - ], + &[("GEM_HOME", TEST_GEM_HOME), ("GEM_PATH", TEST_GEM_HOME)], Ok(Output { status: Some(0), stdout: "Successfully installed ruby-lsp-1.0.0".as_bytes().to_vec(), @@ -335,7 +310,7 @@ mod tests { } #[test] - fn test_install_gem_failure() { + fn test_install_gem_with_custom_env() { let mock_executor = MockGemCommandExecutor::new(); let gem_name = "ruby-lsp"; mock_executor.expect( @@ -351,8 +326,37 @@ mod tests { &[ ("GEM_HOME", TEST_GEM_HOME), ("GEM_PATH", TEST_GEM_HOME), - ("RBENV_DIR", TEST_WORKING_DIR), + ("CUSTOM_VAR", "custom_value"), + ], + Ok(Output { + status: Some(0), + stdout: "Successfully installed ruby-lsp-1.0.0".as_bytes().to_vec(), + stderr: Vec::new(), + }), + ); + let gemset = Gemset::new( + TEST_GEM_HOME.into(), + Some(&[("CUSTOM_VAR", "custom_value")]), + Box::new(mock_executor), + ); + assert!(gemset.install_gem(gem_name).is_ok()); + } + + #[test] + fn test_install_gem_failure() { + let mock_executor = MockGemCommandExecutor::new(); + let gem_name = "ruby-lsp"; + mock_executor.expect( + "gem", + &[ + "install", + "--norc", + "--no-user-install", + "--no-format-executable", + "--no-document", + gem_name, ], + &[("GEM_HOME", TEST_GEM_HOME), ("GEM_PATH", TEST_GEM_HOME)], Ok(Output { status: Some(1), stdout: Vec::new(), @@ -374,11 +378,7 @@ mod tests { mock_executor.expect( "gem", &["update", "--norc", gem_name], - &[ - ("GEM_HOME", TEST_GEM_HOME), - ("GEM_PATH", TEST_GEM_HOME), - ("RBENV_DIR", TEST_WORKING_DIR), - ], + &[("GEM_HOME", TEST_GEM_HOME), ("GEM_PATH", TEST_GEM_HOME)], Ok(Output { status: Some(0), stdout: "Gems updated: ruby-lsp".as_bytes().to_vec(), @@ -396,11 +396,7 @@ mod tests { mock_executor.expect( "gem", &["update", "--norc", gem_name], - &[ - ("GEM_HOME", TEST_GEM_HOME), - ("GEM_PATH", TEST_GEM_HOME), - ("RBENV_DIR", TEST_WORKING_DIR), - ], + &[("GEM_HOME", TEST_GEM_HOME), ("GEM_PATH", TEST_GEM_HOME)], Ok(Output { status: Some(1), stdout: Vec::new(), @@ -428,11 +424,7 @@ mod tests { mock_executor.expect( "gem", &["list", "--norc", "--exact", gem_name], - &[ - ("GEM_HOME", TEST_GEM_HOME), - ("GEM_PATH", TEST_GEM_HOME), - ("RBENV_DIR", TEST_WORKING_DIR), - ], + &[("GEM_HOME", TEST_GEM_HOME), ("GEM_PATH", TEST_GEM_HOME)], Ok(Output { status: Some(0), stdout: gem_list_output.as_bytes().to_vec(), @@ -457,11 +449,7 @@ mod tests { mock_executor.expect( "gem", &["list", "--norc", "--exact", gem_name], - &[ - ("GEM_HOME", TEST_GEM_HOME), - ("GEM_PATH", TEST_GEM_HOME), - ("RBENV_DIR", TEST_WORKING_DIR), - ], + &[("GEM_HOME", TEST_GEM_HOME), ("GEM_PATH", TEST_GEM_HOME)], Ok(Output { status: Some(0), stdout: gem_list_output.as_bytes().to_vec(), @@ -482,11 +470,7 @@ mod tests { mock_executor.expect( "gem", &["list", "--norc", "--exact", gem_name], - &[ - ("GEM_HOME", TEST_GEM_HOME), - ("GEM_PATH", TEST_GEM_HOME), - ("RBENV_DIR", TEST_WORKING_DIR), - ], + &[("GEM_HOME", TEST_GEM_HOME), ("GEM_PATH", TEST_GEM_HOME)], Ok(Output { status: Some(0), stdout: gem_list_output.as_bytes().to_vec(), @@ -505,11 +489,7 @@ mod tests { mock_executor.expect( "gem", &["list", "--norc", "--exact", gem_name], - &[ - ("GEM_HOME", TEST_GEM_HOME), - ("GEM_PATH", TEST_GEM_HOME), - ("RBENV_DIR", TEST_WORKING_DIR), - ], + &[("GEM_HOME", TEST_GEM_HOME), ("GEM_PATH", TEST_GEM_HOME)], Ok(Output { status: Some(127), stdout: Vec::new(), @@ -536,11 +516,7 @@ mod tests { mock_executor.expect( "gem", &["outdated", "--norc"], - &[ - ("GEM_HOME", TEST_GEM_HOME), - ("GEM_PATH", TEST_GEM_HOME), - ("RBENV_DIR", TEST_WORKING_DIR), - ], + &[("GEM_HOME", TEST_GEM_HOME), ("GEM_PATH", TEST_GEM_HOME)], Ok(Output { status: Some(0), stdout: outdated_output.as_bytes().to_vec(), @@ -561,11 +537,7 @@ mod tests { mock_executor.expect( "gem", &["outdated", "--norc"], - &[ - ("GEM_HOME", TEST_GEM_HOME), - ("GEM_PATH", TEST_GEM_HOME), - ("RBENV_DIR", TEST_WORKING_DIR), - ], + &[("GEM_HOME", TEST_GEM_HOME), ("GEM_PATH", TEST_GEM_HOME)], Ok(Output { status: Some(0), stdout: outdated_output.as_bytes().to_vec(), @@ -584,11 +556,7 @@ mod tests { mock_executor.expect( "gem", &["outdated", "--norc"], - &[ - ("GEM_HOME", TEST_GEM_HOME), - ("GEM_PATH", TEST_GEM_HOME), - ("RBENV_DIR", TEST_WORKING_DIR), - ], + &[("GEM_HOME", TEST_GEM_HOME), ("GEM_PATH", TEST_GEM_HOME)], Ok(Output { status: Some(1), stdout: Vec::new(), @@ -612,11 +580,7 @@ mod tests { mock_executor.expect( "gem", &["uninstall", "--norc", gem_name, "--version", gem_version], - &[ - ("GEM_HOME", TEST_GEM_HOME), - ("GEM_PATH", TEST_GEM_HOME), - ("RBENV_DIR", TEST_WORKING_DIR), - ], + &[("GEM_HOME", TEST_GEM_HOME), ("GEM_PATH", TEST_GEM_HOME)], Ok(Output { status: Some(0), stdout: format!("Successfully uninstalled {gem_name}-{gem_version}") @@ -638,7 +602,7 @@ mod tests { mock_executor.expect( "gem", &["uninstall", "--norc", gem_name, "--version", gem_version], - &[("GEM_HOME", TEST_GEM_HOME), ("GEM_PATH", TEST_GEM_HOME), ("RBENV_DIR", TEST_WORKING_DIR)], + &[("GEM_HOME", TEST_GEM_HOME), ("GEM_PATH", TEST_GEM_HOME)], Ok(Output { status: Some(1), stdout: Vec::new(), @@ -664,11 +628,7 @@ mod tests { mock_executor.expect( "gem", &["uninstall", "--norc", gem_name, "--version", gem_version], - &[ - ("GEM_HOME", TEST_GEM_HOME), - ("GEM_PATH", TEST_GEM_HOME), - ("RBENV_DIR", TEST_WORKING_DIR), - ], + &[("GEM_HOME", TEST_GEM_HOME), ("GEM_PATH", TEST_GEM_HOME)], Err("Command not found: gem".to_string()), ); let gemset = create_gemset(mock_executor); diff --git a/src/language_servers/language_server.rs b/src/language_servers/language_server.rs index 82b1931..1380282 100644 --- a/src/language_servers/language_server.rs +++ b/src/language_servers/language_server.rs @@ -222,17 +222,17 @@ pub trait LanguageServer { .to_string_lossy() .to_string(); - let gemset = Gemset::new( - PathBuf::from(&gem_home), - PathBuf::from(worktree.root_path()), - Box::new(RealCommandExecutor), - ); let worktree_shell_env = worktree.shell_env(); let worktree_shell_env_vars: Vec<(&str, &str)> = worktree_shell_env .iter() .map(|(key, value)| (key.as_str(), value.as_str())) .collect(); + let gemset = Gemset::new( + PathBuf::from(&gem_home), + Some(&worktree_shell_env_vars), + Box::new(RealCommandExecutor), + ); zed::set_language_server_installation_status( language_server_id, &zed::LanguageServerInstallationStatus::CheckingForUpdate, diff --git a/src/ruby.rs b/src/ruby.rs index a9f4874..1d651a9 100644 --- a/src/ruby.rs +++ b/src/ruby.rs @@ -154,11 +154,8 @@ impl zed::Extension for RubyExtension { Err(_e) => { let gem_home = std::env::current_dir() .map_err(|e| format!("Failed to get extension directory: {e}"))?; - let gemset = Gemset::new( - gem_home, - PathBuf::from(worktree.root_path()), - Box::new(RealCommandExecutor), - ); + let gemset = + Gemset::new(gem_home, Some(&env_vars), Box::new(RealCommandExecutor)); match gemset.install_gem("debug") { Ok(_) => rdbg_path = gemset.gem_bin_path("rdbg")?,