From 8facf9b94271657faf60b4ea8e8d2a471da86cfb Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 4 Aug 2022 11:44:36 -0700 Subject: [PATCH] terminal: add Selection struct --- src/terminal/Selection.zig | 87 ++++++++++++++++++++++++++++++++++++++ src/terminal/main.zig | 5 +++ src/terminal/point.zig | 7 +++ 3 files changed, 99 insertions(+) create mode 100644 src/terminal/Selection.zig create mode 100644 src/terminal/point.zig diff --git a/src/terminal/Selection.zig b/src/terminal/Selection.zig new file mode 100644 index 000000000..f06848329 --- /dev/null +++ b/src/terminal/Selection.zig @@ -0,0 +1,87 @@ +/// Represents a single selection within the terminal +/// (i.e. a highlight region). +const Selection = @This(); + +const std = @import("std"); +const point = @import("point.zig"); +const Point = point.Point; + +/// Start and end of the selection. 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. +start: Point, +end: Point, + +/// 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, p: Point) bool { + const tl = self.topLeft(); + const br = self.bottomRight(); + + // 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; +} + +/// Returns the top left point of the selection. +pub fn topLeft(self: Selection) Point { + return switch (self.order()) { + .forward => self.start, + .reverse => self.end, + }; +} + +/// Returns the bottom right point of the selection. +pub fn bottomRight(self: Selection) Point { + return switch (self.order()) { + .forward => self.end, + .reverse => self.start, + }; +} + +/// The order of the selection (whether it is selecting forward or back). +const Order = enum { forward, reverse }; + +fn order(self: Selection) Order { + if (self.start.y < self.end.y) return .forward; + if (self.start.y > self.end.y) return .reverse; + if (self.start.x <= self.end.x) return .forward; + return .reverse; +} + +test "Selection: contains" { + const testing = std.testing; + { + const sel: Selection = .{ + .start = .{ .x = 5, .y = 1 }, + .end = .{ .x = 3, .y = 2 }, + }; + + try testing.expect(sel.contains(.{ .x = 6, .y = 1 })); + try testing.expect(sel.contains(.{ .x = 1, .y = 2 })); + try testing.expect(!sel.contains(.{ .x = 1, .y = 1 })); + try testing.expect(!sel.contains(.{ .x = 5, .y = 2 })); + } + + // Reverse + { + const sel: Selection = .{ + .start = .{ .x = 3, .y = 2 }, + .end = .{ .x = 5, .y = 1 }, + }; + + try testing.expect(sel.contains(.{ .x = 6, .y = 1 })); + try testing.expect(sel.contains(.{ .x = 1, .y = 2 })); + try testing.expect(!sel.contains(.{ .x = 1, .y = 1 })); + try testing.expect(!sel.contains(.{ .x = 5, .y = 2 })); + } +} diff --git a/src/terminal/main.zig b/src/terminal/main.zig index fecb37e57..0294346d7 100644 --- a/src/terminal/main.zig +++ b/src/terminal/main.zig @@ -2,10 +2,12 @@ const stream = @import("stream.zig"); const ansi = @import("ansi.zig"); const csi = @import("csi.zig"); const sgr = @import("sgr.zig"); +const point = @import("point.zig"); pub const color = @import("color.zig"); pub const Terminal = @import("Terminal.zig"); pub const Parser = @import("Parser.zig"); +pub const Selection = @import("Selection.zig"); pub const Stream = stream.Stream; pub const CursorStyle = ansi.CursorStyle; pub const DeviceAttributeReq = ansi.DeviceAttributeReq; @@ -17,6 +19,7 @@ pub const EraseDisplay = csi.EraseDisplay; pub const EraseLine = csi.EraseLine; pub const TabClear = csi.TabClear; pub const Attribute = sgr.Attribute; +pub const Point = point.Point; // Not exported because they're just used for tests. @@ -24,9 +27,11 @@ test { _ = ansi; _ = color; _ = csi; + _ = point; _ = sgr; _ = stream; _ = Parser; + _ = Selection; _ = Terminal; _ = @import("osc.zig"); diff --git a/src/terminal/point.zig b/src/terminal/point.zig new file mode 100644 index 000000000..2f834a213 --- /dev/null +++ b/src/terminal/point.zig @@ -0,0 +1,7 @@ +/// Point is a point within the terminal grid. A point is ALWAYS +/// zero-indexed. If you see the "Point" type, you know that a +/// zero-indexed value is expected. +pub const Point = struct { + x: usize = 0, + y: usize = 0, +};