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