textpos/
insert.rs

1use std::{
2    fmt::{Display, Debug},
3    str::FromStr
4};
5
6use crate::TextPosition;
7
8/// Describes a position between two characters.
9#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
10pub struct InsertPosition {
11    line: usize,
12    col: usize,
13}
14
15impl Default for InsertPosition {
16    fn default() -> Self {
17        Self { line: 1, col: 0 }
18    }
19}
20
21impl InsertPosition {
22    /// Crate a new `InsertPosition` from raw parts
23    pub fn new(line: usize, col: usize) -> Self {
24        assert_ne!(line, 0);
25        InsertPosition { line, col }
26    }
27
28    /// The line this position is on. 1 is the first line.
29    ///
30    /// This is always equals to the number of `inc_line()` calls + 1 made on
31    /// this position.
32    pub fn line(&self) -> usize { self.line }
33    /// The column to the left of the position. 0 is the first insert position,
34    /// when there is no column left of the position.
35    ///
36    /// This is always equals to the number of `inc_col()` calls made on this
37    /// position after the last `inc_line()` call.
38    pub fn col(&self) -> usize { self.col }
39
40    /// Move the position forward by one non-newline character.
41    pub fn inc_col(&mut self) {
42        self.col += 1;
43    }
44    /// Move the position forward by one newline character.
45    pub fn inc_line(&mut self) {
46        self.line += 1;
47        self.col = 0;
48    }
49    /// Move the position forward by one character.
50    /// If the character `c` is a newline, it behaves like [`inc_line()`],
51    /// otherwhise it behaves like [`inc_col()`].
52    pub fn inc(&mut self, c: char) {
53        if c == '\n' {
54            self.inc_line();
55        } else {
56            self.inc_col();
57        }
58    }
59
60    pub(crate) fn text_pos_left(&self) -> TextPosition {
61        TextPosition::new(self.line(), self.col())
62    }
63    pub(crate) fn text_pos_right(&self) -> TextPosition {
64        TextPosition::new(self.line(), self.col()+1)
65    }
66}
67
68impl Display for InsertPosition {
69    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70        write!(f, "{}:{}", self.line(), self.col())
71    }
72}
73impl Debug for InsertPosition {
74    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75        write!(f, "{}", self)
76    }
77}
78
79impl FromStr for InsertPosition {
80    type Err = ();
81
82    fn from_str(s: &str) -> Result<Self, Self::Err> {
83        let (line_str, col_str) = s.trim().split_once(':').ok_or(())?;
84        let line: usize = line_str.parse().map_err(|_| ())?;
85        let col: usize = col_str.parse().map_err(|_| ())?;
86
87        // Validate
88        if line == 0 { return Err(()); }
89
90        Ok(InsertPosition::new(line, col))
91    }
92}
93
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn initializes_correctly() {
101        let default = InsertPosition::default();
102        assert_eq!(default.line(), 1);
103        assert_eq!(default.col(), 0);
104    }
105
106    #[test]
107    fn increment_methods() {
108        let mut pos = InsertPosition::default();
109
110        pos.inc_col();
111        pos.inc_col();
112        pos.inc_col();
113
114        assert_eq!(pos.line(), 1);
115        assert_eq!(pos.col(), 3);
116
117        pos.inc_col();
118        pos.inc_line();
119        pos.inc_col();
120
121        assert_eq!(pos.line(), 2);
122        assert_eq!(pos.col(), 1);
123
124        pos.inc_col();
125        pos.inc_line();
126        pos.inc_line();
127
128        assert_eq!(pos.line(), 4);
129        assert_eq!(pos.col(), 0);
130    }
131
132    #[test]
133    fn display_print() {
134        let mut pos = InsertPosition::default();
135        pos.inc_col();
136        pos.inc_line();
137
138        let text_repr = format!("{}", pos);
139        assert_eq!(text_repr, "2:0");
140    }
141
142    #[test]
143    fn debug_print() {
144        let mut pos = InsertPosition::default();
145        pos.inc_col();
146        pos.inc_line();
147
148        let text_repr = format!("{:?}", pos);
149        assert_eq!(text_repr, "2:0");
150    }
151
152    #[test]
153    fn ordering_and_eq() {
154        let mut pos1 = InsertPosition::default();
155        pos1.inc_col();
156        pos1.inc_line();
157        pos1.inc_col();
158
159        let mut pos2 = pos1.clone();
160        pos2.inc_line();
161
162        let pos3 = pos2.clone();
163
164        assert!(pos2 > pos1);
165        assert_eq!(pos2, pos3);
166    }
167
168    #[test]
169    fn parsing() {
170        assert_eq!("4:7".parse(), Ok(InsertPosition::new(4, 7)));
171        assert_eq!("1:0".parse(), Ok(InsertPosition::new(1, 0)));
172        assert_eq!("1:-9".parse::<InsertPosition>(), Err(()));
173        assert_eq!("0:6".parse::<InsertPosition>(), Err(()));
174    }
175}