mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 08:46:08 +03:00
Merge pull request #749 from mitchellh/command-args
Command args, `-e` flag
This commit is contained in:
@ -64,6 +64,11 @@ pub fn parse(comptime T: type, alloc: Allocator, dst: *T, iter: anytype) !void {
|
||||
};
|
||||
|
||||
while (iter.next()) |arg| {
|
||||
// Do manual parsing if we have a hook for it.
|
||||
if (@hasDecl(T, "parseManuallyHook")) {
|
||||
if (!try dst.parseManuallyHook(arena_alloc, arg, iter)) return;
|
||||
}
|
||||
|
||||
if (mem.startsWith(u8, arg, "--")) {
|
||||
var key: []const u8 = arg[2..];
|
||||
const value: ?[]const u8 = value: {
|
||||
|
@ -268,6 +268,16 @@ palette: Palette = .{},
|
||||
///
|
||||
command: ?[]const u8 = null,
|
||||
|
||||
/// A single argument to pass to the command. This can be repeated to
|
||||
/// pass multiple arguments. This slightly clunky configuration style is
|
||||
/// so that Ghostty doesn't have to perform any sort of shell parsing
|
||||
/// to find argument boundaries.
|
||||
///
|
||||
/// This cannot be used to override argv[0]. argv[0] will always be
|
||||
/// set by Ghostty to be the command (possibly with a hyphen-prefix to
|
||||
/// indicate that it is a login shell, depending on the OS).
|
||||
@"command-arg": RepeatableString = .{},
|
||||
|
||||
/// The directory to change to after starting the command.
|
||||
///
|
||||
/// The default is "inherit" except in special scenarios listed next.
|
||||
@ -1135,6 +1145,37 @@ pub fn finalize(self: *Config) !void {
|
||||
if (self.@"window-height" > 0) self.@"window-height" = @max(4, self.@"window-height");
|
||||
}
|
||||
|
||||
/// Callback for src/cli/args.zig to allow us to handle special cases
|
||||
/// like `--help` or `-e`. Returns "false" if the CLI parsing should halt.
|
||||
pub fn parseManuallyHook(self: *Config, alloc: Allocator, arg: []const u8, iter: anytype) !bool {
|
||||
// If it isn't "-e" then we just continue parsing normally.
|
||||
if (!std.mem.eql(u8, arg, "-e")) return true;
|
||||
|
||||
// The first value is the command to run.
|
||||
if (iter.next()) |command| {
|
||||
self.command = try alloc.dupe(u8, command);
|
||||
} else {
|
||||
try self._errors.add(alloc, .{
|
||||
.message = try std.fmt.allocPrintZ(
|
||||
alloc,
|
||||
"missing command after -e",
|
||||
.{},
|
||||
),
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// All further arguments are parameters
|
||||
self.@"command-arg".list.clearRetainingCapacity();
|
||||
while (iter.next()) |param| {
|
||||
try self.@"command-arg".parseCLI(alloc, param);
|
||||
}
|
||||
|
||||
// Do not continue, we consumed everything.
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Create a shallow copy of this config. This will share all the memory
|
||||
/// allocated with the previous config but will have a new arena for
|
||||
/// any changes or new allocations. The config should have `deinit`
|
||||
@ -1268,6 +1309,69 @@ pub const ChangeIterator = struct {
|
||||
}
|
||||
};
|
||||
|
||||
const TestIterator = struct {
|
||||
data: []const []const u8,
|
||||
i: usize = 0,
|
||||
|
||||
pub fn next(self: *TestIterator) ?[]const u8 {
|
||||
if (self.i >= self.data.len) return null;
|
||||
const result = self.data[self.i];
|
||||
self.i += 1;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
test "parse hook: invalid command" {
|
||||
const testing = std.testing;
|
||||
var cfg = try Config.default(testing.allocator);
|
||||
defer cfg.deinit();
|
||||
const alloc = cfg._arena.?.allocator();
|
||||
|
||||
var it: TestIterator = .{ .data = &.{"foo"} };
|
||||
try testing.expect(try cfg.parseManuallyHook(alloc, "--command", &it));
|
||||
try testing.expect(cfg.command == null);
|
||||
}
|
||||
|
||||
test "parse e: command only" {
|
||||
const testing = std.testing;
|
||||
var cfg = try Config.default(testing.allocator);
|
||||
defer cfg.deinit();
|
||||
const alloc = cfg._arena.?.allocator();
|
||||
|
||||
var it: TestIterator = .{ .data = &.{"foo"} };
|
||||
try testing.expect(!try cfg.parseManuallyHook(alloc, "-e", &it));
|
||||
try testing.expectEqualStrings("foo", cfg.command.?);
|
||||
}
|
||||
|
||||
test "parse e: command and args" {
|
||||
const testing = std.testing;
|
||||
var cfg = try Config.default(testing.allocator);
|
||||
defer cfg.deinit();
|
||||
const alloc = cfg._arena.?.allocator();
|
||||
|
||||
var it: TestIterator = .{ .data = &.{ "echo", "foo", "bar baz" } };
|
||||
try testing.expect(!try cfg.parseManuallyHook(alloc, "-e", &it));
|
||||
try testing.expectEqualStrings("echo", cfg.command.?);
|
||||
try testing.expectEqual(@as(usize, 2), cfg.@"command-arg".list.items.len);
|
||||
try testing.expectEqualStrings("foo", cfg.@"command-arg".list.items[0]);
|
||||
try testing.expectEqualStrings("bar baz", cfg.@"command-arg".list.items[1]);
|
||||
}
|
||||
|
||||
test "parse e: command replaces args" {
|
||||
const testing = std.testing;
|
||||
var cfg = try Config.default(testing.allocator);
|
||||
defer cfg.deinit();
|
||||
const alloc = cfg._arena.?.allocator();
|
||||
|
||||
try cfg.@"command-arg".parseCLI(alloc, "foo");
|
||||
try testing.expectEqual(@as(usize, 1), cfg.@"command-arg".list.items.len);
|
||||
|
||||
var it: TestIterator = .{ .data = &.{"echo"} };
|
||||
try testing.expect(!try cfg.parseManuallyHook(alloc, "-e", &it));
|
||||
try testing.expectEqualStrings("echo", cfg.command.?);
|
||||
try testing.expectEqual(@as(usize, 0), cfg.@"command-arg".list.items.len);
|
||||
}
|
||||
|
||||
test "clone default" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
@ -744,24 +744,25 @@ const Subprocess = struct {
|
||||
env.remove("GHOSTTY_MAC_APP");
|
||||
}
|
||||
|
||||
// If we're NOT in a flatpak (usually!), then we just exec the
|
||||
// process directly. If we are in a flatpak, we use flatpak-spawn
|
||||
// to escape the sandbox.
|
||||
const args = if (!internal_os.isFlatpak()) try alloc.dupe(
|
||||
[]const u8,
|
||||
&[_][]const u8{argv0_override orelse path},
|
||||
) else args: {
|
||||
var args = try std.ArrayList([]const u8).initCapacity(alloc, 8);
|
||||
// Build our args list
|
||||
const args = args: {
|
||||
const cap = 1 + opts.full_config.@"command-arg".list.items.len;
|
||||
var args = try std.ArrayList([]const u8).initCapacity(alloc, cap);
|
||||
defer args.deinit();
|
||||
|
||||
// We run our shell wrapped in a /bin/sh login shell because
|
||||
// some systems do not properly initialize the env vars unless
|
||||
// we start this way (NixOS!)
|
||||
try args.append("/bin/sh");
|
||||
try args.append("-l");
|
||||
try args.append("-c");
|
||||
try args.append(path);
|
||||
if (!internal_os.isFlatpak()) {
|
||||
try args.append(argv0_override orelse path);
|
||||
} else {
|
||||
// We run our shell wrapped in a /bin/sh login shell because
|
||||
// some systems do not properly initialize the env vars unless
|
||||
// we start this way (NixOS!)
|
||||
try args.append("/bin/sh");
|
||||
try args.append("-l");
|
||||
try args.append("-c");
|
||||
try args.append(path);
|
||||
}
|
||||
|
||||
try args.appendSlice(opts.full_config.@"command-arg".list.items);
|
||||
break :args try args.toOwnedSlice();
|
||||
};
|
||||
|
||||
|
Reference in New Issue
Block a user