From 82dd9021bf63a13b10b3559ce7d40bdcc23e70f8 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 28 Jan 2026 11:32:08 -0800 Subject: [PATCH] inspector: starting screen --- src/inspector/widgets.zig | 1 + src/inspector/widgets/screen.zig | 120 ++++++++++++++++++++++ src/inspector/widgets/terminal.zig | 156 +++++++++++++++++++++++++++-- 3 files changed, 267 insertions(+), 10 deletions(-) create mode 100644 src/inspector/widgets/screen.zig diff --git a/src/inspector/widgets.zig b/src/inspector/widgets.zig index af3bd414f..d46e3fde4 100644 --- a/src/inspector/widgets.zig +++ b/src/inspector/widgets.zig @@ -1,5 +1,6 @@ const cimgui = @import("dcimgui"); +pub const screen = @import("widgets/screen.zig"); pub const surface = @import("widgets/surface.zig"); pub const terminal = @import("widgets/terminal.zig"); diff --git a/src/inspector/widgets/screen.zig b/src/inspector/widgets/screen.zig new file mode 100644 index 000000000..d7b7b455c --- /dev/null +++ b/src/inspector/widgets/screen.zig @@ -0,0 +1,120 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const assert = @import("../../quirks.zig").inlineAssert; +const Allocator = std.mem.Allocator; +const cimgui = @import("dcimgui"); +const widgets = @import("../widgets.zig"); +const terminal = @import("../../terminal/main.zig"); + +/// Screen information inspector widget. +pub const Info = struct { + pub const empty: Info = .{}; + + /// Draw the screen info contents. + pub fn draw(self: *Info, open: bool, data: struct { + /// The screen that we're inspecting. + screen: *const terminal.Screen, + + /// Which screen key we're viewing. + key: terminal.ScreenSet.Key, + + /// Which screen is active (primary or alternate). + active_key: terminal.ScreenSet.Key, + + /// Whether xterm modify other keys mode 2 is enabled. + modify_other_keys_2: bool, + + /// Color palette for cursor color resolution. + color_palette: *const terminal.color.DynamicPalette, + }) void { + _ = self; + const screen = data.screen; + + // The remainder is the open state + if (!open) return; + + // Show warning if viewing an inactive screen + if (data.key != data.active_key) { + cimgui.c.ImGui_TextColored( + .{ .x = 1.0, .y = 0.8, .z = 0.0, .w = 1.0 }, + "⚠ Viewing inactive screen", + ); + cimgui.c.ImGui_Separator(); + } + + if (cimgui.c.ImGui_CollapsingHeader( + "Keyboard", + cimgui.c.ImGuiTreeNodeFlags_None, + )) keyboardTable( + screen, + data.modify_other_keys_2, + ); + } +}; + +/// Render keyboard information with a table. +fn keyboardTable( + screen: *const terminal.Screen, + modify_other_keys_2: bool, +) void { + if (!cimgui.c.ImGui_BeginTable( + "table_keyboard", + 2, + cimgui.c.ImGuiTableFlags_None, + )) return; + defer cimgui.c.ImGui_EndTable(); + + const kitty_flags = screen.kitty_keyboard.current(); + + { + cimgui.c.ImGui_TableNextRow(); + { + _ = cimgui.c.ImGui_TableSetColumnIndex(0); + cimgui.c.ImGui_Text("Mode"); + } + { + _ = cimgui.c.ImGui_TableSetColumnIndex(1); + const mode = if (kitty_flags.int() != 0) "kitty" else "legacy"; + cimgui.c.ImGui_Text("%s", mode.ptr); + } + } + + if (kitty_flags.int() != 0) { + const Flags = @TypeOf(kitty_flags); + inline for (@typeInfo(Flags).@"struct".fields) |field| { + { + const value = @field(kitty_flags, field.name); + + cimgui.c.ImGui_TableNextRow(); + { + _ = cimgui.c.ImGui_TableSetColumnIndex(0); + const field_name = std.fmt.comptimePrint("{s}", .{field.name}); + cimgui.c.ImGui_Text("%s", field_name.ptr); + } + { + _ = cimgui.c.ImGui_TableSetColumnIndex(1); + cimgui.c.ImGui_Text( + "%s", + if (value) "true".ptr else "false".ptr, + ); + } + } + } + } else { + { + cimgui.c.ImGui_TableNextRow(); + { + _ = cimgui.c.ImGui_TableSetColumnIndex(0); + cimgui.c.ImGui_Text("Xterm modify keys"); + } + { + _ = cimgui.c.ImGui_TableSetColumnIndex(1); + cimgui.c.ImGui_Text( + "%s", + if (modify_other_keys_2) "true".ptr else "false".ptr, + ); + } + } + } // keyboard mode info + +} diff --git a/src/inspector/widgets/terminal.zig b/src/inspector/widgets/terminal.zig index 9382335fd..3dbea3886 100644 --- a/src/inspector/widgets/terminal.zig +++ b/src/inspector/widgets/terminal.zig @@ -20,6 +20,9 @@ pub const Info = struct { color_header: widgets.DetachableHeader, modes_header: widgets.DetachableHeader, + /// Screen detail windows for each screen key. + screens: ScreenMap, + pub const empty: Info = .{ .show_palette = false, .misc_header = .{}, @@ -27,6 +30,7 @@ pub const Info = struct { .mouse_header = .{}, .color_header = .{}, .modes_header = .{}, + .screens = .{}, }; /// Draw the terminal info window. @@ -72,19 +76,59 @@ pub const Info = struct { palette("palette", &t.colors.palette.current); } } - } - fn drawOpen(self: *Info, t: *Terminal) void { - { - widgets.helpMarker( - "This window displays the internal state of the terminal. " ++ - "The terminal state is global to this terminal. Some state " ++ - "is specific to the active screen or other subsystems. Values " ++ - "here reflect the running state and will update as the terminal " ++ - "application modifies them via escape sequences or shell integration. " ++ - "Some can be modified directly for debugging purposes.", + // Screen pop-out windows + var it = self.screens.iterator(); + while (it.next()) |entry| { + const screen = t.screens.get(entry.key) orelse { + // Could happen if we opened up a window for a screen + // and that screen was subsequently deinitialized. In + // this case, hide the window. + self.screens.remove(entry.key); + continue; + }; + + var title_buf: [128]u8 = undefined; + const title = std.fmt.bufPrintZ( + &title_buf, + "Screen: {t}", + .{entry.key}, + ) catch "Screen"; + + // Setup our next window so it has some size to it. + const viewport = cimgui.c.ImGui_GetMainViewport(); + cimgui.c.ImGui_SetNextWindowSize( + .{ + .x = @min(400, viewport.*.Size.x), + .y = @min(300, viewport.*.Size.y), + }, + cimgui.c.ImGuiCond_FirstUseEver, ); + + var screen_open: bool = true; + defer cimgui.c.ImGui_End(); + const screen_draw = cimgui.c.ImGui_Begin( + title, + &screen_open, + cimgui.c.ImGuiWindowFlags_NoFocusOnAppearing, + ); + entry.value.draw(screen_draw, .{ + .screen = screen, + .key = entry.key, + .active_key = t.screens.active_key, + .modify_other_keys_2 = t.flags.modify_other_keys_2, + .color_palette = &t.colors.palette, + }); + + // If the window was closed, remove it from our map so future + // renders don't draw it. + if (!screen_open) self.screens.remove(entry.key); } + } + + fn drawOpen(self: *Info, t: *Terminal) void { + // Show our screens up top. + screensTable(t, &self.screens); if (self.misc_header.header("Misc")) miscTable(t); if (self.layout_header.header("Layout")) layoutTable(t); @@ -94,6 +138,98 @@ pub const Info = struct { } }; +pub const ScreenMap = std.EnumMap( + terminal.ScreenSet.Key, + widgets.screen.Info, +); + +/// Render the table of possible screens with various actions. +fn screensTable( + t: *Terminal, + map: *ScreenMap, +) void { + if (!cimgui.c.ImGui_BeginTable( + "screens", + 3, + cimgui.c.ImGuiTableFlags_Borders | + cimgui.c.ImGuiTableFlags_RowBg | + cimgui.c.ImGuiTableFlags_SizingFixedFit, + )) return; + defer cimgui.c.ImGui_EndTable(); + + cimgui.c.ImGui_TableSetupColumn("Screen", cimgui.c.ImGuiTableColumnFlags_WidthFixed); + cimgui.c.ImGui_TableSetupColumn("Status", cimgui.c.ImGuiTableColumnFlags_WidthFixed); + cimgui.c.ImGui_TableSetupColumn("", cimgui.c.ImGuiTableColumnFlags_WidthFixed); + + // Custom header row to include help marker before "Screen" + { + cimgui.c.ImGui_TableNextRowEx(cimgui.c.ImGuiTableRowFlags_Headers, 0.0); + { + _ = cimgui.c.ImGui_TableSetColumnIndex(0); + cimgui.c.ImGui_PushStyleVarImVec2(cimgui.c.ImGuiStyleVar_FramePadding, .{ .x = 0, .y = 0 }); + widgets.helpMarker( + "A terminal can have multiple screens, only one of which is active at " ++ + "a time. Each screen has its own grid, contents, and other state. " ++ + "This section allows you to inspect the different screens managed by " ++ + "the terminal.", + ); + cimgui.c.ImGui_PopStyleVar(); + cimgui.c.ImGui_SameLineEx(0.0, cimgui.c.ImGui_GetStyle().*.ItemInnerSpacing.x); + cimgui.c.ImGui_TableHeader("Screen"); + } + { + _ = cimgui.c.ImGui_TableSetColumnIndex(1); + cimgui.c.ImGui_TableHeader("Status"); + } + { + _ = cimgui.c.ImGui_TableSetColumnIndex(2); + cimgui.c.ImGui_TableHeader(""); + } + } + + for (std.meta.tags(terminal.ScreenSet.Key)) |key| { + const is_initialized = t.screens.get(key) != null; + const is_active = t.screens.active_key == key; + + cimgui.c.ImGui_TableNextRow(); + { + _ = cimgui.c.ImGui_TableSetColumnIndex(0); + cimgui.c.ImGui_Text("%s", @tagName(key).ptr); + } + { + _ = cimgui.c.ImGui_TableSetColumnIndex(1); + if (is_active) { + cimgui.c.ImGui_TextColored( + .{ .x = 0.4, .y = 1.0, .z = 0.4, .w = 1.0 }, + "active", + ); + } else if (is_initialized) { + cimgui.c.ImGui_TextColored( + .{ .x = 0.6, .y = 0.6, .z = 0.6, .w = 1.0 }, + "initialized", + ); + } else { + cimgui.c.ImGui_TextColored( + .{ .x = 0.4, .y = 0.4, .z = 0.4, .w = 1.0 }, + "(not initialized)", + ); + } + } + { + _ = cimgui.c.ImGui_TableSetColumnIndex(2); + cimgui.c.ImGui_PushIDInt(@intFromEnum(key)); + defer cimgui.c.ImGui_PopID(); + cimgui.c.ImGui_BeginDisabled(!is_initialized); + defer cimgui.c.ImGui_EndDisabled(); + if (cimgui.c.ImGui_Button("View")) { + if (!map.contains(key)) { + map.put(key, .empty); + } + } + } + } +} + /// Table of miscellaneous terminal information. fn miscTable(t: *Terminal) void { _ = cimgui.c.ImGui_BeginTable( -- 2.51.2