libghostty: add ghostty_selection_gesture_event
parent
bbfa984aec
commit
5ac8e6569a
|
|
@ -500,6 +500,35 @@ GHOSTTY_API GhosttyResult ghostty_selection_gesture_event_set(
|
||||||
GhosttySelectionGestureEventOption option,
|
GhosttySelectionGestureEventOption option,
|
||||||
const void* value);
|
const void* value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a selection gesture event and return the resulting selection snapshot.
|
||||||
|
*
|
||||||
|
* This dispatches to the gesture operation matching the event's fixed type.
|
||||||
|
* For GHOSTTY_SELECTION_GESTURE_EVENT_TYPE_PRESS, the event must have
|
||||||
|
* GHOSTTY_SELECTION_GESTURE_EVENT_OPT_REF set before calling this function.
|
||||||
|
* All other press options use their initialized defaults when unset or cleared.
|
||||||
|
*
|
||||||
|
* The returned selection is not installed as the terminal's current selection.
|
||||||
|
* It is a snapshot with the same lifetime rules as GhosttySelection.
|
||||||
|
*
|
||||||
|
* @param gesture Selection gesture handle (NULL returns GHOSTTY_INVALID_VALUE)
|
||||||
|
* @param terminal Terminal used to interpret and update gesture state
|
||||||
|
* @param event Selection gesture event handle (NULL returns GHOSTTY_INVALID_VALUE)
|
||||||
|
* @param[out] out_selection On success, receives the resulting selection. May
|
||||||
|
* be NULL to apply the event and discard the selection result.
|
||||||
|
* @return GHOSTTY_SUCCESS on success, GHOSTTY_NO_VALUE if the event does not
|
||||||
|
* currently produce a selection, GHOSTTY_OUT_OF_MEMORY if tracking
|
||||||
|
* gesture state fails, or GHOSTTY_INVALID_VALUE if gesture, terminal,
|
||||||
|
* event, or required event data is invalid
|
||||||
|
*
|
||||||
|
* @ingroup selection
|
||||||
|
*/
|
||||||
|
GHOSTTY_API GhosttyResult ghostty_selection_gesture_event(
|
||||||
|
GhosttySelectionGesture gesture,
|
||||||
|
GhosttyTerminal terminal,
|
||||||
|
GhosttySelectionGestureEvent event,
|
||||||
|
GhosttySelection* out_selection);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a selection gesture object.
|
* Create a selection gesture object.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -254,6 +254,7 @@ comptime {
|
||||||
@export(&c.selection_gesture_new, .{ .name = "ghostty_selection_gesture_new" });
|
@export(&c.selection_gesture_new, .{ .name = "ghostty_selection_gesture_new" });
|
||||||
@export(&c.selection_gesture_free, .{ .name = "ghostty_selection_gesture_free" });
|
@export(&c.selection_gesture_free, .{ .name = "ghostty_selection_gesture_free" });
|
||||||
@export(&c.selection_gesture_reset, .{ .name = "ghostty_selection_gesture_reset" });
|
@export(&c.selection_gesture_reset, .{ .name = "ghostty_selection_gesture_reset" });
|
||||||
|
@export(&c.selection_gesture_event, .{ .name = "ghostty_selection_gesture_event" });
|
||||||
@export(&c.selection_gesture_get, .{ .name = "ghostty_selection_gesture_get" });
|
@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_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_new, .{ .name = "ghostty_selection_gesture_event_new" });
|
||||||
|
|
|
||||||
|
|
@ -186,6 +186,7 @@ pub const terminal_selection_equal = selection.equal;
|
||||||
pub const selection_gesture_new = selection_gesture.new;
|
pub const selection_gesture_new = selection_gesture.new;
|
||||||
pub const selection_gesture_free = selection_gesture.free;
|
pub const selection_gesture_free = selection_gesture.free;
|
||||||
pub const selection_gesture_reset = selection_gesture.reset;
|
pub const selection_gesture_reset = selection_gesture.reset;
|
||||||
|
pub const selection_gesture_event = selection_gesture.handle_event;
|
||||||
pub const selection_gesture_get = selection_gesture.get;
|
pub const selection_gesture_get = selection_gesture.get;
|
||||||
pub const selection_gesture_get_multi = selection_gesture.get_multi;
|
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_new = selection_gesture.event_new;
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ const CAllocator = lib.alloc.Allocator;
|
||||||
const SelectionGesture = @import("../SelectionGesture.zig");
|
const SelectionGesture = @import("../SelectionGesture.zig");
|
||||||
const selection_codepoints = @import("../selection_codepoints.zig");
|
const selection_codepoints = @import("../selection_codepoints.zig");
|
||||||
const grid_ref = @import("grid_ref.zig");
|
const grid_ref = @import("grid_ref.zig");
|
||||||
|
const selection_c = @import("selection.zig");
|
||||||
const terminal_c = @import("terminal.zig");
|
const terminal_c = @import("terminal.zig");
|
||||||
const types = @import("types.zig");
|
const types = @import("types.zig");
|
||||||
const Result = @import("result.zig").Result;
|
const Result = @import("result.zig").Result;
|
||||||
|
|
@ -29,6 +30,12 @@ const EventWrapper = struct {
|
||||||
press: SelectionGesture.Press,
|
press: SelectionGesture.Press,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Press.pin has no safe sentinel value: PageList.Pin contains a non-null
|
||||||
|
// node pointer and is undefined until the C caller provides a GhosttyGridRef.
|
||||||
|
// Track that separately so event execution can reject a press whose required
|
||||||
|
// ref option was never set, or was later cleared.
|
||||||
|
press_pin_set: bool = false,
|
||||||
|
|
||||||
// Backing storage for Press.word_boundary_codepoints. The C API receives
|
// Backing storage for Press.word_boundary_codepoints. The C API receives
|
||||||
// codepoints as borrowed uint32_t values, but SelectionGesture.Press stores
|
// codepoints as borrowed uint32_t values, but SelectionGesture.Press stores
|
||||||
// a []const u21 slice. We copy/convert into event-owned storage so the real
|
// a []const u21 slice. We copy/convert into event-owned storage so the real
|
||||||
|
|
@ -196,6 +203,28 @@ pub fn reset(
|
||||||
wrapper.gesture.reset(t);
|
wrapper.gesture.reset(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn handle_event(
|
||||||
|
gesture_: Gesture,
|
||||||
|
terminal: terminal_c.Terminal,
|
||||||
|
event_: Event,
|
||||||
|
out_selection: ?*selection_c.CSelection,
|
||||||
|
) callconv(lib.calling_conv) Result {
|
||||||
|
const wrapper = gesture_ orelse return .invalid_value;
|
||||||
|
const t = terminal_c.zigTerminal(terminal) orelse return .invalid_value;
|
||||||
|
const event_wrapper = event_ orelse return .invalid_value;
|
||||||
|
|
||||||
|
return switch (event_wrapper.event) {
|
||||||
|
.press => |press| {
|
||||||
|
if (!event_wrapper.press_pin_set) return .invalid_value;
|
||||||
|
const sel = wrapper.gesture.press(t, press) catch return .out_of_memory;
|
||||||
|
if (out_selection) |out| {
|
||||||
|
out.* = selection_c.CSelection.fromZig(sel orelse return .no_value);
|
||||||
|
} else if (sel == null) return .no_value;
|
||||||
|
return .success;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub fn event_set(
|
pub fn event_set(
|
||||||
event_: Event,
|
event_: Event,
|
||||||
option: EventOption,
|
option: EventOption,
|
||||||
|
|
@ -306,7 +335,7 @@ fn pressSetTyped(
|
||||||
) Result {
|
) Result {
|
||||||
const v = value orelse {
|
const v = value orelse {
|
||||||
switch (option) {
|
switch (option) {
|
||||||
.ref => {},
|
.ref => event.press_pin_set = false,
|
||||||
.position => {
|
.position => {
|
||||||
press.xpos = 0;
|
press.xpos = 0;
|
||||||
press.ypos = 0;
|
press.ypos = 0;
|
||||||
|
|
@ -324,7 +353,10 @@ fn pressSetTyped(
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (option) {
|
switch (option) {
|
||||||
.ref => press.pin = v.toPin() orelse return .invalid_value,
|
.ref => {
|
||||||
|
press.pin = v.toPin() orelse return .invalid_value;
|
||||||
|
event.press_pin_set = true;
|
||||||
|
},
|
||||||
.position => {
|
.position => {
|
||||||
press.xpos = v.x;
|
press.xpos = v.x;
|
||||||
press.ypos = v.y;
|
press.ypos = v.y;
|
||||||
|
|
@ -575,6 +607,115 @@ test "selection gesture event behaviors" {
|
||||||
try testing.expectEqual(Behavior.line, event.?.event.press.behaviors[2]);
|
try testing.expectEqual(Behavior.line, event.?.event.press.behaviors[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "selection gesture event applies press" {
|
||||||
|
var terminal: terminal_c.Terminal = null;
|
||||||
|
try testing.expectEqual(Result.success, terminal_c.new(
|
||||||
|
&lib.alloc.test_allocator,
|
||||||
|
&terminal,
|
||||||
|
.{ .cols = 5, .rows = 2, .max_scrollback = 10_000 },
|
||||||
|
));
|
||||||
|
defer terminal_c.free(terminal);
|
||||||
|
|
||||||
|
var gesture: Gesture = null;
|
||||||
|
try testing.expectEqual(Result.success, new(
|
||||||
|
&lib.alloc.test_allocator,
|
||||||
|
&gesture,
|
||||||
|
));
|
||||||
|
defer free(gesture, terminal);
|
||||||
|
|
||||||
|
var press_event: Event = null;
|
||||||
|
try testing.expectEqual(Result.success, event_new(
|
||||||
|
&lib.alloc.test_allocator,
|
||||||
|
&press_event,
|
||||||
|
.press,
|
||||||
|
));
|
||||||
|
defer event_free(press_event);
|
||||||
|
|
||||||
|
terminal_c.vt_write(terminal, "abc", 3);
|
||||||
|
|
||||||
|
var ref: grid_ref.CGridRef = undefined;
|
||||||
|
try testing.expectEqual(Result.success, terminal_c.grid_ref(terminal, .{
|
||||||
|
.tag = .active,
|
||||||
|
.value = .{ .active = .{ .x = 1, .y = 0 } },
|
||||||
|
}, &ref));
|
||||||
|
try testing.expectEqual(Result.success, event_set(press_event, .ref, &ref));
|
||||||
|
const behaviors: Behaviors = .{
|
||||||
|
.single_click = .word,
|
||||||
|
.double_click = .word,
|
||||||
|
.triple_click = .line,
|
||||||
|
};
|
||||||
|
try testing.expectEqual(Result.success, event_set(press_event, .behaviors, &behaviors));
|
||||||
|
|
||||||
|
var sel: selection_c.CSelection = undefined;
|
||||||
|
try testing.expectEqual(Result.success, handle_event(gesture, terminal, press_event, &sel));
|
||||||
|
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, handle_event(gesture, terminal, press_event, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "selection gesture event press requires ref" {
|
||||||
|
var terminal: terminal_c.Terminal = null;
|
||||||
|
try testing.expectEqual(Result.success, terminal_c.new(
|
||||||
|
&lib.alloc.test_allocator,
|
||||||
|
&terminal,
|
||||||
|
.{ .cols = 5, .rows = 2, .max_scrollback = 10_000 },
|
||||||
|
));
|
||||||
|
defer terminal_c.free(terminal);
|
||||||
|
|
||||||
|
var gesture: Gesture = null;
|
||||||
|
try testing.expectEqual(Result.success, new(
|
||||||
|
&lib.alloc.test_allocator,
|
||||||
|
&gesture,
|
||||||
|
));
|
||||||
|
defer free(gesture, terminal);
|
||||||
|
|
||||||
|
var press_event: Event = null;
|
||||||
|
try testing.expectEqual(Result.success, event_new(
|
||||||
|
&lib.alloc.test_allocator,
|
||||||
|
&press_event,
|
||||||
|
.press,
|
||||||
|
));
|
||||||
|
defer event_free(press_event);
|
||||||
|
|
||||||
|
var sel: selection_c.CSelection = undefined;
|
||||||
|
try testing.expectEqual(Result.invalid_value, handle_event(gesture, terminal, press_event, &sel));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "selection gesture event null output still reports no selection" {
|
||||||
|
var terminal: terminal_c.Terminal = null;
|
||||||
|
try testing.expectEqual(Result.success, terminal_c.new(
|
||||||
|
&lib.alloc.test_allocator,
|
||||||
|
&terminal,
|
||||||
|
.{ .cols = 5, .rows = 2, .max_scrollback = 10_000 },
|
||||||
|
));
|
||||||
|
defer terminal_c.free(terminal);
|
||||||
|
|
||||||
|
var gesture: Gesture = null;
|
||||||
|
try testing.expectEqual(Result.success, new(
|
||||||
|
&lib.alloc.test_allocator,
|
||||||
|
&gesture,
|
||||||
|
));
|
||||||
|
defer free(gesture, terminal);
|
||||||
|
|
||||||
|
var press_event: Event = null;
|
||||||
|
try testing.expectEqual(Result.success, event_new(
|
||||||
|
&lib.alloc.test_allocator,
|
||||||
|
&press_event,
|
||||||
|
.press,
|
||||||
|
));
|
||||||
|
defer event_free(press_event);
|
||||||
|
|
||||||
|
var ref: grid_ref.CGridRef = undefined;
|
||||||
|
try testing.expectEqual(Result.success, terminal_c.grid_ref(terminal, .{
|
||||||
|
.tag = .active,
|
||||||
|
.value = .{ .active = .{ .x = 1, .y = 0 } },
|
||||||
|
}, &ref));
|
||||||
|
try testing.expectEqual(Result.success, event_set(press_event, .ref, &ref));
|
||||||
|
|
||||||
|
try testing.expectEqual(Result.no_value, handle_event(gesture, terminal, press_event, null));
|
||||||
|
}
|
||||||
|
|
||||||
test "selection gesture free null" {
|
test "selection gesture free null" {
|
||||||
free(null, null);
|
free(null, null);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue