freya_hooks/
rope_editor.rs

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