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