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;