mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-14 03:25:50 +00:00
inspector: clean up Inspector
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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});
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user