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