Skip to content

Commit 6ea8390

Browse files
committed
Add support for Fish on Windows
1 parent d30f84c commit 6ea8390

File tree

1 file changed

+66
-22
lines changed

1 file changed

+66
-22
lines changed

crates/pixi_cli/src/shell.rs

Lines changed: 66 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use rattler_shell::{
77
activation::PathModificationBehavior,
88
shell::{Bash, CmdExe, PowerShell, Shell, ShellEnum, ShellScript},
99
};
10+
use tempfile::TempPath;
1011

1112
use pixi_config::{ConfigCli, ConfigCliActivation, ConfigCliPrompt};
1213
use pixi_core::{
@@ -137,60 +138,85 @@ fn start_cmdexe(
137138
}
138139

139140
// allowing dead code so that we test this on unix compilation as well
140-
#[cfg_attr(unix, expect(unused))]
141-
fn start_winbash(
142-
bash: Bash,
141+
#[allow(dead_code)]
142+
fn start_shell_using_winbash<T: Shell + Copy + 'static, F: FnOnce(&TempPath) -> String>(
143+
shell: T,
144+
exec_format: F,
143145
env: &HashMap<String, String>,
144146
prompt: String,
145147
prefix: &Prefix,
146148
source_shell_completions: bool,
147149
) -> miette::Result<Option<i32>> {
148150
// create a tempfile for activation
149-
let mut temp_file = tempfile::Builder::new()
150-
.prefix("pixi_env_")
151-
.suffix(&format!(".{}", bash.extension()))
151+
let mut init_file = tempfile::Builder::new()
152+
.prefix("pixi_init_")
153+
.suffix(".sh")
152154
.rand_bytes(3)
153155
.tempfile()
154156
.into_diagnostic()?;
155157

156-
let mut shell_script = ShellScript::new(bash, Platform::current());
158+
// the init file is always consumed by bash
159+
let mut init_script = ShellScript::new(Bash, Platform::current());
157160
for (key, value) in env {
158161
if key == "PATH" || key == "Path" {
159162
// For Git Bash on Windows, the PATH must be formatted as POSIX paths according
160163
// to the cygpath command, and separated by ":" instead of ";". Use the
161-
// shell_script.set_path call to handle these details.
164+
// init_script.set_path call to handle these details.
162165
let paths = value
163-
.split(";")
166+
.split(';')
164167
.map(PathBuf::from)
165168
.collect::<Vec<PathBuf>>();
166-
shell_script
169+
init_script
167170
.set_path(&paths, PathModificationBehavior::Replace)
168171
.into_diagnostic()?;
169172
} else {
170-
shell_script.set_env_var(key, value).into_diagnostic()?;
173+
init_script.set_env_var(key, value).into_diagnostic()?;
171174
}
172175
}
176+
init_file
177+
.write_all(init_script.contents().into_diagnostic()?.as_bytes())
178+
.into_diagnostic()?;
179+
180+
// the env file is consumed by the actual shell
181+
let mut env_file = tempfile::Builder::new()
182+
.prefix("pixi_env_")
183+
.suffix(&format!(".{}", shell.extension()))
184+
.rand_bytes(3)
185+
.tempfile()
186+
.into_diagnostic()?;
187+
188+
// Write custom prompt to the env file
189+
env_file.write_all(prompt.as_bytes()).into_diagnostic()?;
190+
191+
// Write code to bootstrap the actual shell we want to start
173192
if source_shell_completions {
174-
if let Some(completions_dir) = bash.completion_script_location() {
175-
shell_script
193+
let mut env_script = ShellScript::new(shell, Platform::current());
194+
if let Some(completions_dir) = shell.completion_script_location() {
195+
env_script
176196
.source_completions(&prefix.root().join(completions_dir))
177197
.into_diagnostic()?;
178198
}
199+
env_file
200+
.write_all(env_script.contents().into_diagnostic()?.as_bytes())
201+
.into_diagnostic()?;
179202
}
180-
temp_file
181-
.write_all(shell_script.contents().into_diagnostic()?.as_bytes())
203+
204+
env_file.flush().into_diagnostic()?;
205+
let env_file_path = env_file.into_temp_path();
206+
let exec_str = exec_format(&env_file_path);
207+
208+
init_file
209+
.write_all(exec_str.as_bytes())
182210
.into_diagnostic()?;
183211

184-
// Write custom prompt to the env file
185-
temp_file.write_all(prompt.as_bytes()).into_diagnostic()?;
186-
temp_file.flush().into_diagnostic()?;
212+
init_file.flush().into_diagnostic()?;
187213

188214
// close the file handle, but keep the path (needed for Windows)
189-
let temp_path = temp_file.into_temp_path();
215+
let init_file_path = init_file.into_temp_path();
190216

191-
let mut command = std::process::Command::new(bash.executable());
217+
let mut command = std::process::Command::new(Bash.executable());
192218
command.arg("--init-file");
193-
command.arg(&temp_path);
219+
command.arg(&init_file_path);
194220
command.arg("-i");
195221

196222
ignore_ctrl_c();
@@ -390,7 +416,24 @@ pub async fn execute(args: Args) -> miette::Result<()> {
390416
ShellEnum::PowerShell(pwsh) => start_powershell(pwsh, env, prompt_hook),
391417
ShellEnum::CmdExe(cmdexe) => start_cmdexe(cmdexe, env, prompt_hook),
392418
ShellEnum::Bash(bash) => {
393-
start_winbash(bash, env, prompt_hook, &prefix, source_shell_completions)
419+
start_shell_using_winbash(
420+
bash,
421+
|env_file| format!(". \"{}\"", env_file.as_os_str().to_string_lossy()),
422+
env,
423+
prompt_hook,
424+
&prefix,
425+
source_shell_completions,
426+
)
427+
}
428+
ShellEnum::Fish(fish) => {
429+
start_shell_using_winbash(
430+
fish,
431+
|env_file| format!("exec fish -i -C \". \\\"{}\\\"\"", env_file.as_os_str().to_string_lossy()),
432+
env,
433+
prompt_hook,
434+
&prefix,
435+
source_shell_completions,
436+
)
394437
}
395438
_ => {
396439
miette::bail!("Unsupported shell: {:?}", interactive_shell);
@@ -461,3 +504,4 @@ pub async fn execute(args: Args) -> miette::Result<()> {
461504
}
462505
}
463506
}
507+

0 commit comments

Comments
 (0)