mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-25 13:16:11 +03:00
195 lines
6.0 KiB
Zig
195 lines
6.0 KiB
Zig
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,
|
|
|
|
/// 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});
|
|
}
|
|
|
|
pub fn clone(self: *const Diagnostic, alloc: Allocator) Allocator.Error!Diagnostic {
|
|
return .{
|
|
.location = try self.location.clone(alloc),
|
|
.key = try alloc.dupeZ(u8, self.key),
|
|
.message = try alloc.dupeZ(u8, self.message),
|
|
};
|
|
}
|
|
};
|
|
|
|
/// 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 const Key = @typeInfo(Location).@"union".tag_type.?;
|
|
|
|
pub fn fromIter(iter: anytype, alloc: Allocator) Allocator.Error!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 (try iter.location(alloc)) orelse .none;
|
|
}
|
|
|
|
pub fn clone(self: *const Location, alloc: Allocator) Allocator.Error!Location {
|
|
return switch (self.*) {
|
|
.none,
|
|
.cli,
|
|
=> self.*,
|
|
|
|
.file => |v| .{ .file = .{
|
|
.path = try alloc.dupe(u8, v.path),
|
|
.line = v.line,
|
|
} },
|
|
};
|
|
}
|
|
};
|
|
|
|
/// 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) = .{},
|
|
|
|
pub fn clone(
|
|
self: *const Precompute,
|
|
alloc: Allocator,
|
|
) Allocator.Error!Precompute {
|
|
var result: Precompute = .{};
|
|
try result.messages.ensureTotalCapacity(alloc, self.messages.items.len);
|
|
for (self.messages.items) |msg| {
|
|
result.messages.appendAssumeCapacity(
|
|
try alloc.dupeZ(u8, msg),
|
|
);
|
|
}
|
|
return result;
|
|
}
|
|
} else void;
|
|
|
|
const precompute_init: Precompute = if (precompute_enabled) .{} else {};
|
|
|
|
pub fn clone(
|
|
self: *const DiagnosticList,
|
|
alloc: Allocator,
|
|
) Allocator.Error!DiagnosticList {
|
|
var result: DiagnosticList = .{};
|
|
|
|
try result.list.ensureTotalCapacity(alloc, self.list.items.len);
|
|
for (self.list.items) |*diag| result.list.appendAssumeCapacity(
|
|
try diag.clone(alloc),
|
|
);
|
|
|
|
if (comptime precompute_enabled) {
|
|
result.precompute = try self.precompute.clone(alloc);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
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: *const DiagnosticList) []const Diagnostic {
|
|
return self.list.items;
|
|
}
|
|
|
|
/// Returns true if there are any diagnostics for the given
|
|
/// location type.
|
|
pub fn containsLocation(
|
|
self: *const DiagnosticList,
|
|
location: Location.Key,
|
|
) bool {
|
|
for (self.list.items) |diag| {
|
|
if (diag.location == location) return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
};
|