ad_editor/dot/
cur.rs

1use crate::{buffer::Buffer, key::Arrow};
2use std::cmp::min;
3
4#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
5pub struct Cur {
6    pub idx: usize,
7}
8
9impl Cur {
10    pub fn new(idx: usize) -> Self {
11        Self { idx }
12    }
13
14    pub fn buffer_start() -> Self {
15        Cur { idx: 0 }
16    }
17
18    pub fn buffer_end(b: &Buffer) -> Self {
19        Cur {
20            idx: b.txt.len_chars(),
21        }
22    }
23
24    pub fn as_string_addr(&self, b: &Buffer) -> String {
25        let (y, x) = self.as_yx(b);
26        format!("{}:{}", y + 1, x + 1)
27    }
28
29    pub(crate) fn as_yx(&self, b: &Buffer) -> (usize, usize) {
30        let y = b.txt.char_to_line(self.idx);
31        let x = self.idx - b.txt.line_to_char(y);
32
33        (y, x)
34    }
35
36    pub(crate) fn from_yx(y_idx: usize, x_idx: usize, b: &Buffer) -> Self {
37        let line_start = b.txt.line_to_char(y_idx);
38        let mut x_max = b.txt.line_len_chars(y_idx);
39        if y_idx < b.len_lines() - 1 {
40            x_max -= 1;
41        }
42
43        Self {
44            idx: line_start + min(x_idx, x_max),
45        }
46    }
47
48    pub(super) fn arr_w_count(&self, arr: Arrow, count: usize, b: &Buffer) -> Self {
49        let mut cur = *self;
50
51        for _ in 0..count {
52            cur = cur.arr(arr, b);
53        }
54
55        cur.clamp_idx(b.txt.len_chars());
56        cur
57    }
58
59    pub(super) fn arr(&self, arr: Arrow, b: &Buffer) -> Self {
60        let mut cur = *self;
61
62        match arr {
63            Arrow::Up => {
64                let (mut y, _) = self.as_yx(b);
65                if y != 0 {
66                    y -= 1;
67                    cur.idx = b.txt.line_to_char(y) + b.x_from_rx(y);
68                }
69            }
70            Arrow::Down => {
71                let (mut y, _) = self.as_yx(b);
72                if !b.is_empty() && y < b.len_lines() - 1 {
73                    y += 1;
74                    cur.idx = b.txt.line_to_char(y) + b.x_from_rx(y);
75                }
76            }
77            Arrow::Left => cur.idx = cur.idx.saturating_sub(1),
78            Arrow::Right => cur.idx = min(cur.idx + 1, b.txt.len_chars()),
79        }
80
81        cur
82    }
83
84    pub(crate) fn clamp_idx(&mut self, max_idx: usize) {
85        self.idx = min(self.idx, max_idx);
86    }
87
88    #[must_use]
89    pub(super) fn move_to_line_start(mut self, b: &Buffer) -> Self {
90        let y = b.txt.char_to_line(self.idx);
91        self.idx = b.txt.line_to_char(y);
92        self
93    }
94
95    #[must_use]
96    pub(super) fn move_to_line_end(mut self, b: &Buffer) -> Self {
97        let y = b.txt.char_to_line(self.idx);
98        self.idx = b.txt.line_to_char(y) + b.txt.line_len_chars(y);
99
100        // On the last line of the file we don't have a trailing newline
101        // so we really do want the final character
102        if y < b.len_lines().saturating_sub(1) {
103            self.idx = self.idx.saturating_sub(1);
104        }
105
106        self
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113    use crate::buffer::tests::buffer_from_lines;
114
115    #[test]
116    fn from_yx_focuses_eol() {
117        let lines = &["a", "ab", "abc", "abcd"];
118        let b = buffer_from_lines(lines);
119
120        for (n, s) in lines.iter().enumerate() {
121            let c = Cur::from_yx(n, 100, &b);
122
123            // Should be focused on the newline character at the end of the line
124            // In the case of the last line we should be focused on the index after
125            // the end of the string so that we are appending to the buffer.
126            assert_eq!(c.as_yx(&b), (n, s.len()), "line='{s}'");
127        }
128    }
129
130    #[test]
131    fn arr_right_at_eof_focuses_eof() {
132        let lines = &["a", "ab", "abc", "abcd"];
133        let b = buffer_from_lines(lines);
134        let c = Cur::buffer_end(&b);
135        assert_eq!(c.as_yx(&b), (3, 4));
136
137        let final_c = c.arr(Arrow::Right, &b);
138        assert_eq!(final_c.as_yx(&b), (3, 4));
139    }
140
141    #[test]
142    fn arr_right_at_last_char_focuses_eof() {
143        let lines = &["a", "ab", "abc", "abcd"];
144        let b = buffer_from_lines(lines);
145        let mut c = Cur::buffer_end(&b);
146        c.idx -= 1;
147
148        assert_eq!(c.as_yx(&b), (3, 3));
149
150        let mut final_c = c.arr(Arrow::Right, &b);
151        final_c.clamp_idx(b.txt.len_chars());
152        assert_eq!(final_c.as_yx(&b), (3, 4));
153    }
154}