aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJJ Liebig <jonathan.liebig@gmail.com>2026-09-13 20:24:51 +0400
committerGitHub <noreply@github.com>2026-09-13 18:24:51 +0200
commit38bc172fe8361f019760dd4001279d7e44f09832 (patch)
treeeccbb2f9b6e1fa8a82200b61f057f65bc0e124e2 /src
parent5f26879bcd4190750c79cbff92454e400166ca98 (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.rs123
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),