miden_debug_types/
selection.rs

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