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