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