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