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 Selection::Char { head, .. } => head,
37 Selection::Line { head_row, .. } => Position::new(head_row, 0),
38 Selection::Block { head, .. } => head,
39 }
40 }
41
42 pub fn anchor(self) -> Position {
45 match self {
46 Selection::Char { anchor, .. } => anchor,
47 Selection::Line { anchor_row, .. } => Position::new(anchor_row, 0),
48 Selection::Block { anchor, .. } => anchor,
49 }
50 }
51
52 pub fn extend_to(&mut self, pos: Position) {
56 match self {
57 Selection::Char { head, .. } => *head = pos,
58 Selection::Line { head_row, .. } => *head_row = pos.row,
59 Selection::Block { head, .. } => *head = pos,
60 }
61 }
62
63 pub fn row_span(self, row: usize) -> RowSpan {
74 match self {
75 Selection::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 Selection::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 Selection::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 Selection::Char { anchor, head } => {
114 let (s, e) = order(anchor, head);
115 (s.row, e.row)
116 }
117 Selection::Line {
118 anchor_row,
119 head_row,
120 } => (anchor_row.min(head_row), anchor_row.max(head_row)),
121 Selection::Block { anchor, head } => {
122 (anchor.row.min(head.row), anchor.row.max(head.row))
123 }
124 }
125 }
126}
127
128fn order(a: Position, b: Position) -> (Position, Position) {
130 if a <= b { (a, b) } else { (b, a) }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn char_single_row_inclusive() {
139 let sel = Selection::Char {
140 anchor: Position::new(0, 2),
141 head: Position::new(0, 5),
142 };
143 assert_eq!(sel.row_span(0), Some((2, 5)));
144 assert_eq!(sel.row_span(1), None);
145 }
146
147 #[test]
148 fn char_multi_row_clips_endpoints() {
149 let sel = Selection::Char {
150 anchor: Position::new(1, 3),
151 head: Position::new(3, 7),
152 };
153 assert_eq!(sel.row_span(0), None);
154 assert_eq!(sel.row_span(1), Some((3, usize::MAX)));
155 assert_eq!(sel.row_span(2), Some((0, usize::MAX)));
156 assert_eq!(sel.row_span(3), Some((0, 7)));
157 assert_eq!(sel.row_span(4), None);
158 }
159
160 #[test]
161 fn char_handles_reversed_endpoints() {
162 let sel = Selection::Char {
164 anchor: Position::new(3, 7),
165 head: Position::new(1, 3),
166 };
167 assert_eq!(sel.row_span(1), Some((3, usize::MAX)));
168 assert_eq!(sel.row_span(3), Some((0, 7)));
169 }
170
171 #[test]
172 fn line_covers_whole_rows_only() {
173 let sel = Selection::Line {
174 anchor_row: 5,
175 head_row: 7,
176 };
177 assert_eq!(sel.row_span(4), None);
178 assert_eq!(sel.row_span(5), Some((0, usize::MAX)));
179 assert_eq!(sel.row_span(6), Some((0, usize::MAX)));
180 assert_eq!(sel.row_span(7), Some((0, usize::MAX)));
181 assert_eq!(sel.row_span(8), None);
182 }
183
184 #[test]
185 fn block_inclusive_rect() {
186 let sel = Selection::Block {
187 anchor: Position::new(2, 4),
188 head: Position::new(5, 8),
189 };
190 for row in 2..=5 {
191 assert_eq!(sel.row_span(row), Some((4, 8)));
192 }
193 assert_eq!(sel.row_span(1), None);
194 assert_eq!(sel.row_span(6), None);
195 }
196
197 #[test]
198 fn block_normalises_corners() {
199 let sel = Selection::Block {
201 anchor: Position::new(5, 8),
202 head: Position::new(2, 4),
203 };
204 for row in 2..=5 {
205 assert_eq!(sel.row_span(row), Some((4, 8)));
206 }
207 }
208
209 #[test]
210 fn extend_to_updates_head() {
211 let mut sel = Selection::Char {
212 anchor: Position::new(0, 0),
213 head: Position::new(0, 3),
214 };
215 sel.extend_to(Position::new(2, 9));
216 assert_eq!(sel.head(), Position::new(2, 9));
217 assert_eq!(sel.anchor(), Position::new(0, 0));
218 }
219
220 #[test]
221 fn line_extend_to_drops_column() {
222 let mut sel = Selection::Line {
223 anchor_row: 1,
224 head_row: 1,
225 };
226 sel.extend_to(Position::new(4, 50));
227 assert_eq!(sel.head(), Position::new(4, 0));
228 }
229
230 #[test]
231 fn row_bounds_each_kind() {
232 let c = Selection::Char {
233 anchor: Position::new(2, 0),
234 head: Position::new(5, 0),
235 };
236 assert_eq!(c.row_bounds(), (2, 5));
237 let l = Selection::Line {
238 anchor_row: 7,
239 head_row: 3,
240 };
241 assert_eq!(l.row_bounds(), (3, 7));
242 let b = Selection::Block {
243 anchor: Position::new(8, 1),
244 head: Position::new(2, 9),
245 };
246 assert_eq!(b.row_bounds(), (2, 8));
247 }
248}