mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-17 01:06:08 +03:00
feat: basic +crash-report cli action
Only lists crash reports right now. Viewing and/or submitting crash reports to come later.
This commit is contained in:

committed by
Mitchell Hashimoto

parent
67abd8804e
commit
d907cebae9
@ -11,6 +11,7 @@ const list_colors = @import("list_colors.zig");
|
||||
const list_actions = @import("list_actions.zig");
|
||||
const show_config = @import("show_config.zig");
|
||||
const validate_config = @import("validate_config.zig");
|
||||
const crash_report = @import("crash_report.zig");
|
||||
|
||||
/// Special commands that can be invoked via CLI flags. These are all
|
||||
/// invoked by using `+<action>` as a CLI flag. The only exception is
|
||||
@ -43,6 +44,9 @@ pub const Action = enum {
|
||||
// Validate passed config file
|
||||
@"validate-config",
|
||||
|
||||
// List, (eventually) view, and (eventually) send crash reports.
|
||||
@"crash-report",
|
||||
|
||||
pub const Error = error{
|
||||
/// Multiple actions were detected. You can specify at most one
|
||||
/// action on the CLI otherwise the behavior desired is ambiguous.
|
||||
@ -134,6 +138,7 @@ pub const Action = enum {
|
||||
.@"list-actions" => try list_actions.run(alloc),
|
||||
.@"show-config" => try show_config.run(alloc),
|
||||
.@"validate-config" => try validate_config.run(alloc),
|
||||
.@"crash-report" => try crash_report.run(alloc),
|
||||
};
|
||||
}
|
||||
|
||||
|
87
src/cli/crash_report.zig
Normal file
87
src/cli/crash_report.zig
Normal file
@ -0,0 +1,87 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const args = @import("args.zig");
|
||||
const Action = @import("action.zig").Action;
|
||||
const Config = @import("../config.zig").Config;
|
||||
const cli = @import("../cli.zig");
|
||||
const sentry = @import("../crash/sentry.zig");
|
||||
|
||||
pub const Options = struct {
|
||||
/// View the crash report locally (unimplemented).
|
||||
view: ?[:0]const u8 = null,
|
||||
|
||||
/// Send the crash report to the Ghostty community (unimplemented).
|
||||
send: ?[:0]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 `crash-report command is used to list/view/send crash reports.
|
||||
///
|
||||
/// When executed without any arguments, this will list any existing crash reports.
|
||||
///
|
||||
/// The `--view` argument can be used to inspect a particular crash report.
|
||||
///
|
||||
/// The `--send` argument can be used to send a crash report to the Ghostty community.
|
||||
pub fn run(alloc: std.mem.Allocator) !u8 {
|
||||
var opts: Options = .{};
|
||||
defer opts.deinit();
|
||||
|
||||
{
|
||||
var iter = try std.process.argsWithAllocator(alloc);
|
||||
defer iter.deinit();
|
||||
try args.parse(Options, alloc, &opts, &iter);
|
||||
}
|
||||
|
||||
const stdout = std.io.getStdOut().writer();
|
||||
|
||||
if (opts.view) |_| {
|
||||
try stdout.writeAll("viewing crash reports is unimplemented\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (opts.send) |_| {
|
||||
try stdout.writeAll("sending crash reports is unimplemented\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (try sentry.listCrashReports(alloc)) |reports| {
|
||||
defer {
|
||||
for (reports) |report| {
|
||||
alloc.free(report.name);
|
||||
}
|
||||
alloc.free(reports);
|
||||
}
|
||||
|
||||
std.mem.sort(sentry.CrashReport, reports, {}, lt);
|
||||
try stdout.print("\n {d:} crash reports!\n\n", .{reports.len});
|
||||
|
||||
for (reports, 0..) |report, count| {
|
||||
var buf: [128]u8 = undefined;
|
||||
const now = std.time.nanoTimestamp();
|
||||
const diff = now - report.mtime;
|
||||
const since = if (diff < 0) "now" else s: {
|
||||
const d = Config.Duration{ .duration = @intCast(diff) };
|
||||
break :s try std.fmt.bufPrint(&buf, "{s} ago", .{d.round(std.time.ns_per_s)});
|
||||
};
|
||||
try stdout.print("{d: >4} — {s} ({s})\n", .{ count, report.name, since });
|
||||
}
|
||||
try stdout.writeAll("\n");
|
||||
} else {
|
||||
try stdout.writeAll("\n No crash reports! 👻\n\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
fn lt(_: void, lhs: sentry.CrashReport, rhs: sentry.CrashReport) bool {
|
||||
return lhs.mtime > rhs.mtime;
|
||||
}
|
@ -4126,6 +4126,10 @@ pub const Duration = struct {
|
||||
return self.duration == other.duration;
|
||||
}
|
||||
|
||||
pub fn round(self: Duration, to: u64) Duration {
|
||||
return .{ .duration = self.duration / to * to };
|
||||
}
|
||||
|
||||
pub fn parseCLI(input: ?[]const u8) !Duration {
|
||||
var remaining = input orelse return error.ValueRequired;
|
||||
|
||||
|
@ -277,3 +277,45 @@ pub const Transport = struct {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
pub const CrashReport = struct {
|
||||
name: []const u8,
|
||||
mtime: i128,
|
||||
};
|
||||
|
||||
pub fn listCrashReports(alloc: std.mem.Allocator) !?[]CrashReport {
|
||||
const crash_dir = try internal_os.xdg.state(alloc, .{ .subdir = "ghostty/crash" });
|
||||
defer alloc.free(crash_dir);
|
||||
|
||||
var dir = std.fs.openDirAbsolute(crash_dir, .{ .iterate = true }) catch return null;
|
||||
|
||||
defer dir.close();
|
||||
|
||||
var list = std.ArrayList(CrashReport).init(alloc);
|
||||
errdefer {
|
||||
for (list.items) |item| {
|
||||
alloc.free(item.name);
|
||||
}
|
||||
list.deinit();
|
||||
}
|
||||
|
||||
var it = dir.iterate();
|
||||
while (try it.next()) |entry| {
|
||||
switch (entry.kind) {
|
||||
.file => {
|
||||
if (std.mem.endsWith(u8, entry.name, ".ghosttycrash")) {
|
||||
const stat = dir.statFile(entry.name) catch continue;
|
||||
try list.append(.{
|
||||
.name = try alloc.dupe(u8, entry.name),
|
||||
.mtime = stat.mtime,
|
||||
});
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
if (list.items.len == 0) return null;
|
||||
|
||||
return try list.toOwnedSlice();
|
||||
}
|
||||
|
Reference in New Issue
Block a user