diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-04-04 19:22:04 +0300 |
|---|---|---|
| committer | Can Celik <ogulcancelik@gmail.com> | 2026-04-05 01:23:06 +0300 |
| commit | 41dd733ca7123c9ac4bfc6c781697a660e95b052 (patch) | |
| tree | 3fc91815ca139ef2e0b281bba0abda4ef15175f4 /vendor/libghostty-vt/src/inspector/widgets/page.zig | |
| parent | 55c138263e190a1e79d4b997bfb9397306c0ec1f (diff) | |
feat: finish ghostty pane backend integration
Diffstat (limited to 'vendor/libghostty-vt/src/inspector/widgets/page.zig')
| -rw-r--r-- | vendor/libghostty-vt/src/inspector/widgets/page.zig | 428 |
1 files changed, 428 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/inspector/widgets/page.zig b/vendor/libghostty-vt/src/inspector/widgets/page.zig new file mode 100644 index 00000000..844abc35 --- /dev/null +++ b/vendor/libghostty-vt/src/inspector/widgets/page.zig @@ -0,0 +1,428 @@ +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); + + cimgui.c.ImGui_SeparatorText("Styles"); + stylesList(page); + + cimgui.c.ImGui_SeparatorText("Hyperlinks"); + hyperlinksList(page); + + cimgui.c.ImGui_SeparatorText("Rows"); + rowsTable(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", + 4, + 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("", 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); + widgets.helpMarker("Number of columns in the terminal grid."); + _ = cimgui.c.ImGui_TableSetColumnIndex(2); + cimgui.c.ImGui_Text("%d", size.cols); + _ = cimgui.c.ImGui_TableSetColumnIndex(3); + 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); + widgets.helpMarker("Number of rows in this page."); + _ = cimgui.c.ImGui_TableSetColumnIndex(2); + cimgui.c.ImGui_Text("%d", size.rows); + _ = cimgui.c.ImGui_TableSetColumnIndex(3); + 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); + widgets.helpMarker("Unique text styles (colors, attributes) currently in use."); + _ = cimgui.c.ImGui_TableSetColumnIndex(2); + cimgui.c.ImGui_Text("%d", page.styles.count()); + _ = cimgui.c.ImGui_TableSetColumnIndex(3); + 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); + widgets.helpMarker("Extended grapheme clusters for multi-codepoint characters."); + _ = cimgui.c.ImGui_TableSetColumnIndex(2); + cimgui.c.ImGui_Text("%d", page.graphemeCount()); + _ = cimgui.c.ImGui_TableSetColumnIndex(3); + cimgui.c.ImGui_Text("%d", page.graphemeCapacity()); + + cimgui.c.ImGui_TableNextRow(); + _ = cimgui.c.ImGui_TableSetColumnIndex(0); + cimgui.c.ImGui_Text("Strings (bytes)"); + _ = cimgui.c.ImGui_TableSetColumnIndex(1); + widgets.helpMarker("String storage for hyperlink URIs and other text data."); + _ = cimgui.c.ImGui_TableSetColumnIndex(2); + cimgui.c.ImGui_Text("%d", page.string_alloc.usedBytes(page.memory)); + _ = cimgui.c.ImGui_TableSetColumnIndex(3); + cimgui.c.ImGui_Text("%d", page.string_alloc.capacityBytes()); + + 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); + widgets.helpMarker("Maps cell positions to hyperlink IDs."); + _ = cimgui.c.ImGui_TableSetColumnIndex(2); + cimgui.c.ImGui_Text("%d", hyperlink_map.count()); + _ = cimgui.c.ImGui_TableSetColumnIndex(3); + 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); + widgets.helpMarker("Unique hyperlink definitions (URI + optional ID)."); + _ = cimgui.c.ImGui_TableSetColumnIndex(2); + cimgui.c.ImGui_Text("%d", page.hyperlink_set.count()); + _ = cimgui.c.ImGui_TableSetColumnIndex(3); + cimgui.c.ImGui_Text("%d", page.hyperlink_set.layout.cap); + } +} + +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); + + // Child window so scrolling is separate. + // This defer first is not a bug, EndChild always needs to be called. + defer cimgui.c.ImGui_EndChild(); + if (!cimgui.c.ImGui_BeginChild( + "##page_rows", + .{ .x = 0.0, .y = child_height }, + cimgui.c.ImGuiChildFlags_Borders, + cimgui.c.ImGuiWindowFlags_None, + )) return; + + 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_TableSetupScrollFreeze(0, 1); + 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 stylesList(page: *const Page) void { + const items = page.styles.items.ptr(page.memory)[0..page.styles.layout.cap]; + + var count: usize = 0; + for (items, 0..) |item, index| { + if (index == 0) continue; + if (item.meta.ref == 0) continue; + count += 1; + } + + if (count == 0) { + cimgui.c.ImGui_TextDisabled("(no styles in use)"); + return; + } + + const visible_rows: usize = @min(count, 8); + const row_height: f32 = cimgui.c.ImGui_GetTextLineHeightWithSpacing(); + const child_height: f32 = row_height * (@as(f32, @floatFromInt(visible_rows)) + 2.0); + + defer cimgui.c.ImGui_EndChild(); + if (!cimgui.c.ImGui_BeginChild( + "##page_styles", + .{ .x = 0.0, .y = child_height }, + cimgui.c.ImGuiChildFlags_Borders, + cimgui.c.ImGuiWindowFlags_None, + )) return; + + if (!cimgui.c.ImGui_BeginTable( + "##page_styles_table", + 3, + cimgui.c.ImGuiTableFlags_BordersInnerV | + cimgui.c.ImGuiTableFlags_RowBg | + cimgui.c.ImGuiTableFlags_SizingFixedFit, + )) return; + defer cimgui.c.ImGui_EndTable(); + + cimgui.c.ImGui_TableSetupScrollFreeze(0, 1); + cimgui.c.ImGui_TableSetupColumn("ID", cimgui.c.ImGuiTableColumnFlags_WidthFixed); + cimgui.c.ImGui_TableSetupColumn("Refs", cimgui.c.ImGuiTableColumnFlags_WidthFixed); + cimgui.c.ImGui_TableSetupColumn("Style", cimgui.c.ImGuiTableColumnFlags_WidthStretch); + cimgui.c.ImGui_TableHeadersRow(); + + for (items, 0..) |item, index| { + if (index == 0) continue; + if (item.meta.ref == 0) continue; + + cimgui.c.ImGui_TableNextRow(); + cimgui.c.ImGui_PushIDInt(@intCast(index)); + defer cimgui.c.ImGui_PopID(); + + _ = cimgui.c.ImGui_TableSetColumnIndex(0); + cimgui.c.ImGui_Text("%d", index); + + _ = cimgui.c.ImGui_TableSetColumnIndex(1); + cimgui.c.ImGui_Text("%d", item.meta.ref); + + _ = cimgui.c.ImGui_TableSetColumnIndex(2); + if (cimgui.c.ImGui_TreeNodeEx("Details", cimgui.c.ImGuiTreeNodeFlags_None)) { + defer cimgui.c.ImGui_TreePop(); + widgets.style.table(item.value, null); + } + } +} + +fn hyperlinksList(page: *const Page) void { + const items = page.hyperlink_set.items.ptr(page.memory)[0..page.hyperlink_set.layout.cap]; + + var count: usize = 0; + for (items, 0..) |item, index| { + if (index == 0) continue; + if (item.meta.ref == 0) continue; + count += 1; + } + + if (count == 0) { + cimgui.c.ImGui_TextDisabled("(no hyperlinks in use)"); + return; + } + + const visible_rows: usize = @min(count, 8); + const row_height: f32 = cimgui.c.ImGui_GetTextLineHeightWithSpacing(); + const child_height: f32 = row_height * (@as(f32, @floatFromInt(visible_rows)) + 2.0); + + defer cimgui.c.ImGui_EndChild(); + if (!cimgui.c.ImGui_BeginChild( + "##page_hyperlinks", + .{ .x = 0.0, .y = child_height }, + cimgui.c.ImGuiChildFlags_Borders, + cimgui.c.ImGuiWindowFlags_None, + )) return; + + if (!cimgui.c.ImGui_BeginTable( + "##page_hyperlinks_table", + 4, + cimgui.c.ImGuiTableFlags_BordersInnerV | + cimgui.c.ImGuiTableFlags_RowBg | + cimgui.c.ImGuiTableFlags_SizingFixedFit, + )) return; + defer cimgui.c.ImGui_EndTable(); + + cimgui.c.ImGui_TableSetupScrollFreeze(0, 1); + cimgui.c.ImGui_TableSetupColumn("ID", cimgui.c.ImGuiTableColumnFlags_WidthFixed); + cimgui.c.ImGui_TableSetupColumn("Refs", cimgui.c.ImGuiTableColumnFlags_WidthFixed); + cimgui.c.ImGui_TableSetupColumn("Explicit ID", cimgui.c.ImGuiTableColumnFlags_WidthFixed); + cimgui.c.ImGui_TableSetupColumn("URI", cimgui.c.ImGuiTableColumnFlags_WidthStretch); + cimgui.c.ImGui_TableHeadersRow(); + + for (items, 0..) |item, index| { + if (index == 0) continue; + if (item.meta.ref == 0) continue; + + cimgui.c.ImGui_TableNextRow(); + + _ = cimgui.c.ImGui_TableSetColumnIndex(0); + cimgui.c.ImGui_Text("%d", index); + + _ = cimgui.c.ImGui_TableSetColumnIndex(1); + cimgui.c.ImGui_Text("%d", item.meta.ref); + + _ = cimgui.c.ImGui_TableSetColumnIndex(2); + switch (item.value.id) { + .explicit => |slice| { + const explicit_id = slice.slice(page.memory); + cimgui.c.ImGui_Text("%.*s", explicit_id.len, explicit_id.ptr); + }, + .implicit => cimgui.c.ImGui_TextDisabled("-"), + } + + _ = cimgui.c.ImGui_TableSetColumnIndex(3); + const uri = item.value.uri.slice(page.memory); + cimgui.c.ImGui_Text("%.*s", uri.len, uri.ptr); + } +} + +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("-"); + } +} |
