mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-08-26 09:01:44 +00:00
Fix +new-window -e <command> inheriting forced shell integration from the running GTK application.
Arguments after -e are now marked as an explicit command that requires shell detection. That marker is propagated with the existing command override to the new surface for both the current +new-window and +new-tab GTK paths. When the override is applied, forced shell integration becomes detect; an explicit none remains disabled. This is scoped to the GTK -e path. It does not change the generic forced shell-integration behavior, so configured integration continues to support shell executables with non-standard names. Fixes #12378.
This commit is contained in:
@@ -12,6 +12,7 @@ const gtk = @import("gtk");
|
||||
|
||||
const build_config = @import("../../../build_config.zig");
|
||||
const build_info = @import("../build/info.zig");
|
||||
const cli = @import("../../../cli.zig");
|
||||
const global = @import("../../../global.zig");
|
||||
const i18n = @import("../../../os/main.zig").i18n;
|
||||
const apprt = @import("../../../apprt.zig");
|
||||
@@ -1854,6 +1855,7 @@ pub const Application = extern struct {
|
||||
null,
|
||||
if (overrides) |o| .{
|
||||
.command = o.command,
|
||||
.shell_integration = o.shell_integration,
|
||||
.working_directory = o.working_directory,
|
||||
.title = o.title,
|
||||
} else .none,
|
||||
@@ -1934,6 +1936,7 @@ pub const Application = extern struct {
|
||||
},
|
||||
.{
|
||||
.command = overrides.command,
|
||||
.shell_integration = overrides.shell_integration,
|
||||
.working_directory = overrides.working_directory,
|
||||
.title = overrides.title,
|
||||
},
|
||||
@@ -1946,6 +1949,7 @@ pub const Application = extern struct {
|
||||
null,
|
||||
.{
|
||||
.command = overrides.command,
|
||||
.shell_integration = overrides.shell_integration,
|
||||
.working_directory = overrides.working_directory,
|
||||
.title = overrides.title,
|
||||
},
|
||||
@@ -1957,6 +1961,7 @@ pub const Application = extern struct {
|
||||
|
||||
fn parseOverrides(arena_alloc: Allocator, arguments_it: *glib.VariantIter) (Allocator.Error || error{ValueRequired})!struct {
|
||||
command: ?configpkg.Command = null,
|
||||
shell_integration: ?configpkg.Config.ShellIntegration = null,
|
||||
working_directory: ?[:0]const u8 = null,
|
||||
title: ?[:0]const u8 = null,
|
||||
} {
|
||||
@@ -1965,6 +1970,9 @@ pub const Application = extern struct {
|
||||
var working_directory: ?[:0]const u8 = null;
|
||||
var title: ?[:0]const u8 = null;
|
||||
var command: ?configpkg.Command = null;
|
||||
var parsed_shell_integration: struct {
|
||||
@"shell-integration": ?configpkg.Config.ShellIntegration = null,
|
||||
} = .{};
|
||||
|
||||
const s_variant_type = glib.VariantType.new("s");
|
||||
defer s_variant_type.free();
|
||||
@@ -2010,6 +2018,19 @@ pub const Application = extern struct {
|
||||
command = cmd;
|
||||
continue;
|
||||
}
|
||||
if (std.mem.cutPrefix(u8, str, "--shell-integration=")) |v| {
|
||||
cli.args.parseIntoField(
|
||||
@TypeOf(parsed_shell_integration),
|
||||
arena_alloc,
|
||||
&parsed_shell_integration,
|
||||
"shell-integration",
|
||||
std.mem.trim(u8, v, &std.ascii.whitespace),
|
||||
) catch |err| {
|
||||
log.warn("unable to parse shell integration {s}: {t}", .{ v, err });
|
||||
continue;
|
||||
};
|
||||
continue;
|
||||
}
|
||||
if (std.mem.cutPrefix(u8, str, "--working-directory=")) |v| {
|
||||
working_directory = arena_alloc.dupeZ(u8, std.mem.trim(u8, v, &std.ascii.whitespace)) catch |err| {
|
||||
log.warn("unable to duplicate working directory: {t}", .{err});
|
||||
@@ -2034,6 +2055,7 @@ pub const Application = extern struct {
|
||||
|
||||
return .{
|
||||
.command = command,
|
||||
.shell_integration = parsed_shell_integration.@"shell-integration",
|
||||
.working_directory = working_directory,
|
||||
.title = title,
|
||||
};
|
||||
@@ -2635,6 +2657,7 @@ const Action = struct {
|
||||
target: apprt.Target,
|
||||
overrides: struct {
|
||||
command: ?configpkg.Command = null,
|
||||
shell_integration: ?configpkg.Config.ShellIntegration = null,
|
||||
working_directory: ?[:0]const u8 = null,
|
||||
title: ?[:0]const u8 = null,
|
||||
|
||||
@@ -2661,6 +2684,7 @@ const Action = struct {
|
||||
};
|
||||
window.newTab(core, .{
|
||||
.command = overrides.command,
|
||||
.shell_integration = overrides.shell_integration,
|
||||
.working_directory = overrides.working_directory,
|
||||
.title = overrides.title,
|
||||
});
|
||||
@@ -2674,6 +2698,7 @@ const Action = struct {
|
||||
parent: ?*CoreSurface,
|
||||
overrides: struct {
|
||||
command: ?configpkg.Command = null,
|
||||
shell_integration: ?configpkg.Config.ShellIntegration = null,
|
||||
working_directory: ?[:0]const u8 = null,
|
||||
title: ?[:0]const u8 = null,
|
||||
|
||||
@@ -2696,6 +2721,7 @@ const Action = struct {
|
||||
parent,
|
||||
.{
|
||||
.command = overrides.command,
|
||||
.shell_integration = overrides.shell_integration,
|
||||
.working_directory = overrides.working_directory,
|
||||
.title = overrides.title,
|
||||
},
|
||||
@@ -2708,6 +2734,7 @@ const Action = struct {
|
||||
parent: ?*CoreSurface,
|
||||
overrides: struct {
|
||||
command: ?configpkg.Command = null,
|
||||
shell_integration: ?configpkg.Config.ShellIntegration = null,
|
||||
working_directory: ?[:0]const u8 = null,
|
||||
title: ?[:0]const u8 = null,
|
||||
|
||||
@@ -2728,6 +2755,7 @@ const Action = struct {
|
||||
// Create a new tab with window context (first tab in new window)
|
||||
win.newTabForWindow(parent, .{
|
||||
.command = overrides.command,
|
||||
.shell_integration = overrides.shell_integration,
|
||||
.working_directory = overrides.working_directory,
|
||||
.title = overrides.title,
|
||||
});
|
||||
|
||||
@@ -214,6 +214,7 @@ pub const SplitTree = extern struct {
|
||||
parent_: ?*Surface,
|
||||
overrides: struct {
|
||||
command: ?configpkg.Command = null,
|
||||
shell_integration: ?configpkg.Config.ShellIntegration = null,
|
||||
working_directory: ?[:0]const u8 = null,
|
||||
title: ?[:0]const u8 = null,
|
||||
|
||||
@@ -225,6 +226,7 @@ pub const SplitTree = extern struct {
|
||||
// Create our new surface.
|
||||
const surface: *Surface = .new(.{
|
||||
.command = overrides.command,
|
||||
.shell_integration = overrides.shell_integration,
|
||||
.working_directory = overrides.working_directory,
|
||||
.title = overrides.title,
|
||||
});
|
||||
|
||||
@@ -736,6 +736,7 @@ pub const Surface = extern struct {
|
||||
|
||||
overrides: struct {
|
||||
command: ?configpkg.Command = null,
|
||||
shell_integration: ?configpkg.Config.ShellIntegration = null,
|
||||
working_directory: ?[:0]const u8 = null,
|
||||
|
||||
pub const none: @This() = .{};
|
||||
@@ -746,6 +747,7 @@ pub const Surface = extern struct {
|
||||
|
||||
pub fn new(overrides: struct {
|
||||
command: ?configpkg.Command = null,
|
||||
shell_integration: ?configpkg.Config.ShellIntegration = null,
|
||||
working_directory: ?[:0]const u8 = null,
|
||||
title: ?[:0]const u8 = null,
|
||||
|
||||
@@ -758,6 +760,7 @@ pub const Surface = extern struct {
|
||||
const priv: *Private = self.private();
|
||||
priv.overrides = .{
|
||||
.command = if (overrides.command) |c| c.clone(alloc) catch null else null,
|
||||
.shell_integration = overrides.shell_integration,
|
||||
.working_directory = if (overrides.working_directory) |wd| alloc.dupeZ(u8, wd) catch null else null,
|
||||
};
|
||||
return self;
|
||||
@@ -3527,9 +3530,11 @@ pub const Surface = extern struct {
|
||||
);
|
||||
defer config.deinit();
|
||||
|
||||
if (priv.overrides.command) |c| {
|
||||
config.command = try c.clone(config._arena.?.allocator());
|
||||
}
|
||||
try applyCommandOverrides(
|
||||
&config,
|
||||
priv.overrides.command,
|
||||
priv.overrides.shell_integration,
|
||||
);
|
||||
if (priv.overrides.working_directory) |wd| {
|
||||
const config_alloc = config.arenaAlloc();
|
||||
var wd_val: configpkg.WorkingDirectory = .{ .path = try config_alloc.dupe(u8, wd) };
|
||||
@@ -4393,3 +4398,57 @@ test "computeFraction" {
|
||||
try std.testing.expectEqual(0.0, computeFraction(0));
|
||||
try std.testing.expectEqual(0.5, computeFraction(50));
|
||||
}
|
||||
|
||||
/// Apply command and shell integration overrides received from the CLI.
|
||||
/// Explicit commands should only receive shell integration when their
|
||||
/// executable can be detected as a supported shell. An explicit shell
|
||||
/// integration override is also valid without a command.
|
||||
fn applyCommandOverrides(
|
||||
config: *configpkg.Config,
|
||||
command: ?configpkg.Command,
|
||||
shell_integration: ?configpkg.Config.ShellIntegration,
|
||||
) Allocator.Error!void {
|
||||
if (command) |value| {
|
||||
config.command = try value.clone(config.arenaAlloc());
|
||||
|
||||
if (shell_integration) |integration| {
|
||||
config.@"shell-integration" = integration;
|
||||
} else if (config.@"shell-integration" != .none) {
|
||||
config.@"shell-integration" = .detect;
|
||||
}
|
||||
} else if (shell_integration) |value| {
|
||||
config.@"shell-integration" = value;
|
||||
}
|
||||
}
|
||||
|
||||
test "command and shell integration overrides" {
|
||||
const testing = std.testing;
|
||||
|
||||
var config = try configpkg.Config.default(testing.allocator);
|
||||
defer config.deinit();
|
||||
|
||||
config.@"shell-integration" = .nushell;
|
||||
try applyCommandOverrides(&config, .{ .shell = "vim" }, null);
|
||||
try testing.expectEqual(.detect, config.@"shell-integration");
|
||||
|
||||
config.@"shell-integration" = .none;
|
||||
try applyCommandOverrides(&config, .{ .shell = "vim" }, null);
|
||||
try testing.expectEqual(.none, config.@"shell-integration");
|
||||
|
||||
try applyCommandOverrides(&config, .{ .shell = "nu" }, .nushell);
|
||||
try testing.expectEqual(.nushell, config.@"shell-integration");
|
||||
|
||||
try applyCommandOverrides(&config, .{ .shell = "vim" }, .none);
|
||||
try testing.expectEqual(.none, config.@"shell-integration");
|
||||
|
||||
config.@"shell-integration" = .nushell;
|
||||
try applyCommandOverrides(&config, null, null);
|
||||
try testing.expectEqual(.nushell, config.@"shell-integration");
|
||||
|
||||
config.@"shell-integration" = .none;
|
||||
try applyCommandOverrides(&config, null, .nushell);
|
||||
try testing.expectEqual(.nushell, config.@"shell-integration");
|
||||
|
||||
try applyCommandOverrides(&config, null, .none);
|
||||
try testing.expectEqual(.none, config.@"shell-integration");
|
||||
}
|
||||
|
||||
@@ -189,6 +189,7 @@ pub const Tab = extern struct {
|
||||
|
||||
pub fn new(config: ?*Config, overrides: struct {
|
||||
command: ?configpkg.Command = null,
|
||||
shell_integration: ?configpkg.Config.ShellIntegration = null,
|
||||
working_directory: ?[:0]const u8 = null,
|
||||
title: ?[:0]const u8 = null,
|
||||
|
||||
@@ -212,6 +213,7 @@ pub const Tab = extern struct {
|
||||
// Create our initial surface in the split tree.
|
||||
priv.split_tree.newSplit(.right, null, .{
|
||||
.command = overrides.command,
|
||||
.shell_integration = overrides.shell_integration,
|
||||
.working_directory = overrides.working_directory,
|
||||
.title = overrides.title,
|
||||
}) catch |err| switch (err) {
|
||||
|
||||
@@ -392,6 +392,7 @@ pub const Window = extern struct {
|
||||
/// The new tab will be selected.
|
||||
pub fn newTab(self: *Self, parent_: ?*CoreSurface, overrides: struct {
|
||||
command: ?configpkg.Command = null,
|
||||
shell_integration: ?configpkg.Config.ShellIntegration = null,
|
||||
working_directory: ?[:0]const u8 = null,
|
||||
title: ?[:0]const u8 = null,
|
||||
|
||||
@@ -399,6 +400,7 @@ pub const Window = extern struct {
|
||||
}) void {
|
||||
_ = self.newTabPage(parent_, .tab, .{
|
||||
.command = overrides.command,
|
||||
.shell_integration = overrides.shell_integration,
|
||||
.working_directory = overrides.working_directory,
|
||||
.title = overrides.title,
|
||||
});
|
||||
@@ -409,6 +411,7 @@ pub const Window = extern struct {
|
||||
parent_: ?*CoreSurface,
|
||||
overrides: struct {
|
||||
command: ?configpkg.Command = null,
|
||||
shell_integration: ?configpkg.Config.ShellIntegration = null,
|
||||
working_directory: ?[:0]const u8 = null,
|
||||
title: ?[:0]const u8 = null,
|
||||
|
||||
@@ -420,6 +423,7 @@ pub const Window = extern struct {
|
||||
.window,
|
||||
.{
|
||||
.command = overrides.command,
|
||||
.shell_integration = overrides.shell_integration,
|
||||
.working_directory = overrides.working_directory,
|
||||
.title = overrides.title,
|
||||
},
|
||||
@@ -432,6 +436,7 @@ pub const Window = extern struct {
|
||||
context: apprt.surface.NewSurfaceContext,
|
||||
overrides: struct {
|
||||
command: ?configpkg.Command = null,
|
||||
shell_integration: ?configpkg.Config.ShellIntegration = null,
|
||||
working_directory: ?[:0]const u8 = null,
|
||||
title: ?[:0]const u8 = null,
|
||||
|
||||
@@ -446,6 +451,7 @@ pub const Window = extern struct {
|
||||
priv.config,
|
||||
.{
|
||||
.command = overrides.command,
|
||||
.shell_integration = overrides.shell_integration,
|
||||
.working_directory = overrides.working_directory,
|
||||
.title = overrides.title,
|
||||
},
|
||||
|
||||
@@ -120,10 +120,11 @@ pub const Options = struct {
|
||||
/// and `--surface-id` flag) will be sent to the remote Ghostty instance and
|
||||
/// will be parsed as command line flags. These flags will override certain
|
||||
/// settings when creating the first surface in the new tab. Currently, only
|
||||
/// `--working-directory`, `--command`, and `--title` are supported. `-e` will
|
||||
/// also work as an alias for `--command`, except that if `-e` is found on the
|
||||
/// command line all following arguments will become part of the command and no
|
||||
/// more arguments will be parsed for configuration settings.
|
||||
/// `--working-directory`, `--command`, `--shell-integration=<mode>`, and
|
||||
/// `--title` are supported. `-e` will also work as an alias for `--command`, except that if
|
||||
/// `-e` is found on the command line all following arguments will become part
|
||||
/// of the command and no more arguments will be parsed for configuration
|
||||
/// settings.
|
||||
///
|
||||
/// If `--working-directory` is found on the command line and is a relative
|
||||
/// path (i.e. doesn't start with `/`) it will be resolved to an absolute
|
||||
@@ -164,6 +165,10 @@ pub const Options = struct {
|
||||
///
|
||||
/// * `--command`: The command to be executed in the first surface of the new tab.
|
||||
///
|
||||
/// * `--shell-integration=<mode>`: Whether to enable shell integration and
|
||||
/// which shell to use. See the main configuration documentation for
|
||||
/// supported values.
|
||||
///
|
||||
/// * `--working-directory=<directory>`: The working directory to pass to Ghostty.
|
||||
///
|
||||
/// * `--title`: A title that will override the title of the first surface in
|
||||
|
||||
@@ -119,10 +119,11 @@ pub const Options = struct {
|
||||
/// `--class` flag) will be sent to the remote Ghostty instance and will be
|
||||
/// parsed as command line flags. These flags will override certain settings
|
||||
/// when creating the first surface in the new window. Currently, only
|
||||
/// `--working-directory`, `--command`, and `--title` are supported. `-e` will
|
||||
/// also work as an alias for `--command`, except that if `-e` is found on the
|
||||
/// command line all following arguments will become part of the command and no
|
||||
/// more arguments will be parsed for configuration settings.
|
||||
/// `--working-directory`, `--command`, `--shell-integration=<mode>`, and
|
||||
/// `--title` are supported. `-e` will also work as an alias for `--command`, except that if
|
||||
/// `-e` is found on the command line all following arguments will become part
|
||||
/// of the command and no more arguments will be parsed for configuration
|
||||
/// settings.
|
||||
///
|
||||
/// If `--working-directory` is found on the command line and is a relative
|
||||
/// path (i.e. doesn't start with `/`) it will be resolved to an absolute path
|
||||
@@ -158,6 +159,10 @@ pub const Options = struct {
|
||||
///
|
||||
/// * `--command`: The command to be executed in the first surface of the new window.
|
||||
///
|
||||
/// * `--shell-integration=<mode>`: Whether to enable shell integration and
|
||||
/// which shell to use. See the main configuration documentation for
|
||||
/// supported values.
|
||||
///
|
||||
/// * `--working-directory=<directory>`: The working directory to pass to Ghostty.
|
||||
///
|
||||
/// * `--title`: A title that will override the title of the first surface in
|
||||
|
||||
Reference in New Issue
Block a user