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 history: History,
24 font: Font,
25 bounds: Size,
26 topmost_line_changed: Option<usize>,
27 hint: bool,
28 hint_factor: f32,
29 version: text::Version,
30}
31
32impl Editor {
33 pub fn new() -> Self {
35 Self::default()
36 }
37
38 pub fn buffer(&self) -> &cosmic_text::Buffer {
40 buffer_from_editor(&self.internal().editor)
41 }
42
43 pub fn downgrade(&self) -> Weak {
49 let editor = self.internal();
50
51 Weak {
52 raw: Arc::downgrade(editor),
53 bounds: editor.bounds,
54 }
55 }
56
57 fn internal(&self) -> &Arc<Internal> {
58 self.0
59 .as_ref()
60 .expect("Editor should always be initialized")
61 }
62
63 fn with_internal_mut<T>(&mut self, f: impl FnOnce(&mut Internal) -> T) -> T {
64 let editor = self.0.take().expect("Editor should always be initialized");
65
66 let mut internal =
68 Arc::try_unwrap(editor).expect("Editor cannot have multiple strong references");
69
70 let _ = internal
72 .selection
73 .write()
74 .expect("Write to cursor cache")
75 .take();
76
77 let result = f(&mut internal);
78
79 self.0 = Some(Arc::new(internal));
80
81 result
82 }
83}
84
85impl editor::Editor for Editor {
86 type Font = Font;
87
88 fn with_text(text: &str) -> Self {
89 let mut buffer = cosmic_text::Buffer::new_empty(cosmic_text::Metrics {
90 font_size: 1.0,
91 line_height: 1.0,
92 });
93
94 let mut font_system = text::font_system().write().expect("Write font system");
95
96 buffer.set_text(
97 text,
98 &cosmic_text::Attrs::new(),
99 cosmic_text::Shaping::Advanced,
100 None,
101 );
102 buffer.shape_until_scroll(font_system.raw(), false);
103
104 Editor(Some(Arc::new(Internal {
105 editor: cosmic_text::Editor::new(buffer),
106 version: font_system.version(),
107 ..Default::default()
108 })))
109 }
110
111 fn is_empty(&self) -> bool {
112 let buffer = self.buffer();
113
114 buffer.lines.is_empty() || (buffer.lines.len() == 1 && buffer.lines[0].text().is_empty())
115 }
116
117 fn line(&self, index: usize) -> Option<editor::Line<'_>> {
118 self.buffer().lines.get(index).map(|line| editor::Line {
119 text: Cow::Borrowed(line.text()),
120 ending: match line.ending() {
121 cosmic_text::LineEnding::Lf => editor::LineEnding::Lf,
122 cosmic_text::LineEnding::CrLf => editor::LineEnding::CrLf,
123 cosmic_text::LineEnding::Cr => editor::LineEnding::Cr,
124 cosmic_text::LineEnding::LfCr => editor::LineEnding::LfCr,
125 cosmic_text::LineEnding::None => editor::LineEnding::None,
126 },
127 })
128 }
129
130 fn line_count(&self) -> usize {
131 self.buffer().lines.len()
132 }
133
134 fn copy(&self) -> Option<String> {
135 self.internal().editor.copy_selection()
136 }
137
138 fn selection(&self) -> editor::Selection {
139 let internal = self.internal();
140
141 if let Ok(Some(cursor)) = internal.selection.read().as_deref() {
142 return cursor.clone();
143 }
144
145 let cursor = internal.editor.cursor();
146 let buffer = buffer_from_editor(&internal.editor);
147 let scroll = buffer.scroll();
148
149 let cursor = match internal.editor.selection_bounds() {
150 Some((start, end)) => {
151 let line_height = buffer.metrics().line_height;
152 let selected_lines = end.line - start.line + 1;
153
154 let visual_lines_offset = visual_lines_offset(start.line, buffer);
155
156 let regions = buffer
157 .lines
158 .iter()
159 .skip(start.line)
160 .take(selected_lines)
161 .enumerate()
162 .flat_map(|(i, line)| {
163 highlight_line(
164 line,
165 if i == 0 { start.index } else { 0 },
166 if i == selected_lines - 1 {
167 end.index
168 } else {
169 line.text().len()
170 },
171 )
172 })
173 .enumerate()
174 .filter_map(|(visual_line, (x, width))| {
175 if width > 0.0 {
176 Some(
177 Rectangle {
178 x: x - scroll.horizontal,
179 width,
180 y: (visual_line as i32 + visual_lines_offset) as f32
181 * line_height
182 - scroll.vertical,
183 height: line_height,
184 } * (1.0 / internal.hint_factor),
185 )
186 } else {
187 None
188 }
189 })
190 .collect();
191
192 Selection::Range(regions)
193 }
194 _ => {
195 let line_height = buffer.metrics().line_height;
196
197 let visual_lines_offset = visual_lines_offset(cursor.line, buffer);
198
199 let line = buffer
200 .lines
201 .get(cursor.line)
202 .expect("Cursor line should be present");
203
204 let layout = line.layout_opt().expect("Line layout should be cached");
205
206 let mut lines = layout.iter().enumerate();
207
208 let (visual_line, offset) = lines
209 .find_map(|(i, line)| {
210 let start = line.glyphs.first().map(|glyph| glyph.start).unwrap_or(0);
211 let end = line.glyphs.last().map(|glyph| glyph.end).unwrap_or(0);
212
213 let is_cursor_before_start = start > cursor.index;
214
215 let is_cursor_before_end = match cursor.affinity {
216 cosmic_text::Affinity::Before => cursor.index <= end,
217 cosmic_text::Affinity::After => cursor.index < end,
218 };
219
220 if is_cursor_before_start {
221 Some((i - 1, layout[i - 1].w))
230 } else if is_cursor_before_end {
231 let offset = line
232 .glyphs
233 .iter()
234 .take_while(|glyph| cursor.index > glyph.start)
235 .map(|glyph| glyph.w)
236 .sum();
237
238 Some((i, offset))
239 } else {
240 None
241 }
242 })
243 .unwrap_or((
244 layout.len().saturating_sub(1),
245 layout.last().map(|line| line.w).unwrap_or(0.0),
246 ));
247
248 Selection::Caret(Point::new(
249 (offset - scroll.horizontal) / internal.hint_factor,
250 ((visual_lines_offset + visual_line as i32) as f32 * line_height
251 - scroll.vertical)
252 / internal.hint_factor,
253 ))
254 }
255 };
256
257 *internal.selection.write().expect("Write to cursor cache") = Some(cursor.clone());
258
259 cursor
260 }
261
262 fn cursor(&self) -> Cursor {
263 let editor = &self.internal().editor;
264
265 let position = {
266 let cursor = editor.cursor();
267
268 Position {
269 line: cursor.line,
270 column: cursor.index,
271 }
272 };
273
274 let selection = match editor.selection() {
275 cosmic_text::Selection::None => None,
276 cosmic_text::Selection::Normal(cursor)
277 | cosmic_text::Selection::Line(cursor)
278 | cosmic_text::Selection::Word(cursor) => Some(Position {
279 line: cursor.line,
280 column: cursor.index,
281 }),
282 };
283
284 Cursor {
285 position,
286 selection,
287 }
288 }
289
290 fn perform(&mut self, action: Action) {
291 let mut font_system = text::font_system().write().expect("Write font system");
292
293 self.with_internal_mut(|internal| {
294 let editor = &mut internal.editor;
295
296 match action {
297 Action::Move(motion) => {
299 if let Some((start, end)) = editor.selection_bounds() {
300 editor.set_selection(cosmic_text::Selection::None);
301
302 match motion {
303 Motion::Home
306 | Motion::End
307 | Motion::DocumentStart
308 | Motion::DocumentEnd => {
309 editor.action(
310 font_system.raw(),
311 cosmic_text::Action::Motion(to_motion(motion)),
312 );
313 }
314 _ => editor.set_cursor(match motion.direction() {
316 Direction::Left => start,
317 Direction::Right => end,
318 }),
319 }
320 } else {
321 editor.action(
322 font_system.raw(),
323 cosmic_text::Action::Motion(to_motion(motion)),
324 );
325 }
326
327 let cursor = cosmic_text::Cursor {
328 affinity: cosmic_text::Affinity::Before,
329 ..editor.cursor()
330 };
331
332 editor.set_cursor(cursor);
333 }
334
335 Action::Select(motion) => {
337 let cursor = editor.cursor();
338
339 if editor.selection_bounds().is_none() {
340 editor.set_selection(cosmic_text::Selection::Normal(cursor));
341 }
342
343 editor.action(
344 font_system.raw(),
345 cosmic_text::Action::Motion(to_motion(motion)),
346 );
347
348 if let Some((start, end)) = editor.selection_bounds()
350 && start.line == end.line
351 && start.index == end.index
352 {
353 editor.set_selection(cosmic_text::Selection::None);
354 }
355 }
356 Action::SelectWord => {
357 let cursor = editor.cursor();
358
359 editor.set_selection(cosmic_text::Selection::Word(cursor));
360 }
361 Action::SelectLine => {
362 let cursor = editor.cursor();
363
364 editor.set_selection(cosmic_text::Selection::Line(cursor));
365 }
366 Action::SelectAll => {
367 let buffer = buffer_from_editor(editor);
368
369 if buffer.lines.len() > 1
370 || buffer
371 .lines
372 .first()
373 .is_some_and(|line| !line.text().is_empty())
374 {
375 let cursor = editor.cursor();
376
377 editor.set_selection(cosmic_text::Selection::Normal(cosmic_text::Cursor {
378 line: 0,
379 index: 0,
380 ..cursor
381 }));
382
383 editor.action(
384 font_system.raw(),
385 cosmic_text::Action::Motion(cosmic_text::Motion::BufferEnd),
386 );
387 }
388 }
389
390 Action::Edit(edit) => {
392 let topmost_line_before_edit = editor
393 .selection_bounds()
394 .map(|(start, _)| start)
395 .unwrap_or_else(|| editor.cursor())
396 .line;
397
398 if !matches!(edit, Edit::Undo | Edit::Redo) {
399 editor.start_change();
400 }
401
402 match edit {
403 Edit::Insert(c) => {
404 editor.action(font_system.raw(), cosmic_text::Action::Insert(c));
405 }
406 Edit::Paste(text) => {
407 editor.insert_string(&text, None);
408 }
409 Edit::Indent => {
410 editor.action(font_system.raw(), cosmic_text::Action::Indent);
411 }
412 Edit::Unindent => {
413 editor.action(font_system.raw(), cosmic_text::Action::Unindent);
414 }
415 Edit::Enter => {
416 editor.action(font_system.raw(), cosmic_text::Action::Enter);
417 }
418 Edit::Backspace => {
419 editor.action(font_system.raw(), cosmic_text::Action::Backspace);
420 }
421 Edit::Delete => {
422 editor.action(font_system.raw(), cosmic_text::Action::Delete);
423 }
424 Edit::Undo => {
425 if let Some(change) = internal.history.undo() {
426 let mut change = change.clone();
427 change.reverse();
428
429 let _ = editor.apply_change(&change);
430 }
431 }
432 Edit::Redo => {
433 if let Some(change) = internal.history.redo() {
434 let _ = editor.apply_change(change);
435 }
436 }
437 }
438
439 let cursor = editor.cursor();
440 let selection_start = editor
441 .selection_bounds()
442 .map(|(start, _)| start)
443 .unwrap_or(cursor);
444
445 internal.topmost_line_changed =
446 Some(selection_start.line.min(topmost_line_before_edit));
447 }
448
449 Action::Click(position) => {
451 let scroll = buffer_from_editor(editor).scroll();
452
453 editor.action(
454 font_system.raw(),
455 cosmic_text::Action::Click {
456 x: ((position.x + scroll.horizontal) * internal.hint_factor) as i32,
457 y: (position.y * internal.hint_factor) as i32,
458 },
459 );
460 }
461 Action::Drag(position) => {
462 let scroll = buffer_from_editor(editor).scroll();
463
464 editor.action(
465 font_system.raw(),
466 cosmic_text::Action::Drag {
467 x: ((position.x + scroll.horizontal) * internal.hint_factor) as i32,
468 y: (position.y * internal.hint_factor) as i32,
469 },
470 );
471
472 if let Some((start, end)) = editor.selection_bounds()
474 && start.line == end.line
475 && start.index == end.index
476 {
477 editor.set_selection(cosmic_text::Selection::None);
478 }
479 }
480 Action::Scroll { lines } => {
481 editor.action(
482 font_system.raw(),
483 cosmic_text::Action::Scroll {
484 pixels: lines as f32 * buffer_from_editor(editor).metrics().line_height,
485 },
486 );
487 }
488 }
489
490 if let Some(change) = editor.finish_change()
491 && !change.items.is_empty()
492 {
493 internal.history.push(change);
494 }
495
496 shape_until_cursor(editor, &mut font_system.raw);
497 });
498 }
499
500 fn move_to(&mut self, cursor: Cursor) {
501 self.with_internal_mut(|internal| {
502 internal.editor.set_cursor(cosmic_text::Cursor {
504 line: cursor.position.line,
505 index: cursor.position.column,
506 affinity: cosmic_text::Affinity::Before,
507 });
508
509 if let Some(selection) = cursor.selection {
510 internal
511 .editor
512 .set_selection(cosmic_text::Selection::Normal(cosmic_text::Cursor {
513 line: selection.line,
514 index: selection.column,
515 affinity: cosmic_text::Affinity::Before,
516 }));
517 }
518 });
519 }
520
521 fn bounds(&self) -> Size {
522 self.internal().bounds
523 }
524
525 fn min_bounds(&self) -> Size {
526 let internal = self.internal();
527
528 let (bounds, _has_rtl) = text::measure(buffer_from_editor(&internal.editor));
529
530 bounds * (1.0 / internal.hint_factor)
531 }
532
533 fn hint_factor(&self) -> Option<f32> {
534 let internal = self.internal();
535
536 internal.hint.then_some(internal.hint_factor)
537 }
538
539 fn update(
540 &mut self,
541 new_bounds: Size,
542 new_font: Font,
543 new_size: Pixels,
544 new_line_height: LineHeight,
545 new_wrapping: Wrapping,
546 new_hint_factor: Option<f32>,
547 new_highlighter: &mut impl Highlighter,
548 ) {
549 self.with_internal_mut(|internal| {
550 let mut font_system = text::font_system().write().expect("Write font system");
551
552 let buffer = buffer_mut_from_editor(&mut internal.editor);
553
554 if font_system.version() != internal.version {
555 log::trace!("Updating `FontSystem` of `Editor`...");
556
557 for line in buffer.lines.iter_mut() {
558 line.reset();
559 }
560
561 internal.version = font_system.version();
562 internal.topmost_line_changed = Some(0);
563 }
564
565 if new_font != internal.font {
566 log::trace!("Updating font of `Editor`...");
567
568 for line in buffer.lines.iter_mut() {
569 let _ = line.set_attrs_list(cosmic_text::AttrsList::new(&text::to_attributes(
570 new_font,
571 )));
572 }
573
574 internal.font = new_font;
575 internal.topmost_line_changed = Some(0);
576 }
577
578 let metrics = buffer.metrics();
579 let new_line_height = new_line_height.to_absolute(new_size);
580 let mut hinting_changed = false;
581
582 let new_hint_factor = text::hint_factor(new_size, new_hint_factor);
583
584 if new_hint_factor != internal.hint.then_some(internal.hint_factor) {
585 internal.hint = new_hint_factor.is_some();
586 internal.hint_factor = new_hint_factor.unwrap_or(1.0);
587
588 buffer.set_hinting(if internal.hint {
589 cosmic_text::Hinting::Enabled
590 } else {
591 cosmic_text::Hinting::Disabled
592 });
593
594 hinting_changed = true;
595 }
596
597 if new_size.0 != metrics.font_size
598 || new_line_height.0 != metrics.line_height
599 || hinting_changed
600 {
601 log::trace!("Updating `Metrics` of `Editor`...");
602
603 buffer.set_metrics(cosmic_text::Metrics::new(
604 new_size.0 * internal.hint_factor,
605 new_line_height.0 * internal.hint_factor,
606 ));
607 }
608
609 let new_wrap = text::to_wrap(new_wrapping);
610
611 if new_wrap != buffer.wrap() {
612 log::trace!("Updating `Wrap` strategy of `Editor`...");
613
614 buffer.set_wrap(new_wrap);
615 }
616
617 if new_bounds != internal.bounds || hinting_changed {
618 log::trace!("Updating size of `Editor`...");
619
620 buffer.set_size(
621 Some(new_bounds.width * internal.hint_factor),
622 Some(new_bounds.height * internal.hint_factor),
623 );
624
625 internal.bounds = new_bounds;
626 }
627
628 buffer.shape_until_scroll(font_system.raw(), false);
629
630 if let Some(topmost_line_changed) = internal.topmost_line_changed.take() {
631 log::trace!(
632 "Notifying highlighter of line \
633 change: {topmost_line_changed}"
634 );
635
636 new_highlighter.change_line(topmost_line_changed);
637 }
638
639 internal.editor.shape_as_needed(font_system.raw(), false);
640 });
641 }
642
643 fn highlight<H: Highlighter>(
644 &mut self,
645 font: Self::Font,
646 highlighter: &mut H,
647 format_highlight: impl Fn(&H::Highlight) -> highlighter::Format<Self::Font>,
648 ) {
649 let internal = self.internal();
650 let buffer = buffer_from_editor(&internal.editor);
651
652 let scroll = buffer.scroll();
653 let mut window = (internal.bounds.height * internal.hint_factor
654 / buffer.metrics().line_height)
655 .ceil() as i32;
656
657 let last_visible_line = buffer.lines[scroll.line..]
658 .iter()
659 .enumerate()
660 .find_map(|(i, line)| {
661 let visible_lines = line
662 .layout_opt()
663 .as_ref()
664 .expect("Line layout should be cached")
665 .len() as i32;
666
667 if window > visible_lines {
668 window -= visible_lines;
669 None
670 } else {
671 Some(scroll.line + i)
672 }
673 })
674 .unwrap_or(buffer.lines.len().saturating_sub(1));
675
676 let current_line = highlighter.current_line();
677
678 if current_line > last_visible_line {
679 return;
680 }
681
682 let editor = self.0.take().expect("Editor should always be initialized");
683
684 let mut internal =
685 Arc::try_unwrap(editor).expect("Editor cannot have multiple strong references");
686
687 let mut font_system = text::font_system().write().expect("Write font system");
688
689 let attributes = text::to_attributes(font);
690
691 for line in &mut buffer_mut_from_editor(&mut internal.editor).lines
692 [current_line..=last_visible_line]
693 {
694 let mut list = cosmic_text::AttrsList::new(&attributes);
695
696 for (range, highlight) in highlighter.highlight_line(line.text()) {
697 let format = format_highlight(&highlight);
698
699 if format.color.is_some() || format.font.is_some() {
700 list.add_span(
701 range,
702 &cosmic_text::Attrs {
703 color_opt: format.color.map(text::to_color),
704 ..if let Some(font) = format.font {
705 text::to_attributes(font)
706 } else {
707 attributes.clone()
708 }
709 },
710 );
711 }
712 }
713
714 let _ = line.set_attrs_list(list);
715 }
716
717 internal.editor.shape_as_needed(font_system.raw(), false);
718
719 self.0 = Some(Arc::new(internal));
720 }
721}
722
723impl Default for Editor {
724 fn default() -> Self {
725 Self(Some(Arc::new(Internal::default())))
726 }
727}
728
729impl PartialEq for Internal {
730 fn eq(&self, other: &Self) -> bool {
731 self.font == other.font
732 && self.bounds == other.bounds
733 && buffer_from_editor(&self.editor).metrics()
734 == buffer_from_editor(&other.editor).metrics()
735 }
736}
737
738impl Default for Internal {
739 fn default() -> Self {
740 Self {
741 editor: cosmic_text::Editor::new(cosmic_text::Buffer::new_empty(
742 cosmic_text::Metrics {
743 font_size: 1.0,
744 line_height: 1.0,
745 },
746 )),
747 selection: RwLock::new(None),
748 history: History::new(),
749 font: Font::default(),
750 bounds: Size::ZERO,
751 topmost_line_changed: None,
752 hint: false,
753 hint_factor: 1.0,
754 version: text::Version::default(),
755 }
756 }
757}
758
759impl fmt::Debug for Internal {
760 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
761 f.debug_struct("Internal")
762 .field("font", &self.font)
763 .field("bounds", &self.bounds)
764 .finish()
765 }
766}
767
768#[derive(Debug, Clone)]
770pub struct Weak {
771 raw: sync::Weak<Internal>,
772 pub bounds: Size,
774}
775
776impl Weak {
777 pub fn upgrade(&self) -> Option<Editor> {
779 self.raw.upgrade().map(Some).map(Editor)
780 }
781}
782
783impl PartialEq for Weak {
784 fn eq(&self, other: &Self) -> bool {
785 match (self.raw.upgrade(), other.raw.upgrade()) {
786 (Some(p1), Some(p2)) => p1 == p2,
787 _ => false,
788 }
789 }
790}
791
792fn highlight_line(
793 line: &cosmic_text::BufferLine,
794 from: usize,
795 to: usize,
796) -> impl Iterator<Item = (f32, f32)> + '_ {
797 let layout = line.layout_opt().map(Vec::as_slice).unwrap_or_default();
798
799 layout.iter().map(move |visual_line| {
800 let start = visual_line
801 .glyphs
802 .first()
803 .map(|glyph| glyph.start)
804 .unwrap_or(0);
805 let end = visual_line
806 .glyphs
807 .last()
808 .map(|glyph| glyph.end)
809 .unwrap_or(0);
810
811 let range = start.max(from)..end.min(to);
812
813 if range.is_empty() {
814 (0.0, 0.0)
815 } else if range.start == start && range.end == end {
816 (0.0, visual_line.w)
817 } else {
818 let first_glyph = visual_line
819 .glyphs
820 .iter()
821 .position(|glyph| range.start <= glyph.start)
822 .unwrap_or(0);
823
824 let mut glyphs = visual_line.glyphs.iter();
825
826 let x = glyphs.by_ref().take(first_glyph).map(|glyph| glyph.w).sum();
827
828 let width: f32 = glyphs
829 .take_while(|glyph| range.end > glyph.start)
830 .map(|glyph| glyph.w)
831 .sum();
832
833 (x, width)
834 }
835 })
836}
837
838fn visual_lines_offset(line: usize, buffer: &cosmic_text::Buffer) -> i32 {
839 let scroll = buffer.scroll();
840
841 let start = scroll.line.min(line);
842 let end = scroll.line.max(line);
843
844 let visual_lines_offset: usize = buffer.lines[start..]
845 .iter()
846 .take(end - start)
847 .map(|line| line.layout_opt().map(Vec::len).unwrap_or_default())
848 .sum();
849
850 visual_lines_offset as i32 * if scroll.line < line { 1 } else { -1 }
851}
852
853fn to_motion(motion: Motion) -> cosmic_text::Motion {
854 match motion {
855 Motion::Left => cosmic_text::Motion::Left,
856 Motion::Right => cosmic_text::Motion::Right,
857 Motion::Up => cosmic_text::Motion::Up,
858 Motion::Down => cosmic_text::Motion::Down,
859 Motion::WordLeft => cosmic_text::Motion::LeftWord,
860 Motion::WordRight => cosmic_text::Motion::RightWord,
861 Motion::Home => cosmic_text::Motion::Home,
862 Motion::End => cosmic_text::Motion::End,
863 Motion::PageUp => cosmic_text::Motion::PageUp,
864 Motion::PageDown => cosmic_text::Motion::PageDown,
865 Motion::DocumentStart => cosmic_text::Motion::BufferStart,
866 Motion::DocumentEnd => cosmic_text::Motion::BufferEnd,
867 }
868}
869
870fn buffer_from_editor<'a, 'b>(editor: &'a impl cosmic_text::Edit<'b>) -> &'a cosmic_text::Buffer
871where
872 'b: 'a,
873{
874 match editor.buffer_ref() {
875 cosmic_text::BufferRef::Owned(buffer) => buffer,
876 cosmic_text::BufferRef::Borrowed(buffer) => buffer,
877 cosmic_text::BufferRef::Arc(buffer) => buffer,
878 }
879}
880
881fn buffer_mut_from_editor<'a, 'b>(
882 editor: &'a mut impl cosmic_text::Edit<'b>,
883) -> &'a mut cosmic_text::Buffer
884where
885 'b: 'a,
886{
887 match editor.buffer_ref_mut() {
888 cosmic_text::BufferRef::Owned(buffer) => buffer,
889 cosmic_text::BufferRef::Borrowed(buffer) => buffer,
890 cosmic_text::BufferRef::Arc(_buffer) => unreachable!(),
891 }
892}
893
894fn shape_until_cursor(
895 editor: &mut cosmic_text::Editor<'static>,
896 font_system: &mut cosmic_text::FontSystem,
897) {
898 let cursor = editor.cursor();
899 let buffer = buffer_mut_from_editor(editor);
900
901 buffer.shape_until_cursor(font_system, cursor, false);
902
903 if let Some((x, _)) = editor.cursor_position() {
904 let buffer = buffer_mut_from_editor(editor);
905 let scroll = buffer.scroll();
906 let (width, _) = buffer.size();
907
908 const CURSOR_WIDTH: f32 = 2.0; buffer.set_scroll(cosmic_text::Scroll {
911 horizontal: scroll.horizontal
912 + (x as f32 + CURSOR_WIDTH - scroll.horizontal - width.unwrap_or_default())
913 .clamp(0.0, CURSOR_WIDTH),
914 ..scroll
915 });
916 }
917}
918
919#[derive(Default)]
920struct History {
921 changes: Vec<cosmic_text::Change>,
922 current: usize,
923}
924
925impl History {
926 fn new() -> Self {
927 Self::default()
928 }
929
930 fn undo(&mut self) -> Option<&cosmic_text::Change> {
931 if self.current == 0 {
932 return None;
933 }
934
935 self.current = self.current.saturating_sub(1);
936 self.changes.get(self.current)
937 }
938
939 fn redo(&mut self) -> Option<&cosmic_text::Change> {
940 if self.current >= self.changes.len() {
941 return None;
942 }
943
944 let change = self.changes.get(self.current);
945 self.current += 1;
946 change
947 }
948
949 fn push(&mut self, change: cosmic_text::Change) {
950 self.changes.truncate(self.current);
951 self.changes.push(change);
952 self.current += 1;
953 }
954}