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 },
26}
27
28pub type RowSpan = Option<(usize, usize)>;
33
34impl Selection {
35 pub fn head(self) -> Position {
38 match self {
39 Self::Char { head, .. } => head,
40 Self::Line { head_row, .. } => Position::new(head_row, 0),
41 Self::Block { head, .. } => head,
42 }
43 }
44
45 pub fn anchor(self) -> Position {
48 match self {
49 Self::Char { anchor, .. } => anchor,
50 Self::Line { anchor_row, .. } => Position::new(anchor_row, 0),
51 Self::Block { anchor, .. } => anchor,
52 }
53 }
54
55 pub fn extend_to(&mut self, pos: Position) {
59 match self {
60 Self::Char { head, .. } => *head = pos,
61 Self::Line { head_row, .. } => *head_row = pos.row,
62 Self::Block { head, .. } => *head = pos,
63 }
64 }
65
66 pub fn row_span(self, row: usize) -> RowSpan {
77 match self {
78 Self::Char { anchor, head } => {
79 let (start, end) = order(anchor, head);
80 if row < start.row || row > end.row {
81 return None;
82 }
83 let lo = if row == start.row { start.col } else { 0 };
84 let hi = if row == end.row { end.col } else { usize::MAX };
85 Some((lo, hi))
86 }
87 Self::Line {
88 anchor_row,
89 head_row,
90 } => {
91 let (lo, hi) = if anchor_row <= head_row {
92 (anchor_row, head_row)
93 } else {
94 (head_row, anchor_row)
95 };
96 if row < lo || row > hi {
97 None
98 } else {
99 Some((0, usize::MAX))
100 }
101 }
102 Self::Block { anchor, head } => {
103 let (top, bot) = (anchor.row.min(head.row), anchor.row.max(head.row));
104 if row < top || row > bot {
105 return None;
106 }
107 let (left, right) = (anchor.col.min(head.col), anchor.col.max(head.col));
108 Some((left, right))
109 }
110 }
111 }
112
113 pub fn row_span_on(self, row: usize, line: &str, tab_width: usize) -> RowSpan {
121 let Self::Block { .. } = self else {
122 return self.row_span(row);
123 };
124 let (left, right) = self.row_span(row)?;
125 let mut first = None;
126 let mut last = None;
127 let mut vcol = 0;
128 let mut count = 0;
129 let tab_width = tab_width.max(1);
130 for (i, ch) in line.chars().enumerate() {
131 let next = if ch == '\t' {
132 vcol + tab_width - vcol % tab_width
133 } else {
134 vcol + crate::geom::char_col_to_visual_col(ch.encode_utf8(&mut [0; 4]), 1, 1)
135 };
136 if vcol <= right && next > left {
137 first.get_or_insert(i);
138 last = Some(i);
139 }
140 vcol = next;
141 count = i + 1;
142 }
143 let past = |col: usize| count + col.saturating_sub(vcol);
144 let lo = first.unwrap_or_else(|| past(left));
145 let hi = if right == usize::MAX {
146 usize::MAX
147 } else {
148 match last {
149 Some(i) if right < vcol => i,
150 _ => past(right),
151 }
152 };
153 Some((lo, hi))
154 }
155
156 pub fn row_bounds(self) -> (usize, usize) {
158 match self {
159 Self::Char { anchor, head } => {
160 let (s, e) = order(anchor, head);
161 (s.row, e.row)
162 }
163 Self::Line {
164 anchor_row,
165 head_row,
166 } => (anchor_row.min(head_row), anchor_row.max(head_row)),
167 Self::Block { anchor, head } => (anchor.row.min(head.row), anchor.row.max(head.row)),
168 }
169 }
170}
171
172fn order(a: Position, b: Position) -> (Position, Position) {
174 if a <= b { (a, b) } else { (b, a) }
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180
181 fn block(top: usize, left: usize, bot: usize, right: usize) -> Selection {
182 Selection::Block {
183 anchor: Position::new(top, left),
184 head: Position::new(bot, right),
185 }
186 }
187
188 #[test]
189 fn block_row_span_on_plain_text_is_row_span() {
190 let sel = block(0, 1, 1, 3);
191 assert_eq!(sel.row_span_on(0, "abcdef", 4), sel.row_span(0));
192 }
193
194 #[test]
195 fn block_row_span_on_maps_display_columns_through_a_tab() {
196 let sel = block(0, 2, 1, 4);
198 assert_eq!(sel.row_span_on(0, "\tab", 4), Some((0, 1)));
199 assert_eq!(sel.row_span_on(1, "abcdefgh", 4), Some((2, 4)));
200 }
201
202 #[test]
203 fn block_row_span_on_past_the_line_counts_cells() {
204 let sel = block(0, 6, 1, 9);
206 assert_eq!(sel.row_span_on(0, "\tab", 4), Some((3, 6)));
207 }
208
209 #[test]
210 fn block_row_span_on_dollar_runs_to_the_end() {
211 let sel = block(0, 1, 0, usize::MAX);
212 assert_eq!(sel.row_span_on(0, "\tab", 4), Some((0, usize::MAX)));
213 }
214
215 #[test]
216 fn char_single_row_inclusive() {
217 let sel = Selection::Char {
218 anchor: Position::new(0, 2),
219 head: Position::new(0, 5),
220 };
221 assert_eq!(sel.row_span(0), Some((2, 5)));
222 assert_eq!(sel.row_span(1), None);
223 }
224
225 #[test]
226 fn char_multi_row_clips_endpoints() {
227 let sel = Selection::Char {
228 anchor: Position::new(1, 3),
229 head: Position::new(3, 7),
230 };
231 assert_eq!(sel.row_span(0), None);
232 assert_eq!(sel.row_span(1), Some((3, usize::MAX)));
233 assert_eq!(sel.row_span(2), Some((0, usize::MAX)));
234 assert_eq!(sel.row_span(3), Some((0, 7)));
235 assert_eq!(sel.row_span(4), None);
236 }
237
238 #[test]
239 fn char_handles_reversed_endpoints() {
240 let sel = Selection::Char {
242 anchor: Position::new(3, 7),
243 head: Position::new(1, 3),
244 };
245 assert_eq!(sel.row_span(1), Some((3, usize::MAX)));
246 assert_eq!(sel.row_span(3), Some((0, 7)));
247 }
248
249 #[test]
250 fn line_covers_whole_rows_only() {
251 let sel = Selection::Line {
252 anchor_row: 5,
253 head_row: 7,
254 };
255 assert_eq!(sel.row_span(4), None);
256 assert_eq!(sel.row_span(5), Some((0, usize::MAX)));
257 assert_eq!(sel.row_span(6), Some((0, usize::MAX)));
258 assert_eq!(sel.row_span(7), Some((0, usize::MAX)));
259 assert_eq!(sel.row_span(8), None);
260 }
261
262 #[test]
263 fn block_inclusive_rect() {
264 let sel = Selection::Block {
265 anchor: Position::new(2, 4),
266 head: Position::new(5, 8),
267 };
268 for row in 2..=5 {
269 assert_eq!(sel.row_span(row), Some((4, 8)));
270 }
271 assert_eq!(sel.row_span(1), None);
272 assert_eq!(sel.row_span(6), None);
273 }
274
275 #[test]
276 fn block_normalises_corners() {
277 let sel = Selection::Block {
279 anchor: Position::new(5, 8),
280 head: Position::new(2, 4),
281 };
282 for row in 2..=5 {
283 assert_eq!(sel.row_span(row), Some((4, 8)));
284 }
285 }
286
287 #[test]
288 fn extend_to_updates_head() {
289 let mut sel = Selection::Char {
290 anchor: Position::new(0, 0),
291 head: Position::new(0, 3),
292 };
293 sel.extend_to(Position::new(2, 9));
294 assert_eq!(sel.head(), Position::new(2, 9));
295 assert_eq!(sel.anchor(), Position::new(0, 0));
296 }
297
298 #[test]
299 fn line_extend_to_drops_column() {
300 let mut sel = Selection::Line {
301 anchor_row: 1,
302 head_row: 1,
303 };
304 sel.extend_to(Position::new(4, 50));
305 assert_eq!(sel.head(), Position::new(4, 0));
306 }
307
308 #[test]
309 fn row_bounds_each_kind() {
310 let c = Selection::Char {
311 anchor: Position::new(2, 0),
312 head: Position::new(5, 0),
313 };
314 assert_eq!(c.row_bounds(), (2, 5));
315 let l = Selection::Line {
316 anchor_row: 7,
317 head_row: 3,
318 };
319 assert_eq!(l.row_bounds(), (3, 7));
320 let b = Selection::Block {
321 anchor: Position::new(8, 1),
322 head: Position::new(2, 9),
323 };
324 assert_eq!(b.row_bounds(), (2, 8));
325 }
326}