mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
terminal: add Selection struct
This commit is contained in:
87
src/terminal/Selection.zig
Normal file
87
src/terminal/Selection.zig
Normal file
@ -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 }));
|
||||
}
|
||||
}
|
@ -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");
|
||||
|
7
src/terminal/point.zig
Normal file
7
src/terminal/point.zig
Normal file
@ -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,
|
||||
};
|
Reference in New Issue
Block a user