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