From 2512fad9408bf0fee76dda2eae7a401e28d6b18f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 23 May 2026 15:20:12 -0700 Subject: [PATCH] libghostty: move selection functions to selection doxygen group --- include/ghostty/vt/selection.h | 113 +++++++++++++++++++++++++++++++ include/ghostty/vt/terminal.h | 112 ------------------------------- src/terminal/c/main.zig | 8 +-- src/terminal/c/selection.zig | 93 ++++++++++++++++++++++++++ src/terminal/c/terminal.zig | 117 ++++++--------------------------- 5 files changed, 229 insertions(+), 214 deletions(-) diff --git a/include/ghostty/vt/selection.h b/include/ghostty/vt/selection.h index de00899aa..e397e9a5d 100644 --- a/include/ghostty/vt/selection.h +++ b/include/ghostty/vt/selection.h @@ -10,6 +10,7 @@ #include #include #include +#include #ifdef __cplusplus extern "C" { @@ -157,6 +158,118 @@ typedef enum GHOSTTY_ENUM_TYPED { GHOSTTY_SELECTION_ADJUST_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE, } GhosttySelectionAdjust; +/** + * Adjust a selection snapshot using terminal selection semantics. + * + * This mutates the caller-provided GhosttySelection in place. The logical end + * endpoint is always moved, regardless of whether the selection is forward or + * reversed visually. The input selection remains a snapshot: after adjustment, + * call ghostty_terminal_set() with GHOSTTY_TERMINAL_OPT_SELECTION to install it + * as the terminal-owned selection if desired. + * + * 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 adjust in place + * @param adjustment The adjustment operation to apply + * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the terminal, + * selection, selection references, or adjustment are invalid + * + * @ingroup selection + */ +GHOSTTY_API GhosttyResult ghostty_terminal_selection_adjust( + GhosttyTerminal terminal, + GhosttySelection* selection, + GhosttySelectionAdjust adjustment); + +/** + * Get the current endpoint ordering of a selection snapshot. + * + * 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[out] out_order On success, receives the selection order + * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the terminal, + * selection, selection references, or output pointer are invalid + * + * @ingroup selection + */ +GHOSTTY_API GhosttyResult ghostty_terminal_selection_order( + GhosttyTerminal terminal, + const GhosttySelection* selection, + GhosttySelectionOrder* out_order); + +/** + * Return a selection snapshot with endpoints ordered as requested. + * + * Use GHOSTTY_SELECTION_ORDER_FORWARD to get top-left to bottom-right bounds, + * and GHOSTTY_SELECTION_ORDER_REVERSE to get bottom-right to top-left bounds. + * Mirrored desired orders are accepted but normalized the same as forward. + * The output selection is a fresh untracked snapshot and is not installed as + * the terminal's current selection. + * + * 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 order + * @param desired Desired endpoint order + * @param[out] out_selection On success, receives the ordered selection + * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the terminal, + * selection, selection references, desired order, or output pointer + * are invalid + * + * @ingroup selection + */ +GHOSTTY_API GhosttyResult ghostty_terminal_selection_ordered( + GhosttyTerminal terminal, + const GhosttySelection* selection, + 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 selection + */ +GHOSTTY_API GhosttyResult ghostty_terminal_selection_contains( + GhosttyTerminal terminal, + const GhosttySelection* selection, + GhosttyPoint point, + bool* out_contains); + /** @} */ #ifdef __cplusplus diff --git a/include/ghostty/vt/terminal.h b/include/ghostty/vt/terminal.h index b525af54e..756698449 100644 --- a/include/ghostty/vt/terminal.h +++ b/include/ghostty/vt/terminal.h @@ -1123,118 +1123,6 @@ GHOSTTY_API GhosttyResult ghostty_terminal_get_multi(GhosttyTerminal terminal, void** values, size_t* out_written); -/** - * Adjust a selection snapshot using terminal selection semantics. - * - * This mutates the caller-provided GhosttySelection in place. The logical end - * endpoint is always moved, regardless of whether the selection is forward or - * reversed visually. The input selection remains a snapshot: after adjustment, - * call ghostty_terminal_set() with GHOSTTY_TERMINAL_OPT_SELECTION to install it - * as the terminal-owned selection if desired. - * - * 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 adjust in place - * @param adjustment The adjustment operation to apply - * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the terminal, - * selection, selection references, or adjustment are invalid - * - * @ingroup terminal - */ -GHOSTTY_API GhosttyResult ghostty_terminal_selection_adjust( - GhosttyTerminal terminal, - GhosttySelection* selection, - GhosttySelectionAdjust adjustment); - -/** - * Get the current endpoint ordering of a selection snapshot. - * - * 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[out] out_order On success, receives the selection order - * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the terminal, - * selection, selection references, or output pointer are invalid - * - * @ingroup terminal - */ -GHOSTTY_API GhosttyResult ghostty_terminal_selection_order( - GhosttyTerminal terminal, - const GhosttySelection* selection, - GhosttySelectionOrder* out_order); - -/** - * Return a selection snapshot with endpoints ordered as requested. - * - * Use GHOSTTY_SELECTION_ORDER_FORWARD to get top-left to bottom-right bounds, - * and GHOSTTY_SELECTION_ORDER_REVERSE to get bottom-right to top-left bounds. - * Mirrored desired orders are accepted but normalized the same as forward. - * The output selection is a fresh untracked snapshot and is not installed as - * the terminal's current selection. - * - * 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 order - * @param desired Desired endpoint order - * @param[out] out_selection On success, receives the ordered selection - * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the terminal, - * selection, selection references, desired order, or output pointer - * are invalid - * - * @ingroup terminal - */ -GHOSTTY_API GhosttyResult ghostty_terminal_selection_ordered( - GhosttyTerminal terminal, - const GhosttySelection* selection, - 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. * diff --git a/src/terminal/c/main.zig b/src/terminal/c/main.zig index 0b75fe81b..fd009fce4 100644 --- a/src/terminal/c/main.zig +++ b/src/terminal/c/main.zig @@ -170,10 +170,10 @@ 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_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_selection_adjust = selection.adjust; +pub const terminal_selection_order = selection.order; +pub const terminal_selection_ordered = selection.ordered; +pub const terminal_selection_contains = 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; diff --git a/src/terminal/c/selection.zig b/src/terminal/c/selection.zig index c4c6284e6..3d2581310 100644 --- a/src/terminal/c/selection.zig +++ b/src/terminal/c/selection.zig @@ -1,5 +1,16 @@ +const std = @import("std"); +const testing = std.testing; +const lib = @import("../lib.zig"); const grid_ref = @import("grid_ref.zig"); +const point = @import("../point.zig"); const Selection = @import("../Selection.zig"); +const Result = @import("result.zig").Result; +const terminal_c = @import("terminal.zig"); + +const log = std.log.scoped(.selection_c); + +pub const Adjustment = Selection.Adjustment; +pub const Order = Selection.Order; /// C: GhosttySelection pub const CSelection = extern struct { @@ -22,3 +33,85 @@ pub const CSelection = extern struct { }; } }; + +pub fn adjust( + terminal: terminal_c.Terminal, + selection: ?*CSelection, + adjustment: Selection.Adjustment, +) callconv(lib.calling_conv) Result { + if (comptime std.debug.runtime_safety) { + _ = std.meta.intToEnum(Selection.Adjustment, @intFromEnum(adjustment)) catch { + log.warn("terminal_selection_adjust invalid adjustment value={d}", .{@intFromEnum(adjustment)}); + return .invalid_value; + }; + } + + const t = terminal_c.zigTerminal(terminal) orelse return .invalid_value; + const sel_ptr = selection orelse return .invalid_value; + var sel = sel_ptr.toZig() orelse return .invalid_value; + sel.adjust(t.screens.active, adjustment); + sel_ptr.* = .fromZig(sel); + return .success; +} + +pub fn order( + terminal: terminal_c.Terminal, + selection: ?*const CSelection, + out_order: ?*Selection.Order, +) callconv(lib.calling_conv) Result { + const t = terminal_c.zigTerminal(terminal) orelse return .invalid_value; + const sel = (selection orelse return .invalid_value).toZig() orelse + return .invalid_value; + const out = out_order orelse return .invalid_value; + if (!valid(t, sel)) return .invalid_value; + + out.* = sel.order(t.screens.active); + return .success; +} + +pub fn ordered( + terminal: terminal_c.Terminal, + selection: ?*const CSelection, + desired: Selection.Order, + out_selection: ?*CSelection, +) callconv(lib.calling_conv) Result { + if (comptime std.debug.runtime_safety) { + _ = std.meta.intToEnum(Selection.Order, @intFromEnum(desired)) catch { + log.warn("terminal_selection_ordered invalid desired value={d}", .{@intFromEnum(desired)}); + return .invalid_value; + }; + } + + const t = terminal_c.zigTerminal(terminal) orelse return .invalid_value; + const sel = (selection orelse return .invalid_value).toZig() orelse + return .invalid_value; + const out = out_selection orelse return .invalid_value; + if (!valid(t, sel)) return .invalid_value; + + out.* = .fromZig(sel.ordered(t.screens.active, desired)); + return .success; +} + +pub fn contains( + terminal: terminal_c.Terminal, + selection: ?*const CSelection, + pt: point.Point.C, + out_contains: ?*bool, +) callconv(lib.calling_conv) Result { + const t = terminal_c.zigTerminal(terminal) orelse return .invalid_value; + const sel = (selection orelse return .invalid_value).toZig() orelse + return .invalid_value; + const out = out_contains orelse return .invalid_value; + if (!valid(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 valid(t: *terminal_c.ZigTerminal, sel: Selection) bool { + const screen = t.screens.active; + return screen.pages.pointFromPin(.screen, sel.start()) != null and + screen.pages.pointFromPin(.screen, sel.end()) != null; +} diff --git a/src/terminal/c/terminal.zig b/src/terminal/c/terminal.zig index 6dc8f9e90..6a47bdf22 100644 --- a/src/terminal/c/terminal.zig +++ b/src/terminal/c/terminal.zig @@ -3,11 +3,10 @@ const testing = std.testing; const build_options = @import("terminal_options"); const lib = @import("../lib.zig"); const CAllocator = lib.alloc.Allocator; -const ZigTerminal = @import("../Terminal.zig"); +pub const ZigTerminal = @import("../Terminal.zig"); const Stream = @import("../stream_terminal.zig").Stream; const ScreenSet = @import("../ScreenSet.zig"); const PageList = @import("../PageList.zig"); -const Selection = @import("../Selection.zig"); const apc = @import("../apc.zig"); const kitty = @import("../kitty/key.zig"); const kitty_gfx_c = @import("kitty_graphics.zig"); @@ -211,6 +210,10 @@ const Effects = struct { /// C: GhosttyTerminal pub const Terminal = ?*TerminalWrapper; +pub fn zigTerminal(terminal_: Terminal) ?*ZigTerminal { + return (terminal_ orelse return null).terminal; +} + /// C: GhosttyTerminalOptions pub const Options = extern struct { cols: size.CellCountInt, @@ -665,88 +668,6 @@ pub fn get_multi( return .success; } -pub fn selection_adjust( - terminal_: Terminal, - selection: ?*selection_c.CSelection, - adjustment: Selection.Adjustment, -) callconv(lib.calling_conv) Result { - if (comptime std.debug.runtime_safety) { - _ = std.meta.intToEnum(Selection.Adjustment, @intFromEnum(adjustment)) catch { - log.warn("terminal_selection_adjust invalid adjustment value={d}", .{@intFromEnum(adjustment)}); - return .invalid_value; - }; - } - - const t: *ZigTerminal = (terminal_ orelse return .invalid_value).terminal; - const sel_ptr = selection orelse return .invalid_value; - var sel = sel_ptr.toZig() orelse return .invalid_value; - sel.adjust(t.screens.active, adjustment); - sel_ptr.* = .fromZig(sel); - return .success; -} - -pub fn selection_order( - terminal_: Terminal, - selection: ?*const selection_c.CSelection, - out_order: ?*Selection.Order, -) 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_order orelse return .invalid_value; - if (!selectionValid(t, sel)) return .invalid_value; - - out.* = sel.order(t.screens.active); - return .success; -} - -pub fn selection_ordered( - terminal_: Terminal, - selection: ?*const selection_c.CSelection, - desired: Selection.Order, - out_selection: ?*selection_c.CSelection, -) callconv(lib.calling_conv) Result { - if (comptime std.debug.runtime_safety) { - _ = std.meta.intToEnum(Selection.Order, @intFromEnum(desired)) catch { - log.warn("terminal_selection_ordered invalid desired value={d}", .{@intFromEnum(desired)}); - return .invalid_value; - }; - } - - const t: *ZigTerminal = (terminal_ orelse return .invalid_value).terminal; - const sel = (selection orelse return .invalid_value).toZig() orelse - return .invalid_value; - const out = out_selection orelse return .invalid_value; - if (!selectionValid(t, sel)) return .invalid_value; - - out.* = .fromZig(sel.ordered(t.screens.active, desired)); - 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 - screen.pages.pointFromPin(.screen, sel.end()) != null; -} - fn getTyped( terminal_: Terminal, comptime data: TerminalData, @@ -1503,11 +1424,11 @@ test "selection_adjust mutates snapshot end" { .start = start_ref, .end = end_ref, }; - try testing.expectEqual(Result.success, selection_adjust(t, &sel, .right)); + try testing.expectEqual(Result.success, selection_c.adjust(t, &sel, .right)); try testing.expectEqual(@as(u16, 0), sel.start.toPin().?.x); try testing.expectEqual(@as(u16, 2), sel.end.toPin().?.x); - try testing.expectEqual(Result.success, selection_adjust(t, &sel, .left)); + try testing.expectEqual(Result.success, selection_c.adjust(t, &sel, .left)); try testing.expectEqual(@as(u16, 0), sel.start.toPin().?.x); try testing.expectEqual(@as(u16, 1), sel.end.toPin().?.x); @@ -1515,7 +1436,7 @@ test "selection_adjust mutates snapshot end" { .start = end_ref, .end = start_ref, }; - try testing.expectEqual(Result.success, selection_adjust(t, &sel, .right)); + try testing.expectEqual(Result.success, selection_c.adjust(t, &sel, .right)); try testing.expectEqual(@as(u16, 1), sel.start.toPin().?.x); try testing.expectEqual(@as(u16, 1), sel.end.toPin().?.x); } @@ -1553,19 +1474,19 @@ test "selection_order and selection_ordered" { .rectangle = true, }; - var order: Selection.Order = undefined; - try testing.expectEqual(Result.success, selection_order(t, &sel, &order)); - try testing.expectEqual(Selection.Order.mirrored_forward, order); + var order: selection_c.Order = undefined; + try testing.expectEqual(Result.success, selection_c.order(t, &sel, &order)); + try testing.expectEqual(selection_c.Order.mirrored_forward, order); var out: selection_c.CSelection = undefined; - try testing.expectEqual(Result.success, selection_ordered(t, &sel, .forward, &out)); + try testing.expectEqual(Result.success, selection_c.ordered(t, &sel, .forward, &out)); try testing.expectEqual(@as(u16, 1), out.start.toPin().?.x); try testing.expectEqual(@as(u16, 0), out.start.toPin().?.y); try testing.expectEqual(@as(u16, 3), out.end.toPin().?.x); try testing.expectEqual(@as(u16, 1), out.end.toPin().?.y); try testing.expect(out.rectangle); - try testing.expectEqual(Result.success, selection_ordered(t, &sel, .reverse, &out)); + try testing.expectEqual(Result.success, selection_c.ordered(t, &sel, .reverse, &out)); try testing.expectEqual(@as(u16, 3), out.start.toPin().?.x); try testing.expectEqual(@as(u16, 1), out.start.toPin().?.y); try testing.expectEqual(@as(u16, 1), out.end.toPin().?.x); @@ -1606,13 +1527,13 @@ test "selection_contains" { }; var contains: bool = undefined; - try testing.expectEqual(Result.success, selection_contains(t, &linear, .{ + try testing.expectEqual(Result.success, selection_c.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, .{ + try testing.expectEqual(Result.success, selection_c.contains(t, &linear, .{ .tag = .active, .value = .{ .active = .{ .x = 2, .y = 0 } }, }, &contains)); @@ -1624,7 +1545,7 @@ test "selection_contains" { .rectangle = true, }; - try testing.expectEqual(Result.success, selection_contains(t, &rectangle, .{ + try testing.expectEqual(Result.success, selection_c.contains(t, &rectangle, .{ .tag = .active, .value = .{ .active = .{ .x = 2, .y = 0 } }, }, &contains)); @@ -1644,9 +1565,9 @@ test "selection_order invalid values" { )); defer free(t); - var order: Selection.Order = undefined; - try testing.expectEqual(Result.invalid_value, selection_order(null, null, &order)); - try testing.expectEqual(Result.invalid_value, selection_order(t, null, &order)); + var order: selection_c.Order = undefined; + try testing.expectEqual(Result.invalid_value, selection_c.order(null, null, &order)); + try testing.expectEqual(Result.invalid_value, selection_c.order(t, null, &order)); } test "grid_ref" {