diff --git a/src/Surface.zig b/src/Surface.zig index 0bf3aa008..23531f387 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -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. diff --git a/src/inspector/Inspector.zig b/src/inspector/Inspector.zig index fc7a7de72..0044556a1 100644 --- a/src/inspector/Inspector.zig +++ b/src/inspector/Inspector.zig @@ -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(); diff --git a/src/inspector/main.zig b/src/inspector/main.zig index 2a905b0a4..05e6e4ba2 100644 --- a/src/inspector/main.zig +++ b/src/inspector/main.zig @@ -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()); } diff --git a/src/inspector/widgets/surface.zig b/src/inspector/widgets/surface.zig index 1b7394f4b..9f52501d4 100644 --- a/src/inspector/widgets/surface.zig +++ b/src/inspector/widgets/surface.zig @@ -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), }; } diff --git a/src/inspector/widgets/termio.zig b/src/inspector/widgets/termio.zig index edcb81cde..c0d195970 100644 --- a/src/inspector/widgets/termio.zig +++ b/src/inspector/widgets/termio.zig @@ -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); diff --git a/src/termio/Termio.zig b/src/termio/Termio.zig index a1bcea6d3..f46e2ec05 100644 --- a/src/termio/Termio.zig +++ b/src/termio/Termio.zig @@ -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}); };