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`].
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: 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(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 = 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 cursor = match editor.selection() {
621            Selection::Caret(position) => position,
622            Selection::Range(ranges) => ranges.first().cloned().unwrap_or_default().position(),
623        };
624
625        let line_height = editor.line_height().to_absolute(editor.text_size());
626
627        let position = cursor + translation;
628
629        InputMethod::Enabled {
630            cursor: Rectangle::new(position, Size::new(1.0, f32::from(line_height))),
631            purpose: input_method::Purpose::Normal,
632            preedit: self.preedit.as_ref().map(input_method::Preedit::as_ref),
633        }
634    }
635
636    /// Draws the given [`Editor`] with the current [`State`].
637    pub fn draw<Renderer: text::Renderer>(
638        &self,
639        editor: &Renderer::Editor,
640        renderer: &mut Renderer,
641        position: Point,
642        clip_bounds: Rectangle,
643        style: Style,
644    ) {
645        let bounds = Rectangle::new(position, editor.bounds());
646
647        let Some(clip_bounds) = clip_bounds.intersection(&bounds) else {
648            return;
649        };
650
651        if !editor.is_empty() {
652            renderer.fill_editor(editor, position, style.value, clip_bounds);
653        }
654
655        if !self.is_focused() {
656            return;
657        }
658
659        let translation = position - Point::ORIGIN;
660        let text_size = editor.text_size();
661        let line_height = editor.line_height();
662
663        match editor.selection() {
664            Selection::Caret(position) if self.is_cursor_visible() => {
665                let cursor = Rectangle::new(
666                    position + translation,
667                    Size::new(
668                        if renderer::CRISP {
669                            (1.0 / renderer.hint_factor().unwrap_or(1.0)).max(1.0)
670                        } else {
671                            1.0
672                        },
673                        line_height.to_absolute(text_size).into(),
674                    ),
675                );
676
677                if let Some(clipped_cursor) = clip_bounds.intersection(&cursor) {
678                    renderer.fill_quad(
679                        renderer::Quad {
680                            bounds: clipped_cursor,
681                            ..renderer::Quad::default()
682                        },
683                        style.value,
684                    );
685                }
686            }
687            Selection::Range(ranges) => {
688                for range in ranges
689                    .into_iter()
690                    .filter_map(|range| clip_bounds.intersection(&(range + translation)))
691                {
692                    renderer.fill_quad(
693                        renderer::Quad {
694                            bounds: range.round(),
695                            ..renderer::Quad::default()
696                        },
697                        style.selection,
698                    );
699                }
700            }
701            Selection::Caret(_) => {
702                // Drawing an empty quad helps some renderers to track the damage of the blinking cursor
703                renderer.fill_quad(renderer::Quad::default(), Color::TRANSPARENT);
704            }
705        }
706    }
707
708    /// Returns whether the cursor of the [`Editor`] is visible.
709    pub fn is_cursor_visible(&self) -> bool {
710        self.focus.as_ref().is_some_and(Focus::is_cursor_visible)
711    }
712}
713
714/// The visual style of an [`Editor`].
715pub struct Style {
716    /// The [`Color`] of the contents.
717    pub value: Color,
718
719    /// The background [`Color`] of any selection.
720    pub selection: Color,
721}
722
723#[derive(Debug, Clone)]
724struct Focus {
725    updated_at: Instant,
726    now: Instant,
727    is_window_focused: bool,
728}
729
730impl Focus {
731    const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500;
732
733    fn now() -> Self {
734        let now = Instant::now();
735
736        Self {
737            updated_at: now,
738            now,
739            is_window_focused: true,
740        }
741    }
742
743    fn is_cursor_visible(&self) -> bool {
744        self.is_window_focused
745            && ((self.now - self.updated_at).as_millis() / Self::CURSOR_BLINK_INTERVAL_MILLIS)
746                .is_multiple_of(2)
747    }
748}
749
750impl State {
751    /// Returns whether the [`Editor`] is currently focused or not.
752    pub fn is_focused(&self) -> bool {
753        self.focus.is_some()
754    }
755}
756
757impl Focusable for State {
758    fn is_focused(&self) -> bool {
759        self.focus.is_some()
760    }
761
762    fn focus(&mut self) {
763        self.focus = Some(Focus::now());
764    }
765
766    fn unfocus(&mut self) {
767        self.focus = None;
768    }
769}
770
771/// A binding to an action in the [`Editor`].
772#[derive(Debug, Clone, PartialEq)]
773pub enum Binding<Message> {
774    /// Unfocus the [`Editor`].
775    Unfocus,
776    /// Copy the selection of the [`Editor`].
777    Copy,
778    /// Cut the selection of the [`Editor`].
779    Cut,
780    /// Paste the clipboard contents in the [`Editor`].
781    Paste,
782    /// Undo the last change peformed in the [`Editor`].
783    Undo,
784    /// Redo the last change undone in the [`Editor`].
785    Redo,
786    /// Apply a [`Motion`].
787    Move(Motion),
788    /// Select text with a given [`Motion`].
789    Select(Motion),
790    /// Select the word at the current cursor.
791    SelectWord,
792    /// Select the line at the current cursor.
793    SelectLine,
794    /// Select the entire buffer.
795    SelectAll,
796    /// Insert the given character.
797    Insert(char),
798    /// Break the current line.
799    Enter,
800    /// Delete the previous character.
801    Backspace,
802    /// Delete the word before the cursor.
803    BackspaceWord,
804    /// Delete the line before the cursor.
805    BackspaceLine,
806    /// Delete the next character.
807    Delete,
808    /// Delete the word after the cursor.
809    DeleteWord,
810    /// Delete the line after the cursor.
811    DeleteLine,
812    /// A sequence of bindings to execute.
813    Sequence(Vec<Self>),
814    /// Produce the given message.
815    Custom(Message),
816}
817
818/// A key press.
819#[derive(Debug, Clone, PartialEq, Eq)]
820pub struct KeyPress {
821    /// The original key pressed without modifiers applied to it.
822    ///
823    /// You should use this key for combinations (e.g. Ctrl+C).
824    pub key: keyboard::Key,
825    /// The key pressed with modifiers applied to it.
826    ///
827    /// You should use this key for any single key bindings (e.g. motions).
828    pub modified_key: keyboard::Key,
829    /// The physical key pressed.
830    ///
831    /// You should use this key for layout-independent bindings.
832    pub physical_key: keyboard::key::Physical,
833    /// The state of the keyboard modifiers.
834    pub modifiers: keyboard::Modifiers,
835    /// The text produced by the key press.
836    pub text: Option<SmolStr>,
837    /// Whether the [`Editor`] is focused.
838    pub is_focused: bool,
839}
840
841impl<Message> Binding<Message> {
842    /// Returns the default [`Binding`] for the given key press.
843    pub fn from_key_press(event: KeyPress) -> Option<Self> {
844        let KeyPress {
845            key,
846            modified_key,
847            physical_key,
848            modifiers,
849            text,
850            is_focused,
851        } = event;
852
853        if !is_focused {
854            return None;
855        }
856
857        let combination = match key.to_latin(physical_key) {
858            Some('c') if modifiers.command() => Some(Self::Copy),
859            Some('x') if modifiers.command() => Some(Self::Cut),
860            Some('v') if modifiers.command() && !modifiers.alt() => Some(Self::Paste),
861            Some('a') if modifiers.command() => Some(Self::SelectAll),
862            Some('z') if modifiers.command() => Some(Self::Undo),
863            Some('y') if modifiers.command() => Some(Self::Redo),
864            _ => None,
865        };
866
867        if let Some(binding) = combination {
868            return Some(binding);
869        }
870
871        #[cfg(target_os = "macos")]
872        let modified_key = convert_macos_shortcut(&key, modifiers).unwrap_or(modified_key);
873
874        match modified_key.as_ref() {
875            keyboard::Key::Named(key::Named::Enter) => Some(Self::Enter),
876            keyboard::Key::Named(key::Named::Backspace) => Some(
877                if modifiers.macos_command() || (modifiers.command() && modifiers.shift()) {
878                    Self::BackspaceLine
879                } else if modifiers.jump() {
880                    Self::BackspaceWord
881                } else {
882                    Self::Backspace
883                },
884            ),
885            keyboard::Key::Named(key::Named::Delete)
886                if text.is_none() || text.as_deref() == Some("\u{7f}") =>
887            {
888                Some(
889                    if modifiers.macos_command() || (modifiers.command() && modifiers.shift()) {
890                        Self::DeleteLine
891                    } else if modifiers.jump() {
892                        Self::DeleteWord
893                    } else {
894                        Self::Delete
895                    },
896                )
897            }
898            keyboard::Key::Named(key::Named::Escape) => Some(Self::Unfocus),
899            _ => {
900                if let Some(text) = text {
901                    let c = text.chars().find(|c| !c.is_control())?;
902
903                    Some(Self::Insert(c))
904                } else if let keyboard::Key::Named(named_key) = key.as_ref() {
905                    let motion = motion(named_key)?;
906
907                    let motion = if modifiers.macos_command() {
908                        match motion {
909                            Motion::Left => Motion::Home,
910                            Motion::Right => Motion::End,
911                            _ => motion,
912                        }
913                    } else {
914                        motion
915                    };
916
917                    let motion = if modifiers.jump() {
918                        motion.widen()
919                    } else {
920                        motion
921                    };
922
923                    Some(if modifiers.shift() {
924                        Self::Select(motion)
925                    } else {
926                        Self::Move(motion)
927                    })
928                } else {
929                    None
930                }
931            }
932        }
933    }
934}
935
936/// The update of an [`Editor`], returned by [`State::update`].
937pub enum Update<Message> {
938    /// An [`Action`] must be performed in the [`Editor`].
939    Action(Action),
940    /// The [`Editor`] just gained focus.
941    Focus,
942    /// The [`Editor`] just lost focus.
943    Unfocus,
944    /// The [`Editor`] changed its [`InputMethod`].
945    InputMethod,
946    /// A mouse press was released in the [`Editor`].
947    Release,
948    /// The [`Editor`] must copy some text to the clipboard.
949    Copy(String),
950    /// The [`Editor`] must paste the clipboard contents.
951    Paste,
952    /// The [`Editor`] must be redrawn at the given [`Instant`].
953    RedrawAt(Instant),
954    /// The [`Editor`] produced a custom message that must be published.
955    Custom(Message),
956    /// The [`Editor`] produced a sequence of updates.
957    Sequence(Vec<Update<Message>>),
958}
959
960fn motion(key: key::Named) -> Option<Motion> {
961    match key {
962        key::Named::ArrowLeft => Some(Motion::Left),
963        key::Named::ArrowRight => Some(Motion::Right),
964        key::Named::ArrowUp => Some(Motion::Up),
965        key::Named::ArrowDown => Some(Motion::Down),
966        key::Named::Home => Some(Motion::Home),
967        key::Named::End => Some(Motion::End),
968        key::Named::PageUp => Some(Motion::PageUp),
969        key::Named::PageDown => Some(Motion::PageDown),
970        _ => None,
971    }
972}
973
974#[cfg(target_os = "macos")]
975fn convert_macos_shortcut(
976    key: &keyboard::Key,
977    modifiers: keyboard::Modifiers,
978) -> Option<keyboard::Key> {
979    if modifiers != keyboard::Modifiers::CTRL {
980        return None;
981    }
982
983    let key = match key.as_ref() {
984        keyboard::Key::Character("b") => key::Named::ArrowLeft,
985        keyboard::Key::Character("f") => key::Named::ArrowRight,
986        keyboard::Key::Character("a") => key::Named::Home,
987        keyboard::Key::Character("e") => key::Named::End,
988        keyboard::Key::Character("h") => key::Named::Backspace,
989        keyboard::Key::Character("d") => key::Named::Delete,
990        _ => return None,
991    };
992
993    Some(keyboard::Key::Named(key))
994}
995
996impl<T: Editor> TextInput for T {
997    fn text(&self) -> text::Fragment<'_> {
998        text::Fragment::Owned(Editor::text(self))
999    }
1000
1001    fn move_cursor_to_front(&mut self) {
1002        self.perform(Action::Move(Motion::DocumentStart));
1003    }
1004
1005    fn move_cursor_to_end(&mut self) {
1006        self.perform(Action::Move(Motion::DocumentEnd));
1007    }
1008
1009    fn move_cursor_to(&mut self, position: text::Position) {
1010        self.move_to(Cursor {
1011            position,
1012            selection: None,
1013        });
1014    }
1015
1016    fn select_all(&mut self) {
1017        self.perform(Action::SelectAll);
1018    }
1019
1020    fn select_range(&mut self, start: text::Position, end: text::Position) {
1021        self.move_to(Cursor {
1022            position: start,
1023            selection: Some(end),
1024        });
1025    }
1026}