mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-05-25 14:28:32 +00:00
libghostty: add selection contains API
Expose a C API for checking whether a GhosttyPoint is inside a GhosttySelection. The new terminal helper validates the selection snapshot against the active screen, resolves the point to a grid pin, and delegates to the internal Selection.contains implementation so C consumers get the same linear and rectangular selection semantics as Ghostty. Wire the symbol through the C API exports and public headers, and add a focused test covering linear containment and rectangular selection behavior.
This commit is contained in:
@@ -136,6 +136,7 @@ extern "C" {
|
||||
#include <ghostty/vt/modes.h>
|
||||
#include <ghostty/vt/mouse.h>
|
||||
#include <ghostty/vt/paste.h>
|
||||
#include <ghostty/vt/point.h>
|
||||
#include <ghostty/vt/screen.h>
|
||||
#include <ghostty/vt/selection.h>
|
||||
#include <ghostty/vt/size_report.h>
|
||||
|
||||
@@ -1207,6 +1207,34 @@ GHOSTTY_API GhosttyResult ghostty_terminal_selection_ordered(
|
||||
GhosttySelectionOrder desired,
|
||||
GhosttySelection* out_selection);
|
||||
|
||||
/**
|
||||
* Test whether a terminal point is inside a selection snapshot.
|
||||
*
|
||||
* This uses the same selection semantics as the terminal, including
|
||||
* rectangular/block selections and linear selections spanning multiple rows.
|
||||
*
|
||||
* The selection's start and end grid refs must both be valid untracked
|
||||
* snapshots for the given terminal's currently active screen. In practice,
|
||||
* they must come from that terminal and screen, and no mutating terminal call
|
||||
* may have occurred since the refs were produced or reconstructed from
|
||||
* tracked refs. Passing refs from another terminal, another screen, or stale
|
||||
* refs violates this precondition.
|
||||
*
|
||||
* @param terminal The terminal handle (NULL returns GHOSTTY_INVALID_VALUE)
|
||||
* @param selection Selection snapshot to inspect
|
||||
* @param point Point to test for containment
|
||||
* @param[out] out_contains On success, receives whether point is inside selection
|
||||
* @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the terminal,
|
||||
* selection, selection references, point, or output pointer are invalid
|
||||
*
|
||||
* @ingroup terminal
|
||||
*/
|
||||
GHOSTTY_API GhosttyResult ghostty_terminal_selection_contains(
|
||||
GhosttyTerminal terminal,
|
||||
const GhosttySelection* selection,
|
||||
GhosttyPoint point,
|
||||
bool* out_contains);
|
||||
|
||||
/**
|
||||
* Resolve a point in the terminal grid to a grid reference.
|
||||
*
|
||||
|
||||
@@ -242,6 +242,7 @@ comptime {
|
||||
@export(&c.terminal_selection_adjust, .{ .name = "ghostty_terminal_selection_adjust" });
|
||||
@export(&c.terminal_selection_order, .{ .name = "ghostty_terminal_selection_order" });
|
||||
@export(&c.terminal_selection_ordered, .{ .name = "ghostty_terminal_selection_ordered" });
|
||||
@export(&c.terminal_selection_contains, .{ .name = "ghostty_terminal_selection_contains" });
|
||||
@export(&c.terminal_grid_ref, .{ .name = "ghostty_terminal_grid_ref" });
|
||||
@export(&c.terminal_grid_ref_track, .{ .name = "ghostty_terminal_grid_ref_track" });
|
||||
@export(&c.terminal_point_from_grid_ref, .{ .name = "ghostty_terminal_point_from_grid_ref" });
|
||||
|
||||
@@ -173,6 +173,7 @@ pub const terminal_get_multi = terminal.get_multi;
|
||||
pub const terminal_selection_adjust = terminal.selection_adjust;
|
||||
pub const terminal_selection_order = terminal.selection_order;
|
||||
pub const terminal_selection_ordered = terminal.selection_ordered;
|
||||
pub const terminal_selection_contains = terminal.selection_contains;
|
||||
pub const terminal_grid_ref = terminal.grid_ref;
|
||||
pub const terminal_grid_ref_track = terminal.grid_ref_track;
|
||||
pub const terminal_point_from_grid_ref = terminal.point_from_grid_ref;
|
||||
|
||||
@@ -723,6 +723,24 @@ pub fn selection_ordered(
|
||||
return .success;
|
||||
}
|
||||
|
||||
pub fn selection_contains(
|
||||
terminal_: Terminal,
|
||||
selection: ?*const selection_c.CSelection,
|
||||
pt: point.Point.C,
|
||||
out_contains: ?*bool,
|
||||
) callconv(lib.calling_conv) Result {
|
||||
const t: *ZigTerminal = (terminal_ orelse return .invalid_value).terminal;
|
||||
const sel = (selection orelse return .invalid_value).toZig() orelse
|
||||
return .invalid_value;
|
||||
const out = out_contains orelse return .invalid_value;
|
||||
if (!selectionValid(t, sel)) return .invalid_value;
|
||||
|
||||
const screen = t.screens.active;
|
||||
const pin = screen.pages.pin(.fromC(pt)) orelse return .invalid_value;
|
||||
out.* = sel.contains(screen, pin);
|
||||
return .success;
|
||||
}
|
||||
|
||||
fn selectionValid(t: *ZigTerminal, sel: Selection) bool {
|
||||
const screen = t.screens.active;
|
||||
return screen.pages.pointFromPin(.screen, sel.start()) != null and
|
||||
@@ -1555,6 +1573,64 @@ test "selection_order and selection_ordered" {
|
||||
try testing.expect(out.rectangle);
|
||||
}
|
||||
|
||||
test "selection_contains" {
|
||||
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);
|
||||
|
||||
vt_write(t, "Hello\r\nWorld", 12);
|
||||
|
||||
var start_ref: grid_ref_c.CGridRef = .{};
|
||||
try testing.expectEqual(Result.success, grid_ref(t, .{
|
||||
.tag = .active,
|
||||
.value = .{ .active = .{ .x = 3, .y = 0 } },
|
||||
}, &start_ref));
|
||||
|
||||
var end_ref: grid_ref_c.CGridRef = .{};
|
||||
try testing.expectEqual(Result.success, grid_ref(t, .{
|
||||
.tag = .active,
|
||||
.value = .{ .active = .{ .x = 1, .y = 1 } },
|
||||
}, &end_ref));
|
||||
|
||||
const linear: selection_c.CSelection = .{
|
||||
.start = start_ref,
|
||||
.end = end_ref,
|
||||
};
|
||||
|
||||
var contains: bool = undefined;
|
||||
try testing.expectEqual(Result.success, selection_contains(t, &linear, .{
|
||||
.tag = .active,
|
||||
.value = .{ .active = .{ .x = 4, .y = 0 } },
|
||||
}, &contains));
|
||||
try testing.expect(contains);
|
||||
|
||||
try testing.expectEqual(Result.success, selection_contains(t, &linear, .{
|
||||
.tag = .active,
|
||||
.value = .{ .active = .{ .x = 2, .y = 0 } },
|
||||
}, &contains));
|
||||
try testing.expect(!contains);
|
||||
|
||||
const rectangle: selection_c.CSelection = .{
|
||||
.start = start_ref,
|
||||
.end = end_ref,
|
||||
.rectangle = true,
|
||||
};
|
||||
|
||||
try testing.expectEqual(Result.success, selection_contains(t, &rectangle, .{
|
||||
.tag = .active,
|
||||
.value = .{ .active = .{ .x = 2, .y = 0 } },
|
||||
}, &contains));
|
||||
try testing.expect(contains);
|
||||
}
|
||||
|
||||
test "selection_order invalid values" {
|
||||
var t: Terminal = null;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
|
||||
Reference in New Issue
Block a user