From 28086a7adc60dcedea40100ea795d13a345afaeb Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 28 Jan 2026 11:15:06 -0800 Subject: [PATCH] inspector: terminal migrate content --- src/inspector/widgets/terminal.zig | 493 ++++++++++++++++++++++++++++- 1 file changed, 483 insertions(+), 10 deletions(-) diff --git a/src/inspector/widgets/terminal.zig b/src/inspector/widgets/terminal.zig index 9025e1e63..9382335fd 100644 --- a/src/inspector/widgets/terminal.zig +++ b/src/inspector/widgets/terminal.zig @@ -5,17 +5,30 @@ const Allocator = std.mem.Allocator; const cimgui = @import("dcimgui"); const widgets = @import("../widgets.zig"); const terminal = @import("../../terminal/main.zig"); +const modes = terminal.modes; const Terminal = terminal.Terminal; +/// Terminal information inspector widget. pub const Info = struct { + /// True if we're showing the 256-color palette window. + show_palette: bool, + + /// The various detachable headers. misc_header: widgets.DetachableHeader, + layout_header: widgets.DetachableHeader, + mouse_header: widgets.DetachableHeader, + color_header: widgets.DetachableHeader, + modes_header: widgets.DetachableHeader, pub const empty: Info = .{ + .show_palette = false, .misc_header = .{}, + .layout_header = .{}, + .mouse_header = .{}, + .color_header = .{}, + .modes_header = .{}, }; - const misc_header_label = "Misc"; - /// Draw the terminal info window. pub fn draw( self: *Info, @@ -27,18 +40,43 @@ pub const Info = struct { // Draw our detached state that draws regardless of if // we're open or not. - if (self.misc_header.window(misc_header_label)) |visible| { + if (self.misc_header.window("Terminal Misc")) |visible| { defer self.misc_header.windowEnd(); if (visible) miscTable(t); } + if (self.layout_header.window("Terminal Layout")) |visible| { + defer self.layout_header.windowEnd(); + if (visible) layoutTable(t); + } + if (self.mouse_header.window("Terminal Mouse")) |visible| { + defer self.mouse_header.windowEnd(); + if (visible) mouseTable(t); + } + if (self.color_header.window("Terminal Color")) |visible| { + defer self.color_header.windowEnd(); + if (visible) colorTable(t, &self.show_palette); + } + if (self.modes_header.window("Terminal Modes")) |visible| { + defer self.modes_header.windowEnd(); + if (visible) modesTable(t); + } + + // Palette pop-out window + if (self.show_palette) { + defer cimgui.c.ImGui_End(); + if (cimgui.c.ImGui_Begin( + "256-Color Palette", + &self.show_palette, + cimgui.c.ImGuiWindowFlags_NoFocusOnAppearing, + )) { + palette("palette", &t.colors.palette.current); + } + } } fn drawOpen(self: *Info, t: *Terminal) void { - if (cimgui.c.ImGui_CollapsingHeader( - "Help", - cimgui.c.ImGuiTreeNodeFlags_None, - )) { - cimgui.c.ImGui_TextWrapped( + { + 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 " ++ @@ -48,11 +86,16 @@ pub const Info = struct { ); } - if (self.misc_header.header(misc_header_label)) miscTable(t); + if (self.misc_header.header("Misc")) miscTable(t); + if (self.layout_header.header("Layout")) layoutTable(t); + if (self.mouse_header.header("Mouse")) mouseTable(t); + if (self.color_header.header("Color")) colorTable(t, &self.show_palette); + if (self.modes_header.header("Modes")) modesTable(t); } }; -pub fn miscTable(t: *Terminal) void { +/// Table of miscellaneous terminal information. +fn miscTable(t: *Terminal) void { _ = cimgui.c.ImGui_BeginTable( "table_misc", 2, @@ -115,3 +158,433 @@ pub fn miscTable(t: *Terminal) void { } } } + +/// Table of terminal layout information. +fn layoutTable(t: *Terminal) void { + _ = cimgui.c.ImGui_BeginTable( + "table_layout", + 2, + cimgui.c.ImGuiTableFlags_None, + ); + defer cimgui.c.ImGui_EndTable(); + + { + cimgui.c.ImGui_TableNextRow(); + { + _ = cimgui.c.ImGui_TableSetColumnIndex(0); + cimgui.c.ImGui_Text("Grid"); + cimgui.c.ImGui_SameLine(); + widgets.helpMarker("The size of the terminal grid in columns and rows."); + } + { + _ = cimgui.c.ImGui_TableSetColumnIndex(1); + cimgui.c.ImGui_Text( + "%dc x %dr", + t.cols, + t.rows, + ); + } + } + + { + cimgui.c.ImGui_TableNextRow(); + { + _ = cimgui.c.ImGui_TableSetColumnIndex(0); + cimgui.c.ImGui_Text("Pixels"); + cimgui.c.ImGui_SameLine(); + widgets.helpMarker("The size of the terminal grid in pixels."); + } + { + _ = cimgui.c.ImGui_TableSetColumnIndex(1); + cimgui.c.ImGui_Text( + "%dw x %dh", + t.width_px, + t.height_px, + ); + } + } + + { + cimgui.c.ImGui_TableNextRow(); + { + _ = cimgui.c.ImGui_TableSetColumnIndex(0); + cimgui.c.ImGui_Text("Scroll Region"); + cimgui.c.ImGui_SameLine(); + widgets.helpMarker("The scrolling region boundaries (top, bottom, left, right)."); + } + { + _ = cimgui.c.ImGui_TableSetColumnIndex(1); + cimgui.c.ImGui_PushItemWidth(cimgui.c.ImGui_CalcTextSize("00000").x); + defer cimgui.c.ImGui_PopItemWidth(); + + var override = t.scrolling_region; + var changed = false; + + cimgui.c.ImGui_AlignTextToFramePadding(); + cimgui.c.ImGui_Text("T:"); + cimgui.c.ImGui_SameLine(); + if (cimgui.c.ImGui_InputScalar( + "##scroll_top", + cimgui.c.ImGuiDataType_U16, + &override.top, + )) { + override.top = @min(override.top, t.rows -| 1); + changed = true; + } + + cimgui.c.ImGui_SameLine(); + cimgui.c.ImGui_Text("B:"); + cimgui.c.ImGui_SameLine(); + if (cimgui.c.ImGui_InputScalar( + "##scroll_bottom", + cimgui.c.ImGuiDataType_U16, + &override.bottom, + )) { + override.bottom = @min(override.bottom, t.rows -| 1); + changed = true; + } + + cimgui.c.ImGui_SameLine(); + cimgui.c.ImGui_Text("L:"); + cimgui.c.ImGui_SameLine(); + if (cimgui.c.ImGui_InputScalar( + "##scroll_left", + cimgui.c.ImGuiDataType_U16, + &override.left, + )) { + override.left = @min(override.left, t.cols -| 1); + changed = true; + } + + cimgui.c.ImGui_SameLine(); + cimgui.c.ImGui_Text("R:"); + cimgui.c.ImGui_SameLine(); + if (cimgui.c.ImGui_InputScalar( + "##scroll_right", + cimgui.c.ImGuiDataType_U16, + &override.right, + )) { + override.right = @min(override.right, t.cols -| 1); + changed = true; + } + + if (changed and + override.top < override.bottom and + override.left < override.right) + { + t.scrolling_region = override; + } + } + } +} + +/// Table of mouse-related terminal information. +fn mouseTable(t: *Terminal) void { + _ = cimgui.c.ImGui_BeginTable( + "table_mouse", + 2, + cimgui.c.ImGuiTableFlags_None, + ); + defer cimgui.c.ImGui_EndTable(); + + { + cimgui.c.ImGui_TableNextRow(); + { + _ = cimgui.c.ImGui_TableSetColumnIndex(0); + cimgui.c.ImGui_Text("Event Mode"); + cimgui.c.ImGui_SameLine(); + widgets.helpMarker("The mouse event reporting mode set by the application."); + } + { + _ = cimgui.c.ImGui_TableSetColumnIndex(1); + cimgui.c.ImGui_Text("%s", @tagName(t.flags.mouse_event).ptr); + } + } + + { + cimgui.c.ImGui_TableNextRow(); + { + _ = cimgui.c.ImGui_TableSetColumnIndex(0); + cimgui.c.ImGui_Text("Format"); + cimgui.c.ImGui_SameLine(); + widgets.helpMarker("The mouse event encoding format."); + } + { + _ = cimgui.c.ImGui_TableSetColumnIndex(1); + cimgui.c.ImGui_Text("%s", @tagName(t.flags.mouse_format).ptr); + } + } + + { + cimgui.c.ImGui_TableNextRow(); + { + _ = cimgui.c.ImGui_TableSetColumnIndex(0); + cimgui.c.ImGui_Text("Shape"); + cimgui.c.ImGui_SameLine(); + widgets.helpMarker("The current mouse cursor shape set by the application."); + } + { + _ = cimgui.c.ImGui_TableSetColumnIndex(1); + cimgui.c.ImGui_Text("%s", @tagName(t.mouse_shape).ptr); + } + } + + { + cimgui.c.ImGui_TableNextRow(); + { + _ = cimgui.c.ImGui_TableSetColumnIndex(0); + cimgui.c.ImGui_Text("Shift Capture"); + cimgui.c.ImGui_SameLine(); + widgets.helpMarker("XTSHIFTESCAPE state for capturing shift in mouse protocol."); + } + { + _ = cimgui.c.ImGui_TableSetColumnIndex(1); + if (t.flags.mouse_shift_capture == .null) { + cimgui.c.ImGui_TextDisabled("(unset)"); + } else { + cimgui.c.ImGui_Text("%s", @tagName(t.flags.mouse_shift_capture).ptr); + } + } + } +} + +/// Table of color-related terminal information. +fn colorTable( + t: *Terminal, + show_palette: *bool, +) void { + cimgui.c.ImGui_TextWrapped( + "Color state for the terminal. Note these colors only apply " ++ + "to the palette and unstyled colors. Many modern terminal " ++ + "applications use direct RGB colors which are not reflected here.", + ); + cimgui.c.ImGui_Separator(); + + _ = cimgui.c.ImGui_BeginTable( + "table_color", + 2, + cimgui.c.ImGuiTableFlags_None, + ); + defer cimgui.c.ImGui_EndTable(); + + { + cimgui.c.ImGui_TableNextRow(); + { + _ = cimgui.c.ImGui_TableSetColumnIndex(0); + cimgui.c.ImGui_Text("Background"); + cimgui.c.ImGui_SameLine(); + widgets.helpMarker("Unstyled cell background color."); + } + { + _ = cimgui.c.ImGui_TableSetColumnIndex(1); + _ = dynamicRGB( + "bg_color", + &t.colors.background, + ); + } + } + + { + cimgui.c.ImGui_TableNextRow(); + { + _ = cimgui.c.ImGui_TableSetColumnIndex(0); + cimgui.c.ImGui_Text("Foreground"); + cimgui.c.ImGui_SameLine(); + widgets.helpMarker("Unstyled cell foreground color."); + } + { + _ = cimgui.c.ImGui_TableSetColumnIndex(1); + _ = dynamicRGB( + "fg_color", + &t.colors.foreground, + ); + } + } + + { + cimgui.c.ImGui_TableNextRow(); + { + _ = cimgui.c.ImGui_TableSetColumnIndex(0); + cimgui.c.ImGui_Text("Cursor"); + cimgui.c.ImGui_SameLine(); + widgets.helpMarker("Cursor coloring set by escape sequences."); + } + { + _ = cimgui.c.ImGui_TableSetColumnIndex(1); + _ = dynamicRGB( + "cursor_color", + &t.colors.cursor, + ); + } + } + + { + cimgui.c.ImGui_TableNextRow(); + { + _ = cimgui.c.ImGui_TableSetColumnIndex(0); + cimgui.c.ImGui_Text("Palette"); + cimgui.c.ImGui_SameLine(); + widgets.helpMarker("The 256-color palette."); + } + { + _ = cimgui.c.ImGui_TableSetColumnIndex(1); + if (cimgui.c.ImGui_Button("View")) { + show_palette.* = true; + } + } + } +} + +/// Table of terminal modes. +fn modesTable(t: *Terminal) void { + _ = cimgui.c.ImGui_BeginTable( + "table_modes", + 3, + cimgui.c.ImGuiTableFlags_SizingFixedFit | + cimgui.c.ImGuiTableFlags_RowBg, + ); + defer cimgui.c.ImGui_EndTable(); + + { + cimgui.c.ImGui_TableSetupColumn("", cimgui.c.ImGuiTableColumnFlags_NoResize); + cimgui.c.ImGui_TableSetupColumn("Number", cimgui.c.ImGuiTableColumnFlags_PreferSortAscending); + cimgui.c.ImGui_TableSetupColumn("Name", cimgui.c.ImGuiTableColumnFlags_WidthStretch); + cimgui.c.ImGui_TableHeadersRow(); + } + + inline for (@typeInfo(terminal.Mode).@"enum".fields) |field| { + @setEvalBranchQuota(6000); + const tag: modes.ModeTag = @bitCast(@as(modes.ModeTag.Backing, field.value)); + + cimgui.c.ImGui_TableNextRow(); + cimgui.c.ImGui_PushIDInt(@intCast(field.value)); + defer cimgui.c.ImGui_PopID(); + { + _ = cimgui.c.ImGui_TableSetColumnIndex(0); + var value: bool = t.modes.get(@field(terminal.Mode, field.name)); + _ = cimgui.c.ImGui_Checkbox("##checkbox", &value); + } + { + _ = cimgui.c.ImGui_TableSetColumnIndex(1); + cimgui.c.ImGui_Text( + "%s%d", + if (tag.ansi) "" else "?", + @as(u32, @intCast(tag.value)), + ); + } + { + _ = cimgui.c.ImGui_TableSetColumnIndex(2); + const name = std.fmt.comptimePrint("{s}", .{field.name}); + cimgui.c.ImGui_Text("%s", name.ptr); + } + } +} + +/// Render a DynamicRGB color. +fn dynamicRGB( + label: [:0]const u8, + rgb: *terminal.color.DynamicRGB, +) bool { + _ = cimgui.c.ImGui_BeginTable( + label, + if (rgb.override != null) 2 else 1, + cimgui.c.ImGuiTableFlags_SizingFixedFit, + ); + defer cimgui.c.ImGui_EndTable(); + + if (rgb.override != null) cimgui.c.ImGui_TableSetupColumn( + "##label", + cimgui.c.ImGuiTableColumnFlags_WidthFixed, + ); + cimgui.c.ImGui_TableSetupColumn( + "##value", + cimgui.c.ImGuiTableColumnFlags_WidthStretch, + ); + + if (rgb.override) |c| { + cimgui.c.ImGui_TableNextRow(); + _ = cimgui.c.ImGui_TableSetColumnIndex(0); + cimgui.c.ImGui_Text("override:"); + cimgui.c.ImGui_SameLine(); + widgets.helpMarker("Overridden color set by escape sequences."); + + _ = cimgui.c.ImGui_TableSetColumnIndex(1); + var col = [3]f32{ + @as(f32, @floatFromInt(c.r)) / 255.0, + @as(f32, @floatFromInt(c.g)) / 255.0, + @as(f32, @floatFromInt(c.b)) / 255.0, + }; + _ = cimgui.c.ImGui_ColorEdit3( + "##override", + &col, + cimgui.c.ImGuiColorEditFlags_None, + ); + } + + cimgui.c.ImGui_TableNextRow(); + _ = cimgui.c.ImGui_TableSetColumnIndex(0); + if (rgb.default) |c| { + if (rgb.override != null) { + cimgui.c.ImGui_Text("default:"); + cimgui.c.ImGui_SameLine(); + widgets.helpMarker("Default color from configuration."); + + _ = cimgui.c.ImGui_TableSetColumnIndex(1); + } + + var col = [3]f32{ + @as(f32, @floatFromInt(c.r)) / 255.0, + @as(f32, @floatFromInt(c.g)) / 255.0, + @as(f32, @floatFromInt(c.b)) / 255.0, + }; + _ = cimgui.c.ImGui_ColorEdit3( + "##default", + &col, + cimgui.c.ImGuiColorEditFlags_None, + ); + } else { + cimgui.c.ImGui_TextDisabled("(unset)"); + } + + return false; +} + +/// Render a color palette as a 16x16 grid of color buttons. +fn palette( + label: [:0]const u8, + pal: *const terminal.color.Palette, +) void { + cimgui.c.ImGui_PushID(label); + defer cimgui.c.ImGui_PopID(); + + for (0..16) |row| { + for (0..16) |col| { + const idx = row * 16 + col; + const rgb = pal[idx]; + var col_arr = [3]f32{ + @as(f32, @floatFromInt(rgb.r)) / 255.0, + @as(f32, @floatFromInt(rgb.g)) / 255.0, + @as(f32, @floatFromInt(rgb.b)) / 255.0, + }; + + if (col > 0) cimgui.c.ImGui_SameLine(); + + cimgui.c.ImGui_PushIDInt(@intCast(idx)); + _ = cimgui.c.ImGui_ColorEdit3( + "##color", + &col_arr, + cimgui.c.ImGuiColorEditFlags_NoInputs, + ); + if (cimgui.c.ImGui_IsItemHovered(cimgui.c.ImGuiHoveredFlags_DelayShort)) { + cimgui.c.ImGui_SetTooltip( + "%d: #%02X%02X%02X", + idx, + rgb.r, + rgb.g, + rgb.b, + ); + } + cimgui.c.ImGui_PopID(); + } + } +}