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