Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 122 additions & 29 deletions src/gemset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<dyn CommandExecutor>,
}

impl Gemset {
pub fn new(gem_home: PathBuf, command_executor: Box<dyn CommandExecutor>) -> Self {
pub fn new(
gem_home: PathBuf,
working_dir: PathBuf,
command_executor: Box<dyn CommandExecutor>,
) -> Self {
Self {
gem_home,
working_dir,
command_executor,
}
}
Expand All @@ -26,18 +32,33 @@ impl Gemset {
}

pub fn env(&self, envs: Option<&[(&str, &str)]>) -> Vec<(String, String)> {
let mut env_map: std::collections::HashMap<String, String> = 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> {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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();
Expand All @@ -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");
}
Expand All @@ -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<String, String> = 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<String, String> = 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]
Expand All @@ -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(),
Expand All @@ -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(),
Expand All @@ -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(),
Expand All @@ -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(),
Expand Down Expand Up @@ -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(),
Expand All @@ -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(),
Expand All @@ -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(),
Expand All @@ -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(),
Expand All @@ -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(),
Expand All @@ -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(),
Expand All @@ -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(),
Expand All @@ -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}")
Expand All @@ -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(),
Expand All @@ -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);
Expand Down
6 changes: 5 additions & 1 deletion src/language_servers/language_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
6 changes: 5 additions & 1 deletion src/ruby.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")?,
Expand Down