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 self.dragging.clicked = true;
182
183 let char_position = paragraph.get_glyph_position_at_coordinate(
184 location.mul(*scale_factor).to_i32().to_tuple(),
185 );
186 let press_selection =
187 self.measure_selection(char_position.position as usize, editor_line);
188
189 let new_selection = match EventsCombos::<()>::pressed(location) {
190 PressEventType::Quadruple => {
191 TextSelection::new_range((0, self.rope.len_utf16_cu()))
192 }
193 PressEventType::Triple => {
194 let line = self.char_to_line(press_selection.pos());
195 let line_char = self.line_to_char(line);
196 let line_len = self.line(line).unwrap().utf16_len();
197 TextSelection::new_range((line_char, line_char + line_len))
198 }
199 PressEventType::Double => {
200 let range = self.find_word_boundaries(press_selection.pos());
201 TextSelection::new_range(range)
202 }
203 PressEventType::Single => press_selection,
204 };
205
206 if *self.selection() != new_selection {
207 *self.selection_mut() = new_selection;
208 processed = true;
209 }
210 }
211 EditableEvent::Move {
212 location,
213 editor_line,
214 holder,
215 } => {
216 if self.dragging.clicked {
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.measure_selection(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 }
250 EditableEvent::KeyDown {
251 key,
252 modifiers,
253 editor_line,
254 holder,
255 } => {
256 match key {
257 Key::Named(NamedKey::Shift) => {
259 self.dragging.shift = true;
260 }
261 _ => {
263 let event = self.process_key(
264 key,
265 &modifiers,
266 editor_line,
267 holder,
268 true,
269 true,
270 true,
271 true,
272 );
273 if event.contains(TextEvent::TEXT_CHANGED) {
274 self.parse();
275 self.measure(font_size, font_family);
276 self.dragging = TextDragging::default();
277 }
278 if !event.is_empty() {
279 processed = true;
280 }
281 }
282 }
283 }
284 EditableEvent::KeyUp { key, .. } => {
285 if *key == Key::Named(NamedKey::Shift) {
286 self.dragging.shift = false;
287 }
288 }
289 };
290 processed
291 }
292}
293
294impl Display for CodeEditorData {
295 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
296 f.write_str(&self.rope.to_string())
297 }
298}
299
300impl TextEditor for CodeEditorData {
301 type LinesIterator<'a>
302 = LinesIterator<'a>
303 where
304 Self: 'a;
305
306 fn lines(&self) -> Self::LinesIterator<'_> {
307 unimplemented!("Unused.")
308 }
309
310 fn text(&self) -> Cow<'_, str> {
311 self.rope.slice(..).into()
312 }
313
314 fn insert_char(&mut self, ch: char, idx: usize) -> usize {
315 let idx_utf8 = self.utf16_cu_to_char(idx);
316 let selection = self.selection.clone();
317
318 let start_byte = self.rope.char_to_byte(idx_utf8);
320 let start_line = self.rope.char_to_line(idx_utf8);
321 let start_line_byte = self.rope.line_to_byte(start_line);
322 let start_col = start_byte - start_line_byte;
323
324 let len_before_insert = self.rope.len_utf16_cu();
325 self.rope.insert_char(idx_utf8, ch);
326 let len_after_insert = self.rope.len_utf16_cu();
327
328 let inserted_text_len = len_after_insert - len_before_insert;
329
330 let new_end_char = idx_utf8 + 1; let new_end_byte = self.rope.char_to_byte(new_end_char);
333 let new_end_line = self.rope.char_to_line(new_end_char);
334 let new_end_line_byte = self.rope.line_to_byte(new_end_line);
335 let new_end_col = new_end_byte - new_end_line_byte;
336
337 self.pending_edit = Some(InputEdit::new_edit(
338 start_byte,
339 start_byte,
340 new_end_byte,
341 (start_line, start_col),
342 (start_line, start_col),
343 (new_end_line, new_end_col),
344 ));
345
346 self.history.push_change(HistoryChange::InsertChar {
347 idx,
348 ch,
349 len: inserted_text_len,
350 selection,
351 });
352
353 inserted_text_len
354 }
355
356 fn insert(&mut self, text: &str, idx: usize) -> usize {
357 let idx_utf8 = self.utf16_cu_to_char(idx);
358 let selection = self.selection.clone();
359
360 let start_byte = self.rope.char_to_byte(idx_utf8);
362 let start_line = self.rope.char_to_line(idx_utf8);
363 let start_line_byte = self.rope.line_to_byte(start_line);
364 let start_col = start_byte - start_line_byte;
365
366 let len_before_insert = self.rope.len_utf16_cu();
367 self.rope.insert(idx_utf8, text);
368 let len_after_insert = self.rope.len_utf16_cu();
369
370 let inserted_text_len = len_after_insert - len_before_insert;
371
372 let inserted_chars = text.chars().count();
374 let new_end_char = idx_utf8 + inserted_chars;
375 let new_end_byte = self.rope.char_to_byte(new_end_char);
376 let new_end_line = self.rope.char_to_line(new_end_char);
377 let new_end_line_byte = self.rope.line_to_byte(new_end_line);
378 let new_end_col = new_end_byte - new_end_line_byte;
379
380 self.pending_edit = Some(InputEdit::new_edit(
381 start_byte,
382 start_byte,
383 new_end_byte,
384 (start_line, start_col),
385 (start_line, start_col),
386 (new_end_line, new_end_col),
387 ));
388
389 self.history.push_change(HistoryChange::InsertText {
390 idx,
391 text: text.to_owned(),
392 len: inserted_text_len,
393 selection,
394 });
395
396 inserted_text_len
397 }
398
399 fn remove(&mut self, range_utf16: Range<usize>) -> usize {
400 let range =
401 self.utf16_cu_to_char(range_utf16.start)..self.utf16_cu_to_char(range_utf16.end);
402 let text = self.rope.slice(range.clone()).to_string();
403 let selection = self.selection.clone();
404
405 let start_byte = self.rope.char_to_byte(range.start);
407 let old_end_byte = self.rope.char_to_byte(range.end);
408 let start_line = self.rope.char_to_line(range.start);
409 let start_line_byte = self.rope.line_to_byte(start_line);
410 let start_col = start_byte - start_line_byte;
411 let old_end_line = self.rope.char_to_line(range.end);
412 let old_end_line_byte = self.rope.line_to_byte(old_end_line);
413 let old_end_col = old_end_byte - old_end_line_byte;
414
415 let len_before_remove = self.rope.len_utf16_cu();
416 self.rope.remove(range);
417 let len_after_remove = self.rope.len_utf16_cu();
418
419 let removed_text_len = len_before_remove - len_after_remove;
420
421 self.pending_edit = Some(InputEdit::new_edit(
423 start_byte,
424 old_end_byte,
425 start_byte,
426 (start_line, start_col),
427 (old_end_line, old_end_col),
428 (start_line, start_col),
429 ));
430
431 self.history.push_change(HistoryChange::Remove {
432 idx: range_utf16.end - removed_text_len,
433 text,
434 len: removed_text_len,
435 selection,
436 });
437
438 removed_text_len
439 }
440
441 fn char_to_line(&self, char_idx: usize) -> usize {
442 self.rope.char_to_line(char_idx)
443 }
444
445 fn line_to_char(&self, line_idx: usize) -> usize {
446 self.rope.line_to_char(line_idx)
447 }
448
449 fn utf16_cu_to_char(&self, utf16_cu_idx: usize) -> usize {
450 self.rope.utf16_cu_to_char(utf16_cu_idx)
451 }
452
453 fn char_to_utf16_cu(&self, idx: usize) -> usize {
454 self.rope.char_to_utf16_cu(idx)
455 }
456
457 fn line(&self, line_idx: usize) -> Option<Line<'_>> {
458 let line = self.rope.get_line(line_idx);
459
460 line.map(|line| Line {
461 text: Cow::Owned(line.to_string()),
462 utf16_len: line.len_utf16_cu(),
463 })
464 }
465
466 fn len_lines(&self) -> usize {
467 self.rope.len_lines()
468 }
469
470 fn len_chars(&self) -> usize {
471 self.rope.len_chars()
472 }
473
474 fn len_utf16_cu(&self) -> usize {
475 self.rope.len_utf16_cu()
476 }
477
478 fn has_any_selection(&self) -> bool {
479 self.selection.is_range()
480 }
481
482 fn get_selection(&self) -> Option<(usize, usize)> {
483 match self.selection {
484 TextSelection::Cursor(_) => None,
485 TextSelection::Range { from, to } => Some((from, to)),
486 }
487 }
488
489 fn set(&mut self, text: &str) {
490 self.rope.remove(0..);
491 self.rope.insert(0, text);
492 }
493
494 fn clear_selection(&mut self) {
495 let end = self.selection().end();
496 self.selection_mut().set_as_cursor();
497 self.selection_mut().move_to(end);
498 }
499
500 fn set_selection(&mut self, (from, to): (usize, usize)) {
501 self.selection = TextSelection::Range { from, to };
502 }
503
504 fn get_selected_text(&self) -> Option<String> {
505 let (start, end) = self.get_selection_range()?;
506
507 Some(self.rope.get_slice(start..end)?.to_string())
508 }
509
510 fn get_selection_range(&self) -> Option<(usize, usize)> {
511 let (start, end) = match self.selection {
512 TextSelection::Cursor(_) => return None,
513 TextSelection::Range { from, to } => (from, to),
514 };
515
516 let (start, end) = if start < end {
518 (start, end)
519 } else {
520 (end, start)
521 };
522
523 Some((start, end))
524 }
525
526 fn undo(&mut self) -> Option<TextSelection> {
527 self.pending_edit = None;
529 self.metrics.highlighter.invalidate_tree();
530 self.history.undo(&mut self.rope)
531 }
532
533 fn redo(&mut self) -> Option<TextSelection> {
534 self.pending_edit = None;
536 self.metrics.highlighter.invalidate_tree();
537 self.history.redo(&mut self.rope)
538 }
539
540 fn editor_history(&self) -> &EditorHistory {
541 &self.history
542 }
543
544 fn editor_history_mut(&mut self) -> &mut EditorHistory {
545 &mut self.history
546 }
547
548 fn selection(&self) -> &TextSelection {
549 &self.selection
550 }
551
552 fn selection_mut(&mut self) -> &mut TextSelection {
553 &mut self.selection
554 }
555
556 fn get_indentation(&self) -> u8 {
557 4
558 }
559}