inspector: clean up Inspector

This commit is contained in:
Mitchell Hashimoto
2026-01-29 20:06:36 -08:00
parent e9439533a7
commit 3e825dd608
6 changed files with 77 additions and 95 deletions

View File

@@ -2618,7 +2618,7 @@ pub fn keyCallback(
defer crash.sentry.thread_state = null;
// Setup our inspector event if we have an inspector.
var insp_ev: ?inspectorpkg.key.Event = if (self.inspector != null) ev: {
var insp_ev: ?inspectorpkg.KeyEvent = if (self.inspector != null) ev: {
var copy = event;
copy.utf8 = "";
if (event.utf8.len > 0) copy.utf8 = try self.alloc.dupe(u8, event.utf8);
@@ -2798,7 +2798,7 @@ pub fn keyCallback(
fn maybeHandleBinding(
self: *Surface,
event: input.KeyEvent,
insp_ev: ?*inspectorpkg.key.Event,
insp_ev: ?*inspectorpkg.KeyEvent,
) !?InputEffect {
switch (event.action) {
// Release events never trigger a binding but we need to check if
@@ -3131,7 +3131,7 @@ fn endKeySequence(
fn encodeKey(
self: *Surface,
event: input.KeyEvent,
insp_ev: ?*inspectorpkg.key.Event,
insp_ev: ?*inspectorpkg.KeyEvent,
) !?termio.Message.WriteReq {
const write_req: termio.Message.WriteReq = req: {
// Build our encoding options, which requires the lock.

View File

@@ -22,10 +22,6 @@ const window_imgui_demo = "Dear ImGui Demo";
/// The surface that we're inspecting.
surface: *Surface,
/// This is used to track whether we're rendering for the first time. This
/// is used to set up the initial window positions.
first_render: bool = true,
/// Mouse state that we track in addition to normal mouse states that
/// Ghostty always knows about.
mouse: widgets.surface.Mouse = .{},
@@ -126,7 +122,7 @@ pub fn setup() void {
}
pub fn init(surface: *Surface) !Inspector {
var gui: widgets.surface.Inspector = try .init(surface.alloc, surface);
var gui: widgets.surface.Inspector = try .init(surface.alloc);
errdefer gui.deinit(surface.alloc);
return .{
@@ -141,7 +137,7 @@ pub fn deinit(self: *Inspector) void {
}
/// Record a keyboard event.
pub fn recordKeyEvent(self: *Inspector, ev: inspector.key.Event) !void {
pub fn recordKeyEvent(self: *Inspector, ev: inspector.KeyEvent) !void {
const max_capacity = 50;
const events: *widgets.key.EventRing = &self.gui.key_stream.events;
@@ -163,7 +159,20 @@ 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 {
pub fn recordPtyRead(
self: *Inspector,
alloc: Allocator,
t: *terminal.Terminal,
data: []const u8,
) !void {
// We need to setup our state so that capture works properly.
const handler: *widgets.termio.VTHandler = &self.gui.vt_stream.parser_stream.handler;
handler.state = .{
.alloc = alloc,
.terminal = t,
.events = &self.gui.vt_stream.events,
};
try self.gui.vt_stream.parser_stream.nextSlice(data);
}
@@ -173,58 +182,9 @@ pub fn render(self: *Inspector) void {
self.surface,
self.mouse,
);
if (true) return;
const dock_id = cimgui.c.ImGui_DockSpaceOverViewport();
// Render all of our data. We hold the mutex for this duration. This is
// expensive but this is an initial implementation until it doesn't work
// anymore.
{
self.surface.renderer_state.mutex.lock();
defer self.surface.renderer_state.mutex.unlock();
const t = self.surface.renderer_state.terminal;
self.windows.terminal.render(t);
self.windows.surface.render(.{
.surface = self.surface,
.mouse = self.mouse,
});
self.renderTermioWindow();
self.renderCellWindow();
}
// In debug we show the ImGui demo window so we can easily view available
// widgets and such.
if (builtin.mode == .Debug) {
var show: bool = true;
cimgui.c.ImGui_ShowDemoWindow(&show);
}
// On first render we set up the layout. We can actually do this at
// the end of the frame, allowing the individual rendering to also
// observe the first render flag.
if (self.first_render) {
self.first_render = false;
self.setupLayout(dock_id);
}
}
fn setupLayout(self: *Inspector, dock_id_main: cimgui.c.ImGuiID) void {
_ = self;
// Our initial focus
cimgui.c.ImGui_SetWindowFocusStr(inspector.terminal.Window.name);
// Setup our initial layout - all windows in a single dock as tabs.
// Surface is docked first so it appears as the first tab.
cimgui.ImGui_DockBuilderDockWindow(inspector.surface.Window.name, dock_id_main);
cimgui.ImGui_DockBuilderDockWindow(inspector.terminal.Window.name, dock_id_main);
cimgui.ImGui_DockBuilderDockWindow(window_termio, dock_id_main);
cimgui.ImGui_DockBuilderDockWindow(window_cell, dock_id_main);
cimgui.ImGui_DockBuilderDockWindow(window_imgui_demo, dock_id_main);
cimgui.ImGui_DockBuilderFinish(dock_id_main);
}
/// TODO: OLD, REMOVE EVENTUALLY ONCE WE MIGRATE FUNCTIONALITY
fn renderCellWindow(self: *Inspector) void {
// Start our window. If we're collapsed we do nothing.
defer cimgui.c.ImGui_End();

View File

@@ -1,10 +1,12 @@
const std = @import("std");
// TODO: Remove
pub const cell = @import("cell.zig");
pub const key = @import("widgets/key.zig");
pub const Cell = cell.Cell;
pub const widgets = @import("widgets.zig");
pub const Inspector = @import("Inspector.zig");
pub const KeyEvent = widgets.key.Event;
test {
@import("std").testing.refAllDecls(@This());
}

View File

@@ -24,12 +24,12 @@ pub const Inspector = struct {
terminal_info: widgets.terminal.Info,
vt_stream: widgets.termio.Stream,
pub fn init(alloc: Allocator, surface: *Surface) !Inspector {
pub fn init(alloc: Allocator) !Inspector {
return .{
.surface_info = .empty,
.key_stream = try .init(alloc),
.terminal_info = .empty,
.vt_stream = try .init(alloc, surface),
.vt_stream = try .init(alloc),
};
}

View File

@@ -39,7 +39,7 @@ pub const VTEvent = struct {
/// Initialize the event information for the given parser action.
pub fn init(
alloc: Allocator,
surface: *Surface,
t: *const terminal.Terminal,
action: terminal.Parser.Action,
) !VTEvent {
var md = Metadata.init(alloc);
@@ -60,8 +60,6 @@ pub const VTEvent = struct {
.apc_start, .apc_put, .apc_end => .apc,
};
const t = surface.renderer_state.terminal;
return .{
.kind = kind,
.str = str,
@@ -308,29 +306,43 @@ pub const VTEvent = struct {
/// Our VT stream handler.
pub const VTHandler = struct {
/// The surface that the inspector is attached to. We use this instead
/// of the inspector because this is pointer-stable.
surface: *Surface,
/// The capture state, must be set before use. If null, then
/// events are dropped.
state: ?State,
/// True if the handler is currently recording.
active: bool = true,
/// True to pause this artificially.
paused: bool,
/// Current sequence number
current_seq: usize = 1,
current_seq: usize,
/// Exclude certain actions by tag.
filter_exclude: ActionTagSet = .initMany(&.{.print}),
filter_text: cimgui.c.ImGuiTextFilter = .{},
filter_exclude: ActionTagSet,
filter_text: cimgui.c.ImGuiTextFilter,
const ActionTagSet = std.EnumSet(terminal.Parser.Action.Tag);
pub const ActionTagSet = std.EnumSet(terminal.Parser.Action.Tag);
pub fn init(surface: *Surface) VTHandler {
return .{
.surface = surface,
};
}
pub const State = struct {
/// The allocator to use for the events.
alloc: Allocator,
/// The terminal state at the time of the event.
terminal: *const terminal.Terminal,
/// The event ring to write events to.
events: *VTEventRing,
};
pub const init: VTHandler = .{
.state = null,
.paused = false,
.current_seq = 1,
.filter_exclude = .initMany(&.{.print}),
.filter_text = .{},
};
pub fn deinit(self: *VTHandler) void {
// Required for the parser stream interface
_ = self;
}
@@ -345,16 +357,17 @@ pub const VTHandler = struct {
/// This is called with every single terminal action.
pub fn handleManually(self: *VTHandler, action: terminal.Parser.Action) !bool {
const insp = self.surface.inspector orelse return false;
const vt_events = &insp.gui.vt_stream.events;
const state: *State = if (self.state) |*s| s else return true;
const alloc = state.alloc;
const vt_events = state.events;
// We always increment the sequence number, even if we're paused or
// filter out the event. This helps show the user that there is a gap
// between events and roughly how large that gap was.
defer self.current_seq +%= 1;
// If we're pausing, then we ignore all events.
if (!self.active) return true;
// If we're manually paused, we ignore all events.
if (self.paused) return true;
// We ignore certain action types that are too noisy.
switch (action) {
@@ -367,8 +380,11 @@ pub const VTHandler = struct {
if (self.filter_exclude.contains(std.meta.activeTag(action))) return true;
// Build our event
const alloc = self.surface.alloc;
var ev = try VTEvent.init(alloc, self.surface, action);
var ev: VTEvent = try .init(
alloc,
state.terminal,
action,
);
ev.seq = self.current_seq;
errdefer ev.deinit(alloc);
@@ -383,11 +399,11 @@ pub const VTHandler = struct {
error.OutOfMemory => if (vt_events.capacity() < max_capacity) {
// We're out of memory, but we can allocate to our capacity.
const new_capacity = @min(vt_events.capacity() * 2, max_capacity);
try vt_events.resize(self.surface.alloc, new_capacity);
try vt_events.resize(alloc, new_capacity);
try vt_events.append(ev);
} else {
var it = vt_events.iterator(.forward);
if (it.next()) |old_ev| old_ev.deinit(self.surface.alloc);
if (it.next()) |old_ev| old_ev.deinit(alloc);
vt_events.deleteOldest(1);
try vt_events.append(ev);
},
@@ -420,11 +436,11 @@ pub const Stream = struct {
/// Flag indicating whether the selection was made by keyboard
is_keyboard_selection: bool = false,
pub fn init(alloc: Allocator, surface: *Surface) !Stream {
pub fn init(alloc: Allocator) !Stream {
var events: VTEventRing = try .init(alloc, 2);
errdefer events.deinit(alloc);
var handler = VTHandler.init(surface);
var handler: VTHandler = .init;
errdefer handler.deinit();
return .{
@@ -451,12 +467,12 @@ pub const Stream = struct {
const popup_filter = "Filter";
list: {
const pause_play: [:0]const u8 = if (handler.active)
const pause_play: [:0]const u8 = if (!handler.paused)
"Pause##pause_play"
else
"Resume##pause_play";
if (cimgui.c.ImGui_Button(pause_play.ptr)) {
handler.active = !handler.active;
handler.paused = !handler.paused;
}
cimgui.c.ImGui_SameLineEx(0, cimgui.c.ImGui_GetStyle().*.ItemInnerSpacing.x);

View File

@@ -694,7 +694,11 @@ fn processOutputLocked(self: *Termio, buf: []const u8) void {
// below but at least users only pay for it if they're using the inspector.
if (self.renderer_state.inspector) |insp| {
for (buf, 0..) |byte, i| {
insp.recordPtyRead(buf[i .. i + 1]) catch |err| {
insp.recordPtyRead(
self.alloc,
&self.terminal,
buf[i .. i + 1],
) catch |err| {
log.err("error recording pty read in inspector err={}", .{err});
};