flow/src/tui/status/minilog.zig

145 lines
4.9 KiB
Zig

const std = @import("std");
const tp = @import("thespian");
const log = @import("log");
const Plane = @import("renderer").Plane;
const Widget = @import("../Widget.zig");
const MessageFilter = @import("../MessageFilter.zig");
const tui = @import("../tui.zig");
const mainview = @import("../mainview.zig");
const logview = @import("../logview.zig");
plane: Plane,
msg: std.ArrayList(u8),
msg_counter: usize = 0,
clear_timer: ?tp.Cancellable = null,
level: Level = .info,
on_event: ?Widget.EventHandler,
const message_display_time_seconds = 2;
const error_display_time_seconds = 4;
const Self = @This();
const Level = enum {
info,
err,
};
pub fn create(a: std.mem.Allocator, parent: Plane, event_handler: ?Widget.EventHandler) @import("widget.zig").CreateError!Widget {
const self: *Self = try a.create(Self);
self.* = .{
.plane = try Plane.init(&(Widget.Box{}).opts(@typeName(Self)), parent),
.msg = std.ArrayList(u8).init(a),
.on_event = event_handler,
};
logview.init(a);
try tui.current().message_filters.add(MessageFilter.bind(self, receive_log));
try log.subscribe();
return Widget.to(self);
}
pub fn deinit(self: *Self, a: std.mem.Allocator) void {
if (self.clear_timer) |*t| {
t.cancel() catch {};
t.deinit();
self.clear_timer = null;
}
self.msg.deinit();
log.unsubscribe() catch {};
tui.current().message_filters.remove_ptr(self);
self.plane.deinit();
a.destroy(self);
}
pub fn receive(self: *Self, from: tp.pid_ref, m: tp.message) error{Exit}!bool {
var btn: u32 = 0;
if (try m.match(.{ "D", tp.any, tp.extract(&btn), tp.more })) {
if (self.on_event) |h| h.send(from, m) catch {};
return true;
}
return false;
}
pub fn layout(self: *Self) Widget.Layout {
return .{ .static = if (self.msg.items.len > 0) self.msg.items.len + 2 else 1 };
}
pub fn render(self: *Self, theme: *const Widget.Theme) bool {
const style_normal = theme.statusbar;
const style_info: Widget.Theme.Style = .{ .fg = theme.statusbar.fg, .fs = theme.editor_information.fs };
const style_error: Widget.Theme.Style = .{ .fg = theme.editor_error.fg, .fs = theme.editor_error.fs };
self.plane.set_base_style(" ", style_normal);
self.plane.erase();
self.plane.home();
self.plane.set_style(if (self.level == .err) style_error else style_info);
_ = self.plane.print(" {s} ", .{self.msg.items}) catch {};
return false;
}
fn receive_log(self: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool {
var clear_msg_num: usize = 0;
if (try m.match(.{ "log", tp.more })) {
logview.process_log(m) catch |e| return tp.exit_error(e, @errorReturnTrace());
self.process_log(m) catch |e| return tp.exit_error(e, @errorReturnTrace());
return true;
} else if (try m.match(.{ "MINILOG", tp.extract(&clear_msg_num) })) {
if (clear_msg_num == self.msg_counter)
self.clear();
return true;
}
return false;
}
fn process_log(self: *Self, m: tp.message) !void {
var src: []const u8 = undefined;
var context: []const u8 = undefined;
var msg: []const u8 = undefined;
if (try m.match(.{ "log", tp.extract(&src), tp.extract(&msg) })) {
try self.set(msg, .info);
} else if (try m.match(.{ "log", "error", tp.extract(&src), tp.extract(&context), "->", tp.extract(&msg) })) {
const err_stop = "error.Stop";
if (std.mem.eql(u8, msg, err_stop))
return;
if (msg.len >= err_stop.len + 1 and std.mem.eql(u8, msg[0 .. err_stop.len + 1], err_stop ++ "\n"))
return;
try self.set(msg, .err);
} else if (try m.match(.{ "log", tp.extract(&src), tp.more })) {
self.level = .err;
var s = std.json.writeStream(self.msg.writer(), .{});
var iter: []const u8 = m.buf;
try @import("cbor").JsonStream(@TypeOf(self.msg)).jsonWriteValue(&s, &iter);
Widget.need_render();
try self.update_clear_timer();
}
}
fn update_clear_timer(self: *Self) !void {
self.msg_counter += 1;
const delay = std.time.us_per_s * @as(u64, if (self.level == .err) error_display_time_seconds else message_display_time_seconds);
if (self.clear_timer) |*t| {
t.cancel() catch {};
t.deinit();
self.clear_timer = null;
}
self.clear_timer = try tp.self_pid().delay_send_cancellable(self.msg.allocator, "minilog.clear_timer", delay, .{ "MINILOG", self.msg_counter });
}
fn set(self: *Self, msg: []const u8, level: Level) !void {
if (@intFromEnum(level) < @intFromEnum(self.level)) return;
self.msg.clearRetainingCapacity();
try self.msg.appendSlice(msg);
self.level = level;
Widget.need_render();
try self.update_clear_timer();
}
fn clear(self: *Self) void {
if (self.clear_timer) |*t| {
t.deinit();
self.clear_timer = null;
}
self.level = .info;
self.msg.clearRetainingCapacity();
Widget.need_render();
}