libghostty: handle non-monotonic selection times in SelectionGesture
We expect monotonic time but since this is libghostty-exposed we need to be more careful about guarding what we accept. This also practically makes it easier to integrate with various languages. Compare the two instants first and treat backwards timestamps like any other failed repeat. The next press becomes a fresh single-click anchor, and a regression test covers the reset behavior.pull/12834/head
parent
4e2d7c314b
commit
0e93fbfe76
|
|
@ -477,7 +477,8 @@ typedef enum GHOSTTY_ENUM_TYPED {
|
||||||
* Optional monotonic event time in nanoseconds: uint64_t*.
|
* Optional monotonic event time in nanoseconds: uint64_t*.
|
||||||
*
|
*
|
||||||
* If unset, press treats the event as untimed and only single-click behavior
|
* If unset, press treats the event as untimed and only single-click behavior
|
||||||
* is available.
|
* is available. If a repeat press provides a time earlier than the previous
|
||||||
|
* press, the repeat sequence resets.
|
||||||
*/
|
*/
|
||||||
GHOSTTY_SELECTION_GESTURE_EVENT_OPT_TIME_NS = 3,
|
GHOSTTY_SELECTION_GESTURE_EVENT_OPT_TIME_NS = 3,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3773,22 +3773,19 @@ pub fn mouseButtonCallback(
|
||||||
|
|
||||||
// If we are within the interval that the click would register
|
// If we are within the interval that the click would register
|
||||||
// an increment then we do not extend the selection.
|
// an increment then we do not extend the selection.
|
||||||
if (std.time.Instant.now()) |now| {
|
{
|
||||||
const click_time = self.mouse.selection_gesture.left_click_time orelse
|
const click_time = self.mouse.selection_gesture.left_click_time orelse
|
||||||
break :extend_selection;
|
break :extend_selection;
|
||||||
const since = now.since(click_time);
|
const now: u64 = @intCast(std.time.nanoTimestamp());
|
||||||
|
const since = if (now >= click_time)
|
||||||
|
now - click_time
|
||||||
|
else
|
||||||
|
self.config.mouse_interval +| 1;
|
||||||
if (since <= self.config.mouse_interval) {
|
if (since <= self.config.mouse_interval) {
|
||||||
// Click interval very short, we may be increasing
|
// Click interval very short, we may be increasing
|
||||||
// click counts so we don't extend the selection.
|
// click counts so we don't extend the selection.
|
||||||
break :extend_selection;
|
break :extend_selection;
|
||||||
}
|
}
|
||||||
} else |err| {
|
|
||||||
// This is a weird behavior, I think either behavior is actually
|
|
||||||
// fine. This failure should be exceptionally rare anyways.
|
|
||||||
// My thinking here is that we can't be sure if we should extend
|
|
||||||
// the selection or not so we just don't.
|
|
||||||
log.warn("failed to get time, not extending selection err={}", .{err});
|
|
||||||
break :extend_selection;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const pos = try self.rt_surface.getCursorPos();
|
const pos = try self.rt_surface.getCursorPos();
|
||||||
|
|
@ -3939,10 +3936,7 @@ pub fn mouseButtonCallback(
|
||||||
break :pin pin;
|
break :pin pin;
|
||||||
};
|
};
|
||||||
|
|
||||||
const time = std.time.Instant.now() catch |err| time: {
|
const time: u64 = @intCast(std.time.nanoTimestamp());
|
||||||
log.err("error reading time, mouse multi-click won't work err={}", .{err});
|
|
||||||
break :time null;
|
|
||||||
};
|
|
||||||
var press_selection = try self.mouse.selection_gesture.press(t, .{
|
var press_selection = try self.mouse.selection_gesture.press(t, .{
|
||||||
.time = time,
|
.time = time,
|
||||||
.pin = pin,
|
.pin = pin,
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ left_click_screen_generation: usize,
|
||||||
/// The left click time was the last time the left click was done, if the
|
/// The left click time was the last time the left click was done, if the
|
||||||
/// caller could provide one. If this is null then we only support single clicks.
|
/// caller could provide one. If this is null then we only support single clicks.
|
||||||
left_click_count: u3,
|
left_click_count: u3,
|
||||||
left_click_time: ?std.time.Instant,
|
left_click_time: ?u64,
|
||||||
|
|
||||||
/// The selection behavior chosen for the active left-click gesture.
|
/// The selection behavior chosen for the active left-click gesture.
|
||||||
left_click_behavior: Behavior,
|
left_click_behavior: Behavior,
|
||||||
|
|
@ -217,10 +217,11 @@ pub fn validatedLeftClickPin(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const Press = struct {
|
pub const Press = struct {
|
||||||
/// The time when the press event occurred. Use a monotonic timer.
|
/// The time when the press event occurred. Prefer a monotonic timer;
|
||||||
|
/// non-monotonic entries reset the repeat sequence.
|
||||||
/// This can be null if you're on a system that doesn't support
|
/// This can be null if you're on a system that doesn't support
|
||||||
/// time for some reason. In that case, we only support single clicks.
|
/// time for some reason. In that case, we only support single clicks.
|
||||||
time: ?std.time.Instant,
|
time: ?u64,
|
||||||
|
|
||||||
/// The cell where the click was.
|
/// The cell where the click was.
|
||||||
///
|
///
|
||||||
|
|
@ -619,7 +620,8 @@ fn pressRepeat(
|
||||||
const time = p.time orelse return error.PressRequiresReset;
|
const time = p.time orelse return error.PressRequiresReset;
|
||||||
{
|
{
|
||||||
const prev_time = self.left_click_time orelse return error.PressRequiresReset;
|
const prev_time = self.left_click_time orelse return error.PressRequiresReset;
|
||||||
const since = time.since(prev_time);
|
if (time < prev_time) return error.PressRequiresReset;
|
||||||
|
const since = time - prev_time;
|
||||||
if (since > p.repeat_interval) return error.PressRequiresReset;
|
if (since > p.repeat_interval) return error.PressRequiresReset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -904,7 +906,7 @@ fn untrackPin(self: *SelectionGesture, t: *Terminal) void {
|
||||||
screen.pages.untrackPin(pin);
|
screen.pages.untrackPin(pin);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn testPress(t: *Terminal, x: u16, y: u32, time: ?std.time.Instant) Press {
|
fn testPress(t: *Terminal, x: u16, y: u32, time: ?u64) Press {
|
||||||
return .{
|
return .{
|
||||||
.time = time,
|
.time = time,
|
||||||
.pin = t.screens.active.pages.pin(.{ .active = .{
|
.pin = t.screens.active.pages.pin(.{ .active = .{
|
||||||
|
|
@ -919,6 +921,10 @@ fn testPress(t: *Terminal, x: u16, y: u32, time: ?std.time.Instant) Press {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn testNow() u64 {
|
||||||
|
return @intCast(std.time.nanoTimestamp());
|
||||||
|
}
|
||||||
|
|
||||||
fn testDrag(t: *Terminal, x: u16, y: u32, xpos: f64, ypos: f64) Drag {
|
fn testDrag(t: *Terminal, x: u16, y: u32, xpos: f64, ypos: f64) Drag {
|
||||||
return .{
|
return .{
|
||||||
.pin = t.screens.active.pages.pin(.{ .active = .{
|
.pin = t.screens.active.pages.pin(.{ .active = .{
|
||||||
|
|
@ -1383,7 +1389,7 @@ test "SelectionGesture press records initial click" {
|
||||||
var gesture: SelectionGesture = .init;
|
var gesture: SelectionGesture = .init;
|
||||||
defer gesture.deinit(&t);
|
defer gesture.deinit(&t);
|
||||||
|
|
||||||
const time = try std.time.Instant.now();
|
const time = testNow();
|
||||||
_ = try gesture.press(&t, testPress(&t, 1, 2, time));
|
_ = try gesture.press(&t, testPress(&t, 1, 2, time));
|
||||||
|
|
||||||
try testing.expectEqual(@as(u3, 1), gesture.left_click_count);
|
try testing.expectEqual(@as(u3, 1), gesture.left_click_count);
|
||||||
|
|
@ -1401,7 +1407,7 @@ test "SelectionGesture press returns standard click selections" {
|
||||||
var gesture: SelectionGesture = .init;
|
var gesture: SelectionGesture = .init;
|
||||||
defer gesture.deinit(&t);
|
defer gesture.deinit(&t);
|
||||||
|
|
||||||
const time = try std.time.Instant.now();
|
const time = testNow();
|
||||||
var event = testPress(&t, 1, 0, time);
|
var event = testPress(&t, 1, 0, time);
|
||||||
event.word_boundary_codepoints = &.{ ' ' };
|
event.word_boundary_codepoints = &.{ ' ' };
|
||||||
|
|
||||||
|
|
@ -1428,7 +1434,7 @@ test "SelectionGesture press behaviors choose press and drag behavior" {
|
||||||
var gesture: SelectionGesture = .init;
|
var gesture: SelectionGesture = .init;
|
||||||
defer gesture.deinit(&t);
|
defer gesture.deinit(&t);
|
||||||
|
|
||||||
const time = try std.time.Instant.now();
|
const time = testNow();
|
||||||
var event = testPress(&t, 1, 0, time);
|
var event = testPress(&t, 1, 0, time);
|
||||||
event.behaviors = &.{ .cell, .line, .word };
|
event.behaviors = &.{ .cell, .line, .word };
|
||||||
event.word_boundary_codepoints = &.{ ' ' };
|
event.word_boundary_codepoints = &.{ ' ' };
|
||||||
|
|
@ -1469,7 +1475,7 @@ test "SelectionGesture output behavior selects and drags semantic output" {
|
||||||
var gesture: SelectionGesture = .init;
|
var gesture: SelectionGesture = .init;
|
||||||
defer gesture.deinit(&t);
|
defer gesture.deinit(&t);
|
||||||
|
|
||||||
var event = testPress(&t, 1, 0, try std.time.Instant.now());
|
var event = testPress(&t, 1, 0, testNow());
|
||||||
event.behaviors = &.{ .output, .word, .line };
|
event.behaviors = &.{ .output, .word, .line };
|
||||||
|
|
||||||
const press_selection = (try gesture.press(&t, event)).?;
|
const press_selection = (try gesture.press(&t, event)).?;
|
||||||
|
|
@ -1495,7 +1501,7 @@ test "SelectionGesture drag returns selection and records autoscroll" {
|
||||||
var gesture: SelectionGesture = .init;
|
var gesture: SelectionGesture = .init;
|
||||||
defer gesture.deinit(&t);
|
defer gesture.deinit(&t);
|
||||||
|
|
||||||
var press_event = testPress(&t, 1, 1, try std.time.Instant.now());
|
var press_event = testPress(&t, 1, 1, testNow());
|
||||||
press_event.xpos = 10;
|
press_event.xpos = 10;
|
||||||
_ = try gesture.press(&t, press_event);
|
_ = try gesture.press(&t, press_event);
|
||||||
|
|
||||||
|
|
@ -1523,7 +1529,7 @@ test "SelectionGesture release clears autoscroll and records drag" {
|
||||||
var gesture: SelectionGesture = .init;
|
var gesture: SelectionGesture = .init;
|
||||||
defer gesture.deinit(&t);
|
defer gesture.deinit(&t);
|
||||||
|
|
||||||
_ = try gesture.press(&t, testPress(&t, 1, 1, try std.time.Instant.now()));
|
_ = try gesture.press(&t, testPress(&t, 1, 1, testNow()));
|
||||||
try testing.expectEqual(false, gesture.left_click_dragged);
|
try testing.expectEqual(false, gesture.left_click_dragged);
|
||||||
|
|
||||||
_ = gesture.drag(&t, testDrag(&t, 1, 1, 10, 1));
|
_ = gesture.drag(&t, testDrag(&t, 1, 1, 10, 1));
|
||||||
|
|
@ -1544,7 +1550,7 @@ test "SelectionGesture release with invalidated click records drag" {
|
||||||
var gesture: SelectionGesture = .init;
|
var gesture: SelectionGesture = .init;
|
||||||
defer gesture.deinit(&t);
|
defer gesture.deinit(&t);
|
||||||
|
|
||||||
_ = try gesture.press(&t, testPress(&t, 1, 1, try std.time.Instant.now()));
|
_ = try gesture.press(&t, testPress(&t, 1, 1, testNow()));
|
||||||
try testing.expectEqual(false, gesture.left_click_dragged);
|
try testing.expectEqual(false, gesture.left_click_dragged);
|
||||||
|
|
||||||
_ = try t.screens.getInit(testing.allocator, .alternate, .{
|
_ = try t.screens.getInit(testing.allocator, .alternate, .{
|
||||||
|
|
@ -1565,7 +1571,7 @@ test "SelectionGesture same-cell threshold selection records drag" {
|
||||||
var gesture: SelectionGesture = .init;
|
var gesture: SelectionGesture = .init;
|
||||||
defer gesture.deinit(&t);
|
defer gesture.deinit(&t);
|
||||||
|
|
||||||
var press_event = testPress(&t, 1, 1, try std.time.Instant.now());
|
var press_event = testPress(&t, 1, 1, testNow());
|
||||||
press_event.xpos = 10;
|
press_event.xpos = 10;
|
||||||
_ = try gesture.press(&t, press_event);
|
_ = try gesture.press(&t, press_event);
|
||||||
try testing.expectEqual(false, gesture.left_click_dragged);
|
try testing.expectEqual(false, gesture.left_click_dragged);
|
||||||
|
|
@ -1597,7 +1603,7 @@ test "SelectionGesture drag autoscroll edge boundaries" {
|
||||||
var gesture: SelectionGesture = .init;
|
var gesture: SelectionGesture = .init;
|
||||||
defer gesture.deinit(&t);
|
defer gesture.deinit(&t);
|
||||||
|
|
||||||
var press_event = testPress(&t, 1, 1, try std.time.Instant.now());
|
var press_event = testPress(&t, 1, 1, testNow());
|
||||||
press_event.xpos = 10;
|
press_event.xpos = 10;
|
||||||
_ = try gesture.press(&t, press_event);
|
_ = try gesture.press(&t, press_event);
|
||||||
|
|
||||||
|
|
@ -1621,7 +1627,7 @@ test "SelectionGesture autoscroll tick scrolls and continues drag" {
|
||||||
var gesture: SelectionGesture = .init;
|
var gesture: SelectionGesture = .init;
|
||||||
defer gesture.deinit(&t);
|
defer gesture.deinit(&t);
|
||||||
|
|
||||||
var press_event = testPress(&t, 1, 1, try std.time.Instant.now());
|
var press_event = testPress(&t, 1, 1, testNow());
|
||||||
press_event.xpos = 10;
|
press_event.xpos = 10;
|
||||||
_ = try gesture.press(&t, press_event);
|
_ = try gesture.press(&t, press_event);
|
||||||
|
|
||||||
|
|
@ -1647,7 +1653,7 @@ test "SelectionGesture autoscroll tick resolves drag pin after scrolling" {
|
||||||
var gesture: SelectionGesture = .init;
|
var gesture: SelectionGesture = .init;
|
||||||
defer gesture.deinit(&t);
|
defer gesture.deinit(&t);
|
||||||
|
|
||||||
var press_event = testPress(&t, 1, 1, try std.time.Instant.now());
|
var press_event = testPress(&t, 1, 1, testNow());
|
||||||
press_event.xpos = 10;
|
press_event.xpos = 10;
|
||||||
_ = try gesture.press(&t, press_event);
|
_ = try gesture.press(&t, press_event);
|
||||||
|
|
||||||
|
|
@ -1674,7 +1680,7 @@ test "SelectionGesture autoscroll tick stops with invalidated click" {
|
||||||
var gesture: SelectionGesture = .init;
|
var gesture: SelectionGesture = .init;
|
||||||
defer gesture.deinit(&t);
|
defer gesture.deinit(&t);
|
||||||
|
|
||||||
var press_event = testPress(&t, 1, 1, try std.time.Instant.now());
|
var press_event = testPress(&t, 1, 1, testNow());
|
||||||
press_event.xpos = 10;
|
press_event.xpos = 10;
|
||||||
_ = try gesture.press(&t, press_event);
|
_ = try gesture.press(&t, press_event);
|
||||||
|
|
||||||
|
|
@ -1700,7 +1706,7 @@ test "SelectionGesture deep press selects word and consumes drag" {
|
||||||
var gesture: SelectionGesture = .init;
|
var gesture: SelectionGesture = .init;
|
||||||
defer gesture.deinit(&t);
|
defer gesture.deinit(&t);
|
||||||
|
|
||||||
_ = try gesture.press(&t, testPress(&t, 1, 0, try std.time.Instant.now()));
|
_ = try gesture.press(&t, testPress(&t, 1, 0, testNow()));
|
||||||
_ = gesture.drag(&t, testDrag(&t, 1, 0, 10, 1));
|
_ = gesture.drag(&t, testDrag(&t, 1, 0, 10, 1));
|
||||||
try testing.expectEqual(.up, gesture.left_drag_autoscroll);
|
try testing.expectEqual(.up, gesture.left_drag_autoscroll);
|
||||||
|
|
||||||
|
|
@ -1714,7 +1720,7 @@ test "SelectionGesture deep press selects word and consumes drag" {
|
||||||
false,
|
false,
|
||||||
), sel);
|
), sel);
|
||||||
try testing.expectEqual(@as(u3, 0), gesture.left_click_count);
|
try testing.expectEqual(@as(u3, 0), gesture.left_click_count);
|
||||||
try testing.expectEqual(@as(?std.time.Instant, null), gesture.left_click_time);
|
try testing.expectEqual(@as(?u64, null), gesture.left_click_time);
|
||||||
try testing.expectEqual(true, gesture.left_click_dragged);
|
try testing.expectEqual(true, gesture.left_click_dragged);
|
||||||
try testing.expectEqual(.none, gesture.left_drag_autoscroll);
|
try testing.expectEqual(.none, gesture.left_drag_autoscroll);
|
||||||
try testing.expect(gesture.left_click_pin == null);
|
try testing.expect(gesture.left_click_pin == null);
|
||||||
|
|
@ -1731,7 +1737,7 @@ test "SelectionGesture drag with invalidated click returns null" {
|
||||||
var gesture: SelectionGesture = .init;
|
var gesture: SelectionGesture = .init;
|
||||||
defer gesture.deinit(&t);
|
defer gesture.deinit(&t);
|
||||||
|
|
||||||
var press_event = testPress(&t, 1, 1, try std.time.Instant.now());
|
var press_event = testPress(&t, 1, 1, testNow());
|
||||||
press_event.xpos = 10;
|
press_event.xpos = 10;
|
||||||
_ = try gesture.press(&t, press_event);
|
_ = try gesture.press(&t, press_event);
|
||||||
|
|
||||||
|
|
@ -1756,7 +1762,7 @@ test "SelectionGesture double-click drag selects by word" {
|
||||||
var gesture: SelectionGesture = .init;
|
var gesture: SelectionGesture = .init;
|
||||||
defer gesture.deinit(&t);
|
defer gesture.deinit(&t);
|
||||||
|
|
||||||
const time = try std.time.Instant.now();
|
const time = testNow();
|
||||||
_ = try gesture.press(&t, testPress(&t, 1, 0, time));
|
_ = try gesture.press(&t, testPress(&t, 1, 0, time));
|
||||||
_ = try gesture.press(&t, testPress(&t, 1, 0, time));
|
_ = try gesture.press(&t, testPress(&t, 1, 0, time));
|
||||||
|
|
||||||
|
|
@ -1779,7 +1785,7 @@ test "SelectionGesture double-click drag selects by word backwards" {
|
||||||
var gesture: SelectionGesture = .init;
|
var gesture: SelectionGesture = .init;
|
||||||
defer gesture.deinit(&t);
|
defer gesture.deinit(&t);
|
||||||
|
|
||||||
const time = try std.time.Instant.now();
|
const time = testNow();
|
||||||
_ = try gesture.press(&t, testPress(&t, 7, 0, time));
|
_ = try gesture.press(&t, testPress(&t, 7, 0, time));
|
||||||
_ = try gesture.press(&t, testPress(&t, 7, 0, time));
|
_ = try gesture.press(&t, testPress(&t, 7, 0, time));
|
||||||
|
|
||||||
|
|
@ -1802,7 +1808,7 @@ test "SelectionGesture double-click drag on empty cell selects nearest word" {
|
||||||
var gesture: SelectionGesture = .init;
|
var gesture: SelectionGesture = .init;
|
||||||
defer gesture.deinit(&t);
|
defer gesture.deinit(&t);
|
||||||
|
|
||||||
const time = try std.time.Instant.now();
|
const time = testNow();
|
||||||
_ = try gesture.press(&t, testPress(&t, 1, 0, time));
|
_ = try gesture.press(&t, testPress(&t, 1, 0, time));
|
||||||
_ = try gesture.press(&t, testPress(&t, 1, 0, time));
|
_ = try gesture.press(&t, testPress(&t, 1, 0, time));
|
||||||
|
|
||||||
|
|
@ -1825,7 +1831,7 @@ test "SelectionGesture triple-click drag selects by line" {
|
||||||
var gesture: SelectionGesture = .init;
|
var gesture: SelectionGesture = .init;
|
||||||
defer gesture.deinit(&t);
|
defer gesture.deinit(&t);
|
||||||
|
|
||||||
const time = try std.time.Instant.now();
|
const time = testNow();
|
||||||
_ = try gesture.press(&t, testPress(&t, 1, 0, time));
|
_ = try gesture.press(&t, testPress(&t, 1, 0, time));
|
||||||
_ = try gesture.press(&t, testPress(&t, 1, 0, time));
|
_ = try gesture.press(&t, testPress(&t, 1, 0, time));
|
||||||
_ = try gesture.press(&t, testPress(&t, 1, 0, time));
|
_ = try gesture.press(&t, testPress(&t, 1, 0, time));
|
||||||
|
|
@ -1847,7 +1853,7 @@ test "SelectionGesture triple-click drag selects by line backwards" {
|
||||||
var gesture: SelectionGesture = .init;
|
var gesture: SelectionGesture = .init;
|
||||||
defer gesture.deinit(&t);
|
defer gesture.deinit(&t);
|
||||||
|
|
||||||
const time = try std.time.Instant.now();
|
const time = testNow();
|
||||||
_ = try gesture.press(&t, testPress(&t, 2, 2, time));
|
_ = try gesture.press(&t, testPress(&t, 2, 2, time));
|
||||||
_ = try gesture.press(&t, testPress(&t, 2, 2, time));
|
_ = try gesture.press(&t, testPress(&t, 2, 2, time));
|
||||||
_ = try gesture.press(&t, testPress(&t, 2, 2, time));
|
_ = try gesture.press(&t, testPress(&t, 2, 2, time));
|
||||||
|
|
@ -1868,7 +1874,7 @@ test "SelectionGesture repeat increments click count" {
|
||||||
var gesture: SelectionGesture = .init;
|
var gesture: SelectionGesture = .init;
|
||||||
defer gesture.deinit(&t);
|
defer gesture.deinit(&t);
|
||||||
|
|
||||||
const time = try std.time.Instant.now();
|
const time = testNow();
|
||||||
_ = try gesture.press(&t, testPress(&t, 1, 1, time));
|
_ = try gesture.press(&t, testPress(&t, 1, 1, time));
|
||||||
_ = try gesture.press(&t, testPress(&t, 1, 1, time));
|
_ = try gesture.press(&t, testPress(&t, 1, 1, time));
|
||||||
|
|
||||||
|
|
@ -1882,7 +1888,7 @@ test "SelectionGesture repeat clamps at triple click" {
|
||||||
var gesture: SelectionGesture = .init;
|
var gesture: SelectionGesture = .init;
|
||||||
defer gesture.deinit(&t);
|
defer gesture.deinit(&t);
|
||||||
|
|
||||||
const time = try std.time.Instant.now();
|
const time = testNow();
|
||||||
for (0..4) |_| _ = try gesture.press(&t, testPress(&t, 1, 1, time));
|
for (0..4) |_| _ = try gesture.press(&t, testPress(&t, 1, 1, time));
|
||||||
|
|
||||||
try testing.expectEqual(@as(u3, 3), gesture.left_click_count);
|
try testing.expectEqual(@as(u3, 3), gesture.left_click_count);
|
||||||
|
|
@ -1896,7 +1902,7 @@ test "SelectionGesture null initial time stays single click" {
|
||||||
defer gesture.deinit(&t);
|
defer gesture.deinit(&t);
|
||||||
|
|
||||||
_ = try gesture.press(&t, testPress(&t, 1, 1, null));
|
_ = try gesture.press(&t, testPress(&t, 1, 1, null));
|
||||||
_ = try gesture.press(&t, testPress(&t, 1, 1, try std.time.Instant.now()));
|
_ = try gesture.press(&t, testPress(&t, 1, 1, testNow()));
|
||||||
|
|
||||||
try testing.expectEqual(@as(u3, 1), gesture.left_click_count);
|
try testing.expectEqual(@as(u3, 1), gesture.left_click_count);
|
||||||
try testing.expect(gesture.left_click_time != null);
|
try testing.expect(gesture.left_click_time != null);
|
||||||
|
|
@ -1909,11 +1915,11 @@ test "SelectionGesture null repeat time stays single click" {
|
||||||
var gesture: SelectionGesture = .init;
|
var gesture: SelectionGesture = .init;
|
||||||
defer gesture.deinit(&t);
|
defer gesture.deinit(&t);
|
||||||
|
|
||||||
_ = try gesture.press(&t, testPress(&t, 1, 1, try std.time.Instant.now()));
|
_ = try gesture.press(&t, testPress(&t, 1, 1, testNow()));
|
||||||
_ = try gesture.press(&t, testPress(&t, 1, 1, null));
|
_ = try gesture.press(&t, testPress(&t, 1, 1, null));
|
||||||
|
|
||||||
try testing.expectEqual(@as(u3, 1), gesture.left_click_count);
|
try testing.expectEqual(@as(u3, 1), gesture.left_click_count);
|
||||||
try testing.expectEqual(@as(?std.time.Instant, null), gesture.left_click_time);
|
try testing.expectEqual(@as(?u64, null), gesture.left_click_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "SelectionGesture distant press resets click count" {
|
test "SelectionGesture distant press resets click count" {
|
||||||
|
|
@ -1923,7 +1929,7 @@ test "SelectionGesture distant press resets click count" {
|
||||||
var gesture: SelectionGesture = .init;
|
var gesture: SelectionGesture = .init;
|
||||||
defer gesture.deinit(&t);
|
defer gesture.deinit(&t);
|
||||||
|
|
||||||
const time = try std.time.Instant.now();
|
const time = testNow();
|
||||||
_ = try gesture.press(&t, testPress(&t, 1, 1, time));
|
_ = try gesture.press(&t, testPress(&t, 1, 1, time));
|
||||||
_ = try gesture.press(&t, testPress(&t, 4, 1, time));
|
_ = try gesture.press(&t, testPress(&t, 4, 1, time));
|
||||||
|
|
||||||
|
|
@ -1938,17 +1944,36 @@ test "SelectionGesture expired repeat resets click count" {
|
||||||
var gesture: SelectionGesture = .init;
|
var gesture: SelectionGesture = .init;
|
||||||
defer gesture.deinit(&t);
|
defer gesture.deinit(&t);
|
||||||
|
|
||||||
var event = testPress(&t, 1, 1, try std.time.Instant.now());
|
var event = testPress(&t, 1, 1, testNow());
|
||||||
event.repeat_interval = 0;
|
event.repeat_interval = 0;
|
||||||
_ = try gesture.press(&t, event);
|
_ = try gesture.press(&t, event);
|
||||||
|
|
||||||
std.Thread.sleep(std.time.ns_per_ms);
|
std.Thread.sleep(std.time.ns_per_ms);
|
||||||
event.time = try std.time.Instant.now();
|
event.time = testNow();
|
||||||
_ = try gesture.press(&t, event);
|
_ = try gesture.press(&t, event);
|
||||||
|
|
||||||
try testing.expectEqual(@as(u3, 1), gesture.left_click_count);
|
try testing.expectEqual(@as(u3, 1), gesture.left_click_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "SelectionGesture non-monotonic repeat time resets click count" {
|
||||||
|
var t = try Terminal.init(testing.allocator, .{ .cols = 5, .rows = 5 });
|
||||||
|
defer t.deinit(testing.allocator);
|
||||||
|
|
||||||
|
var gesture: SelectionGesture = .init;
|
||||||
|
defer gesture.deinit(&t);
|
||||||
|
|
||||||
|
const earlier = testNow();
|
||||||
|
std.Thread.sleep(std.time.ns_per_ms);
|
||||||
|
const later = testNow();
|
||||||
|
try testing.expect(later > earlier);
|
||||||
|
|
||||||
|
_ = try gesture.press(&t, testPress(&t, 1, 1, later));
|
||||||
|
_ = try gesture.press(&t, testPress(&t, 1, 1, earlier));
|
||||||
|
|
||||||
|
try testing.expectEqual(@as(u3, 1), gesture.left_click_count);
|
||||||
|
try testing.expectEqual(earlier, gesture.left_click_time.?);
|
||||||
|
}
|
||||||
|
|
||||||
test "SelectionGesture screen switch resets click count" {
|
test "SelectionGesture screen switch resets click count" {
|
||||||
var t = try Terminal.init(testing.allocator, .{ .cols = 5, .rows = 5 });
|
var t = try Terminal.init(testing.allocator, .{ .cols = 5, .rows = 5 });
|
||||||
defer t.deinit(testing.allocator);
|
defer t.deinit(testing.allocator);
|
||||||
|
|
@ -1956,7 +1981,7 @@ test "SelectionGesture screen switch resets click count" {
|
||||||
var gesture: SelectionGesture = .init;
|
var gesture: SelectionGesture = .init;
|
||||||
defer gesture.deinit(&t);
|
defer gesture.deinit(&t);
|
||||||
|
|
||||||
const time = try std.time.Instant.now();
|
const time = testNow();
|
||||||
const primary_tracked = t.screens.active.pages.countTrackedPins();
|
const primary_tracked = t.screens.active.pages.countTrackedPins();
|
||||||
_ = try gesture.press(&t, testPress(&t, 1, 1, time));
|
_ = try gesture.press(&t, testPress(&t, 1, 1, time));
|
||||||
|
|
||||||
|
|
@ -1984,11 +2009,11 @@ test "SelectionGesture removed screen resets without untracking stale pin" {
|
||||||
.rows = t.rows,
|
.rows = t.rows,
|
||||||
});
|
});
|
||||||
t.screens.switchTo(.alternate);
|
t.screens.switchTo(.alternate);
|
||||||
_ = try gesture.press(&t, testPress(&t, 1, 1, try std.time.Instant.now()));
|
_ = try gesture.press(&t, testPress(&t, 1, 1, testNow()));
|
||||||
|
|
||||||
t.screens.switchTo(.primary);
|
t.screens.switchTo(.primary);
|
||||||
t.screens.remove(testing.allocator, .alternate);
|
t.screens.remove(testing.allocator, .alternate);
|
||||||
_ = try gesture.press(&t, testPress(&t, 1, 1, try std.time.Instant.now()));
|
_ = try gesture.press(&t, testPress(&t, 1, 1, testNow()));
|
||||||
|
|
||||||
try testing.expectEqual(@as(u3, 1), gesture.left_click_count);
|
try testing.expectEqual(@as(u3, 1), gesture.left_click_count);
|
||||||
try testing.expectEqual(.primary, gesture.left_click_screen);
|
try testing.expectEqual(.primary, gesture.left_click_screen);
|
||||||
|
|
@ -2000,7 +2025,7 @@ test "SelectionGesture deinit untracks pin" {
|
||||||
|
|
||||||
var gesture: SelectionGesture = .init;
|
var gesture: SelectionGesture = .init;
|
||||||
const tracked = t.screens.active.pages.countTrackedPins();
|
const tracked = t.screens.active.pages.countTrackedPins();
|
||||||
_ = try gesture.press(&t, testPress(&t, 1, 1, try std.time.Instant.now()));
|
_ = try gesture.press(&t, testPress(&t, 1, 1, testNow()));
|
||||||
try testing.expectEqual(tracked + 1, t.screens.active.pages.countTrackedPins());
|
try testing.expectEqual(tracked + 1, t.screens.active.pages.countTrackedPins());
|
||||||
|
|
||||||
gesture.deinit(&t);
|
gesture.deinit(&t);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const builtin = @import("builtin");
|
|
||||||
const lib = @import("../lib.zig");
|
const lib = @import("../lib.zig");
|
||||||
const CAllocator = lib.alloc.Allocator;
|
const CAllocator = lib.alloc.Allocator;
|
||||||
const SelectionGesture = @import("../SelectionGesture.zig");
|
const SelectionGesture = @import("../SelectionGesture.zig");
|
||||||
|
|
@ -485,7 +484,7 @@ fn pressSetTyped(
|
||||||
press.ypos = v.y;
|
press.ypos = v.y;
|
||||||
},
|
},
|
||||||
.repeat_distance => press.max_distance = v.*,
|
.repeat_distance => press.max_distance = v.*,
|
||||||
.time_ns => press.time = instantFromNs(v.*),
|
.time_ns => press.time = v.*,
|
||||||
.repeat_interval_ns => press.repeat_interval = v.*,
|
.repeat_interval_ns => press.repeat_interval = v.*,
|
||||||
.word_boundary_codepoints => return trySetWordBoundaryCodepoints(
|
.word_boundary_codepoints => return trySetWordBoundaryCodepoints(
|
||||||
event,
|
event,
|
||||||
|
|
@ -738,16 +737,6 @@ fn clearWordBoundaryCodepoints(event: *EventWrapper, target: *[]const u21) void
|
||||||
target.* = &selection_codepoints.default_word_boundaries;
|
target.* = &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 {
|
fn validBehavior(behavior: Behavior) bool {
|
||||||
_ = std.meta.intToEnum(Behavior, @intFromEnum(behavior)) catch return false;
|
_ = std.meta.intToEnum(Behavior, @intFromEnum(behavior)) catch return false;
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue