mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-14 03:25:50 +00:00
gtk: use simpler method for passing overrides around
As discussed in Discord, this commit drops the `ConfigOverride` object in favor of a simpler method of passing the overrides around. Completely avoiding changes to the core wasn't possible but it's very minimal now.
This commit is contained in:
@@ -30,7 +30,6 @@ const font = @import("font/main.zig");
|
||||
const Command = @import("Command.zig");
|
||||
const terminal = @import("terminal/main.zig");
|
||||
const configpkg = @import("config.zig");
|
||||
const ConfigOverrides = configpkg.ConfigOverrides;
|
||||
const Duration = configpkg.Config.Duration;
|
||||
const input = @import("input.zig");
|
||||
const App = @import("App.zig");
|
||||
@@ -464,6 +463,12 @@ pub fn init(
|
||||
app: *App,
|
||||
rt_app: *apprt.runtime.App,
|
||||
rt_surface: *apprt.runtime.Surface,
|
||||
overrides: struct {
|
||||
command: ?configpkg.Command = null,
|
||||
working_directory: ?[:0]const u8 = null,
|
||||
|
||||
pub const none: @This() = .{};
|
||||
},
|
||||
) !void {
|
||||
// Apply our conditional state. If we fail to apply the conditional state
|
||||
// then we log and attempt to move forward with the old config.
|
||||
@@ -609,9 +614,8 @@ pub fn init(
|
||||
|
||||
// The command we're going to execute
|
||||
const command: ?configpkg.Command = command: {
|
||||
if (self.getConfigOverrides()) |config_overrides| {
|
||||
if (config_overrides.isSet(.command))
|
||||
break :command config_overrides.get(.command);
|
||||
if (overrides.command) |command| {
|
||||
break :command command;
|
||||
}
|
||||
if (app.first) {
|
||||
if (config.@"initial-command") |command| {
|
||||
@@ -623,9 +627,8 @@ pub fn init(
|
||||
|
||||
// The working directory to execute the command in.
|
||||
const working_directory: ?[]const u8 = wd: {
|
||||
if (self.getConfigOverrides()) |config_overrides| {
|
||||
if (config_overrides.isSet(.@"working-directory"))
|
||||
break :wd config_overrides.get(.@"working-directory");
|
||||
if (overrides.working_directory) |working_directory| {
|
||||
break :wd working_directory;
|
||||
}
|
||||
break :wd config.@"working-directory";
|
||||
};
|
||||
@@ -1807,13 +1810,6 @@ pub fn updateConfig(
|
||||
);
|
||||
}
|
||||
|
||||
fn getConfigOverrides(self: *Surface) ?*const ConfigOverrides {
|
||||
if (@hasDecl(apprt.runtime.Surface, "getConfigOverrides")) {
|
||||
return self.rt_surface.getConfigOverrides();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const InitialSizeError = error{
|
||||
ContentScaleUnavailable,
|
||||
AppActionFailed,
|
||||
|
||||
@@ -572,6 +572,7 @@ pub const Surface = struct {
|
||||
app.core_app,
|
||||
app,
|
||||
self,
|
||||
.none,
|
||||
);
|
||||
errdefer self.core_surface.deinit();
|
||||
|
||||
|
||||
@@ -102,7 +102,3 @@ pub fn defaultTermioEnv(self: *Self) !std.process.EnvMap {
|
||||
pub fn redrawInspector(self: *Self) void {
|
||||
self.surface.redrawInspector();
|
||||
}
|
||||
|
||||
pub fn getConfigOverrides(self: *Self) ?*const configpkg.ConfigOverrides {
|
||||
return self.gobj().getConfigOverrides();
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ const xev = @import("../../../global.zig").xev;
|
||||
const Binding = @import("../../../input.zig").Binding;
|
||||
const CoreConfig = configpkg.Config;
|
||||
const CoreSurface = @import("../../../Surface.zig");
|
||||
const lib = @import("../../../lib/main.zig");
|
||||
|
||||
const ext = @import("../ext.zig");
|
||||
const key = @import("../key.zig");
|
||||
@@ -32,7 +33,6 @@ const ApprtApp = @import("../App.zig");
|
||||
const Common = @import("../class.zig").Common;
|
||||
const WeakRef = @import("../weak_ref.zig").WeakRef;
|
||||
const Config = @import("config.zig").Config;
|
||||
const ConfigOverrides = @import("config_overrides.zig").ConfigOverrides;
|
||||
const Surface = @import("surface.zig").Surface;
|
||||
const SplitTree = @import("split_tree.zig").SplitTree;
|
||||
const Window = @import("window.zig").Window;
|
||||
@@ -710,7 +710,7 @@ pub const Application = extern struct {
|
||||
.app => null,
|
||||
.surface => |v| v,
|
||||
},
|
||||
null,
|
||||
.none,
|
||||
),
|
||||
|
||||
.open_config => return Action.openConfig(self),
|
||||
@@ -1671,18 +1671,26 @@ pub const Application = extern struct {
|
||||
) callconv(.c) void {
|
||||
log.debug("received new window action", .{});
|
||||
|
||||
const config_overrides: ?*ConfigOverrides = config_overrides: {
|
||||
const alloc = Application.default().allocator();
|
||||
|
||||
var working_directory: ?[:0]const u8 = null;
|
||||
defer if (working_directory) |wd| alloc.free(wd);
|
||||
|
||||
var title: ?[:0]const u8 = null;
|
||||
defer if (title) |t| alloc.free(t);
|
||||
|
||||
var command: ?configpkg.Command = null;
|
||||
defer if (command) |c| c.deinit(alloc);
|
||||
|
||||
var args: std.ArrayList([:0]const u8) = .empty;
|
||||
defer {
|
||||
for (args.items) |arg| alloc.free(arg);
|
||||
args.deinit(alloc);
|
||||
}
|
||||
|
||||
overrides: {
|
||||
// were we given a parameter?
|
||||
const parameter = parameter_ orelse break :config_overrides null;
|
||||
|
||||
const alloc = Application.default().allocator();
|
||||
const config_overrides = ConfigOverrides.new(alloc) catch |err| {
|
||||
log.warn("unable to create new config overrides: {t}", .{err});
|
||||
break :config_overrides null;
|
||||
};
|
||||
errdefer config_overrides.unref();
|
||||
|
||||
const co = config_overrides.get();
|
||||
const parameter = parameter_ orelse break :overrides;
|
||||
|
||||
const as_variant_type = glib.VariantType.new("as");
|
||||
defer as_variant_type.free();
|
||||
@@ -1693,7 +1701,7 @@ pub const Application = extern struct {
|
||||
parameter.getTypeString(),
|
||||
as_variant_type.peekString()[0..as_variant_type.getStringLength()],
|
||||
});
|
||||
break :config_overrides null;
|
||||
break :overrides;
|
||||
}
|
||||
|
||||
const s_variant_type = glib.VariantType.new("s");
|
||||
@@ -1702,12 +1710,6 @@ pub const Application = extern struct {
|
||||
var it: glib.VariantIter = undefined;
|
||||
_ = it.init(parameter);
|
||||
|
||||
var args: std.ArrayList([:0]const u8) = .empty;
|
||||
defer {
|
||||
for (args.items) |arg| alloc.free(arg);
|
||||
args.deinit(alloc);
|
||||
}
|
||||
|
||||
var e_seen: bool = false;
|
||||
var i: usize = 0;
|
||||
|
||||
@@ -1721,15 +1723,17 @@ pub const Application = extern struct {
|
||||
const buf = value.getString(&len);
|
||||
const str = buf[0..len];
|
||||
|
||||
log.debug("new-window argument: {d} {s}", .{ i, str });
|
||||
|
||||
if (e_seen) {
|
||||
const cpy = alloc.dupeZ(u8, str) catch |err| {
|
||||
log.warn("unable to duplicate argument {d} {s}: {t}", .{ i, str, err });
|
||||
break :config_overrides null;
|
||||
break :overrides;
|
||||
};
|
||||
errdefer alloc.free(cpy);
|
||||
args.append(alloc, cpy) catch |err| {
|
||||
log.warn("unable to append argument {d} {s}: {t}", .{ i, str, err });
|
||||
break :config_overrides null;
|
||||
break :overrides;
|
||||
};
|
||||
continue;
|
||||
}
|
||||
@@ -1739,27 +1743,52 @@ pub const Application = extern struct {
|
||||
continue;
|
||||
}
|
||||
|
||||
co.parseCLI(str) catch |err| {
|
||||
log.warn("unable to parse argument {d} {s}: {t}", .{ i, str, err });
|
||||
if (lib.cutPrefix(u8, str, "--command=")) |v| {
|
||||
if (command) |c| c.deinit(alloc);
|
||||
var cmd: configpkg.Command = undefined;
|
||||
cmd.parseCLI(alloc, v) catch |err| {
|
||||
log.warn("unable to parse command: {t}", .{err});
|
||||
continue;
|
||||
};
|
||||
command = cmd;
|
||||
continue;
|
||||
};
|
||||
|
||||
log.debug("new-window argument: {d} {s}", .{ i, str });
|
||||
}
|
||||
if (lib.cutPrefix(u8, str, "--working-directory=")) |v| {
|
||||
if (working_directory) |wd| alloc.free(wd);
|
||||
working_directory = alloc.dupeZ(u8, std.mem.trim(u8, v, &std.ascii.whitespace)) catch |err| wd: {
|
||||
log.warn("unable to duplicate working directory: {t}", .{err});
|
||||
break :wd null;
|
||||
};
|
||||
continue;
|
||||
}
|
||||
if (lib.cutPrefix(u8, str, "--title=")) |v| {
|
||||
if (title) |t| alloc.free(t);
|
||||
title = alloc.dupeZ(u8, std.mem.trim(u8, v, &std.ascii.whitespace)) catch |err| t: {
|
||||
log.warn("unable to duplicate title: {t}", .{err});
|
||||
break :t null;
|
||||
};
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (args.items.len > 0) {
|
||||
co.set(.command, .{ .direct = args.items }) catch |err| {
|
||||
log.warn("unable to set command on config overrides: {t}", .{err});
|
||||
break :config_overrides null;
|
||||
};
|
||||
}
|
||||
if (args.items.len > 0) direct: {
|
||||
if (command) |c| c.deinit(alloc);
|
||||
command = .{
|
||||
.direct = args.toOwnedSlice(alloc) catch |err| {
|
||||
log.warn("unable to convert list of arguments to owned slice: {t}", .{err});
|
||||
break :direct;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
break :config_overrides config_overrides;
|
||||
Action.newWindow(self, null, .{
|
||||
.command = command,
|
||||
.working_directory = working_directory,
|
||||
.title = title,
|
||||
}) catch |err| {
|
||||
log.warn("unable to create new window: {t}", .{err});
|
||||
};
|
||||
|
||||
defer if (config_overrides) |v| v.unref();
|
||||
|
||||
Action.newWindow(self, null, config_overrides) catch {};
|
||||
}
|
||||
|
||||
pub fn actionOpenConfig(
|
||||
@@ -2206,7 +2235,13 @@ const Action = struct {
|
||||
pub fn newWindow(
|
||||
self: *Application,
|
||||
parent: ?*CoreSurface,
|
||||
config_overrides: ?*ConfigOverrides,
|
||||
overrides: struct {
|
||||
command: ?configpkg.Command = null,
|
||||
working_directory: ?[:0]const u8 = null,
|
||||
title: ?[:0]const u8 = null,
|
||||
|
||||
pub const none: @This() = .{};
|
||||
},
|
||||
) !void {
|
||||
// Note that we've requested a window at least once. This is used
|
||||
// to trigger quit on no windows. Note I'm not sure if this is REALLY
|
||||
@@ -2215,15 +2250,32 @@ const Action = struct {
|
||||
// was a delay in the event loop before we created a Window.
|
||||
self.private().requested_window = true;
|
||||
|
||||
const win = Window.new(self, config_overrides);
|
||||
initAndShowWindow(self, win, parent, config_overrides);
|
||||
const win = Window.new(self, .{
|
||||
.title = overrides.title,
|
||||
});
|
||||
initAndShowWindow(
|
||||
self,
|
||||
win,
|
||||
parent,
|
||||
.{
|
||||
.command = overrides.command,
|
||||
.working_directory = overrides.working_directory,
|
||||
.title = overrides.title,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn initAndShowWindow(
|
||||
self: *Application,
|
||||
win: *Window,
|
||||
parent: ?*CoreSurface,
|
||||
config_overrides: ?*ConfigOverrides,
|
||||
overrides: struct {
|
||||
command: ?configpkg.Command = null,
|
||||
working_directory: ?[:0]const u8 = null,
|
||||
title: ?[:0]const u8 = null,
|
||||
|
||||
pub const none: @This() = .{};
|
||||
},
|
||||
) void {
|
||||
// Setup a binding so that whenever our config changes so does the
|
||||
// window. There's never a time when the window config should be out
|
||||
@@ -2237,7 +2289,11 @@ const Action = struct {
|
||||
);
|
||||
|
||||
// Create a new tab with window context (first tab in new window)
|
||||
win.newTabForWindow(parent, config_overrides);
|
||||
win.newTabForWindow(parent, .{
|
||||
.command = overrides.command,
|
||||
.working_directory = overrides.working_directory,
|
||||
.title = overrides.title,
|
||||
});
|
||||
|
||||
// Estimate the initial window size before presenting so the window
|
||||
// manager can position it correctly.
|
||||
@@ -2563,7 +2619,7 @@ const Action = struct {
|
||||
.@"quick-terminal" = true,
|
||||
});
|
||||
assert(win.isQuickTerminal());
|
||||
initAndShowWindow(self, win, null, null);
|
||||
initAndShowWindow(self, win, null, .none);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const gobject = @import("gobject");
|
||||
|
||||
const configpkg = @import("../../../config.zig");
|
||||
const Config = configpkg.Config;
|
||||
|
||||
const Common = @import("../class.zig").Common;
|
||||
|
||||
const log = std.log.scoped(.gtk_ghostty_config_overrides);
|
||||
|
||||
/// Wrapper for a ConfigOverrides object that keeps track of which settings have
|
||||
/// been changed.
|
||||
pub const ConfigOverrides = extern struct {
|
||||
const Self = @This();
|
||||
parent_instance: Parent,
|
||||
pub const Parent = gobject.Object;
|
||||
pub const getGObjectType = gobject.ext.defineClass(Self, .{
|
||||
.name = "GhosttyConfigOverrides",
|
||||
.classInit = &Class.init,
|
||||
.parent_class = &Class.parent,
|
||||
.private = .{ .Type = Private, .offset = &Private.offset },
|
||||
});
|
||||
|
||||
pub const properties = struct {};
|
||||
|
||||
const Private = struct {
|
||||
config_overrides: configpkg.ConfigOverrides,
|
||||
|
||||
pub var offset: c_int = 0;
|
||||
};
|
||||
|
||||
pub fn new(alloc: Allocator) Allocator.Error!*ConfigOverrides {
|
||||
const self = gobject.ext.newInstance(Self, .{});
|
||||
errdefer self.unref();
|
||||
|
||||
const priv: *Private = self.private();
|
||||
try priv.config_overrides.init(alloc);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn get(self: *ConfigOverrides) *configpkg.ConfigOverrides {
|
||||
const priv: *Private = self.private();
|
||||
return &priv.config_overrides;
|
||||
}
|
||||
|
||||
fn finalize(self: *ConfigOverrides) callconv(.c) void {
|
||||
const priv: *Private = self.private();
|
||||
priv.config_overrides.deinit();
|
||||
|
||||
gobject.Object.virtual_methods.finalize.call(
|
||||
Class.parent,
|
||||
self.as(Parent),
|
||||
);
|
||||
}
|
||||
|
||||
const C = Common(Self, Private);
|
||||
pub const as = C.as;
|
||||
pub const ref = C.ref;
|
||||
pub const unref = C.unref;
|
||||
const private = C.private;
|
||||
|
||||
pub const Class = extern struct {
|
||||
parent_class: Parent.Class,
|
||||
var parent: *Parent.Class = undefined;
|
||||
pub const Instance = Self;
|
||||
|
||||
fn init(class: *Class) callconv(.c) void {
|
||||
gobject.Object.virtual_methods.finalize.implement(class, &finalize);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
test "GhosttyConfigOverrides" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
const config_overrides: *ConfigOverrides = try .new(alloc);
|
||||
defer config_overrides.unref();
|
||||
|
||||
const co = config_overrides.get();
|
||||
|
||||
try testing.expect(co.isSet(.@"font-size") == false);
|
||||
try co.set(.@"font-size", 24.0);
|
||||
try testing.expect(co.isSet(.@"font-size") == true);
|
||||
try testing.expectApproxEqAbs(24.0, co.get(.@"font-size"), 0.01);
|
||||
|
||||
try testing.expect(co.isSet(.@"working-directory") == false);
|
||||
try co.parseCLI("--working-directory=/home/ghostty");
|
||||
try testing.expect(co.isSet(.@"working-directory") == true);
|
||||
try testing.expectEqualStrings("/home/ghostty", co.get(.@"working-directory").?);
|
||||
}
|
||||
@@ -7,6 +7,7 @@ const glib = @import("glib");
|
||||
const gobject = @import("gobject");
|
||||
const gtk = @import("gtk");
|
||||
|
||||
const configpkg = @import("../../../config.zig");
|
||||
const apprt = @import("../../../apprt.zig");
|
||||
const ext = @import("../ext.zig");
|
||||
const gresource = @import("../build/gresource.zig");
|
||||
@@ -16,7 +17,6 @@ const Application = @import("application.zig").Application;
|
||||
const CloseConfirmationDialog = @import("close_confirmation_dialog.zig").CloseConfirmationDialog;
|
||||
const Surface = @import("surface.zig").Surface;
|
||||
const SurfaceScrolledWindow = @import("surface_scrolled_window.zig").SurfaceScrolledWindow;
|
||||
const ConfigOverrides = @import("config_overrides.zig").ConfigOverrides;
|
||||
|
||||
const log = std.log.scoped(.gtk_ghostty_split_tree);
|
||||
|
||||
@@ -209,12 +209,22 @@ pub const SplitTree = extern struct {
|
||||
self: *Self,
|
||||
direction: Surface.Tree.Split.Direction,
|
||||
parent_: ?*Surface,
|
||||
config_overrides: ?*ConfigOverrides,
|
||||
overrides: struct {
|
||||
command: ?configpkg.Command = null,
|
||||
working_directory: ?[:0]const u8 = null,
|
||||
title: ?[:0]const u8 = null,
|
||||
|
||||
pub const none: @This() = .{};
|
||||
},
|
||||
) Allocator.Error!void {
|
||||
const alloc = Application.default().allocator();
|
||||
|
||||
// Create our new surface.
|
||||
const surface: *Surface = .new(config_overrides);
|
||||
const surface: *Surface = .new(.{
|
||||
.command = overrides.command,
|
||||
.working_directory = overrides.working_directory,
|
||||
.title = overrides.title,
|
||||
});
|
||||
defer surface.unref();
|
||||
_ = surface.refSink();
|
||||
|
||||
@@ -640,7 +650,7 @@ pub const SplitTree = extern struct {
|
||||
self.newSplit(
|
||||
direction,
|
||||
self.getActiveSurface(),
|
||||
null,
|
||||
.none,
|
||||
) catch |err| {
|
||||
log.warn("new split failed error={}", .{err});
|
||||
};
|
||||
|
||||
@@ -26,7 +26,6 @@ const ApprtSurface = @import("../Surface.zig");
|
||||
const Common = @import("../class.zig").Common;
|
||||
const Application = @import("application.zig").Application;
|
||||
const Config = @import("config.zig").Config;
|
||||
const ConfigOverrides = @import("config_overrides.zig").ConfigOverrides;
|
||||
const ResizeOverlay = @import("resize_overlay.zig").ResizeOverlay;
|
||||
const SearchOverlay = @import("search_overlay.zig").SearchOverlay;
|
||||
const KeyStateOverlay = @import("key_state_overlay.zig").KeyStateOverlay;
|
||||
@@ -91,18 +90,6 @@ pub const Surface = extern struct {
|
||||
);
|
||||
};
|
||||
|
||||
pub const @"config-overrides" = struct {
|
||||
pub const name = "config-overrides";
|
||||
const impl = gobject.ext.defineProperty(
|
||||
name,
|
||||
Self,
|
||||
?*ConfigOverrides,
|
||||
.{
|
||||
.accessor = C.privateObjFieldAccessor("config_overrides"),
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
pub const @"child-exited" = struct {
|
||||
pub const name = "child-exited";
|
||||
const impl = gobject.ext.defineProperty(
|
||||
@@ -565,9 +552,6 @@ pub const Surface = extern struct {
|
||||
/// The configuration that this surface is using.
|
||||
config: ?*Config = null,
|
||||
|
||||
/// Any configuration overrides that might apply to this surface.
|
||||
config_overrides: ?*ConfigOverrides = null,
|
||||
|
||||
/// The default size for a window that embeds this surface.
|
||||
default_size: ?*Size = null,
|
||||
|
||||
@@ -721,13 +705,33 @@ pub const Surface = extern struct {
|
||||
/// stops scrolling.
|
||||
pending_horizontal_scroll_reset: ?c_uint = null,
|
||||
|
||||
overrides: struct {
|
||||
command: ?configpkg.Command = null,
|
||||
working_directory: ?[:0]const u8 = null,
|
||||
|
||||
pub const none: @This() = .{};
|
||||
} = .none,
|
||||
|
||||
pub var offset: c_int = 0;
|
||||
};
|
||||
|
||||
pub fn new(config_overrides: ?*ConfigOverrides) *Self {
|
||||
return gobject.ext.newInstance(Self, .{
|
||||
.@"config-overrides" = config_overrides,
|
||||
pub fn new(overrides: struct {
|
||||
command: ?configpkg.Command = null,
|
||||
working_directory: ?[:0]const u8 = null,
|
||||
title: ?[:0]const u8 = null,
|
||||
|
||||
pub const none: @This() = .{};
|
||||
}) *Self {
|
||||
const self = gobject.ext.newInstance(Self, .{
|
||||
.@"title-override" = overrides.title,
|
||||
});
|
||||
const alloc = Application.default().allocator();
|
||||
const priv: *Private = self.private();
|
||||
priv.overrides = .{
|
||||
.command = if (overrides.command) |c| c.clone(alloc) catch null else null,
|
||||
.working_directory = if (overrides.working_directory) |wd| alloc.dupeZ(u8, wd) catch null else null,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn core(self: *Self) ?*CoreSurface {
|
||||
@@ -1817,11 +1821,6 @@ pub const Surface = extern struct {
|
||||
priv.config = null;
|
||||
}
|
||||
|
||||
if (priv.config_overrides) |v| {
|
||||
v.unref();
|
||||
priv.config_overrides = null;
|
||||
}
|
||||
|
||||
if (priv.vadj_signal_group) |group| {
|
||||
group.setTarget(null);
|
||||
group.as(gobject.Object).unref();
|
||||
@@ -1877,6 +1876,7 @@ pub const Surface = extern struct {
|
||||
}
|
||||
|
||||
fn finalize(self: *Self) callconv(.c) void {
|
||||
const alloc = Application.default().allocator();
|
||||
const priv = self.private();
|
||||
if (priv.core_surface) |v| {
|
||||
// Remove ourselves from the list of known surfaces in the app.
|
||||
@@ -1890,7 +1890,6 @@ pub const Surface = extern struct {
|
||||
|
||||
// Deinit the surface
|
||||
v.deinit();
|
||||
const alloc = Application.default().allocator();
|
||||
alloc.destroy(v);
|
||||
|
||||
priv.core_surface = null;
|
||||
@@ -1923,9 +1922,16 @@ pub const Surface = extern struct {
|
||||
glib.free(@ptrCast(@constCast(v)));
|
||||
priv.title_override = null;
|
||||
}
|
||||
if (priv.overrides.command) |c| {
|
||||
c.deinit(alloc);
|
||||
priv.overrides.command = null;
|
||||
}
|
||||
if (priv.overrides.working_directory) |wd| {
|
||||
alloc.free(wd);
|
||||
priv.overrides.working_directory = null;
|
||||
}
|
||||
|
||||
// Clean up key sequence and key table state
|
||||
const alloc = Application.default().allocator();
|
||||
for (priv.key_sequence.items) |s| alloc.free(s);
|
||||
priv.key_sequence.deinit(alloc);
|
||||
for (priv.key_tables.items) |s| alloc.free(s);
|
||||
@@ -2200,12 +2206,6 @@ pub const Surface = extern struct {
|
||||
self.private().search_overlay.setSearchSelected(selected);
|
||||
}
|
||||
|
||||
pub fn getConfigOverrides(self: *Self) ?*const configpkg.ConfigOverrides {
|
||||
const priv: *Private = self.private();
|
||||
const config_overrides = priv.config_overrides orelse return null;
|
||||
return config_overrides.get();
|
||||
}
|
||||
|
||||
fn propConfig(
|
||||
self: *Self,
|
||||
_: *gobject.ParamSpec,
|
||||
@@ -3387,6 +3387,10 @@ pub const Surface = extern struct {
|
||||
app.core(),
|
||||
app.rt(),
|
||||
&priv.rt_surface,
|
||||
.{
|
||||
.command = priv.overrides.command,
|
||||
.working_directory = priv.overrides.working_directory,
|
||||
},
|
||||
) catch |err| {
|
||||
log.warn("failed to initialize surface err={}", .{err});
|
||||
return error.SurfaceError;
|
||||
@@ -3608,7 +3612,6 @@ pub const Surface = extern struct {
|
||||
gobject.ext.registerProperties(class, &.{
|
||||
properties.@"bell-ringing".impl,
|
||||
properties.config.impl,
|
||||
properties.@"config-overrides".impl,
|
||||
properties.@"child-exited".impl,
|
||||
properties.@"default-size".impl,
|
||||
properties.@"error".impl,
|
||||
|
||||
@@ -5,13 +5,13 @@ const glib = @import("glib");
|
||||
const gobject = @import("gobject");
|
||||
const gtk = @import("gtk");
|
||||
|
||||
const configpkg = @import("../../../config.zig");
|
||||
const apprt = @import("../../../apprt.zig");
|
||||
const CoreSurface = @import("../../../Surface.zig");
|
||||
const ext = @import("../ext.zig");
|
||||
const gresource = @import("../build/gresource.zig");
|
||||
const Common = @import("../class.zig").Common;
|
||||
const Config = @import("config.zig").Config;
|
||||
const ConfigOverrides = @import("config_overrides.zig").ConfigOverrides;
|
||||
const Application = @import("application.zig").Application;
|
||||
const SplitTree = @import("split_tree.zig").SplitTree;
|
||||
const Surface = @import("surface.zig").Surface;
|
||||
@@ -187,7 +187,13 @@ pub const Tab = extern struct {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(config: ?*Config, config_overrides: ?*ConfigOverrides) *Self {
|
||||
pub fn new(config: ?*Config, overrides: struct {
|
||||
command: ?configpkg.Command = null,
|
||||
working_directory: ?[:0]const u8 = null,
|
||||
title: ?[:0]const u8 = null,
|
||||
|
||||
pub const none: @This() = .{};
|
||||
}) *Self {
|
||||
const tab = gobject.ext.newInstance(Tab, .{});
|
||||
|
||||
const priv: *Private = tab.private();
|
||||
@@ -204,7 +210,11 @@ pub const Tab = extern struct {
|
||||
tab.as(gobject.Object).notifyByPspec(properties.config.impl.param_spec);
|
||||
|
||||
// Create our initial surface in the split tree.
|
||||
priv.split_tree.newSplit(.right, null, config_overrides) catch |err| switch (err) {
|
||||
priv.split_tree.newSplit(.right, null, .{
|
||||
.command = overrides.command,
|
||||
.working_directory = overrides.working_directory,
|
||||
.title = overrides.title,
|
||||
}) catch |err| switch (err) {
|
||||
error.OutOfMemory => {
|
||||
// TODO: We should make our "no surfaces" state more aesthetically
|
||||
// pleasing and show something like an "Oops, something went wrong"
|
||||
|
||||
@@ -21,7 +21,6 @@ const gresource = @import("../build/gresource.zig");
|
||||
const winprotopkg = @import("../winproto.zig");
|
||||
const Common = @import("../class.zig").Common;
|
||||
const Config = @import("config.zig").Config;
|
||||
const ConfigOverrides = @import("config_overrides.zig").ConfigOverrides;
|
||||
const Application = @import("application.zig").Application;
|
||||
const CloseConfirmationDialog = @import("close_confirmation_dialog.zig").CloseConfirmationDialog;
|
||||
const SplitTree = @import("split_tree.zig").SplitTree;
|
||||
@@ -80,18 +79,6 @@ pub const Window = extern struct {
|
||||
);
|
||||
};
|
||||
|
||||
pub const @"config-overrides" = struct {
|
||||
pub const name = "config-overrides";
|
||||
const impl = gobject.ext.defineProperty(
|
||||
name,
|
||||
Self,
|
||||
?*ConfigOverrides,
|
||||
.{
|
||||
.accessor = C.privateObjFieldAccessor("config_overrides"),
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
pub const debug = struct {
|
||||
pub const name = "debug";
|
||||
const impl = gobject.ext.defineProperty(
|
||||
@@ -244,9 +231,6 @@ pub const Window = extern struct {
|
||||
/// The configuration that this surface is using.
|
||||
config: ?*Config = null,
|
||||
|
||||
/// Configuration overrides.
|
||||
config_overrides: ?*ConfigOverrides = null,
|
||||
|
||||
/// State and logic for windowing protocol for a window.
|
||||
winproto: winprotopkg.Window,
|
||||
|
||||
@@ -282,27 +266,24 @@ pub const Window = extern struct {
|
||||
pub var offset: c_int = 0;
|
||||
};
|
||||
|
||||
pub fn new(app: *Application, config_overrides_: ?*ConfigOverrides) *Self {
|
||||
pub fn new(
|
||||
app: *Application,
|
||||
overrides: struct {
|
||||
title: ?[:0]const u8 = null,
|
||||
|
||||
pub const none: @This() = .{};
|
||||
},
|
||||
) *Self {
|
||||
const win = gobject.ext.newInstance(Self, .{
|
||||
.application = app,
|
||||
});
|
||||
|
||||
const priv: *Private = win.private();
|
||||
|
||||
if (config_overrides_) |v| {
|
||||
priv.config_overrides = v.ref();
|
||||
const config_overrides = v.get();
|
||||
// If the config overrides have a title set, we set that immediately
|
||||
if (overrides.title) |title| {
|
||||
// If the overrides have a title set, we set that immediately
|
||||
// so that any applications inspecting the window states see an
|
||||
// immediate title set when the window appears, rather than waiting
|
||||
// possibly a few event loop ticks for it to sync from the surface.
|
||||
if (config_overrides.isSet(.title)) {
|
||||
const title_ = config_overrides.get(.title);
|
||||
if (title_) |title| {
|
||||
win.as(gtk.Window).setTitle(title);
|
||||
}
|
||||
}
|
||||
win.as(gobject.Object).notifyByPspec(properties.@"config-overrides".impl.param_spec);
|
||||
win.as(gtk.Window).setTitle(title);
|
||||
}
|
||||
|
||||
return win;
|
||||
@@ -353,16 +334,7 @@ pub const Window = extern struct {
|
||||
// an immediate title set when the window appears, rather than
|
||||
// waiting possibly a few event loop ticks for it to sync from
|
||||
// the surface.
|
||||
const title_ = title: {
|
||||
if (priv.config_overrides) |co| {
|
||||
const config_overrides = co.get();
|
||||
if (config_overrides.isSet(.title)) {
|
||||
break :title config_overrides.get(.title);
|
||||
}
|
||||
}
|
||||
break :title config.title;
|
||||
};
|
||||
if (title_) |title| {
|
||||
if (config.title) |title| {
|
||||
self.as(gtk.Window).setTitle(title);
|
||||
}
|
||||
|
||||
@@ -416,19 +388,55 @@ pub const Window = extern struct {
|
||||
/// at the position dictated by the `window-new-tab-position` config.
|
||||
/// The new tab will be selected.
|
||||
pub fn newTab(self: *Self, parent_: ?*CoreSurface) void {
|
||||
_ = self.newTabPage(parent_, .tab, null);
|
||||
_ = self.newTabPage(parent_, .tab, .none);
|
||||
}
|
||||
|
||||
pub fn newTabForWindow(self: *Self, parent_: ?*CoreSurface, config_overrides: ?*ConfigOverrides) void {
|
||||
_ = self.newTabPage(parent_, .window, config_overrides);
|
||||
pub fn newTabForWindow(
|
||||
self: *Self,
|
||||
parent_: ?*CoreSurface,
|
||||
overrides: struct {
|
||||
command: ?configpkg.Command = null,
|
||||
working_directory: ?[:0]const u8 = null,
|
||||
title: ?[:0]const u8 = null,
|
||||
|
||||
pub const none: @This() = .{};
|
||||
},
|
||||
) void {
|
||||
_ = self.newTabPage(
|
||||
parent_,
|
||||
.window,
|
||||
.{
|
||||
.command = overrides.command,
|
||||
.working_directory = overrides.working_directory,
|
||||
.title = overrides.title,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn newTabPage(self: *Self, parent_: ?*CoreSurface, context: apprt.surface.NewSurfaceContext, config_overrides: ?*ConfigOverrides) *adw.TabPage {
|
||||
fn newTabPage(
|
||||
self: *Self,
|
||||
parent_: ?*CoreSurface,
|
||||
context: apprt.surface.NewSurfaceContext,
|
||||
overrides: struct {
|
||||
command: ?configpkg.Command = null,
|
||||
working_directory: ?[:0]const u8 = null,
|
||||
title: ?[:0]const u8 = null,
|
||||
|
||||
pub const none: @This() = .{};
|
||||
},
|
||||
) *adw.TabPage {
|
||||
const priv: *Private = self.private();
|
||||
const tab_view = priv.tab_view;
|
||||
|
||||
// Create our new tab object
|
||||
const tab = Tab.new(priv.config, config_overrides);
|
||||
const tab = Tab.new(
|
||||
priv.config,
|
||||
.{
|
||||
.command = overrides.command,
|
||||
.working_directory = overrides.working_directory,
|
||||
.title = overrides.title,
|
||||
},
|
||||
);
|
||||
|
||||
if (parent_) |p| {
|
||||
// For a new window's first tab, inherit the parent's initial size hints.
|
||||
@@ -1198,37 +1206,6 @@ pub const Window = extern struct {
|
||||
});
|
||||
}
|
||||
|
||||
fn closureTitle(
|
||||
_: *Self,
|
||||
config_: ?*Config,
|
||||
config_overrides_: ?*ConfigOverrides,
|
||||
title_: ?[*:0]const u8,
|
||||
) callconv(.c) ?[*:0]const u8 {
|
||||
config: {
|
||||
if (config_overrides_) |v| {
|
||||
const config_overrides = v.get();
|
||||
if (config_overrides.isSet(.title)) {
|
||||
if (config_overrides.get(.title)) |title| {
|
||||
return glib.ext.dupeZ(u8, title);
|
||||
}
|
||||
// The `title` has explicitly been set to `null`, skip
|
||||
// checking the normal config for it's title setting.
|
||||
break :config;
|
||||
}
|
||||
}
|
||||
if (config_) |v| {
|
||||
const config = v.get();
|
||||
if (config.title) |title| {
|
||||
return glib.ext.dupeZ(u8, title);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (title_) |title| {
|
||||
return glib.ext.dupeZ(u8, std.mem.span(title));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
fn closureSubtitle(
|
||||
_: *Self,
|
||||
config_: ?*Config,
|
||||
@@ -1257,11 +1234,6 @@ pub const Window = extern struct {
|
||||
priv.config = null;
|
||||
}
|
||||
|
||||
if (priv.config_overrides) |v| {
|
||||
v.unref();
|
||||
priv.config_overrides = null;
|
||||
}
|
||||
|
||||
priv.tab_bindings.setSource(null);
|
||||
|
||||
gtk.Widget.disposeTemplate(
|
||||
@@ -1336,7 +1308,7 @@ pub const Window = extern struct {
|
||||
_: *adw.TabOverview,
|
||||
self: *Self,
|
||||
) callconv(.c) *adw.TabPage {
|
||||
return self.newTabPage(if (self.getActiveSurface()) |v| v.core() else null, .tab, null);
|
||||
return self.newTabPage(if (self.getActiveSurface()) |v| v.core() else null, .tab, .none);
|
||||
}
|
||||
|
||||
fn tabOverviewOpen(
|
||||
@@ -2102,7 +2074,6 @@ pub const Window = extern struct {
|
||||
gobject.ext.registerProperties(class, &.{
|
||||
properties.@"active-surface".impl,
|
||||
properties.config.impl,
|
||||
properties.@"config-overrides".impl,
|
||||
properties.debug.impl,
|
||||
properties.@"headerbar-visible".impl,
|
||||
properties.@"quick-terminal".impl,
|
||||
@@ -2141,7 +2112,6 @@ pub const Window = extern struct {
|
||||
class.bindTemplateCallback("notify_quick_terminal", &propQuickTerminal);
|
||||
class.bindTemplateCallback("notify_scale_factor", &propScaleFactor);
|
||||
class.bindTemplateCallback("titlebar_style_is_tabs", &closureTitlebarStyleIsTab);
|
||||
class.bindTemplateCallback("computed_title", &closureTitle);
|
||||
class.bindTemplateCallback("computed_subtitle", &closureSubtitle);
|
||||
|
||||
// Virtual methods
|
||||
|
||||
@@ -40,7 +40,7 @@ template $GhosttyWindow: Adw.ApplicationWindow {
|
||||
visible: bind template.headerbar-visible;
|
||||
|
||||
title-widget: Adw.WindowTitle {
|
||||
title: bind $computed_title(template.config, template.config-overrides, template.title) as <string>;
|
||||
title: bind template.title;
|
||||
// Blueprint auto-formatter won't let me split this into multiple
|
||||
// lines. Let me explain myself. All parameters to a closure are used
|
||||
// as notifications to recompute the value of the closure. All
|
||||
|
||||
@@ -15,9 +15,12 @@ pub const Options = struct {
|
||||
/// If set, open up a new window in a custom instance of Ghostty.
|
||||
class: ?[:0]const u8 = null,
|
||||
|
||||
/// Did the user specify a `--working-directory` argument on the command line?
|
||||
_working_directory_seen: bool = false,
|
||||
|
||||
/// All of the arguments after `+new-window`. They will be sent to Ghosttty
|
||||
/// for processing.
|
||||
_arguments: ?[][:0]const u8 = null,
|
||||
_arguments: std.ArrayList([:0]const u8) = .empty,
|
||||
|
||||
/// Enable arg parsing diagnostics so that we don't get an error if
|
||||
/// there is a "normal" config setting on the cli.
|
||||
@@ -25,32 +28,25 @@ pub const Options = struct {
|
||||
|
||||
/// Manual parse hook, collect all of the arguments after `+new-window`.
|
||||
pub fn parseManuallyHook(self: *Options, alloc: Allocator, arg: []const u8, iter: anytype) (error{InvalidValue} || homedir.ExpandError || std.fs.Dir.RealPathAllocError || Allocator.Error)!bool {
|
||||
var arguments: std.ArrayList([:0]const u8) = .empty;
|
||||
errdefer {
|
||||
for (arguments.items) |argument| alloc.free(argument);
|
||||
arguments.deinit(alloc);
|
||||
}
|
||||
|
||||
var e_seen: bool = std.mem.eql(u8, arg, "-e");
|
||||
|
||||
// Include the argument that triggered the manual parse hook.
|
||||
if (try self.checkArg(alloc, arg)) |a| try arguments.append(alloc, a);
|
||||
if (try self.checkArg(alloc, arg)) |a| try self._arguments.append(alloc, a);
|
||||
|
||||
// Gather up the rest of the arguments to use as the command.
|
||||
while (iter.next()) |param| {
|
||||
if (e_seen) {
|
||||
try arguments.append(alloc, try alloc.dupeZ(u8, param));
|
||||
try self._arguments.append(alloc, try alloc.dupeZ(u8, param));
|
||||
continue;
|
||||
}
|
||||
if (std.mem.eql(u8, param, "-e")) {
|
||||
e_seen = true;
|
||||
try arguments.append(alloc, try alloc.dupeZ(u8, param));
|
||||
try self._arguments.append(alloc, try alloc.dupeZ(u8, param));
|
||||
continue;
|
||||
}
|
||||
if (try self.checkArg(alloc, param)) |a| try arguments.append(alloc, a);
|
||||
if (try self.checkArg(alloc, param)) |a| try self._arguments.append(alloc, a);
|
||||
}
|
||||
|
||||
self._arguments = try arguments.toOwnedSlice(alloc);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -62,13 +58,14 @@ pub const Options = struct {
|
||||
|
||||
if (lib.cutPrefix(u8, arg, "--working-directory=")) |rest| {
|
||||
const stripped = std.mem.trim(u8, rest, &std.ascii.whitespace);
|
||||
if (std.mem.eql(u8, stripped, "home")) return error.InvalidValue;
|
||||
if (std.mem.eql(u8, stripped, "inherit")) return error.InvalidValue;
|
||||
if (std.mem.eql(u8, stripped, "home")) return try alloc.dupeZ(u8, arg);
|
||||
if (std.mem.eql(u8, stripped, "inherit")) return try alloc.dupeZ(u8, arg);
|
||||
const cwd: std.fs.Dir = std.fs.cwd();
|
||||
var expandhome_buf: [std.fs.max_path_bytes]u8 = undefined;
|
||||
const expanded = try homedir.expandHome(stripped, &expandhome_buf);
|
||||
var realpath_buf: [std.fs.max_path_bytes]u8 = undefined;
|
||||
const realpath = try cwd.realpath(expanded, &realpath_buf);
|
||||
self._working_directory_seen = true;
|
||||
return try std.fmt.allocPrintSentinel(alloc, "--working-directory={s}", .{realpath}, 0);
|
||||
}
|
||||
|
||||
@@ -108,9 +105,11 @@ pub const Options = struct {
|
||||
/// 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
|
||||
/// relative to the current working directory that the `ghostty +new-window`
|
||||
/// command is run from. The special values `home` and `inherit` that are
|
||||
/// available as "normal" CLI flags or configuration entries do not work when
|
||||
/// used from the `+new-window` CLI action.
|
||||
/// command is run from. `~/` prefixes will also be expanded to the user's home
|
||||
/// directory.
|
||||
///
|
||||
/// If `--working-directory` is _not_ found on the command line, the working
|
||||
/// directory that `ghostty +new-window` is run from will be passed to Ghostty.
|
||||
///
|
||||
/// GTK uses an application ID to identify instances of applications. If Ghostty
|
||||
/// is compiled with release optimizations, the default application ID will be
|
||||
@@ -135,8 +134,16 @@ pub const Options = struct {
|
||||
/// * `--class=<class>`: If set, open up a new window in a custom instance of
|
||||
/// Ghostty. The class must be a valid GTK application ID.
|
||||
///
|
||||
/// * `--command`: The command to be executed in the first surface of the new window.
|
||||
///
|
||||
/// * `--working-directory=<directory>`: The working directory to pass to Ghostty.
|
||||
///
|
||||
/// * `--title`: A title that will override the title of the first surface in
|
||||
/// the new window. The title override may be edited or removed later.
|
||||
///
|
||||
/// * `-e`: Any arguments after this will be interpreted as a command to
|
||||
/// execute inside the new window instead of the default command.
|
||||
/// execute inside the first surface of the new window instead of the
|
||||
/// default command.
|
||||
///
|
||||
/// Available since: 1.2.0
|
||||
pub fn run(alloc: Allocator) !u8 {
|
||||
@@ -186,11 +193,12 @@ fn runArgs(
|
||||
if (exit) return 1;
|
||||
}
|
||||
|
||||
if (opts._arguments) |arguments| {
|
||||
if (arguments.len == 0) {
|
||||
try stderr.print("The -e flag was specified on the command line, but no other arguments were found.\n", .{});
|
||||
return 1;
|
||||
}
|
||||
if (!opts._working_directory_seen) {
|
||||
const alloc = opts._arena.?.allocator();
|
||||
const cwd: std.fs.Dir = std.fs.cwd();
|
||||
var buf: [std.fs.max_path_bytes]u8 = undefined;
|
||||
const wd = try cwd.realpath(".", &buf);
|
||||
try opts._arguments.append(alloc, try std.fmt.allocPrintSentinel(alloc, "--working-directory={s}", .{wd}, 0));
|
||||
}
|
||||
|
||||
var arena = ArenaAllocator.init(alloc_gpa);
|
||||
@@ -202,7 +210,7 @@ fn runArgs(
|
||||
if (opts.class) |class| .{ .class = class } else .detect,
|
||||
.new_window,
|
||||
.{
|
||||
.arguments = opts._arguments,
|
||||
.arguments = if (opts._arguments.items.len == 0) null else opts._arguments.items,
|
||||
},
|
||||
) catch |err| switch (err) {
|
||||
error.IPCFailed => {
|
||||
|
||||
@@ -3,7 +3,6 @@ const builtin = @import("builtin");
|
||||
const file_load = @import("config/file_load.zig");
|
||||
const formatter = @import("config/formatter.zig");
|
||||
pub const Config = @import("config/Config.zig");
|
||||
pub const ConfigOverrides = @import("config/ConfigOverrides.zig");
|
||||
pub const conditional = @import("config/conditional.zig");
|
||||
pub const io = @import("config/io.zig");
|
||||
pub const string = @import("config/string.zig");
|
||||
|
||||
@@ -30,7 +30,6 @@ const formatterpkg = @import("formatter.zig");
|
||||
const themepkg = @import("theme.zig");
|
||||
const url = @import("url.zig");
|
||||
pub const Key = @import("key.zig").Key;
|
||||
pub const Type = @import("key.zig").Type;
|
||||
const MetricModifier = fontpkg.Metrics.Modifier;
|
||||
const help_strings = @import("help_strings");
|
||||
pub const Command = @import("command.zig").Command;
|
||||
@@ -96,23 +95,6 @@ pub const compatibility = std.StaticStringMap(
|
||||
.{ "macos-dock-drop-behavior", compatMacOSDockDropBehavior },
|
||||
});
|
||||
|
||||
pub fn get(self: *const Config, comptime key: Key) Type(key) {
|
||||
return @field(self, @tagName(key));
|
||||
}
|
||||
|
||||
pub fn set(self: *Config, comptime key: Key, value: Type(key)) Allocator.Error!void {
|
||||
const alloc = self.arenaAlloc();
|
||||
@field(self.*, @tagName(key)) = try cloneValue(alloc, Type(key), value);
|
||||
}
|
||||
|
||||
test "set/get" {
|
||||
var config: Config = try .default(std.testing.allocator);
|
||||
defer config.deinit();
|
||||
try std.testing.expect(config.get(.language) == null);
|
||||
try config.set(.language, "en_US.UTF-8");
|
||||
try std.testing.expectEqualStrings("en_US.UTF-8", config.get(.language).?);
|
||||
}
|
||||
|
||||
/// Set Ghostty's graphical user interface language to a language other than the
|
||||
/// system default language. For example:
|
||||
///
|
||||
@@ -4775,8 +4757,8 @@ fn compatCursorInvertFgBg(
|
||||
// Realistically, these fields were mutually exclusive so anyone
|
||||
// relying on that behavior should just upgrade to the new
|
||||
// cursor-color/cursor-text fields.
|
||||
const isset = cli.args.parseBool(value_ orelse "t") catch return false;
|
||||
if (isset) {
|
||||
const set = cli.args.parseBool(value_ orelse "t") catch return false;
|
||||
if (set) {
|
||||
self.@"cursor-color" = .@"cell-foreground";
|
||||
self.@"cursor-text" = .@"cell-background";
|
||||
}
|
||||
@@ -4793,8 +4775,8 @@ fn compatSelectionInvertFgBg(
|
||||
_ = alloc;
|
||||
assert(std.mem.eql(u8, key, "selection-invert-fg-bg"));
|
||||
|
||||
const isset = cli.args.parseBool(value_ orelse "t") catch return false;
|
||||
if (isset) {
|
||||
const set = cli.args.parseBool(value_ orelse "t") catch return false;
|
||||
if (set) {
|
||||
self.@"selection-foreground" = .@"cell-background";
|
||||
self.@"selection-background" = .@"cell-foreground";
|
||||
}
|
||||
@@ -7279,9 +7261,9 @@ pub const Keybinds = struct {
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
|
||||
var keyset: Keybinds = .{};
|
||||
try keyset.parseCLI(alloc, "shift+a=copy_to_clipboard");
|
||||
try keyset.parseCLI(alloc, "shift+a=csi:hello");
|
||||
var set: Keybinds = .{};
|
||||
try set.parseCLI(alloc, "shift+a=copy_to_clipboard");
|
||||
try set.parseCLI(alloc, "shift+a=csi:hello");
|
||||
}
|
||||
|
||||
test "formatConfig single" {
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
//! Wrapper for a Config object that keeps track of which settings have been
|
||||
//! changed. Settings will be marked as set even if they are set to whatever the
|
||||
//! default value is for that setting. This allows overrides of a setting from
|
||||
//! a non-default value to the default value. To remove an override it must be
|
||||
//! explicitly removed from the set that keeps track of what config entries have
|
||||
//! been changed.
|
||||
|
||||
const ConfigOverrides = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const configpkg = @import("../config.zig");
|
||||
const args = @import("../cli/args.zig");
|
||||
const Config = configpkg.Config;
|
||||
const Key = Config.Key;
|
||||
const Type = Config.Type;
|
||||
|
||||
const log = std.log.scoped(.config_overrides);
|
||||
|
||||
/// Used to keep track of which settings have been overridden.
|
||||
isset: std.EnumSet(configpkg.Config.Key),
|
||||
|
||||
/// Storage for the overriding settings.
|
||||
config: configpkg.Config,
|
||||
|
||||
/// Create a new object that has no config settings overridden.
|
||||
pub fn init(self: *ConfigOverrides, alloc: Allocator) Allocator.Error!void {
|
||||
self.* = .{
|
||||
.isset = .initEmpty(),
|
||||
.config = try .default(alloc),
|
||||
};
|
||||
}
|
||||
|
||||
/// Has a config setting been overridden?
|
||||
pub fn isSet(self: *const ConfigOverrides, comptime key: Key) bool {
|
||||
return self.isset.contains(key);
|
||||
}
|
||||
|
||||
/// Set a configuration entry and mark it as having been overridden.
|
||||
pub fn set(self: *ConfigOverrides, comptime key: Key, value: Type(key)) Allocator.Error!void {
|
||||
try self.config.set(key, value);
|
||||
self.isset.insert(key);
|
||||
}
|
||||
|
||||
/// Mark a configuration entry as having not been overridden.
|
||||
pub fn unset(self: *ConfigOverrides, comptime key: Key) void {
|
||||
self.isset.remove(key);
|
||||
}
|
||||
|
||||
/// Get the value of a configuration entry.
|
||||
pub fn get(self: *const ConfigOverrides, comptime key: Key) Type(key) {
|
||||
return self.config.get(key);
|
||||
}
|
||||
|
||||
/// Parse a string that contains a CLI flag.
|
||||
pub fn parseCLI(self: *ConfigOverrides, str: []const u8) !void {
|
||||
const k: []const u8, const v: ?[]const u8 = kv: {
|
||||
if (!std.mem.startsWith(u8, str, "--")) return;
|
||||
if (std.mem.indexOfScalarPos(u8, str, 2, '=')) |pos| {
|
||||
break :kv .{
|
||||
std.mem.trim(u8, str[2..pos], &std.ascii.whitespace),
|
||||
std.mem.trim(u8, str[pos + 1 ..], &std.ascii.whitespace),
|
||||
};
|
||||
}
|
||||
break :kv .{ std.mem.trim(u8, str[2..], &std.ascii.whitespace), null };
|
||||
};
|
||||
|
||||
const key = std.meta.stringToEnum(Key, k) orelse return;
|
||||
try args.parseIntoField(Config, self.config.arenaAlloc(), &self.config, k, v);
|
||||
self.isset.insert(key);
|
||||
}
|
||||
|
||||
pub fn deinit(self: *ConfigOverrides) callconv(.c) void {
|
||||
self.config.deinit();
|
||||
}
|
||||
|
||||
test "ConfigOverrides" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var config_overrides: ConfigOverrides = undefined;
|
||||
try config_overrides.init(alloc);
|
||||
defer config_overrides.deinit();
|
||||
|
||||
try testing.expect(config_overrides.isSet(.@"font-size") == false);
|
||||
try config_overrides.set(.@"font-size", 24.0);
|
||||
try testing.expect(config_overrides.isSet(.@"font-size") == true);
|
||||
try testing.expectApproxEqAbs(24.0, config_overrides.get(.@"font-size"), 0.01);
|
||||
|
||||
try testing.expect(config_overrides.isSet(.@"working-directory") == false);
|
||||
try config_overrides.parseCLI("--working-directory=/home/ghostty");
|
||||
try testing.expect(config_overrides.isSet(.@"working-directory") == true);
|
||||
try testing.expectEqualStrings("/home/ghostty", config_overrides.get(.@"working-directory").?);
|
||||
}
|
||||
@@ -4,7 +4,7 @@ const key = @import("key.zig");
|
||||
const Config = @import("Config.zig");
|
||||
const Color = Config.Color;
|
||||
const Key = key.Key;
|
||||
const Type = key.Type;
|
||||
const Value = key.Value;
|
||||
|
||||
/// Get a value from the config by key into the given pointer. This is
|
||||
/// specifically for C-compatible APIs. If you're using Zig, just access
|
||||
@@ -17,7 +17,7 @@ pub fn get(config: *const Config, k: Key, ptr_raw: *anyopaque) bool {
|
||||
@setEvalBranchQuota(10_000);
|
||||
switch (k) {
|
||||
inline else => |tag| {
|
||||
const value = config.get(tag);
|
||||
const value = fieldByKey(config, tag);
|
||||
return getValue(ptr_raw, value);
|
||||
},
|
||||
}
|
||||
@@ -102,6 +102,22 @@ fn getValue(ptr_raw: *anyopaque, value: anytype) bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Get a value from the config by key.
|
||||
fn fieldByKey(self: *const Config, comptime k: Key) Value(k) {
|
||||
const field = comptime field: {
|
||||
const fields = std.meta.fields(Config);
|
||||
for (fields) |field| {
|
||||
if (@field(Key, field.name) == k) {
|
||||
break :field field;
|
||||
}
|
||||
}
|
||||
|
||||
unreachable;
|
||||
};
|
||||
|
||||
return @field(self, field.name);
|
||||
}
|
||||
|
||||
test "c_get: u8" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
@@ -165,6 +165,16 @@ pub const Command = union(enum) {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *const Self, alloc: Allocator) void {
|
||||
switch (self.*) {
|
||||
.shell => |v| alloc.free(v),
|
||||
.direct => |l| {
|
||||
for (l) |v| alloc.free(v);
|
||||
alloc.free(l);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn formatEntry(self: Self, formatter: formatterpkg.EntryFormatter) !void {
|
||||
switch (self) {
|
||||
.shell => |v| try formatter.formatEntry([]const u8, v),
|
||||
|
||||
@@ -32,7 +32,7 @@ pub const Key = key: {
|
||||
};
|
||||
|
||||
/// Returns the value type for a key
|
||||
pub fn Type(comptime key: Key) type {
|
||||
pub fn Value(comptime key: Key) type {
|
||||
const field = comptime field: {
|
||||
@setEvalBranchQuota(100_000);
|
||||
|
||||
@@ -52,6 +52,6 @@ pub fn Type(comptime key: Key) type {
|
||||
test "Value" {
|
||||
const testing = std.testing;
|
||||
|
||||
try testing.expectEqual(Config.RepeatableString, Type(.@"font-family"));
|
||||
try testing.expectEqual(?bool, Type(.@"cursor-style-blink"));
|
||||
try testing.expectEqual(Config.RepeatableString, Value(.@"font-family"));
|
||||
try testing.expectEqual(?bool, Value(.@"cursor-style-blink"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user