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