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