diff --git a/src/shell-integration/bash/ghostty.bash b/src/shell-integration/bash/ghostty.bash index 3127b7bdc..7a4159304 100644 --- a/src/shell-integration/bash/ghostty.bash +++ b/src/shell-integration/bash/ghostty.bash @@ -29,52 +29,41 @@ if [ -n "$GHOSTTY_BASH_INJECT" ]; then # At this point, we're in POSIX mode and rely on the injected # flags to guide is through the rest of the startup sequence. - # POSIX mode was requested by the user so there's nothing - # more to do that optionally source their original $ENV. - # No other startup files are read, per the standard. - if [[ "$ghostty_bash_inject" == *"--posix"* ]]; then - if [ -n "$GHOSTTY_BASH_ENV" ]; then - builtin source "$GHOSTTY_BASH_ENV" - builtin export ENV="$GHOSTTY_BASH_ENV" + # Restore bash's default 'posix' behavior. Also reset 'inherit_errexit', + # which doesn't happen as part of the 'posix' reset. + builtin set +o posix + builtin shopt -u inherit_errexit 2>/dev/null + + # Unexport HISTFILE if it was set by the shell integration code. + if [[ -n "$GHOSTTY_BASH_UNEXPORT_HISTFILE" ]]; then + builtin export -n HISTFILE + builtin unset GHOSTTY_BASH_UNEXPORT_HISTFILE + fi + + # Manually source the startup files, respecting the injected flags like + # --norc and --noprofile that we parsed with the shell integration code. + # + # See also: run_startup_files() in shell.c in the Bash source code + if builtin shopt -q login_shell; then + if [[ $ghostty_bash_inject != *"--noprofile"* ]]; then + [ -r /etc/profile ] && builtin source "/etc/profile" + for rcfile in "$HOME/.bash_profile" "$HOME/.bash_login" "$HOME/.profile"; do + [ -r "$rcfile" ] && { builtin source "$rcfile"; break; } + done fi - builtin unset GHOSTTY_BASH_ENV else - # Restore bash's default 'posix' behavior. Also reset 'inherit_errexit', - # which doesn't happen as part of the 'posix' reset. - builtin set +o posix - builtin shopt -u inherit_errexit 2>/dev/null - - # Unexport HISTFILE if it was set by the shell integration code. - if [[ -n "$GHOSTTY_BASH_UNEXPORT_HISTFILE" ]]; then - builtin export -n HISTFILE - builtin unset GHOSTTY_BASH_UNEXPORT_HISTFILE - fi - - # Manually source the startup files, respecting the injected flags like - # --norc and --noprofile that we parsed with the shell integration code. - # - # See also: run_startup_files() in shell.c in the Bash source code - if builtin shopt -q login_shell; then - if [[ $ghostty_bash_inject != *"--noprofile"* ]]; then - [ -r /etc/profile ] && builtin source "/etc/profile" - for rcfile in "$HOME/.bash_profile" "$HOME/.bash_login" "$HOME/.profile"; do - [ -r "$rcfile" ] && { builtin source "$rcfile"; break; } - done - fi - else - if [[ $ghostty_bash_inject != *"--norc"* ]]; then - # The location of the system bashrc is determined at bash build - # time via -DSYS_BASHRC and can therefore vary across distros: - # Arch, Debian, Ubuntu use /etc/bash.bashrc - # Fedora uses /etc/bashrc sourced from ~/.bashrc instead of SYS_BASHRC - # Void Linux uses /etc/bash/bashrc - # Nixos uses /etc/bashrc - for rcfile in /etc/bash.bashrc /etc/bash/bashrc /etc/bashrc; do - [ -r "$rcfile" ] && { builtin source "$rcfile"; break; } - done - if [[ -z "$GHOSTTY_BASH_RCFILE" ]]; then GHOSTTY_BASH_RCFILE="$HOME/.bashrc"; fi - [ -r "$GHOSTTY_BASH_RCFILE" ] && builtin source "$GHOSTTY_BASH_RCFILE" - fi + if [[ $ghostty_bash_inject != *"--norc"* ]]; then + # The location of the system bashrc is determined at bash build + # time via -DSYS_BASHRC and can therefore vary across distros: + # Arch, Debian, Ubuntu use /etc/bash.bashrc + # Fedora uses /etc/bashrc sourced from ~/.bashrc instead of SYS_BASHRC + # Void Linux uses /etc/bash/bashrc + # Nixos uses /etc/bashrc + for rcfile in /etc/bash.bashrc /etc/bash/bashrc /etc/bashrc; do + [ -r "$rcfile" ] && { builtin source "$rcfile"; break; } + done + if [[ -z "$GHOSTTY_BASH_RCFILE" ]]; then GHOSTTY_BASH_RCFILE="$HOME/.bashrc"; fi + [ -r "$GHOSTTY_BASH_RCFILE" ] && builtin source "$GHOSTTY_BASH_RCFILE" fi fi diff --git a/src/termio/shell_integration.zig b/src/termio/shell_integration.zig index 634f6e960..8cd2a92ae 100644 --- a/src/termio/shell_integration.zig +++ b/src/termio/shell_integration.zig @@ -174,31 +174,36 @@ fn setupBash( try args.append("--posix"); // Stores the list of intercepted command line flags that will be passed - // to our shell integration script: --posix --norc --noprofile + // to our shell integration script: --norc --noprofile // We always include at least "1" so the script can differentiate between // being manually sourced or automatically injected (from here). var inject = try std.BoundedArray(u8, 32).init(0); try inject.appendSlice("1"); - var posix = false; - + // Walk through the rest of the given arguments. If we see an option that + // would require complex or unsupported integration behavior, we bail out + // and skip loading our shell integration. Users can still manually source + // the shell integration script. + // + // Unsupported options: + // -c -c is always non-interactive + // --posix POSIX mode (a la /bin/sh) + // // Some additional cases we don't yet cover: // // - If additional file arguments are provided (after a `-` or `--` flag), // and the `i` shell option isn't being explicitly set, we can assume a // non-interactive shell session and skip loading our shell integration. + var rcfile: ?[]const u8 = null; while (iter.next()) |arg| { if (std.mem.eql(u8, arg, "--posix")) { - try inject.appendSlice(" --posix"); - posix = true; + return null; } else if (std.mem.eql(u8, arg, "--norc")) { try inject.appendSlice(" --norc"); } else if (std.mem.eql(u8, arg, "--noprofile")) { try inject.appendSlice(" --noprofile"); } else if (std.mem.eql(u8, arg, "--rcfile") or std.mem.eql(u8, arg, "--init-file")) { - if (iter.next()) |rcfile| { - try env.put("GHOSTTY_BASH_RCFILE", rcfile); - } + rcfile = iter.next(); } else if (arg.len > 1 and arg[0] == '-' and arg[1] != '-') { // '-c command' is always non-interactive if (std.mem.indexOfScalar(u8, arg, 'c') != null) { @@ -210,10 +215,13 @@ fn setupBash( } } try env.put("GHOSTTY_BASH_INJECT", inject.slice()); + if (rcfile) |v| { + try env.put("GHOSTTY_BASH_RCFILE", v); + } // In POSIX mode, HISTFILE defaults to ~/.sh_history, so unless we're // staying in POSIX mode (--posix), change it back to ~/.bash_history. - if (!posix and env.get("HISTFILE") == null) { + if (env.get("HISTFILE") == null) { var home_buf: [1024]u8 = undefined; if (try homedir.home(&home_buf)) |home| { var histfile_buf: [std.fs.max_path_bytes]u8 = undefined; @@ -227,13 +235,6 @@ fn setupBash( } } - // Preserve the existing ENV value when staying in POSIX mode (--posix). - if (env.get("ENV")) |old| { - if (posix) { - try env.put("GHOSTTY_BASH_ENV", old); - } - } - // Set our new ENV to point to our integration script. var path_buf: [std.fs.max_path_bytes]u8 = undefined; const integ_dir = try std.fmt.bufPrint( @@ -262,21 +263,32 @@ test "bash" { try testing.expectEqualStrings("1", env.get("GHOSTTY_BASH_INJECT").?); } -test "bash: inject flags" { +test "bash: unsupported options" { const testing = std.testing; const alloc = testing.allocator; - // bash --posix - { + const cmdlines = [_][]const u8{ + "bash --posix", + "bash --rcfile script.sh --posix", + "bash --init-file script.sh --posix", + "bash -c script.sh", + "bash -ic script.sh", + }; + + for (cmdlines) |cmdline| { var env = EnvMap.init(alloc); defer env.deinit(); - const command = try setupBash(alloc, "bash --posix", ".", &env); - defer if (command) |c| alloc.free(c); - - try testing.expectEqualStrings("bash --posix", command.?); - try testing.expectEqualStrings("1 --posix", env.get("GHOSTTY_BASH_INJECT").?); + try testing.expect(try setupBash(alloc, cmdline, ".", &env) == null); + try testing.expect(env.get("GHOSTTY_BASH_INJECT") == null); + try testing.expect(env.get("GHOSTTY_BASH_RCFILE") == null); + try testing.expect(env.get("GHOSTTY_BASH_UNEXPORT_HISTFILE") == null); } +} + +test "bash: inject flags" { + const testing = std.testing; + const alloc = testing.allocator; // bash --norc { @@ -329,17 +341,6 @@ test "bash: rcfile" { } } -test "bash: -c command" { - const testing = std.testing; - const alloc = testing.allocator; - - var env = EnvMap.init(alloc); - defer env.deinit(); - - try testing.expect(try setupBash(alloc, "bash -c script.sh", ".", &env) == null); - try testing.expect(try setupBash(alloc, "bash -ic script.sh", ".", &env) == null); -} - test "bash: HISTFILE" { const testing = std.testing; const alloc = testing.allocator; @@ -369,68 +370,6 @@ test "bash: HISTFILE" { try testing.expectEqualStrings("my_history", env.get("HISTFILE").?); try testing.expect(env.get("GHOSTTY_BASH_UNEXPORT_HISTFILE") == null); } - - // HISTFILE unset (POSIX mode) - { - var env = EnvMap.init(alloc); - defer env.deinit(); - - const command = try setupBash(alloc, "bash --posix", ".", &env); - defer if (command) |c| alloc.free(c); - - try testing.expect(env.get("HISTFILE") == null); - try testing.expect(env.get("GHOSTTY_BASH_UNEXPORT_HISTFILE") == null); - } - - // HISTFILE set (POSIX mode) - { - var env = EnvMap.init(alloc); - defer env.deinit(); - - try env.put("HISTFILE", "my_history"); - - const command = try setupBash(alloc, "bash --posix", ".", &env); - defer if (command) |c| alloc.free(c); - - try testing.expectEqualStrings("my_history", env.get("HISTFILE").?); - try testing.expect(env.get("GHOSTTY_BASH_UNEXPORT_HISTFILE") == null); - } -} - -test "bash: preserve ENV" { - const testing = std.testing; - const alloc = testing.allocator; - - var env = EnvMap.init(alloc); - defer env.deinit(); - - const original_env = "original-env.bash"; - - // POSIX mode - { - try env.put("ENV", original_env); - const command = try setupBash(alloc, "bash --posix", ".", &env); - defer if (command) |c| alloc.free(c); - - try testing.expect(std.mem.indexOf(u8, command.?, "--posix") != null); - try testing.expect(std.mem.indexOf(u8, env.get("GHOSTTY_BASH_INJECT").?, "posix") != null); - try testing.expectEqualStrings(original_env, env.get("GHOSTTY_BASH_ENV").?); - try testing.expectEqualStrings("./shell-integration/bash/ghostty.bash", env.get("ENV").?); - } - - env.remove("GHOSTTY_BASH_ENV"); - - // Not POSIX mode - { - try env.put("ENV", original_env); - const command = try setupBash(alloc, "bash", ".", &env); - defer if (command) |c| alloc.free(c); - - try testing.expect(std.mem.indexOf(u8, command.?, "--posix") != null); - try testing.expect(std.mem.indexOf(u8, env.get("GHOSTTY_BASH_INJECT").?, "posix") == null); - try testing.expect(env.get("GHOSTTY_BASH_ENV") == null); - try testing.expectEqualStrings("./shell-integration/bash/ghostty.bash", env.get("ENV").?); - } } /// Setup automatic shell integration for shells that include