miv_editor/app/editor/
mod.rs

1pub mod filetypes;
2pub mod gap_buffer;
3pub mod highlighting;
4pub mod motions;
5
6use std::fmt::{self, Debug};
7use std::fs::write;
8use std::{env, fs, path::PathBuf};
9
10use tracing::info;
11use tree_sitter_highlight::{HighlightConfiguration, Highlighter};
12
13use self::highlighting::HighlightSpan;
14use self::motions::Motion;
15use self::{
16    filetypes::FileType,
17    gap_buffer::GapBuffer,
18    highlighting::{get_highlighting_config, get_highlighting_function, HighlightingFn},
19};
20
21use super::{AppResult, InputMode};
22
23pub struct EditorBuffer {
24    /// Position of cursor in the editor area.
25    pub cursor_index: usize,
26    /// Current cursor line
27    pub cursor_line: usize,
28    /// Current cursor column
29    pub cursor_col: usize,
30    /// Gap buffer storing text
31    pub gap_buffer: GapBuffer,
32    /// Gap buffer storing text
33    pub path: Option<PathBuf>,
34    pub filetype: FileType,
35    /// Highlighting utilities
36    pub highlighter: Highlighter,
37    pub highlighter_config: HighlightConfiguration,
38    pub highlight_groups: Vec<HighlightSpan>,
39    highlighting_function: HighlightingFn,
40}
41
42impl Debug for EditorBuffer {
43    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44        f.debug_struct("EditorBuffer")
45            .field("cursor_index", &self.cursor_index)
46            .field("gap_buffer", &self.gap_buffer)
47            .field("path", &self.path)
48            .field("filetype", &self.filetype)
49            .finish()
50    }
51}
52
53impl Default for EditorBuffer {
54    fn default() -> Self {
55        let highlighter = Highlighter::new();
56        let highlighter_config = get_highlighting_config(FileType::Rust);
57        let highlighter_function = get_highlighting_function(FileType::Rust);
58
59        EditorBuffer {
60            cursor_index: 0,
61            cursor_line: 0,
62            cursor_col: 0,
63            gap_buffer: GapBuffer::with_data(""),
64            path: None,
65            filetype: FileType::Rust,
66            highlighter,
67            highlighter_config,
68            highlight_groups: vec![],
69            highlighting_function: highlighter_function,
70        }
71    }
72}
73
74impl EditorBuffer {
75    pub fn from_file(file: String) -> Self {
76        let cwd_path = env::current_dir().unwrap();
77        let full_path = cwd_path.join(file);
78        let source =
79            fs::read_to_string(&full_path).expect("Should have been able to read the file");
80
81        let mut eb = EditorBuffer {
82            gap_buffer: GapBuffer::with_data(&source),
83            path: Some(full_path),
84            ..Self::default()
85        };
86        eb.calculate_highlights();
87        eb
88    }
89
90    pub fn save(&self) -> AppResult<()> {
91        if let Some(path) = &self.path {
92            write(path, self.gap_buffer.get_text_as_string())?;
93            return Ok(());
94        }
95        Err("Failed to save".into())
96    }
97
98    pub fn insert(&mut self, to_insert: String, mode: InputMode) {
99        self.gap_buffer.insert_at(&to_insert, self.cursor_index);
100
101        self.move_cursor(&Motion::CharForward, mode);
102        self.calculate_highlights();
103    }
104
105    pub fn move_cursor(&mut self, motion: &Motion, mode: InputMode) {
106        match motion {
107            Motion::CharForward => self.move_forward_char(),
108            Motion::CharBackward => self.move_backward_char(),
109            Motion::NextWordStart => self.move_forward_to_word_start(),
110            Motion::NextWordProperStart => self.move_forward_to_word_proper_start(),
111            Motion::NextWordEnd => self.move_forward_to_word_end(),
112            Motion::NextWordProperEnd => self.move_forward_to_word_proper_end(),
113            Motion::LastWordStart => self.move_backward_to_word_start(),
114            Motion::LastWordProperStart => self.move_backward_to_word_proper_start(),
115            Motion::LastWordEnd => self.move_backward_to_word_end(),
116            Motion::LastWordProperEnd => self.move_backward_to_word_proper_end(),
117            Motion::LineStart => self.move_to_line_start(),
118            Motion::LineEnd => self.move_to_line_end(),
119            Motion::NextLineStart => self.move_to_next_line_start(),
120        }
121
122        let final_char = self.gap_buffer.get_at(self.cursor_index);
123        if let InputMode::Normal = mode {
124            if final_char == '\n' {
125                self.cursor_index -= 1
126            }
127        };
128    }
129
130    pub fn delete(&mut self, motion: Motion, mode: InputMode) {
131        let delete_start = self.cursor_index;
132        self.move_cursor(&motion, mode);
133        let candidate_char = self.gap_buffer.get_at(self.cursor_index);
134        info!("{:?}", candidate_char);
135        let delete_end = self.cursor_index;
136        let (amount_to_delete, at) = if delete_end < delete_start {
137            (delete_start - delete_end, delete_end)
138        } else {
139            (delete_end - delete_start, delete_start)
140        };
141
142        self.gap_buffer.delete_at(amount_to_delete, at);
143        self.cursor_index = at;
144        self.calculate_highlights();
145    }
146
147    pub fn calculate_highlights(&mut self) {
148        let content = self.gap_buffer.get_text_as_bytes();
149        self.highlight_groups =
150            (self.highlighting_function)(&content, &mut self.highlighter, &self.highlighter_config);
151    }
152
153    fn move_forward_char(&mut self) {
154        let candidate_index = self.cursor_index + 1;
155
156        let data_length = self.gap_buffer.data_length();
157
158        if candidate_index >= data_length {
159            self.cursor_index = data_length
160        } else {
161            self.cursor_index = candidate_index
162        };
163    }
164
165    fn move_backward_char(&mut self) {
166        let candidate_index = self.cursor_index.saturating_sub(1);
167
168        self.cursor_index = candidate_index;
169    }
170
171    fn move_forward_to_word_start(&mut self) {
172        let start_char = self.gap_buffer.get_at(self.cursor_index);
173        let start_is_alphanumeric = start_char.is_alphanumeric();
174
175        let mut whitspace_seen = start_char.is_whitespace();
176        let mut candidate_index = self.cursor_index + 1;
177        let data_length = self.gap_buffer.data_length();
178
179        while candidate_index < data_length {
180            let candidate_char = self.gap_buffer.get_at(candidate_index);
181            if !candidate_char.is_whitespace() {
182                if whitspace_seen {
183                    self.cursor_index = candidate_index;
184                    return;
185                };
186
187                if start_is_alphanumeric && !candidate_char.is_alphanumeric() {
188                    self.cursor_index = candidate_index;
189                    return;
190                };
191
192                if !start_is_alphanumeric && candidate_char.is_alphanumeric() {
193                    self.cursor_index = candidate_index;
194                    return;
195                };
196            } else {
197                whitspace_seen = true;
198            }
199
200            candidate_index += 1
201        }
202
203        self.cursor_index = data_length - 1;
204    }
205
206    fn move_forward_to_word_proper_start(&mut self) {
207        let mut whitspace_seen = self.gap_buffer.get_at(self.cursor_index).is_whitespace();
208        let mut candidate_index = self.cursor_index + 1;
209        let data_length = self.gap_buffer.data_length();
210
211        while candidate_index < data_length {
212            let candidate_char = self.gap_buffer.get_at(candidate_index);
213
214            if whitspace_seen && !candidate_char.is_whitespace() {
215                self.cursor_index = candidate_index;
216                return;
217            };
218
219            if candidate_char.is_whitespace() {
220                whitspace_seen = true;
221            };
222
223            candidate_index += 1
224        }
225
226        self.cursor_index = data_length - 1;
227    }
228
229    fn move_forward_to_word_end(&mut self) {
230        let mut candidate_index = self.cursor_index + 1;
231        let data_length = self.gap_buffer.data_length();
232
233        while candidate_index < data_length - 1 {
234            let candidate_char = self.gap_buffer.get_at(candidate_index);
235            let candidate_char_is_alphanumeric = candidate_char.is_alphanumeric();
236            let next_index = candidate_index + 1;
237            let next_char = self.gap_buffer.get_at(next_index);
238
239            if !candidate_char.is_whitespace()
240                && ((candidate_char_is_alphanumeric ^ next_char.is_alphanumeric())
241                    || next_char.is_whitespace())
242            {
243                self.cursor_index = candidate_index;
244                return;
245            }
246            candidate_index += 1
247        }
248
249        self.cursor_index = data_length - 1;
250    }
251
252    fn move_forward_to_word_proper_end(&mut self) {
253        let mut candidate_index = self.cursor_index + 1;
254        let data_length = self.gap_buffer.data_length();
255
256        while candidate_index < data_length - 1 {
257            let candidate_char = self.gap_buffer.get_at(candidate_index);
258            let next_index = candidate_index + 1;
259            let next_char = self.gap_buffer.get_at(next_index);
260
261            if next_char.is_whitespace() && !candidate_char.is_whitespace() {
262                self.cursor_index = candidate_index;
263                return;
264            };
265
266            candidate_index += 1
267        }
268
269        self.cursor_index = data_length - 1;
270    }
271
272    fn move_backward_to_word_start(&mut self) {
273        let mut candidate_index = self.cursor_index.saturating_sub(1);
274        while candidate_index > 0 {
275            let candidate_char = self.gap_buffer.get_at(candidate_index);
276            let candidate_char_is_alphanumeric = candidate_char.is_alphanumeric();
277            let next_index = candidate_index - 1;
278            let next_char = self.gap_buffer.get_at(next_index);
279
280            if !candidate_char.is_whitespace()
281                && ((candidate_char_is_alphanumeric ^ next_char.is_alphanumeric())
282                    || next_char.is_whitespace())
283            {
284                self.cursor_index = candidate_index;
285                return;
286            }
287            candidate_index -= 1
288        }
289
290        self.cursor_index = 0;
291    }
292
293    fn move_backward_to_word_proper_start(&mut self) {
294        let mut candidate_index = self.cursor_index.saturating_sub(1);
295
296        while candidate_index > 0 {
297            let candidate_char = self.gap_buffer.get_at(candidate_index);
298            let next_index = candidate_index - 1;
299            let next_char = self.gap_buffer.get_at(next_index);
300
301            if next_char.is_whitespace() && !candidate_char.is_whitespace() {
302                self.cursor_index = candidate_index;
303                return;
304            };
305
306            candidate_index -= 1
307        }
308
309        self.cursor_index = 0;
310    }
311
312    fn move_backward_to_word_end(&mut self) {
313        let start_char = self.gap_buffer.get_at(self.cursor_index);
314        let start_is_alphanumeric = start_char.is_alphanumeric();
315
316        let mut whitspace_seen = start_char.is_whitespace();
317        let mut candidate_index = self.cursor_index - 1;
318
319        while candidate_index > 0 {
320            let candidate_char = self.gap_buffer.get_at(candidate_index);
321            if !candidate_char.is_whitespace() {
322                if whitspace_seen {
323                    self.cursor_index = candidate_index;
324                    return;
325                };
326
327                if start_is_alphanumeric && !candidate_char.is_alphanumeric() {
328                    self.cursor_index = candidate_index;
329                    return;
330                };
331
332                if !start_is_alphanumeric && candidate_char.is_alphanumeric() {
333                    self.cursor_index = candidate_index;
334                    return;
335                };
336            } else {
337                whitspace_seen = true;
338            }
339
340            candidate_index -= 1
341        }
342
343        self.cursor_index = 0;
344    }
345
346    fn move_backward_to_word_proper_end(&mut self) {
347        let mut whitspace_seen = self.gap_buffer.get_at(self.cursor_index).is_whitespace();
348        let mut candidate_index = self.cursor_index - 1;
349
350        while candidate_index > 0 {
351            let candidate_char = self.gap_buffer.get_at(candidate_index);
352
353            if whitspace_seen && !candidate_char.is_whitespace() {
354                self.cursor_index = candidate_index;
355                return;
356            };
357
358            if candidate_char.is_whitespace() {
359                whitspace_seen = true;
360            };
361
362            candidate_index -= 1
363        }
364
365        self.cursor_index = 0;
366    }
367
368    fn move_to_line_start(&mut self) {
369        let mut candidate_index = self.cursor_index.saturating_sub(1);
370        while candidate_index > 0 {
371            let candidate_char = self.gap_buffer.get_at(candidate_index);
372            if candidate_char == '\n' {
373                self.cursor_index = candidate_index + 1;
374                return;
375            }
376            candidate_index -= 1
377        }
378
379        self.cursor_index = 0;
380    }
381
382    fn move_to_next_line_start(&mut self) {
383        let mut candidate_index = self.cursor_index + 1;
384        let data_length = self.gap_buffer.data_length();
385
386        while candidate_index < data_length {
387            let candidate_char = self.gap_buffer.get_at(candidate_index);
388            if candidate_char == '\n' {
389                self.cursor_index = candidate_index + 1;
390                return;
391            }
392            candidate_index += 1
393        }
394
395        self.cursor_index = data_length - 1;
396    }
397
398    fn move_to_line_end(&mut self) {
399        let mut candidate_index = self.cursor_index + 1;
400        let data_length = self.gap_buffer.data_length();
401
402        while candidate_index < data_length {
403            let candidate_char = self.gap_buffer.get_at(candidate_index);
404            if candidate_char == '\n' {
405                self.cursor_index = candidate_index;
406                return;
407            }
408            candidate_index += 1
409        }
410
411        self.cursor_index = data_length - 1;
412    }
413}