aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/libghostty-vt/src/cli/explain_config.zig
blob: 6f03256d60cb66f17d87aed16f7766425e536089 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
const std = @import("std");
const args = @import("args.zig");
const Allocator = std.mem.Allocator;
const Action = @import("ghostty.zig").Action;
const help_strings = @import("help_strings");
const Config = @import("../config/Config.zig");
const ConfigKey = @import("../config/key.zig").Key;
const KeybindAction = @import("../input/Binding.zig").Action;
const Pager = @import("Pager.zig");
const global = @import("../global.zig");

pub const Options = struct {
    /// The config option to explain. For example:
    ///
    ///   ghostty +explain-config --option=font-size
    option: ?[]const u8 = null,

    /// The keybind action to explain. For example:
    ///
    ///   ghostty +explain-config --keybind=copy_to_clipboard
    keybind: ?[]const u8 = null,

    pub fn deinit(self: Options) void {
        _ = self;
    }

    /// Enables `-h` and `--help` to work.
    pub fn help(self: Options) !void {
        _ = self;
        return Action.help_error;
    }
};

/// The `explain-config` command prints the documentation for a single
/// Ghostty configuration option or keybind action.
///
/// Examples:
///
///   ghostty +explain-config font-size
///   ghostty +explain-config copy_to_clipboard
///   ghostty +explain-config --option=font-size
///   ghostty +explain-config --keybind=copy_to_clipboard
///
/// Flags:
///
///   * `--option`: The name of the configuration option to explain.
///   * `--keybind`: The name of the keybind action to explain.
///   * `--no-pager`: Disable automatic paging of output.
pub fn run(alloc: Allocator) !u8 {
    var option_name: ?[]const u8 = null;
    var keybind_name: ?[]const u8 = null;
    var positional: ?[]const u8 = null;
    var no_pager: bool = false;

    var iter = try args.argsIterator(alloc, global.args());
    defer iter.deinit();
    defer if (option_name) |s| alloc.free(s);
    defer if (keybind_name) |s| alloc.free(s);
    defer if (positional) |s| alloc.free(s);

    while (iter.next()) |arg| {
        if (std.mem.startsWith(u8, arg, "--option=")) {
            option_name = try alloc.dupe(u8, arg["--option=".len..]);
        } else if (std.mem.startsWith(u8, arg, "--keybind=")) {
            keybind_name = try alloc.dupe(u8, arg["--keybind=".len..]);
        } else if (std.mem.eql(u8, arg, "--no-pager")) {
            no_pager = true;
        } else if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) {
            return Action.help_error;
        } else if (!std.mem.startsWith(u8, arg, "-")) {
            positional = try alloc.dupe(u8, arg);
        }
    }

    // Resolve what to look up. Explicit flags go directly to their
    // respective lookup. A bare positional argument tries config
    // options first, then keybind actions as a fallback.
    const name = keybind_name orelse option_name orelse positional orelse {
        var stderr: std.Io.File = .stderr();
        var buffer: [4096]u8 = undefined;
        var stderr_writer = stderr.writer(global.io(), &buffer);
        try stderr_writer.interface.writeAll("Usage: ghostty +explain-config <option>\n");
        try stderr_writer.interface.writeAll("       ghostty +explain-config --option=<option>\n");
        try stderr_writer.interface.writeAll("       ghostty +explain-config --keybind=<action>\n");
        try stderr_writer.end();
        return 1;
    };

    const text = if (keybind_name != null)
        explainKeybind(name)
    else if (option_name != null)
        explainOption(name)
    else
        explainOption(name) orelse explainKeybind(name);

    var pager: Pager = if (!no_pager) .init() else .{};
    defer pager.deinit();
    var buffer: [4096]u8 = undefined;
    const writer = pager.writer(&buffer);

    if (text) |t| {
        try writer.writeAll(t);
        try writer.writeAll("\n");
    } else {
        try writer.writeAll("Unknown: '");
        try writer.writeAll(name);
        try writer.writeAll("'.\n");
        try writer.flush();
        return 1;
    }

    try writer.flush();
    return 0;
}

fn explainOption(name: []const u8) ?[]const u8 {
    const key = std.meta.stringToEnum(ConfigKey, name) orelse return null;
    return switch (key) {
        inline else => |tag| {
            const field_name = @tagName(tag);
            return if (@hasDecl(help_strings.Config, field_name))
                @field(help_strings.Config, field_name)
            else
                null;
        },
    };
}

fn explainKeybind(name: []const u8) ?[]const u8 {
    const tag = std.meta.stringToEnum(std.meta.Tag(KeybindAction), name) orelse return null;
    return switch (tag) {
        inline else => |t| {
            const field_name = @tagName(t);
            return if (@hasDecl(help_strings.KeybindAction, field_name))
                @field(help_strings.KeybindAction, field_name)
            else
                null;
        },
    };
}

test "explain" {
    // Config options
    try std.testing.expect(explainOption("font-size") != null);
    try std.testing.expect(explainOption("copy_to_clipboard") == null);
    try std.testing.expect(explainOption("unknown-option") == null);

    // Keybind actions
    try std.testing.expect(explainKeybind("copy_to_clipboard") != null);
    try std.testing.expect(explainKeybind("font-size") == null);
    try std.testing.expect(explainKeybind("unknown_keybind") == null);
}