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