1use std::{
2 borrow::Cow,
3 fmt::Display,
4 ops::{
5 Mul,
6 Range,
7 },
8 time::Duration,
9};
10
11use freya_components::scrollviews::ScrollController;
12use freya_core::{
13 elements::paragraph::ParagraphHolderInner,
14 prelude::*,
15};
16use freya_edit::*;
17use ropey::Rope;
18use torin::geometry::Size2D;
19use tree_sitter::InputEdit;
20
21use crate::{
22 editor_theme::EditorSyntaxTheme,
23 languages::EditorLanguage,
24 metrics::EditorMetrics,
25 syntax::InputEditExt,
26};
27
28pub struct CodeEditorData {
29 pub(crate) history: EditorHistory,
30 pub rope: Rope,
31 pub(crate) selection: TextSelection,
32 pub(crate) last_saved_history_change: usize,
33 pub(crate) metrics: EditorMetrics,
34 pub(crate) dragging: TextDragging,
35 pub(crate) pending_edit: Option<InputEdit>,
36 pub language: Option<EditorLanguage>,
37 pub viewport: Size2D,
38 theme: EditorSyntaxTheme,
39}
40
41impl CodeEditorData {
42 pub fn new(rope: Rope, language: impl Into<Option<EditorLanguage>>) -> Self {
44 let mut data = Self {
45 rope,
46 selection: TextSelection::new_cursor(0),
47 history: EditorHistory::new(Duration::from_secs(1)),
48 last_saved_history_change: 0,
49 metrics: EditorMetrics::new(),
50 dragging: TextDragging::default(),
51 pending_edit: None,
52 language: language.into(),
53 viewport: Size2D::default(),
54 theme: EditorSyntaxTheme::default(),
55 };
56 data.configure_highlighter();
57 data
58 }
59
60 pub fn scroll_to_cursor(
64 &self,
65 mut scroll_controller: ScrollController,
66 line_height: f32,
67 ) -> bool {
68 if self.viewport.width <= 0.
69 || self.viewport.height <= 0.
70 || line_height <= 0.
71 || self.metrics.char_width <= 0.
72 {
73 return false;
74 }
75
76 let (scroll_x, scroll_y) = scroll_controller.into();
77 let scrolled_x = -scroll_x as f32;
78 let scrolled_y = -scroll_y as f32;
79 let cursor_left = self.cursor_col() as f32 * self.metrics.char_width;
80 let cursor_right = cursor_left + self.metrics.char_width;
81 let cursor_top = self.cursor_row() as f32 * line_height;
82 let cursor_bottom = cursor_top + line_height;
83
84 let horizontal_target = if cursor_left < scrolled_x {
85 Some(cursor_left)
86 } else if cursor_right > scrolled_x + self.viewport.width {
87 Some(cursor_right - self.viewport.width)
88 } else {
89 None
90 };
91
92 let vertical_target = if cursor_top < scrolled_y {
93 Some(cursor_top)
94 } else if cursor_bottom > scrolled_y + self.viewport.height {
95 Some(cursor_bottom - self.viewport.height)
96 } else {
97 None
98 };
99
100 let mut changed = false;
101
102 if let Some(target) = horizontal_target {
103 changed |= scroll_controller.scroll_to_x(-target as i32);
104 }
105
106 if let Some(target) = vertical_target {
107 changed |= scroll_controller.scroll_to_y(-target as i32);
108 }
109
110 changed
111 }
112
113 fn configure_highlighter(&mut self) {
115 self.metrics
116 .highlighter
117 .set_language(self.language.as_ref(), &self.theme);
118 }
119
120 pub fn set_language(&mut self, language: impl Into<Option<EditorLanguage>>) {
122 self.language = language.into();
123 self.configure_highlighter();
124 }
125
126 pub fn is_edited(&self) -> bool {
127 self.history.current_change() != self.last_saved_history_change
128 }
129
130 pub fn mark_as_saved(&mut self) {
131 self.last_saved_history_change = self.history.current_change();
132 }
133
134 pub fn parse(&mut self) {
135 let edit = self.pending_edit.take();
136 self.metrics.run_parser(&self.rope, edit, &self.theme);
137 }
138
139 pub fn measure(&mut self, font_size: f32, font_family: &str) {
140 self.metrics
141 .measure_longest_line(font_size, font_family, &self.rope);
142 }
143
144 pub fn set_theme(&mut self, theme: EditorSyntaxTheme) {
145 self.theme = theme;
146 self.configure_highlighter();
147 }
148
149 pub fn process(
150 &mut self,
151 font_size: f32,
152 font_family: &str,
153 edit_event: EditableEvent,
154 ) -> bool {
155 let mut processed = false;
156 match edit_event {
157 EditableEvent::Down {
158 location,
159 editor_line,
160 holder,
161 } => {
162 let holder = holder.0.borrow();
163 let ParagraphHolderInner {
164 paragraph,
165 scale_factor,
166 ..
167 } = holder.as_ref().unwrap();
168
169 let current_selection = self.selection().clone();
170
171 if self.dragging.shift || self.dragging.clicked {
172 self.selection_mut().set_as_range();
173 } else {
174 self.clear_selection();
175 }
176
177 if ¤t_selection != self.selection() {
178 processed = true;
179 }
180
181 let char_position = paragraph.get_glyph_position_at_coordinate(
182 location.mul(*scale_factor).to_i32().to_tuple(),
183 );
184 let press_selection =
185 self.measure_selection(char_position.position as usize, editor_line);
186
187 let press_type = EventsCombos::<()>::pressed(location);
188 let new_selection = match press_type {
189 PressEventType::Quadruple => {
190 TextSelection::new_range((0, self.rope.len_utf16_cu()))
191 }
192 PressEventType::Triple => {
193 TextSelection::new_range(self.find_line_boundaries(press_selection.pos()))
194 }
195 PressEventType::Double => {
196 TextSelection::new_range(self.find_word_boundaries(press_selection.pos()))
197 }
198 PressEventType::Single => press_selection,
199 };
200
201 self.dragging
202 .start_selection(press_type, new_selection.clone());
203
204 if *self.selection() != new_selection {
205 *self.selection_mut() = new_selection;
206 processed = true;
207 }
208 }
209 EditableEvent::Move {
210 location,
211 editor_line,
212 holder,
213 } => {
214 if self.dragging.clicked {
215 EventsCombos::<()>::moved(location);
216
217 let paragraph = holder.0.borrow();
218 let ParagraphHolderInner {
219 paragraph,
220 scale_factor,
221 ..
222 } = paragraph.as_ref().unwrap();
223
224 let dist_position = location.mul(*scale_factor);
225
226 let dist_char = paragraph
228 .get_glyph_position_at_coordinate(dist_position.to_i32().to_tuple());
229 let to = dist_char.position as usize;
230
231 if self.get_selection().is_none() {
232 self.selection_mut().set_as_range();
233 processed = true;
234 }
235
236 let current_selection = self.selection().clone();
237
238 let new_selection = self.dragging.measure_selection(self, to, editor_line);
239
240 if current_selection != new_selection {
242 *self.selection_mut() = new_selection;
243 processed = true;
244 }
245 }
246 }
247 EditableEvent::Release => {
248 self.dragging.clicked = false;
249 EventsCombos::<()>::released();
250 }
251 EditableEvent::KeyDown {
252 key,
253 modifiers,
254 editor_line,
255 holder,
256 } => {
257 match key {
258 Key::Named(NamedKey::Shift) => {
260 self.dragging.shift = true;
261 }
262 _ => {
264 let event = self.process_key(
265 key,
266 &modifiers,
267 editor_line,
268 holder,
269 true,
270 true,
271 true,
272 true,
273 );
274 if event.contains(TextEvent::TEXT_CHANGED) {
275 self.parse();
276 self.measure(font_size, font_family);
277 self.dragging = TextDragging::default();
278 }
279 if !event.is_empty() {
280 processed = true;
281 }
282 }
283 }
284 }
285 EditableEvent::KeyUp { key, .. } => {
286 if *key == Key::Named(NamedKey::Shift) {
287 self.dragging.shift = false;
288 }
289 }
290 };
291 processed
292 }
293}
294
295impl Display for CodeEditorData {
296 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
297 f.write_str(&self.rope.to_string())
298 }
299}
300
301impl TextEditor for CodeEditorData {
302 type LinesIterator<'a>
303 = LinesIterator<'a>
304 where
305 Self: 'a;
306
307 fn lines(&self) -> Self::LinesIterator<'_> {
308 unimplemented!("Unused.")
309 }
310
311 fn text(&self) -> Cow<'_, str> {
312 self.rope.slice(..).into()
313 }
314
315 fn insert_char(&mut self, ch: char, idx: usize) -> usize {
316 let idx_utf8 = self.utf16_cu_to_char(idx);
317 let selection = self.selection.clone();
318
319 let start_byte = self.rope.char_to_byte(idx_utf8);
321 let start_line = self.rope.char_to_line(idx_utf8);
322 let start_line_byte = self.rope.line_to_byte(start_line);
323 let start_col = start_byte - start_line_byte;
324
325 let len_before_insert = self.rope.len_utf16_cu();
326 self.rope.insert_char(idx_utf8, ch);
327 let len_after_insert = self.rope.len_utf16_cu();
328
329 let inserted_text_len = len_after_insert - len_before_insert;
330
331 let new_end_char = idx_utf8 + 1; let new_end_byte = self.rope.char_to_byte(new_end_char);
334 let new_end_line = self.rope.char_to_line(new_end_char);
335 let new_end_line_byte = self.rope.line_to_byte(new_end_line);
336 let new_end_col = new_end_byte - new_end_line_byte;
337
338 self.pending_edit = Some(InputEdit::new_edit(
339 start_byte,
340 start_byte,
341 new_end_byte,
342 (start_line, start_col),
343 (start_line, start_col),
344 (new_end_line, new_end_col),
345 ));
346
347 self.history.push_change(HistoryChange::InsertChar {
348 idx,
349 ch,
350 len: inserted_text_len,
351 selection,
352 });
353
354 inserted_text_len
355 }
356
357 fn insert(&mut self, text: &str, idx: usize) -> usize {
358 let idx_utf8 = self.utf16_cu_to_char(idx);
359 let selection = self.selection.clone();
360
361 let start_byte = self.rope.char_to_byte(idx_utf8);
363 let start_line = self.rope.char_to_line(idx_utf8);
364 let start_line_byte = self.rope.line_to_byte(start_line);
365 let start_col = start_byte - start_line_byte;
366
367 let len_before_insert = self.rope.len_utf16_cu();
368 self.rope.insert(idx_utf8, text);
369 let len_after_insert = self.rope.len_utf16_cu();
370
371 let inserted_text_len = len_after_insert - len_before_insert;
372
373 let inserted_chars = text.chars().count();
375 let new_end_char = idx_utf8 + inserted_chars;
376 let new_end_byte = self.rope.char_to_byte(new_end_char);
377 let new_end_line = self.rope.char_to_line(new_end_char);
378 let new_end_line_byte = self.rope.line_to_byte(new_end_line);
379 let new_end_col = new_end_byte - new_end_line_byte;
380
381 self.pending_edit = Some(InputEdit::new_edit(
382 start_byte,
383 start_byte,
384 new_end_byte,
385 (start_line, start_col),
386 (start_line, start_col),
387 (new_end_line, new_end_col),
388 ));
389
390 self.history.push_change(HistoryChange::InsertText {
391 idx,
392 text: text.to_owned(),
393 len: inserted_text_len,
394 selection,
395 });
396
397 inserted_text_len
398 }
399
400 fn remove(&mut self, range_utf16: Range<usize>) -> usize {
401 let range =
402 self.utf16_cu_to_char(range_utf16.start)..self.utf16_cu_to_char(range_utf16.end);
403 let text = self.rope.slice(range.clone()).to_string();
404 let selection = self.selection.clone();
405
406 let start_byte = self.rope.char_to_byte(range.start);
408 let old_end_byte = self.rope.char_to_byte(range.end);
409 let start_line = self.rope.char_to_line(range.start);
410 let start_line_byte = self.rope.line_to_byte(start_line);
411 let start_col = start_byte - start_line_byte;
412 let old_end_line = self.rope.char_to_line(range.end);
413 let old_end_line_byte = self.rope.line_to_byte(old_end_line);
414 let old_end_col = old_end_byte - old_end_line_byte;
415
416 let len_before_remove = self.rope.len_utf16_cu();
417 self.rope.remove(range);
418 let len_after_remove = self.rope.len_utf16_cu();
419
420 let removed_text_len = len_before_remove - len_after_remove;
421
422 self.pending_edit = Some(InputEdit::new_edit(
424 start_byte,
425 old_end_byte,
426 start_byte,
427 (start_line, start_col),
428 (old_end_line, old_end_col),
429 (start_line, start_col),
430 ));
431
432 self.history.push_change(HistoryChange::Remove {
433 idx: range_utf16.end - removed_text_len,
434 text,
435 len: removed_text_len,
436 selection,
437 });
438
439 removed_text_len
440 }
441
442 fn char_to_line(&self, char_idx: usize) -> usize {
443 self.rope.char_to_line(char_idx)
444 }
445
446 fn line_to_char(&self, line_idx: usize) -> usize {
447 self.rope.line_to_char(line_idx)
448 }
449
450 fn utf16_cu_to_char(&self, utf16_cu_idx: usize) -> usize {
451 self.rope.utf16_cu_to_char(utf16_cu_idx)
452 }
453
454 fn char_to_utf16_cu(&self, idx: usize) -> usize {
455 self.rope.char_to_utf16_cu(idx)
456 }
457
458 fn line(&self, line_idx: usize) -> Option<Line<'_>> {
459 let line = self.rope.get_line(line_idx);
460
461 line.map(|line| Line {
462 text: Cow::Owned(line.to_string()),
463 utf16_len: line.len_utf16_cu(),
464 })
465 }
466
467 fn len_lines(&self) -> usize {
468 self.rope.len_lines()
469 }
470
471 fn len_chars(&self) -> usize {
472 self.rope.len_chars()
473 }
474
475 fn len_utf16_cu(&self) -> usize {
476 self.rope.len_utf16_cu()
477 }
478
479 fn has_any_selection(&self) -> bool {
480 self.selection.is_range()
481 }
482
483 fn get_selection(&self) -> Option<(usize, usize)> {
484 match self.selection {
485 TextSelection::Cursor(_) => None,
486 TextSelection::Range { from, to } => Some((from, to)),
487 }
488 }
489
490 fn set(&mut self, text: &str) {
491 self.rope.remove(0..);
492 self.rope.insert(0, text);
493 }
494
495 fn clear_selection(&mut self) {
496 let end = self.selection().end();
497 self.selection_mut().set_as_cursor();
498 self.selection_mut().move_to(end);
499 }
500
501 fn set_selection(&mut self, (from, to): (usize, usize)) {
502 self.selection = TextSelection::Range { from, to };
503 }
504
505 fn get_selected_text(&self) -> Option<String> {
506 let (start, end) = self.get_selection_range()?;
507
508 Some(self.rope.get_slice(start..end)?.to_string())
509 }
510
511 fn get_selection_range(&self) -> Option<(usize, usize)> {
512 let (start, end) = match self.selection {
513 TextSelection::Cursor(_) => return None,
514 TextSelection::Range { from, to } => (from, to),
515 };
516
517 let (start, end) = if start < end {
519 (start, end)
520 } else {
521 (end, start)
522 };
523
524 Some((start, end))
525 }
526
527 fn undo(&mut self) -> Option<TextSelection> {
528 self.pending_edit = None;
530 self.metrics.highlighter.invalidate_tree();
531 self.history.undo(&mut self.rope)
532 }
533
534 fn redo(&mut self) -> Option<TextSelection> {
535 self.pending_edit = None;
537 self.metrics.highlighter.invalidate_tree();
538 self.history.redo(&mut self.rope)
539 }
540
541 fn editor_history(&self) -> &EditorHistory {
542 &self.history
543 }
544
545 fn editor_history_mut(&mut self) -> &mut EditorHistory {
546 &mut self.history
547 }
548
549 fn selection(&self) -> &TextSelection {
550 &self.selection
551 }
552
553 fn selection_mut(&mut self) -> &mut TextSelection {
554 &mut self.selection
555 }
556
557 fn get_indentation(&self) -> u8 {
558 4
559 }
560}