From 365dcc5ac7dc647165cfd51ae15f9d31853e0166 Mon Sep 17 00:00:00 2001 From: Peter Tripp Date: Wed, 19 Feb 2025 20:31:42 +0000 Subject: [PATCH] Improve support for tcsh/csh as login shells (#25122) --- crates/project/src/environment.rs | 41 ++++++++++++++++++------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/crates/project/src/environment.rs b/crates/project/src/environment.rs index b6965abf7a1e6fe85aa3a7237fbdc8cd8efd523c..9a5aa9fba14245e63f44f77d2840e9a70722cf52 100644 --- a/crates/project/src/environment.rs +++ b/crates/project/src/environment.rs @@ -277,10 +277,12 @@ async fn load_shell_environment( (None, Some(message)) } - let marker = "ZED_SHELL_START"; + const MARKER: &str = "ZED_SHELL_START"; let Some(shell) = std::env::var("SHELL").log_err() else { return message("Failed to get login environment. SHELL environment variable is not set"); }; + let shell_path = PathBuf::from(&shell); + let shell_name = shell_path.file_name().and_then(|f| f.to_str()); // What we're doing here is to spawn a shell and then `cd` into // the project directory to get the env in there as if the user @@ -303,22 +305,27 @@ async fn load_shell_environment( // // We still don't know why `$SHELL -l -i -c '/usr/bin/env -0'` would // do that, but it does, and `exit 0` helps. - let additional_command = PathBuf::from(&shell) - .file_name() - .and_then(|f| f.to_str()) - .and_then(|shell| match shell { - "fish" => Some("emit fish_prompt;"), - _ => None, - }); - - let command = format!( - "cd '{}';{} printf '%s' {marker}; /usr/bin/env; exit 0;", - dir.display(), - additional_command.unwrap_or("") - ); + + let command = match shell_name { + Some("fish") => format!( + "cd '{}'; emit fish_prompt; printf '%s' {MARKER}; /usr/bin/env; exit 0;", + dir.display() + ), + _ => format!( + "cd '{}'; printf '%s' {MARKER}; /usr/bin/env; exit 0;", + dir.display() + ), + }; + + // csh/tcsh only supports `-l` if it's the only flag. So this won't be a login shell. + // Users must rely on vars from `~/.tcshrc` or `~/.cshrc` and not `.login` as a result. + let args = match shell_name { + Some("tcsh") | Some("csh") => vec!["-i", "-c", &command], + _ => vec!["-l", "-i", "-c", &command], + }; let Some(output) = smol::process::Command::new(&shell) - .args(["-l", "-i", "-c", &command]) + .args(&args) .output() .await .log_err() @@ -332,7 +339,7 @@ async fn load_shell_environment( } let stdout = String::from_utf8_lossy(&output.stdout); - let Some(env_output_start) = stdout.find(marker) else { + let Some(env_output_start) = stdout.find(MARKER) else { let stderr = String::from_utf8_lossy(&output.stderr); log::error!( "failed to parse output of `env` command in login shell. stdout: {:?}, stderr: {:?}", @@ -343,7 +350,7 @@ async fn load_shell_environment( }; let mut parsed_env = HashMap::default(); - let env_output = &stdout[env_output_start + marker.len()..]; + let env_output = &stdout[env_output_start + MARKER.len()..]; parse_env_output(env_output, |key, value| { parsed_env.insert(key, value);