Skip to main content

smudge_text/
input.rs

1//! The editing engine behind a text input and a text area.
2//!
3//! [`Editor`] keeps the bound signal in step with every edit and turns its
4//! layouts into the glyph runs and geometry a widget paints. The text is
5//! one string, cut into paragraphs at its line breaks, and each paragraph
6//! is laid out on its own: an edit re-lays out the paragraphs it touched
7//! and leaves the rest of a large document shaped as it was, so typing
8//! stays cheap in a document far larger than the screen. All of parley
9//! stays behind this wrapper, so a widget and a test drive the same edits
10//! without touching parley types for anything but geometry.
11
12use std::borrow::Cow;
13use std::cell::{Cell, RefCell};
14use std::collections::VecDeque;
15use std::ops::Range;
16
17use parley::Cluster;
18use parley::editing::Cursor;
19use parley::editing::Selection as ParagraphSelection;
20use parley::layout::Affinity;
21
22use smudge_core::geometry::{Color, Point, Rect, Size};
23use smudge_core::paint::GlyphRun;
24use smudge_core::reactive::{RwSignal, prelude::*};
25
26use crate::glyphs::glyph_runs;
27use crate::layout::TextLayout;
28use crate::line_breaks::flatten_line_breaks;
29use crate::style::TextStyle;
30use crate::system::TextSystem;
31
32/// How many bytes the undo and redo steps of one editor may hold together.
33/// Older undo steps are forgotten past this, but the newest one is always
34/// kept, so a text larger than the bound can still undo once.
35///
36/// Every step keeps the whole text it restores, so the bound is in bytes: a
37/// short text keeps thousands of steps and a text of several megabytes a
38/// few.
39const HISTORY_BYTES: usize = 8 << 20;
40
41/// How wide the highlight of a selected line break is, in font sizes.
42const NEWLINE_WIDTH: f32 = 0.3;
43
44/// How far below the baseline the composing text is underlined, in logical
45/// pixels.
46const UNDERLINE_OFFSET: f32 = 1.0;
47
48/// How thick the underline under the composing text is, in logical pixels.
49const UNDERLINE_THICKNESS: f32 = 1.0;
50
51/// How far the input method area inflates around the text it bounds, in
52/// font sizes, so a candidate window does not jump while the user types.
53/// This assumes an average glyph advance of a bit over half a font size
54/// and pads three advances.
55const IME_INFLATE: f32 = 1.8;
56
57/// One text buffer bound to a signal, over any number of paragraphs.
58///
59/// The editor holds the text and the selection; the signal mirrors the text
60/// without any composing preedit. Every mutating method writes the signal,
61/// so [`Editor::text`] and the signal always agree. A write to the signal
62/// from outside is applied with [`Editor::set_text`], which replaces the
63/// text and moves the caret to the end.
64///
65/// The editor lays out lazily and per paragraph: an edit drops the layouts
66/// of the paragraphs it touched, and the next call that needs geometry
67/// rebuilds them, alone. A document far larger than the screen costs an
68/// edit only the work its own lines take.
69///
70/// # Panics
71///
72/// Every method that edits the text, moves the caret or reads geometry can
73/// build layouts, so it runs the thread's [`TextSystem`] and panics under
74/// the conditions of [`TextSystem::with`], in particular when called from
75/// inside another `with` call on the same thread. No input text or edit
76/// sequence panics.
77pub struct Editor {
78    /// The signal the text is bound to.
79    value: RwSignal<String>,
80    /// The text of the editor, including any composing preedit.
81    buffer: RefCell<String>,
82    /// The paragraphs the buffer is cut into, with their layout caches.
83    paragraphs: RefCell<ParagraphTable>,
84    /// Where the selection sits.
85    selection: RefCell<Selection>,
86    /// The preedit an input method is composing, if one is.
87    compose: RefCell<Option<Compose>>,
88    /// The steps undo and redo walk through.
89    history: RefCell<History>,
90    /// The font style the paragraphs lay out with.
91    style: RefCell<TextStyle>,
92    /// The width paragraphs wrap at, in logical pixels, or `None` to lay
93    /// each out as one unbroken line.
94    width: Cell<Option<f32>>,
95    /// How many times the editor has built or wrapped a paragraph layout,
96    /// which paragraph stamps are counted against.
97    builds: Cell<u64>,
98    /// Whether the editor holds one line, turning every line break that
99    /// reaches it into a space.
100    single_line: bool,
101}
102
103/// The text and selection of an editor at one moment.
104struct Snapshot {
105    /// The text, without any composing preedit.
106    text: String,
107    /// The selection, as the engine keeps it.
108    selection: Selection,
109}
110
111/// What kind of change an edit made, which decides the edits it groups with.
112#[derive(Clone, Copy, PartialEq)]
113enum EditKind {
114    /// Text was typed or inserted.
115    Insert,
116    /// Text was deleted.
117    Delete,
118}
119
120/// How one edit groups with the edits around it in the undo history.
121#[derive(Clone, Copy, PartialEq)]
122enum Grouping {
123    /// Extends the open step of the same kind, and stays open.
124    Joins,
125    /// Starts a step that the next edit of the same kind can join, as a
126    /// typed space starts the step of the word after it.
127    Starts,
128    /// Is a step of its own, which nothing joins.
129    Alone,
130}
131
132/// The undo and redo steps of one editor.
133#[derive(Default)]
134struct History {
135    /// The states before each step, oldest first.
136    undo: VecDeque<Snapshot>,
137    /// The states undo left, most recently undone last.
138    redo: Vec<Snapshot>,
139    /// The bytes the steps of both lists hold, which [`HISTORY_BYTES`]
140    /// bounds.
141    bytes: usize,
142    /// The kind of the last edit and the selection it left, which the next
143    /// edit of the same kind joins when it starts where that one ended.
144    /// A caret move closes it.
145    open: Option<(EditKind, Range<usize>)>,
146}
147
148impl History {
149    /// The bytes one step holds.
150    fn cost(snapshot: &Snapshot) -> usize {
151        snapshot.text.len() + size_of::<Snapshot>()
152    }
153
154    /// Adds a step to undo, forgetting the oldest steps past the byte bound.
155    fn push_undo(&mut self, snapshot: Snapshot) {
156        self.bytes += Self::cost(&snapshot);
157        self.undo.push_back(snapshot);
158        while self.bytes > HISTORY_BYTES && self.undo.len() > 1 {
159            if let Some(oldest) = self.undo.pop_front() {
160                self.bytes -= Self::cost(&oldest);
161            }
162        }
163    }
164
165    /// Takes the newest undo step.
166    fn pop_undo(&mut self) -> Option<Snapshot> {
167        let snapshot = self.undo.pop_back()?;
168        self.bytes -= Self::cost(&snapshot);
169        Some(snapshot)
170    }
171
172    /// Adds a step to redo.
173    fn push_redo(&mut self, snapshot: Snapshot) {
174        self.bytes += Self::cost(&snapshot);
175        self.redo.push(snapshot);
176    }
177
178    /// Takes the newest redo step.
179    fn pop_redo(&mut self) -> Option<Snapshot> {
180        let snapshot = self.redo.pop()?;
181        self.bytes -= Self::cost(&snapshot);
182        Some(snapshot)
183    }
184
185    /// Forgets every redo step.
186    fn clear_redo(&mut self) {
187        for snapshot in self.redo.drain(..) {
188            self.bytes -= Self::cost(&snapshot);
189        }
190    }
191}
192
193/// One end of a selection: a paragraph and a byte offset into its text.
194///
195/// The affinity says which side of the cluster at the offset the caret
196/// sits on. Two anchors at the same visual boundary can carry different
197/// offsets when a line break lies between them: the end of one paragraph
198/// and the start of the next name the break's two sides.
199#[derive(Clone, Copy, Debug, PartialEq)]
200struct Anchor {
201    /// The paragraph the anchor sits in.
202    paragraph: usize,
203    /// The byte offset into the paragraph's text.
204    index: usize,
205    /// Which side of the cluster the caret sits on.
206    affinity: Affinity,
207}
208
209/// The granularity a shift click extends a selection with.
210///
211/// A plain click, a caret move or an edit resets this to clusters. A double
212/// click sets it to words, so the shift clicks that follow extend a word at
213/// a time.
214#[derive(Clone, Copy, Debug, Default, PartialEq)]
215enum Base {
216    /// Extends by cluster.
217    #[default]
218    Cluster,
219    /// Extends to word boundaries, with the two ends the double click made.
220    Word([Anchor; 2]),
221}
222
223/// Where a selection sits, in paragraph-local coordinates.
224#[derive(Clone, Copy, Debug, PartialEq)]
225struct Selection {
226    /// The end the selection extends from.
227    anchor: Anchor,
228    /// The end the selection extends to.
229    focus: Anchor,
230    /// The granularity a shift click extends with.
231    base: Base,
232    /// The horizontal position the focus keeps across vertical moves, in
233    /// logical pixels.
234    h_pos: Option<f32>,
235}
236
237/// The preedit an input method is composing into the buffer.
238struct Compose {
239    /// The byte range of the buffer the preedit occupies.
240    range: Range<usize>,
241    /// The byte range of the input method's cursor inside the preedit,
242    /// relative to its start, or `None` when the input method hides it.
243    cursor: Option<(usize, usize)>,
244}
245
246/// One paragraph of an editor's buffer, with its layout cache.
247struct Paragraph {
248    /// Byte offset of the paragraph's first byte in the buffer.
249    start: usize,
250    /// Byte length of the paragraph, without the line break that ends it.
251    len: usize,
252    /// The laid out paragraph, or `None` when it needs building.
253    layout: Option<TextLayout>,
254    /// The editor's build count when the layout was built or wrapped
255    /// again. A paragraph whose layout is missing was not built yet.
256    stamp: u64,
257}
258
259/// The paragraphs an editor's buffer is cut into, in text order.
260#[derive(Default)]
261struct ParagraphTable {
262    /// One entry per line break plus one for the text after the last one,
263    /// so the table is never empty and covers the buffer exactly. Each
264    /// paragraph but the last is followed by its line break in the buffer.
265    paragraphs: Vec<Paragraph>,
266}
267
268impl ParagraphTable {
269    /// The global byte offset of a paragraph-local anchor.
270    fn global(&self, anchor: Anchor) -> usize {
271        self.paragraphs[anchor.paragraph].start + anchor.index
272    }
273
274    /// The paragraph and local index of a global byte offset.
275    ///
276    /// The line break that ends a paragraph belongs to that paragraph's
277    /// span, so an offset on the break itself names the paragraph before
278    /// it, at the end of its text.
279    fn locate(&self, byte: usize) -> (usize, usize) {
280        let paragraph = self.index_at(byte);
281        (paragraph, byte - self.paragraphs[paragraph].start)
282    }
283
284    /// The index of the paragraph whose span holds `byte`.
285    fn index_at(&self, byte: usize) -> usize {
286        self.paragraphs
287            .partition_point(|paragraph| paragraph.start <= byte)
288            - 1
289    }
290
291    /// Re-cuts the whole buffer into paragraphs, keeping no layouts.
292    fn rebuild(&mut self, buffer: &str) {
293        let mut paragraphs = Vec::new();
294        let mut start = 0;
295        for (offset, byte) in buffer.bytes().enumerate() {
296            if byte == b'\n' {
297                paragraphs.push(Paragraph {
298                    start,
299                    len: offset - start,
300                    layout: None,
301                    stamp: 0,
302                });
303                start = offset + 1;
304            }
305        }
306        paragraphs.push(Paragraph {
307            start,
308            len: buffer.len() - start,
309            layout: None,
310            stamp: 0,
311        });
312        self.paragraphs = paragraphs;
313    }
314
315    /// Re-cuts the buffer into paragraphs after an edit replaced `range`
316    /// with `replacement`, keeping every layout whose paragraph text the
317    /// edit did not touch.
318    ///
319    /// The buffer already holds the edited text: the table follows it. The
320    /// paragraphs before the edit keep their layouts, the paragraphs after
321    /// it keep theirs with shifted offsets, and the paragraphs the
322    /// replacement straddled are laid out again on the next need. A range
323    /// that reaches the next paragraph's start consumed the line break that
324    /// separated the two, so that paragraph's text belongs to the
325    /// replacement's last piece and cuts together with it.
326    fn splice(&mut self, range: Range<usize>, replacement: &str) {
327        let first = self.index_at(range.start);
328        let mut last = if range.is_empty() {
329            first
330        } else {
331            self.index_at(range.end - 1)
332        };
333        if !range.is_empty()
334            && last + 1 < self.paragraphs.len()
335            && range.end == self.paragraphs[last + 1].start
336        {
337            last += 1;
338        }
339        let first_start = self.paragraphs[first].start;
340        let last_end = self.paragraphs[last].start + self.paragraphs[last].len;
341        let head = range.start - first_start;
342        let tail = last_end.saturating_sub(range.end);
343        self.paragraphs.drain(first..=last);
344
345        let delta = replacement.len() as isize - (range.end - range.start) as isize;
346        for paragraph in self.paragraphs[first..].iter_mut() {
347            paragraph.start = (paragraph.start as isize + delta) as usize;
348        }
349
350        let pieces: Vec<&str> = replacement.split('\n').collect();
351        let last_piece = pieces.len() - 1;
352        let mut entries = Vec::with_capacity(pieces.len());
353        let mut start = first_start;
354        for (index, piece) in pieces.iter().enumerate() {
355            let len = piece.len()
356                + if index == 0 { head } else { 0 }
357                + if index == last_piece { tail } else { 0 };
358            entries.push(Paragraph {
359                start,
360                len,
361                layout: None,
362                stamp: 0,
363            });
364            start += len + 1;
365        }
366        self.paragraphs.splice(first..first, entries);
367    }
368}
369
370impl Editor {
371    /// Creates an editor bound to `value`, starting empty.
372    ///
373    /// The text the signal holds is not read: an editor starts empty and
374    /// fills when its widget applies the signal's current value. Binding
375    /// `value` through a widget does that on mount.
376    pub fn new(value: RwSignal<String>) -> Editor {
377        Editor::with_lines(value, false)
378    }
379
380    /// Creates an editor of one line bound to `value`, starting empty.
381    ///
382    /// The editor works as [`Editor::new`] makes it, except that every line
383    /// break in text that reaches it becomes one space: typed or committed
384    /// text, a paste and a write through [`Editor::set_text`] alike, with a
385    /// carriage return and line feed pair counted as one break. The bound
386    /// signal is written with the line the editor kept. A preedit an input
387    /// method is composing is shown as the method sends it, since it is not
388    /// the editor's text until it commits.
389    ///
390    /// ```
391    /// use smudge_core::reactive::{RwSignal, prelude::*};
392    /// use smudge_text::{Editor, TextSystem};
393    ///
394    /// TextSystem::bundled_only().install();
395    /// let value = RwSignal::new(String::new());
396    /// let editor = Editor::single_line(value.clone());
397    /// editor.paste("one\ntwo");
398    /// assert_eq!(value.get(), "one two");
399    /// ```
400    pub fn single_line(value: RwSignal<String>) -> Editor {
401        Editor::with_lines(value, true)
402    }
403
404    /// Creates an empty editor bound to `value`, of one line when
405    /// `single_line` is set.
406    fn with_lines(value: RwSignal<String>, single_line: bool) -> Editor {
407        let mut paragraphs = ParagraphTable::default();
408        paragraphs.rebuild("");
409        Editor {
410            value,
411            buffer: RefCell::new(String::new()),
412            paragraphs: RefCell::new(paragraphs),
413            selection: RefCell::new(Selection {
414                anchor: START,
415                focus: START,
416                base: Base::Cluster,
417                h_pos: None,
418            }),
419            compose: RefCell::new(None),
420            history: RefCell::new(History::default()),
421            style: RefCell::new(TextStyle::default()),
422            width: Cell::new(None),
423            builds: Cell::new(0),
424            single_line,
425        }
426    }
427
428    /// Returns the signal the text is bound to.
429    pub fn value(&self) -> RwSignal<String> {
430        self.value.clone()
431    }
432
433    /// Applies `style` to the text the editor lays out.
434    ///
435    /// The next layout rebuild picks the style up. Changing the font size
436    /// changes what the editor measures, so a widget applies this before it
437    /// measures.
438    pub fn set_style(&self, style: &TextStyle) {
439        *self.style.borrow_mut() = style.clone();
440        self.invalidate_layouts();
441    }
442
443    /// Sets the width the editor wraps its lines to, in logical pixels, or
444    /// `None` to lay each paragraph out as one unbroken line.
445    ///
446    /// An editor starts unwrapped. The paragraphs already laid out break
447    /// their lines again at the new width without shaping their text again,
448    /// and the caret and selection geometry follow. The same width again
449    /// does nothing.
450    pub fn set_wrap_width(&self, width: Option<f32>) {
451        if self.width.get() == width {
452            return;
453        }
454        self.width.set(width);
455        // The width only decides where lines break, so the paragraphs keep
456        // their shaping and wrap again.
457        for paragraph in self.paragraphs.borrow_mut().paragraphs.iter_mut() {
458            if let Some(layout) = paragraph.layout.as_mut() {
459                layout.rebreak(width);
460                self.builds.set(self.builds.get() + 1);
461                paragraph.stamp = self.builds.get();
462            }
463        }
464    }
465
466    /// Returns the text of the editor, without any composing preedit.
467    pub fn text(&self) -> String {
468        let buffer = self.buffer.borrow();
469        let compose = self.compose.borrow();
470        match compose.as_ref() {
471            Some(compose) => {
472                let mut text = String::with_capacity(buffer.len() - compose.range.len());
473                text.push_str(&buffer[..compose.range.start]);
474                text.push_str(&buffer[compose.range.end..]);
475                text
476            }
477            None => buffer.clone(),
478        }
479    }
480
481    /// Returns whether the editor holds no text.
482    pub fn is_empty(&self) -> bool {
483        let preedit = self
484            .compose
485            .borrow()
486            .as_ref()
487            .map_or(0, |compose| compose.range.len());
488        self.buffer.borrow().len() == preedit
489    }
490
491    /// Returns whether the editor holds exactly `text`.
492    pub fn has_text(&self, text: &str) -> bool {
493        let buffer = self.buffer.borrow();
494        match self.compose.borrow().as_ref() {
495            Some(compose) => {
496                let (head, tail) = (&buffer[..compose.range.start], &buffer[compose.range.end..]);
497                text.len() == head.len() + tail.len()
498                    && text.starts_with(head)
499                    && text.ends_with(tail)
500            }
501            None => *buffer == text,
502        }
503    }
504
505    /// Returns the text of the editor, including any composing preedit.
506    pub fn raw_text(&self) -> String {
507        self.buffer.borrow().clone()
508    }
509
510    /// Returns the byte range of the selection, into [`Editor::raw_text`].
511    ///
512    /// The range is empty and both ends equal when the caret is collapsed.
513    pub fn selection(&self) -> Range<usize> {
514        let paragraphs = self.paragraphs.borrow();
515        let selection = self.selection.borrow();
516        let anchor = paragraphs.global(selection.anchor);
517        let focus = paragraphs.global(selection.focus);
518        anchor.min(focus)..anchor.max(focus)
519    }
520
521    /// Returns whether an input method is composing text in the editor.
522    pub fn is_composing(&self) -> bool {
523        self.compose.borrow().is_some()
524    }
525
526    /// Returns the area the input method should anchor to, in the editor's
527    /// own coordinates.
528    ///
529    /// This bounds the preedit while one is being composed, and the selected
530    /// part of the focused line otherwise, inflated a little so a candidate
531    /// window does not jump while the user types.
532    pub fn ime_area(&self) -> Rect {
533        let preedit = self
534            .compose
535            .borrow()
536            .as_ref()
537            .map(|compose| compose.range.clone());
538        self.session(|session| {
539            session.refresh_all();
540            let mut area: Option<Rect> = None;
541            let mut add = |rect: Rect| area = Some(area.map_or(rect, |area| area.union(rect)));
542            match preedit {
543                Some(range) => {
544                    session.range_rects(range, EVERYWHERE, &mut add);
545                    if area.is_none() {
546                        // A preedit of only line breaks holds no glyphs, so
547                        // the caret anchors the area instead.
548                        let focus = session.selection.focus;
549                        let (caret, y) = session.caret_box(focus, 0.0);
550                        area = Some(Rect::new(caret.x0, caret.y0 + y, caret.x1, caret.y1 + y));
551                    }
552                }
553                None => {
554                    // Bound the selected parts of the line the focus sits
555                    // on only, starting from its caret so a collapsed
556                    // selection still gives the area some width.
557                    let focus = session.selection.focus;
558                    let (caret, y) = session.caret_box(focus, 0.0);
559                    let caret = Rect::new(caret.x0, caret.y0 + y, caret.x1, caret.y1 + y);
560                    let range = session.selection_range();
561                    session.range_rects(range, EVERYWHERE, |rect| {
562                        if rect.y0 == caret.y0 {
563                            add(rect);
564                        }
565                    });
566                    add(caret);
567                }
568            }
569            let area = area.expect("the caret always gives the area a rectangle");
570            // The caret and selection boxes span the font's content box,
571            // which a small line height can push past the text's own top
572            // and bottom, and an anchor outside the text means nothing to
573            // an input method.
574            let height: f32 = session
575                .table
576                .paragraphs
577                .iter()
578                .map(|paragraph| {
579                    paragraph
580                        .layout
581                        .as_ref()
582                        .expect("refresh built every layout")
583                        .size()
584                        .height
585                })
586                .sum();
587            let style = self.style.borrow();
588            let inflate = IME_INFLATE * style.size;
589            let width = session.width.unwrap_or(f32::INFINITY);
590            Rect::new(
591                (area.x0 - inflate).max(0.0),
592                area.y0.max(0.0),
593                (area.x1 + inflate).min(width),
594                area.y1.min(height),
595            )
596        })
597    }
598
599    /// Returns one rectangle per piece of the composing preedit, in the
600    /// editor's own coordinates, for the underline drawn beneath it.
601    ///
602    /// The rectangles sit just below the baseline of each piece, in visual
603    /// order, and the list is empty while no input method is composing.
604    pub fn compose_geometry(&self) -> Vec<Rect> {
605        let Some(range) = self
606            .compose
607            .borrow()
608            .as_ref()
609            .map(|compose| compose.range.clone())
610        else {
611            return Vec::new();
612        };
613        self.session(|session| {
614            session.refresh_all();
615            let mut rects = Vec::new();
616            let mut y = 0.0;
617            for index in 0..session.table.paragraphs.len() {
618                let paragraph = &session.table.paragraphs[index];
619                let start = paragraph.start;
620                let len = paragraph.len;
621                let layout = paragraph
622                    .layout
623                    .as_ref()
624                    .expect("refresh built every layout");
625                if range.start < start + len && range.end > start {
626                    let mut index = range.start.max(start) - start;
627                    let end = range.end.min(start + len) - start;
628                    while index < end {
629                        let Some(cluster) = Cluster::from_byte_index(&layout.layout, index) else {
630                            break;
631                        };
632                        // A cluster can hold more than the byte the loop
633                        // advanced to, so the next index is always at least
634                        // one past the current one.
635                        index = cluster.text_range().end.max(index + 1);
636                        let Some(offset) = cluster.visual_offset() else {
637                            continue;
638                        };
639                        let baseline = cluster.line().metrics().baseline;
640                        let top = baseline + UNDERLINE_OFFSET;
641                        rects.push(Rect::new(
642                            offset,
643                            top + y,
644                            offset + cluster.advance(),
645                            top + UNDERLINE_THICKNESS + y,
646                        ));
647                    }
648                }
649                y += layout.size().height;
650            }
651            rects
652        })
653    }
654
655    /// Replaces the whole text and moves the caret to the end.
656    ///
657    /// This is how an outside write to the bound signal reaches the editor:
658    /// the user typed somewhere else, or the application replaced the value.
659    /// The selection collapses at the end, because a selection into text the
660    /// editor no longer has means nothing. The undo history is cleared, so
661    /// undo never brings back text the application replaced, and any
662    /// composition stops, because the text it was composing into is gone.
663    ///
664    /// Every carriage return and line feed pair, and every lone carriage
665    /// return, becomes one line feed, or every line break one space in a
666    /// [single-line](Editor::single_line) editor, and the bound signal is
667    /// written with the text the editor kept.
668    pub fn set_text(&self, text: &str) {
669        let text = self.incoming(text);
670        let text = text.as_ref();
671        self.session(|session| {
672            session.buffer.clear();
673            session.buffer.push_str(text);
674            session.table.rebuild(session.buffer);
675            let end = session.document_end();
676            session.selection = Selection {
677                anchor: end,
678                focus: end,
679                base: Base::Cluster,
680                h_pos: None,
681            };
682        });
683        *self.history.borrow_mut() = History::default();
684        *self.compose.borrow_mut() = None;
685        self.sync_text();
686    }
687
688    /// Inserts `text` at the caret, replacing the selection.
689    ///
690    /// Typed characters group into one undo step until the caret moves, a
691    /// different edit runs, or a whitespace character starts the next step,
692    /// so undo removes text a word at a time. Inserting more than one
693    /// character at once, as a paste does, is a step of its own. The text
694    /// may hold line breaks, which cut the document into more paragraphs. A
695    /// carriage return and line feed pair, or a lone carriage return, is
696    /// inserted as one line feed. A [single-line](Editor::single_line)
697    /// editor inserts every line break as one space instead.
698    pub fn insert(&self, text: &str) {
699        let text = self.incoming(text);
700        let text = text.as_ref();
701        let mut chars = text.chars();
702        let grouping = match (chars.next(), chars.next()) {
703            (Some(single), None) if single.is_whitespace() => Grouping::Starts,
704            (Some(_), None) => Grouping::Joins,
705            _ => Grouping::Alone,
706        };
707        self.edit(EditKind::Insert, grouping, |session| {
708            let range = session.selection_range();
709            let (byte, upstream) = Session::caret_after_insert(range.start, text);
710            session.replace(range, text, byte, upstream)
711        });
712    }
713
714    /// Inserts `text` from a clipboard, replacing the selection, as one undo
715    /// step.
716    ///
717    /// Line breaks in the text cut the document into more paragraphs, with
718    /// a carriage return and line feed pair, or a lone carriage return,
719    /// taken as one line feed. A [single-line](Editor::single_line) editor
720    /// pastes every line break as one space instead, so a paste from a
721    /// multi-line source lands on its one line. An empty paste inserts
722    /// nothing.
723    pub fn paste(&self, text: &str) {
724        if text.is_empty() {
725            return;
726        }
727        let text = self.incoming(text);
728        let text = text.as_ref();
729        self.edit(EditKind::Insert, Grouping::Alone, |session| {
730            let range = session.selection_range();
731            let (byte, upstream) = Session::caret_after_insert(range.start, text);
732            session.replace(range, text, byte, upstream)
733        });
734    }
735
736    /// Deletes the selected text, or nothing when the caret is collapsed.
737    ///
738    /// This is the deletion a cut pairs with the copy, and it is an undo
739    /// step of its own.
740    pub fn delete_selection(&self) {
741        self.edit(EditKind::Delete, Grouping::Alone, |session| {
742            let range = session.selection_range();
743            if range.is_empty() {
744                return false;
745            }
746            let byte = range.start;
747            session.replace(range, "", byte, true)
748        });
749    }
750
751    /// Returns the selected text, or `None` when the selection is collapsed.
752    pub fn selected_text(&self) -> Option<String> {
753        let range = self.selection();
754        if range.is_empty() {
755            return None;
756        }
757        Some(self.buffer.borrow()[range].to_owned())
758    }
759
760    /// Shows, updates or clears the text an input method is composing.
761    ///
762    /// Non-empty `text` becomes the preedit, which replaces the previous
763    /// preedit, and `cursor`, the byte range of the input method's cursor
764    /// inside `text`, moves the caret into the preedit, or hides it when
765    /// `None`. A composition that starts over a selection deletes the
766    /// selection first, the way typing over it would, and that deletion is an
767    /// undo step of its own. Empty `text` removes the preedit and leaves the
768    /// caret where the preedit started.
769    ///
770    /// The preedit is not the editor's text: [`Editor::text`] leaves it out
771    /// and the bound signal is not written, so a canceled or lost
772    /// composition leaves the value as it was. Only [`Editor::commit`] makes
773    /// composing text stick.
774    ///
775    /// Cursor offsets past the end of `text`, or inside one of its
776    /// characters, which a broken input method could report, are clamped or
777    /// snapped to the text.
778    pub fn compose(&self, text: &str, cursor: Option<(usize, usize)>) {
779        if text.is_empty() {
780            self.cancel_compose();
781            return;
782        }
783        if !self.is_composing() {
784            // The platform clears the preedit with an empty text right
785            // before it commits, so the selection has to go when the
786            // composition starts: a selection kept for a cancel to restore
787            // would come back right before the commit and stay next to the
788            // committed text.
789            self.delete_selection();
790        }
791        let cursor =
792            cursor.map(|(anchor, focus)| (char_boundary(text, anchor), char_boundary(text, focus)));
793        self.session(|session| {
794            let (replaced, start) = if let Some(previous) = self.compose.borrow_mut().as_ref() {
795                let range = previous.range.clone();
796                session.buffer.replace_range(range.clone(), text);
797                let start = range.start;
798                (range, start)
799            } else {
800                let start = session.selection_range().start;
801                session.buffer.insert_str(start, text);
802                (start..start, start)
803            };
804            let range = start..start + text.len();
805            *self.compose.borrow_mut() = Some(Compose {
806                range: range.clone(),
807                cursor,
808            });
809            session.table.splice(replaced, text);
810            let (anchor, focus) = match cursor {
811                Some((anchor, focus)) => (start + anchor, start + focus),
812                None => (start, start),
813            };
814            let anchor = session.cursor_anchor(anchor);
815            let focus = session.cursor_anchor(focus);
816            session.selection = Selection {
817                anchor,
818                focus,
819                base: Base::Cluster,
820                h_pos: None,
821            };
822        });
823    }
824
825    /// Removes the preedit of the running composition, if there is one.
826    fn cancel_compose(&self) {
827        if self.compose.borrow().is_some() {
828            self.session(|session| {
829                let Some(compose) = self.compose.borrow_mut().take() else {
830                    return;
831                };
832                let range = compose.range;
833                session.buffer.replace_range(range.clone(), "");
834                session.table.splice(range.clone(), "");
835                let caret = session.cursor_anchor(range.start);
836                session.selection = Selection {
837                    anchor: caret,
838                    focus: caret,
839                    base: Base::Cluster,
840                    h_pos: None,
841                };
842            });
843        }
844    }
845
846    /// Commits `text` from an input method, replacing any preedit with it.
847    ///
848    /// A commit can arrive while the preedit is still showing, in which case
849    /// the committed text takes its place, or after the platform already
850    /// cleared it, in which case it is inserted at the caret. The committed
851    /// text groups for undo like typed text: one character joins the word
852    /// around it, and longer text is a step of its own. An empty commit only
853    /// removes the preedit.
854    pub fn commit(&self, text: &str) {
855        if text.is_empty() {
856            self.cancel_compose();
857            return;
858        }
859        self.insert(text);
860    }
861
862    /// Deletes one grapheme before the caret, or the selection.
863    ///
864    /// A backspace before a combining mark takes the mark off first, and one
865    /// after a line break joins the two lines. Repeated deletes group into
866    /// one undo step until the caret moves or a different edit runs.
867    pub fn backspace(&self) {
868        self.edit(EditKind::Delete, Grouping::Joins, |session| {
869            if session.selection.anchor != session.selection.focus {
870                let range = session.selection_range();
871                let byte = range.start;
872                return session.replace(range, "", byte, true);
873            }
874            let focus = session.selection.focus;
875            let Some(range) = session.backspace_range(focus) else {
876                return false;
877            };
878            let byte = range.start;
879            session.replace(range, "", byte, false)
880        });
881    }
882
883    /// Deletes one word before the caret, or the selection, as one undo step.
884    pub fn backspace_word(&self) {
885        self.edit(EditKind::Delete, Grouping::Alone, |session| {
886            if session.selection.anchor != session.selection.focus {
887                let range = session.selection_range();
888                let byte = range.start;
889                return session.replace(range, "", byte, true);
890            }
891            let focus = session.selection.focus;
892            let Some(start) = session.word_before(focus) else {
893                return false;
894            };
895            let end = session.table.global(focus);
896            if start >= end {
897                return false;
898            }
899            let byte = start;
900            session.replace(byte..end, "", byte, false)
901        });
902    }
903
904    /// Deletes one grapheme after the caret, or the selection.
905    ///
906    /// A delete before a line break joins the two lines. Repeated deletes
907    /// group into one undo step until the caret moves or a different edit
908    /// runs.
909    pub fn delete(&self) {
910        self.edit(EditKind::Delete, Grouping::Joins, |session| {
911            if session.selection.anchor != session.selection.focus {
912                let range = session.selection_range();
913                let byte = range.start;
914                return session.replace(range, "", byte, true);
915            }
916            let focus = session.selection.focus;
917            let Some(range) = session.forward_delete_range(focus) else {
918                return false;
919            };
920            let byte = range.start;
921            session.replace(range, "", byte, false)
922        });
923    }
924
925    /// Deletes one word after the caret, or the selection, as one undo step.
926    pub fn delete_word(&self) {
927        self.edit(EditKind::Delete, Grouping::Alone, |session| {
928            if session.selection.anchor != session.selection.focus {
929                let range = session.selection_range();
930                let byte = range.start;
931                return session.replace(range, "", byte, true);
932            }
933            let focus = session.selection.focus;
934            let start = session.table.global(focus);
935            let Some(end) = session.word_after(focus) else {
936                return false;
937            };
938            if start >= end {
939                return false;
940            }
941            let byte = start;
942            session.replace(byte..end, "", byte, false)
943        });
944    }
945
946    /// Undoes the last undo step, restoring the text and the selection from
947    /// before it, or does nothing when there is none.
948    ///
949    /// Nothing is undone while an input method is composing, because the
950    /// composition owns the text around the caret until it commits.
951    pub fn undo(&self) {
952        if self.is_composing() {
953            return;
954        }
955        let Some(previous) = self.history.borrow_mut().pop_undo() else {
956            return;
957        };
958        let current = self.snapshot();
959        let mut history = self.history.borrow_mut();
960        history.push_redo(current);
961        history.open = None;
962        drop(history);
963        self.restore(previous);
964    }
965
966    /// Redoes the last undone step, or does nothing when there is none.
967    ///
968    /// Any edit after an undo discards the steps it could have redone.
969    pub fn redo(&self) {
970        if self.is_composing() {
971            return;
972        }
973        let Some(next) = self.history.borrow_mut().pop_redo() else {
974            return;
975        };
976        let current = self.snapshot();
977        let mut history = self.history.borrow_mut();
978        history.push_undo(current);
979        history.open = None;
980        drop(history);
981        self.restore(next);
982    }
983
984    /// Moves the caret one grapheme to the left, in visual order.
985    pub fn move_left(&self) {
986        self.caret_move(|session| session.move_horizontally(false, false));
987    }
988
989    /// Moves the caret one grapheme to the right, in visual order.
990    pub fn move_right(&self) {
991        self.caret_move(|session| session.move_horizontally(true, false));
992    }
993
994    /// Moves the caret one word to the left, in visual order.
995    pub fn move_word_left(&self) {
996        self.caret_move(|session| session.move_word(false, false));
997    }
998
999    /// Moves the caret one word to the right, in visual order.
1000    pub fn move_word_right(&self) {
1001        self.caret_move(|session| session.move_word(true, false));
1002    }
1003
1004    /// Moves the caret one visual line up, keeping its horizontal position.
1005    pub fn move_up(&self) {
1006        self.caret_move(|session| session.move_lines(-1, false));
1007    }
1008
1009    /// Moves the caret one visual line down, keeping its horizontal position.
1010    pub fn move_down(&self) {
1011        self.caret_move(|session| session.move_lines(1, false));
1012    }
1013
1014    /// Moves the caret `lines` visual lines down, or up when negative,
1015    /// keeping its horizontal position where the move can.
1016    pub fn move_lines(&self, lines: isize) {
1017        self.caret_move(|session| session.move_lines(lines, false));
1018    }
1019
1020    /// Moves the caret to the start of the text.
1021    pub fn move_to_start(&self) {
1022        self.caret_move(|session| session.move_to_edge(false, false));
1023    }
1024
1025    /// Moves the caret to the end of the text.
1026    pub fn move_to_end(&self) {
1027        self.caret_move(|session| session.move_to_edge(true, false));
1028    }
1029
1030    /// Moves the caret to the start of the visual line it sits on.
1031    ///
1032    /// A paragraph wrapped over several lines moves within the wrapped line,
1033    /// not to the start of the paragraph.
1034    pub fn move_to_line_start(&self) {
1035        self.caret_move(|session| session.move_to_line_edge(false, false));
1036    }
1037
1038    /// Moves the caret to the end of the visual line it sits on.
1039    ///
1040    /// The end of a wrapped line sits after the space the line broke at, on
1041    /// the same line.
1042    pub fn move_to_line_end(&self) {
1043        self.caret_move(|session| session.move_to_line_edge(true, false));
1044    }
1045
1046    /// Moves the selection focus one grapheme to the left.
1047    pub fn extend_left(&self) {
1048        self.caret_move(|session| session.move_horizontally(false, true));
1049    }
1050
1051    /// Moves the selection focus one grapheme to the right.
1052    pub fn extend_right(&self) {
1053        self.caret_move(|session| session.move_horizontally(true, true));
1054    }
1055
1056    /// Moves the selection focus one word to the left.
1057    pub fn extend_word_left(&self) {
1058        self.caret_move(|session| session.move_word(false, true));
1059    }
1060
1061    /// Moves the selection focus one word to the right.
1062    pub fn extend_word_right(&self) {
1063        self.caret_move(|session| session.move_word(true, true));
1064    }
1065
1066    /// Moves the selection focus one visual line up.
1067    pub fn extend_up(&self) {
1068        self.caret_move(|session| session.move_lines(-1, true));
1069    }
1070
1071    /// Moves the selection focus one visual line down.
1072    pub fn extend_down(&self) {
1073        self.caret_move(|session| session.move_lines(1, true));
1074    }
1075
1076    /// Moves the selection focus `lines` visual lines down, or up when
1077    /// negative.
1078    pub fn extend_lines(&self, lines: isize) {
1079        self.caret_move(|session| session.move_lines(lines, true));
1080    }
1081
1082    /// Moves the selection focus to the start of the text.
1083    pub fn extend_to_start(&self) {
1084        self.caret_move(|session| session.move_to_edge(false, true));
1085    }
1086
1087    /// Moves the selection focus to the end of the text.
1088    pub fn extend_to_end(&self) {
1089        self.caret_move(|session| session.move_to_edge(true, true));
1090    }
1091
1092    /// Moves the selection focus to the start of the visual line it sits
1093    /// on.
1094    pub fn extend_to_line_start(&self) {
1095        self.caret_move(|session| session.move_to_line_edge(false, true));
1096    }
1097
1098    /// Moves the selection focus to the end of the visual line it sits on.
1099    pub fn extend_to_line_end(&self) {
1100        self.caret_move(|session| session.move_to_line_edge(true, true));
1101    }
1102
1103    /// Selects the whole text.
1104    pub fn select_all(&self) {
1105        self.caret_move(|session| {
1106            let end = session.document_end();
1107            session.selection = Selection {
1108                anchor: START,
1109                focus: end,
1110                base: Base::Cluster,
1111                h_pos: None,
1112            };
1113        });
1114    }
1115
1116    /// Places the caret at the point, in the editor's own coordinates.
1117    ///
1118    /// The coordinates are relative to the top left of the text, which a
1119    /// widget gets by subtracting where it draws the text from the pointer
1120    /// position. A point below the last line places the caret at the end of
1121    /// its paragraph, and one past a line's end places it at that line's
1122    /// end.
1123    pub fn move_to_point(&self, x: f32, y: f32) {
1124        self.caret_move(|session| {
1125            let target = session.point_anchor(x, y);
1126            session.set_focus(target, false);
1127        });
1128    }
1129
1130    /// Moves the selection focus to the point, in the editor's own
1131    /// coordinates, keeping the anchor where it was.
1132    ///
1133    /// The extension follows the granularity a double or triple click left
1134    /// the selection at: by cluster after a plain drag, and by word after a
1135    /// double click.
1136    pub fn extend_to_point(&self, x: f32, y: f32) {
1137        self.caret_move(|session| session.extend_to_point(x, y));
1138    }
1139
1140    /// Selects the word under the point, in the editor's own coordinates.
1141    pub fn select_word_at_point(&self, x: f32, y: f32) {
1142        self.caret_move(|session| session.select_word_at_point(x, y));
1143    }
1144
1145    /// Returns the rectangle of the caret, `width` logical pixels wide, in
1146    /// the editor's own coordinates, or `None` when there is no caret.
1147    ///
1148    /// A caret an input method hid, by composing without a cursor, is none.
1149    pub fn cursor_geometry(&self, width: f32) -> Option<Rect> {
1150        if self
1151            .compose
1152            .borrow()
1153            .as_ref()
1154            .is_some_and(|compose| compose.cursor.is_none())
1155        {
1156            return None;
1157        }
1158        self.session(|session| {
1159            let focus = session.selection.focus;
1160            let (caret, y) = session.caret_box(focus, width);
1161            Some(Rect::new(caret.x0, caret.y0 + y, caret.x1, caret.y1 + y))
1162        })
1163    }
1164
1165    /// Returns one rectangle per selected piece of a line, in the editor's
1166    /// own coordinates.
1167    ///
1168    /// A collapsed selection yields no rectangles.
1169    pub fn selection_geometry(&self) -> Vec<Rect> {
1170        let mut rects = Vec::new();
1171        self.selection_rects(EVERYWHERE, &mut rects);
1172        rects
1173    }
1174
1175    /// Fills `out` with the rectangles of [`Editor::selection_geometry`]
1176    /// that meet the vertical range `visible`, in the editor's own
1177    /// coordinates, after clearing it.
1178    ///
1179    /// A painter that shows part of a tall document passes the range it
1180    /// shows and keeps `out` between frames, so a selection over the whole
1181    /// document neither walks nor allocates past what is on screen.
1182    pub(crate) fn selection_rects(&self, visible: (f32, f32), out: &mut Vec<Rect>) {
1183        out.clear();
1184        self.session(|session| {
1185            let range = session.selection_range();
1186            if !range.is_empty() {
1187                session.range_rects(range, visible, |rect| out.push(rect));
1188            }
1189        });
1190    }
1191
1192    /// Returns the width and height of the laid out text.
1193    ///
1194    /// The width is the longest paragraph, and the height is the sum of
1195    /// every paragraph's lines.
1196    ///
1197    /// # Panics
1198    ///
1199    /// Panics under the conditions of [`TextSystem::with`].
1200    pub fn layout_size(&self) -> Size {
1201        self.session(|session| {
1202            session.refresh_all();
1203            let mut size = Size {
1204                width: 0.0,
1205                height: 0.0,
1206            };
1207            for paragraph in &session.table.paragraphs {
1208                let paragraph_size = paragraph
1209                    .layout
1210                    .as_ref()
1211                    .expect("refresh built every layout")
1212                    .size();
1213                size.width = size.width.max(paragraph_size.width);
1214                size.height += paragraph_size.height;
1215            }
1216            size
1217        })
1218    }
1219
1220    /// Converts the laid out text into glyph runs at `origin`, in paint
1221    /// order, with every run in `color`.
1222    ///
1223    /// `origin` is where the top left corner of the text box goes, in the
1224    /// caller's paint coordinates. A widget that paints a large document
1225    /// walks [`Editor::paragraphs`] instead and builds the runs of the
1226    /// paragraphs it shows, so an edit elsewhere does not rebuild them.
1227    ///
1228    /// # Panics
1229    ///
1230    /// Panics under the conditions of [`TextSystem::with`], in particular
1231    /// when called from inside another `with` call on the same thread.
1232    pub fn glyph_runs_at(&self, origin: Point, color: Color) -> Vec<GlyphRun> {
1233        self.session(|session| {
1234            session.refresh_all();
1235            let mut runs = Vec::new();
1236            let mut y = 0.0;
1237            for paragraph in &session.table.paragraphs {
1238                let layout = paragraph
1239                    .layout
1240                    .as_ref()
1241                    .expect("refresh built every layout");
1242                runs.extend(glyph_runs(
1243                    &layout.layout,
1244                    Point {
1245                        x: origin.x,
1246                        y: origin.y + y,
1247                    },
1248                    color,
1249                ));
1250                y += layout.size().height;
1251            }
1252            runs
1253        })
1254    }
1255
1256    /// Walks the editor's laid out paragraphs, top to bottom, handing each
1257    /// to `f`.
1258    ///
1259    /// A painter walks these to build its glyph runs one paragraph at a
1260    /// time: each paragraph carries a stamp that changes when its layout
1261    /// does, so a cache keyed on the stamp rebuilds only the paragraphs an
1262    /// edit touched, and a painter that shows part of the text builds the
1263    /// paragraphs it shows alone.
1264    ///
1265    /// # Panics
1266    ///
1267    /// Panics under the conditions of [`TextSystem::with`].
1268    pub fn paragraphs(&self, mut f: impl FnMut(EditorParagraph<'_>)) {
1269        self.session(|session| session.refresh_all());
1270        let table = self.paragraphs.borrow();
1271        let mut y = 0.0;
1272        for paragraph in &table.paragraphs {
1273            let layout = paragraph
1274                .layout
1275                .as_ref()
1276                .expect("paragraphs built every layout");
1277            f(EditorParagraph {
1278                layout,
1279                stamp: paragraph.stamp,
1280                y,
1281            });
1282            y += layout.size().height;
1283        }
1284    }
1285
1286    /// Returns `text` with its line breaks as the editor keeps them: every
1287    /// break one space in a single-line editor, and every carriage return
1288    /// turned into a line feed otherwise.
1289    fn incoming<'a>(&self, text: &'a str) -> Cow<'a, str> {
1290        if self.single_line {
1291            if text.contains(crate::line_breaks::is_line_break) {
1292                Cow::Owned(flatten_line_breaks(text))
1293            } else {
1294                Cow::Borrowed(text)
1295            }
1296        } else {
1297            normalize_breaks(text)
1298        }
1299    }
1300
1301    /// Writes the editor's text into the bound signal, when it changed.
1302    ///
1303    /// An equal write would wake every binding that reads the signal for
1304    /// nothing, so the comparison is what keeps an idle editor from asking
1305    /// for frames.
1306    fn sync_text(&self) {
1307        if !self.value.read(|current| self.has_text(current)) {
1308            self.value.set(self.text());
1309        }
1310    }
1311
1312    /// Runs one edit of `kind` and records it in the undo history.
1313    ///
1314    /// `f` returns whether it changed the text, and an edit that changes no
1315    /// text records nothing. One that [`Grouping::Joins`] extends the open
1316    /// step when the last edit was of the same kind and left the selection
1317    /// where this one starts, and otherwise starts a step of its own, which
1318    /// is the only time the text is copied for undo, right before the text
1319    /// first changes. Any edit discards the steps redo could have taken, and
1320    /// one that is [`Grouping::Alone`] leaves no step open behind it.
1321    fn edit(&self, kind: EditKind, grouping: Grouping, f: impl FnOnce(&mut Session<'_>) -> bool) {
1322        // An edit while composing ends the composition first, so the edit
1323        // acts on the committed text and the preedit never shifts across an
1324        // edit.
1325        self.cancel_compose();
1326        let starts = self.selection();
1327        let selection = *self.selection.borrow();
1328        let continues = grouping == Grouping::Joins
1329            && self
1330                .history
1331                .borrow()
1332                .open
1333                .as_ref()
1334                .is_some_and(|(open, ended)| *open == kind && *ended == starts);
1335        let (changed, before) = self.session_keeping(!continues, f);
1336        if !changed {
1337            return;
1338        }
1339        let mut history = self.history.borrow_mut();
1340        if let Some(text) = before {
1341            history.push_undo(Snapshot { text, selection });
1342        }
1343        history.clear_redo();
1344        history.open = (grouping != Grouping::Alone).then(|| (kind, self.selection()));
1345        drop(history);
1346        self.sync_text();
1347    }
1348
1349    /// Returns the text and the selection as they are now.
1350    fn snapshot(&self) -> Snapshot {
1351        Snapshot {
1352            // A snapshot only happens outside compositions, where the
1353            // buffer is the committed text.
1354            text: copy_for_undo(&self.buffer.borrow()),
1355            selection: *self.selection.borrow(),
1356        }
1357    }
1358
1359    /// Puts the text and the selection of `snapshot` back, and writes the
1360    /// text to the bound signal.
1361    ///
1362    /// Only the bytes between the longest common start and end of the two
1363    /// texts are replaced, so the paragraphs outside them keep their
1364    /// layouts and an undo in a large document lays out what it changed.
1365    fn restore(&self, snapshot: Snapshot) {
1366        self.session(|session| {
1367            let range = changed_range(session.buffer, &snapshot.text);
1368            let kept_end = session.buffer.len() - range.end;
1369            let replacement = &snapshot.text[range.start..snapshot.text.len() - kept_end];
1370            if !range.is_empty() || !replacement.is_empty() {
1371                session.buffer.replace_range(range.clone(), replacement);
1372                session.table.splice(range, replacement);
1373            }
1374            session.selection = snapshot.selection;
1375        });
1376        self.sync_text();
1377    }
1378
1379    /// Lets `f` act on the editor's text, paragraph table and selection.
1380    ///
1381    /// The session borrows the parts an operation touches and works on a
1382    /// copy of the selection, which the editor stores back afterwards.
1383    fn session<R>(&self, f: impl FnOnce(&mut Session<'_>) -> R) -> R {
1384        self.session_keeping(false, f).0
1385    }
1386
1387    /// Runs `f` as [`Editor::session`] does and, when `keep_before` is set,
1388    /// returns the text as it was before `f` first changed it, or `None`
1389    /// when `f` changed nothing.
1390    fn session_keeping<R>(
1391        &self,
1392        keep_before: bool,
1393        f: impl FnOnce(&mut Session<'_>) -> R,
1394    ) -> (R, Option<String>) {
1395        let mut buffer = self.buffer.borrow_mut();
1396        let mut table = self.paragraphs.borrow_mut();
1397        let style = self.style.borrow();
1398        let mut session = Session {
1399            buffer: &mut buffer,
1400            table: &mut table,
1401            style: &style,
1402            width: self.width.get(),
1403            builds: &self.builds,
1404            selection: *self.selection.borrow(),
1405            keep_before,
1406            before: None,
1407        };
1408        let result = f(&mut session);
1409        *self.selection.borrow_mut() = session.selection;
1410        (result, session.before)
1411    }
1412
1413    /// Runs a caret move, which ends the open undo step: text typed after
1414    /// the caret moved is a step of its own, even where the caret came back
1415    /// to.
1416    fn caret_move(&self, f: impl FnOnce(&mut Session<'_>)) {
1417        self.session(f);
1418        self.history.borrow_mut().open = None;
1419    }
1420
1421    /// Drops every paragraph layout so the next need rebuilds them.
1422    fn invalidate_layouts(&self) {
1423        for paragraph in self.paragraphs.borrow_mut().paragraphs.iter_mut() {
1424            paragraph.layout = None;
1425        }
1426    }
1427}
1428
1429/// A vertical range that every rectangle meets.
1430const EVERYWHERE: (f32, f32) = (f32::NEG_INFINITY, f32::INFINITY);
1431
1432/// The anchor the caret starts at in an empty document.
1433const START: Anchor = Anchor {
1434    paragraph: 0,
1435    index: 0,
1436    affinity: Affinity::Downstream,
1437};
1438
1439/// One laid out paragraph of an editor.
1440pub struct EditorParagraph<'a> {
1441    /// The paragraph's layout.
1442    layout: &'a TextLayout,
1443    /// The editor's build count when the layout was built, which changes
1444    /// when the paragraph is laid out again.
1445    stamp: u64,
1446    /// The paragraph's offset from the top of the text, in logical pixels.
1447    y: f32,
1448}
1449
1450impl EditorParagraph<'_> {
1451    /// Returns the stamp the paragraph's layout was built under, which
1452    /// changes when the paragraph is laid out again.
1453    pub fn stamp(&self) -> u64 {
1454        self.stamp
1455    }
1456
1457    /// Returns where the paragraph's top left corner sits, relative to the
1458    /// top left of the text, in logical pixels.
1459    pub fn offset(&self) -> Point {
1460        Point { x: 0.0, y: self.y }
1461    }
1462
1463    /// Returns the width and height of the laid out paragraph in logical
1464    /// pixels.
1465    pub fn size(&self) -> Size {
1466        self.layout.size()
1467    }
1468
1469    /// Converts the paragraph into glyph runs at `origin`, in paint order,
1470    /// with every run in `color`.
1471    ///
1472    /// `origin` is where the top left corner of the paragraph goes, in the
1473    /// caller's paint coordinates.
1474    pub fn glyph_runs_at(&self, origin: Point, color: Color) -> Vec<GlyphRun> {
1475        self.layout.glyph_runs_at(origin, color)
1476    }
1477}
1478
1479/// The state one operation on an editor acts on.
1480///
1481/// The operation borrows the editor's text and paragraph table through the
1482/// editor, works on a copy of the selection, and lays out the paragraphs it
1483/// needs. The editor stores the selection back and counts the operation as
1484/// a change when the operation says it edited the text or the selection's
1485/// ends moved.
1486struct Session<'a> {
1487    /// The editor's text, including any composing preedit.
1488    buffer: &'a mut String,
1489    /// The paragraphs the buffer is cut into, with their layout caches.
1490    table: &'a mut ParagraphTable,
1491    /// The font style the paragraphs lay out with.
1492    style: &'a TextStyle,
1493    /// The width paragraphs wrap at, in logical pixels.
1494    width: Option<f32>,
1495    /// The editor's build count, which paragraph stamps are counted against.
1496    builds: &'a Cell<u64>,
1497    /// A copy of the selection the operation works on.
1498    selection: Selection,
1499    /// Whether the first change copies the text as it was, for undo.
1500    keep_before: bool,
1501    /// The text as it was before the first change, when one was kept.
1502    before: Option<String>,
1503}
1504
1505impl Session<'_> {
1506    /// The byte range the selection covers, in text order.
1507    fn selection_range(&self) -> Range<usize> {
1508        let anchor = self.table.global(self.selection.anchor);
1509        let focus = self.table.global(self.selection.focus);
1510        anchor.min(focus)..anchor.max(focus)
1511    }
1512
1513    /// The anchor a cursor at `byte` takes.
1514    ///
1515    /// A cursor the input method places takes the downstream side of its
1516    /// cluster, or the upstream side of the text's end, which no cluster
1517    /// follows. `upstream` flips that for the ends a deletion leaves, which
1518    /// parley builds from the upstream side.
1519    fn cursor_anchor(&self, byte: usize) -> Anchor {
1520        self.anchor_at(byte, false)
1521    }
1522
1523    fn anchor_at(&self, byte: usize, upstream: bool) -> Anchor {
1524        let (paragraph, index) = self.table.locate(byte);
1525        let affinity = if byte == 0 {
1526            // No cluster ends at the start of the text.
1527            Affinity::Downstream
1528        } else if byte >= self.buffer.len() || upstream {
1529            Affinity::Upstream
1530        } else {
1531            Affinity::Downstream
1532        };
1533        Anchor {
1534            paragraph,
1535            index,
1536            affinity,
1537        }
1538    }
1539
1540    /// The caret byte and side an insert of `text` at `start` leaves, which
1541    /// sits after the text, on the side the text's ending names.
1542    fn caret_after_insert(start: usize, text: &str) -> (usize, bool) {
1543        (
1544            start + text.len(),
1545            !text.ends_with(['\n', '\r', '\u{2028}', '\u{2029}']),
1546        )
1547    }
1548
1549    /// The anchor of the end of the text.
1550    fn document_end(&self) -> Anchor {
1551        let last = self.table.paragraphs.len() - 1;
1552        Anchor {
1553            paragraph: last,
1554            index: self.table.paragraphs[last].len,
1555            affinity: Affinity::Upstream,
1556        }
1557    }
1558
1559    /// Builds the paragraph's layout when it is missing.
1560    fn ensure(&mut self, index: usize) {
1561        if self.table.paragraphs[index].layout.is_some() {
1562            return;
1563        }
1564        let paragraph = &self.table.paragraphs[index];
1565        let text = &self.buffer[paragraph.start..paragraph.start + paragraph.len];
1566        let layout = TextSystem::with(|system| {
1567            TextLayout::build(
1568                text,
1569                self.style,
1570                self.width,
1571                &mut system.fonts,
1572                &mut system.layouts,
1573            )
1574        });
1575        self.builds.set(self.builds.get() + 1);
1576        let paragraph = &mut self.table.paragraphs[index];
1577        paragraph.layout = Some(layout);
1578        paragraph.stamp = self.builds.get();
1579    }
1580
1581    /// Builds every paragraph layout that is missing.
1582    fn refresh_all(&mut self) {
1583        if !self
1584            .table
1585            .paragraphs
1586            .iter()
1587            .any(|paragraph| paragraph.layout.is_none())
1588        {
1589            return;
1590        }
1591        for index in 0..self.table.paragraphs.len() {
1592            self.ensure(index);
1593        }
1594    }
1595
1596    /// Builds the paragraph's layout when it is missing and runs `f` with it.
1597    fn with_layout<R>(&mut self, index: usize, f: impl FnOnce(&TextLayout) -> R) -> R {
1598        self.ensure(index);
1599        let layout = self.table.paragraphs[index]
1600            .layout
1601            .as_ref()
1602            .expect("ensure built the layout");
1603        f(layout)
1604    }
1605
1606    /// The paragraph the point's `y` falls in, with the paragraph's offset
1607    /// from the top of the text.
1608    ///
1609    /// A point below the last paragraph names the last one.
1610    fn paragraph_at_y(&mut self, y: f32) -> (usize, f32) {
1611        let mut offset = 0.0;
1612        let mut index = 0;
1613        loop {
1614            self.ensure(index);
1615            let height = self.table.paragraphs[index]
1616                .layout
1617                .as_ref()
1618                .expect("ensure built the layout")
1619                .size()
1620                .height;
1621            if y < offset + height || index + 1 >= self.table.paragraphs.len() {
1622                return (index, offset);
1623            }
1624            offset += height;
1625            index += 1;
1626        }
1627    }
1628
1629    /// The offset the paragraph sits at from the top of the text, building
1630    /// the layouts the sum runs over.
1631    fn offset_of(&mut self, index: usize) -> f32 {
1632        let mut offset = 0.0;
1633        for i in 0..index {
1634            self.ensure(i);
1635            offset += self.table.paragraphs[i]
1636                .layout
1637                .as_ref()
1638                .expect("ensure built the layout")
1639                .size()
1640                .height;
1641        }
1642        offset
1643    }
1644
1645    /// The rectangle of a paragraph-local caret in its paragraph, with the
1646    /// paragraph's offset from the top of the text.
1647    fn caret_box(&mut self, anchor: Anchor, width: f32) -> (Rect, f32) {
1648        let y = self.offset_of(anchor.paragraph);
1649        self.with_layout(anchor.paragraph, |layout| {
1650            let cursor = Cursor::from_byte_index(&layout.layout, anchor.index, anchor.affinity);
1651            let area = cursor.geometry(&layout.layout, width);
1652            (
1653                Rect::new(
1654                    area.x0 as f32,
1655                    area.y0 as f32,
1656                    area.x1 as f32,
1657                    area.y1 as f32,
1658                ),
1659                y,
1660            )
1661        })
1662    }
1663
1664    /// Hands `f` one rectangle per line piece the byte range covers that
1665    /// meets the vertical range `visible`, in the editor's coordinates.
1666    ///
1667    /// A line break inside the range shows as a narrow rectangle after the
1668    /// last line of its paragraph, so a selected empty line still shows.
1669    /// Paragraphs outside `visible` are passed over without walking their
1670    /// lines.
1671    fn range_rects(&mut self, range: Range<usize>, visible: (f32, f32), mut f: impl FnMut(Rect)) {
1672        self.refresh_all();
1673        let newline_width = NEWLINE_WIDTH * self.style.size;
1674        let count = self.table.paragraphs.len();
1675        let meets = |rect: &Rect| rect.y1 >= visible.0 && rect.y0 <= visible.1;
1676        let mut y = 0.0;
1677        for index in 0..count {
1678            let paragraph = &self.table.paragraphs[index];
1679            let start = paragraph.start;
1680            let end = start + paragraph.len;
1681            let layout = paragraph
1682                .layout
1683                .as_ref()
1684                .expect("refresh built every layout");
1685            let height = layout.size().height;
1686            if range.end < start || y > visible.1 {
1687                break;
1688            }
1689            if range.start > end || y + height < visible.0 {
1690                y += height;
1691                continue;
1692            }
1693            if range.start < end && range.end > start {
1694                let local_start = range.start.max(start) - start;
1695                let local_end = range.end.min(end) - start;
1696                if local_start < local_end {
1697                    let selection = ParagraphSelection::new(
1698                        Cursor::from_byte_index(&layout.layout, local_start, Affinity::Downstream),
1699                        Cursor::from_byte_index(&layout.layout, local_end, Affinity::Downstream),
1700                    );
1701                    selection.geometry_with(&layout.layout, |area, _| {
1702                        let area = rect(area);
1703                        let area = Rect::new(area.x0, area.y0 + y, area.x1, area.y1 + y);
1704                        if area.width() > 0.0 && meets(&area) {
1705                            f(area);
1706                        }
1707                    });
1708                }
1709            }
1710            if index + 1 < count
1711                && range.start <= end
1712                && range.end > end
1713                && let Some(line) = layout.layout.get(layout.layout.len().saturating_sub(1))
1714            {
1715                let metrics = line.metrics();
1716                let top = metrics.block_min_coord.min(metrics.content_block_min_coord);
1717                let bottom = metrics.block_max_coord.max(metrics.content_block_max_coord);
1718                let caret =
1719                    Cursor::from_byte_index(&layout.layout, end - start, Affinity::Upstream)
1720                        .geometry(&layout.layout, 0.0);
1721                let x = caret.x0 as f32;
1722                let (x0, x1) = if layout.layout.is_rtl() {
1723                    (x - newline_width, x)
1724                } else {
1725                    (x, x + newline_width)
1726                };
1727                let area = Rect::new(x0, top + y, x1, bottom + y);
1728                if meets(&area) {
1729                    f(area);
1730                }
1731            }
1732            y += height;
1733        }
1734    }
1735
1736    /// Collapses the selection at `focus`, or extends to it, and resets the
1737    /// granularity a shift click extends with.
1738    fn set_focus(&mut self, focus: Anchor, extend: bool) {
1739        if extend {
1740            self.selection.focus = focus;
1741        } else {
1742            self.selection.anchor = focus;
1743            self.selection.focus = focus;
1744        }
1745        self.selection.base = Base::Cluster;
1746        self.selection.h_pos = None;
1747    }
1748
1749    /// Replaces `range` with `text` and leaves the selection collapsed at
1750    /// `caret_byte`, on the side `upstream` names.
1751    ///
1752    /// The caret anchor is read after the table followed the edit, so an
1753    /// insert of text that holds line breaks names the paragraph the caret
1754    /// actually landed in. Returns whether the text changed: a replacement
1755    /// with the same text only moves the caret, and keeps the layouts.
1756    fn replace(
1757        &mut self,
1758        range: Range<usize>,
1759        text: &str,
1760        caret_byte: usize,
1761        upstream: bool,
1762    ) -> bool {
1763        let changed = self.buffer[range.clone()] != *text;
1764        if changed {
1765            if self.keep_before && self.before.is_none() {
1766                self.before = Some(copy_for_undo(self.buffer));
1767            }
1768            if range.is_empty() {
1769                self.buffer.insert_str(range.start, text);
1770            } else {
1771                self.buffer.replace_range(range.clone(), text);
1772            }
1773            self.table.splice(range, text);
1774        }
1775        let caret = self.anchor_at(caret_byte, upstream);
1776        self.selection = Selection {
1777            anchor: caret,
1778            focus: caret,
1779            base: Base::Cluster,
1780            h_pos: None,
1781        };
1782        changed
1783    }
1784
1785    /// Moves the focus one cluster left or right, in visual order.
1786    fn move_horizontally(&mut self, forward: bool, extend: bool) {
1787        if !extend && self.selection.anchor != self.selection.focus {
1788            // A plain move over a selection collapses to the end the
1789            // movement points at. Which end that is reads from the
1790            // geometry, because in a bidirectional paragraph the text order
1791            // does not decide what comes first.
1792            let (first, last) = self.visual_ends();
1793            self.set_focus(if forward { last } else { first }, false);
1794            return;
1795        }
1796        let focus = self.selection.focus;
1797        if !forward && self.at_visual_left_edge(focus) {
1798            // The previous cluster is the line break that ends the previous
1799            // paragraph, which no layout holds.
1800            if focus.paragraph > 0 {
1801                let previous = focus.paragraph - 1;
1802                let target = Anchor {
1803                    paragraph: previous,
1804                    index: self.visual_right_index(previous),
1805                    affinity: Affinity::Downstream,
1806                };
1807                self.set_focus(target, extend);
1808            }
1809            return;
1810        }
1811        if forward && self.at_visual_right_edge(focus) {
1812            if focus.paragraph + 1 < self.table.paragraphs.len() {
1813                let next = focus.paragraph + 1;
1814                let target = Anchor {
1815                    paragraph: next,
1816                    index: self.visual_left_index(next),
1817                    affinity: Affinity::Downstream,
1818                };
1819                self.set_focus(target, extend);
1820            }
1821            return;
1822        }
1823        let moved = self.with_layout(focus.paragraph, |layout| {
1824            let cursor = Cursor::from_byte_index(&layout.layout, focus.index, focus.affinity);
1825            let moved = if forward {
1826                cursor.next_visual(&layout.layout)
1827            } else {
1828                cursor.previous_visual(&layout.layout)
1829            };
1830            Anchor {
1831                paragraph: focus.paragraph,
1832                index: moved.index(),
1833                affinity: moved.affinity(),
1834            }
1835        });
1836        self.set_focus(moved, extend);
1837    }
1838
1839    /// Whether the paragraph's dominant direction is right-to-left.
1840    fn is_rtl(&mut self, paragraph: usize) -> bool {
1841        self.with_layout(paragraph, |layout| layout.layout.is_rtl())
1842    }
1843
1844    /// Whether a caret at `anchor` sits at the paragraph's visual left
1845    /// edge, with no cluster of the paragraph further left.
1846    ///
1847    /// Which logical index a visual edge holds hangs on the direction: the
1848    /// visual left edge is the logical start of a left-to-right paragraph
1849    /// and the logical end of a right-to-left one. An empty paragraph is
1850    /// both edges at once.
1851    fn at_visual_left_edge(&mut self, anchor: Anchor) -> bool {
1852        let len = self.table.paragraphs[anchor.paragraph].len;
1853        if len == 0 {
1854            return true;
1855        }
1856        if anchor.index >= len {
1857            return self.is_rtl(anchor.paragraph);
1858        }
1859        anchor.index == 0 && !self.is_rtl(anchor.paragraph)
1860    }
1861
1862    /// Whether a caret at `anchor` sits at the paragraph's visual right
1863    /// edge, with no cluster of the paragraph further right.
1864    fn at_visual_right_edge(&mut self, anchor: Anchor) -> bool {
1865        let len = self.table.paragraphs[anchor.paragraph].len;
1866        if len == 0 {
1867            return true;
1868        }
1869        if anchor.index >= len {
1870            return !self.is_rtl(anchor.paragraph);
1871        }
1872        anchor.index == 0 && self.is_rtl(anchor.paragraph)
1873    }
1874
1875    /// The logical index the caret holds at the visual right edge of a
1876    /// paragraph, which its direction decides.
1877    fn visual_right_index(&mut self, paragraph: usize) -> usize {
1878        if self.is_rtl(paragraph) {
1879            0
1880        } else {
1881            self.table.paragraphs[paragraph].len
1882        }
1883    }
1884
1885    /// The logical index the caret holds at the visual left edge of a
1886    /// paragraph, which its direction decides.
1887    fn visual_left_index(&mut self, paragraph: usize) -> usize {
1888        if self.is_rtl(paragraph) {
1889            self.table.paragraphs[paragraph].len
1890        } else {
1891            0
1892        }
1893    }
1894
1895    /// The selection's ends, in visual order: the first that comes first on
1896    /// the screen, the second that comes last.
1897    fn visual_ends(&mut self) -> (Anchor, Anchor) {
1898        let anchor = self.selection.anchor;
1899        let focus = self.selection.focus;
1900        if anchor.paragraph == focus.paragraph {
1901            let (first, last) = self.with_layout(anchor.paragraph, |layout| {
1902                let anchor_box =
1903                    Cursor::from_byte_index(&layout.layout, anchor.index, anchor.affinity)
1904                        .geometry(&layout.layout, 0.0);
1905                let focus_box =
1906                    Cursor::from_byte_index(&layout.layout, focus.index, focus.affinity)
1907                        .geometry(&layout.layout, 0.0);
1908                if (anchor_box.y0, anchor_box.x0) > (focus_box.y0, focus_box.x0) {
1909                    (focus, anchor)
1910                } else {
1911                    (anchor, focus)
1912                }
1913            });
1914            (first, last)
1915        } else if anchor.paragraph < focus.paragraph {
1916            (anchor, focus)
1917        } else {
1918            (focus, anchor)
1919        }
1920    }
1921
1922    /// Moves the focus one word left or right, in visual order.
1923    fn move_word(&mut self, forward: bool, extend: bool) {
1924        let focus = self.selection.focus;
1925        // The word step crosses the line break that ends or begins the
1926        // focus's paragraph, which no layout holds.
1927        let focus = if forward && self.at_visual_right_edge(focus) {
1928            if focus.paragraph + 1 >= self.table.paragraphs.len() {
1929                return;
1930            }
1931            let next = focus.paragraph + 1;
1932            Anchor {
1933                paragraph: next,
1934                index: self.visual_left_index(next),
1935                affinity: Affinity::Downstream,
1936            }
1937        } else if !forward && self.at_visual_left_edge(focus) {
1938            if focus.paragraph == 0 {
1939                return;
1940            }
1941            let previous = focus.paragraph - 1;
1942            Anchor {
1943                paragraph: previous,
1944                index: self.visual_right_index(previous),
1945                affinity: Affinity::Downstream,
1946            }
1947        } else {
1948            focus
1949        };
1950        let steps = self.table.paragraphs[focus.paragraph].len + 1;
1951        let moved = self.with_layout(focus.paragraph, |layout| {
1952            let cursor = Cursor::from_byte_index(&layout.layout, focus.index, focus.affinity);
1953            let moved = visual_word(cursor, &layout.layout, forward, steps);
1954            Anchor {
1955                paragraph: focus.paragraph,
1956                index: moved.index(),
1957                affinity: moved.affinity(),
1958            }
1959        });
1960        self.set_focus(moved, extend);
1961    }
1962
1963    /// The byte offset a backspace word deletes down to before the focus,
1964    /// or `None` when the focus opens the text.
1965    ///
1966    /// The offset is global, so the caller can delete it against the buffer
1967    /// directly.
1968    fn word_before(&mut self, focus: Anchor) -> Option<usize> {
1969        let start = self.table.paragraphs[focus.paragraph].start;
1970        if focus.index == 0 {
1971            if focus.paragraph == 0 {
1972                return None;
1973            }
1974            let previous = focus.paragraph - 1;
1975            let len = self.table.paragraphs[previous].len;
1976            let previous_start = self.table.paragraphs[previous].start;
1977            return self.with_layout(previous, |layout| {
1978                let cursor = Cursor::from_byte_index(&layout.layout, len, Affinity::Downstream);
1979                Some(previous_start + cursor.previous_logical_word(&layout.layout).index())
1980            });
1981        }
1982        self.with_layout(focus.paragraph, |layout| {
1983            let cursor = Cursor::from_byte_index(&layout.layout, focus.index, focus.affinity);
1984            Some(start + cursor.previous_logical_word(&layout.layout).index())
1985        })
1986    }
1987
1988    /// The byte offset a delete word deletes up to after the focus, or
1989    /// `None` when the focus closes the text.
1990    ///
1991    /// The offset is global, so the caller can delete it against the buffer
1992    /// directly.
1993    fn word_after(&mut self, focus: Anchor) -> Option<usize> {
1994        let start = self.table.paragraphs[focus.paragraph].start;
1995        if focus.index >= self.table.paragraphs[focus.paragraph].len {
1996            if focus.paragraph + 1 >= self.table.paragraphs.len() {
1997                return None;
1998            }
1999            let next = focus.paragraph + 1;
2000            let next_start = self.table.paragraphs[next].start;
2001            return self.with_layout(next, |layout| {
2002                let cursor = Cursor::from_byte_index(&layout.layout, 0, Affinity::Downstream);
2003                Some(next_start + cursor.next_logical_word(&layout.layout).index())
2004            });
2005        }
2006        self.with_layout(focus.paragraph, |layout| {
2007            let cursor = Cursor::from_byte_index(&layout.layout, focus.index, focus.affinity);
2008            Some(start + cursor.next_logical_word(&layout.layout).index())
2009        })
2010    }
2011
2012    /// Moves the focus `delta` visual lines down, or up when negative,
2013    /// walking across paragraphs and keeping the horizontal position.
2014    fn move_lines(&mut self, delta: isize, extend: bool) {
2015        if delta == 0 {
2016            return;
2017        }
2018        let focus = self.selection.focus;
2019        let fallback_x = self.with_layout(focus.paragraph, |layout| {
2020            Cursor::from_byte_index(&layout.layout, focus.index, focus.affinity)
2021                .geometry(&layout.layout, 0.0)
2022                .x0 as f32
2023        });
2024        let h_pos = self.selection.h_pos.unwrap_or(fallback_x);
2025        let mut paragraph = focus.paragraph;
2026        let mut line = self.with_layout(focus.paragraph, |layout| {
2027            let cursor = Cursor::from_byte_index(&layout.layout, focus.index, focus.affinity);
2028            let area = cursor.geometry(&layout.layout, 0.0);
2029            let middle = ((area.y0 + area.y1) / 2.0) as f32;
2030            line_at_y(&layout.layout, middle)
2031        });
2032        let mut remaining = delta;
2033        let mut clamped = false;
2034        while remaining != 0 {
2035            if remaining < 0 {
2036                if line > 0 {
2037                    line -= 1;
2038                    remaining += 1;
2039                } else if paragraph > 0 {
2040                    paragraph -= 1;
2041                    line =
2042                        self.with_layout(paragraph, |layout| layout.layout.len().saturating_sub(1));
2043                    remaining += 1;
2044                } else {
2045                    clamped = true;
2046                    break;
2047                }
2048            } else {
2049                let count = self.with_layout(paragraph, |layout| layout.layout.len());
2050                if line + 1 < count {
2051                    line += 1;
2052                    remaining -= 1;
2053                } else if paragraph + 1 < self.table.paragraphs.len() {
2054                    paragraph += 1;
2055                    line = 0;
2056                    remaining -= 1;
2057                } else {
2058                    clamped = true;
2059                    break;
2060                }
2061            }
2062        }
2063        if clamped {
2064            // A move past the ends of the text lands at the start or end of
2065            // the line it stopped on, and the sticky horizontal position
2066            // goes with it.
2067            let target = if remaining < 0 {
2068                Anchor {
2069                    paragraph: 0,
2070                    index: 0,
2071                    affinity: Affinity::Downstream,
2072                }
2073            } else {
2074                self.document_end()
2075            };
2076            self.set_focus(target, extend);
2077            return;
2078        }
2079        let moved = self.with_layout(paragraph, |layout| {
2080            let line = layout
2081                .layout
2082                .get(line)
2083                .expect("the walk stopped within the paragraph");
2084            let metrics = line.metrics();
2085            let middle = (metrics.block_min_coord + metrics.block_max_coord) / 2.0;
2086            let moved = Cursor::from_point(&layout.layout, h_pos, middle);
2087            Anchor {
2088                paragraph,
2089                index: moved.index(),
2090                affinity: moved.affinity(),
2091            }
2092        });
2093        if extend {
2094            self.selection.focus = moved;
2095            self.selection.base = Base::Cluster;
2096        } else {
2097            self.selection.anchor = moved;
2098            self.selection.focus = moved;
2099            self.selection.base = Base::Cluster;
2100        }
2101        self.selection.h_pos = Some(h_pos);
2102    }
2103
2104    /// Moves the focus to the start or end of the text.
2105    fn move_to_edge(&mut self, end: bool, extend: bool) {
2106        let target = if end { self.document_end() } else { START };
2107        self.set_focus(target, extend);
2108    }
2109
2110    /// Moves the focus to the start or end of the visual line it sits on.
2111    fn move_to_line_edge(&mut self, end: bool, extend: bool) {
2112        let focus = self.selection.focus;
2113        let moved = self.with_layout(focus.paragraph, |layout| {
2114            let layout = &layout.layout;
2115            let here = ParagraphSelection::from(Cursor::from_byte_index(
2116                layout,
2117                focus.index,
2118                focus.affinity,
2119            ));
2120            let moved = if end {
2121                here.line_end(layout, false)
2122            } else {
2123                here.line_start(layout, false)
2124            }
2125            .focus();
2126            Anchor {
2127                paragraph: focus.paragraph,
2128                index: moved.index(),
2129                affinity: moved.affinity(),
2130            }
2131        });
2132        self.set_focus(moved, extend);
2133    }
2134
2135    /// The anchor the point names.
2136    fn point_anchor(&mut self, x: f32, y: f32) -> Anchor {
2137        let (paragraph, offset) = self.paragraph_at_y(y);
2138        self.with_layout(paragraph, |layout| {
2139            let moved = Cursor::from_point(&layout.layout, x, y - offset);
2140            Anchor {
2141                paragraph,
2142                index: moved.index(),
2143                affinity: moved.affinity(),
2144            }
2145        })
2146    }
2147
2148    /// Moves the selection focus to the point, keeping the anchor, and
2149    /// extends at the granularity the selection was made at.
2150    fn extend_to_point(&mut self, x: f32, y: f32) {
2151        match self.selection.base {
2152            Base::Cluster => {
2153                let target = self.point_anchor(x, y);
2154                self.selection.focus = target;
2155                self.selection.h_pos = None;
2156            }
2157            Base::Word(base) => {
2158                // Extend over the word the click landed in, taking in every
2159                // byte between the word's far end and the double click's
2160                // anchor, whichever side the click came from. The focus
2161                // goes to the end the pointer is at, so the caret and the
2162                // scroll follow the drag.
2163                let (target_anchor, target_focus) = self.word_at_point(x, y);
2164                let leftward = self.table.global(target_anchor) < self.table.global(base[0]);
2165                let mut ends = [target_anchor, target_focus, base[0], base[1]];
2166                ends.sort_by_key(|anchor| self.table.global(*anchor));
2167                let (anchor, focus) = if leftward {
2168                    (ends[3], ends[0])
2169                } else {
2170                    (ends[0], ends[3])
2171                };
2172                self.selection.anchor = anchor;
2173                self.selection.focus = focus;
2174                self.selection.h_pos = None;
2175            }
2176        }
2177    }
2178
2179    /// Selects the word under the point.
2180    fn select_word_at_point(&mut self, x: f32, y: f32) {
2181        let (anchor, focus) = self.word_at_point(x, y);
2182        self.selection = Selection {
2183            anchor,
2184            focus,
2185            base: Base::Word([anchor, focus]),
2186            h_pos: None,
2187        };
2188    }
2189
2190    /// The two ends of the word under the point, collapsed to the point's
2191    /// paragraph end when the paragraph holds no words.
2192    fn word_at_point(&mut self, x: f32, y: f32) -> (Anchor, Anchor) {
2193        let (paragraph, offset) = self.paragraph_at_y(y);
2194        let word = self.with_layout(paragraph, |layout| {
2195            let layout = &layout.layout;
2196            let (mut cluster, _) = Cluster::from_point(layout, x, y - offset)?;
2197            if !cluster.is_word_boundary()
2198                && let Some(previous) = cluster.previous_logical_word()
2199            {
2200                cluster = previous;
2201            }
2202            let anchor = Cursor::from_byte_index(
2203                layout,
2204                cluster.text_range().start,
2205                affinity_for_dir(cluster.is_rtl(), true),
2206            );
2207            let focus = match cluster.next_logical_word() {
2208                Some(end) => Cursor::from_byte_index(
2209                    layout,
2210                    end.text_range().start,
2211                    affinity_for_dir(end.is_rtl(), true),
2212                ),
2213                None => Cursor::from_byte_index(layout, usize::MAX, Affinity::Downstream),
2214            };
2215            Some((
2216                Anchor {
2217                    paragraph,
2218                    index: anchor.index(),
2219                    affinity: anchor.affinity(),
2220                },
2221                Anchor {
2222                    paragraph,
2223                    index: focus.index(),
2224                    affinity: focus.affinity(),
2225                },
2226            ))
2227        });
2228        word.unwrap_or_else(|| {
2229            let end = Anchor {
2230                paragraph,
2231                index: self.table.paragraphs[paragraph].len,
2232                affinity: Affinity::Upstream,
2233            };
2234            (end, end)
2235        })
2236    }
2237
2238    /// The byte range one backspace deletes before a collapsed focus, or
2239    /// `None` when there is nothing before it.
2240    fn backspace_range(&mut self, focus: Anchor) -> Option<Range<usize>> {
2241        if focus.paragraph == 0 && focus.index == 0 {
2242            return None;
2243        }
2244        let start = self.table.paragraphs[focus.paragraph].start;
2245        if focus.index == 0 {
2246            // The cluster before the caret is the line break that ends the
2247            // previous paragraph, which no layout holds.
2248            return Some(start - 1..start);
2249        }
2250        let paragraph_start = start;
2251        let mut range = None;
2252        let mut whole = false;
2253        self.with_layout(focus.paragraph, |layout| {
2254            let cursor = Cursor::from_byte_index(&layout.layout, focus.index, focus.affinity);
2255            if let Some(cluster) = cursor.upstream_cluster(&layout.layout) {
2256                range = Some(cluster.text_range());
2257                whole = cluster.is_hard_line_break() || cluster.is_emoji();
2258            }
2259        });
2260        let range = range?;
2261        let end = paragraph_start + range.end;
2262        let start = if whole {
2263            paragraph_start + range.start
2264        } else {
2265            // A combining mark comes off before the whole cluster does.
2266            let text = &self.buffer[paragraph_start..end];
2267            let (start, _) = text.char_indices().next_back()?;
2268            paragraph_start + start
2269        };
2270        Some(start..end)
2271    }
2272
2273    /// The byte range one delete removes after a collapsed focus, or `None`
2274    /// when there is nothing after it.
2275    fn forward_delete_range(&mut self, focus: Anchor) -> Option<Range<usize>> {
2276        let paragraph = &self.table.paragraphs[focus.paragraph];
2277        let start = paragraph.start;
2278        let len = paragraph.len;
2279        if focus.index >= len {
2280            if focus.paragraph + 1 >= self.table.paragraphs.len() {
2281                return None;
2282            }
2283            // The next cluster is the line break that ends the paragraph,
2284            // which no layout holds.
2285            return Some(start + len..start + len + 1);
2286        }
2287        let mut range = None;
2288        self.with_layout(focus.paragraph, |layout| {
2289            let cursor = Cursor::from_byte_index(&layout.layout, focus.index, focus.affinity);
2290            if let Some(cluster) = cursor.downstream_cluster(&layout.layout) {
2291                range = Some(cluster.text_range());
2292            }
2293        });
2294        range.map(|local| start + local.start..start + local.end)
2295    }
2296}
2297
2298/// Returns `text` with every carriage return and line feed pair, and every
2299/// lone carriage return, turned into one line feed.
2300///
2301/// The buffer is cut into paragraphs at line feeds alone, and a carriage
2302/// return left at a paragraph's end would lay out as a second, empty line.
2303/// Text without a carriage return is borrowed as it is.
2304fn normalize_breaks(text: &str) -> Cow<'_, str> {
2305    if !text.contains('\r') {
2306        return Cow::Borrowed(text);
2307    }
2308    Cow::Owned(text.replace("\r\n", "\n").replace('\r', "\n"))
2309}
2310
2311/// Copies `text` for an undo or redo step.
2312fn copy_for_undo(text: &str) -> String {
2313    #[cfg(test)]
2314    SNAPSHOTS.with(|snapshots| snapshots.set(snapshots.get() + 1));
2315    text.to_owned()
2316}
2317
2318/// Returns the byte range of `current` that differs from `target`: the
2319/// bytes between the longest start and the longest end the two share,
2320/// without overlapping, on character boundaries of both.
2321///
2322/// Replacing that range of `current` with the matching middle of `target`
2323/// gives `target`.
2324fn changed_range(current: &str, target: &str) -> Range<usize> {
2325    let (a, b) = (current.as_bytes(), target.as_bytes());
2326    let mut start = a.iter().zip(b).take_while(|(x, y)| x == y).count();
2327    while !current.is_char_boundary(start) {
2328        start -= 1;
2329    }
2330    let limit = a.len().min(b.len()) - start;
2331    let mut end = a
2332        .iter()
2333        .rev()
2334        .zip(b.iter().rev())
2335        .take(limit)
2336        .take_while(|(x, y)| x == y)
2337        .count();
2338    while !current.is_char_boundary(a.len() - end) {
2339        end -= 1;
2340    }
2341    start..a.len() - end
2342}
2343
2344/// Returns `byte` clamped to `text` and snapped down to a character
2345/// boundary, which an offset from a broken input method may need.
2346fn char_boundary(text: &str, byte: usize) -> usize {
2347    let mut byte = byte.min(text.len());
2348    while !text.is_char_boundary(byte) {
2349        byte -= 1;
2350    }
2351    byte
2352}
2353
2354/// Converts one of parley's boxes into a rectangle in logical pixels.
2355fn rect(area: parley::BoundingBox) -> Rect {
2356    Rect::new(
2357        area.x0 as f32,
2358        area.y0 as f32,
2359        area.x1 as f32,
2360        area.y1 as f32,
2361    )
2362}
2363
2364/// The affinity a cursor takes when it arrives at a cluster from the given
2365/// side, which bidirectional text flips.
2366fn affinity_for_dir(is_rtl: bool, moving_right: bool) -> Affinity {
2367    match (is_rtl, moving_right) {
2368        (true, true) | (false, false) => Affinity::Downstream,
2369        _ => Affinity::Upstream,
2370    }
2371}
2372
2373/// The index of the layout line whose vertical range holds `y`, in the
2374/// layout's own coordinates, or the last line when the point falls past
2375/// every line.
2376fn line_at_y(layout: &parley::Layout<()>, y: f32) -> usize {
2377    for (index, line) in layout.lines().enumerate() {
2378        if y < line.metrics().block_max_coord {
2379            return index;
2380        }
2381    }
2382    layout.len().saturating_sub(1)
2383}
2384
2385/// Returns the cursor one word away from `cursor` in visual order, the
2386/// way parley's `next_visual_word` and `previous_visual_word` find it, but
2387/// after at most `steps` cluster steps.
2388///
2389/// Parley's visual step can cycle between two positions in mixed-direction
2390/// text that wraps, and its word walk loops until a step stops moving, so
2391/// it never returns there. A paragraph of `len` bytes holds at most `len`
2392/// clusters, so `len + 1` steps reach any boundary a walk without a cycle
2393/// reaches.
2394fn visual_word<B: parley::Brush>(
2395    cursor: Cursor,
2396    layout: &parley::Layout<B>,
2397    forward: bool,
2398    steps: usize,
2399) -> Cursor {
2400    let mut current = cursor;
2401    for _ in 0..steps {
2402        let next = if forward {
2403            current.next_visual(layout)
2404        } else {
2405            current.previous_visual(layout)
2406        };
2407        if next == current {
2408            break;
2409        }
2410        current = next;
2411        let [Some(left), Some(right)] = current.visual_clusters(layout) else {
2412            break;
2413        };
2414        let boundary = match (forward, left.is_rtl()) {
2415            (true, true) => left.is_word_boundary() && !left.is_space_or_nbsp(),
2416            (true, false) => right.is_word_boundary() && !left.is_space_or_nbsp(),
2417            (false, true) => {
2418                left.is_word_boundary()
2419                    && (left.is_space_or_nbsp()
2420                        || (right.is_word_boundary() && !right.is_space_or_nbsp()))
2421            }
2422            (false, false) => right.is_word_boundary() && !right.is_space_or_nbsp(),
2423        };
2424        if boundary {
2425            break;
2426        }
2427    }
2428    current
2429}
2430
2431#[cfg(test)]
2432thread_local! {
2433    /// How many times this thread copied an editor's text for undo, which
2434    /// tests read to tell an edit that copied from one that did not.
2435    pub(crate) static SNAPSHOTS: Cell<usize> = const { Cell::new(0) };
2436}
2437
2438#[cfg(test)]
2439#[path = "input_tests.rs"]
2440mod tests;