revi_core/
buffer.rs

1use ropey::Rope;
2use std::fs::OpenOptions;
3use std::io::BufReader;
4use std::ops::Range;
5use unicode_width::UnicodeWidthStr;
6
7use crate::position::Position;
8
9pub const JUMP_MATCHES: [char; 100] = make_matches();
10
11const fn make_matches() -> [char; 100] {
12    [
13        '^', '@', '|', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/',
14        '[', ']', '{', '`', '}', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<',
15        '=', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
16        'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y',
17        'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
18        'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ',
19    ]
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum CharType {
24    Letter,
25    Punctuation,
26    Number,
27    NewLine,
28    WhiteSpace,
29    None,
30}
31impl From<char> for CharType {
32    fn from(c: char) -> Self {
33        match c {
34            _ if c.is_ascii_alphabetic() => Self::Letter,
35            _ if c.is_ascii_punctuation() => Self::Punctuation,
36            _ if c.is_ascii_digit() => Self::Number,
37            _ if c.is_ascii_whitespace() => Self::WhiteSpace,
38            '\n' => Self::NewLine,
39            _ => Self::None,
40        }
41    }
42}
43
44impl From<&str> for CharType {
45    fn from(s: &str) -> Self {
46        match s.parse::<char>().ok() {
47            Some(c) => Self::from(c),
48            None => Self::None,
49        }
50    }
51}
52
53#[derive(Debug, Default, Clone, PartialEq, Eq)]
54pub struct Buffer {
55    inner: Rope,
56    name: Option<String>,
57}
58
59impl Buffer {
60    /// Creates a new empty buffer with no name.
61    #[must_use]
62    pub fn new() -> Self {
63        Self {
64            inner: Rope::new(),
65            name: None,
66        }
67    }
68
69    /// Creates a new buffer from a string path of file.
70    #[must_use]
71    pub fn from_path(filename: &str) -> Self {
72        let rope = from_path(filename);
73        Self {
74            inner: rope,
75            name: Some(filename.to_string()),
76        }
77    }
78
79    /// Creates a new buffer from string without a name.
80    #[must_use]
81    pub fn new_str(text: &str) -> Self {
82        Self {
83            inner: Rope::from(text),
84            name: None,
85        }
86    }
87
88    #[must_use]
89    pub fn name(&self) -> &Option<String> {
90        &self.name
91    }
92
93    #[must_use]
94    pub fn idx_of_position(&self, pos: &Position) -> usize {
95        self.inner.line_to_char(pos.as_usize_y()) + pos.as_usize_x()
96    }
97
98    #[must_use]
99    pub fn char_at_pos(&self, pos: &Position) -> char {
100        self.inner.char(self.idx_of_position(pos))
101    }
102
103    #[must_use]
104    pub fn line(&self, y: usize) -> String {
105        self.inner.line(y).chars().collect::<String>()
106    }
107
108    #[must_use]
109    pub fn line_len(&self, y: usize) -> usize {
110        let line = self.inner.line(y).chars().collect::<String>();
111        UnicodeWidthStr::width(line.as_str())
112    }
113
114    #[must_use]
115    pub fn len_chars(&self) -> usize {
116        self.inner.len_chars()
117    }
118
119    #[must_use]
120    pub fn len_lines(&self) -> usize {
121        self.inner.len_lines().saturating_sub(2)
122    }
123
124    #[must_use]
125    pub fn line_to_char(&self, line: usize) -> usize {
126        self.inner.line_to_char(line)
127    }
128
129    #[must_use]
130    pub fn char_to_line(&self, line: usize) -> usize {
131        self.inner.char_to_line(line)
132    }
133
134    pub fn insert_char(&mut self, idx: usize, c: char) {
135        self.inner.insert_char(idx, c);
136    }
137
138    pub fn remove(&mut self, range: Range<usize>) {
139        self.inner.remove(range);
140    }
141
142    #[must_use]
143    pub fn contents(&self) -> String {
144        self.inner.chars().collect::<String>()
145    }
146
147    pub fn clear(&mut self) {
148        self.inner = Rope::new();
149    }
150
151    #[must_use]
152    pub fn on_screen(&self, top: usize, bottom: usize) -> String {
153        let top_line = top;
154        let bottom_line = bottom;
155        self.inner
156            .lines_at(top_line)
157            .enumerate()
158            .filter(|(i, _)| *i < bottom_line)
159            .map(|(_, s)| s.to_string())
160            .collect::<String>()
161    }
162
163    pub fn write_to<T: std::io::Write>(&self, writer: T) -> std::io::Result<()> {
164        self.inner.write_to(writer)?;
165        Ok(())
166    }
167
168    #[must_use]
169    pub fn next_jump_idx(&self, pos: &Position) -> Option<usize> {
170        // TODO: Fix this God awful garbage!!!!!!!!!!!
171        let (x, y) = pos.as_usize();
172        let result: Vec<(usize, CharType)> = self.line(y).as_str()[x..]
173            .match_indices(&JUMP_MATCHES[..])
174            .map(|(i, c)| (i, c.into()))
175            .collect();
176        let possible_jumps = word_indices(&result);
177        possible_jumps
178            .get(1)
179            .and_then(|w| w.first())
180            .map(|(i, _)| *i + x)
181    }
182
183    #[must_use]
184    pub fn prev_jump_idx(&self, pos: &Position) -> Option<usize> {
185        // TODO: Fix this God awful garbage!!!!!!!!!!!
186        let (x, y) = pos.as_usize();
187        let result: Vec<(usize, CharType)> = self.line(y).as_str()[..x]
188            .rmatch_indices(&JUMP_MATCHES[..])
189            .map(|(i, c)| (i, c.into()))
190            .collect();
191        let possible_jumps = word_indices(&result);
192        let idx = possible_jumps
193            .get(0)
194            .and_then(|w| w.last())
195            .map_or(false, |(_, i)| i == &CharType::WhiteSpace) as usize;
196        possible_jumps
197            .get(idx)
198            .and_then(|w| w.last())
199            .map(|(i, _)| *i)
200    }
201}
202
203#[must_use]
204pub fn from_path(path: &str) -> Rope {
205    let file = OpenOptions::new()
206        .read(true)
207        .write(true)
208        .create(true)
209        .open(path)
210        .expect("Problem opening the file");
211
212    Rope::from_reader(BufReader::new(file)).expect("Failed to open file.")
213}
214
215fn word_indices(items: &[(usize, CharType)]) -> Vec<Vec<(usize, CharType)>> {
216    // TODO: Fix this God awful garbage!!!!!!!!!!!
217    let mut stream = items.iter().peekable();
218    let mut word_loc = Vec::new();
219    if let Some(f) = stream.next() {
220        word_loc.push(vec![*f]);
221    } else {
222        return word_loc;
223    }
224
225    while let Some(current) = stream.next() {
226        if current.1 == CharType::WhiteSpace {
227            if let Some(f) = word_loc.last() {
228                if !f.is_empty() {
229                    word_loc.push(Vec::new());
230                }
231            }
232            continue;
233        }
234        if let Some(last_word) = word_loc.last_mut() {
235            if let Some(last_char) = last_word.last() {
236                if current.1 != last_char.1 {
237                    word_loc.push(vec![*current]);
238                } else if let Some(next) = stream.peek() {
239                    if next.1 != current.1 {
240                        last_word.push(*current);
241                    }
242                } else {
243                    last_word.push(*current);
244                }
245            } else {
246                last_word.push(*current);
247            }
248        }
249    }
250    word_loc
251}
252
253#[test]
254fn test_buffer_len() {
255    use ropey::Rope;
256    let rope = Rope::from("0\n1\n2\n3\n4\n5\n"); // 7 lines
257    assert_eq!(rope.len_lines(), 7);
258}