inspector: page improvements

This commit is contained in:
Mitchell Hashimoto
2026-01-29 09:48:26 -08:00
parent c8e048f309
commit 9b75f4a799
5 changed files with 414 additions and 464 deletions

View File

@@ -1,7 +1,6 @@
const std = @import("std");
pub const cell = @import("cell.zig");
pub const key = @import("key.zig");
pub const page = @import("page.zig");
pub const termio = @import("termio.zig");

View File

@@ -1,163 +0,0 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const cimgui = @import("dcimgui");
const terminal = @import("../terminal/main.zig");
const units = @import("units.zig");
pub fn render(page: *const terminal.Page) void {
cimgui.c.ImGui_PushIDPtr(page);
defer cimgui.c.ImGui_PopID();
_ = cimgui.c.ImGui_BeginTable(
"##page_state",
2,
cimgui.c.ImGuiTableFlags_None,
);
defer cimgui.c.ImGui_EndTable();
{
cimgui.c.ImGui_TableNextRow();
{
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Memory Size");
}
{
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d bytes (%d KiB)", page.memory.len, units.toKibiBytes(page.memory.len));
cimgui.c.ImGui_Text("%d VM pages", page.memory.len / std.heap.page_size_min);
}
}
{
cimgui.c.ImGui_TableNextRow();
{
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Unique Styles");
}
{
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d", page.styles.count());
}
}
{
cimgui.c.ImGui_TableNextRow();
{
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Grapheme Entries");
}
{
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d", page.graphemeCount());
}
}
{
cimgui.c.ImGui_TableNextRow();
{
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Capacity");
}
{
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
_ = cimgui.c.ImGui_BeginTable(
"##capacity",
2,
cimgui.c.ImGuiTableFlags_None,
);
defer cimgui.c.ImGui_EndTable();
const cap = page.capacity;
{
cimgui.c.ImGui_TableNextRow();
{
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Columns");
}
{
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d", @as(u32, @intCast(cap.cols)));
}
}
{
cimgui.c.ImGui_TableNextRow();
{
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Rows");
}
{
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d", @as(u32, @intCast(cap.rows)));
}
}
{
cimgui.c.ImGui_TableNextRow();
{
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Unique Styles");
}
{
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d", @as(u32, @intCast(cap.styles)));
}
}
{
cimgui.c.ImGui_TableNextRow();
{
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Grapheme Bytes");
}
{
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d", cap.grapheme_bytes);
}
}
}
}
{
cimgui.c.ImGui_TableNextRow();
{
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Size");
}
{
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
_ = cimgui.c.ImGui_BeginTable(
"##size",
2,
cimgui.c.ImGuiTableFlags_None,
);
defer cimgui.c.ImGui_EndTable();
const size = page.size;
{
cimgui.c.ImGui_TableNextRow();
{
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Columns");
}
{
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d", @as(u32, @intCast(size.cols)));
}
}
{
cimgui.c.ImGui_TableNextRow();
{
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Rows");
}
{
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d", @as(u32, @intCast(size.rows)));
}
}
}
} // size table
}

View File

@@ -1,5 +1,6 @@
const cimgui = @import("dcimgui");
pub const page = @import("widgets/page.zig");
pub const pagelist = @import("widgets/pagelist.zig");
pub const screen = @import("widgets/screen.zig");
pub const style = @import("widgets/style.zig");

View File

@@ -0,0 +1,393 @@
const std = @import("std");
const cimgui = @import("dcimgui");
const terminal = @import("../../terminal/main.zig");
const units = @import("../units.zig");
const widgets = @import("../widgets.zig");
const PageList = terminal.PageList;
const Page = terminal.Page;
pub fn inspector(page: *const terminal.Page) void {
cimgui.c.ImGui_SeparatorText("Managed Memory");
managedMemory(page);
}
/// Draw a tree node header with metadata about this page. Returns if
/// the tree node is open or not. If it is open you must close it with
/// TreePop.
pub fn treeNode(state: struct {
/// The page
page: *const terminal.Page,
/// The index of the page in a page list, used for headers.
index: usize,
/// The range of rows this page covers, inclusive.
row_range: [2]usize,
/// Whether this page is the active or viewport node.
active: bool,
viewport: bool,
}) bool {
// Setup our node.
const open = open: {
var label_buf: [160]u8 = undefined;
const label = std.fmt.bufPrintZ(
&label_buf,
"Page {d}",
.{state.index},
) catch "Page";
const flags = cimgui.c.ImGuiTreeNodeFlags_AllowOverlap |
cimgui.c.ImGuiTreeNodeFlags_SpanFullWidth |
cimgui.c.ImGuiTreeNodeFlags_FramePadding;
break :open cimgui.c.ImGui_TreeNodeEx(label.ptr, flags);
};
// Move our cursor into the tree header so we can add extra info.
const header_min = cimgui.c.ImGui_GetItemRectMin();
const header_max = cimgui.c.ImGui_GetItemRectMax();
const header_height = header_max.y - header_min.y;
const text_line = cimgui.c.ImGui_GetTextLineHeight();
const y_center = header_min.y + (header_height - text_line) * 0.5;
cimgui.c.ImGui_SetCursorScreenPos(.{ .x = header_min.x + 170, .y = y_center });
// Metadata
cimgui.c.ImGui_TextDisabled(
"%dc x %dr",
state.page.size.cols,
state.page.size.rows,
);
cimgui.c.ImGui_SameLine();
cimgui.c.ImGui_Text("rows %d..%d", state.row_range[0], state.row_range[1]);
// Labels
if (state.active) {
cimgui.c.ImGui_SameLine();
cimgui.c.ImGui_TextColored(.{ .x = 0.4, .y = 0.9, .z = 0.4, .w = 1.0 }, "active");
}
if (state.viewport) {
cimgui.c.ImGui_SameLine();
cimgui.c.ImGui_TextColored(.{ .x = 0.4, .y = 0.8, .z = 1.0, .w = 1.0 }, "viewport");
}
if (state.page.isDirty()) {
cimgui.c.ImGui_SameLine();
cimgui.c.ImGui_TextColored(.{ .x = 1.0, .y = 0.4, .z = 0.4, .w = 1.0 }, "dirty");
}
return open;
}
pub fn managedMemory(page: *const Page) void {
if (cimgui.c.ImGui_BeginTable(
"##overview",
3,
cimgui.c.ImGuiTableFlags_BordersInnerV |
cimgui.c.ImGuiTableFlags_RowBg |
cimgui.c.ImGuiTableFlags_SizingFixedFit,
)) {
defer cimgui.c.ImGui_EndTable();
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Memory Size");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
widgets.helpMarker(
"Memory allocated for this page. Note the backing memory " ++
"may be a larger allocation from which this page " ++
"uses a portion.",
);
_ = cimgui.c.ImGui_TableSetColumnIndex(2);
cimgui.c.ImGui_Text(
"%d KiB",
units.toKibiBytes(page.memory.len),
);
}
if (cimgui.c.ImGui_BeginTable(
"##managed",
3,
cimgui.c.ImGuiTableFlags_BordersInnerV |
cimgui.c.ImGuiTableFlags_RowBg |
cimgui.c.ImGuiTableFlags_SizingFixedFit,
)) {
defer cimgui.c.ImGui_EndTable();
cimgui.c.ImGui_TableSetupColumn("Resource", cimgui.c.ImGuiTableColumnFlags_WidthFixed);
cimgui.c.ImGui_TableSetupColumn("Used", cimgui.c.ImGuiTableColumnFlags_WidthFixed);
cimgui.c.ImGui_TableSetupColumn("Capacity", cimgui.c.ImGuiTableColumnFlags_WidthFixed);
cimgui.c.ImGui_TableHeadersRow();
const size = page.size;
const cap = page.capacity;
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Columns");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d", size.cols);
_ = cimgui.c.ImGui_TableSetColumnIndex(2);
cimgui.c.ImGui_Text("%d", cap.cols);
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Rows");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d", size.rows);
_ = cimgui.c.ImGui_TableSetColumnIndex(2);
cimgui.c.ImGui_Text("%d", cap.rows);
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Styles");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d", page.styles.count());
_ = cimgui.c.ImGui_TableSetColumnIndex(2);
cimgui.c.ImGui_Text("%d", page.styles.layout.cap);
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Graphemes");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d", page.graphemeCount());
_ = cimgui.c.ImGui_TableSetColumnIndex(2);
cimgui.c.ImGui_Text("%d", page.graphemeCapacity());
{
const StringAlloc = @TypeOf(page.string_alloc);
const string_chunk = StringAlloc.bytesRequired(u8, 1);
const string_total_chunks = page.string_alloc.bitmap_count * StringAlloc.bitmap_bit_size;
var string_free_chunks: usize = 0;
const string_bitmaps = page.string_alloc.bitmap.ptr(page.memory);
for (string_bitmaps[0..page.string_alloc.bitmap_count]) |bitmap| {
string_free_chunks += @popCount(bitmap);
}
const string_used_chunks = string_total_chunks - string_free_chunks;
const string_used_bytes = string_used_chunks * string_chunk;
const string_capacity_bytes = string_total_chunks * string_chunk;
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Strings (bytes)");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d", string_used_bytes);
_ = cimgui.c.ImGui_TableSetColumnIndex(2);
cimgui.c.ImGui_Text("%d", string_capacity_bytes);
}
{
const hyperlink_map = page.hyperlink_map.map(page.memory);
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Hyperlink Map");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d", hyperlink_map.count());
_ = cimgui.c.ImGui_TableSetColumnIndex(2);
cimgui.c.ImGui_Text("%d", hyperlink_map.capacity());
}
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Hyperlink IDs");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d", page.hyperlink_set.count());
_ = cimgui.c.ImGui_TableSetColumnIndex(2);
cimgui.c.ImGui_Text("%d", page.hyperlink_set.layout.cap);
}
}
pub const Context = struct {
page_node: *const PageList.List.Node,
index: usize,
row_start: usize,
row_end: usize,
active_node: *const PageList.List.Node,
viewport_node: *const PageList.List.Node,
};
pub const InspectorState = struct {
open: bool,
rows_with_text: usize,
};
const PageStats = struct {
rows_with_text: usize,
};
fn pageStats(page: *const terminal.Page) PageStats {
var stats: PageStats = .{ .rows_with_text = 0 };
const rows = page.rows.ptr(page.memory)[0..page.size.rows];
for (rows) |*row| {
const cells = page.getCells(row);
for (cells) |cell| {
if (cell.hasText()) {
stats.rows_with_text += 1;
break;
}
}
}
return stats;
}
pub fn draw(page: *const terminal.Page) void {
cimgui.c.ImGui_PushIDPtr(page);
defer cimgui.c.ImGui_PopID();
cimgui.c.ImGui_SeparatorText("Memory");
memoryTable(page);
cimgui.c.ImGui_SeparatorText("Grid");
gridTable(page);
cimgui.c.ImGui_SeparatorText("Rows");
cimgui.c.ImGui_SameLine();
widgets.helpMarker("Per-row metadata. Cells are coming next.");
rowsTable(page);
}
fn memoryTable(page: *const terminal.Page) void {
if (!cimgui.c.ImGui_BeginTable(
"##page_memory",
2,
cimgui.c.ImGuiTableFlags_BordersInnerV |
cimgui.c.ImGuiTableFlags_RowBg |
cimgui.c.ImGuiTableFlags_SizingFixedFit,
)) return;
defer cimgui.c.ImGui_EndTable();
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Memory Size");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text(
"%d bytes (%d KiB)",
page.memory.len,
units.toKibiBytes(page.memory.len),
);
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("VM Pages");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d", page.memory.len / std.heap.page_size_min);
}
fn gridTable(page: *const terminal.Page) void {
if (!cimgui.c.ImGui_BeginTable(
"##page_grid",
3,
cimgui.c.ImGuiTableFlags_BordersInnerV |
cimgui.c.ImGuiTableFlags_RowBg |
cimgui.c.ImGuiTableFlags_SizingFixedFit,
)) return;
defer cimgui.c.ImGui_EndTable();
cimgui.c.ImGui_TableSetupColumn("Dimension", cimgui.c.ImGuiTableColumnFlags_WidthFixed);
cimgui.c.ImGui_TableSetupColumn("Used", cimgui.c.ImGuiTableColumnFlags_WidthFixed);
cimgui.c.ImGui_TableSetupColumn("Capacity", cimgui.c.ImGuiTableColumnFlags_WidthFixed);
cimgui.c.ImGui_TableHeadersRow();
const size = page.size;
const cap = page.capacity;
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Columns");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d", size.cols);
_ = cimgui.c.ImGui_TableSetColumnIndex(2);
cimgui.c.ImGui_Text("%d", cap.cols);
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Rows");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d", size.rows);
_ = cimgui.c.ImGui_TableSetColumnIndex(2);
cimgui.c.ImGui_Text("%d", cap.rows);
}
fn rowsTable(page: *const terminal.Page) void {
const visible_rows: usize = @min(page.size.rows, 12);
const row_height: f32 = cimgui.c.ImGui_GetTextLineHeightWithSpacing();
const child_height: f32 = row_height * (@as(f32, @floatFromInt(visible_rows)) + 2.0);
_ = cimgui.c.ImGui_BeginChild(
"##page_rows",
.{ .x = 0.0, .y = child_height },
cimgui.c.ImGuiChildFlags_Borders,
cimgui.c.ImGuiWindowFlags_None,
);
defer cimgui.c.ImGui_EndChild();
if (!cimgui.c.ImGui_BeginTable(
"##page_rows_table",
10,
cimgui.c.ImGuiTableFlags_BordersInnerV |
cimgui.c.ImGuiTableFlags_RowBg |
cimgui.c.ImGuiTableFlags_SizingFixedFit,
)) return;
defer cimgui.c.ImGui_EndTable();
cimgui.c.ImGui_TableSetupColumn("Row", cimgui.c.ImGuiTableColumnFlags_WidthFixed);
cimgui.c.ImGui_TableSetupColumn("Text", cimgui.c.ImGuiTableColumnFlags_WidthFixed);
cimgui.c.ImGui_TableSetupColumn("Dirty", cimgui.c.ImGuiTableColumnFlags_WidthFixed);
cimgui.c.ImGui_TableSetupColumn("Wrap", cimgui.c.ImGuiTableColumnFlags_WidthFixed);
cimgui.c.ImGui_TableSetupColumn("Cont", cimgui.c.ImGuiTableColumnFlags_WidthFixed);
cimgui.c.ImGui_TableSetupColumn("Styled", cimgui.c.ImGuiTableColumnFlags_WidthFixed);
cimgui.c.ImGui_TableSetupColumn("Grapheme", cimgui.c.ImGuiTableColumnFlags_WidthFixed);
cimgui.c.ImGui_TableSetupColumn("Link", cimgui.c.ImGuiTableColumnFlags_WidthFixed);
cimgui.c.ImGui_TableSetupColumn("Prompt", cimgui.c.ImGuiTableColumnFlags_WidthFixed);
cimgui.c.ImGui_TableSetupColumn("Kitty", cimgui.c.ImGuiTableColumnFlags_WidthFixed);
cimgui.c.ImGui_TableHeadersRow();
const rows = page.rows.ptr(page.memory)[0..page.size.rows];
for (rows, 0..) |*row, row_index| {
var text_cells: usize = 0;
const cells = page.getCells(row);
for (cells) |cell| {
if (cell.hasText()) {
text_cells += 1;
}
}
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("%d", row_index);
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
if (text_cells == 0) {
cimgui.c.ImGui_TextDisabled("0");
} else {
cimgui.c.ImGui_Text("%d", text_cells);
}
_ = cimgui.c.ImGui_TableSetColumnIndex(2);
flagCell(row.dirty);
_ = cimgui.c.ImGui_TableSetColumnIndex(3);
flagCell(row.wrap);
_ = cimgui.c.ImGui_TableSetColumnIndex(4);
flagCell(row.wrap_continuation);
_ = cimgui.c.ImGui_TableSetColumnIndex(5);
flagCell(row.styled);
_ = cimgui.c.ImGui_TableSetColumnIndex(6);
flagCell(row.grapheme);
_ = cimgui.c.ImGui_TableSetColumnIndex(7);
flagCell(row.hyperlink);
_ = cimgui.c.ImGui_TableSetColumnIndex(8);
cimgui.c.ImGui_Text("%s", @tagName(row.semantic_prompt).ptr);
_ = cimgui.c.ImGui_TableSetColumnIndex(9);
flagCell(row.kitty_virtual_placeholder);
}
}
fn flagCell(value: bool) void {
if (value) {
cimgui.c.ImGui_TextColored(.{ .x = 0.4, .y = 0.9, .z = 0.4, .w = 1.0 }, "yes");
} else {
cimgui.c.ImGui_TextDisabled("-");
}
}

View File

@@ -3,7 +3,6 @@ const cimgui = @import("dcimgui");
const terminal = @import("../../terminal/main.zig");
const widgets = @import("../widgets.zig");
const units = @import("../units.zig");
const page_inspector = @import("../page.zig");
const PageList = terminal.PageList;
@@ -47,100 +46,41 @@ pub const Inspector = struct {
cimgui.c.ImGuiTreeNodeFlags_DefaultOpen,
)) {
widgets.helpMarker(
"Pages are linked in scrollback order. Each page holds a grid of rows/cells " ++
"Pages are shown most-recent first. Each page holds a grid of rows/cells " ++
"plus metadata tables for styles, graphemes, strings, and hyperlinks.",
);
const active_pin = pages.getTopLeft(.active);
const viewport_pin = pages.getTopLeft(.viewport);
var row_offset: usize = 0;
var index: usize = 0;
var node = pages.pages.first;
while (node) |page_node| : (node = page_node.next) {
var row_offset = pages.total_rows;
var index: usize = pages.totalPages();
var node = pages.pages.last;
while (node) |page_node| : (node = page_node.prev) {
const page = &page_node.data;
const row_start = row_offset;
const row_end = row_offset + page.size.rows - 1;
const stats = pageStats(page);
row_offset -= page.size.rows;
index -= 1;
row_offset += page.size.rows;
cimgui.c.ImGui_PushIDInt(@intCast(index));
// Use our node pointer, which is guaranteed to be unique
// in this hierarchy, as the ID.
cimgui.c.ImGui_PushIDPtr(node);
defer cimgui.c.ImGui_PopID();
const header_state = pageHeaderRow(
index,
page,
page_node,
row_start,
row_end,
active_pin,
viewport_pin,
stats,
);
if (header_state.open) {
pageMetaTable(page_node, row_start, row_end, active_pin, viewport_pin, stats);
cimgui.c.ImGui_Separator();
page_inspector.render(page);
cimgui.c.ImGui_Separator();
contentStatsTable(stats);
cimgui.c.ImGui_TreePop();
}
index += 1;
// Open up the tree node.
if (!widgets.page.treeNode(.{
.page = page,
.index = index,
.row_range = .{ row_offset, row_offset + page.size.rows - 1 },
.active = node == active_pin.node,
.viewport = node == viewport_pin.node,
})) continue;
defer cimgui.c.ImGui_TreePop();
widgets.page.inspector(page);
}
}
}
};
const PageStats = struct {
rows_with_text: usize = 0,
cells_with_text: usize = 0,
dirty_rows: usize = 0,
wrap_rows: usize = 0,
wrap_cont_rows: usize = 0,
styled_rows: usize = 0,
grapheme_rows: usize = 0,
hyperlink_rows: usize = 0,
first_text_row: ?usize = null,
last_text_row: ?usize = null,
hyperlink_cells: usize = 0,
styled_cells: usize = 0,
grapheme_cells: usize = 0,
};
fn pageStats(page: *const terminal.Page) PageStats {
var stats: PageStats = .{};
const rows = page.rows.ptr(page.memory)[0..page.size.rows];
for (rows, 0..) |*row, row_index| {
if (row.dirty) stats.dirty_rows += 1;
if (row.wrap) stats.wrap_rows += 1;
if (row.wrap_continuation) stats.wrap_cont_rows += 1;
if (row.styled) stats.styled_rows += 1;
if (row.grapheme) stats.grapheme_rows += 1;
if (row.hyperlink) stats.hyperlink_rows += 1;
const cells = page.getCells(row);
var row_cells_with_text: usize = 0;
for (cells) |cell| {
if (cell.hasText()) row_cells_with_text += 1;
if (cell.hasStyling()) stats.styled_cells += 1;
if (cell.hasGrapheme()) stats.grapheme_cells += 1;
if (cell.hyperlink) stats.hyperlink_cells += 1;
}
if (row_cells_with_text > 0) {
stats.rows_with_text += 1;
stats.cells_with_text += row_cells_with_text;
if (stats.first_text_row == null) stats.first_text_row = row_index;
stats.last_text_row = row_index;
}
}
return stats;
}
fn summaryTable(pages: *const PageList) void {
if (!cimgui.c.ImGui_BeginTable(
"pagelist_summary",
@@ -219,158 +159,6 @@ fn summaryTable(pages: *const PageList) void {
cimgui.c.ImGui_Text("%d", pages.countTrackedPins());
}
fn pageMetaTable(
node: *const PageList.List.Node,
row_start: usize,
row_end: usize,
active_pin: terminal.Pin,
viewport_pin: terminal.Pin,
stats: PageStats,
) void {
if (!cimgui.c.ImGui_BeginTable(
"page_meta",
2,
cimgui.c.ImGuiTableFlags_BordersInnerV |
cimgui.c.ImGuiTableFlags_RowBg |
cimgui.c.ImGuiTableFlags_SizingFixedFit,
)) return;
defer cimgui.c.ImGui_EndTable();
const page = &node.data;
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Row Range");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d..%d", row_start, row_end);
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Serial");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d", node.serial);
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Dirty");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%s", if (page.isDirty()) "true".ptr else "false".ptr);
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Active Top");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%s", if (node == active_pin.node) "true".ptr else "false".ptr);
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Viewport Top");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%s", if (node == viewport_pin.node) "true".ptr else "false".ptr);
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Links");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text(
"%d map / %d set",
page.hyperlink_map.map(page.memory).count(),
page.hyperlink_set.count(),
);
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Text Coverage");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d/%d rows", stats.rows_with_text, page.size.rows);
}
fn contentStatsTable(stats: PageStats) void {
if (!cimgui.c.ImGui_BeginTable(
"page_content_stats",
2,
cimgui.c.ImGuiTableFlags_BordersInnerV |
cimgui.c.ImGuiTableFlags_RowBg |
cimgui.c.ImGuiTableFlags_SizingFixedFit,
)) return;
defer cimgui.c.ImGui_EndTable();
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Rows w/ Text");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d", stats.rows_with_text);
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Cells w/ Text");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d", stats.cells_with_text);
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Text Row Range");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
if (stats.first_text_row) |first| {
cimgui.c.ImGui_Text("%d..%d", first, stats.last_text_row.?);
} else {
cimgui.c.ImGui_TextDisabled("(none)");
}
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Dirty Rows");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d", stats.dirty_rows);
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Wrap Rows");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d", stats.wrap_rows);
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Wrap Continuations");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d", stats.wrap_cont_rows);
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Styled Rows");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d", stats.styled_rows);
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Styled Cells");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d", stats.styled_cells);
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Grapheme Rows");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d", stats.grapheme_rows);
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Grapheme Cells");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d", stats.grapheme_cells);
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Hyperlink Rows");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d", stats.hyperlink_rows);
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Hyperlink Cells");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("%d", stats.hyperlink_cells);
}
fn scrollbarInfo(pages: *PageList) void {
const scrollbar = pages.scrollbar();
@@ -554,74 +342,6 @@ fn trackedPinsTable(pages: *const PageList) void {
}
}
const PageHeaderState = struct {
open: bool,
};
fn pageHeaderRow(
index: usize,
page: *const terminal.Page,
page_node: *const PageList.List.Node,
row_start: usize,
row_end: usize,
active_pin: terminal.Pin,
viewport_pin: terminal.Pin,
stats: PageStats,
) PageHeaderState {
var label_buf: [160]u8 = undefined;
const label = std.fmt.bufPrintZ(
&label_buf,
"Page {d}",
.{index},
) catch "Page";
const flags = cimgui.c.ImGuiTreeNodeFlags_AllowOverlap |
cimgui.c.ImGuiTreeNodeFlags_SpanFullWidth |
cimgui.c.ImGuiTreeNodeFlags_FramePadding;
const open = cimgui.c.ImGui_TreeNodeEx(label.ptr, flags);
const header_min = cimgui.c.ImGui_GetItemRectMin();
const header_max = cimgui.c.ImGui_GetItemRectMax();
const header_height = header_max.y - header_min.y;
const text_line = cimgui.c.ImGui_GetTextLineHeight();
const y_center = header_min.y + (header_height - text_line) * 0.5;
cimgui.c.ImGui_SetCursorScreenPos(.{ .x = header_min.x + 170, .y = y_center });
cimgui.c.ImGui_TextDisabled("%dc x %dr", page.size.cols, page.size.rows);
cimgui.c.ImGui_SameLine();
cimgui.c.ImGui_Text("rows %d..%d", row_start, row_end);
if (page_node == active_pin.node) {
cimgui.c.ImGui_SameLine();
cimgui.c.ImGui_TextColored(.{ .x = 0.4, .y = 0.9, .z = 0.4, .w = 1.0 }, "active");
}
if (page_node == viewport_pin.node) {
cimgui.c.ImGui_SameLine();
cimgui.c.ImGui_TextColored(.{ .x = 0.4, .y = 0.8, .z = 1.0, .w = 1.0 }, "viewport");
}
if (page.isDirty()) {
cimgui.c.ImGui_SameLine();
cimgui.c.ImGui_TextColored(.{ .x = 1.0, .y = 0.4, .z = 0.4, .w = 1.0 }, "dirty");
}
const coverage = if (page.size.rows > 0)
@as(f32, @floatFromInt(stats.rows_with_text)) /
@as(f32, @floatFromInt(page.size.rows))
else
0.0;
const bar_width: f32 = 140;
const bar_height: f32 = 0;
cimgui.c.ImGui_SetCursorScreenPos(.{ .x = header_max.x - bar_width - 10, .y = y_center });
cimgui.c.ImGui_ProgressBar(coverage, .{ .x = bar_width, .y = bar_height }, null);
if (cimgui.c.ImGui_IsItemHovered(cimgui.c.ImGuiHoveredFlags_DelayShort)) {
cimgui.c.ImGui_SetTooltip("Text coverage: %d/%d rows", stats.rows_with_text, page.size.rows);
}
return .{ .open = open };
}
fn scrollbarWidget(
scrollbar: *const PageList.Scrollbar,
delta_row: *isize,