mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
config: move theme loading to dedicated file
This commit is contained in:

committed by
Mitchell Hashimoto

parent
87791ed562
commit
d3182c8d7c
@ -24,6 +24,7 @@ const cli = @import("../cli.zig");
|
|||||||
const Command = @import("../Command.zig");
|
const Command = @import("../Command.zig");
|
||||||
|
|
||||||
const formatterpkg = @import("formatter.zig");
|
const formatterpkg = @import("formatter.zig");
|
||||||
|
const themepkg = @import("theme.zig");
|
||||||
const url = @import("url.zig");
|
const url = @import("url.zig");
|
||||||
const Key = @import("key.zig").Key;
|
const Key = @import("key.zig").Key;
|
||||||
const KeyValue = @import("key.zig").Value;
|
const KeyValue = @import("key.zig").Value;
|
||||||
@ -2205,88 +2206,12 @@ pub fn themeDir(alloc: std.mem.Allocator, type_: ThemeDirType) ?[]const u8 {
|
|||||||
fn loadTheme(self: *Config, theme: []const u8) !void {
|
fn loadTheme(self: *Config, theme: []const u8) !void {
|
||||||
const alloc = self._arena.?.allocator();
|
const alloc = self._arena.?.allocator();
|
||||||
|
|
||||||
const file = file: {
|
// Find our theme file and open it. See the open function for details.
|
||||||
if (std.fs.path.isAbsolute(theme)) {
|
const file: std.fs.File = (try themepkg.open(
|
||||||
// Theme is an absolute path, open that file or fail
|
alloc,
|
||||||
break :file std.fs.openFileAbsolute(theme, .{}) catch |err| switch (err) {
|
theme,
|
||||||
error.FileNotFound => {
|
&self._errors,
|
||||||
try self._errors.add(alloc, .{
|
)) orelse return;
|
||||||
.message = try std.fmt.allocPrintZ(
|
|
||||||
alloc,
|
|
||||||
"failed to load theme from the path \"{s}\"",
|
|
||||||
.{theme},
|
|
||||||
),
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
else => {
|
|
||||||
try self._errors.add(alloc, .{
|
|
||||||
.message = try std.fmt.allocPrintZ(
|
|
||||||
alloc,
|
|
||||||
"failed to load theme from the path \"{s}\": {}",
|
|
||||||
.{ theme, err },
|
|
||||||
),
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// The theme is not an absolute path, search the user and system theme
|
|
||||||
// directories
|
|
||||||
|
|
||||||
const dirs: []const struct {
|
|
||||||
type: ThemeDirType,
|
|
||||||
dir: ?[]const u8,
|
|
||||||
} = &.{
|
|
||||||
.{ .type = .user, .dir = themeDir(alloc, .user) },
|
|
||||||
.{ .type = .system, .dir = themeDir(alloc, .system) },
|
|
||||||
};
|
|
||||||
|
|
||||||
const cwd = std.fs.cwd();
|
|
||||||
for (dirs) |dir| {
|
|
||||||
if (dir.dir) |d| {
|
|
||||||
const path = try std.fs.path.join(alloc, &.{
|
|
||||||
d,
|
|
||||||
theme,
|
|
||||||
});
|
|
||||||
if (cwd.openFile(path, .{})) |file| {
|
|
||||||
break :file file;
|
|
||||||
} else |err| switch (err) {
|
|
||||||
error.FileNotFound => {},
|
|
||||||
else => {
|
|
||||||
try self._errors.add(alloc, .{
|
|
||||||
.message = try std.fmt.allocPrintZ(
|
|
||||||
alloc,
|
|
||||||
"failed to load theme \"{s}\" from the file \"{s}\": {}",
|
|
||||||
.{ theme, path, err },
|
|
||||||
),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we get here, no file was found with the theme. Log some errors
|
|
||||||
// and bail.
|
|
||||||
for (dirs) |dir| {
|
|
||||||
if (dir.dir) |d| {
|
|
||||||
try self._errors.add(alloc, .{
|
|
||||||
.message = try std.fmt.allocPrintZ(
|
|
||||||
alloc,
|
|
||||||
"theme \"{s}\" not found, tried {s} path \"{s}\"",
|
|
||||||
.{
|
|
||||||
theme,
|
|
||||||
@tagName(dir.type),
|
|
||||||
d,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
defer file.close();
|
defer file.close();
|
||||||
|
|
||||||
// From this point onwards, we load the theme and do a bit of a dance
|
// From this point onwards, we load the theme and do a bit of a dance
|
||||||
|
181
src/config/theme.zig
Normal file
181
src/config/theme.zig
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
const global_state = &@import("../main.zig").state;
|
||||||
|
const internal_os = @import("../os/main.zig");
|
||||||
|
const ErrorList = @import("ErrorList.zig");
|
||||||
|
|
||||||
|
/// Location of possible themes. The order of this enum matters because
|
||||||
|
/// it defines the priority of theme search (from top to bottom).
|
||||||
|
pub const Location = enum {
|
||||||
|
user, // xdg config dir
|
||||||
|
resources, // Ghostty resources dir
|
||||||
|
|
||||||
|
/// Returns the directory for the given theme based on this location type.
|
||||||
|
///
|
||||||
|
/// This will return null with no error if the directory type doesn't
|
||||||
|
/// exist or is invalid for any reason. For example, it is perfectly
|
||||||
|
/// valid to install and run Ghostty without the resources directory.
|
||||||
|
///
|
||||||
|
/// This may allocate memory but it isn't guaranteed so the allocator
|
||||||
|
/// should be something like an arena. It isn't safe to always free the
|
||||||
|
/// resulting pointer.
|
||||||
|
pub fn dir(
|
||||||
|
self: Location,
|
||||||
|
alloc_arena: Allocator,
|
||||||
|
theme: []const u8,
|
||||||
|
) error{OutOfMemory}!?[]const u8 {
|
||||||
|
if (comptime std.debug.runtime_safety) {
|
||||||
|
assert(!std.fs.path.isAbsolute(theme));
|
||||||
|
}
|
||||||
|
|
||||||
|
return switch (self) {
|
||||||
|
.user => user: {
|
||||||
|
var buf: [std.fs.max_path_bytes]u8 = undefined;
|
||||||
|
const subdir = std.fmt.bufPrint(
|
||||||
|
&buf,
|
||||||
|
"ghostty/themes/{s}",
|
||||||
|
.{theme},
|
||||||
|
) catch |err| switch (err) {
|
||||||
|
error.NoSpaceLeft => return error.OutOfMemory,
|
||||||
|
};
|
||||||
|
|
||||||
|
break :user internal_os.xdg.config(
|
||||||
|
alloc_arena,
|
||||||
|
.{ .subdir = subdir },
|
||||||
|
) catch |err| switch (err) {
|
||||||
|
error.OutOfMemory => return error.OutOfMemory,
|
||||||
|
error.BufferTooSmall => return error.OutOfMemory,
|
||||||
|
|
||||||
|
// No home dir means we don't have an XDG config dir
|
||||||
|
// so we can just return null.
|
||||||
|
error.NoHomeDir => return null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
.resources => try std.fs.path.join(alloc_arena, &.{
|
||||||
|
global_state.resources_dir orelse return null,
|
||||||
|
"themes",
|
||||||
|
theme,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// An iterator that returns all possible locations for a theme in order
|
||||||
|
/// of priority.
|
||||||
|
pub const LocationIterator = struct {
|
||||||
|
alloc_arena: Allocator,
|
||||||
|
theme: []const u8,
|
||||||
|
i: usize = 0,
|
||||||
|
|
||||||
|
pub fn next(self: *LocationIterator) !?[]const u8 {
|
||||||
|
const max = @typeInfo(Location).Enum.fields.len;
|
||||||
|
while (true) {
|
||||||
|
if (self.i >= max) return null;
|
||||||
|
const loc: Location = @enumFromInt(self.i);
|
||||||
|
self.i += 1;
|
||||||
|
const dir_ = try loc.dir(self.alloc_arena, self.theme);
|
||||||
|
const dir = dir_ orelse continue;
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(self: *LocationIterator) void {
|
||||||
|
self.i = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Open the given named theme. If there are any errors then messages
|
||||||
|
/// will be appended to the given error list and null is returned. If
|
||||||
|
/// a non-null return value is returned, there are never any errors added.
|
||||||
|
///
|
||||||
|
/// One error that is not recoverable and may be returned is OOM. This is
|
||||||
|
/// always a critical error for configuration loading so it is returned.
|
||||||
|
pub fn open(
|
||||||
|
alloc_arena: Allocator,
|
||||||
|
theme: []const u8,
|
||||||
|
errors: *ErrorList,
|
||||||
|
) error{OutOfMemory}!?std.fs.File {
|
||||||
|
// Absolute themes are loaded a different path.
|
||||||
|
if (std.fs.path.isAbsolute(theme)) return try openAbsolute(
|
||||||
|
alloc_arena,
|
||||||
|
theme,
|
||||||
|
errors,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Iterate over the possible locations to try to find the
|
||||||
|
// one that exists.
|
||||||
|
var it: LocationIterator = .{ .alloc_arena = alloc_arena, .theme = theme };
|
||||||
|
const cwd = std.fs.cwd();
|
||||||
|
while (try it.next()) |path| {
|
||||||
|
if (cwd.openFile(path, .{})) |file| {
|
||||||
|
return file;
|
||||||
|
} else |err| switch (err) {
|
||||||
|
// Not an error, just continue to the next location.
|
||||||
|
error.FileNotFound => {},
|
||||||
|
|
||||||
|
// Anything else is an error we log and give up on.
|
||||||
|
else => {
|
||||||
|
try errors.add(alloc_arena, .{
|
||||||
|
.message = try std.fmt.allocPrintZ(
|
||||||
|
alloc_arena,
|
||||||
|
"failed to load theme \"{s}\" from the file \"{s}\": {}",
|
||||||
|
.{ theme, path, err },
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlikely scenario: the theme doesn't exist. In this case, we reset
|
||||||
|
// our iterator, reiterate over in order to build a better error message.
|
||||||
|
// This does double allocate some memory but for errors I think thats
|
||||||
|
// fine.
|
||||||
|
it.reset();
|
||||||
|
while (try it.next()) |path| {
|
||||||
|
try errors.add(alloc_arena, .{
|
||||||
|
.message = try std.fmt.allocPrintZ(
|
||||||
|
alloc_arena,
|
||||||
|
"theme \"{s}\" not found, tried path \"{s}\"",
|
||||||
|
.{ theme, path },
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Open the given theme from an absolute path. If there are any errors
|
||||||
|
/// then messages will be appended to the given error list and null is
|
||||||
|
/// returned. If a non-null return value is returned, there are never any
|
||||||
|
/// errors added.
|
||||||
|
pub fn openAbsolute(
|
||||||
|
alloc_arena: Allocator,
|
||||||
|
theme: []const u8,
|
||||||
|
errors: *ErrorList,
|
||||||
|
) error{OutOfMemory}!?std.fs.File {
|
||||||
|
return std.fs.openFileAbsolute(theme, .{}) catch |err| {
|
||||||
|
switch (err) {
|
||||||
|
error.FileNotFound => try errors.add(alloc_arena, .{
|
||||||
|
.message = try std.fmt.allocPrintZ(
|
||||||
|
alloc_arena,
|
||||||
|
"failed to load theme from the path \"{s}\"",
|
||||||
|
.{theme},
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
else => try errors.add(alloc_arena, .{
|
||||||
|
.message = try std.fmt.allocPrintZ(
|
||||||
|
alloc_arena,
|
||||||
|
"failed to load theme from the path \"{s}\": {}",
|
||||||
|
.{ theme, err },
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
}
|
Reference in New Issue
Block a user