inspector: remove cell picker

This commit is contained in:
Mitchell Hashimoto
2026-01-30 09:18:31 -08:00
parent 3793188e38
commit 400d17aa0d
4 changed files with 1 additions and 383 deletions

View File

@@ -3874,36 +3874,8 @@ pub fn mouseButtonCallback(
// log.debug("mouse action={} button={} mods={}", .{ action, button, mods });
// If we have an inspector, we always queue a render
if (self.inspector) |insp| {
if (self.inspector != null) {
defer self.queueRender() catch {};
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
// If the inspector is requesting a cell, then we intercept
// left mouse clicks and send them to the inspector.
if (insp.cell == .requested and
button == .left and
action == .press)
{
const pos = try self.rt_surface.getCursorPos();
const point = self.posToViewport(pos.x, pos.y);
const screen: *terminal.Screen = self.renderer_state.terminal.screens.active;
const p = screen.pages.pin(.{ .viewport = point }) orelse {
log.warn("failed to get pin for clicked point", .{});
return false;
};
insp.cell.select(
self.alloc,
p,
point.x,
point.y,
) catch |err| {
log.warn("error selecting cell for inspector err={}", .{err});
};
return false;
}
}
// Always record our latest mouse state

View File

@@ -14,76 +14,13 @@ const terminal = @import("../terminal/main.zig");
const inspector = @import("main.zig");
const widgets = @import("widgets.zig");
/// The window names. These are used with docking so we need to have access.
const window_cell = "Cell";
const window_termio = "Terminal IO";
const window_imgui_demo = "Dear ImGui Demo";
/// Mouse state that we track in addition to normal mouse states that
/// Ghostty always knows about.
mouse: widgets.surface.Mouse = .{},
/// A selected cell.
cell: CellInspect = .{ .idle = {} },
// ImGui state
gui: widgets.surface.Inspector,
const CellInspect = union(enum) {
/// Idle, no cell inspection is requested
idle: void,
/// Requested, a cell is being picked.
requested: void,
/// The cell has been picked and set to this. This is a copy so that
/// if the cell contents change we still have the original cell.
selected: Selected,
const Selected = struct {
alloc: Allocator,
row: usize,
col: usize,
cell: inspector.Cell,
};
pub fn deinit(self: *CellInspect) void {
switch (self.*) {
.idle, .requested => {},
.selected => |*v| v.cell.deinit(v.alloc),
}
}
pub fn request(self: *CellInspect) void {
switch (self.*) {
.idle => self.* = .requested,
.selected => |*v| {
v.cell.deinit(v.alloc);
self.* = .requested;
},
.requested => {},
}
}
pub fn select(
self: *CellInspect,
alloc: Allocator,
pin: terminal.Pin,
x: usize,
y: usize,
) !void {
assert(self.* == .requested);
const cell = try inspector.Cell.init(alloc, pin);
errdefer cell.deinit(alloc);
self.* = .{ .selected = .{
.alloc = alloc,
.row = y,
.col = x,
.cell = cell,
} };
}
};
/// Setup the ImGui state. This requires an ImGui context to be set.
pub fn setup() void {
const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
@@ -126,7 +63,6 @@ pub fn init(alloc: Allocator) !Inspector {
pub fn deinit(self: *Inspector, alloc: Allocator) void {
self.gui.deinit(alloc);
self.cell.deinit();
}
/// Record a keyboard event.
@@ -179,66 +115,3 @@ pub fn render(
self.mouse,
);
}
/// TODO: OLD, REMOVE EVENTUALLY ONCE WE MIGRATE FUNCTIONALITY
fn renderCellWindow(self: *Inspector) void {
// Start our window. If we're collapsed we do nothing.
defer cimgui.c.ImGui_End();
if (!cimgui.c.ImGui_Begin(
window_cell,
null,
cimgui.c.ImGuiWindowFlags_NoFocusOnAppearing,
)) return;
// Our popup for the picker
const popup_picker = "Cell Picker";
if (cimgui.c.ImGui_Button("Picker")) {
// Request a cell
self.cell.request();
cimgui.c.ImGui_OpenPopup(
popup_picker,
cimgui.c.ImGuiPopupFlags_None,
);
}
if (cimgui.c.ImGui_BeginPopupModal(
popup_picker,
null,
cimgui.c.ImGuiWindowFlags_AlwaysAutoResize,
)) popup: {
defer cimgui.c.ImGui_EndPopup();
// Once we select a cell, close this popup.
if (self.cell == .selected) {
cimgui.c.ImGui_CloseCurrentPopup();
break :popup;
}
cimgui.c.ImGui_Text(
"Click on a cell in the terminal to inspect it.\n" ++
"The click will be intercepted by the picker, \n" ++
"so it won't be sent to the terminal.",
);
cimgui.c.ImGui_Separator();
if (cimgui.c.ImGui_Button("Cancel")) {
cimgui.c.ImGui_CloseCurrentPopup();
}
} // cell pick popup
cimgui.c.ImGui_Separator();
if (self.cell != .selected) {
cimgui.c.ImGui_Text("No cell selected.");
return;
}
const selected = self.cell.selected;
selected.cell.renderTable(
self.surface.renderer_state.terminal,
selected.col,
selected.row,
);
}

View File

@@ -1,223 +0,0 @@
const std = @import("std");
const assert = @import("../quirks.zig").inlineAssert;
const Allocator = std.mem.Allocator;
const cimgui = @import("dcimgui");
const terminal = @import("../terminal/main.zig");
/// A cell being inspected. This duplicates much of the data in
/// the terminal data structure because we want the inspector to
/// not have a reference to the terminal state or to grab any
/// locks.
pub const Cell = struct {
/// The main codepoint for this cell.
codepoint: u21,
/// Codepoints for this cell to produce a single grapheme cluster.
/// This is only non-empty if the cell is part of a multi-codepoint
/// grapheme cluster. This does NOT include the primary codepoint.
cps: []const u21,
/// The style of this cell.
style: terminal.Style,
/// Wide state of the terminal cell
wide: terminal.Cell.Wide,
pub fn init(
alloc: Allocator,
pin: terminal.Pin,
) !Cell {
const cell = pin.rowAndCell().cell;
const style = pin.style(cell);
const cps: []const u21 = if (cell.hasGrapheme()) cps: {
const src = pin.grapheme(cell).?;
assert(src.len > 0);
break :cps try alloc.dupe(u21, src);
} else &.{};
errdefer if (cps.len > 0) alloc.free(cps);
return .{
.codepoint = cell.codepoint(),
.cps = cps,
.style = style,
.wide = cell.wide,
};
}
pub fn deinit(self: *Cell, alloc: Allocator) void {
if (self.cps.len > 0) alloc.free(self.cps);
}
pub fn renderTable(
self: *const Cell,
t: *const terminal.Terminal,
x: usize,
y: usize,
) void {
// We have a selected cell, show information about it.
_ = cimgui.c.ImGui_BeginTable(
"table_cursor",
2,
cimgui.c.ImGuiTableFlags_None,
);
defer cimgui.c.ImGui_EndTable();
{
cimgui.c.ImGui_TableNextRow();
{
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Grid Position");
}
{
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("row=%d col=%d", y, x);
}
}
// NOTE: we don't currently write the character itself because
// we haven't hooked up imgui to our font system. That's hard! We
// can/should instead hook up our renderer to imgui and just render
// the single glyph in an image view so it looks _identical_ to the
// terminal.
codepoint: {
cimgui.c.ImGui_TableNextRow();
{
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Codepoints");
}
{
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
if (cimgui.c.ImGui_BeginListBox("##codepoints", .{ .x = 0, .y = 0 })) {
defer cimgui.c.ImGui_EndListBox();
if (self.codepoint == 0) {
_ = cimgui.c.ImGui_SelectableEx("(empty)", false, 0, .{});
break :codepoint;
}
// Primary codepoint
var buf: [256]u8 = undefined;
{
const key = std.fmt.bufPrintZ(&buf, "U+{X}", .{self.codepoint}) catch
"<internal error>";
_ = cimgui.c.ImGui_SelectableEx(key.ptr, false, 0, .{});
}
// All extras
for (self.cps) |cp| {
const key = std.fmt.bufPrintZ(&buf, "U+{X}", .{cp}) catch
"<internal error>";
_ = cimgui.c.ImGui_SelectableEx(key.ptr, false, 0, .{});
}
}
}
}
// Character width property
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Width Property");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text(@tagName(self.wide));
// If we have a color then we show the color
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Foreground Color");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
switch (self.style.fg_color) {
.none => cimgui.c.ImGui_Text("default"),
.palette => |idx| {
const rgb = t.colors.palette.current[idx];
cimgui.c.ImGui_Text("Palette %d", idx);
var color: [3]f32 = .{
@as(f32, @floatFromInt(rgb.r)) / 255,
@as(f32, @floatFromInt(rgb.g)) / 255,
@as(f32, @floatFromInt(rgb.b)) / 255,
};
_ = cimgui.c.ImGui_ColorEdit3(
"color_fg",
&color,
cimgui.c.ImGuiColorEditFlags_DisplayHex |
cimgui.c.ImGuiColorEditFlags_NoPicker |
cimgui.c.ImGuiColorEditFlags_NoLabel,
);
},
.rgb => |rgb| {
var color: [3]f32 = .{
@as(f32, @floatFromInt(rgb.r)) / 255,
@as(f32, @floatFromInt(rgb.g)) / 255,
@as(f32, @floatFromInt(rgb.b)) / 255,
};
_ = cimgui.c.ImGui_ColorEdit3(
"color_fg",
&color,
cimgui.c.ImGuiColorEditFlags_DisplayHex |
cimgui.c.ImGuiColorEditFlags_NoPicker |
cimgui.c.ImGuiColorEditFlags_NoLabel,
);
},
}
cimgui.c.ImGui_TableNextRow();
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text("Background Color");
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
switch (self.style.bg_color) {
.none => cimgui.c.ImGui_Text("default"),
.palette => |idx| {
const rgb = t.colors.palette.current[idx];
cimgui.c.ImGui_Text("Palette %d", idx);
var color: [3]f32 = .{
@as(f32, @floatFromInt(rgb.r)) / 255,
@as(f32, @floatFromInt(rgb.g)) / 255,
@as(f32, @floatFromInt(rgb.b)) / 255,
};
_ = cimgui.c.ImGui_ColorEdit3(
"color_bg",
&color,
cimgui.c.ImGuiColorEditFlags_DisplayHex |
cimgui.c.ImGuiColorEditFlags_NoPicker |
cimgui.c.ImGuiColorEditFlags_NoLabel,
);
},
.rgb => |rgb| {
var color: [3]f32 = .{
@as(f32, @floatFromInt(rgb.r)) / 255,
@as(f32, @floatFromInt(rgb.g)) / 255,
@as(f32, @floatFromInt(rgb.b)) / 255,
};
_ = cimgui.c.ImGui_ColorEdit3(
"color_bg",
&color,
cimgui.c.ImGuiColorEditFlags_DisplayHex |
cimgui.c.ImGuiColorEditFlags_NoPicker |
cimgui.c.ImGuiColorEditFlags_NoLabel,
);
},
}
// Boolean styles
const styles = .{
"bold", "italic", "faint", "blink",
"inverse", "invisible", "strikethrough",
};
inline for (styles) |style| style: {
if (!@field(self.style.flags, style)) break :style;
cimgui.c.ImGui_TableNextRow();
{
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
cimgui.c.ImGui_Text(style.ptr);
}
{
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
cimgui.c.ImGui_Text("true");
}
}
cimgui.c.ImGui_TextDisabled("(Any styles not shown are not currently set)");
}
};

View File

@@ -1,7 +1,3 @@
// TODO: Remove
pub const cell = @import("cell.zig");
pub const Cell = cell.Cell;
pub const widgets = @import("widgets.zig");
pub const Inspector = @import("Inspector.zig");