From 98d77788f4b233d422606482294cef8142c235b5 Mon Sep 17 00:00:00 2001 From: Leah Amelia Chen Date: Sat, 28 Dec 2024 00:14:21 +0800 Subject: [PATCH] feat(config): generate default template when config file is not found Closes #3203 --- src/config/Config.zig | 64 ++++++++++++++++++++++++++++++-------- src/config/config-template | 43 +++++++++++++++++++++++++ src/config/edit.zig | 8 +---- 3 files changed, 95 insertions(+), 20 deletions(-) create mode 100644 src/config/config-template diff --git a/src/config/Config.zig b/src/config/Config.zig index 4a06e22ac2..eb3d28d955 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -2668,18 +2668,40 @@ pub fn loadFile(self: *Config, alloc: Allocator, path: []const u8) !void { try self.expandPaths(std.fs.path.dirname(path).?); } +pub const OptionalFileAction = enum { loaded, not_found, @"error" }; + /// Load optional configuration file from `path`. All errors are ignored. -pub fn loadOptionalFile(self: *Config, alloc: Allocator, path: []const u8) void { - self.loadFile(alloc, path) catch |err| switch (err) { - error.FileNotFound => std.log.info( - "optional config file not found, not loading path={s}", - .{path}, - ), - else => std.log.warn( - "error reading optional config file, not loading err={} path={s}", - .{ err, path }, - ), - }; +/// +/// Returns the action that was taken. +pub fn loadOptionalFile( + self: *Config, + alloc: Allocator, + path: []const u8, +) OptionalFileAction { + if (self.loadFile(alloc, path)) { + return .loaded; + } else |err| switch (err) { + error.FileNotFound => return .not_found, + else => { + std.log.warn( + "error reading optional config file, not loading err={} path={s}", + .{ err, path }, + ); + + return .@"error"; + }, + } +} + +fn writeConfigTemplate(path: []const u8) !void { + log.info("creating template config file: path={s}", .{path}); + const file = try std.fs.createFileAbsolute(path, .{}); + defer file.close(); + try std.fmt.format( + file.writer(), + @embedFile("./config-template"), + .{ .path = path }, + ); } /// Load configurations from the default configuration files. The default @@ -2688,14 +2710,30 @@ pub fn loadOptionalFile(self: *Config, alloc: Allocator, path: []const u8) void /// On macOS, `$HOME/Library/Application Support/$CFBundleIdentifier/config` /// is also loaded. pub fn loadDefaultFiles(self: *Config, alloc: Allocator) !void { + // Load XDG first const xdg_path = try internal_os.xdg.config(alloc, .{ .subdir = "ghostty/config" }); defer alloc.free(xdg_path); - self.loadOptionalFile(alloc, xdg_path); + const xdg_action = self.loadOptionalFile(alloc, xdg_path); + // On macOS load the app support directory as well if (comptime builtin.os.tag == .macos) { const app_support_path = try internal_os.macos.appSupportDir(alloc, "config"); defer alloc.free(app_support_path); - self.loadOptionalFile(alloc, app_support_path); + const app_support_action = self.loadOptionalFile(alloc, app_support_path); + + // If both files are not found, then we create a template file. + // For macOS, we only create the template file in the app support + if (app_support_action == .not_found and xdg_action == .not_found) { + writeConfigTemplate(app_support_path) catch |err| { + log.warn("error creating template config file err={}", .{err}); + }; + } + } else { + if (xdg_action == .not_found) { + writeConfigTemplate(xdg_path) catch |err| { + log.warn("error creating template config file err={}", .{err}); + }; + } } } diff --git a/src/config/config-template b/src/config/config-template new file mode 100644 index 0000000000..4645e60aa3 --- /dev/null +++ b/src/config/config-template @@ -0,0 +1,43 @@ +# This is the configuration file for Ghostty. +# +# This template file has been automatically created at the following +# path since Ghostty couldn't find any existing config files on your system: +# +# {[path]s} +# +# The template does not set any default options, since Ghostty ships +# with sensible defaults for all options. Users should only need to set +# options that they want to change from the default. +# +# Run `ghostty +show-config --default --docs` to view a list of +# all available config options and their default values. +# +# Additionally, each config option is also explained in detail +# on Ghostty's website, at https://ghostty.org/docs/config. + +# Config syntax crash course +# ========================== +# # The config file consists of simple key-value pairs, +# # separated by equals signs. +# font-family = Iosevka +# window-padding-x = 2 +# +# # Spacing around the equals sign does not matter. +# # All of these are identical: +# key=value +# key= value +# key =value +# key = value +# +# # Any line beginning with a # is a comment. It's not possible to put +# # a comment after a config option, since it would be interpreted as a +# # part of the value. For example, this will have a value of "#123abc": +# background = #123abc +# +# # Empty values are used to reset config keys to default. +# key = +# +# # Some config options have unique syntaxes for their value, +# # which is explained in the docs for that config option. +# # Just for example: +# resize-overlay-duration = 4s 200ms diff --git a/src/config/edit.zig b/src/config/edit.zig index 68d9da88c1..871a1a7554 100644 --- a/src/config/edit.zig +++ b/src/config/edit.zig @@ -48,13 +48,7 @@ pub fn open(alloc_gpa: Allocator) !void { /// /// The allocator must be an arena allocator. No memory is freed by this /// function and the resulting path is not all the memory that is allocated. -/// -/// NOTE: WHY IS THIS INLINE? This is inline because when this is not -/// inline then Zig 0.13 crashes [most of the time] when trying to compile -/// this file. This is a workaround for that issue. This function is only -/// called from one place that is not performance critical so it is fine -/// to be inline. -inline fn configPath(alloc_arena: Allocator) ![]const u8 { +fn configPath(alloc_arena: Allocator) ![]const u8 { const paths: []const []const u8 = try configPathCandidates(alloc_arena); assert(paths.len > 0);