mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-10-06 18:06:33 +00:00
config: richer diagnostics for errors
Rather than storing a list of errors we now store a list of "diagnostics." Each diagnostic has a richer set of structured information, including a message, a key, the location where it occurred. This lets us show more detailed messages, more human friendly messages, and also let's us filter by key or location. We don't take advantage of all of this capability in this initial commit, but we do use every field for something.
This commit is contained in:
124
src/cli/diagnostics.zig
Normal file
124
src/cli/diagnostics.zig
Normal file
@@ -0,0 +1,124 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const assert = std.debug.assert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const build_config = @import("../build_config.zig");
|
||||
|
||||
/// A diagnostic message from parsing. This is used to provide additional
|
||||
/// human-friendly warnings and errors about the parsed data.
|
||||
///
|
||||
/// All of the memory for the diagnostic is allocated from the arena
|
||||
/// associated with the config structure. If an arena isn't available
|
||||
/// then diagnostics are not supported.
|
||||
pub const Diagnostic = struct {
|
||||
location: Location = .none,
|
||||
key: [:0]const u8 = "",
|
||||
message: [:0]const u8,
|
||||
|
||||
/// The possible locations for a diagnostic message. This is used
|
||||
/// to provide context for the message.
|
||||
pub const Location = union(enum) {
|
||||
none,
|
||||
cli: usize,
|
||||
file: struct {
|
||||
path: []const u8,
|
||||
line: usize,
|
||||
},
|
||||
|
||||
pub fn fromIter(iter: anytype) Location {
|
||||
const Iter = t: {
|
||||
const T = @TypeOf(iter);
|
||||
break :t switch (@typeInfo(T)) {
|
||||
.Pointer => |v| v.child,
|
||||
.Struct => T,
|
||||
else => return .none,
|
||||
};
|
||||
};
|
||||
|
||||
if (!@hasDecl(Iter, "location")) return .none;
|
||||
return iter.location() orelse .none;
|
||||
}
|
||||
};
|
||||
|
||||
/// Write the full user-friendly diagnostic message to the writer.
|
||||
pub fn write(self: *const Diagnostic, writer: anytype) !void {
|
||||
switch (self.location) {
|
||||
.none => {},
|
||||
.cli => |index| try writer.print("cli:{}:", .{index}),
|
||||
.file => |file| try writer.print(
|
||||
"{s}:{}:",
|
||||
.{ file.path, file.line },
|
||||
),
|
||||
}
|
||||
|
||||
if (self.key.len > 0) {
|
||||
try writer.print("{s}: ", .{self.key});
|
||||
} else if (self.location != .none) {
|
||||
try writer.print(" ", .{});
|
||||
}
|
||||
|
||||
try writer.print("{s}", .{self.message});
|
||||
}
|
||||
};
|
||||
|
||||
/// A list of diagnostics. The "_diagnostics" field must be this type
|
||||
/// for diagnostics to be supported. If this field is an incorrect type
|
||||
/// a compile-time error will be raised.
|
||||
///
|
||||
/// This is implemented as a simple wrapper around an array list
|
||||
/// so that we can inject some logic around adding diagnostics
|
||||
/// and potentially in the future structure them differently.
|
||||
pub const DiagnosticList = struct {
|
||||
/// The list of diagnostics.
|
||||
list: std.ArrayListUnmanaged(Diagnostic) = .{},
|
||||
|
||||
/// Precomputed data for diagnostics. This is used specifically
|
||||
/// when we build libghostty so that we can precompute the messages
|
||||
/// and return them via the C API without allocating memory at
|
||||
/// call time.
|
||||
precompute: Precompute = precompute_init,
|
||||
|
||||
const precompute_enabled = switch (build_config.artifact) {
|
||||
// We enable precompute for tests so that the logic is
|
||||
// semantically analyzed and run.
|
||||
.exe, .wasm_module => builtin.is_test,
|
||||
|
||||
// We specifically want precompute for libghostty.
|
||||
.lib => true,
|
||||
};
|
||||
const Precompute = if (precompute_enabled) struct {
|
||||
messages: std.ArrayListUnmanaged([:0]const u8) = .{},
|
||||
} else void;
|
||||
const precompute_init: Precompute = if (precompute_enabled) .{} else {};
|
||||
|
||||
pub fn append(
|
||||
self: *DiagnosticList,
|
||||
alloc: Allocator,
|
||||
diag: Diagnostic,
|
||||
) Allocator.Error!void {
|
||||
try self.list.append(alloc, diag);
|
||||
errdefer _ = self.list.pop();
|
||||
|
||||
if (comptime precompute_enabled) {
|
||||
var buf = std.ArrayList(u8).init(alloc);
|
||||
defer buf.deinit();
|
||||
try diag.write(buf.writer());
|
||||
|
||||
const owned: [:0]const u8 = try buf.toOwnedSliceSentinel(0);
|
||||
errdefer alloc.free(owned);
|
||||
|
||||
try self.precompute.messages.append(alloc, owned);
|
||||
errdefer _ = self.precompute.messages.pop();
|
||||
|
||||
assert(self.precompute.messages.items.len == self.list.items.len);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn empty(self: *const DiagnosticList) bool {
|
||||
return self.list.items.len == 0;
|
||||
}
|
||||
|
||||
pub fn items(self: *DiagnosticList) []Diagnostic {
|
||||
return self.list.items;
|
||||
}
|
||||
};
|
Reference in New Issue
Block a user