aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/inspector/widgets
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-07-16 23:28:05 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-07-16 23:29:28 +0300
commit4a3302d19a2baa3a33e910e0088c24e780905b90 (patch)
treedcaf76fae1c82102d933c9225b17ca7ac5d3a847 /vendor/libghostty-vt/src/inspector/widgets
parent550e9f8cc085f428429f5cf2da0bb2dba897e8b8 (diff)
chore: update vendored libghostty-vt
refs #276
Diffstat (limited to 'vendor/libghostty-vt/src/inspector/widgets')
-rw-r--r--vendor/libghostty-vt/src/inspector/widgets/page.zig19
-rw-r--r--vendor/libghostty-vt/src/inspector/widgets/pagelist.zig272
-rw-r--r--vendor/libghostty-vt/src/inspector/widgets/screen.zig2
3 files changed, 239 insertions, 54 deletions
diff --git a/vendor/libghostty-vt/src/inspector/widgets/page.zig b/vendor/libghostty-vt/src/inspector/widgets/page.zig
index 844abc35..e332ab3c 100644
--- a/vendor/libghostty-vt/src/inspector/widgets/page.zig
+++ b/vendor/libghostty-vt/src/inspector/widgets/page.zig
@@ -25,8 +25,9 @@ pub fn inspector(page: *const terminal.Page) void {
/// 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,
+ /// Page dimensions available without reading its backing memory.
+ cols: terminal.size.CellCountInt,
+ rows: terminal.size.CellCountInt,
/// The index of the page in a page list, used for headers.
index: usize,
/// The range of rows this page covers, inclusive.
@@ -34,6 +35,10 @@ pub fn treeNode(state: struct {
/// Whether this page is the active or viewport node.
active: bool,
viewport: bool,
+ /// Whether the page backing memory is currently compressed.
+ compressed: bool,
+ /// Dirty state is unavailable without restoring a compressed page.
+ dirty: ?bool,
}) bool {
// Setup our node.
const open = open: {
@@ -61,8 +66,8 @@ pub fn treeNode(state: struct {
// Metadata
cimgui.c.ImGui_TextDisabled(
"%dc x %dr",
- state.page.size.cols,
- state.page.size.rows,
+ state.cols,
+ state.rows,
);
cimgui.c.ImGui_SameLine();
cimgui.c.ImGui_Text("rows %d..%d", state.row_range[0], state.row_range[1]);
@@ -76,7 +81,11 @@ pub fn treeNode(state: struct {
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()) {
+ if (state.compressed) {
+ cimgui.c.ImGui_SameLine();
+ cimgui.c.ImGui_TextColored(.{ .x = 0.8, .y = 0.6, .z = 1.0, .w = 1.0 }, "compressed");
+ }
+ if (state.dirty orelse false) {
cimgui.c.ImGui_SameLine();
cimgui.c.ImGui_TextColored(.{ .x = 1.0, .y = 0.4, .z = 0.4, .w = 1.0 }, "dirty");
}
diff --git a/vendor/libghostty-vt/src/inspector/widgets/pagelist.zig b/vendor/libghostty-vt/src/inspector/widgets/pagelist.zig
index bedbfb59..7e001289 100644
--- a/vendor/libghostty-vt/src/inspector/widgets/pagelist.zig
+++ b/vendor/libghostty-vt/src/inspector/widgets/pagelist.zig
@@ -1,12 +1,23 @@
const std = @import("std");
const cimgui = @import("dcimgui");
const terminal = @import("../../terminal/main.zig");
+const pagepkg = @import("../../terminal/page.zig");
const stylepkg = @import("../../terminal/style.zig");
const widgets = @import("../widgets.zig");
-const units = @import("../units.zig");
const PageList = terminal.PageList;
+/// Cell pointers resolved against a Node.PreservedPage rather than the
+/// PageList node.
+/// Omitting the node makes it impossible for inspector helpers to
+/// accidentally restore the original compressed page.
+const InspectedCell = struct {
+ row: *pagepkg.Row,
+ cell: *pagepkg.Cell,
+ row_idx: terminal.size.CellCountInt,
+ col_idx: terminal.size.CellCountInt,
+};
+
/// PageList inspector widget.
pub const Inspector = struct {
pub const empty: Inspector = .{};
@@ -26,6 +37,13 @@ pub const Inspector = struct {
}
if (cimgui.c.ImGui_CollapsingHeader(
+ "Page Compression",
+ cimgui.c.ImGuiTreeNodeFlags_DefaultOpen,
+ )) {
+ compressionTable(pages);
+ }
+
+ if (cimgui.c.ImGui_CollapsingHeader(
"Scrollbar & Regions",
cimgui.c.ImGuiTreeNodeFlags_DefaultOpen,
)) {
@@ -58,8 +76,10 @@ pub const Inspector = struct {
var index: usize = pages.totalPages();
var node = pages.pages.last;
while (node) |page_node| : (node = page_node.prev) {
- const page = &page_node.data;
- row_offset -= page.size.rows;
+ const rows = page_node.rows();
+ const resident = page_node.pageIfResident();
+ const compressed = page_node.storage() == .compressed;
+ row_offset -= rows;
index -= 1;
// We use our location as the ID so that even if reallocations
@@ -69,14 +89,29 @@ pub const Inspector = struct {
// Open up the tree node.
if (!widgets.page.treeNode(.{
- .page = page,
+ .cols = page_node.cols(),
+ .rows = rows,
.index = index,
- .row_range = .{ row_offset, row_offset + page.size.rows - 1 },
+ .row_range = .{ row_offset, row_offset + rows - 1 },
.active = node == active_pin.node,
.viewport = node == viewport_pin.node,
+ .compressed = compressed,
+ .dirty = if (resident) |page| page.isDirty() else null,
})) continue;
defer cimgui.c.ImGui_TreePop();
- widgets.page.inspector(page);
+
+ // Decode compressed contents into a temporary page. The
+ // original node stays compressed while its entry is open.
+ var preserved = page_node.pagePreservingState(
+ std.heap.page_allocator,
+ ) catch {
+ cimgui.c.ImGui_TextDisabled(
+ "(unable to copy compressed page)",
+ );
+ continue;
+ };
+ defer preserved.deinit();
+ widgets.page.inspector(preserved.page());
}
}
}
@@ -106,7 +141,7 @@ fn summaryTable(pages: *const PageList) void {
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
widgets.helpMarker("Total number of pages in the linked list.");
_ = cimgui.c.ImGui_TableSetColumnIndex(2);
- cimgui.c.ImGui_Text("%d", pages.totalPages());
+ cimgui.c.ImGui_Text("%zu", pages.totalPages());
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
@@ -114,34 +149,21 @@ fn summaryTable(pages: *const PageList) void {
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
widgets.helpMarker("Total rows represented by scrollback + active area.");
_ = cimgui.c.ImGui_TableSetColumnIndex(2);
- cimgui.c.ImGui_Text("%d", pages.total_rows);
-
- cimgui.c.ImGui_TableNextRow();
- _ = cimgui.c.ImGui_TableSetColumnIndex(0);
- cimgui.c.ImGui_Text("Page Bytes");
- _ = cimgui.c.ImGui_TableSetColumnIndex(1);
- widgets.helpMarker("Total bytes allocated for active pages.");
- _ = cimgui.c.ImGui_TableSetColumnIndex(2);
- cimgui.c.ImGui_Text(
- "%d KiB",
- units.toKibiBytes(pages.page_size),
- );
+ cimgui.c.ImGui_Text("%zu", pages.total_rows);
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
- cimgui.c.ImGui_Text("Max Size");
+ cimgui.c.ImGui_Text("Scrollback Limit");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
widgets.helpMarker(
- \\Maximum bytes before pages must be evicated. The total
- \\used bytes may be higher due to minimum individual page
- \\sizes but the next allocation that would exceed this limit
- \\will evict pages from the front of the list to free up space.
+ \\Maximum uncompressed logical page memory before the oldest
+ \\history is evicted. Minimum page allocation sizes can make
+ \\current usage temporarily exceed this value.
);
_ = cimgui.c.ImGui_TableSetColumnIndex(2);
- cimgui.c.ImGui_Text(
- "%d KiB",
- units.toKibiBytes(pages.maxSize()),
- );
+ var limit_buf: [64]u8 = undefined;
+ const limit = formatBytes(&limit_buf, pages.maxSize());
+ cimgui.c.ImGui_TextUnformatted(limit.ptr);
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
@@ -150,14 +172,142 @@ fn summaryTable(pages: *const PageList) void {
widgets.helpMarker("Current viewport anchoring mode.");
_ = cimgui.c.ImGui_TableSetColumnIndex(2);
cimgui.c.ImGui_Text("%s", @tagName(pages.viewport).ptr);
+}
+
+fn compressionTable(pages: *const PageList) void {
+ const memory = pages.memoryStats();
+
+ if (!cimgui.c.ImGui_BeginTable(
+ "pagelist_compression",
+ 3,
+ cimgui.c.ImGuiTableFlags_BordersInnerV |
+ cimgui.c.ImGuiTableFlags_RowBg |
+ cimgui.c.ImGuiTableFlags_SizingFixedFit,
+ )) return;
+ defer cimgui.c.ImGui_EndTable();
+
+ compressionTextRow(
+ "Platform Support",
+ "Whether this target can discard physical page memory while retaining " ++
+ "its virtual address range. The scrollback-compression setting may " ++
+ "still disable automatic compression on a supported platform.",
+ if (terminal.compression_enabled) "supported" else "unsupported",
+ );
+ var state_buf: [96]u8 = undefined;
+ const state = std.fmt.bufPrintZ(
+ &state_buf,
+ "{d} compressed, {d} resident",
+ .{ memory.compressed_pages, memory.resident_pages },
+ ) catch unreachable;
+ compressionTextRow(
+ "Page States",
+ "Compressed pages retain an encoded allocation while their raw mapping " ++
+ "is decommitted. Resident pages still have physical raw backing. " ++
+ "Active, visible, and recently changed pages are expected to be resident.",
+ state,
+ );
+
+ var raw_buf: [64]u8 = undefined;
+ compressionTextRow(
+ "Uncompressed Size",
+ "Total size of all raw page mappings. This is the approximate page " ++
+ "backing memory required if no pages were compressed and is also " ++
+ "the virtual address space retained across compression.",
+ formatBytes(&raw_buf, memory.raw_bytes),
+ );
+
+ var encoded_buf: [64]u8 = undefined;
+ var compressed_raw_buf: [64]u8 = undefined;
+ var storage_buf: [160]u8 = undefined;
+ const storage = if (memory.decommitted_raw_bytes > 0) storage: {
+ const ratio = percentage(
+ memory.encoded_bytes,
+ memory.decommitted_raw_bytes,
+ );
+ break :storage std.fmt.bufPrintZ(
+ &storage_buf,
+ "{s} encoded / {s} raw ({d:.1}%)",
+ .{
+ formatBytes(&encoded_buf, memory.encoded_bytes),
+ formatBytes(
+ &compressed_raw_buf,
+ memory.decommitted_raw_bytes,
+ ),
+ ratio,
+ },
+ ) catch unreachable;
+ } else "none";
+ compressionTextRow(
+ "Compressed Storage",
+ "Encoded allocation size compared with the original raw size of only " ++
+ "the compressed pages. The percentage is the compression ratio, " ++
+ "so smaller is better. Raw physical pages have been discarded.",
+ storage,
+ );
+
+ var resident_buf: [64]u8 = undefined;
+ compressionTextRow(
+ "Estimated Resident",
+ "Physical page backing estimated to remain after compression: resident " ++
+ "raw allocations, including unused pool tails, plus encoded storage. " ++
+ "Node and allocator metadata and unrelated terminal memory are excluded.",
+ formatBytes(&resident_buf, memory.estimatedResidentBytes()),
+ );
+
+ var savings_bytes_buf: [64]u8 = undefined;
+ var savings_buf: [128]u8 = undefined;
+ const savings = memory.estimatedSavings();
+ const savings_text = std.fmt.bufPrintZ(
+ &savings_buf,
+ "{s} ({d:.1}% of raw page memory)",
+ .{
+ formatBytes(&savings_bytes_buf, savings),
+ percentage(savings, memory.raw_bytes),
+ },
+ ) catch unreachable;
+ compressionTextRow(
+ "Estimated Savings",
+ "Physical page backing avoided across the complete PageList. This is " ++
+ "decommitted raw memory minus replacement encoded storage and is " ++
+ "not a measurement of total process RSS.",
+ savings_text,
+ );
+}
+
+fn compressionTextRow(
+ label: [:0]const u8,
+ help: [:0]const u8,
+ value: [:0]const u8,
+) void {
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
- cimgui.c.ImGui_Text("Tracked Pins");
+ cimgui.c.ImGui_Text("%s", label.ptr);
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
- widgets.helpMarker("Number of pins tracked for automatic updates.");
+ widgets.helpMarker(help);
_ = cimgui.c.ImGui_TableSetColumnIndex(2);
- cimgui.c.ImGui_Text("%d", pages.countTrackedPins());
+ cimgui.c.ImGui_TextUnformatted(value.ptr);
+}
+
+fn formatBytes(buf: []u8, bytes: usize) [:0]const u8 {
+ if (bytes >= 1024 * 1024) {
+ const value: f64 = @as(f64, @floatFromInt(bytes)) / (1024 * 1024);
+ return std.fmt.bufPrintZ(buf, "{d:.2} MiB", .{value}) catch unreachable;
+ }
+
+ if (bytes >= 1024) {
+ const value: f64 = @as(f64, @floatFromInt(bytes)) / 1024;
+ return std.fmt.bufPrintZ(buf, "{d:.1} KiB", .{value}) catch unreachable;
+ }
+
+ return std.fmt.bufPrintZ(buf, "{d} B", .{bytes}) catch unreachable;
+}
+
+fn percentage(numerator: usize, denominator: usize) f64 {
+ if (denominator == 0) return 0;
+ return 100 *
+ @as(f64, @floatFromInt(numerator)) /
+ @as(f64, @floatFromInt(denominator));
}
fn scrollbarInfo(pages: *PageList) void {
@@ -321,11 +471,18 @@ fn trackedPinsTable(pages: *const PageList) void {
}
_ = cimgui.c.ImGui_TableSetColumnIndex(3);
- const dirty = pin.isDirty();
- if (dirty) {
- cimgui.c.ImGui_TextColored(.{ .x = 1.0, .y = 0.4, .z = 0.4, .w = 1.0 }, "dirty");
+ if (pin.node.pageIfResident()) |page| {
+ const dirty = page.dirty or
+ page.getRowAndCell(pin.x, pin.y).row.dirty;
+ if (dirty) {
+ cimgui.c.ImGui_TextColored(.{ .x = 1.0, .y = 0.4, .z = 0.4, .w = 1.0 }, "dirty");
+ } else {
+ cimgui.c.ImGui_TextDisabled("clean");
+ }
} else {
- cimgui.c.ImGui_TextDisabled("clean");
+ // Dirty state lives in the discarded mapping. Keep inspector
+ // traversal metadata-only rather than restoring this page.
+ cimgui.c.ImGui_TextDisabled("compressed");
}
_ = cimgui.c.ImGui_TableSetColumnIndex(4);
@@ -540,17 +697,37 @@ pub const CellChooser = struct {
.history => terminal.Point{ .history = self.lookup_coord },
};
- const cell = pages.getCell(pt) orelse {
+ const pin = pages.pin(pt) orelse {
cimgui.c.ImGui_TextDisabled("(cell out of range)");
return;
};
- self.cell_info.draw(cell, pt);
+ // Cell pointers must come from the same page whose auxiliary tables
+ // we inspect. For compressed nodes this is an independent preserved
+ // page, leaving the PageList node compressed across inspector frames.
+ var preserved = pin.node.pagePreservingState(
+ std.heap.page_allocator,
+ ) catch {
+ cimgui.c.ImGui_TextDisabled("(unable to copy compressed page)");
+ return;
+ };
+ defer preserved.deinit();
+
+ const page = preserved.page();
+ const rac = page.getRowAndCell(pin.x, pin.y);
+ const cell: InspectedCell = .{
+ .row = rac.row,
+ .cell = rac.cell,
+ .row_idx = pin.y,
+ .col_idx = pin.x,
+ };
+
+ self.cell_info.draw(cell, pt, page);
if (cell.cell.style_id != stylepkg.default_id) {
cimgui.c.ImGui_SeparatorText("Style");
- const style = cell.node.data.styles.get(
- cell.node.data.memory,
+ const style = page.styles.get(
+ page.memory,
cell.cell.style_id,
).*;
widgets.style.table(style, null);
@@ -558,12 +735,12 @@ pub const CellChooser = struct {
if (cell.cell.hyperlink) {
cimgui.c.ImGui_SeparatorText("Hyperlink");
- hyperlinkTable(cell);
+ hyperlinkTable(cell, page);
}
if (cell.cell.hasGrapheme()) {
cimgui.c.ImGui_SeparatorText("Grapheme");
- graphemeTable(cell);
+ graphemeTable(cell, page);
}
}
};
@@ -577,7 +754,7 @@ fn maxCoord(
return br_point.coord();
}
-fn hyperlinkTable(cell: PageList.Cell) void {
+fn hyperlinkTable(cell: InspectedCell, page: *const terminal.Page) void {
if (!cimgui.c.ImGui_BeginTable(
"cell_hyperlink",
2,
@@ -585,7 +762,6 @@ fn hyperlinkTable(cell: PageList.Cell) void {
)) return;
defer cimgui.c.ImGui_EndTable();
- const page = &cell.node.data;
const link_id = page.lookupHyperlink(cell.cell) orelse {
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
@@ -632,7 +808,7 @@ fn hyperlinkTable(cell: PageList.Cell) void {
cimgui.c.ImGui_Text("%d", refs);
}
-fn graphemeTable(cell: PageList.Cell) void {
+fn graphemeTable(cell: InspectedCell, page: *const terminal.Page) void {
if (!cimgui.c.ImGui_BeginTable(
"cell_grapheme",
2,
@@ -640,7 +816,6 @@ fn graphemeTable(cell: PageList.Cell) void {
)) return;
defer cimgui.c.ImGui_EndTable();
- const page = &cell.node.data;
const cps = page.lookupGrapheme(cell.cell) orelse {
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
@@ -680,8 +855,9 @@ pub const CellInfo = struct {
pub fn draw(
_: *const CellInfo,
- cell: PageList.Cell,
+ cell: InspectedCell,
point: terminal.Point,
+ page: *const terminal.Page,
) void {
if (!cimgui.c.ImGui_BeginTable(
"cell_info",
@@ -747,7 +923,7 @@ pub const CellInfo = struct {
_ = cimgui.c.ImGui_TableSetColumnIndex(2);
if (cimgui.c.ImGui_BeginListBox("##cell_grapheme", .{ .x = 0, .y = 0 })) {
defer cimgui.c.ImGui_EndListBox();
- if (cell.node.data.lookupGrapheme(cell.cell)) |cps| {
+ if (page.lookupGrapheme(cell.cell)) |cps| {
var buf: [96]u8 = undefined;
for (cps) |cp| {
const label = std.fmt.bufPrintZ(&buf, "U+{X}", .{cp}) catch "U+?";
@@ -845,7 +1021,7 @@ pub const CellInfo = struct {
widgets.helpMarker("OSC8 hyperlink ID associated with this cell.");
_ = cimgui.c.ImGui_TableSetColumnIndex(2);
- const link_id = cell.node.data.lookupHyperlink(cell.cell) orelse 0;
+ const link_id = page.lookupHyperlink(cell.cell) orelse 0;
cimgui.c.ImGui_Text("id=%d", link_id);
}
}
diff --git a/vendor/libghostty-vt/src/inspector/widgets/screen.zig b/vendor/libghostty-vt/src/inspector/widgets/screen.zig
index bfe961f4..41c113e7 100644
--- a/vendor/libghostty-vt/src/inspector/widgets/screen.zig
+++ b/vendor/libghostty-vt/src/inspector/widgets/screen.zig
@@ -393,7 +393,7 @@ pub fn semanticPromptTable(
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
switch (semantic_prompt.click) {
.none => cimgui.c.ImGui_TextDisabled("(none)"),
- .click_events => cimgui.c.ImGui_Text("click_events"),
+ .click_events => |click_events| cimgui.c.ImGui_Text("click_events=%s", @tagName(click_events).ptr),
.cl => |cl| cimgui.c.ImGui_Text("cl=%s", @tagName(cl).ptr),
}
}