freya_code_editor/
editor_data.rs

1use std::{
2    borrow::Cow,
3    fmt::Display,
4    ops::{
5        Mul,
6        Range,
7    },
8    time::Duration,
9};
10
11use freya_core::{
12    elements::paragraph::ParagraphHolderInner,
13    prelude::*,
14};
15use freya_edit::*;
16use ropey::Rope;
17use tree_sitter::InputEdit;
18
19use crate::{
20    editor_theme::SyntaxTheme,
21    languages::LanguageId,
22    metrics::EditorMetrics,
23    syntax::InputEditExt,
24};
25
26pub struct CodeEditorData {
27    pub(crate) history: EditorHistory,
28    pub rope: Rope,
29    pub(crate) selection: TextSelection,
30    pub(crate) last_saved_history_change: usize,
31    pub(crate) metrics: EditorMetrics,
32    pub(crate) dragging: TextDragging,
33    pub(crate) scrolls: (i32, i32),
34    pub(crate) pending_edit: Option<InputEdit>,
35    pub language_id: LanguageId,
36    theme: SyntaxTheme,
37}
38
39impl CodeEditorData {
40    pub fn new(rope: Rope, language_id: LanguageId) -> Self {
41        Self {
42            rope,
43            selection: TextSelection::new_cursor(0),
44            history: EditorHistory::new(Duration::from_secs(1)),
45            last_saved_history_change: 0,
46            metrics: EditorMetrics::new(),
47            dragging: TextDragging::default(),
48            scrolls: (0, 0),
49            pending_edit: None,
50            language_id,
51            theme: SyntaxTheme::default(),
52        }
53    }
54
55    pub fn is_edited(&self) -> bool {
56        self.history.current_change() != self.last_saved_history_change
57    }
58
59    pub fn mark_as_saved(&mut self) {
60        self.last_saved_history_change = self.history.current_change();
61    }
62
63    pub fn parse(&mut self) {
64        let edit = self.pending_edit.take();
65        self.metrics
66            .run_parser(&self.rope, self.language_id, edit, &self.theme);
67    }
68
69    pub fn measure(&mut self, font_size: f32) {
70        self.metrics.measure_longest_line(font_size, &self.rope);
71    }
72
73    pub fn set_theme(&mut self, theme: SyntaxTheme) {
74        self.theme = theme;
75    }
76
77    pub fn process(&mut self, font_size: f32, edit_event: EditableEvent) -> bool {
78        let mut processed = false;
79        match edit_event {
80            EditableEvent::Down {
81                location,
82                editor_line,
83                holder,
84            } => {
85                let holder = holder.0.borrow();
86                let ParagraphHolderInner {
87                    paragraph,
88                    scale_factor,
89                } = holder.as_ref().unwrap();
90
91                let current_selection = self.selection().clone();
92
93                if self.dragging.shift || self.dragging.clicked {
94                    self.selection_mut().set_as_range();
95                } else {
96                    self.clear_selection();
97                }
98
99                if &current_selection != self.selection() {
100                    processed = true;
101                }
102
103                self.dragging.clicked = true;
104
105                let char_position = paragraph.get_glyph_position_at_coordinate(
106                    location.mul(*scale_factor).to_i32().to_tuple(),
107                );
108                let press_selection =
109                    self.measure_selection(char_position.position as usize, editor_line);
110
111                let new_selection = match EventsCombos::pressed(location) {
112                    PressEventType::Triple => {
113                        let line = self.char_to_line(press_selection.pos());
114                        let line_char = self.line_to_char(line);
115                        let line_len = self.line(line).unwrap().utf16_len();
116                        TextSelection::new_range((line_char, line_char + line_len))
117                    }
118                    PressEventType::Double => {
119                        let range = self.find_word_boundaries(press_selection.pos());
120                        TextSelection::new_range(range)
121                    }
122                    PressEventType::Single => press_selection,
123                };
124
125                if *self.selection() != new_selection {
126                    *self.selection_mut() = new_selection;
127                    processed = true;
128                }
129            }
130            EditableEvent::Move {
131                location,
132                editor_line,
133                holder,
134            } => {
135                if self.dragging.clicked {
136                    let paragraph = holder.0.borrow();
137                    let ParagraphHolderInner {
138                        paragraph,
139                        scale_factor,
140                    } = paragraph.as_ref().unwrap();
141
142                    let dist_position = location.mul(*scale_factor);
143
144                    // Calculate the end of the highlighting
145                    let dist_char = paragraph
146                        .get_glyph_position_at_coordinate(dist_position.to_i32().to_tuple());
147                    let to = dist_char.position as usize;
148
149                    if self.get_selection().is_none() {
150                        self.selection_mut().set_as_range();
151                        processed = true;
152                    }
153
154                    let current_selection = self.selection().clone();
155
156                    let new_selection = self.measure_selection(to, editor_line);
157
158                    // Update the cursor if it has changed
159                    if current_selection != new_selection {
160                        *self.selection_mut() = new_selection;
161                        processed = true;
162                    }
163                }
164            }
165            EditableEvent::Release => {
166                self.dragging.clicked = false;
167            }
168            EditableEvent::KeyDown { key, modifiers } => {
169                match key {
170                    // Handle dragging
171                    Key::Named(NamedKey::Shift) => {
172                        self.dragging.shift = true;
173                    }
174                    // Handle editing
175                    _ => {
176                        let event = self.process_key(key, &modifiers, true, true, true);
177                        if event.contains(TextEvent::TEXT_CHANGED) {
178                            self.parse();
179                            self.measure(font_size);
180                            self.dragging = TextDragging::default();
181                        }
182                        if !event.is_empty() {
183                            processed = true;
184                        }
185                    }
186                }
187            }
188            EditableEvent::KeyUp { key, .. } => {
189                if *key == Key::Named(NamedKey::Shift) {
190                    self.dragging.shift = false;
191                }
192            }
193        };
194        processed
195    }
196}
197
198impl Display for CodeEditorData {
199    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
200        f.write_str(&self.rope.to_string())
201    }
202}
203
204impl TextEditor for CodeEditorData {
205    type LinesIterator<'a>
206        = LinesIterator<'a>
207    where
208        Self: 'a;
209
210    fn lines(&self) -> Self::LinesIterator<'_> {
211        unimplemented!("Unused.")
212    }
213
214    fn insert_char(&mut self, ch: char, idx: usize) -> usize {
215        let idx_utf8 = self.utf16_cu_to_char(idx);
216        let selection = self.selection.clone();
217
218        // Capture byte offset and position before mutation for InputEdit.
219        let start_byte = self.rope.char_to_byte(idx_utf8);
220        let start_line = self.rope.char_to_line(idx_utf8);
221        let start_line_byte = self.rope.line_to_byte(start_line);
222        let start_col = start_byte - start_line_byte;
223
224        let len_before_insert = self.rope.len_utf16_cu();
225        self.rope.insert_char(idx_utf8, ch);
226        let len_after_insert = self.rope.len_utf16_cu();
227
228        let inserted_text_len = len_after_insert - len_before_insert;
229
230        // Compute new end position after insertion.
231        let new_end_char = idx_utf8 + 1; // one char inserted
232        let new_end_byte = self.rope.char_to_byte(new_end_char);
233        let new_end_line = self.rope.char_to_line(new_end_char);
234        let new_end_line_byte = self.rope.line_to_byte(new_end_line);
235        let new_end_col = new_end_byte - new_end_line_byte;
236
237        self.pending_edit = Some(InputEdit::new_edit(
238            start_byte,
239            start_byte,
240            new_end_byte,
241            (start_line, start_col),
242            (start_line, start_col),
243            (new_end_line, new_end_col),
244        ));
245
246        self.history.push_change(HistoryChange::InsertChar {
247            idx,
248            ch,
249            len: inserted_text_len,
250            selection,
251        });
252
253        inserted_text_len
254    }
255
256    fn insert(&mut self, text: &str, idx: usize) -> usize {
257        let idx_utf8 = self.utf16_cu_to_char(idx);
258        let selection = self.selection.clone();
259
260        // Capture byte offset and position before mutation for InputEdit.
261        let start_byte = self.rope.char_to_byte(idx_utf8);
262        let start_line = self.rope.char_to_line(idx_utf8);
263        let start_line_byte = self.rope.line_to_byte(start_line);
264        let start_col = start_byte - start_line_byte;
265
266        let len_before_insert = self.rope.len_utf16_cu();
267        self.rope.insert(idx_utf8, text);
268        let len_after_insert = self.rope.len_utf16_cu();
269
270        let inserted_text_len = len_after_insert - len_before_insert;
271
272        // Compute new end position after insertion.
273        let inserted_chars = text.chars().count();
274        let new_end_char = idx_utf8 + inserted_chars;
275        let new_end_byte = self.rope.char_to_byte(new_end_char);
276        let new_end_line = self.rope.char_to_line(new_end_char);
277        let new_end_line_byte = self.rope.line_to_byte(new_end_line);
278        let new_end_col = new_end_byte - new_end_line_byte;
279
280        self.pending_edit = Some(InputEdit::new_edit(
281            start_byte,
282            start_byte,
283            new_end_byte,
284            (start_line, start_col),
285            (start_line, start_col),
286            (new_end_line, new_end_col),
287        ));
288
289        self.history.push_change(HistoryChange::InsertText {
290            idx,
291            text: text.to_owned(),
292            len: inserted_text_len,
293            selection,
294        });
295
296        inserted_text_len
297    }
298
299    fn remove(&mut self, range_utf16: Range<usize>) -> usize {
300        let range =
301            self.utf16_cu_to_char(range_utf16.start)..self.utf16_cu_to_char(range_utf16.end);
302        let text = self.rope.slice(range.clone()).to_string();
303        let selection = self.selection.clone();
304
305        // Capture byte offsets and positions before mutation for InputEdit.
306        let start_byte = self.rope.char_to_byte(range.start);
307        let old_end_byte = self.rope.char_to_byte(range.end);
308        let start_line = self.rope.char_to_line(range.start);
309        let start_line_byte = self.rope.line_to_byte(start_line);
310        let start_col = start_byte - start_line_byte;
311        let old_end_line = self.rope.char_to_line(range.end);
312        let old_end_line_byte = self.rope.line_to_byte(old_end_line);
313        let old_end_col = old_end_byte - old_end_line_byte;
314
315        let len_before_remove = self.rope.len_utf16_cu();
316        self.rope.remove(range);
317        let len_after_remove = self.rope.len_utf16_cu();
318
319        let removed_text_len = len_before_remove - len_after_remove;
320
321        // After removal, new_end == start (the removed range collapses to a point).
322        self.pending_edit = Some(InputEdit::new_edit(
323            start_byte,
324            old_end_byte,
325            start_byte,
326            (start_line, start_col),
327            (old_end_line, old_end_col),
328            (start_line, start_col),
329        ));
330
331        self.history.push_change(HistoryChange::Remove {
332            idx: range_utf16.end - removed_text_len,
333            text,
334            len: removed_text_len,
335            selection,
336        });
337
338        removed_text_len
339    }
340
341    fn char_to_line(&self, char_idx: usize) -> usize {
342        self.rope.char_to_line(char_idx)
343    }
344
345    fn line_to_char(&self, line_idx: usize) -> usize {
346        self.rope.line_to_char(line_idx)
347    }
348
349    fn utf16_cu_to_char(&self, utf16_cu_idx: usize) -> usize {
350        self.rope.utf16_cu_to_char(utf16_cu_idx)
351    }
352
353    fn char_to_utf16_cu(&self, idx: usize) -> usize {
354        self.rope.char_to_utf16_cu(idx)
355    }
356
357    fn line(&self, line_idx: usize) -> Option<Line<'_>> {
358        let line = self.rope.get_line(line_idx);
359
360        line.map(|line| Line {
361            text: Cow::Owned(line.to_string()),
362            utf16_len: line.len_utf16_cu(),
363        })
364    }
365
366    fn len_lines(&self) -> usize {
367        self.rope.len_lines()
368    }
369
370    fn len_chars(&self) -> usize {
371        self.rope.len_chars()
372    }
373
374    fn len_utf16_cu(&self) -> usize {
375        self.rope.len_utf16_cu()
376    }
377
378    fn has_any_selection(&self) -> bool {
379        self.selection.is_range()
380    }
381
382    fn get_selection(&self) -> Option<(usize, usize)> {
383        match self.selection {
384            TextSelection::Cursor(_) => None,
385            TextSelection::Range { from, to } => Some((from, to)),
386        }
387    }
388
389    fn set(&mut self, text: &str) {
390        self.rope.remove(0..);
391        self.rope.insert(0, text);
392    }
393
394    fn clear_selection(&mut self) {
395        let end = self.selection().end();
396        self.selection_mut().set_as_cursor();
397        self.selection_mut().move_to(end);
398    }
399
400    fn set_selection(&mut self, (from, to): (usize, usize)) {
401        self.selection = TextSelection::Range { from, to };
402    }
403
404    fn get_selected_text(&self) -> Option<String> {
405        let (start, end) = self.get_selection_range()?;
406
407        Some(self.rope.get_slice(start..end)?.to_string())
408    }
409
410    fn get_selection_range(&self) -> Option<(usize, usize)> {
411        let (start, end) = match self.selection {
412            TextSelection::Cursor(_) => return None,
413            TextSelection::Range { from, to } => (from, to),
414        };
415
416        // Use left-to-right selection
417        let (start, end) = if start < end {
418            (start, end)
419        } else {
420            (end, start)
421        };
422
423        Some((start, end))
424    }
425
426    fn undo(&mut self) -> Option<TextSelection> {
427        // Undo can make arbitrary changes — invalidate the tree for a full re-parse.
428        self.pending_edit = None;
429        self.metrics.highlighter.invalidate_tree();
430        self.history.undo(&mut self.rope)
431    }
432
433    fn redo(&mut self) -> Option<TextSelection> {
434        // Redo can make arbitrary changes — invalidate the tree for a full re-parse.
435        self.pending_edit = None;
436        self.metrics.highlighter.invalidate_tree();
437        self.history.redo(&mut self.rope)
438    }
439
440    fn editor_history(&mut self) -> &mut EditorHistory {
441        &mut self.history
442    }
443
444    fn selection(&self) -> &TextSelection {
445        &self.selection
446    }
447
448    fn selection_mut(&mut self) -> &mut TextSelection {
449        &mut self.selection
450    }
451
452    fn get_indentation(&self) -> u8 {
453        4
454    }
455}