mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-14 11:35:48 +00:00
libghostty: add ghostty_terminal_point_from_grid_ref
Add the inverse of ghostty_terminal_grid_ref(), converting a grid reference back to coordinates in a requested coordinate system (active, viewport, screen, or history). This wraps the existing internal PageList.pointFromPin and is placed on the terminal API since it requires terminal-owned PageList state to resolve the top-left anchor for each coordinate system. Returns GHOSTTY_NO_VALUE when the ref falls outside the requested range, e.g. a scrollback ref cannot be expressed in active coordinates.
This commit is contained in:
@@ -1064,6 +1064,39 @@ GHOSTTY_API GhosttyResult ghostty_terminal_grid_ref(GhosttyTerminal terminal,
|
||||
GhosttyPoint point,
|
||||
GhosttyGridRef *out_ref);
|
||||
|
||||
/**
|
||||
* Convert a grid reference back to a point in the given coordinate system.
|
||||
*
|
||||
* This is the inverse of ghostty_terminal_grid_ref(): given a grid reference,
|
||||
* it returns the x/y coordinates in the requested coordinate system (active,
|
||||
* viewport, screen, or history).
|
||||
*
|
||||
* The grid reference must have been obtained from the same terminal instance.
|
||||
* Like all grid references, it is only valid until the next mutating terminal
|
||||
* call.
|
||||
*
|
||||
* Not every grid reference is representable in every coordinate system. For
|
||||
* example, a cell in scrollback history cannot be expressed in active
|
||||
* coordinates, and a cell that has scrolled off the visible area cannot be
|
||||
* expressed in viewport coordinates. In these cases, the function returns
|
||||
* GHOSTTY_NO_VALUE.
|
||||
*
|
||||
* @param terminal The terminal handle (NULL returns GHOSTTY_INVALID_VALUE)
|
||||
* @param ref Pointer to the grid reference to convert
|
||||
* @param tag The target coordinate system
|
||||
* @param[out] out On success, set to the coordinate in the requested system (may be NULL)
|
||||
* @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the terminal
|
||||
* or ref is NULL/invalid, GHOSTTY_NO_VALUE if the ref falls outside
|
||||
* the requested coordinate system
|
||||
*
|
||||
* @ingroup terminal
|
||||
*/
|
||||
GHOSTTY_API GhosttyResult ghostty_terminal_point_from_grid_ref(
|
||||
GhosttyTerminal terminal,
|
||||
const GhosttyGridRef *ref,
|
||||
GhosttyPointTag tag,
|
||||
GhosttyPointCoordinate *out);
|
||||
|
||||
/** @} */
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -233,6 +233,7 @@ comptime {
|
||||
@export(&c.terminal_mode_set, .{ .name = "ghostty_terminal_mode_set" });
|
||||
@export(&c.terminal_get, .{ .name = "ghostty_terminal_get" });
|
||||
@export(&c.terminal_grid_ref, .{ .name = "ghostty_terminal_grid_ref" });
|
||||
@export(&c.terminal_point_from_grid_ref, .{ .name = "ghostty_terminal_point_from_grid_ref" });
|
||||
@export(&c.kitty_graphics_get, .{ .name = "ghostty_kitty_graphics_get" });
|
||||
@export(&c.kitty_graphics_image, .{ .name = "ghostty_kitty_graphics_image" });
|
||||
@export(&c.kitty_graphics_image_get, .{ .name = "ghostty_kitty_graphics_image_get" });
|
||||
|
||||
@@ -157,6 +157,7 @@ pub const terminal_mode_get = terminal.mode_get;
|
||||
pub const terminal_mode_set = terminal.mode_set;
|
||||
pub const terminal_get = terminal.get;
|
||||
pub const terminal_grid_ref = terminal.grid_ref;
|
||||
pub const terminal_point_from_grid_ref = terminal.point_from_grid_ref;
|
||||
|
||||
pub const type_json = types.get_json;
|
||||
|
||||
|
||||
@@ -697,6 +697,20 @@ pub fn grid_ref(
|
||||
return .success;
|
||||
}
|
||||
|
||||
pub fn point_from_grid_ref(
|
||||
terminal_: Terminal,
|
||||
ref: *const grid_ref_c.CGridRef,
|
||||
tag: point.Tag,
|
||||
out: ?*point.Coordinate,
|
||||
) callconv(lib.calling_conv) Result {
|
||||
const t: *ZigTerminal = (terminal_ orelse return .invalid_value).terminal;
|
||||
const p = ref.toPin() orelse return .invalid_value;
|
||||
const pt = t.screens.active.pages.pointFromPin(tag, p) orelse
|
||||
return .no_value;
|
||||
if (out) |o| o.* = pt.coord();
|
||||
return .success;
|
||||
}
|
||||
|
||||
pub fn free(terminal_: Terminal) callconv(lib.calling_conv) void {
|
||||
const wrapper = terminal_ orelse return;
|
||||
const t = wrapper.terminal;
|
||||
@@ -1261,6 +1275,102 @@ test "grid_ref null terminal" {
|
||||
}, &out_ref));
|
||||
}
|
||||
|
||||
test "point_from_grid_ref roundtrip active" {
|
||||
var t: Terminal = null;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 10_000 },
|
||||
));
|
||||
defer free(t);
|
||||
|
||||
vt_write(t, "Hello", 5);
|
||||
|
||||
// Get a grid ref at (2, 0) in active coords
|
||||
var ref: grid_ref_c.CGridRef = .{};
|
||||
try testing.expectEqual(Result.success, grid_ref(t, .{
|
||||
.tag = .active,
|
||||
.value = .{ .active = .{ .x = 2, .y = 0 } },
|
||||
}, &ref));
|
||||
|
||||
// Convert back to active coords
|
||||
var coord: point.Coordinate = undefined;
|
||||
try testing.expectEqual(Result.success, point_from_grid_ref(t, &ref, .active, &coord));
|
||||
try testing.expectEqual(@as(size.CellCountInt, 2), coord.x);
|
||||
try testing.expectEqual(@as(u32, 0), coord.y);
|
||||
}
|
||||
|
||||
test "point_from_grid_ref roundtrip viewport" {
|
||||
var t: Terminal = null;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 10_000 },
|
||||
));
|
||||
defer free(t);
|
||||
|
||||
vt_write(t, "Hello", 5);
|
||||
|
||||
var ref: grid_ref_c.CGridRef = .{};
|
||||
try testing.expectEqual(Result.success, grid_ref(t, .{
|
||||
.tag = .viewport,
|
||||
.value = .{ .viewport = .{ .x = 0, .y = 0 } },
|
||||
}, &ref));
|
||||
|
||||
var coord: point.Coordinate = undefined;
|
||||
try testing.expectEqual(Result.success, point_from_grid_ref(t, &ref, .viewport, &coord));
|
||||
try testing.expectEqual(@as(size.CellCountInt, 0), coord.x);
|
||||
try testing.expectEqual(@as(u32, 0), coord.y);
|
||||
}
|
||||
|
||||
test "point_from_grid_ref history ref to active returns no_value" {
|
||||
var t: Terminal = null;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 4, .max_scrollback = 10_000 },
|
||||
));
|
||||
defer free(t);
|
||||
|
||||
// Write enough lines to push content into scrollback
|
||||
for (0..10) |_| {
|
||||
vt_write(t, "line\n", 5);
|
||||
}
|
||||
|
||||
// Get a ref to the first line (now in scrollback)
|
||||
var ref: grid_ref_c.CGridRef = .{};
|
||||
try testing.expectEqual(Result.success, grid_ref(t, .{
|
||||
.tag = .screen,
|
||||
.value = .{ .screen = .{ .x = 0, .y = 0 } },
|
||||
}, &ref));
|
||||
|
||||
// Should succeed for screen coords
|
||||
var coord: point.Coordinate = undefined;
|
||||
try testing.expectEqual(Result.success, point_from_grid_ref(t, &ref, .screen, &coord));
|
||||
try testing.expectEqual(@as(u32, 0), coord.y);
|
||||
|
||||
// Should fail for active coords (it's in scrollback)
|
||||
try testing.expectEqual(Result.no_value, point_from_grid_ref(t, &ref, .active, &coord));
|
||||
}
|
||||
|
||||
test "point_from_grid_ref null terminal" {
|
||||
var ref: grid_ref_c.CGridRef = .{};
|
||||
try testing.expectEqual(Result.invalid_value, point_from_grid_ref(null, &ref, .active, null));
|
||||
}
|
||||
|
||||
test "point_from_grid_ref null node" {
|
||||
var t: Terminal = null;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib.alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 0 },
|
||||
));
|
||||
defer free(t);
|
||||
|
||||
const ref: grid_ref_c.CGridRef = .{};
|
||||
try testing.expectEqual(Result.invalid_value, point_from_grid_ref(t, &ref, .active, null));
|
||||
}
|
||||
|
||||
test "set write_pty callback" {
|
||||
var t: Terminal = null;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
|
||||
Reference in New Issue
Block a user