diff options
| author | JJ Liebig <jonathan.liebig@gmail.com> | 2026-09-13 20:24:51 +0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-09-13 18:24:51 +0200 |
| commit | 38bc172fe8361f019760dd4001279d7e44f09832 (patch) | |
| tree | eccbb2f9b6e1fa8a82200b61f057f65bc0e124e2 /src | |
| parent | 5f26879bcd4190750c79cbff92454e400166ca98 (diff) | |
fix(windows): honor default_shell in login shell mode (#4060)
* fix(windows): honor default_shell in login shell mode
Login-mode panes used portable-pty's default-program builder, which resolves to %ComSpec% (cmd.exe) on Windows and ignores the SHELL env override, so [terminal].default_shell was silently ignored. Build the launch command from the configured shell instead and append the shell's own login flag for POSIX-style shells that support one.
refs #1445
* fix(windows): add csh and tcsh to login shell flags
refs #1445
Diffstat (limited to 'src')
| -rw-r--r-- | src/pane.rs | 123 |
1 files changed, 123 insertions, 0 deletions
diff --git a/src/pane.rs b/src/pane.rs index c56cfbb3..a931d3ad 100644 --- a/src/pane.rs +++ b/src/pane.rs @@ -1717,12 +1717,41 @@ fn resolve_shell_for_login_mode(shell: &str) -> io::Result<String> { /// prompt would show success after a failed command (verified on 5.1). pub(crate) const WINDOWS_POWERSHELL_SHELL_INTEGRATION_COMMAND: &str = r"if ($null -eq $global:__HerdrOriginalPrompt) { $global:__HerdrOriginalPrompt = $function:prompt; function global:prompt { $out = @(& $global:__HerdrOriginalPrompt) -join ' '; $loc = $ExecutionContext.SessionState.Path.CurrentLocation; if ($loc.Provider.Name -eq 'FileSystem') { try { [Environment]::CurrentDirectory = $loc.ProviderPath } catch {}; $esc = [string][char]27; $out += $esc + ']9;9;' + $loc.ProviderPath + $esc + '\' }; $out } }"; +/// Login flags appended when launching POSIX-style shells on Windows. +/// +/// `portable-pty`'s default-program path resolves to `%ComSpec%` (cmd.exe) on +/// Windows and ignores the `SHELL` env override, and the Unix argv0-prefix +/// login convention does not exist there. A login shell therefore has to be +/// launched directly with the shell's own login flag. Only POSIX-style shells +/// that define one (the sh family, fish, and csh/tcsh) get it; Windows-native +/// shells such as cmd.exe and PowerShell have no login concept and launch +/// plain. +fn windows_login_shell_args(shell: &str) -> &'static [&'static str] { + let name = shell + .rsplit(['/', '\\']) + .next() + .unwrap_or(shell) + .to_ascii_lowercase(); + let name = name.strip_suffix(".exe").unwrap_or(&name); + match name { + "sh" | "bash" | "zsh" | "ksh" | "dash" | "ash" | "mksh" | "fish" | "csh" | "tcsh" => { + &["-l"] + } + _ => &[], + } +} + fn pane_shell_command_builder_for_target( shell_config: PaneShellConfig<'_>, target: ShellLaunchTarget, ) -> io::Result<CommandBuilder> { let shell = pane_shell(shell_config.default_shell); if shell_mode_uses_login_shell(shell_config.mode, target) { + if target == ShellLaunchTarget::Windows { + let mut cmd = CommandBuilder::new(&shell); + cmd.args(windows_login_shell_args(&shell)); + return Ok(cmd); + } let mut cmd = CommandBuilder::new_default_prog(); cmd.env("SHELL", resolve_shell_for_login_mode(&shell)?); Ok(cmd) @@ -3809,6 +3838,100 @@ mod tests { } #[test] + fn windows_login_shell_builder_launches_configured_shell_with_login_flag() { + let cmd = pane_shell_command_builder_for_target( + PaneShellConfig::new("bash.exe", crate::config::ShellModeConfig::Login), + ShellLaunchTarget::Windows, + ) + .unwrap(); + + assert!( + !cmd.is_default_prog(), + "Windows login mode must not fall back to the default program (cmd.exe)" + ); + assert_eq!( + cmd.get_argv(), + &[ + std::ffi::OsString::from("bash.exe"), + std::ffi::OsString::from("-l"), + ] + ); + } + + #[test] + fn windows_login_shell_builder_launches_native_shells_without_flag() { + for shell in [ + "cmd.exe", + "powershell.exe", + "C:\\Windows\\System32\\cmd.exe", + ] { + let cmd = pane_shell_command_builder_for_target( + PaneShellConfig::new(shell, crate::config::ShellModeConfig::Login), + ShellLaunchTarget::Windows, + ) + .unwrap(); + + assert!(!cmd.is_default_prog(), "shell {shell:?}"); + assert_eq!( + cmd.get_argv(), + &[std::ffi::OsString::from(shell)], + "shell {shell:?}" + ); + } + } + + #[test] + fn windows_login_shell_args_maps_posix_shells_to_login_flag() { + for shell in [ + "sh", + "sh.exe", + "bash", + "bash.exe", + "BASH.EXE", + "zsh", + "zsh.exe", + "ksh", + "dash", + "ash", + "mksh", + "fish", + "fish.exe", + "csh", + "csh.exe", + "tcsh", + "tcsh.exe", + "C:\\Program Files\\Git\\bin\\bash.exe", + ] { + assert_eq!( + windows_login_shell_args(shell), + &["-l"], + "shell {shell:?} should get the login flag" + ); + } + } + + #[test] + fn windows_login_shell_args_omits_flag_for_shells_without_login_concept() { + for shell in [ + "cmd", + "cmd.exe", + "powershell", + "powershell.exe", + "pwsh", + "pwsh.exe", + "nu", + "nu.exe", + "C:\\Windows\\System32\\cmd.exe", + ] { + assert_eq!( + windows_login_shell_args(shell), + &[] as &[&str], + "shell {shell:?} must not get a login flag" + ); + } + } + + #[test] fn auto_shell_builder_keeps_direct_shell_on_non_macos_target() { let cmd = pane_shell_command_builder_for_target( PaneShellConfig::new("/bin/sh", crate::config::ShellModeConfig::Auto), |
