mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-25 13:16:11 +03:00

This commit changes a LOT of areas of the code to use decl literals instead of redundantly referring to the type. These changes were mostly driven by some regex searches and then manual adjustment on a case-by-case basis. I almost certainly missed quite a few places where decl literals could be used, but this is a good first step in converting things, and other instances can be addressed when they're discovered. I tested GLFW+Metal and building the framework on macOS and tested a GTK build on Linux, so I'm 99% sure I didn't introduce any syntax errors or other problems with this. (fingers crossed)
1545 lines
49 KiB
Zig
1545 lines
49 KiB
Zig
//! Represents a single selection within the terminal (i.e. a highlight region).
|
|
const Selection = @This();
|
|
|
|
const std = @import("std");
|
|
const assert = std.debug.assert;
|
|
const page = @import("page.zig");
|
|
const point = @import("point.zig");
|
|
const PageList = @import("PageList.zig");
|
|
const Screen = @import("Screen.zig");
|
|
const Pin = PageList.Pin;
|
|
|
|
// NOTE(mitchellh): I'm not very happy with how this is implemented, because
|
|
// the ordering operations which are used frequently require using
|
|
// pointFromPin which -- at the time of writing this -- is slow. The overall
|
|
// style of this struct is due to porting it from the previous implementation
|
|
// which had an efficient ordering operation.
|
|
//
|
|
// While reimplementing this, there were too many callers that already
|
|
// depended on this behavior so I kept it despite the inefficiency. In the
|
|
// future, we should take a look at this again!
|
|
|
|
/// The bounds of the selection.
|
|
bounds: Bounds,
|
|
|
|
/// Whether or not this selection refers to a rectangle, rather than whole
|
|
/// lines of a buffer. In this mode, start and end refer to the top left and
|
|
/// bottom right of the rectangle, or vice versa if the selection is backwards.
|
|
rectangle: bool = false,
|
|
|
|
/// The bounds of the selection. A selection bounds can be either tracked
|
|
/// or untracked. Untracked bounds are unsafe beyond the point the terminal
|
|
/// screen may be modified, since they may point to invalid memory. Tracked
|
|
/// bounds are always valid and will be updated as the screen changes, but
|
|
/// are more expensive to exist.
|
|
///
|
|
/// In all cases, start and end can be in any order. There is no guarantee that
|
|
/// start is before end or vice versa. If a user selects backwards,
|
|
/// start will be after end, and vice versa. Use the struct functions
|
|
/// to not have to worry about this.
|
|
pub const Bounds = union(enum) {
|
|
untracked: struct {
|
|
start: Pin,
|
|
end: Pin,
|
|
},
|
|
|
|
tracked: struct {
|
|
start: *Pin,
|
|
end: *Pin,
|
|
},
|
|
};
|
|
|
|
/// Initialize a new selection with the given start and end pins on
|
|
/// the screen. The screen will be used for pin tracking.
|
|
pub fn init(
|
|
start_pin: Pin,
|
|
end_pin: Pin,
|
|
rect: bool,
|
|
) Selection {
|
|
return .{
|
|
.bounds = .{ .untracked = .{
|
|
.start = start_pin,
|
|
.end = end_pin,
|
|
} },
|
|
.rectangle = rect,
|
|
};
|
|
}
|
|
|
|
pub fn deinit(
|
|
self: Selection,
|
|
s: *Screen,
|
|
) void {
|
|
switch (self.bounds) {
|
|
.tracked => |v| {
|
|
s.pages.untrackPin(v.start);
|
|
s.pages.untrackPin(v.end);
|
|
},
|
|
|
|
.untracked => {},
|
|
}
|
|
}
|
|
|
|
/// Returns true if this selection is equal to another selection.
|
|
pub fn eql(self: Selection, other: Selection) bool {
|
|
return self.start().eql(other.start()) and
|
|
self.end().eql(other.end()) and
|
|
self.rectangle == other.rectangle;
|
|
}
|
|
|
|
/// The starting pin of the selection. This is NOT ordered.
|
|
pub fn startPtr(self: *Selection) *Pin {
|
|
return switch (self.bounds) {
|
|
.untracked => |*v| &v.start,
|
|
.tracked => |v| v.start,
|
|
};
|
|
}
|
|
|
|
/// The ending pin of the selection. This is NOT ordered.
|
|
pub fn endPtr(self: *Selection) *Pin {
|
|
return switch (self.bounds) {
|
|
.untracked => |*v| &v.end,
|
|
.tracked => |v| v.end,
|
|
};
|
|
}
|
|
|
|
pub fn start(self: Selection) Pin {
|
|
return switch (self.bounds) {
|
|
.untracked => |v| v.start,
|
|
.tracked => |v| v.start.*,
|
|
};
|
|
}
|
|
|
|
pub fn end(self: Selection) Pin {
|
|
return switch (self.bounds) {
|
|
.untracked => |v| v.end,
|
|
.tracked => |v| v.end.*,
|
|
};
|
|
}
|
|
|
|
/// Returns true if this is a tracked selection.
|
|
pub fn tracked(self: *const Selection) bool {
|
|
return switch (self.bounds) {
|
|
.untracked => false,
|
|
.tracked => true,
|
|
};
|
|
}
|
|
|
|
/// Convert this selection a tracked selection. It is asserted this is
|
|
/// an untracked selection. The tracked selection is returned.
|
|
pub fn track(self: *const Selection, s: *Screen) !Selection {
|
|
assert(!self.tracked());
|
|
|
|
// Track our pins
|
|
const start_pin = self.bounds.untracked.start;
|
|
const end_pin = self.bounds.untracked.end;
|
|
const tracked_start = try s.pages.trackPin(start_pin);
|
|
errdefer s.pages.untrackPin(tracked_start);
|
|
const tracked_end = try s.pages.trackPin(end_pin);
|
|
errdefer s.pages.untrackPin(tracked_end);
|
|
|
|
return .{
|
|
.bounds = .{ .tracked = .{
|
|
.start = tracked_start,
|
|
.end = tracked_end,
|
|
} },
|
|
.rectangle = self.rectangle,
|
|
};
|
|
}
|
|
|
|
/// Returns the top left point of the selection.
|
|
pub fn topLeft(self: Selection, s: *const Screen) Pin {
|
|
return switch (self.order(s)) {
|
|
.forward => self.start(),
|
|
.reverse => self.end(),
|
|
.mirrored_forward => pin: {
|
|
var p = self.start();
|
|
p.x = self.end().x;
|
|
break :pin p;
|
|
},
|
|
.mirrored_reverse => pin: {
|
|
var p = self.end();
|
|
p.x = self.start().x;
|
|
break :pin p;
|
|
},
|
|
};
|
|
}
|
|
|
|
/// Returns the bottom right point of the selection.
|
|
pub fn bottomRight(self: Selection, s: *const Screen) Pin {
|
|
return switch (self.order(s)) {
|
|
.forward => self.end(),
|
|
.reverse => self.start(),
|
|
.mirrored_forward => pin: {
|
|
var p = self.end();
|
|
p.x = self.start().x;
|
|
break :pin p;
|
|
},
|
|
.mirrored_reverse => pin: {
|
|
var p = self.start();
|
|
p.x = self.end().x;
|
|
break :pin p;
|
|
},
|
|
};
|
|
}
|
|
|
|
/// The order of the selection:
|
|
///
|
|
/// * forward: start(x, y) is before end(x, y) (top-left to bottom-right).
|
|
/// * reverse: end(x, y) is before start(x, y) (bottom-right to top-left).
|
|
/// * mirrored_[forward|reverse]: special, rectangle selections only (see below).
|
|
///
|
|
/// For regular selections, the above also holds for top-right to bottom-left
|
|
/// (forward) and bottom-left to top-right (reverse). However, for rectangle
|
|
/// selections, both of these selections are *mirrored* as orientation
|
|
/// operations only flip the x or y axis, not both. Depending on the y axis
|
|
/// direction, this is either mirrored_forward or mirrored_reverse.
|
|
///
|
|
pub const Order = enum { forward, reverse, mirrored_forward, mirrored_reverse };
|
|
|
|
pub fn order(self: Selection, s: *const Screen) Order {
|
|
const start_pt = s.pages.pointFromPin(.screen, self.start()).?.screen;
|
|
const end_pt = s.pages.pointFromPin(.screen, self.end()).?.screen;
|
|
|
|
if (self.rectangle) {
|
|
// Reverse (also handles single-column)
|
|
if (start_pt.y > end_pt.y and start_pt.x >= end_pt.x) return .reverse;
|
|
if (start_pt.y >= end_pt.y and start_pt.x > end_pt.x) return .reverse;
|
|
|
|
// Mirror, bottom-left to top-right
|
|
if (start_pt.y > end_pt.y and start_pt.x < end_pt.x) return .mirrored_reverse;
|
|
|
|
// Mirror, top-right to bottom-left
|
|
if (start_pt.y < end_pt.y and start_pt.x > end_pt.x) return .mirrored_forward;
|
|
|
|
// Forward
|
|
return .forward;
|
|
}
|
|
|
|
if (start_pt.y < end_pt.y) return .forward;
|
|
if (start_pt.y > end_pt.y) return .reverse;
|
|
if (start_pt.x <= end_pt.x) return .forward;
|
|
return .reverse;
|
|
}
|
|
|
|
/// Returns the selection in the given order.
|
|
///
|
|
/// The returned selection is always a new untracked selection.
|
|
///
|
|
/// Note that only forward and reverse are useful desired orders for this
|
|
/// function. All other orders act as if forward order was desired.
|
|
pub fn ordered(self: Selection, s: *const Screen, desired: Order) Selection {
|
|
if (self.order(s) == desired) return .init(
|
|
self.start(),
|
|
self.end(),
|
|
self.rectangle,
|
|
);
|
|
|
|
const tl = self.topLeft(s);
|
|
const br = self.bottomRight(s);
|
|
return switch (desired) {
|
|
.forward => .init(tl, br, self.rectangle),
|
|
.reverse => .init(br, tl, self.rectangle),
|
|
else => .init(tl, br, self.rectangle),
|
|
};
|
|
}
|
|
|
|
/// Returns true if the selection contains the given point.
|
|
///
|
|
/// This recalculates top left and bottom right each call. If you have
|
|
/// many points to check, it is cheaper to do the containment logic
|
|
/// yourself and cache the topleft/bottomright.
|
|
pub fn contains(self: Selection, s: *const Screen, pin: Pin) bool {
|
|
const tl_pin = self.topLeft(s);
|
|
const br_pin = self.bottomRight(s);
|
|
|
|
// This is definitely not very efficient. Low-hanging fruit to
|
|
// improve this.
|
|
const tl = s.pages.pointFromPin(.screen, tl_pin).?.screen;
|
|
const br = s.pages.pointFromPin(.screen, br_pin).?.screen;
|
|
const p = s.pages.pointFromPin(.screen, pin).?.screen;
|
|
|
|
// If we're in rectangle select, we can short-circuit with an easy check
|
|
// here
|
|
if (self.rectangle)
|
|
return p.y >= tl.y and p.y <= br.y and p.x >= tl.x and p.x <= br.x;
|
|
|
|
// If tl/br are same line
|
|
if (tl.y == br.y) return p.y == tl.y and
|
|
p.x >= tl.x and
|
|
p.x <= br.x;
|
|
|
|
// If on top line, just has to be left of X
|
|
if (p.y == tl.y) return p.x >= tl.x;
|
|
|
|
// If on bottom line, just has to be right of X
|
|
if (p.y == br.y) return p.x <= br.x;
|
|
|
|
// If between the top/bottom, always good.
|
|
return p.y > tl.y and p.y < br.y;
|
|
}
|
|
|
|
/// Get a selection for a single row in the screen. This will return null
|
|
/// if the row is not included in the selection.
|
|
pub fn containedRow(self: Selection, s: *const Screen, pin: Pin) ?Selection {
|
|
const tl_pin = self.topLeft(s);
|
|
const br_pin = self.bottomRight(s);
|
|
|
|
// This is definitely not very efficient. Low-hanging fruit to
|
|
// improve this.
|
|
const tl = s.pages.pointFromPin(.screen, tl_pin).?.screen;
|
|
const br = s.pages.pointFromPin(.screen, br_pin).?.screen;
|
|
const p = s.pages.pointFromPin(.screen, pin).?.screen;
|
|
|
|
if (p.y < tl.y or p.y > br.y) return null;
|
|
|
|
// Rectangle case: we can return early as the x range will always be the
|
|
// same. We've already validated that the row is in the selection.
|
|
if (self.rectangle) return init(
|
|
s.pages.pin(.{ .screen = .{ .y = p.y, .x = tl.x } }).?,
|
|
s.pages.pin(.{ .screen = .{ .y = p.y, .x = br.x } }).?,
|
|
true,
|
|
);
|
|
|
|
if (p.y == tl.y) {
|
|
// If the selection is JUST this line, return it as-is.
|
|
if (p.y == br.y) {
|
|
return init(tl_pin, br_pin, false);
|
|
}
|
|
|
|
// Selection top-left line matches only.
|
|
return init(
|
|
tl_pin,
|
|
s.pages.pin(.{ .screen = .{ .y = p.y, .x = s.pages.cols - 1 } }).?,
|
|
false,
|
|
);
|
|
}
|
|
|
|
// Row is our bottom selection, so we return the selection from the
|
|
// beginning of the line to the br. We know our selection is more than
|
|
// one line (due to conditionals above)
|
|
if (p.y == br.y) {
|
|
assert(p.y != tl.y);
|
|
return init(
|
|
s.pages.pin(.{ .screen = .{ .y = p.y, .x = 0 } }).?,
|
|
br_pin,
|
|
false,
|
|
);
|
|
}
|
|
|
|
// Row is somewhere between our selection lines so we return the full line.
|
|
return init(
|
|
s.pages.pin(.{ .screen = .{ .y = p.y, .x = 0 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .y = p.y, .x = s.pages.cols - 1 } }).?,
|
|
false,
|
|
);
|
|
}
|
|
|
|
/// Possible adjustments to the selection.
|
|
pub const Adjustment = enum {
|
|
left,
|
|
right,
|
|
up,
|
|
down,
|
|
home,
|
|
end,
|
|
page_up,
|
|
page_down,
|
|
beginning_of_line,
|
|
end_of_line,
|
|
};
|
|
|
|
/// Adjust the selection by some given adjustment. An adjustment allows
|
|
/// a selection to be expanded slightly left, right, up, down, etc.
|
|
pub fn adjust(
|
|
self: *Selection,
|
|
s: *const Screen,
|
|
adjustment: Adjustment,
|
|
) void {
|
|
// Note that we always adjust "end" because end always represents
|
|
// the last point of the selection by mouse, not necessarily the
|
|
// top/bottom visually. So this results in the correct behavior
|
|
// whether the user drags up or down.
|
|
const end_pin = self.endPtr();
|
|
switch (adjustment) {
|
|
.up => if (end_pin.up(1)) |new_end| {
|
|
end_pin.* = new_end;
|
|
} else {
|
|
self.adjust(s, .beginning_of_line);
|
|
},
|
|
|
|
.down => {
|
|
// Find the next non-blank row
|
|
var current = end_pin.*;
|
|
while (current.down(1)) |next| : (current = next) {
|
|
const rac = next.rowAndCell();
|
|
const cells = next.node.data.getCells(rac.row);
|
|
if (page.Cell.hasTextAny(cells)) {
|
|
end_pin.* = next;
|
|
break;
|
|
}
|
|
} else {
|
|
// If we're at the bottom, just go to the end of the line
|
|
self.adjust(s, .end_of_line);
|
|
}
|
|
},
|
|
|
|
.left => {
|
|
var it = end_pin.cellIterator(.left_up, null);
|
|
_ = it.next();
|
|
while (it.next()) |next| {
|
|
const rac = next.rowAndCell();
|
|
if (rac.cell.hasText()) {
|
|
end_pin.* = next;
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
|
|
.right => {
|
|
// Step right, wrapping to the next row down at the start of each new line,
|
|
// until we find a non-empty cell.
|
|
var it = end_pin.cellIterator(.right_down, null);
|
|
_ = it.next();
|
|
while (it.next()) |next| {
|
|
const rac = next.rowAndCell();
|
|
if (rac.cell.hasText()) {
|
|
end_pin.* = next;
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
|
|
.page_up => if (end_pin.up(s.pages.rows)) |new_end| {
|
|
end_pin.* = new_end;
|
|
} else {
|
|
self.adjust(s, .home);
|
|
},
|
|
|
|
.page_down => if (end_pin.down(s.pages.rows)) |new_end| {
|
|
end_pin.* = new_end;
|
|
} else {
|
|
self.adjust(s, .end);
|
|
},
|
|
|
|
.home => end_pin.* = s.pages.pin(.{ .screen = .{
|
|
.x = 0,
|
|
.y = 0,
|
|
} }).?,
|
|
|
|
.end => {
|
|
var it = s.pages.rowIterator(
|
|
.left_up,
|
|
.{ .screen = .{} },
|
|
null,
|
|
);
|
|
while (it.next()) |next| {
|
|
const rac = next.rowAndCell();
|
|
const cells = next.node.data.getCells(rac.row);
|
|
if (page.Cell.hasTextAny(cells)) {
|
|
end_pin.* = next;
|
|
end_pin.x = @intCast(cells.len - 1);
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
|
|
.beginning_of_line => end_pin.x = 0,
|
|
|
|
.end_of_line => end_pin.x = end_pin.node.data.size.cols - 1,
|
|
}
|
|
}
|
|
|
|
test "Selection: adjust right" {
|
|
const testing = std.testing;
|
|
var s = try Screen.init(testing.allocator, 10, 10, 0);
|
|
defer s.deinit();
|
|
try s.testWriteString("A1234\nB5678\nC1234\nD5678");
|
|
|
|
// Simple movement right
|
|
{
|
|
var sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 5, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 3, .y = 3 } }).?,
|
|
false,
|
|
);
|
|
defer sel.deinit(&s);
|
|
sel.adjust(&s, .right);
|
|
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 5,
|
|
.y = 1,
|
|
} }, s.pages.pointFromPin(.screen, sel.start()).?);
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 4,
|
|
.y = 3,
|
|
} }, s.pages.pointFromPin(.screen, sel.end()).?);
|
|
}
|
|
|
|
// Already at end of the line.
|
|
{
|
|
var sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 4, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 4, .y = 2 } }).?,
|
|
false,
|
|
);
|
|
defer sel.deinit(&s);
|
|
sel.adjust(&s, .right);
|
|
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 4,
|
|
.y = 1,
|
|
} }, s.pages.pointFromPin(.screen, sel.start()).?);
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 0,
|
|
.y = 3,
|
|
} }, s.pages.pointFromPin(.screen, sel.end()).?);
|
|
}
|
|
|
|
// Already at end of the screen
|
|
{
|
|
var sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 5, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 4, .y = 3 } }).?,
|
|
false,
|
|
);
|
|
defer sel.deinit(&s);
|
|
sel.adjust(&s, .right);
|
|
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 5,
|
|
.y = 1,
|
|
} }, s.pages.pointFromPin(.screen, sel.start()).?);
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 4,
|
|
.y = 3,
|
|
} }, s.pages.pointFromPin(.screen, sel.end()).?);
|
|
}
|
|
}
|
|
|
|
test "Selection: adjust left" {
|
|
const testing = std.testing;
|
|
var s = try Screen.init(testing.allocator, 10, 10, 0);
|
|
defer s.deinit();
|
|
try s.testWriteString("A1234\nB5678\nC1234\nD5678");
|
|
|
|
// Simple movement left
|
|
{
|
|
var sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 5, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 3, .y = 3 } }).?,
|
|
false,
|
|
);
|
|
defer sel.deinit(&s);
|
|
sel.adjust(&s, .left);
|
|
|
|
// Start line
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 5,
|
|
.y = 1,
|
|
} }, s.pages.pointFromPin(.screen, sel.start()).?);
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 2,
|
|
.y = 3,
|
|
} }, s.pages.pointFromPin(.screen, sel.end()).?);
|
|
}
|
|
|
|
// Already at beginning of the line.
|
|
{
|
|
var sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 5, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 0, .y = 3 } }).?,
|
|
false,
|
|
);
|
|
defer sel.deinit(&s);
|
|
sel.adjust(&s, .left);
|
|
|
|
// Start line
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 5,
|
|
.y = 1,
|
|
} }, s.pages.pointFromPin(.screen, sel.start()).?);
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 4,
|
|
.y = 2,
|
|
} }, s.pages.pointFromPin(.screen, sel.end()).?);
|
|
}
|
|
}
|
|
|
|
test "Selection: adjust left skips blanks" {
|
|
const testing = std.testing;
|
|
var s = try Screen.init(testing.allocator, 10, 10, 0);
|
|
defer s.deinit();
|
|
try s.testWriteString("A1234\nB5678\nC12\nD56");
|
|
|
|
// Same line
|
|
{
|
|
var sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 5, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 4, .y = 3 } }).?,
|
|
false,
|
|
);
|
|
defer sel.deinit(&s);
|
|
sel.adjust(&s, .left);
|
|
|
|
// Start line
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 5,
|
|
.y = 1,
|
|
} }, s.pages.pointFromPin(.screen, sel.start()).?);
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 2,
|
|
.y = 3,
|
|
} }, s.pages.pointFromPin(.screen, sel.end()).?);
|
|
}
|
|
|
|
// Edge
|
|
{
|
|
var sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 5, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 0, .y = 3 } }).?,
|
|
false,
|
|
);
|
|
defer sel.deinit(&s);
|
|
sel.adjust(&s, .left);
|
|
|
|
// Start line
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 5,
|
|
.y = 1,
|
|
} }, s.pages.pointFromPin(.screen, sel.start()).?);
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 2,
|
|
.y = 2,
|
|
} }, s.pages.pointFromPin(.screen, sel.end()).?);
|
|
}
|
|
}
|
|
|
|
test "Selection: adjust up" {
|
|
const testing = std.testing;
|
|
var s = try Screen.init(testing.allocator, 10, 10, 0);
|
|
defer s.deinit();
|
|
try s.testWriteString("A\nB\nC\nD\nE");
|
|
|
|
// Not on the first line
|
|
{
|
|
var sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 5, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 3, .y = 3 } }).?,
|
|
false,
|
|
);
|
|
defer sel.deinit(&s);
|
|
sel.adjust(&s, .up);
|
|
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 5,
|
|
.y = 1,
|
|
} }, s.pages.pointFromPin(.screen, sel.start()).?);
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 3,
|
|
.y = 2,
|
|
} }, s.pages.pointFromPin(.screen, sel.end()).?);
|
|
}
|
|
|
|
// On the first line
|
|
{
|
|
var sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 5, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 3, .y = 0 } }).?,
|
|
false,
|
|
);
|
|
defer sel.deinit(&s);
|
|
sel.adjust(&s, .up);
|
|
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 5,
|
|
.y = 1,
|
|
} }, s.pages.pointFromPin(.screen, sel.start()).?);
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 0,
|
|
.y = 0,
|
|
} }, s.pages.pointFromPin(.screen, sel.end()).?);
|
|
}
|
|
}
|
|
|
|
test "Selection: adjust down" {
|
|
const testing = std.testing;
|
|
var s = try Screen.init(testing.allocator, 10, 10, 0);
|
|
defer s.deinit();
|
|
try s.testWriteString("A\nB\nC\nD\nE");
|
|
|
|
// Not on the first line
|
|
{
|
|
var sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 5, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 3, .y = 3 } }).?,
|
|
false,
|
|
);
|
|
defer sel.deinit(&s);
|
|
sel.adjust(&s, .down);
|
|
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 5,
|
|
.y = 1,
|
|
} }, s.pages.pointFromPin(.screen, sel.start()).?);
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 3,
|
|
.y = 4,
|
|
} }, s.pages.pointFromPin(.screen, sel.end()).?);
|
|
}
|
|
|
|
// On the last line
|
|
{
|
|
var sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 4, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 3, .y = 4 } }).?,
|
|
false,
|
|
);
|
|
defer sel.deinit(&s);
|
|
sel.adjust(&s, .down);
|
|
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 4,
|
|
.y = 1,
|
|
} }, s.pages.pointFromPin(.screen, sel.start()).?);
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 9,
|
|
.y = 4,
|
|
} }, s.pages.pointFromPin(.screen, sel.end()).?);
|
|
}
|
|
}
|
|
|
|
test "Selection: adjust down with not full screen" {
|
|
const testing = std.testing;
|
|
var s = try Screen.init(testing.allocator, 10, 10, 0);
|
|
defer s.deinit();
|
|
try s.testWriteString("A\nB\nC");
|
|
|
|
// On the last line
|
|
{
|
|
var sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 4, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 3, .y = 2 } }).?,
|
|
false,
|
|
);
|
|
defer sel.deinit(&s);
|
|
sel.adjust(&s, .down);
|
|
|
|
// Start line
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 4,
|
|
.y = 1,
|
|
} }, s.pages.pointFromPin(.screen, sel.start()).?);
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 9,
|
|
.y = 2,
|
|
} }, s.pages.pointFromPin(.screen, sel.end()).?);
|
|
}
|
|
}
|
|
|
|
test "Selection: adjust home" {
|
|
const testing = std.testing;
|
|
var s = try Screen.init(testing.allocator, 10, 10, 0);
|
|
defer s.deinit();
|
|
try s.testWriteString("A\nB\nC");
|
|
|
|
// On the last line
|
|
{
|
|
var sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 4, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 2 } }).?,
|
|
false,
|
|
);
|
|
defer sel.deinit(&s);
|
|
sel.adjust(&s, .home);
|
|
|
|
// Start line
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 4,
|
|
.y = 1,
|
|
} }, s.pages.pointFromPin(.screen, sel.start()).?);
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 0,
|
|
.y = 0,
|
|
} }, s.pages.pointFromPin(.screen, sel.end()).?);
|
|
}
|
|
}
|
|
|
|
test "Selection: adjust end with not full screen" {
|
|
const testing = std.testing;
|
|
var s = try Screen.init(testing.allocator, 10, 10, 0);
|
|
defer s.deinit();
|
|
try s.testWriteString("A\nB\nC");
|
|
|
|
// On the last line
|
|
{
|
|
var sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 4, .y = 0 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 1 } }).?,
|
|
false,
|
|
);
|
|
defer sel.deinit(&s);
|
|
sel.adjust(&s, .end);
|
|
|
|
// Start line
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 4,
|
|
.y = 0,
|
|
} }, s.pages.pointFromPin(.screen, sel.start()).?);
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 9,
|
|
.y = 2,
|
|
} }, s.pages.pointFromPin(.screen, sel.end()).?);
|
|
}
|
|
}
|
|
|
|
test "Selection: adjust beginning of line" {
|
|
const testing = std.testing;
|
|
var s = try Screen.init(testing.allocator, 8, 10, 0);
|
|
defer s.deinit();
|
|
try s.testWriteString("A12 B34\nC12 D34");
|
|
|
|
// Not at beginning of the line
|
|
{
|
|
var sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 5, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 5, .y = 1 } }).?,
|
|
false,
|
|
);
|
|
defer sel.deinit(&s);
|
|
sel.adjust(&s, .beginning_of_line);
|
|
|
|
// Start line
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 5,
|
|
.y = 1,
|
|
} }, s.pages.pointFromPin(.screen, sel.start()).?);
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 0,
|
|
.y = 1,
|
|
} }, s.pages.pointFromPin(.screen, sel.end()).?);
|
|
}
|
|
|
|
// Already at beginning of the line
|
|
{
|
|
var sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 5, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 0, .y = 1 } }).?,
|
|
false,
|
|
);
|
|
defer sel.deinit(&s);
|
|
sel.adjust(&s, .beginning_of_line);
|
|
|
|
// Start line
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 5,
|
|
.y = 1,
|
|
} }, s.pages.pointFromPin(.screen, sel.start()).?);
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 0,
|
|
.y = 1,
|
|
} }, s.pages.pointFromPin(.screen, sel.end()).?);
|
|
}
|
|
|
|
// End pin moves to start pin
|
|
{
|
|
var sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 0, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 5, .y = 1 } }).?,
|
|
false,
|
|
);
|
|
defer sel.deinit(&s);
|
|
sel.adjust(&s, .beginning_of_line);
|
|
|
|
// Start line
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 0,
|
|
.y = 1,
|
|
} }, s.pages.pointFromPin(.screen, sel.start()).?);
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 0,
|
|
.y = 1,
|
|
} }, s.pages.pointFromPin(.screen, sel.end()).?);
|
|
}
|
|
}
|
|
|
|
test "Selection: adjust end of line" {
|
|
const testing = std.testing;
|
|
var s = try Screen.init(testing.allocator, 8, 10, 0);
|
|
defer s.deinit();
|
|
try s.testWriteString("A12 B34\nC12 D34");
|
|
|
|
// Not at end of the line
|
|
{
|
|
var sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 0 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 0 } }).?,
|
|
false,
|
|
);
|
|
defer sel.deinit(&s);
|
|
sel.adjust(&s, .end_of_line);
|
|
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 1,
|
|
.y = 0,
|
|
} }, s.pages.pointFromPin(.screen, sel.start()).?);
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 7,
|
|
.y = 0,
|
|
} }, s.pages.pointFromPin(.screen, sel.end()).?);
|
|
}
|
|
|
|
// Already at end of the line
|
|
{
|
|
var sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 0 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 7, .y = 0 } }).?,
|
|
false,
|
|
);
|
|
defer sel.deinit(&s);
|
|
sel.adjust(&s, .end_of_line);
|
|
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 1,
|
|
.y = 0,
|
|
} }, s.pages.pointFromPin(.screen, sel.start()).?);
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 7,
|
|
.y = 0,
|
|
} }, s.pages.pointFromPin(.screen, sel.end()).?);
|
|
}
|
|
|
|
// End pin moves to start pin
|
|
{
|
|
var sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 7, .y = 0 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 0 } }).?,
|
|
false,
|
|
);
|
|
defer sel.deinit(&s);
|
|
sel.adjust(&s, .end_of_line);
|
|
|
|
// Start line
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 7,
|
|
.y = 0,
|
|
} }, s.pages.pointFromPin(.screen, sel.start()).?);
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 7,
|
|
.y = 0,
|
|
} }, s.pages.pointFromPin(.screen, sel.end()).?);
|
|
}
|
|
}
|
|
|
|
test "Selection: order, standard" {
|
|
const testing = std.testing;
|
|
const alloc = testing.allocator;
|
|
|
|
var s = try Screen.init(alloc, 100, 100, 1);
|
|
defer s.deinit();
|
|
|
|
{
|
|
// forward, multi-line
|
|
const sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 2, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 2, .y = 2 } }).?,
|
|
false,
|
|
);
|
|
defer sel.deinit(&s);
|
|
|
|
try testing.expect(sel.order(&s) == .forward);
|
|
}
|
|
{
|
|
// reverse, multi-line
|
|
const sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 2, .y = 2 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 2, .y = 1 } }).?,
|
|
false,
|
|
);
|
|
defer sel.deinit(&s);
|
|
|
|
try testing.expect(sel.order(&s) == .reverse);
|
|
}
|
|
{
|
|
// forward, same-line
|
|
const sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 2, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 3, .y = 1 } }).?,
|
|
false,
|
|
);
|
|
defer sel.deinit(&s);
|
|
|
|
try testing.expect(sel.order(&s) == .forward);
|
|
}
|
|
{
|
|
// forward, single char
|
|
const sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 2, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 2, .y = 1 } }).?,
|
|
false,
|
|
);
|
|
defer sel.deinit(&s);
|
|
|
|
try testing.expect(sel.order(&s) == .forward);
|
|
}
|
|
{
|
|
// reverse, single line
|
|
const sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 2, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 1 } }).?,
|
|
false,
|
|
);
|
|
defer sel.deinit(&s);
|
|
|
|
try testing.expect(sel.order(&s) == .reverse);
|
|
}
|
|
}
|
|
|
|
test "Selection: order, rectangle" {
|
|
const testing = std.testing;
|
|
const alloc = testing.allocator;
|
|
|
|
var s = try Screen.init(alloc, 100, 100, 1);
|
|
defer s.deinit();
|
|
|
|
// Conventions:
|
|
// TL - top left
|
|
// BL - bottom left
|
|
// TR - top right
|
|
// BR - bottom right
|
|
{
|
|
// forward (TL -> BR)
|
|
const sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 2, .y = 2 } }).?,
|
|
true,
|
|
);
|
|
defer sel.deinit(&s);
|
|
|
|
try testing.expect(sel.order(&s) == .forward);
|
|
}
|
|
{
|
|
// reverse (BR -> TL)
|
|
const sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 2, .y = 2 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 1 } }).?,
|
|
true,
|
|
);
|
|
defer sel.deinit(&s);
|
|
|
|
try testing.expect(sel.order(&s) == .reverse);
|
|
}
|
|
{
|
|
// mirrored_forward (TR -> BL)
|
|
const sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 3, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 3 } }).?,
|
|
true,
|
|
);
|
|
defer sel.deinit(&s);
|
|
|
|
try testing.expect(sel.order(&s) == .mirrored_forward);
|
|
}
|
|
{
|
|
// mirrored_reverse (BL -> TR)
|
|
const sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 3 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 3, .y = 1 } }).?,
|
|
true,
|
|
);
|
|
defer sel.deinit(&s);
|
|
|
|
try testing.expect(sel.order(&s) == .mirrored_reverse);
|
|
}
|
|
{
|
|
// forward, single line (left -> right )
|
|
const sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 3, .y = 1 } }).?,
|
|
true,
|
|
);
|
|
defer sel.deinit(&s);
|
|
|
|
try testing.expect(sel.order(&s) == .forward);
|
|
}
|
|
{
|
|
// reverse, single line (right -> left)
|
|
const sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 3, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 1 } }).?,
|
|
true,
|
|
);
|
|
defer sel.deinit(&s);
|
|
|
|
try testing.expect(sel.order(&s) == .reverse);
|
|
}
|
|
{
|
|
// forward, single column (top -> bottom)
|
|
const sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 2, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 2, .y = 3 } }).?,
|
|
true,
|
|
);
|
|
defer sel.deinit(&s);
|
|
|
|
try testing.expect(sel.order(&s) == .forward);
|
|
}
|
|
{
|
|
// reverse, single column (bottom -> top)
|
|
const sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 2, .y = 3 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 2, .y = 1 } }).?,
|
|
true,
|
|
);
|
|
defer sel.deinit(&s);
|
|
|
|
try testing.expect(sel.order(&s) == .reverse);
|
|
}
|
|
{
|
|
// forward, single cell
|
|
const sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 1 } }).?,
|
|
true,
|
|
);
|
|
defer sel.deinit(&s);
|
|
|
|
try testing.expect(sel.order(&s) == .forward);
|
|
}
|
|
}
|
|
|
|
test "topLeft" {
|
|
const testing = std.testing;
|
|
|
|
var s = try Screen.init(testing.allocator, 10, 10, 0);
|
|
defer s.deinit();
|
|
{
|
|
// forward
|
|
const sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 3, .y = 1 } }).?,
|
|
true,
|
|
);
|
|
defer sel.deinit(&s);
|
|
const tl = sel.topLeft(&s);
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 1,
|
|
.y = 1,
|
|
} }, s.pages.pointFromPin(.screen, tl));
|
|
}
|
|
{
|
|
// reverse
|
|
const sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 3, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 1 } }).?,
|
|
true,
|
|
);
|
|
defer sel.deinit(&s);
|
|
const tl = sel.topLeft(&s);
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 1,
|
|
.y = 1,
|
|
} }, s.pages.pointFromPin(.screen, tl));
|
|
}
|
|
{
|
|
// mirrored_forward
|
|
const sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 3, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 3 } }).?,
|
|
true,
|
|
);
|
|
defer sel.deinit(&s);
|
|
const tl = sel.topLeft(&s);
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 1,
|
|
.y = 1,
|
|
} }, s.pages.pointFromPin(.screen, tl));
|
|
}
|
|
{
|
|
// mirrored_reverse
|
|
const sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 3 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 3, .y = 1 } }).?,
|
|
true,
|
|
);
|
|
defer sel.deinit(&s);
|
|
const tl = sel.topLeft(&s);
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 1,
|
|
.y = 1,
|
|
} }, s.pages.pointFromPin(.screen, tl));
|
|
}
|
|
}
|
|
|
|
test "bottomRight" {
|
|
const testing = std.testing;
|
|
|
|
var s = try Screen.init(testing.allocator, 10, 10, 0);
|
|
defer s.deinit();
|
|
{
|
|
// forward
|
|
const sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 3, .y = 1 } }).?,
|
|
false,
|
|
);
|
|
defer sel.deinit(&s);
|
|
const br = sel.bottomRight(&s);
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 3,
|
|
.y = 1,
|
|
} }, s.pages.pointFromPin(.screen, br));
|
|
}
|
|
{
|
|
// reverse
|
|
const sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 3, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 1 } }).?,
|
|
false,
|
|
);
|
|
defer sel.deinit(&s);
|
|
const br = sel.bottomRight(&s);
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 3,
|
|
.y = 1,
|
|
} }, s.pages.pointFromPin(.screen, br));
|
|
}
|
|
{
|
|
// mirrored_forward
|
|
const sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 3, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 3 } }).?,
|
|
true,
|
|
);
|
|
defer sel.deinit(&s);
|
|
const br = sel.bottomRight(&s);
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 3,
|
|
.y = 3,
|
|
} }, s.pages.pointFromPin(.screen, br));
|
|
}
|
|
{
|
|
// mirrored_reverse
|
|
const sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 3 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 3, .y = 1 } }).?,
|
|
true,
|
|
);
|
|
defer sel.deinit(&s);
|
|
const br = sel.bottomRight(&s);
|
|
try testing.expectEqual(point.Point{ .screen = .{
|
|
.x = 3,
|
|
.y = 3,
|
|
} }, s.pages.pointFromPin(.screen, br));
|
|
}
|
|
}
|
|
|
|
test "ordered" {
|
|
const testing = std.testing;
|
|
|
|
var s = try Screen.init(testing.allocator, 10, 10, 0);
|
|
defer s.deinit();
|
|
{
|
|
// forward
|
|
const sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 3, .y = 1 } }).?,
|
|
false,
|
|
);
|
|
const sel_reverse = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 3, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 1 } }).?,
|
|
false,
|
|
);
|
|
try testing.expect(sel.ordered(&s, .forward).eql(sel));
|
|
try testing.expect(sel.ordered(&s, .reverse).eql(sel_reverse));
|
|
try testing.expect(sel.ordered(&s, .mirrored_forward).eql(sel));
|
|
}
|
|
{
|
|
// reverse
|
|
const sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 3, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 1 } }).?,
|
|
false,
|
|
);
|
|
const sel_forward = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 3, .y = 1 } }).?,
|
|
false,
|
|
);
|
|
try testing.expect(sel.ordered(&s, .forward).eql(sel_forward));
|
|
try testing.expect(sel.ordered(&s, .reverse).eql(sel));
|
|
try testing.expect(sel.ordered(&s, .mirrored_forward).eql(sel_forward));
|
|
}
|
|
{
|
|
// mirrored_forward
|
|
const sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 3, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 3 } }).?,
|
|
true,
|
|
);
|
|
const sel_forward = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 3, .y = 3 } }).?,
|
|
true,
|
|
);
|
|
const sel_reverse = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 3, .y = 3 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 1 } }).?,
|
|
true,
|
|
);
|
|
try testing.expect(sel.ordered(&s, .forward).eql(sel_forward));
|
|
try testing.expect(sel.ordered(&s, .reverse).eql(sel_reverse));
|
|
try testing.expect(sel.ordered(&s, .mirrored_reverse).eql(sel_forward));
|
|
}
|
|
{
|
|
// mirrored_reverse
|
|
const sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 3 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 3, .y = 1 } }).?,
|
|
true,
|
|
);
|
|
const sel_forward = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 3, .y = 3 } }).?,
|
|
true,
|
|
);
|
|
const sel_reverse = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 3, .y = 3 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 1 } }).?,
|
|
true,
|
|
);
|
|
try testing.expect(sel.ordered(&s, .forward).eql(sel_forward));
|
|
try testing.expect(sel.ordered(&s, .reverse).eql(sel_reverse));
|
|
try testing.expect(sel.ordered(&s, .mirrored_forward).eql(sel_forward));
|
|
}
|
|
}
|
|
|
|
test "Selection: contains" {
|
|
const testing = std.testing;
|
|
|
|
var s = try Screen.init(testing.allocator, 10, 10, 0);
|
|
defer s.deinit();
|
|
{
|
|
const sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 5, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 3, .y = 2 } }).?,
|
|
false,
|
|
);
|
|
|
|
try testing.expect(sel.contains(&s, s.pages.pin(.{ .screen = .{ .x = 6, .y = 1 } }).?));
|
|
try testing.expect(sel.contains(&s, s.pages.pin(.{ .screen = .{ .x = 1, .y = 2 } }).?));
|
|
try testing.expect(!sel.contains(&s, s.pages.pin(.{ .screen = .{ .x = 1, .y = 1 } }).?));
|
|
try testing.expect(!sel.contains(&s, s.pages.pin(.{ .screen = .{ .x = 5, .y = 2 } }).?));
|
|
}
|
|
|
|
// Reverse
|
|
{
|
|
const sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 3, .y = 2 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 5, .y = 1 } }).?,
|
|
false,
|
|
);
|
|
|
|
try testing.expect(sel.contains(&s, s.pages.pin(.{ .screen = .{ .x = 6, .y = 1 } }).?));
|
|
try testing.expect(sel.contains(&s, s.pages.pin(.{ .screen = .{ .x = 1, .y = 2 } }).?));
|
|
try testing.expect(!sel.contains(&s, s.pages.pin(.{ .screen = .{ .x = 1, .y = 1 } }).?));
|
|
try testing.expect(!sel.contains(&s, s.pages.pin(.{ .screen = .{ .x = 5, .y = 2 } }).?));
|
|
}
|
|
|
|
// Single line
|
|
{
|
|
const sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 5, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 8, .y = 1 } }).?,
|
|
false,
|
|
);
|
|
|
|
try testing.expect(sel.contains(&s, s.pages.pin(.{ .screen = .{ .x = 6, .y = 1 } }).?));
|
|
try testing.expect(!sel.contains(&s, s.pages.pin(.{ .screen = .{ .x = 2, .y = 1 } }).?));
|
|
try testing.expect(!sel.contains(&s, s.pages.pin(.{ .screen = .{ .x = 9, .y = 1 } }).?));
|
|
}
|
|
}
|
|
|
|
test "Selection: contains, rectangle" {
|
|
const testing = std.testing;
|
|
|
|
var s = try Screen.init(testing.allocator, 15, 15, 0);
|
|
defer s.deinit();
|
|
{
|
|
const sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 3, .y = 3 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 7, .y = 9 } }).?,
|
|
true,
|
|
);
|
|
|
|
try testing.expect(sel.contains(&s, s.pages.pin(.{ .screen = .{ .x = 5, .y = 6 } }).?)); // Center
|
|
try testing.expect(sel.contains(&s, s.pages.pin(.{ .screen = .{ .x = 3, .y = 6 } }).?)); // Left border
|
|
try testing.expect(sel.contains(&s, s.pages.pin(.{ .screen = .{ .x = 7, .y = 6 } }).?)); // Right border
|
|
try testing.expect(sel.contains(&s, s.pages.pin(.{ .screen = .{ .x = 5, .y = 3 } }).?)); // Top border
|
|
try testing.expect(sel.contains(&s, s.pages.pin(.{ .screen = .{ .x = 5, .y = 9 } }).?)); // Bottom border
|
|
|
|
try testing.expect(!sel.contains(&s, s.pages.pin(.{ .screen = .{ .x = 5, .y = 2 } }).?)); // Above center
|
|
try testing.expect(!sel.contains(&s, s.pages.pin(.{ .screen = .{ .x = 5, .y = 10 } }).?)); // Below center
|
|
try testing.expect(!sel.contains(&s, s.pages.pin(.{ .screen = .{ .x = 2, .y = 6 } }).?)); // Left center
|
|
try testing.expect(!sel.contains(&s, s.pages.pin(.{ .screen = .{ .x = 8, .y = 6 } }).?)); // Right center
|
|
try testing.expect(!sel.contains(&s, s.pages.pin(.{ .screen = .{ .x = 8, .y = 3 } }).?)); // Just right of top right
|
|
try testing.expect(!sel.contains(&s, s.pages.pin(.{ .screen = .{ .x = 2, .y = 9 } }).?)); // Just left of bottom left
|
|
}
|
|
|
|
// Reverse
|
|
{
|
|
const sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 7, .y = 9 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 3, .y = 3 } }).?,
|
|
true,
|
|
);
|
|
|
|
try testing.expect(sel.contains(&s, s.pages.pin(.{ .screen = .{ .x = 5, .y = 6 } }).?)); // Center
|
|
try testing.expect(sel.contains(&s, s.pages.pin(.{ .screen = .{ .x = 3, .y = 6 } }).?)); // Left border
|
|
try testing.expect(sel.contains(&s, s.pages.pin(.{ .screen = .{ .x = 7, .y = 6 } }).?)); // Right border
|
|
try testing.expect(sel.contains(&s, s.pages.pin(.{ .screen = .{ .x = 5, .y = 3 } }).?)); // Top border
|
|
try testing.expect(sel.contains(&s, s.pages.pin(.{ .screen = .{ .x = 5, .y = 9 } }).?)); // Bottom border
|
|
|
|
try testing.expect(!sel.contains(&s, s.pages.pin(.{ .screen = .{ .x = 5, .y = 2 } }).?)); // Above center
|
|
try testing.expect(!sel.contains(&s, s.pages.pin(.{ .screen = .{ .x = 5, .y = 10 } }).?)); // Below center
|
|
try testing.expect(!sel.contains(&s, s.pages.pin(.{ .screen = .{ .x = 2, .y = 6 } }).?)); // Left center
|
|
try testing.expect(!sel.contains(&s, s.pages.pin(.{ .screen = .{ .x = 8, .y = 6 } }).?)); // Right center
|
|
try testing.expect(!sel.contains(&s, s.pages.pin(.{ .screen = .{ .x = 8, .y = 3 } }).?)); // Just right of top right
|
|
try testing.expect(!sel.contains(&s, s.pages.pin(.{ .screen = .{ .x = 2, .y = 9 } }).?)); // Just left of bottom left
|
|
}
|
|
|
|
// Single line
|
|
// NOTE: This is the same as normal selection but we just do it for brevity
|
|
{
|
|
const sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 5, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 10, .y = 1 } }).?,
|
|
true,
|
|
);
|
|
|
|
try testing.expect(sel.contains(&s, s.pages.pin(.{ .screen = .{ .x = 6, .y = 1 } }).?));
|
|
try testing.expect(!sel.contains(&s, s.pages.pin(.{ .screen = .{ .x = 2, .y = 1 } }).?));
|
|
try testing.expect(!sel.contains(&s, s.pages.pin(.{ .screen = .{ .x = 12, .y = 1 } }).?));
|
|
}
|
|
}
|
|
|
|
test "Selection: containedRow" {
|
|
const testing = std.testing;
|
|
var s = try Screen.init(testing.allocator, 10, 5, 0);
|
|
defer s.deinit();
|
|
|
|
{
|
|
const sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 5, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 3, .y = 3 } }).?,
|
|
false,
|
|
);
|
|
|
|
// Not contained
|
|
try testing.expect(sel.containedRow(
|
|
&s,
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 4 } }).?,
|
|
) == null);
|
|
|
|
// Start line
|
|
try testing.expectEqual(Selection.init(
|
|
sel.start(),
|
|
s.pages.pin(.{ .screen = .{ .x = s.pages.cols - 1, .y = 1 } }).?,
|
|
false,
|
|
), sel.containedRow(
|
|
&s,
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 1 } }).?,
|
|
).?);
|
|
|
|
// End line
|
|
try testing.expectEqual(Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 0, .y = 3 } }).?,
|
|
sel.end(),
|
|
false,
|
|
), sel.containedRow(
|
|
&s,
|
|
s.pages.pin(.{ .screen = .{ .x = 2, .y = 3 } }).?,
|
|
).?);
|
|
|
|
// Middle line
|
|
try testing.expectEqual(Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 0, .y = 2 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = s.pages.cols - 1, .y = 2 } }).?,
|
|
false,
|
|
), sel.containedRow(
|
|
&s,
|
|
s.pages.pin(.{ .screen = .{ .x = 2, .y = 2 } }).?,
|
|
).?);
|
|
}
|
|
|
|
// Rectangle
|
|
{
|
|
const sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 3, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 6, .y = 3 } }).?,
|
|
true,
|
|
);
|
|
|
|
// Not contained
|
|
try testing.expect(sel.containedRow(
|
|
&s,
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 4 } }).?,
|
|
) == null);
|
|
|
|
// Start line
|
|
try testing.expectEqual(Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 3, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 6, .y = 1 } }).?,
|
|
true,
|
|
), sel.containedRow(
|
|
&s,
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 1 } }).?,
|
|
).?);
|
|
|
|
// End line
|
|
try testing.expectEqual(Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 3, .y = 3 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 6, .y = 3 } }).?,
|
|
true,
|
|
), sel.containedRow(
|
|
&s,
|
|
s.pages.pin(.{ .screen = .{ .x = 2, .y = 3 } }).?,
|
|
).?);
|
|
|
|
// Middle line
|
|
try testing.expectEqual(Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 3, .y = 2 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 6, .y = 2 } }).?,
|
|
true,
|
|
), sel.containedRow(
|
|
&s,
|
|
s.pages.pin(.{ .screen = .{ .x = 2, .y = 2 } }).?,
|
|
).?);
|
|
}
|
|
|
|
// Single-line selection
|
|
{
|
|
const sel = Selection.init(
|
|
s.pages.pin(.{ .screen = .{ .x = 2, .y = 1 } }).?,
|
|
s.pages.pin(.{ .screen = .{ .x = 6, .y = 1 } }).?,
|
|
false,
|
|
);
|
|
|
|
// Not contained
|
|
try testing.expect(sel.containedRow(
|
|
&s,
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 0 } }).?,
|
|
) == null);
|
|
try testing.expect(sel.containedRow(
|
|
&s,
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 2 } }).?,
|
|
) == null);
|
|
|
|
// Contained
|
|
try testing.expectEqual(sel, sel.containedRow(
|
|
&s,
|
|
s.pages.pin(.{ .screen = .{ .x = 1, .y = 1 } }).?,
|
|
).?);
|
|
}
|
|
}
|