Something went wrong. Try again.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605//! Represents a single selection within the terminal (i.e. a highlight region).const Selection = @This();
const std = @import("std");const assert = @import("../quirks.zig").inlineAssert;const Allocator = std.mem.Allocator;const lib = @import("lib.zig");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) Allocator.Error!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 = lib.Enum(lib.target, &.{ "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.////// This is a very expensive operation. It has to traverse the linked list/// of pages for the top-left, bottom-right, and the given pin to find/// the coordinates. If you are calling this repeatedly, prefer/// `containedRowCached`.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. Callers should prefer containedRowCached if they // can swing it. 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;
return self.containedRowCached( s, tl_pin, br_pin, pin, tl, br, p, );}
/// Same as containedRow but useful if you're calling it repeatedly/// so that the pins can be cached across calls. Advanced.pub fn containedRowCached( self: Selection, s: *const Screen, tl_pin: Pin, br_pin: Pin, pin: Pin, tl: point.Coordinate, br: point.Coordinate, p: point.Coordinate,) ?Selection { 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( start: { var copy: Pin = pin; copy.x = tl.x; break :start copy; }, end: { var copy: Pin = pin; copy.x = br.x; break :end copy; }, 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, end: { var copy: Pin = pin; copy.x = s.pages.cols - 1; break :end copy; }, 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( start: { var copy: Pin = pin; copy.x = 0; break :start copy; }, br_pin, false, ); }
// Row is somewhere between our selection lines so we return the full line. return init( start: { var copy: Pin = pin; copy.x = 0; break :start copy; }, end: { var copy: Pin = pin; copy.x = s.pages.cols - 1; break :end copy; }, false, );}
/// Possible adjustments to the selection.pub const Adjustment = lib.Enum(lib.target, &.{ "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, .{ .cols = 10, .rows = 10, .max_scrollback = 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, .{ .cols = 10, .rows = 10, .max_scrollback = 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, .{ .cols = 10, .rows = 10, .max_scrollback = 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, .{ .cols = 10, .rows = 10, .max_scrollback = 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, .{ .cols = 10, .rows = 10, .max_scrollback = 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, .{ .cols = 10, .rows = 10, .max_scrollback = 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, .{ .cols = 10, .rows = 10, .max_scrollback = 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, .{ .cols = 10, .rows = 10, .max_scrollback = 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, .{ .cols = 8, .rows = 10, .max_scrollback = 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, .{ .cols = 8, .rows = 10, .max_scrollback = 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, .{ .cols = 100, .rows = 100, .max_scrollback = 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, .{ .cols = 100, .rows = 100, .max_scrollback = 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, .{ .cols = 10, .rows = 10, .max_scrollback = 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, .{ .cols = 10, .rows = 10, .max_scrollback = 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, .{ .cols = 10, .rows = 10, .max_scrollback = 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, .{ .cols = 10, .rows = 10, .max_scrollback = 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, .{ .cols = 15, .rows = 15, .max_scrollback = 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, .{ .cols = 10, .rows = 5, .max_scrollback = 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 } }).?, ).?); }}