mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
Merge pull request #1729 from jparise/bash-integration
shell-integration: automatic bash integration
This commit is contained in:
@ -201,10 +201,10 @@ The currently supported shell integration features in Ghostty:
|
|||||||
|
|
||||||
#### Shell Integration Installation and Verification
|
#### Shell Integration Installation and Verification
|
||||||
|
|
||||||
Ghostty will automatically inject the shell integration code for `zsh` and
|
Ghostty will automatically inject the shell integration code for `bash`, `zsh`
|
||||||
`fish`. `bash` does not support automatic injection but you can manually
|
and `fish`. Other shells do not have shell integration code written but will
|
||||||
`source` the `ghostty.bash` file in `src/shell-integration`. Other shells are
|
function fine within Ghostty with the above mentioned shell integration features
|
||||||
not supported. **If you want to disable this feature,** set
|
inoperative. **If you want to disable automatic shell integration,** set
|
||||||
`shell-integration = none` in your configuration file.
|
`shell-integration = none` in your configuration file.
|
||||||
|
|
||||||
**For the automatic shell integration to work,** Ghostty must either be run
|
**For the automatic shell integration to work,** Ghostty must either be run
|
||||||
|
@ -408,7 +408,7 @@ palette: Palette = .{},
|
|||||||
/// Ghostty does not do any shell command parsing.
|
/// Ghostty does not do any shell command parsing.
|
||||||
///
|
///
|
||||||
/// If you're using the `ghostty` CLI there is also a shortcut to run a command
|
/// If you're using the `ghostty` CLI there is also a shortcut to run a command
|
||||||
/// with argumens directly: you can use the `-e` flag. For example: `ghostty -e
|
/// with arguments directly: you can use the `-e` flag. For example: `ghostty -e
|
||||||
/// fish --with --custom --args`.
|
/// fish --with --custom --args`.
|
||||||
command: ?[]const u8 = null,
|
command: ?[]const u8 = null,
|
||||||
|
|
||||||
@ -841,7 +841,7 @@ keybind: Keybinds = .{},
|
|||||||
///
|
///
|
||||||
/// * `detect` - Detect the shell based on the filename.
|
/// * `detect` - Detect the shell based on the filename.
|
||||||
///
|
///
|
||||||
/// * `fish`, `zsh` - Use this specific shell injection scheme.
|
/// * `bash`, `fish`, `zsh` - Use this specific shell injection scheme.
|
||||||
///
|
///
|
||||||
/// The default value is `detect`.
|
/// The default value is `detect`.
|
||||||
@"shell-integration": ShellIntegration = .detect,
|
@"shell-integration": ShellIntegration = .detect,
|
||||||
@ -3402,6 +3402,7 @@ pub const CopyOnSelect = enum {
|
|||||||
pub const ShellIntegration = enum {
|
pub const ShellIntegration = enum {
|
||||||
none,
|
none,
|
||||||
detect,
|
detect,
|
||||||
|
bash,
|
||||||
fish,
|
fish,
|
||||||
zsh,
|
zsh,
|
||||||
};
|
};
|
||||||
|
@ -13,6 +13,66 @@
|
|||||||
if [[ "$-" != *i* ]] ; then builtin return; fi
|
if [[ "$-" != *i* ]] ; then builtin return; fi
|
||||||
if [ -z "$GHOSTTY_RESOURCES_DIR" ]; then builtin return; fi
|
if [ -z "$GHOSTTY_RESOURCES_DIR" ]; then builtin return; fi
|
||||||
|
|
||||||
|
# When automatic shell integration is active, we need to manually
|
||||||
|
# load the normal bash startup files based on the injected state.
|
||||||
|
if [ -n "$GHOSTTY_BASH_INJECT" ]; then
|
||||||
|
builtin declare ghostty_bash_inject="$GHOSTTY_BASH_INJECT"
|
||||||
|
builtin unset GHOSTTY_BASH_INJECT ENV
|
||||||
|
|
||||||
|
# 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"
|
||||||
|
fi
|
||||||
|
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
|
||||||
|
for rcfile in /etc/bash.bashrc /etc/bash/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
|
||||||
|
fi
|
||||||
|
|
||||||
|
builtin unset GHOSTTY_BASH_ENV GHOSTTY_BASH_RCFILE
|
||||||
|
builtin unset ghostty_bash_inject rcfile
|
||||||
|
fi
|
||||||
|
|
||||||
# Import bash-preexec, safe to do multiple times
|
# Import bash-preexec, safe to do multiple times
|
||||||
builtin source "$GHOSTTY_RESOURCES_DIR/shell-integration/bash/bash-preexec.sh"
|
builtin source "$GHOSTTY_RESOURCES_DIR/shell-integration/bash/bash-preexec.sh"
|
||||||
|
|
||||||
|
@ -907,12 +907,6 @@ const Subprocess = struct {
|
|||||||
errdefer arena.deinit();
|
errdefer arena.deinit();
|
||||||
const alloc = arena.allocator();
|
const alloc = arena.allocator();
|
||||||
|
|
||||||
// Determine the shell command we're executing
|
|
||||||
const shell_command = opts.full_config.command orelse switch (builtin.os.tag) {
|
|
||||||
.windows => "cmd.exe",
|
|
||||||
else => "sh",
|
|
||||||
};
|
|
||||||
|
|
||||||
// Set our env vars. For Flatpak builds running in Flatpak we don't
|
// Set our env vars. For Flatpak builds running in Flatpak we don't
|
||||||
// inherit our environment because the login shell on the host side
|
// inherit our environment because the login shell on the host side
|
||||||
// will get it.
|
// will get it.
|
||||||
@ -1019,6 +1013,47 @@ const Subprocess = struct {
|
|||||||
env.remove("GHOSTTY_MAC_APP");
|
env.remove("GHOSTTY_MAC_APP");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Setup our shell integration, if we can.
|
||||||
|
const integrated_shell: ?shell_integration.Shell, const shell_command: []const u8 = shell: {
|
||||||
|
const default_shell_command = opts.full_config.command orelse switch (builtin.os.tag) {
|
||||||
|
.windows => "cmd.exe",
|
||||||
|
else => "sh",
|
||||||
|
};
|
||||||
|
|
||||||
|
const force: ?shell_integration.Shell = switch (opts.full_config.@"shell-integration") {
|
||||||
|
.none => break :shell .{ null, default_shell_command },
|
||||||
|
.detect => null,
|
||||||
|
.bash => .bash,
|
||||||
|
.fish => .fish,
|
||||||
|
.zsh => .zsh,
|
||||||
|
};
|
||||||
|
|
||||||
|
const dir = opts.resources_dir orelse break :shell .{
|
||||||
|
null,
|
||||||
|
default_shell_command,
|
||||||
|
};
|
||||||
|
|
||||||
|
const integration = try shell_integration.setup(
|
||||||
|
alloc,
|
||||||
|
dir,
|
||||||
|
default_shell_command,
|
||||||
|
&env,
|
||||||
|
force,
|
||||||
|
opts.full_config.@"shell-integration-features",
|
||||||
|
) orelse break :shell .{ null, default_shell_command };
|
||||||
|
|
||||||
|
break :shell .{ integration.shell, integration.command };
|
||||||
|
};
|
||||||
|
|
||||||
|
if (integrated_shell) |shell| {
|
||||||
|
log.info(
|
||||||
|
"shell integration automatically injected shell={}",
|
||||||
|
.{shell},
|
||||||
|
);
|
||||||
|
} else if (opts.full_config.@"shell-integration" != .none) {
|
||||||
|
log.warn("shell could not be detected, no automatic shell integration will be injected", .{});
|
||||||
|
}
|
||||||
|
|
||||||
// Build our args list
|
// Build our args list
|
||||||
const args = args: {
|
const args = args: {
|
||||||
const cap = 9; // the most we'll ever use
|
const cap = 9; // the most we'll ever use
|
||||||
@ -1157,35 +1192,6 @@ const Subprocess = struct {
|
|||||||
else
|
else
|
||||||
null;
|
null;
|
||||||
|
|
||||||
// Setup our shell integration, if we can.
|
|
||||||
const shell_integrated: ?shell_integration.Shell = shell: {
|
|
||||||
const force: ?shell_integration.Shell = switch (opts.full_config.@"shell-integration") {
|
|
||||||
.none => break :shell null,
|
|
||||||
.detect => null,
|
|
||||||
.fish => .fish,
|
|
||||||
.zsh => .zsh,
|
|
||||||
};
|
|
||||||
|
|
||||||
const dir = opts.resources_dir orelse break :shell null;
|
|
||||||
|
|
||||||
break :shell try shell_integration.setup(
|
|
||||||
gpa,
|
|
||||||
dir,
|
|
||||||
shell_command,
|
|
||||||
&env,
|
|
||||||
force,
|
|
||||||
opts.full_config.@"shell-integration-features",
|
|
||||||
);
|
|
||||||
};
|
|
||||||
if (shell_integrated) |shell| {
|
|
||||||
log.info(
|
|
||||||
"shell integration automatically injected shell={}",
|
|
||||||
.{shell},
|
|
||||||
);
|
|
||||||
} else if (opts.full_config.@"shell-integration" != .none) {
|
|
||||||
log.warn("shell could not be detected, no automatic shell integration will be injected", .{});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Our screen size should be our padded size
|
// Our screen size should be our padded size
|
||||||
const padded_size = opts.screen_size.subPadding(opts.padding);
|
const padded_size = opts.screen_size.subPadding(opts.padding);
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||||
const EnvMap = std.process.EnvMap;
|
const EnvMap = std.process.EnvMap;
|
||||||
const config = @import("../config.zig");
|
const config = @import("../config.zig");
|
||||||
|
|
||||||
@ -7,28 +8,42 @@ const log = std.log.scoped(.shell_integration);
|
|||||||
|
|
||||||
/// Shell types we support
|
/// Shell types we support
|
||||||
pub const Shell = enum {
|
pub const Shell = enum {
|
||||||
|
bash,
|
||||||
fish,
|
fish,
|
||||||
zsh,
|
zsh,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// The result of setting up a shell integration.
|
||||||
|
pub const ShellIntegration = struct {
|
||||||
|
/// The successfully-integrated shell.
|
||||||
|
shell: Shell,
|
||||||
|
|
||||||
|
/// The command to use to start the shell with the integration.
|
||||||
|
/// In most cases this is identical to the command given but for
|
||||||
|
/// bash in particular it may be different.
|
||||||
|
///
|
||||||
|
/// The memory is allocated in the arena given to setup.
|
||||||
|
command: []const u8,
|
||||||
|
};
|
||||||
|
|
||||||
/// Setup the command execution environment for automatic
|
/// Setup the command execution environment for automatic
|
||||||
/// integrated shell integration. This returns true if shell
|
/// integrated shell integration and return a ShellIntegration
|
||||||
/// integration was successful. False could mean many things:
|
/// struct describing the integration. If integration fails
|
||||||
/// the shell type wasn't detected, etc.
|
/// (shell type couldn't be detected, etc.), this will return null.
|
||||||
///
|
///
|
||||||
/// The allocator is only used for temporary values, so it should
|
/// The allocator is used for temporary values and to allocate values
|
||||||
/// be given a general purpose allocator. No allocated memory remains
|
/// in the ShellIntegration result. It is expected to be an arena to
|
||||||
/// after this function returns except anything allocated by the
|
/// simplify cleanup.
|
||||||
/// EnvMap.
|
|
||||||
pub fn setup(
|
pub fn setup(
|
||||||
alloc: Allocator,
|
alloc_arena: Allocator,
|
||||||
resource_dir: []const u8,
|
resource_dir: []const u8,
|
||||||
command: []const u8,
|
command: []const u8,
|
||||||
env: *EnvMap,
|
env: *EnvMap,
|
||||||
force_shell: ?Shell,
|
force_shell: ?Shell,
|
||||||
features: config.ShellIntegrationFeatures,
|
features: config.ShellIntegrationFeatures,
|
||||||
) !?Shell {
|
) !?ShellIntegration {
|
||||||
const exe = if (force_shell) |shell| switch (shell) {
|
const exe = if (force_shell) |shell| switch (shell) {
|
||||||
|
.bash => "bash",
|
||||||
.fish => "fish",
|
.fish => "fish",
|
||||||
.zsh => "zsh",
|
.zsh => "zsh",
|
||||||
} else exe: {
|
} else exe: {
|
||||||
@ -38,15 +53,34 @@ pub fn setup(
|
|||||||
break :exe std.fs.path.basename(command[0..idx]);
|
break :exe std.fs.path.basename(command[0..idx]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const shell: Shell = shell: {
|
const result: ShellIntegration = shell: {
|
||||||
|
if (std.mem.eql(u8, "bash", exe)) {
|
||||||
|
const new_command = try setupBash(
|
||||||
|
alloc_arena,
|
||||||
|
command,
|
||||||
|
resource_dir,
|
||||||
|
env,
|
||||||
|
) orelse return null;
|
||||||
|
break :shell .{
|
||||||
|
.shell = .bash,
|
||||||
|
.command = new_command,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (std.mem.eql(u8, "fish", exe)) {
|
if (std.mem.eql(u8, "fish", exe)) {
|
||||||
try setupFish(alloc, resource_dir, env);
|
try setupFish(alloc_arena, resource_dir, env);
|
||||||
break :shell .fish;
|
break :shell .{
|
||||||
|
.shell = .fish,
|
||||||
|
.command = command,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (std.mem.eql(u8, "zsh", exe)) {
|
if (std.mem.eql(u8, "zsh", exe)) {
|
||||||
try setupZsh(resource_dir, env);
|
try setupZsh(resource_dir, env);
|
||||||
break :shell .zsh;
|
break :shell .{
|
||||||
|
.shell = .zsh,
|
||||||
|
.command = command,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@ -57,7 +91,292 @@ pub fn setup(
|
|||||||
if (!features.sudo) try env.put("GHOSTTY_SHELL_INTEGRATION_NO_SUDO", "1");
|
if (!features.sudo) try env.put("GHOSTTY_SHELL_INTEGRATION_NO_SUDO", "1");
|
||||||
if (!features.title) try env.put("GHOSTTY_SHELL_INTEGRATION_NO_TITLE", "1");
|
if (!features.title) try env.put("GHOSTTY_SHELL_INTEGRATION_NO_TITLE", "1");
|
||||||
|
|
||||||
return shell;
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
test "force shell" {
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
var arena = ArenaAllocator.init(testing.allocator);
|
||||||
|
defer arena.deinit();
|
||||||
|
const alloc = arena.allocator();
|
||||||
|
|
||||||
|
var env = EnvMap.init(alloc);
|
||||||
|
defer env.deinit();
|
||||||
|
|
||||||
|
inline for (@typeInfo(Shell).Enum.fields) |field| {
|
||||||
|
const shell = @field(Shell, field.name);
|
||||||
|
const result = try setup(alloc, ".", "sh", &env, shell, .{});
|
||||||
|
try testing.expectEqual(shell, result.?.shell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Setup the bash automatic shell integration. This works by
|
||||||
|
/// starting bash in POSIX mode and using the ENV environment
|
||||||
|
/// variable to load our bash integration script. This prevents
|
||||||
|
/// bash from loading its normal startup files, which becomes
|
||||||
|
/// our script's responsibility (along with disabling POSIX
|
||||||
|
/// mode).
|
||||||
|
///
|
||||||
|
/// This returns a new (allocated) shell command string that
|
||||||
|
/// enables the integration or null if integration failed.
|
||||||
|
fn setupBash(
|
||||||
|
alloc: Allocator,
|
||||||
|
command: []const u8,
|
||||||
|
resource_dir: []const u8,
|
||||||
|
env: *EnvMap,
|
||||||
|
) !?[]const u8 {
|
||||||
|
// Accumulates the arguments that will form the final shell command line.
|
||||||
|
// We can build this list on the stack because we're just temporarily
|
||||||
|
// referencing other slices, but we can fall back to heap in extreme cases.
|
||||||
|
var args_alloc = std.heap.stackFallback(1024, alloc);
|
||||||
|
var args = try std.ArrayList([]const u8).initCapacity(args_alloc.get(), 2);
|
||||||
|
defer args.deinit();
|
||||||
|
|
||||||
|
// Iterator that yields each argument in the original command line.
|
||||||
|
// This will allocate once proportionate to the command line length.
|
||||||
|
var iter = try std.process.ArgIteratorGeneral(.{}).init(alloc, command);
|
||||||
|
defer iter.deinit();
|
||||||
|
|
||||||
|
// Start accumulating arguments with the executable and `--posix` mode flag.
|
||||||
|
if (iter.next()) |exe| {
|
||||||
|
try args.append(exe);
|
||||||
|
} else return null;
|
||||||
|
try args.append("--posix");
|
||||||
|
|
||||||
|
// Stores the list of intercepted command line flags that will be passed
|
||||||
|
// to our shell integration script: --posix --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;
|
||||||
|
|
||||||
|
// Some additional cases we don't yet cover:
|
||||||
|
//
|
||||||
|
// - If the `c` shell option is set, interactive mode is disabled, so skip
|
||||||
|
// loading our shell integration.
|
||||||
|
// - 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.
|
||||||
|
while (iter.next()) |arg| {
|
||||||
|
if (std.mem.eql(u8, arg, "--posix")) {
|
||||||
|
try inject.appendSlice(" --posix");
|
||||||
|
posix = true;
|
||||||
|
} 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);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try args.append(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try env.put("GHOSTTY_BASH_INJECT", inject.slice());
|
||||||
|
|
||||||
|
// In POSIX mode, HISTFILE defaults to ~/.sh_history.
|
||||||
|
if (!posix and env.get("HISTFILE") == null) {
|
||||||
|
try env.put("HISTFILE", "~/.bash_history");
|
||||||
|
try env.put("GHOSTTY_BASH_UNEXPORT_HISTFILE", "1");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preserve the existing ENV value in POSIX mode.
|
||||||
|
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(
|
||||||
|
&path_buf,
|
||||||
|
"{s}/shell-integration/bash/ghostty.bash",
|
||||||
|
.{resource_dir},
|
||||||
|
);
|
||||||
|
try env.put("ENV", integ_dir);
|
||||||
|
|
||||||
|
// Join the acculumated arguments to form the final command string.
|
||||||
|
return try std.mem.join(alloc, " ", args.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "bash" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
var env = EnvMap.init(alloc);
|
||||||
|
defer env.deinit();
|
||||||
|
|
||||||
|
const command = try setupBash(alloc, "bash", ".", &env);
|
||||||
|
defer if (command) |c| alloc.free(c);
|
||||||
|
|
||||||
|
try testing.expectEqualStrings("bash --posix", command.?);
|
||||||
|
try testing.expectEqualStrings("./shell-integration/bash/ghostty.bash", env.get("ENV").?);
|
||||||
|
try testing.expectEqualStrings("1", env.get("GHOSTTY_BASH_INJECT").?);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "bash: inject flags" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
// bash --posix
|
||||||
|
{
|
||||||
|
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").?);
|
||||||
|
}
|
||||||
|
|
||||||
|
// bash --norc
|
||||||
|
{
|
||||||
|
var env = EnvMap.init(alloc);
|
||||||
|
defer env.deinit();
|
||||||
|
|
||||||
|
const command = try setupBash(alloc, "bash --norc", ".", &env);
|
||||||
|
defer if (command) |c| alloc.free(c);
|
||||||
|
|
||||||
|
try testing.expectEqualStrings("bash --posix", command.?);
|
||||||
|
try testing.expectEqualStrings("1 --norc", env.get("GHOSTTY_BASH_INJECT").?);
|
||||||
|
}
|
||||||
|
|
||||||
|
// bash --noprofile
|
||||||
|
{
|
||||||
|
var env = EnvMap.init(alloc);
|
||||||
|
defer env.deinit();
|
||||||
|
|
||||||
|
const command = try setupBash(alloc, "bash --noprofile", ".", &env);
|
||||||
|
defer if (command) |c| alloc.free(c);
|
||||||
|
|
||||||
|
try testing.expectEqualStrings("bash --posix", command.?);
|
||||||
|
try testing.expectEqualStrings("1 --noprofile", env.get("GHOSTTY_BASH_INJECT").?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "bash: rcfile" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
var env = EnvMap.init(alloc);
|
||||||
|
defer env.deinit();
|
||||||
|
|
||||||
|
// bash --rcfile
|
||||||
|
{
|
||||||
|
const command = try setupBash(alloc, "bash --rcfile profile.sh", ".", &env);
|
||||||
|
defer if (command) |c| alloc.free(c);
|
||||||
|
|
||||||
|
try testing.expectEqualStrings("bash --posix", command.?);
|
||||||
|
try testing.expectEqualStrings("profile.sh", env.get("GHOSTTY_BASH_RCFILE").?);
|
||||||
|
}
|
||||||
|
|
||||||
|
// bash --init-file
|
||||||
|
{
|
||||||
|
const command = try setupBash(alloc, "bash --init-file profile.sh", ".", &env);
|
||||||
|
defer if (command) |c| alloc.free(c);
|
||||||
|
|
||||||
|
try testing.expectEqualStrings("bash --posix", command.?);
|
||||||
|
try testing.expectEqualStrings("profile.sh", env.get("GHOSTTY_BASH_RCFILE").?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "bash: HISTFILE" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
// HISTFILE unset
|
||||||
|
{
|
||||||
|
var env = EnvMap.init(alloc);
|
||||||
|
defer env.deinit();
|
||||||
|
|
||||||
|
const command = try setupBash(alloc, "bash", ".", &env);
|
||||||
|
defer if (command) |c| alloc.free(c);
|
||||||
|
|
||||||
|
try testing.expectEqualStrings("~/.bash_history", env.get("HISTFILE").?);
|
||||||
|
try testing.expectEqualStrings("1", env.get("GHOSTTY_BASH_UNEXPORT_HISTFILE").?);
|
||||||
|
}
|
||||||
|
|
||||||
|
// HISTFILE set
|
||||||
|
{
|
||||||
|
var env = EnvMap.init(alloc);
|
||||||
|
defer env.deinit();
|
||||||
|
|
||||||
|
try env.put("HISTFILE", "my_history");
|
||||||
|
|
||||||
|
const command = try setupBash(alloc, "bash", ".", &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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 the fish automatic shell integration. This works by
|
/// Setup the fish automatic shell integration. This works by
|
||||||
@ -65,7 +384,7 @@ pub fn setup(
|
|||||||
/// Fish will automatically load configuration in XDG_DATA_DIRS
|
/// Fish will automatically load configuration in XDG_DATA_DIRS
|
||||||
/// "fish/vendor_conf.d/*.fish".
|
/// "fish/vendor_conf.d/*.fish".
|
||||||
fn setupFish(
|
fn setupFish(
|
||||||
alloc_gpa: Allocator,
|
alloc_arena: Allocator,
|
||||||
resource_dir: []const u8,
|
resource_dir: []const u8,
|
||||||
env: *EnvMap,
|
env: *EnvMap,
|
||||||
) !void {
|
) !void {
|
||||||
@ -91,7 +410,7 @@ fn setupFish(
|
|||||||
// 4K is a reasonable size for this for most cases. However, env
|
// 4K is a reasonable size for this for most cases. However, env
|
||||||
// vars can be significantly larger so if we have to we fall
|
// vars can be significantly larger so if we have to we fall
|
||||||
// back to a heap allocated value.
|
// back to a heap allocated value.
|
||||||
var stack_alloc = std.heap.stackFallback(4096, alloc_gpa);
|
var stack_alloc = std.heap.stackFallback(4096, alloc_arena);
|
||||||
const alloc = stack_alloc.get();
|
const alloc = stack_alloc.get();
|
||||||
const prepended = try std.fmt.allocPrint(
|
const prepended = try std.fmt.allocPrint(
|
||||||
alloc,
|
alloc,
|
||||||
@ -128,16 +447,3 @@ fn setupZsh(
|
|||||||
);
|
);
|
||||||
try env.put("ZDOTDIR", integ_dir);
|
try env.put("ZDOTDIR", integ_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "force shell" {
|
|
||||||
const testing = std.testing;
|
|
||||||
const alloc = testing.allocator;
|
|
||||||
|
|
||||||
var env = EnvMap.init(alloc);
|
|
||||||
defer env.deinit();
|
|
||||||
|
|
||||||
inline for (@typeInfo(Shell).Enum.fields) |field| {
|
|
||||||
const shell = @field(Shell, field.name);
|
|
||||||
try testing.expectEqual(shell, setup(alloc, ".", "sh", &env, shell, .{}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Reference in New Issue
Block a user