mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-05-28 07:45:20 +00:00
libghostty: GhosttySelectionGestureEvent
This commit is contained in:
@@ -50,6 +50,16 @@ extern "C" {
|
||||
*/
|
||||
typedef struct GhosttySelectionGestureImpl* GhosttySelectionGesture;
|
||||
|
||||
/**
|
||||
* Opaque handle to reusable input data for selection gesture operations.
|
||||
*
|
||||
* Event options are set with ghostty_selection_gesture_event_set(). Individual
|
||||
* gesture operations document which options are required or optional.
|
||||
*
|
||||
* @ingroup selection
|
||||
*/
|
||||
typedef struct GhosttySelectionGestureEventImpl* GhosttySelectionGestureEvent;
|
||||
|
||||
/**
|
||||
* A snapshot selection range defined by two grid references.
|
||||
*
|
||||
@@ -315,6 +325,22 @@ typedef enum GHOSTTY_ENUM_TYPED {
|
||||
GHOSTTY_SELECTION_GESTURE_BEHAVIOR_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
|
||||
} GhosttySelectionGestureBehavior;
|
||||
|
||||
/**
|
||||
* Selection behaviors for single-, double-, and triple-click gestures.
|
||||
*
|
||||
* @ingroup selection
|
||||
*/
|
||||
typedef struct {
|
||||
/** Behavior for single-click selection gestures. */
|
||||
GhosttySelectionGestureBehavior single_click;
|
||||
|
||||
/** Behavior for double-click selection gestures. */
|
||||
GhosttySelectionGestureBehavior double_click;
|
||||
|
||||
/** Behavior for triple-click selection gestures. */
|
||||
GhosttySelectionGestureBehavior triple_click;
|
||||
} GhosttySelectionGestureBehaviors;
|
||||
|
||||
/**
|
||||
* Current autoscroll direction for an active selection drag gesture.
|
||||
*
|
||||
@@ -364,6 +390,116 @@ typedef enum GHOSTTY_ENUM_TYPED {
|
||||
GHOSTTY_SELECTION_GESTURE_DATA_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
|
||||
} GhosttySelectionGestureData;
|
||||
|
||||
/**
|
||||
* Selection gesture event type.
|
||||
*
|
||||
* The event type is fixed when the event is created. Each event type documents
|
||||
* which options are valid and which options are required by gesture operations.
|
||||
*
|
||||
* @ingroup selection
|
||||
*/
|
||||
typedef enum GHOSTTY_ENUM_TYPED {
|
||||
/** Press event for ghostty_selection_gesture_press(). */
|
||||
GHOSTTY_SELECTION_GESTURE_EVENT_TYPE_PRESS = 0,
|
||||
|
||||
GHOSTTY_SELECTION_GESTURE_EVENT_TYPE_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
|
||||
} GhosttySelectionGestureEventType;
|
||||
|
||||
/**
|
||||
* Options stored on a reusable selection gesture event.
|
||||
*
|
||||
* Passing NULL as the value to ghostty_selection_gesture_event_set() clears the
|
||||
* corresponding option.
|
||||
*
|
||||
* @ingroup selection
|
||||
*/
|
||||
typedef enum GHOSTTY_ENUM_TYPED {
|
||||
/** Grid reference under the pointer: GhosttyGridRef*. */
|
||||
GHOSTTY_SELECTION_GESTURE_EVENT_OPT_REF = 0,
|
||||
|
||||
/** Surface-space pointer position: GhosttySurfacePosition*. */
|
||||
GHOSTTY_SELECTION_GESTURE_EVENT_OPT_POSITION = 1,
|
||||
|
||||
/** Maximum repeat-click distance in pixels: double*. */
|
||||
GHOSTTY_SELECTION_GESTURE_EVENT_OPT_REPEAT_DISTANCE = 2,
|
||||
|
||||
/**
|
||||
* Optional monotonic event time in nanoseconds: uint64_t*.
|
||||
*
|
||||
* If unset, press treats the event as untimed and only single-click behavior
|
||||
* is available.
|
||||
*/
|
||||
GHOSTTY_SELECTION_GESTURE_EVENT_OPT_TIME_NS = 3,
|
||||
|
||||
/** Maximum interval between repeat clicks in nanoseconds: uint64_t*. */
|
||||
GHOSTTY_SELECTION_GESTURE_EVENT_OPT_REPEAT_INTERVAL_NS = 4,
|
||||
|
||||
/**
|
||||
* Word-boundary codepoints: GhosttyCodepoints*.
|
||||
*
|
||||
* The codepoints are copied into event-owned storage when set. If unset,
|
||||
* operations that need word boundaries use Ghostty's defaults.
|
||||
*/
|
||||
GHOSTTY_SELECTION_GESTURE_EVENT_OPT_WORD_BOUNDARY_CODEPOINTS = 5,
|
||||
|
||||
/**
|
||||
* Selection behavior table: GhosttySelectionGestureBehaviors*.
|
||||
*
|
||||
* If unset, press uses the default behavior table: cell, word, line.
|
||||
*/
|
||||
GHOSTTY_SELECTION_GESTURE_EVENT_OPT_BEHAVIORS = 6,
|
||||
|
||||
GHOSTTY_SELECTION_GESTURE_EVENT_OPT_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
|
||||
} GhosttySelectionGestureEventOption;
|
||||
|
||||
/**
|
||||
* Create a reusable selection gesture event object.
|
||||
*
|
||||
* @param allocator Allocator, or NULL for the default allocator
|
||||
* @param out_event Receives the created event handle
|
||||
* @param type Event type. This is fixed for the lifetime of the event.
|
||||
* @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if out_event is
|
||||
* NULL or type is invalid, or GHOSTTY_OUT_OF_MEMORY if allocation fails
|
||||
*
|
||||
* @ingroup selection
|
||||
*/
|
||||
GHOSTTY_API GhosttyResult ghostty_selection_gesture_event_new(
|
||||
const GhosttyAllocator* allocator,
|
||||
GhosttySelectionGestureEvent* out_event,
|
||||
GhosttySelectionGestureEventType type);
|
||||
|
||||
/**
|
||||
* Free a selection gesture event object.
|
||||
*
|
||||
* Passing NULL is allowed and is a no-op.
|
||||
*
|
||||
* @param event Selection gesture event handle to free
|
||||
*
|
||||
* @ingroup selection
|
||||
*/
|
||||
GHOSTTY_API void ghostty_selection_gesture_event_free(
|
||||
GhosttySelectionGestureEvent event);
|
||||
|
||||
/**
|
||||
* Set or clear an option on a selection gesture event.
|
||||
*
|
||||
* The value type depends on option and is documented by
|
||||
* GhosttySelectionGestureEventOption. Passing NULL for value clears the option.
|
||||
*
|
||||
* @param event Selection gesture event handle (NULL returns GHOSTTY_INVALID_VALUE)
|
||||
* @param option Event option to set or clear
|
||||
* @param value Pointer to the input value for option, or NULL to clear
|
||||
* @return GHOSTTY_SUCCESS on success, GHOSTTY_OUT_OF_MEMORY if copying
|
||||
* event-owned data fails, or GHOSTTY_INVALID_VALUE if event, option, or
|
||||
* value is invalid
|
||||
*
|
||||
* @ingroup selection
|
||||
*/
|
||||
GHOSTTY_API GhosttyResult ghostty_selection_gesture_event_set(
|
||||
GhosttySelectionGestureEvent event,
|
||||
GhosttySelectionGestureEventOption option,
|
||||
const void* value);
|
||||
|
||||
/**
|
||||
* Create a selection gesture object.
|
||||
*
|
||||
|
||||
@@ -227,6 +227,38 @@ typedef struct {
|
||||
size_t len;
|
||||
} GhosttyString;
|
||||
|
||||
/**
|
||||
* A surface-space position in pixels.
|
||||
*
|
||||
* This is not a terminal grid coordinate. It represents an x/y position in the
|
||||
* rendered surface coordinate space, with (0, 0) at the top-left of the
|
||||
* surface.
|
||||
*/
|
||||
typedef struct {
|
||||
/** X position in surface pixels. */
|
||||
double x;
|
||||
|
||||
/** Y position in surface pixels. */
|
||||
double y;
|
||||
} GhosttySurfacePosition;
|
||||
|
||||
/**
|
||||
* A borrowed list of Unicode scalar values.
|
||||
*
|
||||
* Values are encoded as uint32_t scalar values. The memory is not owned by this
|
||||
* struct. The pointer is only valid for the lifetime documented by the API that
|
||||
* consumes or produces it.
|
||||
*
|
||||
* APIs may document special handling for NULL + len 0, such as “use defaults”.
|
||||
*/
|
||||
typedef struct {
|
||||
/** Pointer to Unicode scalar values. */
|
||||
const uint32_t* ptr;
|
||||
|
||||
/** Number of entries in ptr. */
|
||||
size_t len;
|
||||
} GhosttyCodepoints;
|
||||
|
||||
/**
|
||||
* Initialize a sized struct to zero and set its size field.
|
||||
*
|
||||
|
||||
@@ -256,6 +256,9 @@ comptime {
|
||||
@export(&c.selection_gesture_reset, .{ .name = "ghostty_selection_gesture_reset" });
|
||||
@export(&c.selection_gesture_get, .{ .name = "ghostty_selection_gesture_get" });
|
||||
@export(&c.selection_gesture_get_multi, .{ .name = "ghostty_selection_gesture_get_multi" });
|
||||
@export(&c.selection_gesture_event_new, .{ .name = "ghostty_selection_gesture_event_new" });
|
||||
@export(&c.selection_gesture_event_free, .{ .name = "ghostty_selection_gesture_event_free" });
|
||||
@export(&c.selection_gesture_event_set, .{ .name = "ghostty_selection_gesture_event_set" });
|
||||
@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" });
|
||||
|
||||
@@ -188,6 +188,9 @@ pub const selection_gesture_free = selection_gesture.free;
|
||||
pub const selection_gesture_reset = selection_gesture.reset;
|
||||
pub const selection_gesture_get = selection_gesture.get;
|
||||
pub const selection_gesture_get_multi = selection_gesture.get_multi;
|
||||
pub const selection_gesture_event_new = selection_gesture.event_new;
|
||||
pub const selection_gesture_event_free = selection_gesture.event_free;
|
||||
pub const selection_gesture_event_set = selection_gesture.event_set;
|
||||
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;
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
const std = @import("std");
|
||||
const testing = std.testing;
|
||||
const builtin = @import("builtin");
|
||||
const lib = @import("../lib.zig");
|
||||
const CAllocator = lib.alloc.Allocator;
|
||||
const SelectionGesture = @import("../SelectionGesture.zig");
|
||||
const selection_codepoints = @import("../selection_codepoints.zig");
|
||||
const grid_ref = @import("grid_ref.zig");
|
||||
const terminal_c = @import("terminal.zig");
|
||||
const types = @import("types.zig");
|
||||
const Result = @import("result.zig").Result;
|
||||
|
||||
const log = std.log.scoped(.selection_gesture_c);
|
||||
@@ -12,17 +15,71 @@ const log = std.log.scoped(.selection_gesture_c);
|
||||
/// C: GhosttySelectionGesture
|
||||
pub const Gesture = ?*GestureWrapper;
|
||||
|
||||
/// C: GhosttySelectionGestureEvent
|
||||
pub const Event = ?*EventWrapper;
|
||||
|
||||
const GestureWrapper = struct {
|
||||
alloc: std.mem.Allocator,
|
||||
gesture: SelectionGesture = .init,
|
||||
};
|
||||
|
||||
const EventWrapper = struct {
|
||||
alloc: std.mem.Allocator,
|
||||
event: union(EventType) {
|
||||
press: SelectionGesture.Press,
|
||||
},
|
||||
|
||||
// Backing storage for Press.word_boundary_codepoints. The C API receives
|
||||
// codepoints as borrowed uint32_t values, but SelectionGesture.Press stores
|
||||
// a []const u21 slice. We copy/convert into event-owned storage so the real
|
||||
// Press payload can safely point at it until the event is changed or freed.
|
||||
word_boundary_codepoints: ?[]u21 = null,
|
||||
|
||||
// Backing storage for Press.behaviors. The C API sets behaviors as a value
|
||||
// struct, but SelectionGesture.Press stores a pointer to a [3]Behavior.
|
||||
// Keep the array on the event wrapper so the Press payload can point at a
|
||||
// stable location for the lifetime of the event.
|
||||
behaviors: [3]Behavior = SelectionGesture.default_behaviors,
|
||||
|
||||
fn init(self: *EventWrapper, event_type: EventType) void {
|
||||
self.event = switch (event_type) {
|
||||
.press => .{ .press = self.defaultPress() },
|
||||
};
|
||||
}
|
||||
|
||||
fn defaultPress(self: *EventWrapper) SelectionGesture.Press {
|
||||
return .{
|
||||
.time = null,
|
||||
.pin = undefined,
|
||||
.xpos = 0,
|
||||
.ypos = 0,
|
||||
.max_distance = 0,
|
||||
.repeat_interval = 0,
|
||||
.word_boundary_codepoints = &selection_codepoints.default_word_boundaries,
|
||||
.behaviors = &self.behaviors,
|
||||
};
|
||||
}
|
||||
|
||||
fn deinit(self: *EventWrapper) void {
|
||||
if (self.word_boundary_codepoints) |cps| {
|
||||
if (cps.len > 0) self.alloc.free(cps);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// C: GhosttySelectionGestureBehavior
|
||||
pub const Behavior = SelectionGesture.Behavior;
|
||||
|
||||
/// C: GhosttySelectionGestureAutoscroll
|
||||
pub const Autoscroll = SelectionGesture.Autoscroll;
|
||||
|
||||
/// C: GhosttySelectionGestureBehaviors
|
||||
pub const Behaviors = extern struct {
|
||||
single_click: Behavior,
|
||||
double_click: Behavior,
|
||||
triple_click: Behavior,
|
||||
};
|
||||
|
||||
/// C: GhosttySelectionGestureData
|
||||
pub const Data = enum(c_int) {
|
||||
click_count = 0,
|
||||
@@ -42,6 +99,34 @@ pub const Data = enum(c_int) {
|
||||
}
|
||||
};
|
||||
|
||||
/// C: GhosttySelectionGestureEventType
|
||||
pub const EventType = enum(c_int) {
|
||||
press = 0,
|
||||
};
|
||||
|
||||
/// C: GhosttySelectionGestureEventOption
|
||||
pub const EventOption = enum(c_int) {
|
||||
ref = 0,
|
||||
position = 1,
|
||||
repeat_distance = 2,
|
||||
time_ns = 3,
|
||||
repeat_interval_ns = 4,
|
||||
word_boundary_codepoints = 5,
|
||||
behaviors = 6,
|
||||
|
||||
pub fn Type(comptime self: EventOption) type {
|
||||
return switch (self) {
|
||||
.ref => grid_ref.CGridRef,
|
||||
.position => types.SurfacePosition,
|
||||
.repeat_distance => f64,
|
||||
.time_ns => u64,
|
||||
.repeat_interval_ns => u64,
|
||||
.word_boundary_codepoints => types.Codepoints,
|
||||
.behaviors => Behaviors,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub fn new(
|
||||
alloc_: ?*const CAllocator,
|
||||
out_gesture: ?*Gesture,
|
||||
@@ -60,6 +145,29 @@ pub fn new(
|
||||
return .success;
|
||||
}
|
||||
|
||||
pub fn event_new(
|
||||
alloc_: ?*const CAllocator,
|
||||
out_event: ?*Event,
|
||||
event_type: EventType,
|
||||
) callconv(lib.calling_conv) Result {
|
||||
const out = out_event orelse return .invalid_value;
|
||||
_ = std.meta.intToEnum(EventType, @intFromEnum(event_type)) catch
|
||||
return .invalid_value;
|
||||
|
||||
const alloc = lib.alloc.default(alloc_);
|
||||
const event = alloc.create(EventWrapper) catch {
|
||||
out.* = null;
|
||||
return .out_of_memory;
|
||||
};
|
||||
event.* = .{
|
||||
.alloc = alloc,
|
||||
.event = undefined,
|
||||
};
|
||||
event.init(event_type);
|
||||
out.* = event;
|
||||
return .success;
|
||||
}
|
||||
|
||||
pub fn free(
|
||||
gesture_: Gesture,
|
||||
terminal: terminal_c.Terminal,
|
||||
@@ -72,6 +180,13 @@ pub fn free(
|
||||
alloc.destroy(wrapper);
|
||||
}
|
||||
|
||||
pub fn event_free(event_: Event) callconv(lib.calling_conv) void {
|
||||
const event = event_ orelse return;
|
||||
event.deinit();
|
||||
const alloc = event.alloc;
|
||||
alloc.destroy(event);
|
||||
}
|
||||
|
||||
pub fn reset(
|
||||
gesture_: Gesture,
|
||||
terminal: terminal_c.Terminal,
|
||||
@@ -81,6 +196,27 @@ pub fn reset(
|
||||
wrapper.gesture.reset(t);
|
||||
}
|
||||
|
||||
pub fn event_set(
|
||||
event_: Event,
|
||||
option: EventOption,
|
||||
value: ?*const anyopaque,
|
||||
) callconv(lib.calling_conv) Result {
|
||||
if (comptime std.debug.runtime_safety) {
|
||||
_ = std.meta.intToEnum(EventOption, @intFromEnum(option)) catch {
|
||||
log.warn("selection_gesture_event_set invalid option value={d}", .{@intFromEnum(option)});
|
||||
return .invalid_value;
|
||||
};
|
||||
}
|
||||
|
||||
return switch (option) {
|
||||
inline else => |comptime_option| eventSetTyped(
|
||||
event_,
|
||||
comptime_option,
|
||||
if (value) |ptr| @ptrCast(@alignCast(ptr)) else null,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn get(
|
||||
gesture_: Gesture,
|
||||
terminal: terminal_c.Terminal,
|
||||
@@ -151,6 +287,102 @@ fn getTyped(
|
||||
return .success;
|
||||
}
|
||||
|
||||
fn eventSetTyped(
|
||||
event_: Event,
|
||||
comptime option: EventOption,
|
||||
value: ?*const option.Type(),
|
||||
) Result {
|
||||
const event = event_ orelse return .invalid_value;
|
||||
return switch (event.event) {
|
||||
.press => |*press| pressSetTyped(event, press, option, value),
|
||||
};
|
||||
}
|
||||
|
||||
fn pressSetTyped(
|
||||
event: *EventWrapper,
|
||||
press: *SelectionGesture.Press,
|
||||
comptime option: EventOption,
|
||||
value: ?*const option.Type(),
|
||||
) Result {
|
||||
const v = value orelse {
|
||||
switch (option) {
|
||||
.ref => {},
|
||||
.position => {
|
||||
press.xpos = 0;
|
||||
press.ypos = 0;
|
||||
},
|
||||
.repeat_distance => press.max_distance = 0,
|
||||
.time_ns => press.time = null,
|
||||
.repeat_interval_ns => press.repeat_interval = 0,
|
||||
.word_boundary_codepoints => clearPressCodepoints(event, press),
|
||||
.behaviors => {
|
||||
event.behaviors = SelectionGesture.default_behaviors;
|
||||
press.behaviors = &event.behaviors;
|
||||
},
|
||||
}
|
||||
return .success;
|
||||
};
|
||||
|
||||
switch (option) {
|
||||
.ref => press.pin = v.toPin() orelse return .invalid_value,
|
||||
.position => {
|
||||
press.xpos = v.x;
|
||||
press.ypos = v.y;
|
||||
},
|
||||
.repeat_distance => press.max_distance = v.*,
|
||||
.time_ns => press.time = instantFromNs(v.*),
|
||||
.repeat_interval_ns => press.repeat_interval = v.*,
|
||||
.word_boundary_codepoints => {
|
||||
if (v.len > 0 and v.ptr == null) return .invalid_value;
|
||||
clearPressCodepoints(event, press);
|
||||
const ptr = v.ptr orelse {
|
||||
event.word_boundary_codepoints = &.{};
|
||||
press.word_boundary_codepoints = event.word_boundary_codepoints.?;
|
||||
return .success;
|
||||
};
|
||||
const copy = event.alloc.alloc(u21, v.len) catch return .out_of_memory;
|
||||
errdefer event.alloc.free(copy);
|
||||
for (copy, ptr[0..v.len]) |*dst, cp| {
|
||||
dst.* = std.math.cast(u21, cp) orelse return .invalid_value;
|
||||
}
|
||||
event.word_boundary_codepoints = copy;
|
||||
press.word_boundary_codepoints = copy;
|
||||
},
|
||||
.behaviors => {
|
||||
if (!validBehavior(v.single_click) or
|
||||
!validBehavior(v.double_click) or
|
||||
!validBehavior(v.triple_click)) return .invalid_value;
|
||||
event.behaviors = .{ v.single_click, v.double_click, v.triple_click };
|
||||
press.behaviors = &event.behaviors;
|
||||
},
|
||||
}
|
||||
|
||||
return .success;
|
||||
}
|
||||
|
||||
fn clearPressCodepoints(event: *EventWrapper, press: *SelectionGesture.Press) void {
|
||||
if (event.word_boundary_codepoints) |cps| {
|
||||
if (cps.len > 0) event.alloc.free(cps);
|
||||
}
|
||||
event.word_boundary_codepoints = null;
|
||||
press.word_boundary_codepoints = &selection_codepoints.default_word_boundaries;
|
||||
}
|
||||
|
||||
fn instantFromNs(ns: u64) std.time.Instant {
|
||||
return switch (builtin.os.tag) {
|
||||
.windows, .uefi, .wasi => .{ .timestamp = ns },
|
||||
else => .{ .timestamp = .{
|
||||
.sec = @intCast(ns / std.time.ns_per_s),
|
||||
.nsec = @intCast(ns % std.time.ns_per_s),
|
||||
} },
|
||||
};
|
||||
}
|
||||
|
||||
fn validBehavior(behavior: Behavior) bool {
|
||||
_ = std.meta.intToEnum(Behavior, @intFromEnum(behavior)) catch return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
test "selection gesture lifecycle and get" {
|
||||
var terminal: terminal_c.Terminal = null;
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
@@ -267,6 +499,86 @@ test "selection gesture get_multi returns first failing index" {
|
||||
try testing.expect(dragged);
|
||||
}
|
||||
|
||||
test "selection gesture event set clear and free" {
|
||||
var event: Event = null;
|
||||
try testing.expectEqual(Result.success, event_new(
|
||||
&lib.alloc.test_allocator,
|
||||
&event,
|
||||
.press,
|
||||
));
|
||||
defer event_free(event);
|
||||
|
||||
const in_pos: types.SurfacePosition = .{ .x = 12.5, .y = -3.25 };
|
||||
try testing.expectEqual(Result.success, event_set(event, .position, &in_pos));
|
||||
try testing.expectEqual(@as(f64, 12.5), event.?.event.press.xpos);
|
||||
try testing.expectEqual(@as(f64, -3.25), event.?.event.press.ypos);
|
||||
|
||||
try testing.expectEqual(Result.success, event_set(event, .position, null));
|
||||
try testing.expectEqual(@as(f64, 0), event.?.event.press.xpos);
|
||||
try testing.expectEqual(@as(f64, 0), event.?.event.press.ypos);
|
||||
|
||||
const repeat_distance: f64 = 4.0;
|
||||
try testing.expectEqual(Result.success, event_set(event, .repeat_distance, &repeat_distance));
|
||||
try testing.expectEqual(repeat_distance, event.?.event.press.max_distance);
|
||||
}
|
||||
|
||||
test "selection gesture event copies clears and frees codepoints" {
|
||||
var event: Event = null;
|
||||
try testing.expectEqual(Result.success, event_new(
|
||||
&lib.alloc.test_allocator,
|
||||
&event,
|
||||
.press,
|
||||
));
|
||||
defer event_free(event);
|
||||
|
||||
var values = [_]u32{ ' ', '\t' };
|
||||
const in: types.Codepoints = .{ .ptr = &values, .len = values.len };
|
||||
try testing.expectEqual(Result.success, event_set(event, .word_boundary_codepoints, &in));
|
||||
|
||||
values[0] = 'x';
|
||||
|
||||
try testing.expectEqual(@as(usize, 2), event.?.event.press.word_boundary_codepoints.len);
|
||||
try testing.expectEqual(@as(u21, ' '), event.?.event.press.word_boundary_codepoints[0]);
|
||||
try testing.expectEqual(@as(u21, '\t'), event.?.event.press.word_boundary_codepoints[1]);
|
||||
|
||||
const invalid: types.Codepoints = .{ .ptr = null, .len = 1 };
|
||||
try testing.expectEqual(Result.invalid_value, event_set(event, .word_boundary_codepoints, &invalid));
|
||||
|
||||
try testing.expectEqual(Result.success, event_set(event, .word_boundary_codepoints, null));
|
||||
try testing.expectEqual(
|
||||
selection_codepoints.default_word_boundaries.len,
|
||||
event.?.event.press.word_boundary_codepoints.len,
|
||||
);
|
||||
|
||||
const empty: types.Codepoints = .{ .ptr = null, .len = 0 };
|
||||
try testing.expectEqual(Result.success, event_set(event, .word_boundary_codepoints, &empty));
|
||||
try testing.expectEqual(@as(usize, 0), event.?.event.press.word_boundary_codepoints.len);
|
||||
}
|
||||
|
||||
test "selection gesture event behaviors" {
|
||||
var event: Event = null;
|
||||
try testing.expectEqual(Result.success, event_new(
|
||||
&lib.alloc.test_allocator,
|
||||
&event,
|
||||
.press,
|
||||
));
|
||||
defer event_free(event);
|
||||
|
||||
const in: Behaviors = .{
|
||||
.single_click = .cell,
|
||||
.double_click = .word,
|
||||
.triple_click = .line,
|
||||
};
|
||||
try testing.expectEqual(Result.success, event_set(event, .behaviors, &in));
|
||||
try testing.expectEqual(Behavior.cell, event.?.event.press.behaviors[0]);
|
||||
try testing.expectEqual(Behavior.word, event.?.event.press.behaviors[1]);
|
||||
try testing.expectEqual(Behavior.line, event.?.event.press.behaviors[2]);
|
||||
}
|
||||
|
||||
test "selection gesture free null" {
|
||||
free(null, null);
|
||||
}
|
||||
|
||||
test "selection gesture event free null" {
|
||||
event_free(null);
|
||||
}
|
||||
|
||||
@@ -14,15 +14,29 @@ const size_report = @import("size_report.zig");
|
||||
const terminal = @import("terminal.zig");
|
||||
const formatter = @import("formatter.zig");
|
||||
const selection = @import("selection.zig");
|
||||
const selection_gesture = @import("selection_gesture.zig");
|
||||
const render = @import("render.zig");
|
||||
const style_c = @import("style.zig");
|
||||
const mouse_encode = @import("mouse_encode.zig");
|
||||
const grid_ref = @import("grid_ref.zig");
|
||||
|
||||
/// C: GhosttySurfacePosition
|
||||
pub const SurfacePosition = extern struct {
|
||||
x: f64,
|
||||
y: f64,
|
||||
};
|
||||
|
||||
/// C: GhosttyCodepoints
|
||||
pub const Codepoints = extern struct {
|
||||
ptr: ?[*]const u32 = null,
|
||||
len: usize = 0,
|
||||
};
|
||||
|
||||
/// All C API structs and their Ghostty C names.
|
||||
pub const structs: std.StaticStringMap(StructInfo) = structs: {
|
||||
@setEvalBranchQuota(10_000);
|
||||
break :structs .initComptime(.{
|
||||
.{ "GhosttyCodepoints", StructInfo.init(Codepoints) },
|
||||
.{ "GhosttyColorRgb", StructInfo.init(color.RGB.C) },
|
||||
.{ "GhosttyDeviceAttributes", StructInfo.init(terminal.DeviceAttributes) },
|
||||
.{ "GhosttyDeviceAttributesPrimary", StructInfo.init(terminal.DeviceAttributes.Primary) },
|
||||
@@ -41,8 +55,10 @@ pub const structs: std.StaticStringMap(StructInfo) = structs: {
|
||||
.{ "GhosttyPoint", StructInfo.init(point.Point.C) },
|
||||
.{ "GhosttyPointCoordinate", StructInfo.init(point.Coordinate) },
|
||||
.{ "GhosttyRenderStateColors", StructInfo.init(render.Colors) },
|
||||
.{ "GhosttySelectionGestureBehaviors", StructInfo.init(selection_gesture.Behaviors) },
|
||||
.{ "GhosttySizeReportSize", StructInfo.init(size_report.Size) },
|
||||
.{ "GhosttyString", StructInfo.init(lib.String) },
|
||||
.{ "GhosttySurfacePosition", StructInfo.init(SurfacePosition) },
|
||||
.{ "GhosttyStyle", StructInfo.init(style_c.Style) },
|
||||
.{ "GhosttyStyleColor", StructInfo.init(style_c.Color) },
|
||||
.{ "GhosttyTerminalOptions", StructInfo.init(terminal.Options) },
|
||||
@@ -150,6 +166,11 @@ fn jsonWriteAll(writer: *std.Io.Writer) std.Io.Writer.Error!void {
|
||||
fn typeName(comptime T: type) []const u8 {
|
||||
return switch (@typeInfo(T)) {
|
||||
.bool => "bool",
|
||||
.float => |info| switch (info.bits) {
|
||||
32 => "f32",
|
||||
64 => "f64",
|
||||
else => @compileError("unsupported float size"),
|
||||
},
|
||||
.int => |info| switch (info.signedness) {
|
||||
.signed => switch (info.bits) {
|
||||
8 => "i8",
|
||||
|
||||
Reference in New Issue
Block a user