mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 08:46:08 +03:00
108 lines
3.6 KiB
Zig
108 lines
3.6 KiB
Zig
const std = @import("std");
|
|
const builtin = @import("builtin");
|
|
const assert = std.debug.assert;
|
|
const Allocator = std.mem.Allocator;
|
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
|
const internal_os = @import("../os/main.zig");
|
|
|
|
/// Open the configuration in the OS default editor according to the default
|
|
/// paths the main config file could be in.
|
|
///
|
|
/// On Linux, this will open the file at the XDG config path. This is the
|
|
/// only valid path for Linux so we don't need to check for other paths.
|
|
///
|
|
/// On macOS, both XDG and AppSupport paths are valid. Because Ghostty
|
|
/// prioritizes AppSupport over XDG, we will open AppSupport if it exists,
|
|
/// followed by XDG if it exists, and finally AppSupport if neither exist.
|
|
/// For the existence check, we also prefer non-empty files over empty
|
|
/// files.
|
|
pub fn open(alloc_gpa: Allocator) !void {
|
|
// Use an arena to make memory management easier in here.
|
|
var arena = ArenaAllocator.init(alloc_gpa);
|
|
defer arena.deinit();
|
|
const alloc = arena.allocator();
|
|
|
|
// Get the path we should open
|
|
const config_path = try configPath(alloc);
|
|
|
|
// Create config directory recursively.
|
|
if (std.fs.path.dirname(config_path)) |config_dir| {
|
|
try std.fs.cwd().makePath(config_dir);
|
|
}
|
|
|
|
// Try to create file and go on if it already exists
|
|
_ = std.fs.createFileAbsolute(
|
|
config_path,
|
|
.{ .exclusive = true },
|
|
) catch |err| {
|
|
switch (err) {
|
|
error.PathAlreadyExists => {},
|
|
else => return err,
|
|
}
|
|
};
|
|
|
|
try internal_os.open(alloc, .text, config_path);
|
|
}
|
|
|
|
/// Returns the config path to use for open for the current OS.
|
|
///
|
|
/// 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.
|
|
fn configPath(alloc_arena: Allocator) ![]const u8 {
|
|
const paths: []const []const u8 = try configPathCandidates(alloc_arena);
|
|
assert(paths.len > 0);
|
|
|
|
// Find the first path that exists and is non-empty. If no paths are
|
|
// non-empty but at least one exists, we will return the first path that
|
|
// exists.
|
|
var exists: ?[]const u8 = null;
|
|
for (paths) |path| {
|
|
const f = std.fs.openFileAbsolute(path, .{}) catch |err| {
|
|
switch (err) {
|
|
// File doesn't exist, continue.
|
|
error.BadPathName, error.FileNotFound => continue,
|
|
|
|
// Some other error, assume it exists and return it.
|
|
else => return err,
|
|
}
|
|
};
|
|
defer f.close();
|
|
|
|
// We expect stat to succeed because we just opened the file.
|
|
const stat = try f.stat();
|
|
|
|
// If the file is non-empty, return it.
|
|
if (stat.size > 0) return path;
|
|
|
|
// If the file is empty, remember it exists.
|
|
if (exists == null) exists = path;
|
|
}
|
|
|
|
// No paths are non-empty, return the first path that exists.
|
|
if (exists) |v| return v;
|
|
|
|
// No paths are non-empty or exist, return the first path.
|
|
return paths[0];
|
|
}
|
|
|
|
/// Returns a const list of possible paths the main config file could be
|
|
/// in for the current OS.
|
|
fn configPathCandidates(alloc_arena: Allocator) ![]const []const u8 {
|
|
var paths = try std.ArrayList([]const u8).initCapacity(alloc_arena, 2);
|
|
errdefer paths.deinit();
|
|
|
|
if (comptime builtin.os.tag == .macos) {
|
|
paths.appendAssumeCapacity(try internal_os.macos.appSupportDir(
|
|
alloc_arena,
|
|
"config",
|
|
));
|
|
}
|
|
|
|
paths.appendAssumeCapacity(try internal_os.xdg.config(
|
|
alloc_arena,
|
|
.{ .subdir = "ghostty/config" },
|
|
));
|
|
|
|
return paths.items;
|
|
}
|