1use crate::Position;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum Selection {
11 Char { anchor: Position, head: Position },
16 Line { anchor_row: usize, head_row: usize },
19 Block { anchor: Position, head: Position },
23}
24
25pub type RowSpan = Option<(usize, usize)>;
30
31impl Selection {
32 pub fn head(self) -> Position {
35 match self {
36 Self::Char { head, .. } => head,
37 Self::Line { head_row, .. } => Position::new(head_row, 0),
38 Self::Block { head, .. } => head,
39 }
40 }
41
42 pub fn anchor(self) -> Position {
45 match self {
46 Self::Char { anchor, .. } => anchor,
47 Self::Line { anchor_row, .. } => Position::new(anchor_row, 0),
48 Self::Block { anchor, .. } => anchor,
49 }
50 }
51
52 pub fn extend_to(&mut self, pos: Position) {
56 match self {
57 Self::Char { head, .. } => *head = pos,
58 Self::Line { head_row, .. } => *head_row = pos.row,
59 Self::Block { head, .. } => *head = pos,
60 }
61 }
62
63 pub fn row_span(self, row: usize) -> RowSpan {
74 match self {
75 Self::Char { anchor, head } => {
76 let (start, end) = order(anchor, head);
77 if row < start.row || row > end.row {
78 return None;
79 }
80 let lo = if row == start.row { start.col } else { 0 };
81 let hi = if row == end.row { end.col } else { usize::MAX };
82 Some((lo, hi))
83 }
84 Self::Line {
85 anchor_row,
86 head_row,
87 } => {
88 let (lo, hi) = if anchor_row <= head_row {
89 (anchor_row, head_row)
90 } else {
91 (head_row, anchor_row)
92 };
93 if row < lo || row > hi {
94 None
95 } else {
96 Some((0, usize::MAX))
97 }
98 }
99 Self::Block { anchor, head } => {
100 let (top, bot) = (anchor.row.min(head.row), anchor.row.max(head.row));
101 if row < top || row > bot {
102 return None;
103 }
104 let (left, right) = (anchor.col.min(head.col), anchor.col.max(head.col));
105 Some((left, right))
106 }
107 }
108 }
109
110 pub fn row_bounds(self) -> (usize, usize) {
112 match self {
113 Self::Char { anchor, head } => {
114 let (s, e) = order(anchor, head);
115 (s.row, e.row)
116 }
117 Self::Line {
118 anchor_row,
119 head_row,
120 } => (anchor_row.min(head_row), anchor_row.max(head_row)),
121 Self::Block { anchor, head } => (anchor.row.min(head.row), anchor.row.max(head.row)),
122 }
123 }
124}
125
126fn order(a: Position, b: Position) -> (Position, Position) {
128 if a <= b { (a, b) } else { (b, a) }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134
135 #[test]
136 fn char_single_row_inclusive() {
137 let sel = Selection::Char {
138 anchor: Position::new(0, 2),
139 head: Position::new(0, 5),
140 };
141 assert_eq!(sel.row_span(0), Some((2, 5)));
142 assert_eq!(sel.row_span(1), None);
143 }
144
145 #[test]
146 fn char_multi_row_clips_endpoints() {
147 let sel = Selection::Char {
148 anchor: Position::new(1, 3),
149 head: Position::new(3, 7),
150 };
151 assert_eq!(sel.row_span(0), None);
152 assert_eq!(sel.row_span(1), Some((3, usize::MAX)));
153 assert_eq!(sel.row_span(2), Some((0, usize::MAX)));
154 assert_eq!(sel.row_span(3), Some((0, 7)));
155 assert_eq!(sel.row_span(4), None);
156 }
157
158 #[test]
159 fn char_handles_reversed_endpoints() {
160 let sel = Selection::Char {
162 anchor: Position::new(3, 7),
163 head: Position::new(1, 3),
164 };
165 assert_eq!(sel.row_span(1), Some((3, usize::MAX)));
166 assert_eq!(sel.row_span(3), Some((0, 7)));
167 }
168
169 #[test]
170 fn line_covers_whole_rows_only() {
171 let sel = Selection::Line {
172 anchor_row: 5,
173 head_row: 7,
174 };
175 assert_eq!(sel.row_span(4), None);
176 assert_eq!(sel.row_span(5), Some((0, usize::MAX)));
177 assert_eq!(sel.row_span(6), Some((0, usize::MAX)));
178 assert_eq!(sel.row_span(7), Some((0, usize::MAX)));
179 assert_eq!(sel.row_span(8), None);
180 }
181
182 #[test]
183 fn block_inclusive_rect() {
184 let sel = Selection::Block {
185 anchor: Position::new(2, 4),
186 head: Position::new(5, 8),
187 };
188 for row in 2..=5 {
189 assert_eq!(sel.row_span(row), Some((4, 8)));
190 }
191 assert_eq!(sel.row_span(1), None);
192 assert_eq!(sel.row_span(6), None);
193 }
194
195 #[test]
196 fn block_normalises_corners() {
197 let sel = Selection::Block {
199 anchor: Position::new(5, 8),
200 head: Position::new(2, 4),
201 };
202 for row in 2..=5 {
203 assert_eq!(sel.row_span(row), Some((4, 8)));
204 }
205 }
206
207 #[test]
208 fn extend_to_updates_head() {
209 let mut sel = Selection::Char {
210 anchor: Position::new(0, 0),
211 head: Position::new(0, 3),
212 };
213 sel.extend_to(Position::new(2, 9));
214 assert_eq!(sel.head(), Position::new(2, 9));
215 assert_eq!(sel.anchor(), Position::new(0, 0));
216 }
217
218 #[test]
219 fn line_extend_to_drops_column() {
220 let mut sel = Selection::Line {
221 anchor_row: 1,
222 head_row: 1,
223 };
224 sel.extend_to(Position::new(4, 50));
225 assert_eq!(sel.head(), Position::new(4, 0));
226 }
227
228 #[test]
229 fn row_bounds_each_kind() {
230 let c = Selection::Char {
231 anchor: Position::new(2, 0),
232 head: Position::new(5, 0),
233 };
234 assert_eq!(c.row_bounds(), (2, 5));
235 let l = Selection::Line {
236 anchor_row: 7,
237 head_row: 3,
238 };
239 assert_eq!(l.row_bounds(), (3, 7));
240 let b = Selection::Block {
241 anchor: Position::new(8, 1),
242 head: Position::new(2, 9),
243 };
244 assert_eq!(b.row_bounds(), (2, 8));
245 }
246}