nimbus_fml/editing/
cursor_position.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use std::ops::Add;

use unicode_segmentation::UnicodeSegmentation;

#[derive(Clone, Debug, Default, PartialEq)]
pub struct CursorPosition {
    pub line: u32,
    pub col: u32,
}

#[allow(dead_code)]
impl CursorPosition {
    pub(crate) fn new(line: usize, col: usize) -> Self {
        Self {
            line: line as u32,
            col: col as u32,
        }
    }
}

impl From<(usize, usize)> for CursorPosition {
    fn from((line, col): (usize, usize)) -> Self {
        Self::new(line, col)
    }
}

/// CursorSpan is used to for reporting errors and defining corrections.
/// This is passed across the FFI and used by the editor.
#[derive(Clone, Debug, Default, PartialEq)]
pub struct CursorSpan {
    pub from: CursorPosition,
    pub to: CursorPosition,
}

/// CursorPosition + &str -> CursorSpan.
/// This is a convenient way of making CursorSpans.
impl Add<&str> for CursorPosition {
    type Output = CursorSpan;

    fn add(self, rhs: &str) -> Self::Output {
        let mut line_count = 0;
        let mut last_line = None;

        for line in rhs.lines() {
            line_count += 1;
            last_line = Some(line);
        }

        if rhs.ends_with('\n') {
            line_count += 1;
            last_line = None;
        }

        let last_line_length = match last_line {
            Some(line) => UnicodeSegmentation::graphemes(line, true).count(),
            None => 0,
        } as u32;

        let to = match line_count {
            0 => self.clone(),
            1 => CursorPosition {
                line: self.line,
                col: self.col + last_line_length,
            },
            _ => CursorPosition {
                line: self.line + line_count - 1,
                col: last_line_length,
            },
        };

        Self::Output { from: self, to }
    }
}

impl Add<CursorSpan> for CursorPosition {
    type Output = CursorSpan;

    fn add(self, rhs: CursorSpan) -> Self::Output {
        Self::Output {
            from: self,
            to: rhs.to,
        }
    }
}

#[cfg(test)]
mod unit_tests {
    use super::*;

    #[test]
    fn test_add_str() {
        // start at (10, 10) so we can check that things are zeroing properly.
        let from = CursorPosition::new(10, 10);

        assert_eq!(CursorPosition::new(10, 10), (from.clone() + "").to);
        assert_eq!(CursorPosition::new(10, 11), (from.clone() + "0").to);
        assert_eq!(CursorPosition::new(10, 12), (from.clone() + "01").to);

        assert_eq!(CursorPosition::new(11, 1), (from.clone() + "\n0").to);
        assert_eq!(CursorPosition::new(12, 1), (from.clone() + "\n\n0").to);

        assert_eq!(CursorPosition::new(11, 0), (from.clone() + "\n").to);
        assert_eq!(CursorPosition::new(12, 0), (from.clone() + "\n\n").to);
    }
}