Skip to main content

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