Files
Mitchell Hashimoto 2355550a94 libghostty: add tracked grid ref API
Add a C API for tracked pins, known as a tracked grid ref in C.

The new API can create tracked refs from terminal points, snapshot them
back to regular grid refs for cell access, convert them to coordinates,
move them to a new point, report when their semantic location was lost,
and free the tracked pin bookkeeping. This is backed by PageList tracked
pins and exposed through the libghostty-vt export layer and headers.
2026-05-23 14:37:59 -07:00

105 lines
3.9 KiB
Zig

const std = @import("std");
const Allocator = std.mem.Allocator;
const lib = @import("lib.zig");
const size = @import("size.zig");
/// The possible reference locations for a point. When someone says "(42, 80)"
/// in the context of a terminal, that could mean multiple things: it is in the
/// current visible viewport? the current active area of the screen where the
/// cursor is? the entire scrollback history? etc.
///
/// This tag is used to differentiate those cases.
pub const Tag = lib.Enum(lib.target, &.{
// Top-left is part of the active area where a running program can
// jump the cursor and make changes. The active area is the "editable"
// part of the screen.
//
// The bottom-right of the active tag differs from all other tags
// because it includes the full height (rows) of the screen, including
// rows that may not be written yet. This is required because the active
// area is fully "addressable" by the running program (see below) whereas
// the other tags are used primarily for reading/modifying past-written
// data so they can't address unwritten rows.
//
// Note for those less familiar with terminal functionality: there
// are escape sequences to move the cursor to any position on
// the screen, but it is limited to the size of the viewport and
// the bottommost part of the screen. Terminal programs can't --
// with sequences at the time of writing this comment -- modify
// anything in the scrollback, visible viewport (if it differs
// from the active area), etc.
"active",
// Top-left is the visible viewport. This means that if the user
// has scrolled in any direction, top-left changes. The bottom-right
// is the last written row from the top-left.
"viewport",
// Top-left is the furthest back in the scrollback history
// supported by the screen and the bottom-right is the bottom-right
// of the last written row. Note this last point is important: the
// bottom right is NOT necessarily the same as "active" because
// "active" always allows referencing the full rows tall of the
// screen whereas "screen" only contains written rows.
"screen",
// The top-left is the same as "screen" but the bottom-right is
// the line just before the top of "active". This contains only
// the scrollback history.
"history",
});
/// An x/y point in the terminal for some definition of location (tag).
pub const Point = union(Tag) {
active: Coordinate,
viewport: Coordinate,
screen: Coordinate,
history: Coordinate,
pub inline fn coord(self: Point) Coordinate {
return switch (self) {
.active,
.viewport,
.screen,
.history,
=> |v| v,
};
}
const c_union = lib.TaggedUnion(
lib.target,
@This(),
// Padding: largest variant is Coordinate (u16 + u32 = 6 bytes).
// Use [2]u64 (16 bytes) for future expansion.
[2]u64,
);
pub const C = c_union.C;
pub const CValue = c_union.CValue;
pub const cval = c_union.cval;
/// Convert a C ABI point into the native Zig tagged union.
pub fn fromC(pt: C) Point {
return switch (pt.tag) {
.active => .{ .active = pt.value.active },
.viewport => .{ .viewport = pt.value.viewport },
.screen => .{ .screen = pt.value.screen },
.history => .{ .history = pt.value.history },
};
}
};
pub const Coordinate = extern struct {
/// x can use size.CellCountInt because the number of columns
/// can't ever be more than a valid number of columns in a Page.
x: size.CellCountInt = 0,
/// y does not use size.CellCountInt because certain coordinate
/// usage such as screen/history can have more rows than are possible
/// in a single page.
y: u32 = 0,
pub fn eql(self: Coordinate, other: Coordinate) bool {
return self.x == other.x and self.y == other.y;
}
};