mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
inspector: start termio log
This commit is contained in:
@ -16,6 +16,7 @@ const inspector = @import("main.zig");
|
||||
const window_cell = "Cell";
|
||||
const window_modes = "Modes";
|
||||
const window_keyboard = "Keyboard";
|
||||
const window_termio = "Terminal IO";
|
||||
const window_screen = "Screen";
|
||||
const window_size = "Surface Info";
|
||||
const window_imgui_demo = "Dear ImGui Demo";
|
||||
@ -44,6 +45,10 @@ cell: CellInspect = .{ .idle = {} },
|
||||
/// The list of keyboard events
|
||||
key_events: inspector.key.EventRing,
|
||||
|
||||
/// The VT stream
|
||||
vt_events: inspector.termio.VTEventRing,
|
||||
vt_stream: inspector.termio.Stream,
|
||||
|
||||
const CellInspect = union(enum) {
|
||||
/// Idle, no cell inspection is requested
|
||||
idle: void,
|
||||
@ -107,9 +112,24 @@ pub fn init(surface: *Surface) !Inspector {
|
||||
var key_buf = try inspector.key.EventRing.init(surface.alloc, 2);
|
||||
errdefer key_buf.deinit(surface.alloc);
|
||||
|
||||
var vt_events = try inspector.termio.VTEventRing.init(surface.alloc, 2);
|
||||
errdefer vt_events.deinit(surface.alloc);
|
||||
|
||||
return .{
|
||||
.surface = surface,
|
||||
.key_events = key_buf,
|
||||
.vt_events = vt_events,
|
||||
.vt_stream = .{
|
||||
.handler = .{
|
||||
.surface = surface,
|
||||
},
|
||||
|
||||
.parser = .{
|
||||
.osc_parser = .{
|
||||
.alloc = surface.alloc,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -119,6 +139,13 @@ pub fn deinit(self: *Inspector) void {
|
||||
while (it.next()) |v| v.deinit(self.surface.alloc);
|
||||
self.key_events.deinit(self.surface.alloc);
|
||||
}
|
||||
|
||||
{
|
||||
var it = self.vt_events.iterator(.forward);
|
||||
while (it.next()) |v| v.deinit(self.surface.alloc);
|
||||
self.vt_events.deinit(self.surface.alloc);
|
||||
self.vt_stream.deinit();
|
||||
}
|
||||
}
|
||||
|
||||
/// Record a keyboard event.
|
||||
@ -141,6 +168,11 @@ pub fn recordKeyEvent(self: *Inspector, ev: inspector.key.Event) !void {
|
||||
};
|
||||
}
|
||||
|
||||
/// Record data read from the pty.
|
||||
pub fn recordPtyRead(self: *Inspector, data: []const u8) !void {
|
||||
try self.vt_stream.nextSlice(data);
|
||||
}
|
||||
|
||||
/// Render the frame.
|
||||
pub fn render(self: *Inspector) void {
|
||||
const dock_id = cimgui.c.igDockSpaceOverViewport(
|
||||
@ -158,6 +190,7 @@ pub fn render(self: *Inspector) void {
|
||||
self.renderScreenWindow();
|
||||
self.renderModesWindow();
|
||||
self.renderKeyboardWindow();
|
||||
self.renderTermioWindow();
|
||||
self.renderCellWindow();
|
||||
self.renderSizeWindow();
|
||||
}
|
||||
@ -208,6 +241,7 @@ fn setupLayout(self: *Inspector, dock_id_main: cimgui.c.ImGuiID) void {
|
||||
cimgui.c.igDockBuilderDockWindow(window_cell, dock_id.left);
|
||||
cimgui.c.igDockBuilderDockWindow(window_modes, dock_id.left);
|
||||
cimgui.c.igDockBuilderDockWindow(window_keyboard, dock_id.left);
|
||||
cimgui.c.igDockBuilderDockWindow(window_termio, dock_id.left);
|
||||
cimgui.c.igDockBuilderDockWindow(window_screen, dock_id.left);
|
||||
cimgui.c.igDockBuilderDockWindow(window_imgui_demo, dock_id.left);
|
||||
cimgui.c.igDockBuilderDockWindow(window_size, dock_id.right);
|
||||
@ -1048,3 +1082,50 @@ fn renderKeyboardWindow(self: *Inspector) void {
|
||||
}
|
||||
} // table
|
||||
}
|
||||
|
||||
fn renderTermioWindow(self: *Inspector) void {
|
||||
// Start our window. If we're collapsed we do nothing.
|
||||
defer cimgui.c.igEnd();
|
||||
if (!cimgui.c.igBegin(
|
||||
window_termio,
|
||||
null,
|
||||
cimgui.c.ImGuiWindowFlags_NoFocusOnAppearing,
|
||||
)) return;
|
||||
|
||||
list: {
|
||||
if (self.vt_events.empty()) {
|
||||
cimgui.c.igText("Waiting for events...");
|
||||
break :list;
|
||||
}
|
||||
|
||||
if (cimgui.c.igButton("Clear", .{ .x = 0, .y = 0 })) {
|
||||
var it = self.vt_events.iterator(.forward);
|
||||
while (it.next()) |v| v.deinit(self.surface.alloc);
|
||||
self.vt_events.clear();
|
||||
}
|
||||
|
||||
cimgui.c.igSeparator();
|
||||
|
||||
_ = cimgui.c.igBeginTable(
|
||||
"table_vt_events",
|
||||
1,
|
||||
cimgui.c.ImGuiTableFlags_RowBg |
|
||||
cimgui.c.ImGuiTableFlags_Borders,
|
||||
.{ .x = 0, .y = 0 },
|
||||
0,
|
||||
);
|
||||
defer cimgui.c.igEndTable();
|
||||
|
||||
var it = self.vt_events.iterator(.reverse);
|
||||
while (it.next()) |ev| {
|
||||
// Need to push an ID so that our selectable is unique.
|
||||
cimgui.c.igPushID_Ptr(ev);
|
||||
defer cimgui.c.igPopID();
|
||||
|
||||
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
|
||||
_ = cimgui.c.igTableSetColumnIndex(0);
|
||||
|
||||
cimgui.c.igText("%s", ev.str.ptr);
|
||||
}
|
||||
} // table
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
pub const key = @import("key.zig");
|
||||
pub const termio = @import("termio.zig");
|
||||
pub const Inspector = @import("Inspector.zig");
|
||||
|
||||
test {
|
||||
|
61
src/inspector/termio.zig
Normal file
61
src/inspector/termio.zig
Normal file
@ -0,0 +1,61 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const terminal = @import("../terminal/main.zig");
|
||||
const CircBuf = @import("../circ_buf.zig").CircBuf;
|
||||
const Surface = @import("../Surface.zig");
|
||||
|
||||
/// The stream handler for our inspector.
|
||||
pub const Stream = terminal.Stream(Handler);
|
||||
|
||||
/// VT event circular buffer.
|
||||
pub const VTEventRing = CircBuf(VTEvent, undefined);
|
||||
|
||||
/// VT event
|
||||
pub const VTEvent = struct {
|
||||
/// The formatted string of the event. This is allocated. We format the
|
||||
/// event for now because there is so much data to copy if we wanted to
|
||||
/// store the raw event.
|
||||
str: [:0]const u8,
|
||||
|
||||
pub fn deinit(self: *VTEvent, alloc: Allocator) void {
|
||||
alloc.free(self.str);
|
||||
}
|
||||
};
|
||||
|
||||
/// Our VT stream handler.
|
||||
const Handler = struct {
|
||||
/// The surface that the inspector is attached to. We use this instead
|
||||
/// of the inspector because this is pointer-stable.
|
||||
surface: *Surface,
|
||||
|
||||
/// This is called with every single terminal action.
|
||||
pub fn handleManually(self: *Handler, action: terminal.Parser.Action) !bool {
|
||||
const insp = self.surface.inspector orelse return false;
|
||||
const alloc = self.surface.alloc;
|
||||
const formatted = try std.fmt.allocPrintZ(alloc, "{}", .{action});
|
||||
errdefer alloc.free(formatted);
|
||||
|
||||
const ev: VTEvent = .{
|
||||
.str = formatted,
|
||||
};
|
||||
|
||||
const max_capacity = 100;
|
||||
insp.vt_events.append(ev) catch |err| switch (err) {
|
||||
error.OutOfMemory => if (insp.vt_events.capacity() < max_capacity) {
|
||||
// We're out of memory, but we can allocate to our capacity.
|
||||
const new_capacity = @min(insp.vt_events.capacity() * 2, max_capacity);
|
||||
try insp.vt_events.resize(insp.surface.alloc, new_capacity);
|
||||
try insp.vt_events.append(ev);
|
||||
} else {
|
||||
var it = insp.vt_events.iterator(.forward);
|
||||
if (it.next()) |old_ev| old_ev.deinit(insp.surface.alloc);
|
||||
insp.vt_events.deleteOldest(1);
|
||||
try insp.vt_events.append(ev);
|
||||
},
|
||||
|
||||
else => return err,
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
@ -58,11 +58,29 @@ pub fn Stream(comptime Handler: type) type {
|
||||
// log.debug("char: {c}", .{c});
|
||||
const actions = self.parser.next(c);
|
||||
for (actions) |action_opt| {
|
||||
const action = action_opt orelse continue;
|
||||
|
||||
// If this handler handles everything manually then we do nothing
|
||||
// if it can be processed.
|
||||
if (@hasDecl(T, "handleManually")) {
|
||||
const processed = self.handler.handleManually(action) catch |err| err: {
|
||||
log.warn("error handling action manually err={} action={}", .{
|
||||
err,
|
||||
action,
|
||||
});
|
||||
|
||||
break :err false;
|
||||
};
|
||||
|
||||
if (processed) continue;
|
||||
}
|
||||
|
||||
// if (action_opt) |action| {
|
||||
// if (action != .print)
|
||||
// log.info("action: {}", .{action});
|
||||
// }
|
||||
switch (action_opt orelse continue) {
|
||||
|
||||
switch (action) {
|
||||
.print => |p| if (@hasDecl(T, "print")) try self.handler.print(p),
|
||||
.execute => |code| try self.execute(code),
|
||||
.csi_dispatch => |csi_action| try self.csiDispatch(csi_action),
|
||||
|
@ -1133,6 +1133,17 @@ const ReadThread = struct {
|
||||
// Schedule a render
|
||||
ev.queueRender() catch unreachable;
|
||||
|
||||
// If we have an inspector, send the data to it too. The inspector
|
||||
// will keep track of the VT sequences that we are processing.
|
||||
if (ev.renderer_state.inspector) |insp| {
|
||||
// This forces all VT sequences to be parsed twice, meaning our
|
||||
// vt processing always slows down by roughly 50% with the inspector
|
||||
// active. That's fine for now but room for improvement.
|
||||
insp.recordPtyRead(buf) catch |err| {
|
||||
log.err("error recording pty read in inspector err={}", .{err});
|
||||
};
|
||||
}
|
||||
|
||||
// Process the terminal data. This is an extremely hot part of the
|
||||
// terminal emulator, so we do some abstraction leakage to avoid
|
||||
// function calls and unnecessary logic.
|
||||
|
Reference in New Issue
Block a user