Skip to main content

kode_core/
selection.rs

1/// A position in the document as (line, column), both 0-indexed.
2/// Column is measured in chars (not bytes).
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
4pub struct Position {
5    pub line: usize,
6    pub col: usize,
7}
8
9impl Position {
10    pub fn new(line: usize, col: usize) -> Self {
11        Self { line, col }
12    }
13
14    pub fn zero() -> Self {
15        Self { line: 0, col: 0 }
16    }
17}
18
19impl PartialOrd for Position {
20    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
21        Some(self.cmp(other))
22    }
23}
24
25impl Ord for Position {
26    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
27        self.line.cmp(&other.line).then(self.col.cmp(&other.col))
28    }
29}
30
31/// A selection in the document. `anchor` is where the selection started,
32/// `head` is where the cursor currently is. When anchor == head, it's a
33/// simple cursor with no selected text.
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub struct Selection {
36    pub anchor: Position,
37    pub head: Position,
38}
39
40impl Selection {
41    /// A cursor at the given position (no selected text).
42    pub fn cursor(pos: Position) -> Self {
43        Self {
44            anchor: pos,
45            head: pos,
46        }
47    }
48
49    /// A selection from anchor to head.
50    pub fn new(anchor: Position, head: Position) -> Self {
51        Self { anchor, head }
52    }
53
54    /// The earlier of anchor/head.
55    pub fn start(&self) -> Position {
56        std::cmp::min(self.anchor, self.head)
57    }
58
59    /// The later of anchor/head.
60    pub fn end(&self) -> Position {
61        std::cmp::max(self.anchor, self.head)
62    }
63
64    /// True if no text is selected (cursor only).
65    pub fn is_cursor(&self) -> bool {
66        self.anchor == self.head
67    }
68
69    /// True if the selection extends backward (head before anchor).
70    pub fn is_backward(&self) -> bool {
71        self.head < self.anchor
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78
79    #[test]
80    fn position_ordering() {
81        let a = Position::new(0, 5);
82        let b = Position::new(1, 0);
83        let c = Position::new(1, 3);
84        assert!(a < b);
85        assert!(b < c);
86        assert!(a < c);
87    }
88
89    #[test]
90    fn cursor_is_cursor() {
91        let sel = Selection::cursor(Position::new(2, 4));
92        assert!(sel.is_cursor());
93        assert!(!sel.is_backward());
94        assert_eq!(sel.start(), sel.end());
95    }
96
97    #[test]
98    fn forward_selection() {
99        let sel = Selection::new(Position::new(0, 0), Position::new(1, 5));
100        assert!(!sel.is_cursor());
101        assert!(!sel.is_backward());
102        assert_eq!(sel.start(), Position::new(0, 0));
103        assert_eq!(sel.end(), Position::new(1, 5));
104    }
105
106    #[test]
107    fn backward_selection() {
108        let sel = Selection::new(Position::new(3, 2), Position::new(1, 0));
109        assert!(sel.is_backward());
110        assert_eq!(sel.start(), Position::new(1, 0));
111        assert_eq!(sel.end(), Position::new(3, 2));
112    }
113}