diff --git a/src/gemset.rs b/src/gemset.rs index 5872101..59fc688 100644 --- a/src/gemset.rs +++ b/src/gemset.rs @@ -4,14 +4,20 @@ use std::path::PathBuf; /// A simple wrapper around the `gem` command. pub struct Gemset { - pub gem_home: PathBuf, + gem_home: PathBuf, + working_dir: PathBuf, command_executor: Box, } impl Gemset { - pub fn new(gem_home: PathBuf, command_executor: Box) -> Self { + pub fn new( + gem_home: PathBuf, + working_dir: PathBuf, + command_executor: Box, + ) -> Self { Self { gem_home, + working_dir, command_executor, } } @@ -26,18 +32,33 @@ impl Gemset { } pub fn env(&self, envs: Option<&[(&str, &str)]>) -> Vec<(String, String)> { - let mut env_map: std::collections::HashMap = envs - .unwrap_or(&[]) - .iter() - .map(|(k, v)| (k.to_string(), v.to_string())) - .collect(); - - env_map.insert( + let gem_bin_path = self.gem_home.join("bin"); + let gem_bin_str = gem_bin_path.display().to_string(); + let gem_path = ( "GEM_PATH".to_string(), format!("{}:$GEM_PATH", self.gem_home.display()), ); - env_map.into_iter().collect() + 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() } pub fn install_gem(&self, name: &str) -> Result<(), String> { @@ -108,7 +129,16 @@ impl Gemset { .gem_home .to_str() .ok_or("Failed to convert gem_home path to string")?; - let command_envs = &[("GEM_HOME", gem_home_str), ("GEM_PATH", gem_home_str)]; + 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), + ]; self.command_executor .execute("gem", &full_args, command_envs) @@ -209,15 +239,21 @@ 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(), Box::new(mock_executor)) + Gemset::new( + TEST_GEM_HOME.into(), + TEST_WORKING_DIR.into(), + Box::new(mock_executor), + ) } #[test] fn test_gem_bin_path() { let gemset = Gemset::new( TEST_GEM_HOME.into(), + TEST_WORKING_DIR.into(), Box::new(MockGemCommandExecutor::new()), ); let path = gemset.gem_bin_path("ruby-lsp").unwrap(); @@ -228,10 +264,11 @@ mod tests { fn test_gem_env() { let gemset = Gemset::new( TEST_GEM_HOME.into(), + TEST_WORKING_DIR.into(), Box::new(MockGemCommandExecutor::new()), ); let env = gemset.env(None); - assert_eq!(env.len(), 1); + assert_eq!(env.len(), 2); assert_eq!(env[0].0, "GEM_PATH"); assert_eq!(env[0].1, "/test/gem_home:$GEM_PATH"); } @@ -240,28 +277,32 @@ mod tests { fn test_gem_env_with_env_vars() { let gemset = Gemset::new( TEST_GEM_HOME.into(), + TEST_WORKING_DIR.into(), Box::new(MockGemCommandExecutor::new()), ); let env = gemset.env(Some(&[("GEM_HOME", "/home/user/.gem")])); - assert_eq!(env.len(), 2); + assert_eq!(env.len(), 3); 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(), Box::new(MockGemCommandExecutor::new()), ); let env = gemset.env(Some(&[("GEM_PATH", "/home/user/.gem")])); - assert_eq!(env.len(), 1); + assert_eq!(env.len(), 3); // 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] @@ -278,7 +319,11 @@ mod tests { "--no-document", gem_name, ], - &[("GEM_HOME", TEST_GEM_HOME), ("GEM_PATH", TEST_GEM_HOME)], + &[ + ("GEM_HOME", TEST_GEM_HOME), + ("GEM_PATH", TEST_GEM_HOME), + ("RBENV_DIR", TEST_WORKING_DIR), + ], Ok(Output { status: Some(0), stdout: "Successfully installed ruby-lsp-1.0.0".as_bytes().to_vec(), @@ -303,7 +348,11 @@ mod tests { "--no-document", gem_name, ], - &[("GEM_HOME", TEST_GEM_HOME), ("GEM_PATH", TEST_GEM_HOME)], + &[ + ("GEM_HOME", TEST_GEM_HOME), + ("GEM_PATH", TEST_GEM_HOME), + ("RBENV_DIR", TEST_WORKING_DIR), + ], Ok(Output { status: Some(1), stdout: Vec::new(), @@ -325,7 +374,11 @@ mod tests { mock_executor.expect( "gem", &["update", "--norc", gem_name], - &[("GEM_HOME", TEST_GEM_HOME), ("GEM_PATH", TEST_GEM_HOME)], + &[ + ("GEM_HOME", TEST_GEM_HOME), + ("GEM_PATH", TEST_GEM_HOME), + ("RBENV_DIR", TEST_WORKING_DIR), + ], Ok(Output { status: Some(0), stdout: "Gems updated: ruby-lsp".as_bytes().to_vec(), @@ -343,7 +396,11 @@ mod tests { mock_executor.expect( "gem", &["update", "--norc", gem_name], - &[("GEM_HOME", TEST_GEM_HOME), ("GEM_PATH", TEST_GEM_HOME)], + &[ + ("GEM_HOME", TEST_GEM_HOME), + ("GEM_PATH", TEST_GEM_HOME), + ("RBENV_DIR", TEST_WORKING_DIR), + ], Ok(Output { status: Some(1), stdout: Vec::new(), @@ -371,7 +428,11 @@ mod tests { mock_executor.expect( "gem", &["list", "--norc", "--exact", gem_name], - &[("GEM_HOME", TEST_GEM_HOME), ("GEM_PATH", TEST_GEM_HOME)], + &[ + ("GEM_HOME", TEST_GEM_HOME), + ("GEM_PATH", TEST_GEM_HOME), + ("RBENV_DIR", TEST_WORKING_DIR), + ], Ok(Output { status: Some(0), stdout: gem_list_output.as_bytes().to_vec(), @@ -396,7 +457,11 @@ mod tests { mock_executor.expect( "gem", &["list", "--norc", "--exact", gem_name], - &[("GEM_HOME", TEST_GEM_HOME), ("GEM_PATH", TEST_GEM_HOME)], + &[ + ("GEM_HOME", TEST_GEM_HOME), + ("GEM_PATH", TEST_GEM_HOME), + ("RBENV_DIR", TEST_WORKING_DIR), + ], Ok(Output { status: Some(0), stdout: gem_list_output.as_bytes().to_vec(), @@ -417,7 +482,11 @@ mod tests { mock_executor.expect( "gem", &["list", "--norc", "--exact", gem_name], - &[("GEM_HOME", TEST_GEM_HOME), ("GEM_PATH", TEST_GEM_HOME)], + &[ + ("GEM_HOME", TEST_GEM_HOME), + ("GEM_PATH", TEST_GEM_HOME), + ("RBENV_DIR", TEST_WORKING_DIR), + ], Ok(Output { status: Some(0), stdout: gem_list_output.as_bytes().to_vec(), @@ -436,7 +505,11 @@ mod tests { mock_executor.expect( "gem", &["list", "--norc", "--exact", gem_name], - &[("GEM_HOME", TEST_GEM_HOME), ("GEM_PATH", TEST_GEM_HOME)], + &[ + ("GEM_HOME", TEST_GEM_HOME), + ("GEM_PATH", TEST_GEM_HOME), + ("RBENV_DIR", TEST_WORKING_DIR), + ], Ok(Output { status: Some(127), stdout: Vec::new(), @@ -463,7 +536,11 @@ mod tests { mock_executor.expect( "gem", &["outdated", "--norc"], - &[("GEM_HOME", TEST_GEM_HOME), ("GEM_PATH", TEST_GEM_HOME)], + &[ + ("GEM_HOME", TEST_GEM_HOME), + ("GEM_PATH", TEST_GEM_HOME), + ("RBENV_DIR", TEST_WORKING_DIR), + ], Ok(Output { status: Some(0), stdout: outdated_output.as_bytes().to_vec(), @@ -484,7 +561,11 @@ mod tests { mock_executor.expect( "gem", &["outdated", "--norc"], - &[("GEM_HOME", TEST_GEM_HOME), ("GEM_PATH", TEST_GEM_HOME)], + &[ + ("GEM_HOME", TEST_GEM_HOME), + ("GEM_PATH", TEST_GEM_HOME), + ("RBENV_DIR", TEST_WORKING_DIR), + ], Ok(Output { status: Some(0), stdout: outdated_output.as_bytes().to_vec(), @@ -503,7 +584,11 @@ mod tests { mock_executor.expect( "gem", &["outdated", "--norc"], - &[("GEM_HOME", TEST_GEM_HOME), ("GEM_PATH", TEST_GEM_HOME)], + &[ + ("GEM_HOME", TEST_GEM_HOME), + ("GEM_PATH", TEST_GEM_HOME), + ("RBENV_DIR", TEST_WORKING_DIR), + ], Ok(Output { status: Some(1), stdout: Vec::new(), @@ -527,7 +612,11 @@ mod tests { mock_executor.expect( "gem", &["uninstall", "--norc", gem_name, "--version", gem_version], - &[("GEM_HOME", TEST_GEM_HOME), ("GEM_PATH", TEST_GEM_HOME)], + &[ + ("GEM_HOME", TEST_GEM_HOME), + ("GEM_PATH", TEST_GEM_HOME), + ("RBENV_DIR", TEST_WORKING_DIR), + ], Ok(Output { status: Some(0), stdout: format!("Successfully uninstalled {gem_name}-{gem_version}") @@ -549,7 +638,7 @@ mod tests { mock_executor.expect( "gem", &["uninstall", "--norc", gem_name, "--version", gem_version], - &[("GEM_HOME", TEST_GEM_HOME), ("GEM_PATH", TEST_GEM_HOME)], + &[("GEM_HOME", TEST_GEM_HOME), ("GEM_PATH", TEST_GEM_HOME), ("RBENV_DIR", TEST_WORKING_DIR)], Ok(Output { status: Some(1), stdout: Vec::new(), @@ -575,7 +664,11 @@ mod tests { mock_executor.expect( "gem", &["uninstall", "--norc", gem_name, "--version", gem_version], - &[("GEM_HOME", TEST_GEM_HOME), ("GEM_PATH", TEST_GEM_HOME)], + &[ + ("GEM_HOME", TEST_GEM_HOME), + ("GEM_PATH", TEST_GEM_HOME), + ("RBENV_DIR", TEST_WORKING_DIR), + ], 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 e9f419b..82b1931 100644 --- a/src/language_servers/language_server.rs +++ b/src/language_servers/language_server.rs @@ -222,7 +222,11 @@ pub trait LanguageServer { .to_string_lossy() .to_string(); - let gemset = Gemset::new(PathBuf::from(&gem_home), Box::new(RealCommandExecutor)); + 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() diff --git a/src/ruby.rs b/src/ruby.rs index e56d9d7..a9f4874 100644 --- a/src/ruby.rs +++ b/src/ruby.rs @@ -154,7 +154,11 @@ 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, Box::new(RealCommandExecutor)); + let gemset = Gemset::new( + gem_home, + PathBuf::from(worktree.root_path()), + Box::new(RealCommandExecutor), + ); match gemset.install_gem("debug") { Ok(_) => rdbg_path = gemset.gem_bin_path("rdbg")?,