mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 08:46:08 +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_cell = "Cell";
|
||||||
const window_modes = "Modes";
|
const window_modes = "Modes";
|
||||||
const window_keyboard = "Keyboard";
|
const window_keyboard = "Keyboard";
|
||||||
|
const window_termio = "Terminal IO";
|
||||||
const window_screen = "Screen";
|
const window_screen = "Screen";
|
||||||
const window_size = "Surface Info";
|
const window_size = "Surface Info";
|
||||||
const window_imgui_demo = "Dear ImGui Demo";
|
const window_imgui_demo = "Dear ImGui Demo";
|
||||||
@ -44,6 +45,10 @@ cell: CellInspect = .{ .idle = {} },
|
|||||||
/// The list of keyboard events
|
/// The list of keyboard events
|
||||||
key_events: inspector.key.EventRing,
|
key_events: inspector.key.EventRing,
|
||||||
|
|
||||||
|
/// The VT stream
|
||||||
|
vt_events: inspector.termio.VTEventRing,
|
||||||
|
vt_stream: inspector.termio.Stream,
|
||||||
|
|
||||||
const CellInspect = union(enum) {
|
const CellInspect = union(enum) {
|
||||||
/// Idle, no cell inspection is requested
|
/// Idle, no cell inspection is requested
|
||||||
idle: void,
|
idle: void,
|
||||||
@ -107,9 +112,24 @@ pub fn init(surface: *Surface) !Inspector {
|
|||||||
var key_buf = try inspector.key.EventRing.init(surface.alloc, 2);
|
var key_buf = try inspector.key.EventRing.init(surface.alloc, 2);
|
||||||
errdefer key_buf.deinit(surface.alloc);
|
errdefer key_buf.deinit(surface.alloc);
|
||||||
|
|
||||||
|
var vt_events = try inspector.termio.VTEventRing.init(surface.alloc, 2);
|
||||||
|
errdefer vt_events.deinit(surface.alloc);
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.surface = surface,
|
.surface = surface,
|
||||||
.key_events = key_buf,
|
.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);
|
while (it.next()) |v| v.deinit(self.surface.alloc);
|
||||||
self.key_events.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.
|
/// 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.
|
/// Render the frame.
|
||||||
pub fn render(self: *Inspector) void {
|
pub fn render(self: *Inspector) void {
|
||||||
const dock_id = cimgui.c.igDockSpaceOverViewport(
|
const dock_id = cimgui.c.igDockSpaceOverViewport(
|
||||||
@ -158,6 +190,7 @@ pub fn render(self: *Inspector) void {
|
|||||||
self.renderScreenWindow();
|
self.renderScreenWindow();
|
||||||
self.renderModesWindow();
|
self.renderModesWindow();
|
||||||
self.renderKeyboardWindow();
|
self.renderKeyboardWindow();
|
||||||
|
self.renderTermioWindow();
|
||||||
self.renderCellWindow();
|
self.renderCellWindow();
|
||||||
self.renderSizeWindow();
|
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_cell, dock_id.left);
|
||||||
cimgui.c.igDockBuilderDockWindow(window_modes, dock_id.left);
|
cimgui.c.igDockBuilderDockWindow(window_modes, dock_id.left);
|
||||||
cimgui.c.igDockBuilderDockWindow(window_keyboard, 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_screen, dock_id.left);
|
||||||
cimgui.c.igDockBuilderDockWindow(window_imgui_demo, dock_id.left);
|
cimgui.c.igDockBuilderDockWindow(window_imgui_demo, dock_id.left);
|
||||||
cimgui.c.igDockBuilderDockWindow(window_size, dock_id.right);
|
cimgui.c.igDockBuilderDockWindow(window_size, dock_id.right);
|
||||||
@ -1048,3 +1082,50 @@ fn renderKeyboardWindow(self: *Inspector) void {
|
|||||||
}
|
}
|
||||||
} // table
|
} // 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 key = @import("key.zig");
|
||||||
|
pub const termio = @import("termio.zig");
|
||||||
pub const Inspector = @import("Inspector.zig");
|
pub const Inspector = @import("Inspector.zig");
|
||||||
|
|
||||||
test {
|
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});
|
// log.debug("char: {c}", .{c});
|
||||||
const actions = self.parser.next(c);
|
const actions = self.parser.next(c);
|
||||||
for (actions) |action_opt| {
|
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_opt) |action| {
|
||||||
// if (action != .print)
|
// if (action != .print)
|
||||||
// log.info("action: {}", .{action});
|
// log.info("action: {}", .{action});
|
||||||
// }
|
// }
|
||||||
switch (action_opt orelse continue) {
|
|
||||||
|
switch (action) {
|
||||||
.print => |p| if (@hasDecl(T, "print")) try self.handler.print(p),
|
.print => |p| if (@hasDecl(T, "print")) try self.handler.print(p),
|
||||||
.execute => |code| try self.execute(code),
|
.execute => |code| try self.execute(code),
|
||||||
.csi_dispatch => |csi_action| try self.csiDispatch(csi_action),
|
.csi_dispatch => |csi_action| try self.csiDispatch(csi_action),
|
||||||
|
@ -1133,6 +1133,17 @@ const ReadThread = struct {
|
|||||||
// Schedule a render
|
// Schedule a render
|
||||||
ev.queueRender() catch unreachable;
|
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
|
// Process the terminal data. This is an extremely hot part of the
|
||||||
// terminal emulator, so we do some abstraction leakage to avoid
|
// terminal emulator, so we do some abstraction leakage to avoid
|
||||||
// function calls and unnecessary logic.
|
// function calls and unnecessary logic.
|
||||||
|
Reference in New Issue
Block a user