1use crate::core::text::editor::{
3 self, Action, Cursor, Direction, Edit, Motion,
4};
5use crate::core::text::highlighter::{self, Highlighter};
6use crate::core::text::{LineHeight, Wrapping};
7use crate::core::{Font, Pixels, Point, Rectangle, Size};
8use crate::text;
9
10use cosmic_text::Edit as _;
11
12use std::borrow::Cow;
13use std::fmt;
14use std::sync::{self, Arc, RwLock};
15
16#[derive(Debug, PartialEq)]
18pub struct Editor(Option<Arc<Internal>>);
19
20struct Internal {
21 editor: cosmic_text::Editor<'static>,
22 cursor: RwLock<Option<Cursor>>,
23 font: Font,
24 bounds: Size,
25 topmost_line_changed: Option<usize>,
26 version: text::Version,
27}
28
29impl Editor {
30 pub fn new() -> Self {
32 Self::default()
33 }
34
35 pub fn buffer(&self) -> &cosmic_text::Buffer {
37 buffer_from_editor(&self.internal().editor)
38 }
39
40 pub fn downgrade(&self) -> Weak {
46 let editor = self.internal();
47
48 Weak {
49 raw: Arc::downgrade(editor),
50 bounds: editor.bounds,
51 }
52 }
53
54 fn internal(&self) -> &Arc<Internal> {
55 self.0
56 .as_ref()
57 .expect("Editor should always be initialized")
58 }
59}
60
61impl editor::Editor for Editor {
62 type Font = Font;
63
64 fn with_text(text: &str) -> Self {
65 let mut buffer = cosmic_text::Buffer::new_empty(cosmic_text::Metrics {
66 font_size: 1.0,
67 line_height: 1.0,
68 });
69
70 let mut font_system =
71 text::font_system().write().expect("Write font system");
72
73 buffer.set_text(
74 font_system.raw(),
75 text,
76 &cosmic_text::Attrs::new(),
77 cosmic_text::Shaping::Advanced,
78 );
79
80 Editor(Some(Arc::new(Internal {
81 editor: cosmic_text::Editor::new(buffer),
82 version: font_system.version(),
83 ..Default::default()
84 })))
85 }
86
87 fn is_empty(&self) -> bool {
88 let buffer = self.buffer();
89
90 buffer.lines.is_empty()
91 || (buffer.lines.len() == 1 && buffer.lines[0].text().is_empty())
92 }
93
94 fn line(&self, index: usize) -> Option<editor::Line<'_>> {
95 self.buffer().lines.get(index).map(|line| editor::Line {
96 text: Cow::Borrowed(line.text()),
97 ending: match line.ending() {
98 cosmic_text::LineEnding::Lf => editor::LineEnding::Lf,
99 cosmic_text::LineEnding::CrLf => editor::LineEnding::CrLf,
100 cosmic_text::LineEnding::Cr => editor::LineEnding::Cr,
101 cosmic_text::LineEnding::LfCr => editor::LineEnding::LfCr,
102 cosmic_text::LineEnding::None => editor::LineEnding::None,
103 },
104 })
105 }
106
107 fn line_count(&self) -> usize {
108 self.buffer().lines.len()
109 }
110
111 fn selection(&self) -> Option<String> {
112 self.internal().editor.copy_selection()
113 }
114
115 fn cursor(&self) -> editor::Cursor {
116 let internal = self.internal();
117
118 if let Ok(Some(cursor)) = internal.cursor.read().as_deref() {
119 return cursor.clone();
120 }
121
122 let cursor = internal.editor.cursor();
123 let buffer = buffer_from_editor(&internal.editor);
124
125 let cursor = match internal.editor.selection_bounds() {
126 Some((start, end)) => {
127 let line_height = buffer.metrics().line_height;
128 let selected_lines = end.line - start.line + 1;
129
130 let visual_lines_offset =
131 visual_lines_offset(start.line, buffer);
132
133 let regions = buffer
134 .lines
135 .iter()
136 .skip(start.line)
137 .take(selected_lines)
138 .enumerate()
139 .flat_map(|(i, line)| {
140 highlight_line(
141 line,
142 if i == 0 { start.index } else { 0 },
143 if i == selected_lines - 1 {
144 end.index
145 } else {
146 line.text().len()
147 },
148 )
149 })
150 .enumerate()
151 .filter_map(|(visual_line, (x, width))| {
152 if width > 0.0 {
153 Some(Rectangle {
154 x,
155 width,
156 y: (visual_line as i32 + visual_lines_offset)
157 as f32
158 * line_height
159 - buffer.scroll().vertical,
160 height: line_height,
161 })
162 } else {
163 None
164 }
165 })
166 .collect();
167
168 Cursor::Selection(regions)
169 }
170 _ => {
171 let line_height = buffer.metrics().line_height;
172
173 let visual_lines_offset =
174 visual_lines_offset(cursor.line, buffer);
175
176 let line = buffer
177 .lines
178 .get(cursor.line)
179 .expect("Cursor line should be present");
180
181 let layout =
182 line.layout_opt().expect("Line layout should be cached");
183
184 let mut lines = layout.iter().enumerate();
185
186 let (visual_line, offset) = lines
187 .find_map(|(i, line)| {
188 let start = line
189 .glyphs
190 .first()
191 .map(|glyph| glyph.start)
192 .unwrap_or(0);
193 let end = line
194 .glyphs
195 .last()
196 .map(|glyph| glyph.end)
197 .unwrap_or(0);
198
199 let is_cursor_before_start = start > cursor.index;
200
201 let is_cursor_before_end = match cursor.affinity {
202 cosmic_text::Affinity::Before => {
203 cursor.index <= end
204 }
205 cosmic_text::Affinity::After => cursor.index < end,
206 };
207
208 if is_cursor_before_start {
209 Some((i - 1, layout[i - 1].w))
218 } else if is_cursor_before_end {
219 let offset = line
220 .glyphs
221 .iter()
222 .take_while(|glyph| cursor.index > glyph.start)
223 .map(|glyph| glyph.w)
224 .sum();
225
226 Some((i, offset))
227 } else {
228 None
229 }
230 })
231 .unwrap_or((
232 layout.len().saturating_sub(1),
233 layout.last().map(|line| line.w).unwrap_or(0.0),
234 ));
235
236 Cursor::Caret(Point::new(
237 offset,
238 (visual_lines_offset + visual_line as i32) as f32
239 * line_height
240 - buffer.scroll().vertical,
241 ))
242 }
243 };
244
245 *internal.cursor.write().expect("Write to cursor cache") =
246 Some(cursor.clone());
247
248 cursor
249 }
250
251 fn cursor_position(&self) -> (usize, usize) {
252 let cursor = self.internal().editor.cursor();
253
254 (cursor.line, cursor.index)
255 }
256
257 fn perform(&mut self, action: Action) {
258 let mut font_system =
259 text::font_system().write().expect("Write font system");
260
261 let editor =
262 self.0.take().expect("Editor should always be initialized");
263
264 let mut internal = Arc::try_unwrap(editor)
266 .expect("Editor cannot have multiple strong references");
267
268 let editor = &mut internal.editor;
269
270 let _ = internal
272 .cursor
273 .write()
274 .expect("Write to cursor cache")
275 .take();
276
277 match action {
278 Action::Move(motion) => {
280 if let Some((start, end)) = editor.selection_bounds() {
281 editor.set_selection(cosmic_text::Selection::None);
282
283 match motion {
284 Motion::Home
287 | Motion::End
288 | Motion::DocumentStart
289 | Motion::DocumentEnd => {
290 editor.action(
291 font_system.raw(),
292 cosmic_text::Action::Motion(to_motion(motion)),
293 );
294 }
295 _ => editor.set_cursor(match motion.direction() {
297 Direction::Left => start,
298 Direction::Right => end,
299 }),
300 }
301 } else {
302 editor.action(
303 font_system.raw(),
304 cosmic_text::Action::Motion(to_motion(motion)),
305 );
306 }
307 }
308
309 Action::Select(motion) => {
311 let cursor = editor.cursor();
312
313 if editor.selection_bounds().is_none() {
314 editor
315 .set_selection(cosmic_text::Selection::Normal(cursor));
316 }
317
318 editor.action(
319 font_system.raw(),
320 cosmic_text::Action::Motion(to_motion(motion)),
321 );
322
323 if let Some((start, end)) = editor.selection_bounds()
325 && start.line == end.line
326 && start.index == end.index
327 {
328 editor.set_selection(cosmic_text::Selection::None);
329 }
330 }
331 Action::SelectWord => {
332 let cursor = editor.cursor();
333
334 editor.set_selection(cosmic_text::Selection::Word(cursor));
335 }
336 Action::SelectLine => {
337 let cursor = editor.cursor();
338
339 editor.set_selection(cosmic_text::Selection::Line(cursor));
340 }
341 Action::SelectAll => {
342 let buffer = buffer_from_editor(editor);
343
344 if buffer.lines.len() > 1
345 || buffer
346 .lines
347 .first()
348 .is_some_and(|line| !line.text().is_empty())
349 {
350 let cursor = editor.cursor();
351
352 editor.set_selection(cosmic_text::Selection::Normal(
353 cosmic_text::Cursor {
354 line: 0,
355 index: 0,
356 ..cursor
357 },
358 ));
359
360 editor.action(
361 font_system.raw(),
362 cosmic_text::Action::Motion(
363 cosmic_text::Motion::BufferEnd,
364 ),
365 );
366 }
367 }
368
369 Action::Edit(edit) => {
371 match edit {
372 Edit::Insert(c) => {
373 editor.action(
374 font_system.raw(),
375 cosmic_text::Action::Insert(c),
376 );
377 }
378 Edit::Paste(text) => {
379 editor.insert_string(&text, None);
380 }
381 Edit::Indent => {
382 editor.action(
383 font_system.raw(),
384 cosmic_text::Action::Indent,
385 );
386 }
387 Edit::Unindent => {
388 editor.action(
389 font_system.raw(),
390 cosmic_text::Action::Unindent,
391 );
392 }
393 Edit::Enter => {
394 editor.action(
395 font_system.raw(),
396 cosmic_text::Action::Enter,
397 );
398 }
399 Edit::Backspace => {
400 editor.action(
401 font_system.raw(),
402 cosmic_text::Action::Backspace,
403 );
404 }
405 Edit::Delete => {
406 editor.action(
407 font_system.raw(),
408 cosmic_text::Action::Delete,
409 );
410 }
411 }
412
413 let cursor = editor.cursor();
414 let selection_start = editor
415 .selection_bounds()
416 .map(|(start, _)| start)
417 .unwrap_or(cursor);
418
419 internal.topmost_line_changed = Some(selection_start.line);
420 }
421
422 Action::Click(position) => {
424 editor.action(
425 font_system.raw(),
426 cosmic_text::Action::Click {
427 x: position.x as i32,
428 y: position.y as i32,
429 },
430 );
431 }
432 Action::Drag(position) => {
433 editor.action(
434 font_system.raw(),
435 cosmic_text::Action::Drag {
436 x: position.x as i32,
437 y: position.y as i32,
438 },
439 );
440
441 if let Some((start, end)) = editor.selection_bounds()
443 && start.line == end.line
444 && start.index == end.index
445 {
446 editor.set_selection(cosmic_text::Selection::None);
447 }
448 }
449 Action::Scroll { lines } => {
450 editor.action(
451 font_system.raw(),
452 cosmic_text::Action::Scroll { lines },
453 );
454 }
455 }
456
457 self.0 = Some(Arc::new(internal));
458 }
459
460 fn bounds(&self) -> Size {
461 self.internal().bounds
462 }
463
464 fn min_bounds(&self) -> Size {
465 let internal = self.internal();
466
467 let (bounds, _has_rtl) =
468 text::measure(buffer_from_editor(&internal.editor));
469
470 bounds
471 }
472
473 fn update(
474 &mut self,
475 new_bounds: Size,
476 new_font: Font,
477 new_size: Pixels,
478 new_line_height: LineHeight,
479 new_wrapping: Wrapping,
480 new_highlighter: &mut impl Highlighter,
481 ) {
482 let editor =
483 self.0.take().expect("Editor should always be initialized");
484
485 let mut internal = Arc::try_unwrap(editor)
486 .expect("Editor cannot have multiple strong references");
487
488 let mut font_system =
489 text::font_system().write().expect("Write font system");
490
491 let buffer = buffer_mut_from_editor(&mut internal.editor);
492
493 if font_system.version() != internal.version {
494 log::trace!("Updating `FontSystem` of `Editor`...");
495
496 for line in buffer.lines.iter_mut() {
497 line.reset();
498 }
499
500 internal.version = font_system.version();
501 internal.topmost_line_changed = Some(0);
502 }
503
504 if new_font != internal.font {
505 log::trace!("Updating font of `Editor`...");
506
507 for line in buffer.lines.iter_mut() {
508 let _ = line.set_attrs_list(cosmic_text::AttrsList::new(
509 &text::to_attributes(new_font),
510 ));
511 }
512
513 internal.font = new_font;
514 internal.topmost_line_changed = Some(0);
515 }
516
517 let metrics = buffer.metrics();
518 let new_line_height = new_line_height.to_absolute(new_size);
519
520 if new_size.0 != metrics.font_size
521 || new_line_height.0 != metrics.line_height
522 {
523 log::trace!("Updating `Metrics` of `Editor`...");
524
525 buffer.set_metrics(
526 font_system.raw(),
527 cosmic_text::Metrics::new(new_size.0, new_line_height.0),
528 );
529 }
530
531 let new_wrap = text::to_wrap(new_wrapping);
532
533 if new_wrap != buffer.wrap() {
534 log::trace!("Updating `Wrap` strategy of `Editor`...");
535
536 buffer.set_wrap(font_system.raw(), new_wrap);
537 }
538
539 if new_bounds != internal.bounds {
540 log::trace!("Updating size of `Editor`...");
541
542 buffer.set_size(
543 font_system.raw(),
544 Some(new_bounds.width),
545 Some(new_bounds.height),
546 );
547
548 internal.bounds = new_bounds;
549 }
550
551 if let Some(topmost_line_changed) = internal.topmost_line_changed.take()
552 {
553 log::trace!(
554 "Notifying highlighter of line change: {topmost_line_changed}"
555 );
556
557 new_highlighter.change_line(topmost_line_changed);
558 }
559
560 internal.editor.shape_as_needed(font_system.raw(), false);
561
562 let _ = internal
564 .cursor
565 .write()
566 .expect("Write to cursor cache")
567 .take();
568
569 self.0 = Some(Arc::new(internal));
570 }
571
572 fn highlight<H: Highlighter>(
573 &mut self,
574 font: Self::Font,
575 highlighter: &mut H,
576 format_highlight: impl Fn(&H::Highlight) -> highlighter::Format<Self::Font>,
577 ) {
578 let internal = self.internal();
579 let buffer = buffer_from_editor(&internal.editor);
580
581 let scroll = buffer.scroll();
582 let mut window = (internal.bounds.height / buffer.metrics().line_height)
583 .ceil() as i32;
584
585 let last_visible_line = buffer.lines[scroll.line..]
586 .iter()
587 .enumerate()
588 .find_map(|(i, line)| {
589 let visible_lines = line
590 .layout_opt()
591 .as_ref()
592 .expect("Line layout should be cached")
593 .len() as i32;
594
595 if window > visible_lines {
596 window -= visible_lines;
597 None
598 } else {
599 Some(scroll.line + i)
600 }
601 })
602 .unwrap_or(buffer.lines.len().saturating_sub(1));
603
604 let current_line = highlighter.current_line();
605
606 if current_line > last_visible_line {
607 return;
608 }
609
610 let editor =
611 self.0.take().expect("Editor should always be initialized");
612
613 let mut internal = Arc::try_unwrap(editor)
614 .expect("Editor cannot have multiple strong references");
615
616 let mut font_system =
617 text::font_system().write().expect("Write font system");
618
619 let attributes = text::to_attributes(font);
620
621 for line in &mut buffer_mut_from_editor(&mut internal.editor).lines
622 [current_line..=last_visible_line]
623 {
624 let mut list = cosmic_text::AttrsList::new(&attributes);
625
626 for (range, highlight) in highlighter.highlight_line(line.text()) {
627 let format = format_highlight(&highlight);
628
629 if format.color.is_some() || format.font.is_some() {
630 list.add_span(
631 range,
632 &cosmic_text::Attrs {
633 color_opt: format.color.map(text::to_color),
634 ..if let Some(font) = format.font {
635 text::to_attributes(font)
636 } else {
637 attributes.clone()
638 }
639 },
640 );
641 }
642 }
643
644 let _ = line.set_attrs_list(list);
645 }
646
647 internal.editor.shape_as_needed(font_system.raw(), false);
648
649 self.0 = Some(Arc::new(internal));
650 }
651}
652
653impl Default for Editor {
654 fn default() -> Self {
655 Self(Some(Arc::new(Internal::default())))
656 }
657}
658
659impl PartialEq for Internal {
660 fn eq(&self, other: &Self) -> bool {
661 self.font == other.font
662 && self.bounds == other.bounds
663 && buffer_from_editor(&self.editor).metrics()
664 == buffer_from_editor(&other.editor).metrics()
665 }
666}
667
668impl Default for Internal {
669 fn default() -> Self {
670 Self {
671 editor: cosmic_text::Editor::new(cosmic_text::Buffer::new_empty(
672 cosmic_text::Metrics {
673 font_size: 1.0,
674 line_height: 1.0,
675 },
676 )),
677 cursor: RwLock::new(None),
678 font: Font::default(),
679 bounds: Size::ZERO,
680 topmost_line_changed: None,
681 version: text::Version::default(),
682 }
683 }
684}
685
686impl fmt::Debug for Internal {
687 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
688 f.debug_struct("Internal")
689 .field("font", &self.font)
690 .field("bounds", &self.bounds)
691 .finish()
692 }
693}
694
695#[derive(Debug, Clone)]
697pub struct Weak {
698 raw: sync::Weak<Internal>,
699 pub bounds: Size,
701}
702
703impl Weak {
704 pub fn upgrade(&self) -> Option<Editor> {
706 self.raw.upgrade().map(Some).map(Editor)
707 }
708}
709
710impl PartialEq for Weak {
711 fn eq(&self, other: &Self) -> bool {
712 match (self.raw.upgrade(), other.raw.upgrade()) {
713 (Some(p1), Some(p2)) => p1 == p2,
714 _ => false,
715 }
716 }
717}
718
719fn highlight_line(
720 line: &cosmic_text::BufferLine,
721 from: usize,
722 to: usize,
723) -> impl Iterator<Item = (f32, f32)> + '_ {
724 let layout = line.layout_opt().map(Vec::as_slice).unwrap_or_default();
725
726 layout.iter().map(move |visual_line| {
727 let start = visual_line
728 .glyphs
729 .first()
730 .map(|glyph| glyph.start)
731 .unwrap_or(0);
732 let end = visual_line
733 .glyphs
734 .last()
735 .map(|glyph| glyph.end)
736 .unwrap_or(0);
737
738 let range = start.max(from)..end.min(to);
739
740 if range.is_empty() {
741 (0.0, 0.0)
742 } else if range.start == start && range.end == end {
743 (0.0, visual_line.w)
744 } else {
745 let first_glyph = visual_line
746 .glyphs
747 .iter()
748 .position(|glyph| range.start <= glyph.start)
749 .unwrap_or(0);
750
751 let mut glyphs = visual_line.glyphs.iter();
752
753 let x =
754 glyphs.by_ref().take(first_glyph).map(|glyph| glyph.w).sum();
755
756 let width: f32 = glyphs
757 .take_while(|glyph| range.end > glyph.start)
758 .map(|glyph| glyph.w)
759 .sum();
760
761 (x, width)
762 }
763 })
764}
765
766fn visual_lines_offset(line: usize, buffer: &cosmic_text::Buffer) -> i32 {
767 let scroll = buffer.scroll();
768
769 let start = scroll.line.min(line);
770 let end = scroll.line.max(line);
771
772 let visual_lines_offset: usize = buffer.lines[start..]
773 .iter()
774 .take(end - start)
775 .map(|line| line.layout_opt().map(Vec::len).unwrap_or_default())
776 .sum();
777
778 visual_lines_offset as i32 * if scroll.line < line { 1 } else { -1 }
779}
780
781fn to_motion(motion: Motion) -> cosmic_text::Motion {
782 match motion {
783 Motion::Left => cosmic_text::Motion::Left,
784 Motion::Right => cosmic_text::Motion::Right,
785 Motion::Up => cosmic_text::Motion::Up,
786 Motion::Down => cosmic_text::Motion::Down,
787 Motion::WordLeft => cosmic_text::Motion::LeftWord,
788 Motion::WordRight => cosmic_text::Motion::RightWord,
789 Motion::Home => cosmic_text::Motion::Home,
790 Motion::End => cosmic_text::Motion::End,
791 Motion::PageUp => cosmic_text::Motion::PageUp,
792 Motion::PageDown => cosmic_text::Motion::PageDown,
793 Motion::DocumentStart => cosmic_text::Motion::BufferStart,
794 Motion::DocumentEnd => cosmic_text::Motion::BufferEnd,
795 }
796}
797
798fn buffer_from_editor<'a, 'b>(
799 editor: &'a impl cosmic_text::Edit<'b>,
800) -> &'a cosmic_text::Buffer
801where
802 'b: 'a,
803{
804 match editor.buffer_ref() {
805 cosmic_text::BufferRef::Owned(buffer) => buffer,
806 cosmic_text::BufferRef::Borrowed(buffer) => buffer,
807 cosmic_text::BufferRef::Arc(buffer) => buffer,
808 }
809}
810
811fn buffer_mut_from_editor<'a, 'b>(
812 editor: &'a mut impl cosmic_text::Edit<'b>,
813) -> &'a mut cosmic_text::Buffer
814where
815 'b: 'a,
816{
817 match editor.buffer_ref_mut() {
818 cosmic_text::BufferRef::Owned(buffer) => buffer,
819 cosmic_text::BufferRef::Borrowed(buffer) => buffer,
820 cosmic_text::BufferRef::Arc(_buffer) => unreachable!(),
821 }
822}