Skip to main content

iced_core/text/
editor.rs

1//! Edit text.
2use crate::clipboard;
3use crate::input_method;
4use crate::keyboard;
5use crate::keyboard::key;
6use crate::mouse;
7use crate::renderer;
8use crate::text::highlighter::{self, Highlighter};
9use crate::text::{self, Alignment, LineHeight, Position, Wrapping};
10use crate::time::{Duration, Instant};
11use crate::widget::operation::{Focusable, TextInput};
12use crate::window;
13use crate::{Color, Event, InputMethod, Padding, Pixels, Point, Rectangle, Size, SmolStr, Vector};
14
15use std::borrow::Cow;
16use std::sync::Arc;
17
18/// A component that can be used by widgets to edit multi-line text.
19pub trait Editor: Sized + Default {
20    /// The font of the [`Editor`].
21    type Font: Copy + PartialEq + Default;
22
23    /// Creates a new [`Editor`] laid out with the given text.
24    fn with_text(text: &str) -> Self;
25
26    /// Returns true if the [`Editor`] has no contents.
27    fn is_empty(&self) -> bool;
28
29    /// Returns the current [`Cursor`] of the [`Editor`].
30    fn cursor(&self) -> Cursor;
31
32    /// Returns the current [`Selection`] of the [`Editor`].
33    fn selection(&self) -> Selection;
34
35    /// Returns the current selected text of the [`Editor`].
36    fn copy(&self) -> Option<String>;
37
38    /// Returns the text of the given line in the [`Editor`], if it exists.
39    fn line(&self, index: usize) -> Option<Line<'_>>;
40
41    /// Returns the amount of lines in the [`Editor`].
42    fn line_count(&self) -> usize;
43
44    /// Performs an [`Action`] on the [`Editor`].
45    fn perform(&mut self, action: Action);
46
47    /// Moves the cursor to the given position.
48    fn move_to(&mut self, cursor: Cursor);
49
50    /// Returns the current boundaries of the [`Editor`].
51    fn bounds(&self) -> Size;
52
53    /// Returns the minimum boundaries to fit the current contents of
54    /// the [`Editor`].
55    fn min_bounds(&self) -> Size;
56
57    /// Returns the hint factor of the [`Editor`].
58    fn hint_factor(&self) -> Option<f32>;
59
60    /// Updates the [`Editor`] with some new attributes.
61    fn update(
62        &mut self,
63        new_bounds: Size,
64        new_font: Self::Font,
65        new_size: Pixels,
66        new_line_height: LineHeight,
67        new_wrapping: Wrapping,
68        new_alignment: Alignment,
69        new_hint_factor: Option<f32>,
70        new_highlighter: &mut impl Highlighter,
71    );
72
73    /// Overwrites the current contents of the [`Editor`].
74    fn overwrite(&mut self, new_text: &str);
75
76    /// Runs a text [`Highlighter`] in the [`Editor`].
77    fn highlight<H: Highlighter>(
78        &mut self,
79        font: Self::Font,
80        highlighter: &mut H,
81        format_highlight: impl Fn(&H::Highlight) -> highlighter::Format<Self::Font>,
82    );
83
84    /// Returns an iterator of the text of the lines in the [`Editor`].
85    fn lines(&self) -> impl Iterator<Item = Line<'_>> {
86        (0..)
87            .map(|i| self.line(i))
88            .take_while(Option::is_some)
89            .flatten()
90    }
91
92    /// Returns the text of the [`Editor`].
93    fn text(&self) -> String {
94        let mut contents = String::new();
95        let mut lines = self.lines().peekable();
96
97        while let Some(line) = lines.next() {
98            contents.push_str(&line.text);
99
100            if lines.peek().is_some() {
101                contents.push_str(if line.ending == LineEnding::None {
102                    LineEnding::default().as_str()
103                } else {
104                    line.ending.as_str()
105                });
106            }
107        }
108
109        contents
110    }
111
112    /// Returns the current [`Font`](Self::Font) of the [`Editor`].
113    fn font(&self) -> Self::Font;
114
115    /// Returns the current text size of the [`Editor`].
116    fn text_size(&self) -> Pixels;
117
118    /// Returns the current [`LineHeight`] of the [`Editor`].
119    fn line_height(&self) -> LineHeight;
120}
121
122/// An interaction with an [`Editor`].
123#[derive(Debug, Clone, PartialEq)]
124pub enum Action {
125    /// Apply a [`Motion`].
126    Move(Motion),
127    /// Select text with a given [`Motion`].
128    Select(Motion),
129    /// Select the word at the current cursor.
130    SelectWord,
131    /// Select the line at the current cursor.
132    SelectLine,
133    /// Select the entire buffer.
134    SelectAll,
135    /// Perform an [`Edit`].
136    Edit(Edit),
137    /// Click the [`Editor`] at the given [`Point`].
138    Click(Point, mouse::click::Kind),
139    /// Drag the mouse on the [`Editor`] to the given [`Point`].
140    Drag(Point),
141    /// Scroll the [`Editor`] a certain amount of lines.
142    Scroll {
143        /// The amount of lines to scroll.
144        lines: i32,
145    },
146}
147
148impl Action {
149    /// Returns whether the [`Action`] is an editing action.
150    pub fn is_edit(&self) -> bool {
151        matches!(self, Self::Edit(_))
152    }
153}
154
155/// An action that edits text.
156#[derive(Debug, Clone, PartialEq)]
157pub enum Edit {
158    /// Insert the given character.
159    Insert(char),
160    /// Paste the given text.
161    Paste(Arc<String>),
162    /// Break the current line.
163    Enter,
164    /// Indent the current line.
165    Indent,
166    /// Unindent the current line.
167    Unindent,
168    /// Delete the previous character.
169    Backspace,
170    /// Delete the word before the cursor.
171    BackspaceWord,
172    /// Delete the line before the cursor.
173    BackspaceLine,
174    /// Delete the next character.
175    Delete,
176    /// Delete the word after the cursor.
177    DeleteWord,
178    /// Delete the line after the cursor.
179    DeleteLine,
180    /// Undo the last change performed on the [`Editor`].
181    Undo,
182    /// Redo the last undone change on the [`Editor`].
183    Redo,
184}
185
186/// A cursor movement.
187#[derive(Debug, Clone, Copy, PartialEq)]
188pub enum Motion {
189    /// Move left.
190    Left,
191    /// Move right.
192    Right,
193    /// Move up.
194    Up,
195    /// Move down.
196    Down,
197    /// Move to the left boundary of a word.
198    WordLeft,
199    /// Move to the right boundary of a word.
200    WordRight,
201    /// Move to the start of the line.
202    Home,
203    /// Move to the end of the line.
204    End,
205    /// Move to the start of the previous window.
206    PageUp,
207    /// Move to the start of the next window.
208    PageDown,
209    /// Move to the start of the text.
210    DocumentStart,
211    /// Move to the end of the text.
212    DocumentEnd,
213}
214
215impl Motion {
216    /// Widens the [`Motion`], if possible.
217    pub fn widen(self) -> Self {
218        match self {
219            Self::Left => Self::WordLeft,
220            Self::Right => Self::WordRight,
221            Self::Home => Self::DocumentStart,
222            Self::End => Self::DocumentEnd,
223            _ => self,
224        }
225    }
226
227    /// Returns the [`Direction`] of the [`Motion`].
228    pub fn direction(&self) -> Direction {
229        match self {
230            Self::Left
231            | Self::Up
232            | Self::WordLeft
233            | Self::Home
234            | Self::PageUp
235            | Self::DocumentStart => Direction::Left,
236            Self::Right
237            | Self::Down
238            | Self::WordRight
239            | Self::End
240            | Self::PageDown
241            | Self::DocumentEnd => Direction::Right,
242        }
243    }
244}
245
246/// A direction in some text.
247#[derive(Debug, Clone, Copy, PartialEq, Eq)]
248pub enum Direction {
249    /// <-
250    Left,
251    /// ->
252    Right,
253}
254
255/// The cursor of an [`Editor`].
256#[derive(Debug, Clone)]
257pub enum Selection {
258    /// Cursor without a selection
259    Caret(Point),
260
261    /// Cursor selecting a range of text
262    Range(Vec<Rectangle>),
263}
264
265/// The range of an [`Editor`].
266#[derive(Debug, Clone, Copy, PartialEq)]
267pub struct Cursor {
268    /// The cursor position.
269    pub position: Position,
270
271    /// The selection position, if any.
272    pub selection: Option<Position>,
273}
274
275/// A line of an [`Editor`].
276#[derive(Clone, Debug, Default, Eq, PartialEq)]
277pub struct Line<'a> {
278    /// The raw text of the [`Line`].
279    pub text: Cow<'a, str>,
280    /// The line ending of the [`Line`].
281    pub ending: LineEnding,
282}
283
284/// The line ending of a [`Line`].
285#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
286pub enum LineEnding {
287    /// Use `\n` for line ending (POSIX-style)
288    #[default]
289    Lf,
290    /// Use `\r\n` for line ending (Windows-style)
291    CrLf,
292    /// Use `\r` for line ending (many legacy systems)
293    Cr,
294    /// Use `\n\r` for line ending (some legacy systems)
295    LfCr,
296    /// No line ending
297    None,
298}
299
300impl LineEnding {
301    /// Gets the string representation of the [`LineEnding`].
302    pub fn as_str(self) -> &'static str {
303        match self {
304            Self::Lf => "\n",
305            Self::CrLf => "\r\n",
306            Self::Cr => "\r",
307            Self::LfCr => "\n\r",
308            Self::None => "",
309        }
310    }
311}
312
313/// The internal state of an [`Editor`].
314#[derive(Debug, Clone, Default)]
315pub struct State {
316    focus: Option<Focus>,
317    preedit: Option<input_method::Preedit>,
318    last_click: Option<mouse::Click>,
319    is_dragging: bool,
320    partial_scroll: f32,
321}
322
323impl State {
324    /// Creates a new [`State`].
325    pub fn new() -> Self {
326        Self::default()
327    }
328
329    /// Updates the [`State`] for the given [`Editor`] and returns any relevant [`Update`].
330    pub fn update<Message>(
331        &mut self,
332        editor: &impl Editor,
333        event: &Event,
334        bounds: Rectangle,
335        padding: Padding,
336        cursor: mouse::Cursor,
337        key_binding: impl Fn(KeyPress) -> Option<Binding<Message>>,
338    ) -> Option<Update<Message>> {
339        match event {
340            Event::Window(window::Event::Unfocused) => {
341                if let Some(focus) = &mut self.focus {
342                    focus.is_window_focused = false;
343                }
344
345                None
346            }
347            Event::Window(window::Event::Focused) => {
348                if let Some(focus) = &mut self.focus {
349                    focus.is_window_focused = true;
350                    focus.updated_at = Instant::now();
351                }
352
353                Some(Update::Focus)
354            }
355            Event::Window(window::Event::RedrawRequested(now)) => {
356                let focus = self.focus.as_mut()?;
357
358                if !focus.is_window_focused {
359                    return None;
360                }
361
362                focus.now = *now;
363
364                let millis_until_redraw = Focus::CURSOR_BLINK_INTERVAL_MILLIS
365                    - (focus.now - focus.updated_at).as_millis()
366                        % Focus::CURSOR_BLINK_INTERVAL_MILLIS;
367
368                Some(Update::RedrawAt(
369                    focus.now + Duration::from_millis(millis_until_redraw as u64),
370                ))
371            }
372            Event::Clipboard(clipboard::Event::Read(Ok(content))) => {
373                let focus = self.focus.as_ref()?;
374
375                if !focus.is_window_focused {
376                    return None;
377                }
378
379                let clipboard::Content::Text(text) = content.as_ref() else {
380                    return None;
381                };
382
383                Some(Update::Action(Action::Edit(Edit::Paste(Arc::new(
384                    text.clone(),
385                )))))
386            }
387            Event::Mouse(event) => match event {
388                mouse::Event::ButtonPressed(mouse::Button::Left) => {
389                    if let Some(cursor_position) = cursor.position_in(bounds) {
390                        let cursor_position =
391                            cursor_position - Vector::new(padding.left, padding.top);
392
393                        let click = mouse::Click::new(
394                            cursor_position,
395                            mouse::Button::Left,
396                            self.last_click,
397                        );
398
399                        self.focus = Some(Focus::now());
400                        self.last_click = Some(click);
401                        self.is_dragging = true;
402
403                        Some(Update::Action(Action::Click(
404                            click.position(),
405                            click.kind(),
406                        )))
407                    } else if self.focus.is_some() {
408                        self.focus = None;
409
410                        Some(Update::Unfocus)
411                    } else {
412                        None
413                    }
414                }
415                mouse::Event::ButtonReleased(mouse::Button::Left) => {
416                    self.is_dragging = false;
417
418                    Some(Update::Release)
419                }
420                mouse::Event::CursorMoved { .. } if self.is_dragging => {
421                    let position =
422                        cursor.position_in(bounds)? - Vector::new(padding.left, padding.top);
423
424                    Some(Update::Action(Action::Drag(position)))
425                }
426                mouse::Event::WheelScrolled { delta } if cursor.is_over(bounds) => {
427                    let bounds = editor.bounds();
428
429                    if bounds.height >= i32::MAX as f32 {
430                        return None;
431                    }
432
433                    let lines = match delta {
434                        mouse::ScrollDelta::Lines { y, .. } => {
435                            if y.abs() > 0.0 {
436                                y.signum() * -(y.abs() * 4.0).max(1.0)
437                            } else {
438                                0.0
439                            }
440                        }
441                        mouse::ScrollDelta::Pixels { y, .. } => -y / 4.0,
442                    };
443
444                    let lines = lines + self.partial_scroll;
445                    self.partial_scroll = lines.fract();
446
447                    Some(Update::Action(Action::Scroll {
448                        lines: lines as i32,
449                    }))
450                }
451                _ => None,
452            },
453            Event::InputMethod(event) => match event {
454                input_method::Event::Opened | input_method::Event::Closed => {
455                    let is_open = matches!(event, input_method::Event::Opened);
456                    self.preedit = is_open.then(input_method::Preedit::new);
457
458                    Some(Update::InputMethod)
459                }
460                input_method::Event::Preedit(content, selection) if self.focus.is_some() => {
461                    self.preedit = Some(input_method::Preedit {
462                        content: content.clone(),
463                        selection: selection.clone(),
464                        text_size: Some(editor.text_size()),
465                    });
466
467                    Some(Update::InputMethod)
468                }
469                input_method::Event::Commit(content) if self.focus.is_some() => Some(
470                    Update::Action(Action::Edit(Edit::Paste(Arc::new(content.clone())))),
471                ),
472                _ => None,
473            },
474            Event::Keyboard(keyboard::Event::KeyPressed {
475                key,
476                modified_key,
477                physical_key,
478                modifiers,
479                text,
480                ..
481            }) => {
482                let key_press = KeyPress {
483                    key: key.clone(),
484                    modified_key: modified_key.clone(),
485                    physical_key: *physical_key,
486                    modifiers: *modifiers,
487                    text: text.clone(),
488                    is_focused: self.is_focused(),
489                };
490
491                fn apply_binding<Message>(
492                    binding: Binding<Message>,
493                    editor: &impl Editor,
494                    state: &mut State,
495                ) -> Option<Update<Message>> {
496                    let action = |action| Update::Action(action);
497                    let edit = |edit| action(Action::Edit(edit));
498
499                    match binding {
500                        Binding::Unfocus => {
501                            state.focus = None;
502                            state.is_dragging = false;
503
504                            None
505                        }
506                        Binding::Copy => {
507                            let selection = editor.copy()?;
508
509                            Some(Update::Copy(selection))
510                        }
511                        Binding::Cut => {
512                            let selection = editor.copy()?;
513
514                            Some(Update::Sequence(vec![
515                                Update::Copy(selection),
516                                edit(Edit::Backspace),
517                            ]))
518                        }
519                        Binding::Paste => Some(Update::Paste),
520                        Binding::Undo => Some(edit(Edit::Undo)),
521                        Binding::Redo => Some(edit(Edit::Redo)),
522                        Binding::Move(motion) => Some(action(Action::Move(motion))),
523                        Binding::Select(motion) => Some(action(Action::Select(motion))),
524                        Binding::SelectWord => Some(action(Action::SelectWord)),
525                        Binding::SelectLine => Some(action(Action::SelectLine)),
526                        Binding::SelectAll => Some(action(Action::SelectAll)),
527                        Binding::Insert(c) => Some(edit(Edit::Insert(c))),
528                        Binding::Enter => Some(edit(Edit::Enter)),
529                        Binding::Backspace => Some(edit(Edit::Backspace)),
530                        Binding::BackspaceWord => Some(edit(Edit::BackspaceWord)),
531                        Binding::BackspaceLine => Some(edit(Edit::BackspaceLine)),
532                        Binding::Delete => Some(action(Action::Edit(Edit::Delete))),
533                        Binding::DeleteWord => Some(edit(Edit::DeleteWord)),
534                        Binding::DeleteLine => Some(edit(Edit::DeleteLine)),
535                        Binding::Sequence(sequence) => {
536                            let updates: Vec<_> = sequence
537                                .into_iter()
538                                .flat_map(|binding| apply_binding(binding, editor, state))
539                                .collect();
540
541                            if updates.is_empty() {
542                                return None;
543                            }
544
545                            Some(Update::Sequence(updates))
546                        }
547                        Binding::Custom(message) => Some(Update::Custom(message)),
548                    }
549                }
550
551                let update = apply_binding(key_binding(key_press)?, editor, self);
552
553                if let Some(focus) = &mut self.focus {
554                    focus.updated_at = Instant::now();
555                }
556
557                update
558            }
559            _ => None,
560        }
561    }
562
563    /// Returns the current [`InputMethod`] of the [`State`] for the given [`Editor`].
564    pub fn input_method<'a>(
565        &'a self,
566        editor: &impl Editor,
567        position: Point,
568    ) -> InputMethod<&'a str> {
569        let Some(Focus {
570            is_window_focused: true,
571            ..
572        }) = &self.focus
573        else {
574            return InputMethod::Disabled;
575        };
576
577        let translation = position - Point::ORIGIN;
578
579        let cursor = match editor.selection() {
580            Selection::Caret(position) => position,
581            Selection::Range(ranges) => ranges.first().cloned().unwrap_or_default().position(),
582        };
583
584        let line_height = editor.line_height().to_absolute(editor.text_size());
585
586        let position = cursor + translation;
587
588        InputMethod::Enabled {
589            cursor: Rectangle::new(position, Size::new(1.0, f32::from(line_height))),
590            purpose: input_method::Purpose::Normal,
591            preedit: self.preedit.as_ref().map(input_method::Preedit::as_ref),
592        }
593    }
594
595    /// Draws the given [`Editor`] with the current [`State`].
596    pub fn draw<Renderer: text::Renderer>(
597        &self,
598        editor: &Renderer::Editor,
599        renderer: &mut Renderer,
600        position: Point,
601        clip_bounds: Rectangle,
602        style: Style,
603    ) {
604        let bounds = Rectangle::new(position, editor.bounds());
605
606        let Some(clip_bounds) = clip_bounds.intersection(&bounds) else {
607            return;
608        };
609
610        if !editor.is_empty() {
611            renderer.fill_editor(editor, position, style.value, clip_bounds);
612        }
613
614        if !self.is_focused() {
615            return;
616        }
617
618        let translation = position - Point::ORIGIN;
619        let text_size = editor.text_size();
620        let line_height = editor.line_height();
621
622        match editor.selection() {
623            Selection::Caret(position) if self.is_cursor_visible() => {
624                let cursor = Rectangle::new(
625                    position + translation,
626                    Size::new(
627                        if renderer::CRISP {
628                            (1.0 / renderer.hint_factor().unwrap_or(1.0)).max(1.0)
629                        } else {
630                            1.0
631                        },
632                        line_height.to_absolute(text_size).into(),
633                    ),
634                );
635
636                if let Some(clipped_cursor) = clip_bounds.intersection(&cursor) {
637                    renderer.fill_quad(
638                        renderer::Quad {
639                            bounds: clipped_cursor,
640                            ..renderer::Quad::default()
641                        },
642                        style.value,
643                    );
644                }
645            }
646            Selection::Range(ranges) => {
647                for range in ranges
648                    .into_iter()
649                    .filter_map(|range| clip_bounds.intersection(&(range + translation)))
650                {
651                    renderer.fill_quad(
652                        renderer::Quad {
653                            bounds: range.round(),
654                            ..renderer::Quad::default()
655                        },
656                        style.selection,
657                    );
658                }
659            }
660            Selection::Caret(_) => {
661                // Drawing an empty quad helps some renderers to track the damage of the blinking cursor
662                renderer.fill_quad(renderer::Quad::default(), Color::TRANSPARENT);
663            }
664        }
665    }
666
667    /// Returns whether the cursor of the [`Editor`] is visible.
668    pub fn is_cursor_visible(&self) -> bool {
669        self.focus.as_ref().is_some_and(Focus::is_cursor_visible)
670    }
671}
672
673/// The visual style of an [`Editor`].
674pub struct Style {
675    /// The [`Color`] of the contents.
676    pub value: Color,
677
678    /// The background [`Color`] of any selection.
679    pub selection: Color,
680}
681
682#[derive(Debug, Clone)]
683struct Focus {
684    updated_at: Instant,
685    now: Instant,
686    is_window_focused: bool,
687}
688
689impl Focus {
690    const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500;
691
692    fn now() -> Self {
693        let now = Instant::now();
694
695        Self {
696            updated_at: now,
697            now,
698            is_window_focused: true,
699        }
700    }
701
702    fn is_cursor_visible(&self) -> bool {
703        self.is_window_focused
704            && ((self.now - self.updated_at).as_millis() / Self::CURSOR_BLINK_INTERVAL_MILLIS)
705                .is_multiple_of(2)
706    }
707}
708
709impl State {
710    /// Returns whether the [`Editor`] is currently focused or not.
711    pub fn is_focused(&self) -> bool {
712        self.focus.is_some()
713    }
714}
715
716impl Focusable for State {
717    fn is_focused(&self) -> bool {
718        self.focus.is_some()
719    }
720
721    fn focus(&mut self) {
722        self.focus = Some(Focus::now());
723    }
724
725    fn unfocus(&mut self) {
726        self.focus = None;
727    }
728}
729
730/// A binding to an action in the [`Editor`].
731#[derive(Debug, Clone, PartialEq)]
732pub enum Binding<Message> {
733    /// Unfocus the [`Editor`].
734    Unfocus,
735    /// Copy the selection of the [`Editor`].
736    Copy,
737    /// Cut the selection of the [`Editor`].
738    Cut,
739    /// Paste the clipboard contents in the [`Editor`].
740    Paste,
741    /// Undo the last change peformed in the [`Editor`].
742    Undo,
743    /// Redo the last change undone in the [`Editor`].
744    Redo,
745    /// Apply a [`Motion`].
746    Move(Motion),
747    /// Select text with a given [`Motion`].
748    Select(Motion),
749    /// Select the word at the current cursor.
750    SelectWord,
751    /// Select the line at the current cursor.
752    SelectLine,
753    /// Select the entire buffer.
754    SelectAll,
755    /// Insert the given character.
756    Insert(char),
757    /// Break the current line.
758    Enter,
759    /// Delete the previous character.
760    Backspace,
761    /// Delete the word before the cursor.
762    BackspaceWord,
763    /// Delete the line before the cursor.
764    BackspaceLine,
765    /// Delete the next character.
766    Delete,
767    /// Delete the word after the cursor.
768    DeleteWord,
769    /// Delete the line after the cursor.
770    DeleteLine,
771    /// A sequence of bindings to execute.
772    Sequence(Vec<Self>),
773    /// Produce the given message.
774    Custom(Message),
775}
776
777/// A key press.
778#[derive(Debug, Clone, PartialEq, Eq)]
779pub struct KeyPress {
780    /// The original key pressed without modifiers applied to it.
781    ///
782    /// You should use this key for combinations (e.g. Ctrl+C).
783    pub key: keyboard::Key,
784    /// The key pressed with modifiers applied to it.
785    ///
786    /// You should use this key for any single key bindings (e.g. motions).
787    pub modified_key: keyboard::Key,
788    /// The physical key pressed.
789    ///
790    /// You should use this key for layout-independent bindings.
791    pub physical_key: keyboard::key::Physical,
792    /// The state of the keyboard modifiers.
793    pub modifiers: keyboard::Modifiers,
794    /// The text produced by the key press.
795    pub text: Option<SmolStr>,
796    /// Whether the [`Editor`] is focused.
797    pub is_focused: bool,
798}
799
800impl<Message> Binding<Message> {
801    /// Returns the default [`Binding`] for the given key press.
802    pub fn from_key_press(event: KeyPress) -> Option<Self> {
803        let KeyPress {
804            key,
805            modified_key,
806            physical_key,
807            modifiers,
808            text,
809            is_focused,
810        } = event;
811
812        if !is_focused {
813            return None;
814        }
815
816        let combination = match key.to_latin(physical_key) {
817            Some('c') if modifiers.command() => Some(Self::Copy),
818            Some('x') if modifiers.command() => Some(Self::Cut),
819            Some('v') if modifiers.command() && !modifiers.alt() => Some(Self::Paste),
820            Some('a') if modifiers.command() => Some(Self::SelectAll),
821            Some('z') if modifiers.command() => Some(Self::Undo),
822            Some('y') if modifiers.command() => Some(Self::Redo),
823            _ => None,
824        };
825
826        if let Some(binding) = combination {
827            return Some(binding);
828        }
829
830        #[cfg(target_os = "macos")]
831        let modified_key = convert_macos_shortcut(&key, modifiers).unwrap_or(modified_key);
832
833        match modified_key.as_ref() {
834            keyboard::Key::Named(key::Named::Enter) => Some(Self::Enter),
835            keyboard::Key::Named(key::Named::Backspace) => Some(if modifiers.command() {
836                if modifiers.shift() {
837                    Self::BackspaceLine
838                } else {
839                    Self::BackspaceWord
840                }
841            } else {
842                Self::Backspace
843            }),
844            keyboard::Key::Named(key::Named::Delete)
845                if text.is_none() || text.as_deref() == Some("\u{7f}") =>
846            {
847                Some(if modifiers.command() {
848                    if modifiers.shift() {
849                        Self::DeleteLine
850                    } else {
851                        Self::DeleteWord
852                    }
853                } else {
854                    Self::Delete
855                })
856            }
857            keyboard::Key::Named(key::Named::Escape) => Some(Self::Unfocus),
858            _ => {
859                if let Some(text) = text {
860                    let c = text.chars().find(|c| !c.is_control())?;
861
862                    Some(Self::Insert(c))
863                } else if let keyboard::Key::Named(named_key) = key.as_ref() {
864                    let motion = motion(named_key)?;
865
866                    let motion = if modifiers.macos_command() {
867                        match motion {
868                            Motion::Left => Motion::Home,
869                            Motion::Right => Motion::End,
870                            _ => motion,
871                        }
872                    } else {
873                        motion
874                    };
875
876                    let motion = if modifiers.jump() {
877                        motion.widen()
878                    } else {
879                        motion
880                    };
881
882                    Some(if modifiers.shift() {
883                        Self::Select(motion)
884                    } else {
885                        Self::Move(motion)
886                    })
887                } else {
888                    None
889                }
890            }
891        }
892    }
893}
894
895/// The update of an [`Editor`], returned by [`State::update`].
896pub enum Update<Message> {
897    /// An [`Action`] must be performed in the [`Editor`].
898    Action(Action),
899    /// The [`Editor`] just gained focus.
900    Focus,
901    /// The [`Editor`] just lost focus.
902    Unfocus,
903    /// The [`Editor`] changed its [`InputMethod`].
904    InputMethod,
905    /// A mouse press was released in the [`Editor`].
906    Release,
907    /// The [`Editor`] must copy some text to the clipboard.
908    Copy(String),
909    /// The [`Editor`] must paste the clipboard contents.
910    Paste,
911    /// The [`Editor`] must be redrawn at the given [`Instant`].
912    RedrawAt(Instant),
913    /// The [`Editor`] produced a custom message that must be published.
914    Custom(Message),
915    /// The [`Editor`] produced a sequence of updates.
916    Sequence(Vec<Update<Message>>),
917}
918
919fn motion(key: key::Named) -> Option<Motion> {
920    match key {
921        key::Named::ArrowLeft => Some(Motion::Left),
922        key::Named::ArrowRight => Some(Motion::Right),
923        key::Named::ArrowUp => Some(Motion::Up),
924        key::Named::ArrowDown => Some(Motion::Down),
925        key::Named::Home => Some(Motion::Home),
926        key::Named::End => Some(Motion::End),
927        key::Named::PageUp => Some(Motion::PageUp),
928        key::Named::PageDown => Some(Motion::PageDown),
929        _ => None,
930    }
931}
932
933#[cfg(target_os = "macos")]
934fn convert_macos_shortcut(
935    key: &keyboard::Key,
936    modifiers: keyboard::Modifiers,
937) -> Option<keyboard::Key> {
938    if modifiers != keyboard::Modifiers::CTRL {
939        return None;
940    }
941
942    let key = match key.as_ref() {
943        keyboard::Key::Character("b") => key::Named::ArrowLeft,
944        keyboard::Key::Character("f") => key::Named::ArrowRight,
945        keyboard::Key::Character("a") => key::Named::Home,
946        keyboard::Key::Character("e") => key::Named::End,
947        keyboard::Key::Character("h") => key::Named::Backspace,
948        keyboard::Key::Character("d") => key::Named::Delete,
949        _ => return None,
950    };
951
952    Some(keyboard::Key::Named(key))
953}
954
955impl<T: Editor> TextInput for T {
956    fn text(&self) -> text::Fragment<'_> {
957        text::Fragment::Owned(Editor::text(self))
958    }
959
960    fn move_cursor_to_front(&mut self) {
961        self.perform(Action::Move(Motion::DocumentStart));
962    }
963
964    fn move_cursor_to_end(&mut self) {
965        self.perform(Action::Move(Motion::DocumentEnd));
966    }
967
968    fn move_cursor_to(&mut self, position: text::Position) {
969        self.move_to(Cursor {
970            position,
971            selection: None,
972        });
973    }
974
975    fn select_all(&mut self) {
976        self.perform(Action::SelectAll);
977    }
978
979    fn select_range(&mut self, start: text::Position, end: text::Position) {
980        self.move_to(Cursor {
981            position: start,
982            selection: Some(end),
983        });
984    }
985}