mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +03:00
config: load dark/light theme based on conditional state
This commit is contained in:
@ -2582,7 +2582,6 @@ pub fn loadRecursiveFiles(self: *Config, alloc_gpa: Allocator) !void {
|
|||||||
/// that if you change the conditional state and the user in the interim
|
/// that if you change the conditional state and the user in the interim
|
||||||
/// deleted a file that was referenced in the configuration, then the
|
/// deleted a file that was referenced in the configuration, then the
|
||||||
/// configuration can still be reloaded.
|
/// configuration can still be reloaded.
|
||||||
/// TODO: totally untested
|
|
||||||
pub fn changeConditionalState(
|
pub fn changeConditionalState(
|
||||||
self: *Config,
|
self: *Config,
|
||||||
new: conditional.State,
|
new: conditional.State,
|
||||||
@ -2598,6 +2597,7 @@ pub fn changeConditionalState(
|
|||||||
// Replay all of our steps to rebuild the configuration
|
// Replay all of our steps to rebuild the configuration
|
||||||
var it = Replay.iterator(self._replay_steps.items, &new_config);
|
var it = Replay.iterator(self._replay_steps.items, &new_config);
|
||||||
try new_config.loadIter(alloc_gpa, &it);
|
try new_config.loadIter(alloc_gpa, &it);
|
||||||
|
try new_config.finalize();
|
||||||
|
|
||||||
return new_config;
|
return new_config;
|
||||||
}
|
}
|
||||||
@ -2626,11 +2626,18 @@ fn expandPaths(self: *Config, base: []const u8) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn loadTheme(self: *Config, theme: Theme) !void {
|
fn loadTheme(self: *Config, theme: Theme) !void {
|
||||||
|
// Load the correct theme depending on the conditional state.
|
||||||
|
// Dark/light themes were programmed prior to conditional configuration
|
||||||
|
// so when we introduce that we probably want to replace this.
|
||||||
|
const name: []const u8 = switch (self._conditional_state.theme) {
|
||||||
|
.light => theme.light,
|
||||||
|
.dark => theme.dark,
|
||||||
|
};
|
||||||
|
|
||||||
// Find our theme file and open it. See the open function for details.
|
// Find our theme file and open it. See the open function for details.
|
||||||
// TODO: handle dark
|
|
||||||
const themefile = (try themepkg.open(
|
const themefile = (try themepkg.open(
|
||||||
self._arena.?.allocator(),
|
self._arena.?.allocator(),
|
||||||
theme.light,
|
name,
|
||||||
&self._diagnostics,
|
&self._diagnostics,
|
||||||
)) orelse return;
|
)) orelse return;
|
||||||
const path = themefile.path;
|
const path = themefile.path;
|
||||||
@ -2650,10 +2657,6 @@ fn loadTheme(self: *Config, theme: Theme) !void {
|
|||||||
// Point 2 is strictly a result of aur approach to point 1, but it is
|
// Point 2 is strictly a result of aur approach to point 1, but it is
|
||||||
// a nice property to have to limit memory bloat as much as possible.
|
// a nice property to have to limit memory bloat as much as possible.
|
||||||
|
|
||||||
// Keep track of our replay length prior to loading the theme
|
|
||||||
// so that we can replay the previous config to override values.
|
|
||||||
const replay_len = self._replay_steps.items.len;
|
|
||||||
|
|
||||||
// Load into a new configuration so that we can free the existing memory.
|
// Load into a new configuration so that we can free the existing memory.
|
||||||
const alloc_gpa = self._arena.?.child_allocator;
|
const alloc_gpa = self._arena.?.child_allocator;
|
||||||
var new_config = try default(alloc_gpa);
|
var new_config = try default(alloc_gpa);
|
||||||
@ -2666,9 +2669,44 @@ fn loadTheme(self: *Config, theme: Theme) !void {
|
|||||||
var iter: Iter = .{ .r = reader, .filepath = path };
|
var iter: Iter = .{ .r = reader, .filepath = path };
|
||||||
try new_config.loadIter(alloc_gpa, &iter);
|
try new_config.loadIter(alloc_gpa, &iter);
|
||||||
|
|
||||||
|
// Setup our replay to be conditional.
|
||||||
|
for (new_config._replay_steps.items) |*item| switch (item.*) {
|
||||||
|
.expand => {},
|
||||||
|
|
||||||
|
// Change our arg to be conditional on our theme.
|
||||||
|
.arg => |v| {
|
||||||
|
const alloc_arena = new_config._arena.?.allocator();
|
||||||
|
const conds = try alloc_arena.alloc(Conditional, 1);
|
||||||
|
conds[0] = .{
|
||||||
|
.key = .theme,
|
||||||
|
.op = .eq,
|
||||||
|
.value = @tagName(self._conditional_state.theme),
|
||||||
|
};
|
||||||
|
item.* = .{ .conditional_arg = .{
|
||||||
|
.conditions = conds,
|
||||||
|
.arg = v,
|
||||||
|
} };
|
||||||
|
},
|
||||||
|
|
||||||
|
.conditional_arg => |v| {
|
||||||
|
const alloc_arena = new_config._arena.?.allocator();
|
||||||
|
const conds = try alloc_arena.alloc(Conditional, v.conditions.len + 1);
|
||||||
|
conds[0] = .{
|
||||||
|
.key = .theme,
|
||||||
|
.op = .eq,
|
||||||
|
.value = @tagName(self._conditional_state.theme),
|
||||||
|
};
|
||||||
|
@memcpy(conds[1..], v.conditions);
|
||||||
|
item.* = .{ .conditional_arg = .{
|
||||||
|
.conditions = conds,
|
||||||
|
.arg = v.arg,
|
||||||
|
} };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// Replay our previous inputs so that we can override values
|
// Replay our previous inputs so that we can override values
|
||||||
// from the theme.
|
// from the theme.
|
||||||
var slice_it = Replay.iterator(self._replay_steps.items[0..replay_len], &new_config);
|
var slice_it = Replay.iterator(self._replay_steps.items, &new_config);
|
||||||
try new_config.loadIter(alloc_gpa, &slice_it);
|
try new_config.loadIter(alloc_gpa, &slice_it);
|
||||||
|
|
||||||
// Success, swap our new config in and free the old.
|
// Success, swap our new config in and free the old.
|
||||||
@ -3138,7 +3176,9 @@ const Replay = struct {
|
|||||||
log.warn("error expanding paths err={}", .{err});
|
log.warn("error expanding paths err={}", .{err});
|
||||||
},
|
},
|
||||||
|
|
||||||
.arg => |arg| return arg,
|
.arg => |arg| {
|
||||||
|
return arg;
|
||||||
|
},
|
||||||
|
|
||||||
.conditional_arg => |v| conditional: {
|
.conditional_arg => |v| conditional: {
|
||||||
// All conditions must match.
|
// All conditions must match.
|
||||||
@ -5197,3 +5237,95 @@ test "theme priority is lower than config" {
|
|||||||
.b = 0xEF,
|
.b = 0xEF,
|
||||||
}, cfg.background);
|
}, cfg.background);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "theme loading correct light/dark" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
var arena = ArenaAllocator.init(alloc);
|
||||||
|
defer arena.deinit();
|
||||||
|
const alloc_arena = arena.allocator();
|
||||||
|
|
||||||
|
// Setup our test theme
|
||||||
|
var td = try internal_os.TempDir.init();
|
||||||
|
defer td.deinit();
|
||||||
|
{
|
||||||
|
var file = try td.dir.createFile("theme_light", .{});
|
||||||
|
defer file.close();
|
||||||
|
try file.writer().writeAll(@embedFile("testdata/theme_light"));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var file = try td.dir.createFile("theme_dark", .{});
|
||||||
|
defer file.close();
|
||||||
|
try file.writer().writeAll(@embedFile("testdata/theme_dark"));
|
||||||
|
}
|
||||||
|
var light_buf: [std.fs.max_path_bytes]u8 = undefined;
|
||||||
|
const light = try td.dir.realpath("theme_light", &light_buf);
|
||||||
|
var dark_buf: [std.fs.max_path_bytes]u8 = undefined;
|
||||||
|
const dark = try td.dir.realpath("theme_dark", &dark_buf);
|
||||||
|
|
||||||
|
// Light
|
||||||
|
{
|
||||||
|
var cfg = try Config.default(alloc);
|
||||||
|
defer cfg.deinit();
|
||||||
|
var it: TestIterator = .{ .data = &.{
|
||||||
|
try std.fmt.allocPrint(
|
||||||
|
alloc_arena,
|
||||||
|
"--theme=light:{s},dark:{s}",
|
||||||
|
.{ light, dark },
|
||||||
|
),
|
||||||
|
} };
|
||||||
|
try cfg.loadIter(alloc, &it);
|
||||||
|
try cfg.finalize();
|
||||||
|
|
||||||
|
try testing.expectEqual(Color{
|
||||||
|
.r = 0xFF,
|
||||||
|
.g = 0xFF,
|
||||||
|
.b = 0xFF,
|
||||||
|
}, cfg.background);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dark
|
||||||
|
{
|
||||||
|
var cfg = try Config.default(alloc);
|
||||||
|
defer cfg.deinit();
|
||||||
|
cfg._conditional_state = .{ .theme = .dark };
|
||||||
|
var it: TestIterator = .{ .data = &.{
|
||||||
|
try std.fmt.allocPrint(
|
||||||
|
alloc_arena,
|
||||||
|
"--theme=light:{s},dark:{s}",
|
||||||
|
.{ light, dark },
|
||||||
|
),
|
||||||
|
} };
|
||||||
|
try cfg.loadIter(alloc, &it);
|
||||||
|
try cfg.finalize();
|
||||||
|
|
||||||
|
try testing.expectEqual(Color{
|
||||||
|
.r = 0xEE,
|
||||||
|
.g = 0xEE,
|
||||||
|
.b = 0xEE,
|
||||||
|
}, cfg.background);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Light to Dark
|
||||||
|
{
|
||||||
|
var cfg = try Config.default(alloc);
|
||||||
|
defer cfg.deinit();
|
||||||
|
var it: TestIterator = .{ .data = &.{
|
||||||
|
try std.fmt.allocPrint(
|
||||||
|
alloc_arena,
|
||||||
|
"--theme=light:{s},dark:{s}",
|
||||||
|
.{ light, dark },
|
||||||
|
),
|
||||||
|
} };
|
||||||
|
try cfg.loadIter(alloc, &it);
|
||||||
|
try cfg.finalize();
|
||||||
|
|
||||||
|
var new = try cfg.changeConditionalState(.{ .theme = .dark });
|
||||||
|
defer new.deinit();
|
||||||
|
try testing.expectEqual(Color{
|
||||||
|
.r = 0xEE,
|
||||||
|
.g = 0xEE,
|
||||||
|
.b = 0xEE,
|
||||||
|
}, new.background);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
1
src/config/testdata/theme_dark
vendored
Normal file
1
src/config/testdata/theme_dark
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
background = #EEEEEE
|
1
src/config/testdata/theme_light
vendored
Normal file
1
src/config/testdata/theme_light
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
background = #FFFFFF
|
Reference in New Issue
Block a user