1use crate::core::text::editor::{self, Action, Cursor, Direction, Edit, Motion, Selection};
3use crate::core::text::highlighter;
4use crate::core::text::{Alignment, LineHeight, Parser, Position, Wrapping};
5use crate::core::{Font, Pixels, Point, Rectangle, Size};
6use crate::text;
7
8use cosmic_text::Edit as _;
9
10use std::borrow::Cow;
11use std::fmt;
12use std::sync::{self, Arc, RwLock};
13
14#[derive(Debug, PartialEq)]
16pub struct Editor(Option<Arc<Internal>>);
17
18struct Internal {
19 editor: cosmic_text::Editor<'static>,
20 selection: RwLock<Option<Selection>>,
21 history: History,
22 font: Font,
23 bounds: Size,
24 alignment: Alignment,
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 fn with_text(text: &str) -> Self {
86 let mut buffer = cosmic_text::Buffer::new_empty(cosmic_text::Metrics {
87 font_size: 1.0,
88 line_height: 1.0,
89 });
90
91 let mut font_system = text::font_system().write().expect("Write font system");
92
93 buffer.set_text(
94 text,
95 &cosmic_text::Attrs::new(),
96 cosmic_text::Shaping::Advanced,
97 None,
98 );
99 buffer.shape_until_scroll(font_system.raw(), false);
100
101 Editor(Some(Arc::new(Internal {
102 editor: cosmic_text::Editor::new(buffer),
103 version: font_system.version(),
104 ..Default::default()
105 })))
106 }
107
108 fn is_empty(&self) -> bool {
109 let buffer = self.buffer();
110
111 buffer.lines.is_empty() || (buffer.lines.len() == 1 && buffer.lines[0].text().is_empty())
112 }
113
114 fn line(&self, index: usize) -> Option<editor::Line<'_>> {
115 self.buffer().lines.get(index).map(|line| editor::Line {
116 text: Cow::Borrowed(line.text()),
117 ending: match line.ending() {
118 cosmic_text::LineEnding::Lf => editor::LineEnding::Lf,
119 cosmic_text::LineEnding::CrLf => editor::LineEnding::CrLf,
120 cosmic_text::LineEnding::Cr => editor::LineEnding::Cr,
121 cosmic_text::LineEnding::LfCr => editor::LineEnding::LfCr,
122 cosmic_text::LineEnding::None => editor::LineEnding::None,
123 },
124 })
125 }
126
127 fn line_count(&self) -> usize {
128 self.buffer().lines.len()
129 }
130
131 fn copy(&self) -> Option<String> {
132 self.internal().editor.copy_selection()
133 }
134
135 fn selection(&self) -> editor::Selection {
136 let internal = self.internal();
137
138 if let Ok(Some(cursor)) = internal.selection.read().as_deref() {
139 return cursor.clone();
140 }
141
142 let cursor = internal.editor.cursor();
143 let buffer = buffer_from_editor(&internal.editor);
144 let scroll = buffer.scroll();
145
146 let cursor = match internal.editor.selection_bounds() {
147 Some((start, end)) => {
148 let line_height = buffer.metrics().line_height;
149 let selected_lines = end.line - start.line + 1;
150
151 let visual_lines_offset = visual_lines_offset(start.line, buffer);
152
153 let regions = buffer
154 .lines
155 .iter()
156 .skip(start.line)
157 .take(selected_lines)
158 .enumerate()
159 .flat_map(|(i, line)| {
160 highlight_line(
161 line,
162 if i == 0 { start.index } else { 0 },
163 if i == selected_lines - 1 {
164 end.index
165 } else {
166 line.text().len()
167 },
168 )
169 })
170 .enumerate()
171 .filter_map(|(visual_line, (x, width))| {
172 if width > 0.0 {
173 Some(
174 Rectangle {
175 x: x - scroll.horizontal,
176 width,
177 y: (visual_line as i32 + visual_lines_offset) as f32
178 * line_height
179 - scroll.vertical,
180 height: line_height,
181 } * (1.0 / internal.hint_factor),
182 )
183 } else {
184 None
185 }
186 })
187 .collect();
188
189 Selection::Range(regions)
190 }
191 _ => {
192 let line_height = buffer.metrics().line_height;
193
194 let visual_lines_offset = visual_lines_offset(cursor.line, buffer);
195
196 let line = buffer
197 .lines
198 .get(cursor.line)
199 .expect("Cursor line should be present");
200
201 let layout = line.layout_opt().expect("Line layout should be cached");
202
203 let empty_offset = match internal.alignment {
204 Alignment::Default | Alignment::Left | Alignment::Justified => 0.0,
205 Alignment::Center => internal.bounds.width / 2.0,
206 Alignment::Right => internal.bounds.width,
207 };
208
209 let (visual_line, offset) = layout
210 .iter()
211 .enumerate()
212 .find_map(|(i, line)| {
213 let (start, offset) = line
214 .glyphs
215 .first()
216 .map(|glyph| (glyph.start, glyph.x))
217 .unwrap_or((0, empty_offset));
218
219 let end = line.glyphs.last().map(|glyph| glyph.end).unwrap_or(0);
220
221 let is_cursor_before_start = start > cursor.index;
222
223 let is_cursor_before_end = match cursor.affinity {
224 cosmic_text::Affinity::Before => cursor.index <= end,
225 cosmic_text::Affinity::After => cursor.index < end,
226 };
227
228 if is_cursor_before_start {
229 Some((i - 1, layout[i - 1].w + offset))
238 } else if is_cursor_before_end {
239 let x: f32 = line
240 .glyphs
241 .iter()
242 .take_while(|glyph| cursor.index > glyph.start)
243 .map(|glyph| glyph.w)
244 .sum();
245
246 Some((i, x + offset))
247 } else {
248 None
249 }
250 })
251 .unwrap_or_else(|| {
252 (
253 layout.len().saturating_sub(1),
254 layout
255 .last()
256 .map(|line| {
257 line.w
258 + line
259 .glyphs
260 .first()
261 .map(|glyph| glyph.x)
262 .unwrap_or(empty_offset)
263 })
264 .unwrap_or(empty_offset),
265 )
266 });
267
268 Selection::Caret(Point::new(
269 (offset - scroll.horizontal) / internal.hint_factor,
270 ((visual_lines_offset + visual_line as i32) as f32 * line_height
271 - scroll.vertical)
272 / internal.hint_factor,
273 ))
274 }
275 };
276
277 *internal.selection.write().expect("Write to cursor cache") = Some(cursor.clone());
278
279 cursor
280 }
281
282 fn cursor(&self) -> Cursor {
283 let editor = &self.internal().editor;
284
285 let position = {
286 let cursor = editor.cursor();
287
288 Position {
289 line: cursor.line,
290 index: cursor.index,
291 }
292 };
293
294 let selection = match editor.selection() {
295 cosmic_text::Selection::None => None,
296 cosmic_text::Selection::Normal(cursor)
297 | cosmic_text::Selection::Line(cursor)
298 | cosmic_text::Selection::Word(cursor) => Some(Position {
299 line: cursor.line,
300 index: cursor.index,
301 }),
302 };
303
304 Cursor {
305 position,
306 selection,
307 }
308 }
309
310 fn perform(&mut self, action: Action) {
311 let mut font_system = text::font_system().write().expect("Write font system");
312
313 self.with_internal_mut(|internal| {
314 let editor = &mut internal.editor;
315
316 match action {
317 Action::Move(motion) => {
319 if let Some((start, end)) = editor.selection_bounds() {
320 editor.set_selection(cosmic_text::Selection::None);
321
322 match motion {
323 Motion::Home
326 | Motion::End
327 | Motion::DocumentStart
328 | Motion::DocumentEnd => {
329 editor.action(
330 font_system.raw(),
331 cosmic_text::Action::Motion(to_motion(motion)),
332 );
333 }
334 _ => editor.set_cursor(match motion.direction() {
336 Direction::Left => start,
337 Direction::Right => end,
338 }),
339 }
340 } else {
341 editor.action(
342 font_system.raw(),
343 cosmic_text::Action::Motion(to_motion(motion)),
344 );
345 }
346
347 if buffer_from_editor(editor).wrap() == cosmic_text::Wrap::None {
348 let cursor = cosmic_text::Cursor {
349 affinity: cosmic_text::Affinity::Before,
350 ..editor.cursor()
351 };
352
353 editor.set_cursor(cursor);
354 }
355
356 shape_until_cursor(editor, &mut font_system.raw);
357 }
358
359 Action::Select(motion) => {
361 let cursor = editor.cursor();
362
363 if editor.selection_bounds().is_none() {
364 editor.set_selection(cosmic_text::Selection::Normal(cursor));
365 }
366
367 editor.action(
368 font_system.raw(),
369 cosmic_text::Action::Motion(to_motion(motion)),
370 );
371
372 if let Some((start, end)) = editor.selection_bounds()
374 && start.line == end.line
375 && start.index == end.index
376 {
377 editor.set_selection(cosmic_text::Selection::None);
378 }
379
380 shape_until_cursor(editor, &mut font_system.raw);
381 }
382 Action::SelectWord => {
383 let cursor = editor.cursor();
384
385 editor.set_selection(cosmic_text::Selection::Word(cursor));
386 }
387 Action::SelectLine => {
388 let cursor = editor.cursor();
389
390 editor.set_selection(cosmic_text::Selection::Line(cursor));
391 }
392 Action::SelectAll => {
393 let buffer = buffer_from_editor(editor);
394
395 if buffer.lines.len() > 1
396 || buffer
397 .lines
398 .first()
399 .is_some_and(|line| !line.text().is_empty())
400 {
401 let cursor = editor.cursor();
402
403 editor.set_selection(cosmic_text::Selection::Normal(cosmic_text::Cursor {
404 line: 0,
405 index: 0,
406 ..cursor
407 }));
408
409 editor.action(
410 font_system.raw(),
411 cosmic_text::Action::Motion(cosmic_text::Motion::BufferEnd),
412 );
413 }
414 }
415
416 Action::Edit(edit) => {
418 let lines_before_edit = buffer_from_editor(editor).lines.len();
419
420 let topmost_line_before_edit = editor
421 .selection_bounds()
422 .map(|(start, _)| start)
423 .unwrap_or_else(|| editor.cursor())
424 .line;
425
426 if !matches!(edit, Edit::Undo | Edit::Redo) {
427 editor.start_change();
428 }
429
430 match edit {
431 Edit::Insert(c) => {
432 editor.action(font_system.raw(), cosmic_text::Action::Insert(c));
433 }
434 Edit::Paste(text) => {
435 editor.insert_string(&text, None);
436 }
437 Edit::Indent => {
438 editor.action(font_system.raw(), cosmic_text::Action::Indent);
439 }
440 Edit::Unindent => {
441 editor.action(font_system.raw(), cosmic_text::Action::Unindent);
442 }
443 Edit::Enter => {
444 editor.action(font_system.raw(), cosmic_text::Action::Enter);
445 }
446 Edit::Backspace => {
447 editor.action(font_system.raw(), cosmic_text::Action::Backspace);
448 }
449 Edit::BackspaceWord => {
450 if editor.selection() == cosmic_text::Selection::None {
451 editor
452 .set_selection(cosmic_text::Selection::Normal(editor.cursor()));
453
454 editor.action(
455 font_system.raw(),
456 cosmic_text::Action::Motion(cosmic_text::Motion::PreviousWord),
457 );
458 }
459
460 editor.action(font_system.raw(), cosmic_text::Action::Backspace);
461 }
462 Edit::BackspaceLine => {
463 if editor.selection() == cosmic_text::Selection::None {
464 editor
465 .set_selection(cosmic_text::Selection::Normal(editor.cursor()));
466
467 editor.action(
468 font_system.raw(),
469 cosmic_text::Action::Motion(cosmic_text::Motion::Home),
470 );
471 }
472
473 editor.action(font_system.raw(), cosmic_text::Action::Backspace);
474 }
475 Edit::Delete => {
476 editor.action(font_system.raw(), cosmic_text::Action::Delete);
477 }
478 Edit::DeleteWord => {
479 if editor.selection() == cosmic_text::Selection::None {
480 editor
481 .set_selection(cosmic_text::Selection::Normal(editor.cursor()));
482
483 editor.action(
484 font_system.raw(),
485 cosmic_text::Action::Motion(cosmic_text::Motion::NextWord),
486 );
487 }
488
489 editor.action(font_system.raw(), cosmic_text::Action::Delete);
490 }
491 Edit::DeleteLine => {
492 if editor.selection() == cosmic_text::Selection::None {
493 editor
494 .set_selection(cosmic_text::Selection::Normal(editor.cursor()));
495
496 editor.action(
497 font_system.raw(),
498 cosmic_text::Action::Motion(cosmic_text::Motion::End),
499 );
500 }
501
502 editor.action(font_system.raw(), cosmic_text::Action::Delete);
503 }
504 Edit::Undo => {
505 if let Some(change) = internal.history.undo() {
506 let mut change = change.clone();
507 change.reverse();
508
509 let _ = editor.apply_change(&change);
510 }
511 }
512 Edit::Redo => {
513 if let Some(change) = internal.history.redo() {
514 let _ = editor.apply_change(change);
515 }
516 }
517 }
518
519 let lines_after_edit = buffer_from_editor(editor).lines.len();
520
521 if lines_after_edit > lines_before_edit {
522 let align = text::to_align(internal.alignment);
523
524 for line in &mut buffer_mut_from_editor(editor).lines {
525 let _ = line.set_align(align);
526 }
527 }
528
529 let cursor = editor.cursor();
530 let selection_start = editor
531 .selection_bounds()
532 .map(|(start, _)| start)
533 .unwrap_or(cursor);
534
535 internal.topmost_line_changed =
536 Some(selection_start.line.min(topmost_line_before_edit));
537
538 shape_until_cursor(editor, &mut font_system.raw);
539 }
540
541 Action::Click(position, kind) => {
543 let scroll = buffer_from_editor(editor).scroll();
544
545 let x = ((position.x + scroll.horizontal) * internal.hint_factor) as i32;
546 let y = (position.y * internal.hint_factor) as i32;
547
548 editor.action(
549 font_system.raw(),
550 match kind {
551 iced_core::mouse::click::Kind::Single => {
552 cosmic_text::Action::Click { x, y }
553 }
554 iced_core::mouse::click::Kind::Double => {
555 cosmic_text::Action::DoubleClick { x, y }
556 }
557 iced_core::mouse::click::Kind::Triple => {
558 cosmic_text::Action::TripleClick { x, y }
559 }
560 },
561 );
562
563 shape_until_cursor(editor, &mut font_system.raw);
564 }
565 Action::Drag(position) => {
566 let scroll = buffer_from_editor(editor).scroll();
567
568 editor.action(
569 font_system.raw(),
570 cosmic_text::Action::Drag {
571 x: ((position.x + scroll.horizontal) * internal.hint_factor) as i32,
572 y: (position.y * internal.hint_factor) as i32,
573 },
574 );
575
576 if let Some((start, end)) = editor.selection_bounds()
578 && start.line == end.line
579 && start.index == end.index
580 {
581 editor.set_selection(cosmic_text::Selection::None);
582 }
583
584 shape_until_cursor(editor, &mut font_system.raw);
585 }
586 Action::Scroll { lines } => {
587 editor.action(
588 font_system.raw(),
589 cosmic_text::Action::Scroll {
590 pixels: lines as f32 * buffer_from_editor(editor).metrics().line_height,
591 },
592 );
593
594 buffer_mut_from_editor(editor).shape_until_scroll(&mut font_system.raw, false);
595 }
596 }
597
598 if let Some(change) = editor.finish_change()
599 && !change.items.is_empty()
600 {
601 internal.history.push(change);
602 }
603 });
604 }
605
606 fn move_to(&mut self, cursor: Cursor) {
607 self.with_internal_mut(|internal| {
608 internal.editor.set_cursor(cosmic_text::Cursor {
610 line: cursor.position.line,
611 index: cursor.position.index,
612 affinity: cosmic_text::Affinity::Before,
613 });
614
615 if let Some(selection) = cursor.selection {
616 internal
617 .editor
618 .set_selection(cosmic_text::Selection::Normal(cosmic_text::Cursor {
619 line: selection.line,
620 index: selection.index,
621 affinity: cosmic_text::Affinity::Before,
622 }));
623 } else {
624 internal.editor.set_selection(cosmic_text::Selection::None);
625 }
626 });
627 }
628
629 fn bounds(&self) -> Size {
630 self.internal().bounds
631 }
632
633 fn min_bounds(&self) -> Size {
634 let internal = self.internal();
635
636 let (bounds, _has_rtl) = text::measure(buffer_from_editor(&internal.editor));
637
638 bounds * (1.0 / internal.hint_factor)
639 }
640
641 fn hint_factor(&self) -> Option<f32> {
642 let internal = self.internal();
643
644 internal.hint.then_some(internal.hint_factor)
645 }
646
647 fn update(
648 &mut self,
649 new_bounds: Size,
650 new_font: Font,
651 new_size: Pixels,
652 new_line_height: LineHeight,
653 new_wrapping: Wrapping,
654 new_alignment: Alignment,
655 new_hint_factor: Option<f32>,
656 new_parser: &mut impl Parser,
657 ) {
658 self.with_internal_mut(|internal| {
659 let mut font_system = text::font_system().write().expect("Write font system");
660
661 let buffer = buffer_mut_from_editor(&mut internal.editor);
662
663 if font_system.version() != internal.version {
664 log::trace!("Updating `FontSystem` of `Editor`...");
665
666 for line in buffer.lines.iter_mut() {
667 line.reset();
668 }
669
670 internal.version = font_system.version();
671 internal.topmost_line_changed = Some(0);
672 }
673
674 if new_font != internal.font {
675 log::trace!("Updating font of `Editor`...");
676
677 for line in buffer.lines.iter_mut() {
678 let _ = line.set_attrs_list(cosmic_text::AttrsList::new(&text::to_attributes(
679 new_font,
680 )));
681 }
682
683 internal.font = new_font;
684 internal.topmost_line_changed = Some(0);
685 }
686
687 let metrics = buffer.metrics();
688 let new_line_height = new_line_height.to_absolute(new_size);
689 let mut hinting_changed = false;
690
691 let new_hint_factor = text::hint_factor(new_size, new_hint_factor);
692
693 if new_hint_factor != internal.hint.then_some(internal.hint_factor) {
694 internal.hint = new_hint_factor.is_some();
695 internal.hint_factor = new_hint_factor.unwrap_or(1.0);
696
697 buffer.set_hinting(if internal.hint {
698 cosmic_text::Hinting::Enabled
699 } else {
700 cosmic_text::Hinting::Disabled
701 });
702
703 hinting_changed = true;
704 }
705
706 if new_size.0 != metrics.font_size
707 || new_line_height.0 != metrics.line_height
708 || hinting_changed
709 {
710 log::trace!("Updating `Metrics` of `Editor`...");
711
712 buffer.set_metrics(cosmic_text::Metrics::new(
713 new_size.0 * internal.hint_factor,
714 new_line_height.0 * internal.hint_factor,
715 ));
716 }
717
718 let new_wrap = text::to_wrap(new_wrapping);
719
720 if new_wrap != buffer.wrap() {
721 log::trace!("Updating `Wrap` strategy of `Editor`...");
722
723 buffer.set_wrap(new_wrap);
724 }
725
726 if new_bounds != internal.bounds || hinting_changed {
727 log::trace!("Updating size of `Editor`...");
728
729 buffer.set_size(
730 Some(new_bounds.width * internal.hint_factor),
731 Some(new_bounds.height * internal.hint_factor),
732 );
733
734 if internal.bounds == Size::ZERO {
735 buffer.set_scroll(cosmic_text::Scroll::default());
736 }
737
738 internal.bounds = new_bounds;
739 }
740
741 if new_alignment != internal.alignment {
742 let new_align = text::to_align(new_alignment);
743
744 for line in &mut buffer.lines {
745 let _ = line.set_align(new_align);
746 }
747
748 internal.alignment = new_alignment;
749 }
750
751 buffer.shape_until_scroll(font_system.raw(), false);
752
753 if let Some(topmost_line_changed) = internal.topmost_line_changed.take() {
754 log::trace!("Notifying parser of line change: {topmost_line_changed}");
755 new_parser.change_line(topmost_line_changed);
756 }
757
758 internal.editor.shape_as_needed(font_system.raw(), false);
759 });
760 }
761
762 fn overwrite(&mut self, new_text: &str) {
763 self.with_internal_mut(|internal| {
764 let mut font_system = text::font_system().write().expect("Write font system");
765
766 let cursor = internal.editor.cursor();
767 let buffer = buffer_mut_from_editor(&mut internal.editor);
768
769 buffer.set_text(
770 new_text,
771 &cosmic_text::Attrs::new(),
772 cosmic_text::Shaping::Advanced,
773 text::to_align(internal.alignment),
774 );
775
776 let line = cursor.line.min(buffer.lines.len().saturating_sub(1));
777
778 let new_cursor = cosmic_text::Cursor {
779 line,
780 index: buffer
781 .lines
782 .get(line)
783 .map(|line| line.text().floor_char_boundary(cursor.index))
784 .unwrap_or_default(),
785 affinity: if !buffer.lines.is_empty() {
786 cursor.affinity
787 } else {
788 cosmic_text::Affinity::Before
789 },
790 };
791
792 internal.editor.set_cursor(new_cursor);
793
794 shape_until_cursor(&mut internal.editor, &mut font_system.raw);
795 });
796 }
797
798 fn highlight<P: Parser>(
799 &mut self,
800 font: Font,
801 parser: &mut P,
802 highlight: impl Fn(P::Output) -> highlighter::Style,
803 ) {
804 let internal = self.internal();
805 let buffer = buffer_from_editor(&internal.editor);
806
807 let scroll = buffer.scroll();
808 let mut window = (internal.bounds.height * internal.hint_factor
809 / buffer.metrics().line_height)
810 .ceil() as i32;
811
812 let last_visible_line = buffer.lines[scroll.line..]
813 .iter()
814 .enumerate()
815 .find_map(|(i, line)| {
816 let visible_lines = line
817 .layout_opt()
818 .as_ref()
819 .expect("Line layout should be cached")
820 .len() as i32;
821
822 if window > visible_lines {
823 window -= visible_lines;
824 None
825 } else {
826 Some(scroll.line + i)
827 }
828 })
829 .unwrap_or(buffer.lines.len().saturating_sub(1));
830
831 let current_line = parser.current_line();
832
833 if current_line > last_visible_line {
834 return;
835 }
836
837 let editor = self.0.take().expect("Editor should always be initialized");
838
839 let mut internal =
840 Arc::try_unwrap(editor).expect("Editor cannot have multiple strong references");
841
842 let mut font_system = text::font_system().write().expect("Write font system");
843
844 let attributes = text::to_attributes(font);
845
846 for line in &mut buffer_mut_from_editor(&mut internal.editor).lines
847 [current_line..=last_visible_line]
848 {
849 let mut list = cosmic_text::AttrsList::new(&attributes);
850
851 for (range, output) in parser.parse_line(line.text()) {
852 let format = highlight(output);
853
854 if format.color.is_some() || format.style.is_some() {
855 list.add_span(
856 range,
857 &cosmic_text::Attrs {
858 color_opt: format.color.map(text::to_color),
859 ..if let Some(style) = format.style {
860 cosmic_text::Attrs {
861 style: text::to_style(style),
862 ..attributes.clone()
863 }
864 } else {
865 attributes.clone()
866 }
867 },
868 );
869 }
870 }
871
872 let _ = line.set_attrs_list(list);
873 }
874
875 internal.editor.shape_as_needed(font_system.raw(), false);
876
877 self.0 = Some(Arc::new(internal));
878 }
879
880 fn text_size(&self) -> Pixels {
881 let internal = self.internal();
882
883 Pixels(buffer_from_editor(&internal.editor).metrics().font_size / internal.hint_factor)
884 }
885
886 fn line_height(&self) -> LineHeight {
887 let internal = self.internal();
888
889 LineHeight::Absolute(Pixels(
890 buffer_from_editor(&internal.editor).metrics().line_height / internal.hint_factor,
891 ))
892 }
893
894 fn font(&self) -> Font {
895 self.internal().font
896 }
897}
898
899impl Default for Editor {
900 fn default() -> Self {
901 Self(Some(Arc::new(Internal::default())))
902 }
903}
904
905impl PartialEq for Internal {
906 fn eq(&self, other: &Self) -> bool {
907 self.font == other.font
908 && self.bounds == other.bounds
909 && buffer_from_editor(&self.editor).metrics()
910 == buffer_from_editor(&other.editor).metrics()
911 }
912}
913
914impl Default for Internal {
915 fn default() -> Self {
916 Self {
917 editor: cosmic_text::Editor::new(cosmic_text::Buffer::new_empty(
918 cosmic_text::Metrics {
919 font_size: 1.0,
920 line_height: 1.0,
921 },
922 )),
923 selection: RwLock::new(None),
924 history: History::new(),
925 font: Font::default(),
926 bounds: Size::ZERO,
927 alignment: Alignment::Default,
928 topmost_line_changed: None,
929 hint: false,
930 hint_factor: 1.0,
931 version: text::Version::default(),
932 }
933 }
934}
935
936impl fmt::Debug for Internal {
937 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
938 f.debug_struct("Internal")
939 .field("font", &self.font)
940 .field("bounds", &self.bounds)
941 .finish()
942 }
943}
944
945#[derive(Debug, Clone)]
947pub struct Weak {
948 raw: sync::Weak<Internal>,
949 pub bounds: Size,
951}
952
953impl Weak {
954 pub fn upgrade(&self) -> Option<Editor> {
956 self.raw.upgrade().map(Some).map(Editor)
957 }
958}
959
960impl PartialEq for Weak {
961 fn eq(&self, other: &Self) -> bool {
962 match (self.raw.upgrade(), other.raw.upgrade()) {
963 (Some(p1), Some(p2)) => p1 == p2,
964 _ => false,
965 }
966 }
967}
968
969fn highlight_line(
970 line: &cosmic_text::BufferLine,
971 from: usize,
972 to: usize,
973) -> impl Iterator<Item = (f32, f32)> + '_ {
974 let layout = line.layout_opt().map(Vec::as_slice).unwrap_or_default();
975
976 layout.iter().map(move |visual_line| {
977 let (start, offset) = visual_line
978 .glyphs
979 .first()
980 .map(|glyph| (glyph.start, glyph.x))
981 .unwrap_or_default();
982
983 let end = visual_line
984 .glyphs
985 .last()
986 .map(|glyph| glyph.end)
987 .unwrap_or(0);
988
989 let range = start.max(from)..end.min(to);
990
991 if range.is_empty() {
992 (offset, 0.0)
993 } else if range.start == start && range.end == end {
994 (offset, visual_line.w)
995 } else {
996 let first_glyph = visual_line
997 .glyphs
998 .iter()
999 .position(|glyph| range.start <= glyph.start)
1000 .unwrap_or(0);
1001
1002 let mut glyphs = visual_line.glyphs.iter();
1003
1004 let x: f32 = glyphs.by_ref().take(first_glyph).map(|glyph| glyph.w).sum();
1005
1006 let width: f32 = glyphs
1007 .take_while(|glyph| range.end > glyph.start)
1008 .map(|glyph| glyph.w)
1009 .sum();
1010
1011 (x + offset, width)
1012 }
1013 })
1014}
1015
1016fn visual_lines_offset(line: usize, buffer: &cosmic_text::Buffer) -> i32 {
1017 let scroll = buffer.scroll();
1018
1019 let start = scroll.line.min(line);
1020 let end = scroll.line.max(line);
1021
1022 let visual_lines_offset: usize = buffer.lines[start..]
1023 .iter()
1024 .take(end - start)
1025 .map(|line| line.layout_opt().map(Vec::len).unwrap_or_default())
1026 .sum();
1027
1028 visual_lines_offset as i32 * if scroll.line < line { 1 } else { -1 }
1029}
1030
1031fn to_motion(motion: Motion) -> cosmic_text::Motion {
1032 match motion {
1033 Motion::Left => cosmic_text::Motion::Left,
1034 Motion::Right => cosmic_text::Motion::Right,
1035 Motion::Up => cosmic_text::Motion::Up,
1036 Motion::Down => cosmic_text::Motion::Down,
1037 Motion::WordLeft => cosmic_text::Motion::LeftWord,
1038 Motion::WordRight => cosmic_text::Motion::RightWord,
1039 Motion::Home => cosmic_text::Motion::Home,
1040 Motion::End => cosmic_text::Motion::End,
1041 Motion::PageUp => cosmic_text::Motion::PageUp,
1042 Motion::PageDown => cosmic_text::Motion::PageDown,
1043 Motion::DocumentStart => cosmic_text::Motion::BufferStart,
1044 Motion::DocumentEnd => cosmic_text::Motion::BufferEnd,
1045 }
1046}
1047
1048fn buffer_from_editor<'a, 'b>(editor: &'a impl cosmic_text::Edit<'b>) -> &'a cosmic_text::Buffer
1049where
1050 'b: 'a,
1051{
1052 match editor.buffer_ref() {
1053 cosmic_text::BufferRef::Owned(buffer) => buffer,
1054 cosmic_text::BufferRef::Borrowed(buffer) => buffer,
1055 cosmic_text::BufferRef::Arc(buffer) => buffer,
1056 }
1057}
1058
1059fn buffer_mut_from_editor<'a, 'b>(
1060 editor: &'a mut impl cosmic_text::Edit<'b>,
1061) -> &'a mut cosmic_text::Buffer
1062where
1063 'b: 'a,
1064{
1065 match editor.buffer_ref_mut() {
1066 cosmic_text::BufferRef::Owned(buffer) => buffer,
1067 cosmic_text::BufferRef::Borrowed(buffer) => buffer,
1068 cosmic_text::BufferRef::Arc(_buffer) => unreachable!(),
1069 }
1070}
1071
1072fn shape_until_cursor(
1073 editor: &mut cosmic_text::Editor<'static>,
1074 font_system: &mut cosmic_text::FontSystem,
1075) {
1076 let cursor = editor.cursor();
1077 let buffer = buffer_mut_from_editor(editor);
1078
1079 buffer.shape_until_cursor(font_system, cursor, false);
1080
1081 if let Some((x, _)) = editor.cursor_position() {
1082 let buffer = buffer_mut_from_editor(editor);
1083 let scroll = buffer.scroll();
1084 let (width, _) = buffer.size();
1085
1086 const CURSOR_WIDTH: f32 = 2.0; buffer.set_scroll(cosmic_text::Scroll {
1089 horizontal: scroll.horizontal
1090 + (x as f32 + CURSOR_WIDTH - scroll.horizontal - width.unwrap_or_default())
1091 .clamp(0.0, CURSOR_WIDTH),
1092 ..scroll
1093 });
1094 }
1095}
1096
1097#[derive(Default)]
1098struct History {
1099 changes: Vec<cosmic_text::Change>,
1100 current: usize,
1101}
1102
1103impl History {
1104 fn new() -> Self {
1105 Self::default()
1106 }
1107
1108 fn undo(&mut self) -> Option<&cosmic_text::Change> {
1109 if self.current == 0 {
1110 return None;
1111 }
1112
1113 self.current = self.current.saturating_sub(1);
1114 self.changes.get(self.current)
1115 }
1116
1117 fn redo(&mut self) -> Option<&cosmic_text::Change> {
1118 if self.current >= self.changes.len() {
1119 return None;
1120 }
1121
1122 let change = self.changes.get(self.current);
1123 self.current += 1;
1124 change
1125 }
1126
1127 fn push(&mut self, change: cosmic_text::Change) {
1128 self.changes.truncate(self.current);
1129 self.changes.push(change);
1130 self.current += 1;
1131 }
1132}