termio: shell integration uses arena

This commit is contained in:
Mitchell Hashimoto
2024-05-07 19:57:26 -07:00
parent 861edc722f
commit d64fa6d9db
2 changed files with 57 additions and 49 deletions

View File

@@ -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
var 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.
@@ -1020,36 +1014,42 @@ const Subprocess = struct {
} }
// Setup our shell integration, if we can. // Setup our shell integration, if we can.
const integrated_shell: ?shell_integration.ShellIntegration = shell: { 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") { const force: ?shell_integration.Shell = switch (opts.full_config.@"shell-integration") {
.none => break :shell null, .none => break :shell .{ null, default_shell_command },
.detect => null, .detect => null,
.bash => .bash, .bash => .bash,
.fish => .fish, .fish => .fish,
.zsh => .zsh, .zsh => .zsh,
}; };
const dir = opts.resources_dir orelse break :shell null; const dir = opts.resources_dir orelse break :shell .{
null,
default_shell_command,
};
break :shell try shell_integration.setup( const integration = try shell_integration.setup(
gpa, alloc,
dir, dir,
shell_command, default_shell_command,
&env, &env,
force, force,
opts.full_config.@"shell-integration-features", opts.full_config.@"shell-integration-features",
); ) orelse break :shell .{ null, default_shell_command };
break :shell .{ integration.shell, integration.command };
}; };
defer if (integrated_shell) |shell| shell.deinit(gpa);
if (integrated_shell) |shell| { if (integrated_shell) |shell| {
log.info( log.info(
"shell integration automatically injected shell={}", "shell integration automatically injected shell={}",
.{shell.shell}, .{shell},
); );
if (shell.command) |command| {
shell_command = command;
}
} else if (opts.full_config.@"shell-integration" != .none) { } else if (opts.full_config.@"shell-integration" != .none) {
log.warn("shell could not be detected, no automatic shell integration will be injected", .{}); log.warn("shell could not be detected, no automatic shell integration will be injected", .{});
} }

View File

@@ -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");
@@ -12,18 +13,17 @@ pub const Shell = enum {
zsh, zsh,
}; };
/// The result of setting up a shell integration.
pub const ShellIntegration = struct { pub const ShellIntegration = struct {
/// The successfully-integrated shell. /// The successfully-integrated shell.
shell: Shell, shell: Shell,
/// A revised, integration-aware shell command. /// The command to use to start the shell with the integration.
command: ?[]const u8 = null, /// In most cases this is identical to the command given but for
/// bash in particular it may be different.
pub fn deinit(self: ShellIntegration, alloc: Allocator) void { ///
if (self.command) |command| { /// The memory is allocated in the arena given to setup.
alloc.free(command); command: []const u8,
}
}
}; };
/// Setup the command execution environment for automatic /// Setup the command execution environment for automatic
@@ -32,9 +32,10 @@ pub const ShellIntegration = struct {
/// (shell type couldn't be detected, etc.), this will return null. /// (shell type couldn't be detected, etc.), this will return null.
/// ///
/// The allocator is used for temporary values and to allocate values /// The allocator is used for temporary values and to allocate values
/// in the ShellIntegration result. /// in the ShellIntegration result. It is expected to be an arena to
/// simplify cleanup.
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,
@@ -52,22 +53,34 @@ pub fn setup(
break :exe std.fs.path.basename(command[0..idx]); break :exe std.fs.path.basename(command[0..idx]);
}; };
var new_command: ?[]const u8 = null; const result: ShellIntegration = shell: {
const shell: Shell = shell: {
if (std.mem.eql(u8, "bash", exe)) { if (std.mem.eql(u8, "bash", exe)) {
new_command = try setupBash(alloc, command, resource_dir, env); const new_command = try setupBash(
if (new_command == null) return null; alloc_arena,
break :shell .bash; 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;
@@ -78,15 +91,15 @@ 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 .{ return result;
.shell = shell,
.command = new_command,
};
} }
test "force shell" { test "force shell" {
const testing = std.testing; const testing = std.testing;
const alloc = testing.allocator;
var arena = ArenaAllocator.init(testing.allocator);
defer arena.deinit();
const alloc = arena.allocator();
var env = EnvMap.init(alloc); var env = EnvMap.init(alloc);
defer env.deinit(); defer env.deinit();
@@ -94,12 +107,7 @@ test "force shell" {
inline for (@typeInfo(Shell).Enum.fields) |field| { inline for (@typeInfo(Shell).Enum.fields) |field| {
const shell = @field(Shell, field.name); const shell = @field(Shell, field.name);
const result = try setup(alloc, ".", "sh", &env, shell, .{}); const result = try setup(alloc, ".", "sh", &env, shell, .{});
try testing.expectEqual(shell, result.?.shell);
try testing.expect(result != null);
if (result) |r| {
try testing.expectEqual(shell, r.shell);
r.deinit(alloc);
}
} }
} }
@@ -376,7 +384,7 @@ test "bash: preserve ENV" {
/// 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 {
@@ -402,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,