mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
config: overhaul help string generation
This commit is contained in:
@ -8,6 +8,7 @@ const builtin = @import("builtin");
|
|||||||
const apprt = @import("../apprt.zig");
|
const apprt = @import("../apprt.zig");
|
||||||
const font = @import("../font/main.zig");
|
const font = @import("../font/main.zig");
|
||||||
const renderer = @import("../renderer.zig");
|
const renderer = @import("../renderer.zig");
|
||||||
|
const configpkg = @import("../config.zig");
|
||||||
const Command = @import("../Command.zig");
|
const Command = @import("../Command.zig");
|
||||||
const WasmTarget = @import("../os/wasm/target.zig").Target;
|
const WasmTarget = @import("../os/wasm/target.zig").Target;
|
||||||
|
|
||||||
@ -396,7 +397,7 @@ pub fn addOptions(self: *const Config, step: *std.Build.Step.Options) !void {
|
|||||||
.{self.version},
|
.{self.version},
|
||||||
));
|
));
|
||||||
step.addOption(
|
step.addOption(
|
||||||
ReleaseChannel,
|
configpkg.Config.ReleaseChannel,
|
||||||
"release_channel",
|
"release_channel",
|
||||||
channel: {
|
channel: {
|
||||||
const pre = self.version.pre orelse break :channel .stable;
|
const pre = self.version.pre orelse break :channel .stable;
|
||||||
@ -492,12 +493,3 @@ pub const ExeEntrypoint = enum {
|
|||||||
bench_grapheme_break,
|
bench_grapheme_break,
|
||||||
bench_page_init,
|
bench_page_init,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The release channel for the build.
|
|
||||||
pub const ReleaseChannel = enum {
|
|
||||||
/// Unstable builds on every commit.
|
|
||||||
tip,
|
|
||||||
|
|
||||||
/// Stable tagged releases.
|
|
||||||
stable,
|
|
||||||
};
|
|
||||||
|
@ -34,6 +34,7 @@ pub fn genConfig(writer: anytype, cli: bool) !void {
|
|||||||
if (cli) try writer.writeAll("--");
|
if (cli) try writer.writeAll("--");
|
||||||
try writer.writeAll(field.name);
|
try writer.writeAll(field.name);
|
||||||
try writer.writeAll("`**\n\n");
|
try writer.writeAll("`**\n\n");
|
||||||
|
|
||||||
if (@hasDecl(help_strings.Config, field.name)) {
|
if (@hasDecl(help_strings.Config, field.name)) {
|
||||||
var iter = std.mem.splitScalar(u8, @field(help_strings.Config, field.name), '\n');
|
var iter = std.mem.splitScalar(u8, @field(help_strings.Config, field.name), '\n');
|
||||||
var first = true;
|
var first = true;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Config = @import("../../config/Config.zig");
|
const Config = @import("../../config/Config.zig");
|
||||||
const help_strings = @import("help_strings");
|
const help_strings = @import("help_strings");
|
||||||
|
const formatter = @import("../../config/formatter.zig");
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
const output = std.io.getStdOut().writer();
|
const output = std.io.getStdOut().writer();
|
||||||
@ -77,10 +78,16 @@ pub fn genConfig(writer: anytype) !void {
|
|||||||
callout_note,
|
callout_note,
|
||||||
callout_warning,
|
callout_warning,
|
||||||
} = null;
|
} = null;
|
||||||
|
var block_indent: usize = 0;
|
||||||
|
|
||||||
|
while (iter.next()) |line| {
|
||||||
|
var indent: usize = 0;
|
||||||
|
while (indent < line.len and line[indent] == ' ') : (indent += 1) {}
|
||||||
|
const s = line[indent..];
|
||||||
|
|
||||||
while (iter.next()) |s| {
|
|
||||||
// Empty line resets our block
|
// Empty line resets our block
|
||||||
if (std.mem.eql(u8, s, "")) {
|
if (std.mem.eql(u8, s, "")) {
|
||||||
|
try writer.writeByteNTimes(' ', block_indent);
|
||||||
try endBlock(writer, block);
|
try endBlock(writer, block);
|
||||||
block = null;
|
block = null;
|
||||||
|
|
||||||
@ -90,30 +97,35 @@ pub fn genConfig(writer: anytype) !void {
|
|||||||
|
|
||||||
// If we don't have a block figure out our type.
|
// If we don't have a block figure out our type.
|
||||||
const first: bool = block == null;
|
const first: bool = block == null;
|
||||||
if (block == null) {
|
if (block == null) block: {
|
||||||
if (std.mem.startsWith(u8, s, " ")) {
|
if (indent == 4) {
|
||||||
block = .code;
|
block = .code;
|
||||||
try writer.writeAll("```\n");
|
try writer.writeAll("```\n");
|
||||||
} else if (std.ascii.startsWithIgnoreCase(s, "note:")) {
|
break :block;
|
||||||
|
}
|
||||||
|
|
||||||
|
try writer.writeByteNTimes(' ', indent);
|
||||||
|
block_indent = indent;
|
||||||
|
|
||||||
|
if (std.ascii.startsWithIgnoreCase(s, "note:")) {
|
||||||
block = .callout_note;
|
block = .callout_note;
|
||||||
try writer.writeAll("<Note>\n");
|
try writer.writeAll("<Note>\n");
|
||||||
|
try writer.writeByteNTimes(' ', indent);
|
||||||
} else if (std.ascii.startsWithIgnoreCase(s, "warning:")) {
|
} else if (std.ascii.startsWithIgnoreCase(s, "warning:")) {
|
||||||
block = .callout_warning;
|
block = .callout_warning;
|
||||||
try writer.writeAll("<Warning>\n");
|
try writer.writeAll("<Warning>\n");
|
||||||
|
try writer.writeByteNTimes(' ', indent);
|
||||||
} else {
|
} else {
|
||||||
block = .text;
|
block = .text;
|
||||||
}
|
}
|
||||||
|
} else if (block != .code) {
|
||||||
|
try writer.writeByteNTimes(' ', indent);
|
||||||
}
|
}
|
||||||
|
|
||||||
try writer.writeAll(switch (block.?) {
|
try writer.writeAll(switch (block.?) {
|
||||||
.text => s,
|
.text, .code => s,
|
||||||
.callout_note => if (first) s["note:".len..] else s,
|
.callout_note => std.mem.trim(u8, if (first) s["note:".len..] else s, " "),
|
||||||
.callout_warning => if (first) s["warning:".len..] else s,
|
.callout_warning => std.mem.trim(u8, if (first) s["warning:".len..] else s, " "),
|
||||||
|
|
||||||
.code => if (std.mem.startsWith(u8, s, " "))
|
|
||||||
s[4..]
|
|
||||||
else
|
|
||||||
s,
|
|
||||||
});
|
});
|
||||||
try writer.writeAll("\n");
|
try writer.writeAll("\n");
|
||||||
}
|
}
|
||||||
|
@ -11,15 +11,14 @@ const font = @import("font/main.zig");
|
|||||||
const rendererpkg = @import("renderer.zig");
|
const rendererpkg = @import("renderer.zig");
|
||||||
const WasmTarget = @import("os/wasm/target.zig").Target;
|
const WasmTarget = @import("os/wasm/target.zig").Target;
|
||||||
const BuildConfig = @import("build/Config.zig");
|
const BuildConfig = @import("build/Config.zig");
|
||||||
|
const Config = @import("config/Config.zig");
|
||||||
pub const ReleaseChannel = BuildConfig.ReleaseChannel;
|
|
||||||
|
|
||||||
/// The semantic version of this build.
|
/// The semantic version of this build.
|
||||||
pub const version = options.app_version;
|
pub const version = options.app_version;
|
||||||
pub const version_string = options.app_version_string;
|
pub const version_string = options.app_version_string;
|
||||||
|
|
||||||
/// The release channel for this build.
|
/// The release channel for this build.
|
||||||
pub const release_channel = std.meta.stringToEnum(ReleaseChannel, @tagName(options.release_channel)).?;
|
pub const release_channel = std.meta.stringToEnum(Config.ReleaseChannel, @tagName(options.release_channel)).?;
|
||||||
|
|
||||||
/// The optimization mode as a string.
|
/// The optimization mode as a string.
|
||||||
pub const mode_string = mode: {
|
pub const mode_string = mode: {
|
||||||
|
@ -121,12 +121,12 @@ pub const Action = enum {
|
|||||||
// for all commands by just changing this one place.
|
// for all commands by just changing this one place.
|
||||||
|
|
||||||
if (std.mem.eql(u8, field.name, @tagName(self))) {
|
if (std.mem.eql(u8, field.name, @tagName(self))) {
|
||||||
const stdout = std.io.getStdOut().writer();
|
// const stdout = std.io.getStdOut().writer();
|
||||||
const text = @field(help_strings.Action, field.name) ++ "\n";
|
// const text = @field(help_strings.Action, field.name) ++ "\n";
|
||||||
stdout.writeAll(text) catch |write_err| {
|
// stdout.writeAll(text) catch |write_err| {
|
||||||
std.log.warn("failed to write help text: {}\n", .{write_err});
|
// std.log.warn("failed to write help text: {}\n", .{write_err});
|
||||||
break :err 1;
|
// break :err 1;
|
||||||
};
|
// };
|
||||||
|
|
||||||
break :err 0;
|
break :err 0;
|
||||||
}
|
}
|
||||||
|
@ -41,10 +41,10 @@ pub fn run(alloc: Allocator) !u8 {
|
|||||||
try stdout.print("{s}", .{field.name});
|
try stdout.print("{s}", .{field.name});
|
||||||
if (opts.docs) {
|
if (opts.docs) {
|
||||||
try stdout.print(":\n", .{});
|
try stdout.print(":\n", .{});
|
||||||
var iter = std.mem.splitScalar(u8, std.mem.trimRight(u8, @field(help_strings.KeybindAction, field.name), &std.ascii.whitespace), '\n');
|
// var iter = std.mem.splitScalar(u8, std.mem.trimRight(u8, @field(help_strings.KeybindAction, field.name), &std.ascii.whitespace), '\n');
|
||||||
while (iter.next()) |line| {
|
// while (iter.next()) |line| {
|
||||||
try stdout.print(" {s}\n", .{line});
|
// try stdout.print(" {s}\n", .{line});
|
||||||
}
|
// }
|
||||||
} else {
|
} else {
|
||||||
try stdout.print("\n", .{});
|
try stdout.print("\n", .{});
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,7 @@
|
|||||||
const Metrics = @This();
|
const Metrics = @This();
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const Config = @import("../config/Config.zig");
|
||||||
|
|
||||||
/// Recommended cell width and height for a monospace grid using this font.
|
/// Recommended cell width and height for a monospace grid using this font.
|
||||||
cell_width: u32,
|
cell_width: u32,
|
||||||
@ -257,130 +258,7 @@ fn clamp(self: *Metrics) void {
|
|||||||
/// little space as possible.
|
/// little space as possible.
|
||||||
pub const ModifierSet = std.AutoHashMapUnmanaged(Key, Modifier);
|
pub const ModifierSet = std.AutoHashMapUnmanaged(Key, Modifier);
|
||||||
|
|
||||||
/// A modifier to apply to a metrics value. The modifier value represents
|
pub const Modifier = Config.MetricModifier;
|
||||||
/// a delta, so percent is a percentage to change, not a percentage of.
|
|
||||||
/// For example, "20%" is 20% larger, not 20% of the value. Likewise,
|
|
||||||
/// an absolute value of "20" is 20 larger, not literally 20.
|
|
||||||
pub const Modifier = union(enum) {
|
|
||||||
percent: f64,
|
|
||||||
absolute: i32,
|
|
||||||
|
|
||||||
/// Parses the modifier value. If the value ends in "%" it is assumed
|
|
||||||
/// to be a percent, otherwise the value is parsed as an integer.
|
|
||||||
pub fn parse(input: []const u8) !Modifier {
|
|
||||||
if (input.len == 0) return error.InvalidFormat;
|
|
||||||
|
|
||||||
if (input[input.len - 1] == '%') {
|
|
||||||
var percent = std.fmt.parseFloat(
|
|
||||||
f64,
|
|
||||||
input[0 .. input.len - 1],
|
|
||||||
) catch return error.InvalidFormat;
|
|
||||||
percent /= 100;
|
|
||||||
|
|
||||||
if (percent <= -1) return .{ .percent = 0 };
|
|
||||||
if (percent < 0) return .{ .percent = 1 + percent };
|
|
||||||
return .{ .percent = 1 + percent };
|
|
||||||
}
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.absolute = std.fmt.parseInt(i32, input, 10) catch
|
|
||||||
return error.InvalidFormat,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// So it works with the config framework.
|
|
||||||
pub fn parseCLI(input: ?[]const u8) !Modifier {
|
|
||||||
return try parse(input orelse return error.ValueRequired);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Used by config formatter
|
|
||||||
pub fn formatEntry(self: Modifier, formatter: anytype) !void {
|
|
||||||
var buf: [1024]u8 = undefined;
|
|
||||||
switch (self) {
|
|
||||||
.percent => |v| {
|
|
||||||
try formatter.formatEntry(
|
|
||||||
[]const u8,
|
|
||||||
std.fmt.bufPrint(
|
|
||||||
&buf,
|
|
||||||
"{d}%",
|
|
||||||
.{(v - 1) * 100},
|
|
||||||
) catch return error.OutOfMemory,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
.absolute => |v| {
|
|
||||||
try formatter.formatEntry(
|
|
||||||
[]const u8,
|
|
||||||
std.fmt.bufPrint(
|
|
||||||
&buf,
|
|
||||||
"{d}",
|
|
||||||
.{v},
|
|
||||||
) catch return error.OutOfMemory,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Apply a modifier to a numeric value.
|
|
||||||
pub fn apply(self: Modifier, v: anytype) @TypeOf(v) {
|
|
||||||
const T = @TypeOf(v);
|
|
||||||
const signed = @typeInfo(T).Int.signedness == .signed;
|
|
||||||
return switch (self) {
|
|
||||||
.percent => |p| percent: {
|
|
||||||
const p_clamped: f64 = @max(0, p);
|
|
||||||
const v_f64: f64 = @floatFromInt(v);
|
|
||||||
const applied_f64: f64 = @round(v_f64 * p_clamped);
|
|
||||||
const applied_T: T = @intFromFloat(applied_f64);
|
|
||||||
break :percent applied_T;
|
|
||||||
},
|
|
||||||
|
|
||||||
.absolute => |abs| absolute: {
|
|
||||||
const v_i64: i64 = @intCast(v);
|
|
||||||
const abs_i64: i64 = @intCast(abs);
|
|
||||||
const applied_i64: i64 = v_i64 +| abs_i64;
|
|
||||||
const clamped_i64: i64 = if (signed) applied_i64 else @max(0, applied_i64);
|
|
||||||
const applied_T: T = std.math.cast(T, clamped_i64) orelse
|
|
||||||
std.math.maxInt(T) * @as(T, @intCast(std.math.sign(clamped_i64)));
|
|
||||||
break :absolute applied_T;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Hash using the hasher.
|
|
||||||
pub fn hash(self: Modifier, hasher: anytype) void {
|
|
||||||
const autoHash = std.hash.autoHash;
|
|
||||||
autoHash(hasher, std.meta.activeTag(self));
|
|
||||||
switch (self) {
|
|
||||||
// floats can't be hashed directly so we bitcast to i64.
|
|
||||||
// for the purpose of what we're trying to do this seems
|
|
||||||
// good enough but I would prefer value hashing.
|
|
||||||
.percent => |v| autoHash(hasher, @as(i64, @bitCast(v))),
|
|
||||||
.absolute => |v| autoHash(hasher, v),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test "formatConfig percent" {
|
|
||||||
const configpkg = @import("../config.zig");
|
|
||||||
const testing = std.testing;
|
|
||||||
var buf = std.ArrayList(u8).init(testing.allocator);
|
|
||||||
defer buf.deinit();
|
|
||||||
|
|
||||||
const p = try parseCLI("24%");
|
|
||||||
try p.formatEntry(configpkg.entryFormatter("a", buf.writer()));
|
|
||||||
try std.testing.expectEqualSlices(u8, "a = 24%\n", buf.items);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "formatConfig absolute" {
|
|
||||||
const configpkg = @import("../config.zig");
|
|
||||||
const testing = std.testing;
|
|
||||||
var buf = std.ArrayList(u8).init(testing.allocator);
|
|
||||||
defer buf.deinit();
|
|
||||||
|
|
||||||
const p = try parseCLI("-30");
|
|
||||||
try p.formatEntry(configpkg.entryFormatter("a", buf.writer()));
|
|
||||||
try std.testing.expectEqualSlices(u8, "a = -30\n", buf.items);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Key is an enum of all the available metrics keys.
|
/// Key is an enum of all the available metrics keys.
|
||||||
pub const Key = key: {
|
pub const Key = key: {
|
||||||
|
390
src/helpgen.zig
390
src/helpgen.zig
@ -27,46 +27,15 @@ fn genConfig(alloc: std.mem.Allocator, writer: anytype) !void {
|
|||||||
var ast = try std.zig.Ast.parse(alloc, @embedFile("config/Config.zig"), .zig);
|
var ast = try std.zig.Ast.parse(alloc, @embedFile("config/Config.zig"), .zig);
|
||||||
defer ast.deinit(alloc);
|
defer ast.deinit(alloc);
|
||||||
|
|
||||||
try writer.writeAll(
|
try genStringsStruct(
|
||||||
\\/// Configuration help
|
alloc,
|
||||||
\\pub const Config = struct {
|
writer,
|
||||||
\\
|
ast,
|
||||||
\\
|
"Config",
|
||||||
|
0,
|
||||||
|
ast.rootDecls(),
|
||||||
|
0,
|
||||||
);
|
);
|
||||||
|
|
||||||
inline for (@typeInfo(Config).Struct.fields) |field| {
|
|
||||||
if (field.name[0] == '_') continue;
|
|
||||||
try genConfigField(alloc, writer, ast, field.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
try writer.writeAll("};\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn genConfigField(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
writer: anytype,
|
|
||||||
ast: std.zig.Ast,
|
|
||||||
comptime field: []const u8,
|
|
||||||
) !void {
|
|
||||||
const tokens = ast.tokens.items(.tag);
|
|
||||||
for (tokens, 0..) |token, i| {
|
|
||||||
// We only care about identifiers that are preceded by doc comments.
|
|
||||||
if (token != .identifier) continue;
|
|
||||||
if (tokens[i - 1] != .doc_comment) continue;
|
|
||||||
|
|
||||||
// Identifier may have @"" so we strip that.
|
|
||||||
const name = ast.tokenSlice(@intCast(i));
|
|
||||||
const key = if (name[0] == '@') name[2 .. name.len - 1] else name;
|
|
||||||
if (!std.mem.eql(u8, key, field)) continue;
|
|
||||||
|
|
||||||
const comment = try extractDocComments(alloc, ast, @intCast(i - 1), tokens);
|
|
||||||
try writer.writeAll("pub const ");
|
|
||||||
try writer.writeAll(name);
|
|
||||||
try writer.writeAll(": [:0]const u8 = \n");
|
|
||||||
try writer.writeAll(comment);
|
|
||||||
try writer.writeAll("\n");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn genActions(alloc: std.mem.Allocator, writer: anytype) !void {
|
fn genActions(alloc: std.mem.Allocator, writer: anytype) !void {
|
||||||
@ -86,6 +55,7 @@ fn genActions(alloc: std.mem.Allocator, writer: anytype) !void {
|
|||||||
|
|
||||||
var ast = try std.zig.Ast.parse(alloc, @embedFile(action_file), .zig);
|
var ast = try std.zig.Ast.parse(alloc, @embedFile(action_file), .zig);
|
||||||
defer ast.deinit(alloc);
|
defer ast.deinit(alloc);
|
||||||
|
|
||||||
const tokens: []std.zig.Token.Tag = ast.tokens.items(.tag);
|
const tokens: []std.zig.Token.Tag = ast.tokens.items(.tag);
|
||||||
|
|
||||||
for (tokens, 0..) |token, i| {
|
for (tokens, 0..) |token, i| {
|
||||||
@ -102,12 +72,18 @@ fn genActions(alloc: std.mem.Allocator, writer: anytype) !void {
|
|||||||
std.process.exit(1);
|
std.process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const comment = try extractDocComments(alloc, ast, @intCast(i - 2), tokens);
|
const comment = try extractDocComments(
|
||||||
|
alloc,
|
||||||
|
ast,
|
||||||
|
@intCast(i - 2),
|
||||||
|
0,
|
||||||
|
) orelse continue;
|
||||||
|
|
||||||
try writer.writeAll("pub const @\"");
|
try writer.writeAll("pub const @\"");
|
||||||
try writer.writeAll(field.name);
|
try writer.writeAll(field.name);
|
||||||
try writer.writeAll("\" = \n");
|
try writer.writeAll("\" = \n");
|
||||||
try writer.writeAll(comment);
|
try writer.writeAll(comment);
|
||||||
try writer.writeAll("\n\n");
|
try writer.writeAll(";\n\n");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -120,26 +96,337 @@ fn genKeybindActions(alloc: std.mem.Allocator, writer: anytype) !void {
|
|||||||
defer ast.deinit(alloc);
|
defer ast.deinit(alloc);
|
||||||
|
|
||||||
try writer.writeAll(
|
try writer.writeAll(
|
||||||
\\/// keybind actions help
|
|
||||||
\\pub const KeybindAction = struct {
|
\\pub const KeybindAction = struct {
|
||||||
\\
|
\\
|
||||||
|
\\};
|
||||||
|
);
|
||||||
|
|
||||||
|
// for (ast.rootDecls()) |decl| {
|
||||||
|
// if (ast.fullVarDecl(decl)) |var_decl| {
|
||||||
|
// var buf: [2]std.zig.Ast.TokenIndex = undefined;
|
||||||
|
// const decl_container = ast.fullContainerDecl(&buf, var_decl.ast.init_node) orelse continue;
|
||||||
|
// const name = ast.tokenSlice(var_decl.ast.mut_token + 1);
|
||||||
|
|
||||||
|
// if (!std.mem.eql(u8, name, "Action")) continue;
|
||||||
|
|
||||||
|
// _ = decl_container;
|
||||||
|
// _ = writer;
|
||||||
|
|
||||||
|
// try genStringsStruct(
|
||||||
|
// alloc,
|
||||||
|
// writer,
|
||||||
|
// ast,
|
||||||
|
// "KeybindAction",
|
||||||
|
// decl_container.ast.members,
|
||||||
|
// var_decl.firstToken() - 1,
|
||||||
|
// );
|
||||||
|
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn genStringsStruct(
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
writer: anytype,
|
||||||
|
ast: std.zig.Ast,
|
||||||
|
name: []const u8,
|
||||||
|
main_token: std.zig.Ast.TokenIndex,
|
||||||
|
members: []const std.zig.Ast.Node.Index,
|
||||||
|
doc_comment_token: ?std.zig.Ast.TokenIndex,
|
||||||
|
) !void {
|
||||||
|
var fields: std.ArrayListUnmanaged(std.zig.Ast.full.ContainerField) = .{};
|
||||||
|
defer fields.deinit(alloc);
|
||||||
|
|
||||||
|
var decls: std.ArrayListUnmanaged(std.zig.Ast.full.VarDecl) = .{};
|
||||||
|
defer decls.deinit(alloc);
|
||||||
|
|
||||||
|
try writer.print("pub const {s} = struct {{\n", .{name});
|
||||||
|
|
||||||
|
for (members) |member| {
|
||||||
|
if (ast.fullContainerField(member)) |field| {
|
||||||
|
try fields.append(alloc, field);
|
||||||
|
} else if (ast.fullVarDecl(member)) |var_decl| {
|
||||||
|
// Is it defining a subtype?
|
||||||
|
try decls.append(alloc, var_decl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (fields.items) |field| {
|
||||||
|
try genConfigField(alloc, writer, ast, field, decls.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (decls.items) |var_decl| {
|
||||||
|
var buf: [2]std.zig.Ast.TokenIndex = undefined;
|
||||||
|
const decl_container = ast.fullContainerDecl(&buf, var_decl.ast.init_node) orelse continue;
|
||||||
|
|
||||||
|
try genStringsStruct(
|
||||||
|
alloc,
|
||||||
|
writer,
|
||||||
|
ast,
|
||||||
|
ast.tokenSlice(var_decl.ast.mut_token + 1),
|
||||||
|
decl_container.ast.main_token,
|
||||||
|
decl_container.ast.members,
|
||||||
|
var_decl.firstToken() - 1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try writer.writeAll(
|
||||||
|
\\pub const @"DOC-COMMENT": []const u8 =
|
||||||
\\
|
\\
|
||||||
);
|
);
|
||||||
|
|
||||||
inline for (@typeInfo(KeybindAction).Union.fields) |field| {
|
if (doc_comment_token) |token| {
|
||||||
if (field.name[0] == '_') continue;
|
if (try extractDocComments(
|
||||||
try genConfigField(alloc, writer, ast, field.name);
|
alloc,
|
||||||
|
ast,
|
||||||
|
@intCast(token),
|
||||||
|
0,
|
||||||
|
)) |comment| {
|
||||||
|
try writer.writeAll(comment);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try writer.writeAll("};\n");
|
try genValidValues(
|
||||||
|
alloc,
|
||||||
|
writer,
|
||||||
|
ast,
|
||||||
|
main_token,
|
||||||
|
members,
|
||||||
|
);
|
||||||
|
|
||||||
|
try writer.writeAll(
|
||||||
|
\\ \\
|
||||||
|
\\;
|
||||||
|
\\};
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn genValidValues(
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
writer: anytype,
|
||||||
|
ast: std.zig.Ast,
|
||||||
|
main_token: std.zig.Ast.TokenIndex,
|
||||||
|
members: []const std.zig.Ast.Node.Index,
|
||||||
|
) !void {
|
||||||
|
const token_tags = ast.tokens.items(.tag);
|
||||||
|
|
||||||
|
const ContainerType = enum {
|
||||||
|
@"enum",
|
||||||
|
@"union",
|
||||||
|
bitfield,
|
||||||
|
};
|
||||||
|
|
||||||
|
const container_type: ContainerType = switch (token_tags[main_token]) {
|
||||||
|
.keyword_enum => .@"enum",
|
||||||
|
.keyword_union => .@"union",
|
||||||
|
.keyword_struct => switch (token_tags[main_token - 1]) {
|
||||||
|
.keyword_packed => .bitfield,
|
||||||
|
else => return,
|
||||||
|
},
|
||||||
|
else => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
try writer.writeAll(
|
||||||
|
\\\\
|
||||||
|
\\\\Valid values:
|
||||||
|
\\\\
|
||||||
|
\\
|
||||||
|
);
|
||||||
|
|
||||||
|
for (members) |member| {
|
||||||
|
const field = ast.fullContainerField(member) orelse continue;
|
||||||
|
var field_name = ast.tokenSlice(field.ast.main_token);
|
||||||
|
|
||||||
|
if (std.mem.startsWith(u8, field_name, "@\"")) {
|
||||||
|
field_name = field_name[2..][0 .. field_name.len - 3];
|
||||||
|
}
|
||||||
|
|
||||||
|
try writer.writeAll(
|
||||||
|
\\\\ -
|
||||||
|
);
|
||||||
|
|
||||||
|
switch (container_type) {
|
||||||
|
.@"enum" => try writer.print(" `{s}`\n", .{field_name}),
|
||||||
|
.@"union" => {
|
||||||
|
const field_type = ast.getNodeSource(field.ast.type_expr);
|
||||||
|
|
||||||
|
// Only generate the field name if the field is "enum-variant-like":
|
||||||
|
// type is void or nonexistent.
|
||||||
|
if (field.ast.main_token == ast.firstToken(field.ast.type_expr) or
|
||||||
|
std.mem.eql(u8, field_type, "void"))
|
||||||
|
{
|
||||||
|
try writer.print(" `{s}`\n", .{field_name});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.bitfield => {
|
||||||
|
const default_value = ast.tokenSlice(ast.firstToken(field.ast.value_expr));
|
||||||
|
const is_default = std.mem.eql(u8, default_value, "true");
|
||||||
|
|
||||||
|
if (is_default) {
|
||||||
|
try writer.print(" [x] `{s}` (Enabled by default)\n", .{field_name});
|
||||||
|
} else {
|
||||||
|
try writer.print(" [ ] `{s}`\n", .{field_name});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
try writer.writeAll(
|
||||||
|
\\\\
|
||||||
|
\\
|
||||||
|
);
|
||||||
|
|
||||||
|
if (try extractDocComments(
|
||||||
|
alloc,
|
||||||
|
ast,
|
||||||
|
field.firstToken() - 1,
|
||||||
|
3, // 4 indents would be an indented code block
|
||||||
|
)) |comment| {
|
||||||
|
try writer.writeAll(comment);
|
||||||
|
try writer.writeAll(
|
||||||
|
\\\\
|
||||||
|
\\
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn genConfigField(
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
writer: anytype,
|
||||||
|
ast: std.zig.Ast,
|
||||||
|
field: std.zig.Ast.full.ContainerField,
|
||||||
|
decls: []std.zig.Ast.full.VarDecl,
|
||||||
|
) !void {
|
||||||
|
const name = ast.tokenSlice(field.ast.main_token);
|
||||||
|
if (name[0] == '_') return;
|
||||||
|
|
||||||
|
// Escape special identifiers that are valid as enum variants but not as field names
|
||||||
|
const special_identifiers = &[_][]const u8{ "true", "false", "null" };
|
||||||
|
|
||||||
|
const is_special = for (special_identifiers) |special| {
|
||||||
|
if (std.mem.eql(u8, name, special)) break true;
|
||||||
|
} else false;
|
||||||
|
|
||||||
|
const comment = try extractDocComments(
|
||||||
|
alloc,
|
||||||
|
ast,
|
||||||
|
field.firstToken() - 1,
|
||||||
|
0,
|
||||||
|
) orelse return;
|
||||||
|
|
||||||
|
try writer.writeAll("pub const ");
|
||||||
|
if (is_special) try writer.writeAll("@\"");
|
||||||
|
try writer.writeAll(name);
|
||||||
|
if (is_special) try writer.writeAll("\"");
|
||||||
|
try writer.writeAll(": [:0]const u8 = \n");
|
||||||
|
try writer.writeAll(comment);
|
||||||
|
|
||||||
|
const type_name = ast.tokenSlice(ast.lastToken(field.ast.type_expr));
|
||||||
|
|
||||||
|
for (decls) |decl| {
|
||||||
|
if (std.mem.eql(u8, type_name, ast.tokenSlice(decl.ast.mut_token + 1))) {
|
||||||
|
try writer.writeAll(
|
||||||
|
\\\\
|
||||||
|
\\\\
|
||||||
|
\\++
|
||||||
|
\\
|
||||||
|
);
|
||||||
|
try writer.writeAll(type_name);
|
||||||
|
try writer.writeAll(
|
||||||
|
\\.@"DOC-COMMENT"
|
||||||
|
\\
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try genDefaultValue(writer, ast, field);
|
||||||
|
|
||||||
|
try writer.writeAll(";\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn genDefaultValue(
|
||||||
|
writer: anytype,
|
||||||
|
ast: std.zig.Ast,
|
||||||
|
field: std.zig.Ast.full.ContainerField,
|
||||||
|
) !void {
|
||||||
|
const value = ast.nodes.get(field.ast.value_expr);
|
||||||
|
|
||||||
|
switch (value.tag) {
|
||||||
|
.number_literal, .string_literal => {
|
||||||
|
try writer.writeAll(
|
||||||
|
\\++
|
||||||
|
\\\\
|
||||||
|
\\\\Defaults to `{s}`.
|
||||||
|
, ast.getNodeSource(field.ast.value_expr));
|
||||||
|
},
|
||||||
|
.identifier, .number_literal, .string_literal, .enum_literal => {
|
||||||
|
try writer.writeAll(
|
||||||
|
\\++
|
||||||
|
\\\\
|
||||||
|
\\\\
|
||||||
|
\\\\
|
||||||
|
);
|
||||||
|
|
||||||
|
const default = switch (value.tag) {
|
||||||
|
.enum_literal, .identifier => id: {
|
||||||
|
// Escape @"blah"
|
||||||
|
const slice = ast.tokenSlice(value.main_token);
|
||||||
|
break :id if (std.mem.startsWith(u8, slice, "@\""))
|
||||||
|
slice[2..][0 .. slice.len - 3]
|
||||||
|
else
|
||||||
|
slice;
|
||||||
|
},
|
||||||
|
.number_literal => ast.tokenSlice(value.main_token),
|
||||||
|
// We really don't know. Guess.
|
||||||
|
else => ast.getNodeSource(field.ast.value_expr),
|
||||||
|
};
|
||||||
|
|
||||||
|
// var default = ast.getNodeSource(field.ast.value_expr);
|
||||||
|
// if (default[0] == '.') {
|
||||||
|
// default = default[1..];
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (std.mem.eql(u8, default, "null")) {
|
||||||
|
try writer.writeAll("Unset by default.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const default_type_node = ast.nodes.get(field.ast.type_expr);
|
||||||
|
// ?bool is still semantically boolean
|
||||||
|
const default_type = if (default_type_node.tag == .optional_type)
|
||||||
|
ast.getNodeSource(default_type_node.data.lhs)
|
||||||
|
else
|
||||||
|
ast.getNodeSource(field.ast.type_expr);
|
||||||
|
|
||||||
|
// There are some enums/tagged unions with variants called `true`
|
||||||
|
// or `false`, and it's not accurate to call them enabled or
|
||||||
|
// disabled in some circumstances.
|
||||||
|
// Thus we only consider booleans here.
|
||||||
|
if (std.mem.eql(u8, default_type, "bool")) {
|
||||||
|
if (std.mem.eql(u8, default, "true")) {
|
||||||
|
try writer.writeAll("Enabled by default.\n");
|
||||||
|
} else if (std.mem.eql(u8, default, "false")) {
|
||||||
|
try writer.writeAll("Disabled by default.\n");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try writer.print("Defaults to `{s}`.\n", .{default});
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extractDocComments(
|
fn extractDocComments(
|
||||||
alloc: std.mem.Allocator,
|
alloc: std.mem.Allocator,
|
||||||
ast: std.zig.Ast,
|
ast: std.zig.Ast,
|
||||||
index: std.zig.Ast.TokenIndex,
|
index: std.zig.Ast.TokenIndex,
|
||||||
tokens: []std.zig.Token.Tag,
|
comptime indent: usize,
|
||||||
) ![]const u8 {
|
) !?[]const u8 {
|
||||||
|
if (index == 0) return null;
|
||||||
|
const tokens = ast.tokens.items(.tag);
|
||||||
|
|
||||||
// Find the first index of the doc comments. The doc comments are
|
// Find the first index of the doc comments. The doc comments are
|
||||||
// always stacked on top of each other so we can just go backwards.
|
// always stacked on top of each other so we can just go backwards.
|
||||||
const start_idx: usize = start_idx: for (0..index) |i| {
|
const start_idx: usize = start_idx: for (0..index) |i| {
|
||||||
@ -161,14 +448,15 @@ fn extractDocComments(
|
|||||||
var buffer = std.ArrayList(u8).init(alloc);
|
var buffer = std.ArrayList(u8).init(alloc);
|
||||||
const writer = buffer.writer();
|
const writer = buffer.writer();
|
||||||
const prefix = findCommonPrefix(lines);
|
const prefix = findCommonPrefix(lines);
|
||||||
|
|
||||||
|
if (lines.items.len == 0) return null;
|
||||||
for (lines.items) |line| {
|
for (lines.items) |line| {
|
||||||
try writer.writeAll(" \\\\");
|
try writer.writeAll(" \\\\" ++ " " ** indent);
|
||||||
try writer.writeAll(line[@min(prefix, line.len)..]);
|
try writer.writeAll(line[@min(prefix, line.len)..]);
|
||||||
try writer.writeAll("\n");
|
try writer.writeAll("\n");
|
||||||
}
|
}
|
||||||
try writer.writeAll(";\n");
|
|
||||||
|
|
||||||
return buffer.toOwnedSlice();
|
return try buffer.toOwnedSlice();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn findCommonPrefix(lines: std.ArrayList([]const u8)) usize {
|
fn findCommonPrefix(lines: std.ArrayList([]const u8)) usize {
|
||||||
|
Reference in New Issue
Block a user