nimbus_fml/editing/
cursor_position.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2* License, v. 2.0. If a copy of the MPL was not distributed with this
3* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use std::ops::Add;
6
7use unicode_segmentation::UnicodeSegmentation;
8
9#[derive(Clone, Debug, Default, PartialEq)]
10pub struct CursorPosition {
11    pub line: u32,
12    pub col: u32,
13}
14
15#[allow(dead_code)]
16impl CursorPosition {
17    pub(crate) fn new(line: usize, col: usize) -> Self {
18        Self {
19            line: line as u32,
20            col: col as u32,
21        }
22    }
23}
24
25impl From<(usize, usize)> for CursorPosition {
26    fn from((line, col): (usize, usize)) -> Self {
27        Self::new(line, col)
28    }
29}
30
31/// CursorSpan is used to for reporting errors and defining corrections.
32/// This is passed across the FFI and used by the editor.
33#[derive(Clone, Debug, Default, PartialEq)]
34pub struct CursorSpan {
35    pub from: CursorPosition,
36    pub to: CursorPosition,
37}
38
39/// CursorPosition + &str -> CursorSpan.
40/// This is a convenient way of making CursorSpans.
41impl Add<&str> for CursorPosition {
42    type Output = CursorSpan;
43
44    fn add(self, rhs: &str) -> Self::Output {
45        let mut line_count = 0;
46        let mut last_line = None;
47
48        for line in rhs.lines() {
49            line_count += 1;
50            last_line = Some(line);
51        }
52
53        if rhs.ends_with('\n') {
54            line_count += 1;
55            last_line = None;
56        }
57
58        let last_line_length = match last_line {
59            Some(line) => UnicodeSegmentation::graphemes(line, true).count(),
60            None => 0,
61        } as u32;
62
63        let to = match line_count {
64            0 => self.clone(),
65            1 => CursorPosition {
66                line: self.line,
67                col: self.col + last_line_length,
68            },
69            _ => CursorPosition {
70                line: self.line + line_count - 1,
71                col: last_line_length,
72            },
73        };
74
75        Self::Output { from: self, to }
76    }
77}
78
79impl Add<CursorSpan> for CursorPosition {
80    type Output = CursorSpan;
81
82    fn add(self, rhs: CursorSpan) -> Self::Output {
83        Self::Output {
84            from: self,
85            to: rhs.to,
86        }
87    }
88}
89
90#[cfg(test)]
91mod unit_tests {
92    use super::*;
93
94    #[test]
95    fn test_add_str() {
96        // start at (10, 10) so we can check that things are zeroing properly.
97        let from = CursorPosition::new(10, 10);
98
99        assert_eq!(CursorPosition::new(10, 10), (from.clone() + "").to);
100        assert_eq!(CursorPosition::new(10, 11), (from.clone() + "0").to);
101        assert_eq!(CursorPosition::new(10, 12), (from.clone() + "01").to);
102
103        assert_eq!(CursorPosition::new(11, 1), (from.clone() + "\n0").to);
104        assert_eq!(CursorPosition::new(12, 1), (from.clone() + "\n\n0").to);
105
106        assert_eq!(CursorPosition::new(11, 0), (from.clone() + "\n").to);
107        assert_eq!(CursorPosition::new(12, 0), (from.clone() + "\n\n").to);
108    }
109}