Skip to main content

litcheck_core/diagnostics/
selection.rs

1use super::{ColumnIndex, LineIndex};
2
3/// A range in a text document expressed as (zero-based) start and end positions.
4///
5/// This is comparable to a selection in an editor, therefore the end position is exclusive.
6#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
7pub struct Selection {
8    pub start: Position,
9    pub end: Position,
10}
11
12impl Selection {
13    #[inline]
14    pub fn new(start: Position, end: Position) -> Self {
15        if start <= end {
16            Self { start, end }
17        } else {
18            Self {
19                start: end,
20                end: start,
21            }
22        }
23    }
24
25    pub fn canonicalize(&mut self) {
26        if self.start > self.end {
27            core::mem::swap(&mut self.start, &mut self.end);
28        }
29    }
30}
31
32impl From<core::ops::Range<Position>> for Selection {
33    #[inline]
34    fn from(value: core::ops::Range<Position>) -> Self {
35        Self::new(value.start, value.end)
36    }
37}
38
39impl From<core::ops::Range<LineIndex>> for Selection {
40    #[inline]
41    fn from(value: core::ops::Range<LineIndex>) -> Self {
42        Self::new(value.start.into(), value.end.into())
43    }
44}
45
46/// Position in a text document expressed as zero-based line and character offset.
47///
48/// A position is between two characters like an insert cursor in an editor.
49#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
50pub struct Position {
51    pub line: LineIndex,
52    pub character: ColumnIndex,
53}
54
55impl Position {
56    pub const fn new(line: u32, character: u32) -> Self {
57        Self {
58            line: LineIndex(line),
59            character: ColumnIndex(character),
60        }
61    }
62}
63
64impl From<LineIndex> for Position {
65    #[inline]
66    fn from(line: LineIndex) -> Self {
67        Self {
68            line,
69            character: ColumnIndex(0),
70        }
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn selection_new_orders_bounds_when_reversed() {
80        let a = Position::new(10, 5);
81        let b = Position::new(2, 3);
82        let sel = Selection::new(a, b);
83        assert!(sel.start <= sel.end);
84        assert_eq!(sel.start, b);
85        assert_eq!(sel.end, a);
86    }
87
88    #[test]
89    fn selection_new_keeps_order_when_already_ordered() {
90        let a = Position::new(2, 3);
91        let b = Position::new(10, 5);
92        let sel = Selection::new(a, b);
93        assert_eq!(sel.start, a);
94        assert_eq!(sel.end, b);
95    }
96
97    #[test]
98    fn canonicalize_swaps_only_when_start_greater_than_end() {
99        let a = Position::new(10, 5);
100        let b = Position::new(2, 3);
101        let mut sel = Selection { start: a, end: b };
102        sel.canonicalize();
103        assert!(sel.start <= sel.end);
104        assert_eq!(sel.start, b);
105        assert_eq!(sel.end, a);
106
107        let mut sel2 = Selection { start: b, end: a };
108        sel2.canonicalize();
109        assert_eq!(sel2.start, b);
110        assert_eq!(sel2.end, a);
111    }
112}