freya_edit/
rope_editor.rs

1use std::{
2    cmp::Ordering,
3    fmt::Display,
4    ops::Range,
5};
6
7use ropey::{
8    Rope,
9    iter::Lines,
10};
11use unicode_segmentation::UnicodeSegmentation;
12
13use crate::{
14    editor_history::{
15        EditorHistory,
16        HistoryChange,
17    },
18    mode::EditableMode,
19    text_editor::{
20        Line,
21        TextCursor,
22        TextEditor,
23    },
24};
25
26/// TextEditor implementing a Rope
27pub struct RopeEditor {
28    pub(crate) rope: Rope,
29    pub(crate) cursor: TextCursor,
30    pub(crate) identation: u8,
31    pub(crate) mode: EditableMode,
32    pub(crate) selected: Option<(usize, usize)>,
33    pub(crate) history: EditorHistory,
34}
35
36impl Display for RopeEditor {
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38        f.write_str(&self.rope.to_string())
39    }
40}
41
42impl RopeEditor {
43    // Create a new [`RopeEditor`]
44    pub fn new(
45        text: String,
46        cursor: TextCursor,
47        identation: u8,
48        mode: EditableMode,
49        history: EditorHistory,
50    ) -> Self {
51        Self {
52            rope: Rope::from_str(&text),
53            cursor,
54            identation,
55            selected: None,
56            mode,
57            history,
58        }
59    }
60
61    pub fn rope(&self) -> &Rope {
62        &self.rope
63    }
64}
65
66impl TextEditor for RopeEditor {
67    type LinesIterator<'a> = LinesIterator<'a>;
68
69    fn lines(&self) -> Self::LinesIterator<'_> {
70        let lines = self.rope.lines();
71        LinesIterator { lines }
72    }
73
74    fn insert_char(&mut self, ch: char, idx: usize) -> usize {
75        let idx_utf8 = self.utf16_cu_to_char(idx);
76
77        let len_before_insert = self.rope.len_utf16_cu();
78        self.rope.insert_char(idx_utf8, ch);
79        let len_after_insert = self.rope.len_utf16_cu();
80
81        let inserted_text_len = len_after_insert - len_before_insert;
82
83        self.history.push_change(HistoryChange::InsertChar {
84            idx,
85            ch,
86            len: inserted_text_len,
87        });
88
89        inserted_text_len
90    }
91
92    fn insert(&mut self, text: &str, idx: usize) -> usize {
93        let idx_utf8 = self.utf16_cu_to_char(idx);
94
95        let len_before_insert = self.rope.len_utf16_cu();
96        self.rope.insert(idx_utf8, text);
97        let len_after_insert = self.rope.len_utf16_cu();
98
99        let inserted_text_len = len_after_insert - len_before_insert;
100
101        self.history.push_change(HistoryChange::InsertText {
102            idx,
103            text: text.to_owned(),
104            len: inserted_text_len,
105        });
106
107        inserted_text_len
108    }
109
110    fn remove(&mut self, range_utf16: Range<usize>) -> usize {
111        let range =
112            self.utf16_cu_to_char(range_utf16.start)..self.utf16_cu_to_char(range_utf16.end);
113        let text = self.rope.slice(range.clone()).to_string();
114
115        let len_before_remove = self.rope.len_utf16_cu();
116        self.rope.remove(range);
117        let len_after_remove = self.rope.len_utf16_cu();
118
119        let removed_text_len = len_before_remove - len_after_remove;
120
121        self.history.push_change(HistoryChange::Remove {
122            idx: range_utf16.end - removed_text_len,
123            text,
124            len: removed_text_len,
125        });
126
127        removed_text_len
128    }
129
130    fn char_to_line(&self, char_idx: usize) -> usize {
131        self.rope.char_to_line(char_idx)
132    }
133
134    fn line_to_char(&self, line_idx: usize) -> usize {
135        self.rope.line_to_char(line_idx)
136    }
137
138    fn utf16_cu_to_char(&self, utf16_cu_idx: usize) -> usize {
139        self.rope.utf16_cu_to_char(utf16_cu_idx)
140    }
141
142    fn char_to_utf16_cu(&self, idx: usize) -> usize {
143        self.rope.char_to_utf16_cu(idx)
144    }
145
146    fn line(&self, line_idx: usize) -> Option<Line<'_>> {
147        let line = self.rope.get_line(line_idx);
148
149        line.map(|line| Line {
150            text: line.into(),
151            utf16_len: line.len_utf16_cu(),
152        })
153    }
154
155    fn len_lines(&self) -> usize {
156        self.rope.len_lines()
157    }
158
159    fn len_chars(&self) -> usize {
160        self.rope.len_chars()
161    }
162
163    fn len_utf16_cu(&self) -> usize {
164        self.rope.len_utf16_cu()
165    }
166
167    fn cursor(&self) -> &TextCursor {
168        &self.cursor
169    }
170
171    fn cursor_mut(&mut self) -> &mut TextCursor {
172        &mut self.cursor
173    }
174
175    fn expand_selection_to_cursor(&mut self) {
176        let pos = self.cursor_pos();
177        if let Some(selected) = self.selected.as_mut() {
178            selected.1 = pos;
179        } else {
180            self.selected = Some((self.cursor_pos(), self.cursor_pos()))
181        }
182    }
183
184    fn has_any_selection(&self) -> bool {
185        self.selected.is_some()
186    }
187
188    fn get_selection(&self) -> Option<(usize, usize)> {
189        self.selected
190    }
191
192    fn get_visible_selection(&self, editor_id: usize) -> Option<(usize, usize)> {
193        let (selected_from, selected_to) = self.selected?;
194
195        if self.mode == EditableMode::SingleLineMultipleEditors {
196            let selected_from_row = self.char_to_line(self.utf16_cu_to_char(selected_from));
197            let selected_to_row = self.char_to_line(self.utf16_cu_to_char(selected_to));
198
199            let editor_row_idx = self.char_to_utf16_cu(self.line_to_char(editor_id));
200            let selected_from_row_idx = self.char_to_utf16_cu(self.line_to_char(selected_from_row));
201            let selected_to_row_idx = self.char_to_utf16_cu(self.line_to_char(selected_to_row));
202
203            let selected_from_col_idx = selected_from - selected_from_row_idx;
204            let selected_to_col_idx = selected_to - selected_to_row_idx;
205
206            // Between starting line and endling line
207            if (editor_id > selected_from_row && editor_id < selected_to_row)
208                || (editor_id < selected_from_row && editor_id > selected_to_row)
209            {
210                let len = self.line(editor_id).unwrap().utf16_len();
211                return Some((0, len));
212            }
213
214            match selected_from_row.cmp(&selected_to_row) {
215                // Selection direction is from bottom -> top
216                Ordering::Greater => {
217                    if selected_from_row == editor_id {
218                        // Starting line
219                        Some((0, selected_from_col_idx))
220                    } else if selected_to_row == editor_id {
221                        // Ending line
222                        let len = self.line(selected_to_row).unwrap().utf16_len();
223                        Some((selected_to_col_idx, len))
224                    } else {
225                        None
226                    }
227                }
228                // Selection direction is from top -> bottom
229                Ordering::Less => {
230                    if selected_from_row == editor_id {
231                        // Starting line
232                        let len = self.line(selected_from_row).unwrap().utf16_len();
233                        Some((selected_from_col_idx, len))
234                    } else if selected_to_row == editor_id {
235                        // Ending line
236                        Some((0, selected_to_col_idx))
237                    } else {
238                        None
239                    }
240                }
241                Ordering::Equal if selected_from_row == editor_id => {
242                    // Starting and endline line are the same
243                    Some((selected_from - editor_row_idx, selected_to - editor_row_idx))
244                }
245                _ => None,
246            }
247        } else {
248            Some((selected_from, selected_to))
249        }
250    }
251
252    fn set(&mut self, text: &str) {
253        self.rope.remove(0..);
254        self.rope.insert(0, text);
255        if self.cursor_pos() > text.len() {
256            self.set_cursor_pos(text.len());
257        }
258    }
259
260    fn clear_selection(&mut self) {
261        self.selected = None;
262    }
263
264    fn measure_new_selection(&self, from: usize, to: usize, editor_id: usize) -> (usize, usize) {
265        if self.mode == EditableMode::SingleLineMultipleEditors {
266            let row_idx = self.line_to_char(editor_id);
267            let row_idx = self.char_to_utf16_cu(row_idx);
268            if let Some((start, _)) = self.selected {
269                (start, row_idx + to)
270            } else {
271                (row_idx + from, row_idx + to)
272            }
273        } else if let Some((start, _)) = self.selected {
274            (start, to)
275        } else {
276            (from, to)
277        }
278    }
279
280    fn measure_new_cursor(&self, to: usize, editor_id: usize) -> TextCursor {
281        if self.mode == EditableMode::SingleLineMultipleEditors {
282            let row_char = self.line_to_char(editor_id);
283            let pos = self.char_to_utf16_cu(row_char) + to;
284            TextCursor::new(pos)
285        } else {
286            TextCursor::new(to)
287        }
288    }
289
290    fn set_selection(&mut self, selected: (usize, usize)) {
291        self.selected = Some(selected);
292    }
293
294    fn get_selected_text(&self) -> Option<String> {
295        let (start, end) = self.get_selection_range()?;
296
297        let start = self.utf16_cu_to_char(start);
298        let end = self.utf16_cu_to_char(end);
299
300        Some(self.rope().get_slice(start..end)?.to_string())
301    }
302
303    fn get_selection_range(&self) -> Option<(usize, usize)> {
304        let (start, end) = self.selected?;
305
306        // Use left-to-right selection
307        let (start, end) = if start < end {
308            (start, end)
309        } else {
310            (end, start)
311        };
312
313        Some((start, end))
314    }
315
316    fn undo(&mut self) -> Option<usize> {
317        self.history.undo(&mut self.rope)
318    }
319
320    fn redo(&mut self) -> Option<usize> {
321        self.history.redo(&mut self.rope)
322    }
323
324    fn editor_history(&mut self) -> &mut EditorHistory {
325        &mut self.history
326    }
327
328    fn get_identation(&self) -> u8 {
329        self.identation
330    }
331
332    fn find_word_boundaries(&self, pos: usize) -> (usize, usize) {
333        let pos_char = self.utf16_cu_to_char(pos);
334        let len_chars = self.rope.len_chars();
335
336        if len_chars == 0 {
337            return (pos, pos);
338        }
339
340        // Get the line containing the cursor
341        let line_idx = self.rope.char_to_line(pos_char);
342        let line_char = self.rope.line_to_char(line_idx);
343        let line = self.rope.line(line_idx);
344
345        let line_str: std::borrow::Cow<str> = line.into();
346        let pos_in_line = pos_char - line_char;
347
348        // Find word boundaries within the line
349        let mut char_offset = 0;
350        for word in line_str.split_word_bounds() {
351            let word_char_len = word.chars().count();
352            let word_start = char_offset;
353            let word_end = char_offset + word_char_len;
354
355            if pos_in_line >= word_start && pos_in_line < word_end {
356                let start_char = line_char + word_start;
357                let end_char = line_char + word_end;
358                return (
359                    self.char_to_utf16_cu(start_char),
360                    self.char_to_utf16_cu(end_char),
361                );
362            }
363
364            char_offset = word_end;
365        }
366
367        (pos, pos)
368    }
369}
370
371/// Iterator over text lines.
372pub struct LinesIterator<'a> {
373    pub lines: Lines<'a>,
374}
375
376impl<'a> Iterator for LinesIterator<'a> {
377    type Item = Line<'a>;
378
379    fn next(&mut self) -> Option<Self::Item> {
380        let line = self.lines.next();
381
382        line.map(|line| Line {
383            text: line.into(),
384            utf16_len: line.len_utf16_cu(),
385        })
386    }
387}