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;
9use crate::text::{self, Alignment, LineHeight, Position, Wrapping};
10use crate::time::{Duration, Instant};
11use crate::touch;
12use crate::widget::operation::{Focusable, TextInput};
13use crate::window;
14use crate::{
15    Color, Event, Font, InputMethod, Padding, Pixels, Point, Rectangle, Size, SmolStr, Vector,
16};
17
18use std::borrow::Cow;
19use std::sync::Arc;
20
21/// A component that can be used by widgets to edit multi-line text.
22pub trait Editor: Sized + Default {
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`], if visible.
33    fn selection(&self) -> Option<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: 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_parser: &mut impl text::Parser,
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<P: text::Parser>(
78        &mut self,
79        font: Font,
80        parser: &mut P,
81        highlight: impl Fn(P::Output) -> highlighter::Style,
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`] of the [`Editor`].
113    fn font(&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(Arc<[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 = cursor.position_from(bounds.position())?
422                        - 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::Touch(event) => match event {
454                touch::Event::FingerPressed { .. } => {
455                    if let Some(cursor_position) = cursor.position_in(bounds) {
456                        let cursor_position =
457                            cursor_position - Vector::new(padding.left, padding.top);
458
459                        let click = mouse::Click::new(
460                            cursor_position,
461                            mouse::Button::Left,
462                            self.last_click,
463                        );
464
465                        self.focus = Some(Focus::now());
466                        self.last_click = Some(click);
467                        self.is_dragging = true;
468
469                        Some(Update::Action(Action::Click(
470                            click.position(),
471                            click.kind(),
472                        )))
473                    } else if self.focus.is_some() {
474                        self.focus = None;
475
476                        Some(Update::Unfocus)
477                    } else {
478                        None
479                    }
480                }
481                touch::Event::FingerLifted { .. } | touch::Event::FingerLost { .. } => {
482                    self.is_dragging = false;
483
484                    Some(Update::Release)
485                }
486                touch::Event::FingerMoved { .. } if self.is_dragging => {
487                    let position =
488                        cursor.position_in(bounds)? - Vector::new(padding.left, padding.top);
489
490                    Some(Update::Action(Action::Drag(position)))
491                }
492                touch::Event::FingerMoved { .. } => None,
493            },
494            Event::InputMethod(event) => match event {
495                input_method::Event::Opened | input_method::Event::Closed => {
496                    let is_open = matches!(event, input_method::Event::Opened);
497                    self.preedit = is_open.then(input_method::Preedit::new);
498
499                    Some(Update::InputMethod)
500                }
501                input_method::Event::Preedit(content, selection) if self.focus.is_some() => {
502                    self.preedit = Some(input_method::Preedit {
503                        content: content.clone(),
504                        selection: selection.clone(),
505                        text_size: Some(editor.text_size()),
506                    });
507
508                    Some(Update::InputMethod)
509                }
510                input_method::Event::Commit(content) if self.focus.is_some() => Some(
511                    Update::Action(Action::Edit(Edit::Paste(Arc::new(content.clone())))),
512                ),
513                _ => None,
514            },
515            Event::Keyboard(keyboard::Event::KeyPressed {
516                key,
517                modified_key,
518                physical_key,
519                modifiers,
520                text,
521                ..
522            }) => {
523                let key_press = KeyPress {
524                    key: key.clone(),
525                    modified_key: modified_key.clone(),
526                    physical_key: *physical_key,
527                    modifiers: *modifiers,
528                    text: text.clone(),
529                    is_focused: self.is_focused(),
530                };
531
532                fn apply_binding<Message>(
533                    binding: Binding<Message>,
534                    editor: &impl Editor,
535                    state: &mut State,
536                ) -> Option<Update<Message>> {
537                    let action = |action| Update::Action(action);
538                    let edit = |edit| action(Action::Edit(edit));
539
540                    match binding {
541                        Binding::Unfocus => {
542                            state.focus = None;
543                            state.is_dragging = false;
544
545                            None
546                        }
547                        Binding::Copy => {
548                            let selection = editor.copy()?;
549
550                            Some(Update::Copy(selection))
551                        }
552                        Binding::Cut => {
553                            let selection = editor.copy()?;
554
555                            Some(Update::Sequence(vec![
556                                Update::Copy(selection),
557                                edit(Edit::Backspace),
558                            ]))
559                        }
560                        Binding::Paste => Some(Update::Paste),
561                        Binding::Undo => Some(edit(Edit::Undo)),
562                        Binding::Redo => Some(edit(Edit::Redo)),
563                        Binding::Move(motion) => Some(action(Action::Move(motion))),
564                        Binding::Select(motion) => Some(action(Action::Select(motion))),
565                        Binding::SelectWord => Some(action(Action::SelectWord)),
566                        Binding::SelectLine => Some(action(Action::SelectLine)),
567                        Binding::SelectAll => Some(action(Action::SelectAll)),
568                        Binding::Insert(c) => Some(edit(Edit::Insert(c))),
569                        Binding::Enter => Some(edit(Edit::Enter)),
570                        Binding::Backspace => Some(edit(Edit::Backspace)),
571                        Binding::BackspaceWord => Some(edit(Edit::BackspaceWord)),
572                        Binding::BackspaceLine => Some(edit(Edit::BackspaceLine)),
573                        Binding::Delete => Some(action(Action::Edit(Edit::Delete))),
574                        Binding::DeleteWord => Some(edit(Edit::DeleteWord)),
575                        Binding::DeleteLine => Some(edit(Edit::DeleteLine)),
576                        Binding::Sequence(sequence) => {
577                            let updates: Vec<_> = sequence
578                                .into_iter()
579                                .flat_map(|binding| apply_binding(binding, editor, state))
580                                .collect();
581
582                            if updates.is_empty() {
583                                return None;
584                            }
585
586                            Some(Update::Sequence(updates))
587                        }
588                        Binding::Custom(message) => Some(Update::Custom(message)),
589                    }
590                }
591
592                let update = apply_binding(key_binding(key_press)?, editor, self);
593
594                if let Some(focus) = &mut self.focus {
595                    focus.updated_at = Instant::now();
596                }
597
598                update
599            }
600            _ => None,
601        }
602    }
603
604    /// Returns the current [`InputMethod`] of the [`State`] for the given [`Editor`].
605    pub fn input_method<'a>(
606        &'a self,
607        editor: &impl Editor,
608        position: Point,
609    ) -> InputMethod<&'a str> {
610        let Some(Focus {
611            is_window_focused: true,
612            ..
613        }) = &self.focus
614        else {
615            return InputMethod::Disabled;
616        };
617
618        let translation = position - Point::ORIGIN;
619
620        let Some(selection) = editor.selection() else {
621            return InputMethod::Disabled;
622        };
623
624        let cursor = match selection {
625            Selection::Caret(position) => position,
626            Selection::Range(ranges) => ranges.first().cloned().unwrap_or_default().position(),
627        };
628
629        let line_height = editor.line_height().to_absolute(editor.text_size());
630
631        let position = cursor + translation;
632
633        InputMethod::Enabled {
634            cursor: Rectangle::new(position, Size::new(1.0, f32::from(line_height))),
635            purpose: input_method::Purpose::Normal,
636            preedit: self.preedit.as_ref().map(input_method::Preedit::as_ref),
637        }
638    }
639
640    /// Draws the given [`Editor`] with the current [`State`].
641    pub fn draw<Renderer: text::Renderer>(
642        &self,
643        editor: &Renderer::Editor,
644        renderer: &mut Renderer,
645        position: Point,
646        clip_bounds: Rectangle,
647        style: Style,
648    ) {
649        let bounds = Rectangle::new(position, editor.bounds());
650
651        let Some(clip_bounds) = clip_bounds.intersection(&bounds) else {
652            return;
653        };
654
655        if !editor.is_empty() {
656            renderer.fill_editor(editor, position, style.value, clip_bounds);
657        }
658
659        if !self.is_focused() {
660            return;
661        }
662
663        let translation = position - Point::ORIGIN;
664        let text_size = editor.text_size();
665        let line_height = editor.line_height();
666
667        let Some(selection) = editor.selection() else {
668            return;
669        };
670
671        match selection {
672            Selection::Caret(position) if self.is_cursor_visible() => {
673                let cursor = Rectangle::new(
674                    position + translation,
675                    Size::new(
676                        if renderer::CRISP {
677                            (1.0 / renderer.hint_factor().unwrap_or(1.0)).max(1.0)
678                        } else {
679                            1.0
680                        },
681                        line_height.to_absolute(text_size).into(),
682                    ),
683                );
684
685                if let Some(clipped_cursor) = clip_bounds.intersection(&cursor) {
686                    renderer.fill_quad(
687                        renderer::Quad {
688                            bounds: clipped_cursor,
689                            ..renderer::Quad::default()
690                        },
691                        style.value,
692                    );
693                }
694            }
695            Selection::Range(ranges) => {
696                for range in ranges
697                    .iter()
698                    .filter_map(|range| clip_bounds.intersection(&(*range + translation)))
699                {
700                    renderer.fill_quad(
701                        renderer::Quad {
702                            bounds: range.round(),
703                            ..renderer::Quad::default()
704                        },
705                        style.selection,
706                    );
707                }
708            }
709            Selection::Caret(_) => {
710                // Drawing an empty quad helps some renderers to track the damage of the blinking cursor
711                renderer.fill_quad(renderer::Quad::default(), Color::TRANSPARENT);
712            }
713        }
714    }
715
716    /// Returns whether the cursor of the [`Editor`] is visible.
717    pub fn is_cursor_visible(&self) -> bool {
718        self.focus.as_ref().is_some_and(Focus::is_cursor_visible)
719    }
720}
721
722/// The visual style of an [`Editor`].
723pub struct Style {
724    /// The [`Color`] of the contents.
725    pub value: Color,
726
727    /// The background [`Color`] of any selection.
728    pub selection: Color,
729}
730
731#[derive(Debug, Clone)]
732struct Focus {
733    updated_at: Instant,
734    now: Instant,
735    is_window_focused: bool,
736}
737
738impl Focus {
739    const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500;
740
741    fn now() -> Self {
742        let now = Instant::now();
743
744        Self {
745            updated_at: now,
746            now,
747            is_window_focused: true,
748        }
749    }
750
751    fn is_cursor_visible(&self) -> bool {
752        self.is_window_focused
753            && ((self.now - self.updated_at).as_millis() / Self::CURSOR_BLINK_INTERVAL_MILLIS)
754                .is_multiple_of(2)
755    }
756}
757
758impl State {
759    /// Returns whether the [`Editor`] is currently focused or not.
760    pub fn is_focused(&self) -> bool {
761        self.focus.is_some()
762    }
763}
764
765impl Focusable for State {
766    fn is_focused(&self) -> bool {
767        self.focus.is_some()
768    }
769
770    fn focus(&mut self) {
771        self.focus = Some(Focus::now());
772    }
773
774    fn unfocus(&mut self) {
775        self.focus = None;
776    }
777}
778
779/// A binding to an action in the [`Editor`].
780#[derive(Debug, Clone, PartialEq)]
781pub enum Binding<Message> {
782    /// Unfocus the [`Editor`].
783    Unfocus,
784    /// Copy the selection of the [`Editor`].
785    Copy,
786    /// Cut the selection of the [`Editor`].
787    Cut,
788    /// Paste the clipboard contents in the [`Editor`].
789    Paste,
790    /// Undo the last change peformed in the [`Editor`].
791    Undo,
792    /// Redo the last change undone in the [`Editor`].
793    Redo,
794    /// Apply a [`Motion`].
795    Move(Motion),
796    /// Select text with a given [`Motion`].
797    Select(Motion),
798    /// Select the word at the current cursor.
799    SelectWord,
800    /// Select the line at the current cursor.
801    SelectLine,
802    /// Select the entire buffer.
803    SelectAll,
804    /// Insert the given character.
805    Insert(char),
806    /// Break the current line.
807    Enter,
808    /// Delete the previous character.
809    Backspace,
810    /// Delete the word before the cursor.
811    BackspaceWord,
812    /// Delete the line before the cursor.
813    BackspaceLine,
814    /// Delete the next character.
815    Delete,
816    /// Delete the word after the cursor.
817    DeleteWord,
818    /// Delete the line after the cursor.
819    DeleteLine,
820    /// A sequence of bindings to execute.
821    Sequence(Vec<Self>),
822    /// Produce the given message.
823    Custom(Message),
824}
825
826/// A key press.
827#[derive(Debug, Clone, PartialEq, Eq)]
828pub struct KeyPress {
829    /// The original key pressed without modifiers applied to it.
830    ///
831    /// You should use this key for combinations (e.g. Ctrl+C).
832    pub key: keyboard::Key,
833    /// The key pressed with modifiers applied to it.
834    ///
835    /// You should use this key for any single key bindings (e.g. motions).
836    pub modified_key: keyboard::Key,
837    /// The physical key pressed.
838    ///
839    /// You should use this key for layout-independent bindings.
840    pub physical_key: keyboard::key::Physical,
841    /// The state of the keyboard modifiers.
842    pub modifiers: keyboard::Modifiers,
843    /// The text produced by the key press.
844    pub text: Option<SmolStr>,
845    /// Whether the [`Editor`] is focused.
846    pub is_focused: bool,
847}
848
849impl<Message> Binding<Message> {
850    /// Returns the default [`Binding`] for the given key press.
851    pub fn from_key_press(event: KeyPress) -> Option<Self> {
852        let KeyPress {
853            key,
854            modified_key,
855            physical_key,
856            modifiers,
857            text,
858            is_focused,
859        } = event;
860
861        if !is_focused {
862            return None;
863        }
864
865        let combination = match key.to_latin(physical_key) {
866            Some('c') if modifiers.command() => Some(Self::Copy),
867            Some('x') if modifiers.command() => Some(Self::Cut),
868            Some('v') if modifiers.command() && !modifiers.alt() => Some(Self::Paste),
869            Some('a') if modifiers.command() => Some(Self::SelectAll),
870            Some('z') if modifiers.command() => Some(Self::Undo),
871            Some('y') if modifiers.command() => Some(Self::Redo),
872            _ => None,
873        };
874
875        if let Some(binding) = combination {
876            return Some(binding);
877        }
878
879        #[cfg(target_os = "macos")]
880        let modified_key = convert_macos_shortcut(&key, modifiers).unwrap_or(modified_key);
881
882        match modified_key.as_ref() {
883            keyboard::Key::Named(key::Named::Enter) => Some(Self::Enter),
884            keyboard::Key::Named(key::Named::Backspace) => Some(
885                if modifiers.macos_command() || (modifiers.command() && modifiers.shift()) {
886                    Self::BackspaceLine
887                } else if modifiers.jump() {
888                    Self::BackspaceWord
889                } else {
890                    Self::Backspace
891                },
892            ),
893            keyboard::Key::Named(key::Named::Delete)
894                if text.is_none() || text.as_deref() == Some("\u{7f}") =>
895            {
896                Some(
897                    if modifiers.macos_command() || (modifiers.command() && modifiers.shift()) {
898                        Self::DeleteLine
899                    } else if modifiers.jump() {
900                        Self::DeleteWord
901                    } else {
902                        Self::Delete
903                    },
904                )
905            }
906            keyboard::Key::Named(key::Named::Escape) => Some(Self::Unfocus),
907            _ => {
908                if let Some(text) = text {
909                    let c = text.chars().find(|c| !c.is_control())?;
910
911                    Some(Self::Insert(c))
912                } else if let keyboard::Key::Named(named_key) = key.as_ref() {
913                    let motion = motion(named_key)?;
914
915                    let motion = if modifiers.macos_command() {
916                        match motion {
917                            Motion::Left => Motion::Home,
918                            Motion::Right => Motion::End,
919                            _ => motion,
920                        }
921                    } else {
922                        motion
923                    };
924
925                    let motion = if modifiers.jump() {
926                        motion.widen()
927                    } else {
928                        motion
929                    };
930
931                    Some(if modifiers.shift() {
932                        Self::Select(motion)
933                    } else {
934                        Self::Move(motion)
935                    })
936                } else {
937                    None
938                }
939            }
940        }
941    }
942}
943
944/// The update of an [`Editor`], returned by [`State::update`].
945pub enum Update<Message> {
946    /// An [`Action`] must be performed in the [`Editor`].
947    Action(Action),
948    /// The [`Editor`] just gained focus.
949    Focus,
950    /// The [`Editor`] just lost focus.
951    Unfocus,
952    /// The [`Editor`] changed its [`InputMethod`].
953    InputMethod,
954    /// A mouse press was released in the [`Editor`].
955    Release,
956    /// The [`Editor`] must copy some text to the clipboard.
957    Copy(String),
958    /// The [`Editor`] must paste the clipboard contents.
959    Paste,
960    /// The [`Editor`] must be redrawn at the given [`Instant`].
961    RedrawAt(Instant),
962    /// The [`Editor`] produced a custom message that must be published.
963    Custom(Message),
964    /// The [`Editor`] produced a sequence of updates.
965    Sequence(Vec<Update<Message>>),
966}
967
968fn motion(key: key::Named) -> Option<Motion> {
969    match key {
970        key::Named::ArrowLeft => Some(Motion::Left),
971        key::Named::ArrowRight => Some(Motion::Right),
972        key::Named::ArrowUp => Some(Motion::Up),
973        key::Named::ArrowDown => Some(Motion::Down),
974        key::Named::Home => Some(Motion::Home),
975        key::Named::End => Some(Motion::End),
976        key::Named::PageUp => Some(Motion::PageUp),
977        key::Named::PageDown => Some(Motion::PageDown),
978        _ => None,
979    }
980}
981
982#[cfg(target_os = "macos")]
983fn convert_macos_shortcut(
984    key: &keyboard::Key,
985    modifiers: keyboard::Modifiers,
986) -> Option<keyboard::Key> {
987    if modifiers != keyboard::Modifiers::CTRL {
988        return None;
989    }
990
991    let key = match key.as_ref() {
992        keyboard::Key::Character("b") => key::Named::ArrowLeft,
993        keyboard::Key::Character("f") => key::Named::ArrowRight,
994        keyboard::Key::Character("a") => key::Named::Home,
995        keyboard::Key::Character("e") => key::Named::End,
996        keyboard::Key::Character("h") => key::Named::Backspace,
997        keyboard::Key::Character("d") => key::Named::Delete,
998        _ => return None,
999    };
1000
1001    Some(keyboard::Key::Named(key))
1002}
1003
1004impl<T: Editor> TextInput for T {
1005    fn text(&self) -> text::Fragment<'_> {
1006        text::Fragment::Owned(Editor::text(self))
1007    }
1008
1009    fn move_cursor_to_front(&mut self) {
1010        self.perform(Action::Move(Motion::DocumentStart));
1011    }
1012
1013    fn move_cursor_to_end(&mut self) {
1014        self.perform(Action::Move(Motion::DocumentEnd));
1015    }
1016
1017    fn move_cursor_to(&mut self, position: text::Position) {
1018        self.move_to(Cursor {
1019            position,
1020            selection: None,
1021        });
1022    }
1023
1024    fn select_all(&mut self) {
1025        self.perform(Action::SelectAll);
1026    }
1027
1028    fn select_range(&mut self, start: text::Position, end: text::Position) {
1029        self.move_to(Cursor {
1030            position: start,
1031            selection: Some(end),
1032        });
1033    }
1034}