From 41b5dc492881c79ce96cab9fa60d8fca3393cf9f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 27 Jan 2026 09:08:37 -0800 Subject: [PATCH] inspector: terminal tab --- src/inspector/Inspector.zig | 10 +- src/inspector/main.zig | 1 + src/inspector/terminal.zig | 215 ++++++++++++++++++++++++++++++++++++ src/inspector/widgets.zig | 14 +++ 4 files changed, 239 insertions(+), 1 deletion(-) create mode 100644 src/inspector/terminal.zig create mode 100644 src/inspector/widgets.zig diff --git a/src/inspector/Inspector.zig b/src/inspector/Inspector.zig index 6ffb43d43..a5204ef89 100644 --- a/src/inspector/Inspector.zig +++ b/src/inspector/Inspector.zig @@ -62,6 +62,11 @@ need_scroll_to_selected: bool = false, /// Flag indicating whether the selection was made by keyboard is_keyboard_selection: bool = false, +/// Windows +windows: struct { + terminal: inspector.terminal.Window = .{}, +} = .{}, + /// Enum representing keyboard navigation actions const KeyAction = enum { down, @@ -229,6 +234,8 @@ pub fn render(self: *Inspector) void { { self.surface.renderer_state.mutex.lock(); defer self.surface.renderer_state.mutex.unlock(); + const t = self.surface.renderer_state.terminal; + self.windows.terminal.render(t); self.renderScreenWindow(); self.renderModesWindow(); self.renderKeyboardWindow(); @@ -257,7 +264,7 @@ fn setupLayout(self: *Inspector, dock_id_main: cimgui.c.ImGuiID) void { _ = self; // Our initial focus - cimgui.c.ImGui_SetWindowFocusStr(window_screen); + cimgui.c.ImGui_SetWindowFocusStr(inspector.terminal.Window.name); // Setup our initial layout. const dock_id: struct { @@ -285,6 +292,7 @@ fn setupLayout(self: *Inspector, dock_id_main: cimgui.c.ImGuiID) void { cimgui.ImGui_DockBuilderDockWindow(window_keyboard, dock_id.left); cimgui.ImGui_DockBuilderDockWindow(window_termio, dock_id.left); cimgui.ImGui_DockBuilderDockWindow(window_screen, dock_id.left); + cimgui.ImGui_DockBuilderDockWindow(inspector.terminal.Window.name, dock_id.left); cimgui.ImGui_DockBuilderDockWindow(window_imgui_demo, dock_id.left); cimgui.ImGui_DockBuilderDockWindow(window_size, dock_id.right); cimgui.ImGui_DockBuilderFinish(dock_id_main); diff --git a/src/inspector/main.zig b/src/inspector/main.zig index ee871f200..8299c0a0d 100644 --- a/src/inspector/main.zig +++ b/src/inspector/main.zig @@ -4,6 +4,7 @@ pub const cursor = @import("cursor.zig"); pub const key = @import("key.zig"); pub const page = @import("page.zig"); pub const termio = @import("termio.zig"); +pub const terminal = @import("terminal.zig"); pub const Cell = cell.Cell; pub const Inspector = @import("Inspector.zig"); diff --git a/src/inspector/terminal.zig b/src/inspector/terminal.zig new file mode 100644 index 000000000..981fb1293 --- /dev/null +++ b/src/inspector/terminal.zig @@ -0,0 +1,215 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const cimgui = @import("dcimgui"); +const terminal = @import("../terminal/main.zig"); +const Terminal = terminal.Terminal; +const widgets = @import("widgets.zig"); + +/// Window to show terminal state information. +pub const Window = struct { + /// Window name/id. + pub const name = "Terminal"; + + // Render + pub fn render(self: *Window, t: *Terminal) void { + _ = self; + + // Start our window. If we're collapsed we do nothing. + defer cimgui.c.ImGui_End(); + if (!cimgui.c.ImGui_Begin( + name, + null, + cimgui.c.ImGuiWindowFlags_NoFocusOnAppearing, + )) return; + + if (cimgui.c.ImGui_CollapsingHeader( + "General", + cimgui.c.ImGuiTreeNodeFlags_DefaultOpen, + )) { + _ = cimgui.c.ImGui_BeginTable( + "table_general", + 2, + cimgui.c.ImGuiTableFlags_None, + ); + defer cimgui.c.ImGui_EndTable(); + + { + cimgui.c.ImGui_TableNextRow(); + { + _ = cimgui.c.ImGui_TableSetColumnIndex(0); + cimgui.c.ImGui_Text("Working Directory"); + cimgui.c.ImGui_SameLine(); + widgets.helpMarker("The current working directory reported by the shell."); + } + { + _ = cimgui.c.ImGui_TableSetColumnIndex(1); + if (t.pwd.items.len > 0) { + cimgui.c.ImGui_Text( + "%.*s", + t.pwd.items.len, + t.pwd.items.ptr, + ); + } else { + cimgui.c.ImGui_TextDisabled("(none)"); + } + } + } + + { + cimgui.c.ImGui_TableNextRow(); + { + _ = cimgui.c.ImGui_TableSetColumnIndex(0); + cimgui.c.ImGui_Text("Focused"); + cimgui.c.ImGui_SameLine(); + widgets.helpMarker("Whether the terminal itself is currently focused."); + } + { + _ = cimgui.c.ImGui_TableSetColumnIndex(1); + var value: bool = t.flags.focused; + _ = cimgui.c.ImGui_Checkbox("##focused", &value); + } + } + + { + cimgui.c.ImGui_TableNextRow(); + { + _ = cimgui.c.ImGui_TableSetColumnIndex(0); + cimgui.c.ImGui_Text("Previous Char"); + cimgui.c.ImGui_SameLine(); + widgets.helpMarker("The previously printed character, used only for the REP sequence."); + } + { + _ = cimgui.c.ImGui_TableSetColumnIndex(1); + if (t.previous_char) |c| { + cimgui.c.ImGui_Text("U+%04X", @as(u32, c)); + } else { + cimgui.c.ImGui_TextDisabled("(none)"); + } + } + } + } + + if (cimgui.c.ImGui_CollapsingHeader( + "Layout", + cimgui.c.ImGuiTreeNodeFlags_DefaultOpen, + )) { + _ = 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 we modified it then update our scrolling region + // directly. + if (changed and + override.top < override.bottom and + override.left < override.right) + { + t.scrolling_region = override; + } + } + } + } // cursor + } +}; diff --git a/src/inspector/widgets.zig b/src/inspector/widgets.zig new file mode 100644 index 000000000..224e8b1ba --- /dev/null +++ b/src/inspector/widgets.zig @@ -0,0 +1,14 @@ +const cimgui = @import("dcimgui"); + +/// Draws a "(?)" disabled text marker that shows some help text +/// on hover. +pub fn helpMarker(text: [:0]const u8) void { + cimgui.c.ImGui_TextDisabled("(?)"); + if (!cimgui.c.ImGui_BeginItemTooltip()) return; + defer cimgui.c.ImGui_EndTooltip(); + + cimgui.c.ImGui_PushTextWrapPos(cimgui.c.ImGui_GetFontSize() * 35.0); + defer cimgui.c.ImGui_PopTextWrapPos(); + + cimgui.c.ImGui_TextUnformatted(text.ptr); +}