Skip to main content

iced_graphics/text/
editor.rs

1//! Draw and edit text.
2use crate::core::text::editor::{self, Action, Cursor, Direction, Edit, Motion, Selection};
3use crate::core::text::highlighter;
4use crate::core::text::{Alignment, LineHeight, Parser, Position, Wrapping};
5use crate::core::{Font, Pixels, Point, Size};
6use crate::text;
7
8use cosmic_text::Edit as _;
9
10use std::borrow::Cow;
11use std::fmt;
12use std::sync::{self, Arc, RwLock};
13
14/// A multi-line text editor.
15#[derive(Debug, PartialEq)]
16pub struct Editor(Option<Arc<Internal>>);
17
18struct Internal {
19    editor: cosmic_text::Editor<'static>,
20    selection: RwLock<Option<Selection>>,
21    history: History,
22    font: Font,
23    bounds: Size,
24    alignment: Alignment,
25    topmost_line_changed: Option<usize>,
26    hint: bool,
27    hint_factor: f32,
28    version: text::Version,
29}
30
31impl Editor {
32    /// Creates a new empty [`Editor`].
33    pub fn new() -> Self {
34        Self::default()
35    }
36
37    /// Returns the buffer of the [`Editor`].
38    pub fn buffer(&self) -> &cosmic_text::Buffer {
39        buffer_from_editor(&self.internal().editor)
40    }
41
42    /// Creates a [`Weak`] reference to the [`Editor`].
43    ///
44    /// This is useful to avoid cloning the [`Editor`] when
45    /// referential guarantees are unnecessary. For instance,
46    /// when creating a rendering tree.
47    pub fn downgrade(&self) -> Weak {
48        let editor = self.internal();
49
50        Weak {
51            raw: Arc::downgrade(editor),
52            bounds: editor.bounds,
53        }
54    }
55
56    fn internal(&self) -> &Arc<Internal> {
57        self.0
58            .as_ref()
59            .expect("Editor should always be initialized")
60    }
61
62    fn with_internal_mut<T>(&mut self, f: impl FnOnce(&mut Internal) -> T) -> T {
63        let editor = self.0.take().expect("Editor should always be initialized");
64
65        // TODO: Handle multiple strong references somehow
66        let mut internal =
67            Arc::try_unwrap(editor).expect("Editor cannot have multiple strong references");
68
69        // Clear cursor cache
70        let _ = internal
71            .selection
72            .write()
73            .expect("Write to cursor cache")
74            .take();
75
76        let result = f(&mut internal);
77
78        self.0 = Some(Arc::new(internal));
79
80        result
81    }
82}
83
84impl editor::Editor for Editor {
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            text,
95            &cosmic_text::Attrs::new(),
96            cosmic_text::Shaping::Advanced,
97            None,
98        );
99        buffer.shape_until_scroll(font_system.raw(), false);
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) -> Option<editor::Selection> {
136        let internal = self.internal();
137
138        if let Ok(Some(cursor)) = internal.selection.read().as_deref() {
139            return Some(cursor.clone());
140        }
141
142        let cursor = internal.editor.cursor();
143        let buffer = buffer_from_editor(&internal.editor);
144        let scroll = buffer.scroll();
145
146        let cursor = match internal.editor.selection_bounds() {
147            Some((start, end)) => Selection::Range(Arc::from(text::regions(buffer, start, end))),
148            _ => {
149                let line_height = buffer.metrics().line_height;
150                let visual_lines_offset = visual_lines_offset(cursor.line, buffer);
151                let line = buffer.lines.get(cursor.line)?;
152                let layout = line.layout_opt()?;
153
154                let empty_offset = match internal.alignment {
155                    Alignment::Default | Alignment::Left | Alignment::Justified => 0.0,
156                    Alignment::Center => internal.bounds.width / 2.0,
157                    Alignment::Right => internal.bounds.width,
158                };
159
160                let (visual_line, offset) = layout
161                    .iter()
162                    .enumerate()
163                    .find_map(|(i, line)| {
164                        let (start, offset) = line
165                            .glyphs
166                            .first()
167                            .map(|glyph| (glyph.start, glyph.x))
168                            .unwrap_or((0, empty_offset));
169
170                        let end = line.glyphs.last().map(|glyph| glyph.end).unwrap_or(0);
171
172                        let is_cursor_before_start = start > cursor.index;
173
174                        let is_cursor_before_end = match cursor.affinity {
175                            cosmic_text::Affinity::Before => cursor.index <= end,
176                            cosmic_text::Affinity::After => cursor.index < end,
177                        };
178
179                        if is_cursor_before_start {
180                            // Sometimes, the glyph we are looking for is right
181                            // between lines. This can happen when a line wraps
182                            // on a space.
183                            // In that case, we can assume the cursor is at the
184                            // end of the previous line.
185                            // i is guaranteed to be > 0 because `start` is always
186                            // 0 for the first line, so there is no way for the
187                            // cursor to be before it.
188                            Some((i - 1, layout[i - 1].w + offset))
189                        } else if is_cursor_before_end {
190                            let x: f32 = line
191                                .glyphs
192                                .iter()
193                                .take_while(|glyph| cursor.index > glyph.start)
194                                .map(|glyph| glyph.w)
195                                .sum();
196
197                            Some((i, x + offset))
198                        } else {
199                            None
200                        }
201                    })
202                    .unwrap_or_else(|| {
203                        (
204                            layout.len().saturating_sub(1),
205                            layout
206                                .last()
207                                .map(|line| {
208                                    line.w
209                                        + line
210                                            .glyphs
211                                            .first()
212                                            .map(|glyph| glyph.x)
213                                            .unwrap_or(empty_offset)
214                                })
215                                .unwrap_or(empty_offset),
216                        )
217                    });
218
219                Selection::Caret(Point::new(
220                    (offset - scroll.horizontal) / internal.hint_factor,
221                    ((visual_lines_offset + visual_line as i32) as f32 * line_height
222                        - scroll.vertical)
223                        / internal.hint_factor,
224                ))
225            }
226        };
227
228        *internal.selection.write().expect("Write to cursor cache") = Some(cursor.clone());
229
230        Some(cursor)
231    }
232
233    fn cursor(&self) -> Cursor {
234        let editor = &self.internal().editor;
235
236        let position = {
237            let cursor = editor.cursor();
238
239            Position {
240                line: cursor.line,
241                index: cursor.index,
242            }
243        };
244
245        let selection = match editor.selection() {
246            cosmic_text::Selection::None => None,
247            cosmic_text::Selection::Normal(cursor)
248            | cosmic_text::Selection::Line(cursor)
249            | cosmic_text::Selection::Word(cursor) => Some(Position {
250                line: cursor.line,
251                index: cursor.index,
252            }),
253        };
254
255        Cursor {
256            position,
257            selection,
258        }
259    }
260
261    fn perform(&mut self, action: Action) {
262        let mut font_system = text::font_system().write().expect("Write font system");
263
264        self.with_internal_mut(|internal| {
265            let editor = &mut internal.editor;
266
267            match action {
268                // Motion events
269                Action::Move(motion) => {
270                    if let Some((start, end)) = editor.selection_bounds() {
271                        editor.set_selection(cosmic_text::Selection::None);
272
273                        match motion {
274                            // These motions are performed as-is even when a selection
275                            // is present
276                            Motion::Home
277                            | Motion::End
278                            | Motion::DocumentStart
279                            | Motion::DocumentEnd => {
280                                editor.action(
281                                    font_system.raw(),
282                                    cosmic_text::Action::Motion(to_motion(motion)),
283                                );
284                            }
285                            // Other motions simply move the cursor to one end of the selection
286                            _ => editor.set_cursor(match motion.direction() {
287                                Direction::Left => start,
288                                Direction::Right => end,
289                            }),
290                        }
291                    } else {
292                        editor.action(
293                            font_system.raw(),
294                            cosmic_text::Action::Motion(to_motion(motion)),
295                        );
296                    }
297
298                    if buffer_from_editor(editor).wrap() == cosmic_text::Wrap::None {
299                        let cursor = cosmic_text::Cursor {
300                            affinity: cosmic_text::Affinity::Before,
301                            ..editor.cursor()
302                        };
303
304                        editor.set_cursor(cursor);
305                    }
306
307                    shape_until_cursor(editor, &mut font_system.raw);
308                }
309
310                // Selection events
311                Action::Select(motion) => {
312                    let cursor = editor.cursor();
313
314                    if editor.selection_bounds().is_none() {
315                        editor.set_selection(cosmic_text::Selection::Normal(cursor));
316                    }
317
318                    editor.action(
319                        font_system.raw(),
320                        cosmic_text::Action::Motion(to_motion(motion)),
321                    );
322
323                    // Deselect if selection matches cursor position
324                    if let Some((start, end)) = editor.selection_bounds()
325                        && start.line == end.line
326                        && start.index == end.index
327                    {
328                        editor.set_selection(cosmic_text::Selection::None);
329                    }
330
331                    shape_until_cursor(editor, &mut font_system.raw);
332                }
333                Action::SelectWord => {
334                    let cursor = editor.cursor();
335
336                    editor.set_selection(cosmic_text::Selection::Word(cursor));
337                }
338                Action::SelectLine => {
339                    let cursor = editor.cursor();
340
341                    editor.set_selection(cosmic_text::Selection::Line(cursor));
342                }
343                Action::SelectAll => {
344                    let buffer = buffer_from_editor(editor);
345
346                    if buffer.lines.len() > 1
347                        || buffer
348                            .lines
349                            .first()
350                            .is_some_and(|line| !line.text().is_empty())
351                    {
352                        let cursor = editor.cursor();
353
354                        editor.set_selection(cosmic_text::Selection::Normal(cosmic_text::Cursor {
355                            line: 0,
356                            index: 0,
357                            ..cursor
358                        }));
359
360                        editor.action(
361                            font_system.raw(),
362                            cosmic_text::Action::Motion(cosmic_text::Motion::BufferEnd),
363                        );
364                    }
365                }
366
367                // Editing events
368                Action::Edit(edit) => {
369                    let lines_before_edit = buffer_from_editor(editor).lines.len();
370
371                    let topmost_line_before_edit = editor
372                        .selection_bounds()
373                        .map(|(start, _)| start)
374                        .unwrap_or_else(|| editor.cursor())
375                        .line;
376
377                    if !matches!(edit, Edit::Undo | Edit::Redo) {
378                        editor.start_change();
379                    }
380
381                    match edit {
382                        Edit::Insert(c) => {
383                            editor.action(font_system.raw(), cosmic_text::Action::Insert(c));
384                        }
385                        Edit::Paste(text) => {
386                            editor.insert_string(&text, None);
387                        }
388                        Edit::Indent => {
389                            editor.action(font_system.raw(), cosmic_text::Action::Indent);
390                        }
391                        Edit::Unindent => {
392                            editor.action(font_system.raw(), cosmic_text::Action::Unindent);
393                        }
394                        Edit::Enter => {
395                            editor.action(font_system.raw(), cosmic_text::Action::Enter);
396                        }
397                        Edit::Backspace => {
398                            editor.action(font_system.raw(), cosmic_text::Action::Backspace);
399                        }
400                        Edit::BackspaceWord => {
401                            if editor.selection() == cosmic_text::Selection::None {
402                                editor
403                                    .set_selection(cosmic_text::Selection::Normal(editor.cursor()));
404
405                                editor.action(
406                                    font_system.raw(),
407                                    cosmic_text::Action::Motion(cosmic_text::Motion::PreviousWord),
408                                );
409                            }
410
411                            editor.action(font_system.raw(), cosmic_text::Action::Backspace);
412                        }
413                        Edit::BackspaceLine => {
414                            if editor.selection() == cosmic_text::Selection::None {
415                                editor
416                                    .set_selection(cosmic_text::Selection::Normal(editor.cursor()));
417
418                                editor.action(
419                                    font_system.raw(),
420                                    cosmic_text::Action::Motion(cosmic_text::Motion::Home),
421                                );
422                            }
423
424                            editor.action(font_system.raw(), cosmic_text::Action::Backspace);
425                        }
426                        Edit::Delete => {
427                            editor.action(font_system.raw(), cosmic_text::Action::Delete);
428                        }
429                        Edit::DeleteWord => {
430                            if editor.selection() == cosmic_text::Selection::None {
431                                editor
432                                    .set_selection(cosmic_text::Selection::Normal(editor.cursor()));
433
434                                editor.action(
435                                    font_system.raw(),
436                                    cosmic_text::Action::Motion(cosmic_text::Motion::NextWord),
437                                );
438                            }
439
440                            editor.action(font_system.raw(), cosmic_text::Action::Delete);
441                        }
442                        Edit::DeleteLine => {
443                            if editor.selection() == cosmic_text::Selection::None {
444                                editor
445                                    .set_selection(cosmic_text::Selection::Normal(editor.cursor()));
446
447                                editor.action(
448                                    font_system.raw(),
449                                    cosmic_text::Action::Motion(cosmic_text::Motion::End),
450                                );
451                            }
452
453                            editor.action(font_system.raw(), cosmic_text::Action::Delete);
454                        }
455                        Edit::Undo => {
456                            if let Some(change) = internal.history.undo() {
457                                let mut change = change.clone();
458                                change.reverse();
459
460                                let _ = editor.apply_change(&change);
461                            }
462                        }
463                        Edit::Redo => {
464                            if let Some(change) = internal.history.redo() {
465                                let _ = editor.apply_change(change);
466                            }
467                        }
468                    }
469
470                    let lines_after_edit = buffer_from_editor(editor).lines.len();
471
472                    if lines_after_edit > lines_before_edit {
473                        let align = text::to_align(internal.alignment);
474
475                        for line in &mut buffer_mut_from_editor(editor).lines {
476                            let _ = line.set_align(align);
477                        }
478                    }
479
480                    let cursor = editor.cursor();
481                    let selection_start = editor
482                        .selection_bounds()
483                        .map(|(start, _)| start)
484                        .unwrap_or(cursor);
485
486                    internal.topmost_line_changed =
487                        Some(selection_start.line.min(topmost_line_before_edit));
488
489                    shape_until_cursor(editor, &mut font_system.raw);
490                }
491
492                // Mouse events
493                Action::Click(position, kind) => {
494                    let scroll = buffer_from_editor(editor).scroll();
495
496                    let x = ((position.x + scroll.horizontal) * internal.hint_factor) as i32;
497                    let y = (position.y * internal.hint_factor) as i32;
498
499                    editor.action(
500                        font_system.raw(),
501                        match kind {
502                            iced_core::mouse::click::Kind::Single => {
503                                cosmic_text::Action::Click { x, y }
504                            }
505                            iced_core::mouse::click::Kind::Double => {
506                                cosmic_text::Action::DoubleClick { x, y }
507                            }
508                            iced_core::mouse::click::Kind::Triple => {
509                                cosmic_text::Action::TripleClick { x, y }
510                            }
511                        },
512                    );
513
514                    shape_until_cursor(editor, &mut font_system.raw);
515                }
516                Action::Drag(position) => {
517                    let scroll = buffer_from_editor(editor).scroll();
518
519                    editor.action(
520                        font_system.raw(),
521                        cosmic_text::Action::Drag {
522                            x: ((position.x + scroll.horizontal) * internal.hint_factor) as i32,
523                            y: (position.y * internal.hint_factor) as i32,
524                        },
525                    );
526
527                    // Deselect if selection matches cursor position
528                    if let Some((start, end)) = editor.selection_bounds()
529                        && start.line == end.line
530                        && start.index == end.index
531                    {
532                        editor.set_selection(cosmic_text::Selection::None);
533                    }
534
535                    shape_until_cursor(editor, &mut font_system.raw);
536                }
537                Action::Scroll { lines } => {
538                    editor.action(
539                        font_system.raw(),
540                        cosmic_text::Action::Scroll {
541                            pixels: lines as f32 * buffer_from_editor(editor).metrics().line_height,
542                        },
543                    );
544
545                    buffer_mut_from_editor(editor).shape_until_scroll(&mut font_system.raw, false);
546                }
547            }
548
549            if let Some(change) = editor.finish_change()
550                && !change.items.is_empty()
551            {
552                internal.history.push(change);
553            }
554        });
555    }
556
557    fn move_to(&mut self, cursor: Cursor) {
558        self.with_internal_mut(|internal| {
559            // TODO: Expose `Affinity`
560            internal.editor.set_cursor(cosmic_text::Cursor {
561                line: cursor.position.line,
562                index: cursor.position.index,
563                affinity: cosmic_text::Affinity::Before,
564            });
565
566            if let Some(selection) = cursor.selection {
567                internal
568                    .editor
569                    .set_selection(cosmic_text::Selection::Normal(cosmic_text::Cursor {
570                        line: selection.line,
571                        index: selection.index,
572                        affinity: cosmic_text::Affinity::Before,
573                    }));
574            } else {
575                internal.editor.set_selection(cosmic_text::Selection::None);
576            }
577        });
578    }
579
580    fn bounds(&self) -> Size {
581        self.internal().bounds
582    }
583
584    fn min_bounds(&self) -> Size {
585        let internal = self.internal();
586
587        let (bounds, _has_rtl) = text::measure(buffer_from_editor(&internal.editor));
588
589        bounds * (1.0 / internal.hint_factor)
590    }
591
592    fn hint_factor(&self) -> Option<f32> {
593        let internal = self.internal();
594
595        internal.hint.then_some(internal.hint_factor)
596    }
597
598    fn update(
599        &mut self,
600        new_bounds: Size,
601        new_font: Font,
602        new_size: Pixels,
603        new_line_height: LineHeight,
604        new_wrapping: Wrapping,
605        new_alignment: Alignment,
606        new_hint_factor: Option<f32>,
607        new_parser: &mut impl Parser,
608    ) {
609        self.with_internal_mut(|internal| {
610            let mut font_system = text::font_system().write().expect("Write font system");
611
612            let buffer = buffer_mut_from_editor(&mut internal.editor);
613
614            if font_system.version() != internal.version {
615                log::trace!("Updating `FontSystem` of `Editor`...");
616
617                for line in buffer.lines.iter_mut() {
618                    line.reset();
619                }
620
621                internal.version = font_system.version();
622                internal.topmost_line_changed = Some(0);
623            }
624
625            if new_font != internal.font {
626                log::trace!("Updating font of `Editor`...");
627
628                for line in buffer.lines.iter_mut() {
629                    let _ = line.set_attrs_list(cosmic_text::AttrsList::new(&text::to_attributes(
630                        new_font,
631                    )));
632                }
633
634                internal.font = new_font;
635                internal.topmost_line_changed = Some(0);
636            }
637
638            let metrics = buffer.metrics();
639            let new_line_height = new_line_height.to_absolute(new_size);
640            let mut hinting_changed = false;
641
642            let new_hint_factor = text::hint_factor(new_size, new_hint_factor);
643
644            if new_hint_factor != internal.hint.then_some(internal.hint_factor) {
645                internal.hint = new_hint_factor.is_some();
646                internal.hint_factor = new_hint_factor.unwrap_or(1.0);
647
648                buffer.set_hinting(if internal.hint {
649                    cosmic_text::Hinting::Enabled
650                } else {
651                    cosmic_text::Hinting::Disabled
652                });
653
654                hinting_changed = true;
655            }
656
657            if new_size.0 != metrics.font_size
658                || new_line_height.0 != metrics.line_height
659                || hinting_changed
660            {
661                log::trace!("Updating `Metrics` of `Editor`...");
662
663                buffer.set_metrics(cosmic_text::Metrics::new(
664                    new_size.0 * internal.hint_factor,
665                    new_line_height.0 * internal.hint_factor,
666                ));
667            }
668
669            let new_wrap = text::to_wrap(new_wrapping);
670
671            if new_wrap != buffer.wrap() {
672                log::trace!("Updating `Wrap` strategy of `Editor`...");
673
674                buffer.set_wrap(new_wrap);
675            }
676
677            if new_bounds != internal.bounds || hinting_changed {
678                log::trace!("Updating size of `Editor`...");
679
680                buffer.set_size(
681                    Some(new_bounds.width * internal.hint_factor),
682                    Some(new_bounds.height * internal.hint_factor),
683                );
684
685                if internal.bounds == Size::ZERO {
686                    buffer.set_scroll(cosmic_text::Scroll::default());
687                }
688
689                internal.bounds = new_bounds;
690            }
691
692            if new_alignment != internal.alignment {
693                let new_align = text::to_align(new_alignment);
694
695                for line in &mut buffer.lines {
696                    let _ = line.set_align(new_align);
697                }
698
699                internal.alignment = new_alignment;
700            }
701
702            buffer.shape_until_scroll(font_system.raw(), false);
703
704            if let Some(topmost_line_changed) = internal.topmost_line_changed.take() {
705                log::trace!("Notifying parser of line change: {topmost_line_changed}");
706                new_parser.change_line(topmost_line_changed);
707            }
708
709            internal.editor.shape_as_needed(font_system.raw(), false);
710        });
711    }
712
713    fn overwrite(&mut self, new_text: &str) {
714        self.with_internal_mut(|internal| {
715            let mut font_system = text::font_system().write().expect("Write font system");
716
717            let cursor = internal.editor.cursor();
718            let buffer = buffer_mut_from_editor(&mut internal.editor);
719
720            buffer.set_text(
721                new_text,
722                &cosmic_text::Attrs::new(),
723                cosmic_text::Shaping::Advanced,
724                text::to_align(internal.alignment),
725            );
726
727            let line = cursor.line.min(buffer.lines.len().saturating_sub(1));
728
729            let new_cursor = cosmic_text::Cursor {
730                line,
731                index: buffer
732                    .lines
733                    .get(line)
734                    .map(|line| line.text().floor_char_boundary(cursor.index))
735                    .unwrap_or_default(),
736                affinity: if !buffer.lines.is_empty() {
737                    cursor.affinity
738                } else {
739                    cosmic_text::Affinity::Before
740                },
741            };
742
743            internal.editor.set_cursor(new_cursor);
744
745            shape_until_cursor(&mut internal.editor, &mut font_system.raw);
746        });
747    }
748
749    fn highlight<P: Parser>(
750        &mut self,
751        font: Font,
752        parser: &mut P,
753        highlight: impl Fn(P::Output) -> highlighter::Style,
754    ) {
755        let internal = self.internal();
756        let buffer = buffer_from_editor(&internal.editor);
757
758        let scroll = buffer.scroll();
759        let mut window = (internal.bounds.height * internal.hint_factor
760            / buffer.metrics().line_height)
761            .ceil() as i32;
762
763        let last_visible_line = buffer.lines[scroll.line..]
764            .iter()
765            .enumerate()
766            .find_map(|(i, line)| {
767                let visible_lines = line.layout_opt()?.len() as i32;
768
769                if window > visible_lines {
770                    window -= visible_lines;
771                    None
772                } else {
773                    Some(scroll.line + i)
774                }
775            })
776            .unwrap_or(buffer.lines.len().saturating_sub(1));
777
778        let current_line = parser.current_line();
779
780        if current_line > last_visible_line {
781            return;
782        }
783
784        let editor = self.0.take().expect("Editor should always be initialized");
785
786        let mut internal =
787            Arc::try_unwrap(editor).expect("Editor cannot have multiple strong references");
788
789        let mut font_system = text::font_system().write().expect("Write font system");
790
791        let attributes = text::to_attributes(font);
792
793        for line in &mut buffer_mut_from_editor(&mut internal.editor).lines
794            [current_line..=last_visible_line]
795        {
796            let mut list = cosmic_text::AttrsList::new(&attributes);
797
798            for (range, output) in parser.parse_line(line.text()) {
799                let format = highlight(output);
800
801                if format.color.is_some() || format.style.is_some() {
802                    list.add_span(
803                        range,
804                        &cosmic_text::Attrs {
805                            color_opt: format.color.map(text::to_color),
806                            ..if let Some(style) = format.style {
807                                cosmic_text::Attrs {
808                                    style: text::to_style(style),
809                                    ..attributes.clone()
810                                }
811                            } else {
812                                attributes.clone()
813                            }
814                        },
815                    );
816                }
817            }
818
819            let _ = line.set_attrs_list(list);
820        }
821
822        internal.editor.shape_as_needed(font_system.raw(), false);
823
824        self.0 = Some(Arc::new(internal));
825    }
826
827    fn text_size(&self) -> Pixels {
828        let internal = self.internal();
829
830        Pixels(buffer_from_editor(&internal.editor).metrics().font_size / internal.hint_factor)
831    }
832
833    fn line_height(&self) -> LineHeight {
834        let internal = self.internal();
835
836        LineHeight::Absolute(Pixels(
837            buffer_from_editor(&internal.editor).metrics().line_height / internal.hint_factor,
838        ))
839    }
840
841    fn font(&self) -> Font {
842        self.internal().font
843    }
844}
845
846impl Default for Editor {
847    fn default() -> Self {
848        Self(Some(Arc::new(Internal::default())))
849    }
850}
851
852impl PartialEq for Internal {
853    fn eq(&self, other: &Self) -> bool {
854        self.font == other.font
855            && self.bounds == other.bounds
856            && buffer_from_editor(&self.editor).metrics()
857                == buffer_from_editor(&other.editor).metrics()
858    }
859}
860
861impl Default for Internal {
862    fn default() -> Self {
863        Self {
864            editor: cosmic_text::Editor::new(cosmic_text::Buffer::new_empty(
865                cosmic_text::Metrics {
866                    font_size: 1.0,
867                    line_height: 1.0,
868                },
869            )),
870            selection: RwLock::new(None),
871            history: History::new(),
872            font: Font::default(),
873            bounds: Size::ZERO,
874            alignment: Alignment::Default,
875            topmost_line_changed: None,
876            hint: false,
877            hint_factor: 1.0,
878            version: text::Version::default(),
879        }
880    }
881}
882
883impl fmt::Debug for Internal {
884    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
885        f.debug_struct("Internal")
886            .field("font", &self.font)
887            .field("bounds", &self.bounds)
888            .finish()
889    }
890}
891
892/// A weak reference to an [`Editor`].
893#[derive(Debug, Clone)]
894pub struct Weak {
895    raw: sync::Weak<Internal>,
896    /// The bounds of the [`Editor`].
897    pub bounds: Size,
898}
899
900impl Weak {
901    /// Tries to update the reference into an [`Editor`].
902    pub fn upgrade(&self) -> Option<Editor> {
903        self.raw.upgrade().map(Some).map(Editor)
904    }
905}
906
907impl PartialEq for Weak {
908    fn eq(&self, other: &Self) -> bool {
909        match (self.raw.upgrade(), other.raw.upgrade()) {
910            (Some(p1), Some(p2)) => p1 == p2,
911            _ => false,
912        }
913    }
914}
915
916fn visual_lines_offset(line: usize, buffer: &cosmic_text::Buffer) -> i32 {
917    let scroll = buffer.scroll();
918
919    let start = scroll.line.min(line);
920    let end = scroll.line.max(line);
921
922    let visual_lines_offset: usize = buffer.lines[start..]
923        .iter()
924        .take(end - start)
925        .map(|line| line.layout_opt().map(Vec::len).unwrap_or_default())
926        .sum();
927
928    visual_lines_offset as i32 * if scroll.line < line { 1 } else { -1 }
929}
930
931fn to_motion(motion: Motion) -> cosmic_text::Motion {
932    match motion {
933        Motion::Left => cosmic_text::Motion::Left,
934        Motion::Right => cosmic_text::Motion::Right,
935        Motion::Up => cosmic_text::Motion::Up,
936        Motion::Down => cosmic_text::Motion::Down,
937        Motion::WordLeft => cosmic_text::Motion::LeftWord,
938        Motion::WordRight => cosmic_text::Motion::RightWord,
939        Motion::Home => cosmic_text::Motion::Home,
940        Motion::End => cosmic_text::Motion::End,
941        Motion::PageUp => cosmic_text::Motion::PageUp,
942        Motion::PageDown => cosmic_text::Motion::PageDown,
943        Motion::DocumentStart => cosmic_text::Motion::BufferStart,
944        Motion::DocumentEnd => cosmic_text::Motion::BufferEnd,
945    }
946}
947
948fn buffer_from_editor<'a, 'b>(editor: &'a impl cosmic_text::Edit<'b>) -> &'a cosmic_text::Buffer
949where
950    'b: 'a,
951{
952    match editor.buffer_ref() {
953        cosmic_text::BufferRef::Owned(buffer) => buffer,
954        cosmic_text::BufferRef::Borrowed(buffer) => buffer,
955        cosmic_text::BufferRef::Arc(buffer) => buffer,
956    }
957}
958
959fn buffer_mut_from_editor<'a, 'b>(
960    editor: &'a mut impl cosmic_text::Edit<'b>,
961) -> &'a mut cosmic_text::Buffer
962where
963    'b: 'a,
964{
965    match editor.buffer_ref_mut() {
966        cosmic_text::BufferRef::Owned(buffer) => buffer,
967        cosmic_text::BufferRef::Borrowed(buffer) => buffer,
968        cosmic_text::BufferRef::Arc(_buffer) => unreachable!(),
969    }
970}
971
972fn shape_until_cursor(
973    editor: &mut cosmic_text::Editor<'static>,
974    font_system: &mut cosmic_text::FontSystem,
975) {
976    let cursor = editor.cursor();
977    let buffer = buffer_mut_from_editor(editor);
978
979    buffer.shape_until_cursor(font_system, cursor, false);
980
981    if let Some((x, _)) = editor.cursor_position() {
982        let buffer = buffer_mut_from_editor(editor);
983        let scroll = buffer.scroll();
984        let (width, _) = buffer.size();
985
986        const CURSOR_WIDTH: f32 = 2.0; // TODO: Configurable!
987
988        buffer.set_scroll(cosmic_text::Scroll {
989            horizontal: scroll.horizontal
990                + (x as f32 + CURSOR_WIDTH - scroll.horizontal - width.unwrap_or_default())
991                    .clamp(0.0, CURSOR_WIDTH),
992            ..scroll
993        });
994    }
995}
996
997#[derive(Default)]
998struct History {
999    changes: Vec<cosmic_text::Change>,
1000    current: usize,
1001}
1002
1003impl History {
1004    fn new() -> Self {
1005        Self::default()
1006    }
1007
1008    fn undo(&mut self) -> Option<&cosmic_text::Change> {
1009        if self.current == 0 {
1010            return None;
1011        }
1012
1013        self.current = self.current.saturating_sub(1);
1014        self.changes.get(self.current)
1015    }
1016
1017    fn redo(&mut self) -> Option<&cosmic_text::Change> {
1018        if self.current >= self.changes.len() {
1019            return None;
1020        }
1021
1022        let change = self.changes.get(self.current);
1023        self.current += 1;
1024        change
1025    }
1026
1027    fn push(&mut self, change: cosmic_text::Change) {
1028        self.changes.truncate(self.current);
1029        self.changes.push(change);
1030        self.current += 1;
1031    }
1032}