mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +03:00
Load $XDG_CONFIG_HOME/ghostty/config
if it exists (#25)
Ghostty now loads the config file in `$XDG_CONFIG_HOME/ghostty/config` if it exists on startup. This follows the XDG base dir specification so if $XDG_CONFIG_HOME is not set, we default to `$HOME/.config/ghostty/config`.
This commit is contained in:

committed by
GitHub

parent
116a157e17
commit
d75e869b4e
@ -60,8 +60,6 @@ pub fn parse(comptime T: type, alloc: Allocator, dst: *T, iter: anytype) !void {
|
|||||||
try parseIntoField(T, arena_alloc, dst, key, value);
|
try parseIntoField(T, arena_alloc, dst, key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (@hasDecl(T, "finalize")) try dst.finalize();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a single key/value pair into the destination type T.
|
/// Parse a single key/value pair into the destination type T.
|
||||||
@ -195,28 +193,6 @@ test "parse: simple" {
|
|||||||
try testing.expect(!data.@"b-f");
|
try testing.expect(!data.@"b-f");
|
||||||
}
|
}
|
||||||
|
|
||||||
test "parse: finalize" {
|
|
||||||
const testing = std.testing;
|
|
||||||
|
|
||||||
var data: struct {
|
|
||||||
a: []const u8 = "",
|
|
||||||
_arena: ?ArenaAllocator = null,
|
|
||||||
|
|
||||||
pub fn finalize(self: *@This()) !void {
|
|
||||||
self.a = "YO";
|
|
||||||
}
|
|
||||||
} = .{};
|
|
||||||
defer if (data._arena) |arena| arena.deinit();
|
|
||||||
|
|
||||||
var iter = try std.process.ArgIteratorGeneral(.{}).init(
|
|
||||||
testing.allocator,
|
|
||||||
"--a=42",
|
|
||||||
);
|
|
||||||
defer iter.deinit();
|
|
||||||
try parse(@TypeOf(data), testing.allocator, &data, &iter);
|
|
||||||
try testing.expectEqualStrings("YO", data.a);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "parseIntoField: string" {
|
test "parseIntoField: string" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
var arena = ArenaAllocator.init(testing.allocator);
|
var arena = ArenaAllocator.init(testing.allocator);
|
||||||
|
91
src/homedir.zig
Normal file
91
src/homedir.zig
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const passwd = @import("passwd.zig");
|
||||||
|
|
||||||
|
const Error = error{
|
||||||
|
/// The buffer used for output is not large enough to store the value.
|
||||||
|
BufferTooSmall,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Determine the home directory for the currently executing user. This
|
||||||
|
/// is generally an expensive process so the value should be cached.
|
||||||
|
pub inline fn home(buf: []u8) !?[]u8 {
|
||||||
|
return switch (builtin.os.tag) {
|
||||||
|
inline .linux, .macos => try homeUnix(buf),
|
||||||
|
else => @compileError("unimplemented"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn homeUnix(buf: []u8) !?[]u8 {
|
||||||
|
// First: if we have a HOME env var, then we use that.
|
||||||
|
if (std.os.getenv("HOME")) |result| {
|
||||||
|
if (buf.len < result.len) return Error.BufferTooSmall;
|
||||||
|
std.mem.copy(u8, buf, result);
|
||||||
|
return buf[0..result.len];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Everything below here will require some allocation
|
||||||
|
var tempBuf: [1024]u8 = undefined;
|
||||||
|
var fba = std.heap.FixedBufferAllocator.init(&tempBuf);
|
||||||
|
|
||||||
|
// If we're on darwin, we try the directory service. I'm not sure if there
|
||||||
|
// is a Mac API to do this but if so we can link to that...
|
||||||
|
if (builtin.os.tag == .macos) {
|
||||||
|
const exec = try std.ChildProcess.exec(.{
|
||||||
|
.allocator = fba.allocator(),
|
||||||
|
.argv = &[_][]const u8{
|
||||||
|
"/bin/sh",
|
||||||
|
"-c",
|
||||||
|
"dscl -q . -read /Users/\"$(whoami)\" NFSHomeDirectory | sed 's/^[^ ]*: //'",
|
||||||
|
},
|
||||||
|
.max_output_bytes = fba.buffer.len / 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (exec.term == .Exited and exec.term.Exited == 0) {
|
||||||
|
const result = trimSpace(exec.stdout);
|
||||||
|
if (buf.len < result.len) return Error.BufferTooSmall;
|
||||||
|
std.mem.copy(u8, buf, result);
|
||||||
|
return buf[0..result.len];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We try passwd. This doesn't work on multi-user mac but we try it anyways.
|
||||||
|
fba.reset();
|
||||||
|
const pw = try passwd.get(fba.allocator());
|
||||||
|
if (pw.home) |result| {
|
||||||
|
if (buf.len < result.len) return Error.BufferTooSmall;
|
||||||
|
std.mem.copy(u8, buf, result);
|
||||||
|
return buf[0..result.len];
|
||||||
|
}
|
||||||
|
|
||||||
|
// If all else fails, have the shell tell us...
|
||||||
|
fba.reset();
|
||||||
|
const exec = try std.ChildProcess.exec(.{
|
||||||
|
.allocator = fba.allocator(),
|
||||||
|
.argv = &[_][]const u8{ "/bin/sh", "-c", "cd && pwd" },
|
||||||
|
.max_output_bytes = fba.buffer.len / 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (exec.term == .Exited and exec.term.Exited == 0) {
|
||||||
|
const result = trimSpace(exec.stdout);
|
||||||
|
if (buf.len < result.len) return Error.BufferTooSmall;
|
||||||
|
std.mem.copy(u8, buf, result);
|
||||||
|
return buf[0..result.len];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn trimSpace(input: []const u8) []const u8 {
|
||||||
|
return std.mem.trim(u8, input, " \n\t");
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
var buf: [1024]u8 = undefined;
|
||||||
|
const result = try home(&buf);
|
||||||
|
try testing.expect(result != null);
|
||||||
|
try testing.expect(result.?.len > 0);
|
||||||
|
}
|
51
src/main.zig
51
src/main.zig
@ -8,6 +8,7 @@ const harfbuzz = @import("harfbuzz");
|
|||||||
const macos = @import("macos");
|
const macos = @import("macos");
|
||||||
const tracy = @import("tracy");
|
const tracy = @import("tracy");
|
||||||
const renderer = @import("renderer.zig");
|
const renderer = @import("renderer.zig");
|
||||||
|
const xdg = @import("xdg.zig");
|
||||||
|
|
||||||
const App = @import("App.zig");
|
const App = @import("App.zig");
|
||||||
const cli_args = @import("cli_args.zig");
|
const cli_args = @import("cli_args.zig");
|
||||||
@ -58,23 +59,48 @@ pub fn main() !void {
|
|||||||
break :alloc tracy_alloc.allocator();
|
break :alloc tracy_alloc.allocator();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parse the config from the CLI args
|
// Try reading our config
|
||||||
var config = config: {
|
var config = try Config.default(alloc);
|
||||||
var result = try Config.default(alloc);
|
|
||||||
errdefer result.deinit();
|
|
||||||
var iter = try std.process.argsWithAllocator(alloc);
|
|
||||||
defer iter.deinit();
|
|
||||||
try cli_args.parse(Config, alloc, &result, &iter);
|
|
||||||
break :config result;
|
|
||||||
};
|
|
||||||
defer config.deinit();
|
defer config.deinit();
|
||||||
|
|
||||||
// Parse the config files
|
// If we have a configuration file in our home directory, parse that first.
|
||||||
|
const cwd = std.fs.cwd();
|
||||||
|
{
|
||||||
|
const home_config_path = try xdg.config(alloc, .{ .subdir = "ghostty/config" });
|
||||||
|
defer alloc.free(home_config_path);
|
||||||
|
|
||||||
|
if (cwd.openFile(home_config_path, .{})) |file| {
|
||||||
|
defer file.close();
|
||||||
|
|
||||||
|
var buf_reader = std.io.bufferedReader(file.reader());
|
||||||
|
var iter = cli_args.lineIterator(buf_reader.reader());
|
||||||
|
try cli_args.parse(Config, alloc, &config, &iter);
|
||||||
|
} else |err| switch (err) {
|
||||||
|
error.FileNotFound => std.log.info(
|
||||||
|
"homedir config not found, not loading path={s}",
|
||||||
|
.{home_config_path},
|
||||||
|
),
|
||||||
|
|
||||||
|
else => std.log.warn(
|
||||||
|
"error reading homedir config file, not loading err={} path={s}",
|
||||||
|
.{ err, home_config_path },
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the config from the CLI args
|
||||||
|
{
|
||||||
|
var iter = try std.process.argsWithAllocator(alloc);
|
||||||
|
defer iter.deinit();
|
||||||
|
try cli_args.parse(Config, alloc, &config, &iter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the config files that were added from our file and CLI args.
|
||||||
|
// TODO(mitchellh): we should parse the files form the homedir first
|
||||||
// TODO(mitchellh): support nesting (config-file in a config file)
|
// TODO(mitchellh): support nesting (config-file in a config file)
|
||||||
// TODO(mitchellh): detect cycles when nesting
|
// TODO(mitchellh): detect cycles when nesting
|
||||||
if (config.@"config-file".list.items.len > 0) {
|
if (config.@"config-file".list.items.len > 0) {
|
||||||
const len = config.@"config-file".list.items.len;
|
const len = config.@"config-file".list.items.len;
|
||||||
const cwd = std.fs.cwd();
|
|
||||||
for (config.@"config-file".list.items) |path| {
|
for (config.@"config-file".list.items) |path| {
|
||||||
var file = try cwd.openFile(path, .{});
|
var file = try cwd.openFile(path, .{});
|
||||||
defer file.close();
|
defer file.close();
|
||||||
@ -91,6 +117,7 @@ pub fn main() !void {
|
|||||||
return error.ConfigFileInConfigFile;
|
return error.ConfigFileInConfigFile;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
try config.finalize();
|
||||||
std.log.info("config={}", .{config});
|
std.log.info("config={}", .{config});
|
||||||
|
|
||||||
// We want to log all our errors
|
// We want to log all our errors
|
||||||
@ -177,7 +204,9 @@ test {
|
|||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
_ = @import("config.zig");
|
_ = @import("config.zig");
|
||||||
|
_ = @import("homedir.zig");
|
||||||
_ = @import("passwd.zig");
|
_ = @import("passwd.zig");
|
||||||
|
_ = @import("xdg.zig");
|
||||||
_ = @import("cli_args.zig");
|
_ = @import("cli_args.zig");
|
||||||
_ = @import("lru.zig");
|
_ = @import("lru.zig");
|
||||||
}
|
}
|
||||||
|
66
src/xdg.zig
Normal file
66
src/xdg.zig
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
//! Implementation of the XDG Base Directory specification
|
||||||
|
//! (https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html)
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
const homedir = @import("homedir.zig");
|
||||||
|
|
||||||
|
pub const Options = struct {
|
||||||
|
/// Subdirectories to join to the base. This avoids extra allocations
|
||||||
|
/// when building up the directory. This is commonly the application.
|
||||||
|
subdir: ?[]const u8 = null,
|
||||||
|
|
||||||
|
/// The home directory for the user. If this is not set, we will attempt
|
||||||
|
/// to look it up which is an expensive process. By setting this, you can
|
||||||
|
/// avoid lookups.
|
||||||
|
home: ?[]const u8 = null,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Get the XDG user config directory. The returned value is allocated.
|
||||||
|
pub fn config(alloc: Allocator, opts: Options) ![]u8 {
|
||||||
|
if (std.os.getenv("XDG_CONFIG_HOME")) |env| {
|
||||||
|
// If we have a subdir, then we use the env as-is to avoid a copy.
|
||||||
|
if (opts.subdir) |subdir| {
|
||||||
|
return try std.fs.path.join(alloc, &[_][]const u8{
|
||||||
|
env,
|
||||||
|
subdir,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return try alloc.dupe(u8, env);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have a cached home dir, use that.
|
||||||
|
if (opts.home) |home| {
|
||||||
|
return try std.fs.path.join(alloc, &[_][]const u8{
|
||||||
|
home,
|
||||||
|
".config",
|
||||||
|
opts.subdir orelse "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get our home dir
|
||||||
|
var buf: [1024]u8 = undefined;
|
||||||
|
if (try homedir.home(&buf)) |home| {
|
||||||
|
return try std.fs.path.join(alloc, &[_][]const u8{
|
||||||
|
home,
|
||||||
|
".config",
|
||||||
|
opts.subdir orelse "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return error.NoHomeDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
{
|
||||||
|
const value = try config(alloc, .{});
|
||||||
|
defer alloc.free(value);
|
||||||
|
try testing.expect(value.len > 0);
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user