druid_shell/
text.rs

1// Copyright 2020 The Druid Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Types and functions for cross-platform text input.
16//!
17//! Text input is a notoriously difficult problem.  Unlike many other aspects of
18//! user interfaces, text input can not be correctly modeled using discrete
19//! events passed from the platform to the application. For example, many mobile
20//! phones implement autocorrect: when the user presses the spacebar, the
21//! platform peeks at the word directly behind the caret, and potentially
22//! replaces it if it's misspelled. This means the platform needs to know the
23//! contents of a text field. On other devices, the platform may need to draw an
24//! emoji window under the caret, or look up the on-screen locations of letters
25//! for crossing out with a stylus, both of which require fetching on-screen
26//! coordinates from the application.
27//!
28//! This is all to say: text editing is a bidirectional conversation between the
29//! application and the platform. The application, when the platform asks for
30//! it, serves up text field coordinates and content.  The platform looks at
31//! this information and combines it with input from keyboards (physical or
32//! onscreen), voice, styluses, the user's language settings, and then sends
33//! edit commands to the application.
34//!
35//! Many platforms have an additional complication: this input fusion often
36//! happens in a different process from your application. If we don't
37//! specifically account for this fact, we might get race conditions!  In the
38//! autocorrect example, if I sloppily type "meoow" and press space, the
39//! platform might issue edits to "delete backwards one word and insert meow".
40//! However, if I concurrently click somewhere else in the document to move the
41//! caret, this will replace some *other* word with "meow", and leave the
42//! "meoow" disappointingly present. To mitigate this problem, we use locks,
43//! represented by the `InputHandler` trait.
44//!
45//! ## Lifecycle of a Text Input
46//!
47//! 1. The user clicks a link or switches tabs, and the window content now
48//!    contains a new text field.  The application registers this new field by
49//!    calling `WindowHandle::add_text_field`, and gets a `TextFieldToken` that
50//!    represents this new field.
51//! 2. The user clicks on that text field, focusing it. The application lets the
52//!    platform know by calling `WindowHandle::set_focused_text_field` with that
53//!    field's `TextFieldToken`.
54//! 3. The user presses a key on the keyboard. The platform first calls
55//!    `WinHandler::key_down`. If this method returns `true`, the application
56//!    has indicated the keypress was captured, and we skip the remaining steps.
57//! 4. If `key_down` returned `false`, `druid-shell` forwards the key event to the
58//!    platform's text input system
59//! 5. The platform, in response to either this key event or some other user
60//!    action, determines it's time for some text input. It calls
61//!    `WinHandler::text_input` to acquire a lock on the text field's state.
62//!    The application returns an `InputHandler` object corresponding to the
63//!    requested text field. To prevent race conditions, your application may
64//!    not make any changes
65//!    to the text field's state until the platform drops the `InputHandler`.
66//! 6. The platform calls various `InputHandler` methods to inspect and edit the
67//!    text field's state. Later, usually within a few milliseconds, the
68//!    platform drops the `InputHandler`, allowing the application to once again
69//!    make changes to the text field's state. These commands might be "insert
70//!    `q`" for a smartphone user tapping on their virtual keyboard, or
71//!    "move the caret one word left" for a user pressing the left arrow key
72//!    while holding control.
73//! 7. Eventually, after many keypresses cause steps 3–6 to repeat, the user
74//!    unfocuses the text field. The application indicates this to the platform
75//!    by calling `set_focused_text_field`.  Note that even though focus has
76//!    shifted away from our text field, the platform may still send edits to it
77//!    by calling `WinHandler::text_input`.
78//! 8. At some point, the user clicks a link or switches a tab, and the text
79//!    field is no longer present in the window.  The application calls
80//!    `WindowHandle::remove_text_field`, and the platform may no longer call
81//!    `WinHandler::text_input` to make changes to it.
82//!
83//! The application also has a series of steps it follows if it wants to make
84//! its own changes to the text field's state:
85//!
86//! 1. The application determines it would like to make a change to the text
87//!    field; perhaps the user has scrolled and and the text field has changed
88//!    its visible location on screen, or perhaps the user has clicked to move
89//!    the caret to a new location.
90//! 2. The application first checks to see if there's an outstanding
91//!    `InputHandler` lock for this text field; if so, it waits until the last
92//!    `InputHandler` is dropped before continuing.
93//! 3. The application then makes the change to the text input. If the change
94//!    would affect state visible from an `InputHandler`, the application must
95//!    notify the platform via `WinHandler::update_text_field`.
96//!
97//! ## Supported Platforms
98//!
99//! Currently, `druid-shell` text input is fully implemented on macOS. Our goal
100//! is to have full support for all `druid-shell` targets, but for now,
101//! `InputHandler` calls are simulated from keypresses on other platforms, which
102//! doesn't allow for IME input, dead keys, etc.
103
104use crate::keyboard::{KbKey, KeyEvent};
105use crate::kurbo::{Point, Rect};
106use crate::piet::HitTestPoint;
107use crate::window::{TextFieldToken, WinHandler};
108use std::borrow::Cow;
109use std::ops::Range;
110
111/// An event representing an application-initiated change in [`InputHandler`]
112/// state.
113///
114/// When we change state that may have previously been retrieved from an
115/// [`InputHandler`], we notify the platform so that it can invalidate any
116/// data if necessary.
117#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
118#[non_exhaustive]
119pub enum Event {
120    /// Indicates the value returned by `InputHandler::selection` may have changed.
121    SelectionChanged,
122
123    /// Indicates the values returned by one or more of these methods may have changed:
124    /// - `InputHandler::hit_test_point`
125    /// - `InputHandler::line_range`
126    /// - `InputHandler::bounding_box`
127    /// - `InputHandler::slice_bounding_box`
128    LayoutChanged,
129
130    /// Indicates any value returned from any `InputHandler` method may have changed.
131    Reset,
132}
133
134/// A range of selected text, or a caret.
135///
136/// A caret is the blinking vertical bar where text is to be inserted. We
137/// represent it as a selection with zero length, where `anchor == active`.
138/// Indices are always expressed in UTF-8 bytes, and must be between 0 and the
139/// document length, inclusive.
140///
141/// As an example, if the input caret is at the start of the document `hello
142/// world`, we would expect both `anchor` and `active` to be `0`. If the user
143/// holds shift and presses the right arrow key five times, we would expect the
144/// word `hello` to be selected, the `anchor` to still be `0`, and the `active`
145/// to now be `5`.
146#[derive(Clone, Copy, Debug, Default, PartialEq)]
147#[non_exhaustive]
148pub struct Selection {
149    /// The 'anchor' end of the selection.
150    ///
151    /// This is the end of the selection that stays unchanged while holding
152    /// shift and pressing the arrow keys.
153    pub anchor: usize,
154    /// The 'active' end of the selection.
155    ///
156    /// This is the end of the selection that moves while holding shift and
157    /// pressing the arrow keys.
158    pub active: usize,
159    /// The saved horizontal position, during vertical movement.
160    ///
161    /// This should not be set by the IME; it will be tracked and handled by
162    /// the text field.
163    pub h_pos: Option<f64>,
164}
165
166#[allow(clippy::len_without_is_empty)]
167impl Selection {
168    /// Create a new `Selection` with the provided `anchor` and `active` positions.
169    ///
170    /// Both positions refer to UTF-8 byte indices in some text.
171    ///
172    /// If your selection is a caret, you can use [`Selection::caret`] instead.
173    pub fn new(anchor: usize, active: usize) -> Selection {
174        Selection {
175            anchor,
176            active,
177            h_pos: None,
178        }
179    }
180
181    /// Create a new caret (zero-length selection) at the provided UTF-8 byte index.
182    ///
183    /// `index` must be a grapheme cluster boundary.
184    pub fn caret(index: usize) -> Selection {
185        Selection {
186            anchor: index,
187            active: index,
188            h_pos: None,
189        }
190    }
191
192    /// Construct a new selection from this selection, with the provided h_pos.
193    ///
194    /// # Note
195    ///
196    /// `h_pos` is used to track the *pixel* location of the cursor when moving
197    /// vertically; lines may have available cursor positions at different
198    /// positions, and arrowing down and then back up should always result
199    /// in a cursor at the original starting location; doing this correctly
200    /// requires tracking this state.
201    ///
202    /// You *probably* don't need to use this, unless you are implementing a new
203    /// text field, or otherwise implementing vertical cursor motion, in which
204    /// case you will want to set this during vertical motion if it is not
205    /// already set.
206    pub fn with_h_pos(mut self, h_pos: Option<f64>) -> Self {
207        self.h_pos = h_pos;
208        self
209    }
210
211    /// Create a new selection that is guaranteed to be valid for the provided
212    /// text.
213    #[must_use = "constrained constructs a new Selection"]
214    pub fn constrained(mut self, s: &str) -> Self {
215        let s_len = s.len();
216        self.anchor = self.anchor.min(s_len);
217        self.active = self.active.min(s_len);
218        while !s.is_char_boundary(self.anchor) {
219            self.anchor += 1;
220        }
221        while !s.is_char_boundary(self.active) {
222            self.active += 1;
223        }
224        self
225    }
226
227    /// Return the position of the upstream end of the selection.
228    ///
229    /// This is end with the lesser byte index.
230    ///
231    /// Because of bidirectional text, this is not necessarily "left".
232    pub fn min(&self) -> usize {
233        usize::min(self.anchor, self.active)
234    }
235
236    /// Return the position of the downstream end of the selection.
237    ///
238    /// This is the end with the greater byte index.
239    ///
240    /// Because of bidirectional text, this is not necessarily "right".
241    pub fn max(&self) -> usize {
242        usize::max(self.anchor, self.active)
243    }
244
245    /// The sequential range of the document represented by this selection.
246    ///
247    /// This is the range that would be replaced if text were inserted at this
248    /// selection.
249    pub fn range(&self) -> Range<usize> {
250        self.min()..self.max()
251    }
252
253    /// The length, in bytes of the selected region.
254    ///
255    /// If the selection is a caret, this is `0`.
256    pub fn len(&self) -> usize {
257        if self.anchor > self.active {
258            self.anchor - self.active
259        } else {
260            self.active - self.anchor
261        }
262    }
263
264    /// Returns `true` if the selection's length is `0`.
265    pub fn is_caret(&self) -> bool {
266        self.len() == 0
267    }
268}
269
270/// A lock on a text field that allows the platform to retrieve state and make
271/// edits.
272///
273/// This trait is implemented by the application or UI framework.  The platform
274/// acquires this lock temporarily to apply edits corresponding to some user
275/// input.  So long as the `InputHandler` has not been dropped, the only changes
276/// to the document state must come from calls to `InputHandler`.
277///
278/// Some methods require a mutable lock, as indicated when acquiring the lock
279/// with `WinHandler::text_input`.  If a mutable method is called on a immutable
280/// lock, `InputHandler` may panic.
281///
282/// All ranges, lengths, and indices are specified in UTF-8 code units, unless
283/// specified otherwise.
284pub trait InputHandler {
285    /// The document's current [`Selection`].
286    ///
287    /// If the selection is a vertical caret bar, then `range.start == range.end`.
288    /// Both `selection.anchor` and `selection.active` must be less than or
289    /// equal to the value returned from `InputHandler::len()`, and must land on
290    /// a extended grapheme cluster boundary in the document.
291    fn selection(&self) -> Selection;
292
293    /// Set the document's selection.
294    ///
295    /// If the selection is a vertical caret bar, then `range.start == range.end`.
296    /// Both `selection.anchor` and `selection.active` must be less
297    /// than or equal to the value returned from `InputHandler::len()`.
298    ///
299    /// Properties of the `Selection` *other* than `anchor` and `active` may
300    /// be ignored by the handler.
301    ///
302    /// The `set_selection` implementation should round up (downstream) both
303    /// `selection.anchor` and `selection.active` to the nearest extended
304    /// grapheme cluster boundary.
305    ///
306    /// Requires a mutable lock.
307    fn set_selection(&mut self, selection: Selection);
308
309    /// The current composition region.
310    ///
311    /// This should be `Some` only if the IME is currently active, in which
312    /// case it represents the range of text that may be modified by the IME.
313    ///
314    /// Both `range.start` and `range.end` must be less than or equal
315    /// to the value returned from `InputHandler::len()`, and must land on a
316    /// extended grapheme cluster boundary in the document.
317    fn composition_range(&self) -> Option<Range<usize>>;
318
319    /// Set the composition region.
320    ///
321    /// If this is `Some` it means that the IME is currently active for this
322    /// region of the document. If it is `None` it means that the IME is not
323    /// currently active.
324    ///
325    /// Both `range.start` and `range.end` must be less than or equal to the
326    /// value returned from `InputHandler::len()`.
327    ///
328    /// The `set_selection` implementation should round up (downstream) both
329    /// `range.start` and `range.end` to the nearest extended grapheme cluster
330    /// boundary.
331    ///
332    /// Requires a mutable lock.
333    fn set_composition_range(&mut self, range: Option<Range<usize>>);
334
335    /// Check if the provided index is the first byte of a UTF-8 code point
336    /// sequence, or is the end of the document.
337    ///
338    /// Equivalent in functionality to [`str::is_char_boundary`].
339    fn is_char_boundary(&self, i: usize) -> bool;
340
341    /// The length of the document in UTF-8 code units.
342    fn len(&self) -> usize;
343
344    /// Returns `true` if the length of the document is `0`.
345    fn is_empty(&self) -> bool {
346        self.len() == 0
347    }
348
349    /// Returns the subslice of the document represented by `range`.
350    ///
351    /// # Panics
352    ///
353    /// Panics if the start or end of the range do not fall on a code point
354    /// boundary.
355    fn slice(&self, range: Range<usize>) -> Cow<str>;
356
357    /// Returns the number of UTF-16 code units in the provided UTF-8 range.
358    ///
359    /// Converts the document into UTF-8, looks up the range specified by
360    /// `utf8_range` (in UTF-8 code units), reencodes that substring into
361    /// UTF-16, and then returns the number of UTF-16 code units in that
362    /// substring.
363    ///
364    /// This is automatically implemented, but you can override this if you have
365    /// some faster system to determine string length.
366    ///
367    /// # Panics
368    ///
369    /// Panics if the start or end of the range do not fall on a code point
370    /// boundary.
371    fn utf8_to_utf16(&self, utf8_range: Range<usize>) -> usize {
372        self.slice(utf8_range).encode_utf16().count()
373    }
374
375    /// Returns the number of UTF-8 code units in the provided UTF-16 range.
376    ///
377    /// Converts the document into UTF-16, looks up the range specified by
378    /// `utf16_range` (in UTF-16 code units), reencodes that substring into
379    /// UTF-8, and then returns the number of UTF-8 code units in that
380    /// substring.
381    ///
382    /// This is automatically implemented, but you can override this if you have
383    /// some faster system to determine string length.
384    fn utf16_to_utf8(&self, utf16_range: Range<usize>) -> usize {
385        if utf16_range.is_empty() {
386            return 0;
387        }
388        let doc_range = 0..self.len();
389        let text = self.slice(doc_range);
390        //FIXME: we can do this without allocating; there's an impl in piet
391        let utf16: Vec<u16> = text
392            .encode_utf16()
393            .skip(utf16_range.start)
394            .take(utf16_range.end)
395            .collect();
396        String::from_utf16_lossy(&utf16).len()
397    }
398
399    /// Replaces a range of the text document with `text`.
400    ///
401    /// This method also sets the composition range to `None`, and updates the
402    /// selection:
403    ///
404    /// - If both the selection's anchor and active are `< range.start`, then
405    /// nothing is updated.  - If both the selection's anchor and active are `>
406    /// range.end`, then subtract `range.len()` from both, and add `text.len()`.
407    /// - If neither of the previous two conditions are true, then set both
408    /// anchor and active to `range.start + text.len()`.
409    ///
410    /// After the above update, if we increase each end of the selection if
411    /// necessary to put it on a grapheme cluster boundary.
412    ///
413    /// Requires a mutable lock.
414    ///
415    /// # Panics
416    ///
417    /// Panics if either end of the range does not fall on a code point
418    /// boundary.
419    fn replace_range(&mut self, range: Range<usize>, text: &str);
420
421    /// Given a `Point`, determine the corresponding text position.
422    fn hit_test_point(&self, point: Point) -> HitTestPoint;
423
424    /// Returns the range, in UTF-8 code units, of the line (soft- or hard-wrapped)
425    /// containing the byte specified by `index`.
426    fn line_range(&self, index: usize, affinity: Affinity) -> Range<usize>;
427
428    /// Returns the bounding box, in window coordinates, of the visible text
429    /// document.
430    ///
431    /// For instance, a text box's bounding box would be the rectangle
432    /// of the border surrounding it, even if the text box is empty.  If the
433    /// text document is completely offscreen, return `None`.
434    fn bounding_box(&self) -> Option<Rect>;
435
436    /// Returns the bounding box, in window coordinates, of the range of text specified by `range`.
437    ///
438    /// Ranges will always be equal to or a subrange of some line range returned
439    /// by `InputHandler::line_range`.  If a range spans multiple lines,
440    /// `slice_bounding_box` may panic.
441    fn slice_bounding_box(&self, range: Range<usize>) -> Option<Rect>;
442
443    /// Applies an [`Action`] to the text field.
444    ///
445    /// Requires a mutable lock.
446    fn handle_action(&mut self, action: Action);
447}
448
449#[allow(dead_code)]
450/// Simulates `InputHandler` calls on `handler` for a given keypress `event`.
451///
452/// This circumvents the platform, and so can't work with important features
453/// like input method editors! However, it's necessary while we build up our
454/// input support on various platforms, which takes a lot of time. We want
455/// applications to start building on the new `InputHandler` interface
456/// immediately, with a hopefully seamless upgrade process as we implement IME
457/// input on more platforms.
458pub fn simulate_input<H: WinHandler + ?Sized>(
459    handler: &mut H,
460    token: Option<TextFieldToken>,
461    event: KeyEvent,
462) -> bool {
463    if handler.key_down(event.clone()) {
464        return true;
465    }
466
467    let token = match token {
468        Some(v) => v,
469        None => return false,
470    };
471    let mut input_handler = handler.acquire_input_lock(token, true);
472    match event.key {
473        KbKey::Character(c) if !event.mods.ctrl() && !event.mods.meta() && !event.mods.alt() => {
474            let selection = input_handler.selection();
475            input_handler.replace_range(selection.range(), &c);
476            let new_caret_index = selection.min() + c.len();
477            input_handler.set_selection(Selection::caret(new_caret_index));
478        }
479        KbKey::ArrowLeft => {
480            let movement = if event.mods.ctrl() {
481                Movement::Word(Direction::Left)
482            } else {
483                Movement::Grapheme(Direction::Left)
484            };
485            if event.mods.shift() {
486                input_handler.handle_action(Action::MoveSelecting(movement));
487            } else {
488                input_handler.handle_action(Action::Move(movement));
489            }
490        }
491        KbKey::ArrowRight => {
492            let movement = if event.mods.ctrl() {
493                Movement::Word(Direction::Right)
494            } else {
495                Movement::Grapheme(Direction::Right)
496            };
497            if event.mods.shift() {
498                input_handler.handle_action(Action::MoveSelecting(movement));
499            } else {
500                input_handler.handle_action(Action::Move(movement));
501            }
502        }
503        KbKey::ArrowUp => {
504            let movement = Movement::Vertical(VerticalMovement::LineUp);
505            if event.mods.shift() {
506                input_handler.handle_action(Action::MoveSelecting(movement));
507            } else {
508                input_handler.handle_action(Action::Move(movement));
509            }
510        }
511        KbKey::ArrowDown => {
512            let movement = Movement::Vertical(VerticalMovement::LineDown);
513            if event.mods.shift() {
514                input_handler.handle_action(Action::MoveSelecting(movement));
515            } else {
516                input_handler.handle_action(Action::Move(movement));
517            }
518        }
519        KbKey::Backspace => {
520            let movement = if event.mods.ctrl() {
521                Movement::Word(Direction::Upstream)
522            } else {
523                Movement::Grapheme(Direction::Upstream)
524            };
525            input_handler.handle_action(Action::Delete(movement));
526        }
527        KbKey::Delete => {
528            let movement = if event.mods.ctrl() {
529                Movement::Word(Direction::Downstream)
530            } else {
531                Movement::Grapheme(Direction::Downstream)
532            };
533            input_handler.handle_action(Action::Delete(movement));
534        }
535        KbKey::Enter => {
536            // I'm sorry windows, you'll get IME soon.
537            input_handler.handle_action(Action::InsertNewLine {
538                ignore_hotkey: false,
539                newline_type: '\n',
540            });
541        }
542        KbKey::Tab => {
543            let action = if event.mods.shift() {
544                Action::InsertBacktab
545            } else {
546                Action::InsertTab {
547                    ignore_hotkey: false,
548                }
549            };
550            input_handler.handle_action(action);
551        }
552        KbKey::Home => {
553            let movement = if event.mods.ctrl() {
554                Movement::Vertical(VerticalMovement::DocumentStart)
555            } else {
556                Movement::Line(Direction::Upstream)
557            };
558            if event.mods.shift() {
559                input_handler.handle_action(Action::MoveSelecting(movement));
560            } else {
561                input_handler.handle_action(Action::Move(movement));
562            }
563        }
564        KbKey::End => {
565            let movement = if event.mods.ctrl() {
566                Movement::Vertical(VerticalMovement::DocumentEnd)
567            } else {
568                Movement::Line(Direction::Downstream)
569            };
570            if event.mods.shift() {
571                input_handler.handle_action(Action::MoveSelecting(movement));
572            } else {
573                input_handler.handle_action(Action::Move(movement));
574            }
575        }
576        KbKey::PageUp => {
577            let movement = Movement::Vertical(VerticalMovement::PageUp);
578            if event.mods.shift() {
579                input_handler.handle_action(Action::MoveSelecting(movement));
580            } else {
581                input_handler.handle_action(Action::Move(movement));
582            }
583        }
584        KbKey::PageDown => {
585            let movement = Movement::Vertical(VerticalMovement::PageDown);
586            if event.mods.shift() {
587                input_handler.handle_action(Action::MoveSelecting(movement));
588            } else {
589                input_handler.handle_action(Action::Move(movement));
590            }
591        }
592        _ => {
593            handler.release_input_lock(token);
594            return false;
595        }
596    };
597    handler.release_input_lock(token);
598    true
599}
600
601/// Indicates a movement that transforms a particular text position in a
602/// document.
603///
604/// These movements transform only single indices — not selections.
605///
606/// You'll note that a lot of these operations are idempotent, but you can get
607/// around this by first sending a `Grapheme` movement.  If for instance, you
608/// want a `ParagraphStart` that is not idempotent, you can first send
609/// `Movement::Grapheme(Direction::Upstream)`, and then follow it with
610/// `ParagraphStart`.
611#[non_exhaustive]
612#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
613pub enum Movement {
614    /// A movement that stops when it reaches an extended grapheme cluster boundary.
615    ///
616    /// This movement is achieved on most systems by pressing the left and right
617    /// arrow keys.  For more information on grapheme clusters, see
618    /// [Unicode Text Segmentation](https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries).
619    Grapheme(Direction),
620    /// A movement that stops when it reaches a word boundary.
621    ///
622    /// This movement is achieved on most systems by pressing the left and right
623    /// arrow keys while holding control. For more information on words, see
624    /// [Unicode Text Segmentation](https://unicode.org/reports/tr29/#Word_Boundaries).
625    Word(Direction),
626    /// A movement that stops when it reaches a soft line break.
627    ///
628    /// This movement is achieved on macOS by pressing the left and right arrow
629    /// keys while holding command.  `Line` should be idempotent: if the
630    /// position is already at the end of a soft-wrapped line, this movement
631    /// should never push it onto another soft-wrapped line.
632    ///
633    /// In order to implement this properly, your text positions should remember
634    /// their affinity.
635    Line(Direction),
636    /// An upstream movement that stops when it reaches a hard line break.
637    ///
638    /// `ParagraphStart` should be idempotent: if the position is already at the
639    /// start of a hard-wrapped line, this movement should never push it onto
640    /// the previous line.
641    ParagraphStart,
642    /// A downstream movement that stops when it reaches a hard line break.
643    ///
644    /// `ParagraphEnd` should be idempotent: if the position is already at the
645    /// end of a hard-wrapped line, this movement should never push it onto the
646    /// next line.
647    ParagraphEnd,
648    /// A vertical movement, see `VerticalMovement` for more details.
649    Vertical(VerticalMovement),
650}
651
652/// Indicates a horizontal direction in the text.
653#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
654pub enum Direction {
655    /// The direction visually to the left.
656    ///
657    /// This may be byte-wise forwards or backwards in the document, depending
658    /// on the text direction around the position being moved.
659    Left,
660    /// The direction visually to the right.
661    ///
662    /// This may be byte-wise forwards or backwards in the document, depending
663    /// on the text direction around the position being moved.
664    Right,
665    /// Byte-wise backwards in the document.
666    ///
667    /// In a left-to-right context, this value is the same as `Left`.
668    Upstream,
669    /// Byte-wise forwards in the document.
670    ///
671    /// In a left-to-right context, this value is the same as `Right`.
672    Downstream,
673}
674
675impl Direction {
676    /// Returns `true` if this direction is byte-wise backwards for
677    /// the provided [`WritingDirection`].
678    ///
679    /// The provided direction *must not be* `WritingDirection::Natural`.
680    pub fn is_upstream_for_direction(self, direction: WritingDirection) -> bool {
681        assert!(
682            !matches!(direction, WritingDirection::Natural),
683            "writing direction must be resolved"
684        );
685        match self {
686            Direction::Upstream => true,
687            Direction::Downstream => false,
688            Direction::Left => matches!(direction, WritingDirection::LeftToRight),
689            Direction::Right => matches!(direction, WritingDirection::RightToLeft),
690        }
691    }
692}
693
694/// Distinguishes between two visually distinct locations with the same byte
695/// index.
696///
697/// Sometimes, a byte location in a document has two visual locations. For
698/// example, the end of a soft-wrapped line and the start of the subsequent line
699/// have different visual locations (and we want to be able to place an input
700/// caret in either place!) but the same byte-wise location. This also shows up
701/// in bidirectional text contexts. Affinity allows us to disambiguate between
702/// these two visual locations.
703pub enum Affinity {
704    Upstream,
705    Downstream,
706}
707
708/// Indicates a horizontal direction for writing text.
709#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
710pub enum WritingDirection {
711    LeftToRight,
712    RightToLeft,
713    /// Indicates writing direction should be automatically detected based on
714    /// the text contents.
715    Natural,
716}
717
718/// Indicates a vertical movement in a text document.
719#[non_exhaustive]
720#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
721pub enum VerticalMovement {
722    LineUp,
723    LineDown,
724    PageUp,
725    PageDown,
726    DocumentStart,
727    DocumentEnd,
728}
729
730/// A special text editing command sent from the platform to the application.
731#[non_exhaustive]
732#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
733pub enum Action {
734    /// Moves the selection.
735    ///
736    /// Before moving, if the active and the anchor of the selection are not at
737    /// the same position (it's a non-caret selection), then:
738    ///
739    /// 1. First set both active and anchor to the same position: the
740    ///    selection's upstream index if `Movement` is an upstream movement, or
741    ///    the downstream index if `Movement` is a downstream movement.
742    ///
743    /// 2. If `Movement` is `Grapheme`, then stop. Otherwise, apply the
744    ///    `Movement` as per the usual rules.
745    Move(Movement),
746
747    /// Moves just the selection's active edge.
748    ///
749    /// Equivalent to holding shift while performing movements or clicks on most
750    /// operating systems.
751    MoveSelecting(Movement),
752
753    /// Select the entire document.
754    SelectAll,
755
756    /// Expands the selection to the entire soft-wrapped line.
757    ///
758    /// If multiple lines are already selected, expands the selection to
759    /// encompass all soft-wrapped lines that intersected with the prior
760    /// selection.  If the selection is a caret is on a soft line break, uses
761    /// the affinity of the caret to determine which of the two lines to select.
762    /// `SelectLine` should be idempotent: it should never expand onto adjacent
763    /// lines.
764    SelectLine,
765
766    /// Expands the selection to the entire hard-wrapped line.
767    ///
768    /// If multiple lines are already selected, expands the selection to
769    /// encompass all hard-wrapped lines that intersected with the prior
770    /// selection.  `SelectParagraph` should be idempotent: it should never
771    /// expand onto adjacent lines.
772    SelectParagraph,
773
774    /// Expands the selection to the entire word.
775    ///
776    /// If multiple words are already selected, expands the selection to
777    /// encompass all words that intersected with the prior selection.  If the
778    /// selection is a caret is on a word boundary, selects the word downstream
779    /// of the caret.  `SelectWord` should be idempotent: it should never expand
780    /// onto adjacent words.
781    ///
782    /// For more information on what these so-called "words" are, see
783    /// [Unicode Text Segmentation](https://unicode.org/reports/tr29/#Word_Boundaries).
784    SelectWord,
785
786    /// Deletes some text.
787    ///
788    /// If some text is already selected, `Movement` is ignored, and the
789    /// selection is deleted.  If the selection's anchor is the same as the
790    /// active, then first apply `MoveSelecting(Movement)` and then delete the
791    /// resulting selection.
792    Delete(Movement),
793
794    /// Delete backwards, potentially breaking graphemes.
795    ///
796    /// A special kind of backspace that, instead of deleting the entire
797    /// grapheme upstream of the caret, may in some cases and character sets
798    /// delete a subset of that grapheme's code points.
799    DecomposingBackspace,
800
801    /// Maps the characters in the selection to uppercase.
802    ///
803    /// For more information on case mapping, see the
804    /// [Unicode Case Mapping FAQ](https://unicode.org/faq/casemap_charprop.html#7)
805    UppercaseSelection,
806
807    /// Maps the characters in the selection to lowercase.
808    ///
809    /// For more information on case mapping, see the
810    /// [Unicode Case Mapping FAQ](https://unicode.org/faq/casemap_charprop.html#7)
811    LowercaseSelection,
812
813    /// Maps the characters in the selection to titlecase.
814    ///
815    /// When calculating whether a character is at the beginning of a word, you
816    /// may have to peek outside the selection to other characters in the document.
817    ///
818    /// For more information on case mapping, see the
819    /// [Unicode Case Mapping FAQ](https://unicode.org/faq/casemap_charprop.html#7)
820    TitlecaseSelection,
821
822    /// Inserts a newline character into the document.
823    InsertNewLine {
824        /// If `true`, then always insert a newline, even if normally you
825        /// would run a keyboard shortcut attached to the return key, like
826        /// sending a message or activating autocomplete.
827        ///
828        /// On macOS, this is triggered by pressing option-return.
829        ignore_hotkey: bool,
830        /// Either `U+000A`, `U+2029`, or `U+2028`. For instance, on macOS, control-enter inserts `U+2028`.
831        //FIXME: what about windows?
832        newline_type: char,
833    },
834
835    /// Inserts a tab character into the document.
836    InsertTab {
837        /// If `true`, then always insert a tab, even if normally you would run
838        /// a keyboard shortcut attached to the return key, like indenting a
839        /// line or activating autocomplete.
840        ///
841        /// On macOS, this is triggered by pressing option-tab.
842        ignore_hotkey: bool,
843    },
844
845    /// Indicates the reverse of inserting tab; corresponds to shift-tab on most
846    /// operating systems.
847    InsertBacktab,
848
849    InsertSingleQuoteIgnoringSmartQuotes,
850    InsertDoubleQuoteIgnoringSmartQuotes,
851
852    /// Scrolls the text field without modifying the selection.
853    Scroll(VerticalMovement),
854
855    /// Centers the selection vertically in the text field.
856    ///
857    /// The average of the anchor's y and the active's y should be exactly
858    /// halfway down the field.  If the selection is taller than the text
859    /// field's visible height, then instead scrolls the minimum distance such
860    /// that the text field is completely vertically filled by the selection.
861    ScrollToSelection,
862
863    /// Sets the writing direction of the selected text or caret.
864    SetSelectionWritingDirection(WritingDirection),
865
866    /// Sets the writing direction of all paragraphs that partially or fully
867    /// intersect with the selection or caret.
868    SetParagraphWritingDirection(WritingDirection),
869
870    /// Cancels the current window or operation.
871    ///
872    /// Triggered on most operating systems with escape.
873    Cancel,
874}