Skip to main content

guise/input/
keys.rs

1//! Keyboard handling for single-line text fields.
2//!
3//! Shared by [`TextInput`](super::TextInput) and by hosts that drive a
4//! [`TextEdit`](super::TextEdit) directly — inline fields that render their own
5//! chrome (a search bar, a palette) rather than embedding the full component.
6//! macOS/Linux conventions: Option = word-wise, Cmd = line-wise, plus the
7//! Emacs-style Ctrl+A / Ctrl+E / Ctrl+K.
8
9use gpui::Keystroke;
10
11use super::edit::TextEdit;
12
13/// What a keystroke did to a single-line field, so the host can react.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum KeyOutcome {
16    /// Enter — the host should commit.
17    Submit,
18    /// Escape — the host should dismiss.
19    Cancel,
20    /// The field changed; redraw.
21    Edited,
22    /// Not handled here; the host may act on it (e.g. Tab, Cmd+W).
23    Pass,
24}
25
26/// Apply `ks` to `edit`, returning what the host should do. `platform` is Cmd
27/// on macOS; `alt` is Option.
28pub fn apply_key(edit: &mut TextEdit, ks: &Keystroke) -> KeyOutcome {
29    let m = &ks.modifiers;
30    match ks.key.as_str() {
31        "enter" => return KeyOutcome::Submit,
32        "escape" => return KeyOutcome::Cancel,
33        "left" => {
34            if m.platform {
35                edit.home();
36            } else if m.alt {
37                edit.word_left();
38            } else {
39                edit.left();
40            }
41            return KeyOutcome::Edited;
42        }
43        "right" => {
44            if m.platform {
45                edit.end();
46            } else if m.alt {
47                edit.word_right();
48            } else {
49                edit.right();
50            }
51            return KeyOutcome::Edited;
52        }
53        // Single-line: vertical keys collapse to the line edges.
54        "up" | "home" => {
55            edit.home();
56            return KeyOutcome::Edited;
57        }
58        "down" | "end" => {
59            edit.end();
60            return KeyOutcome::Edited;
61        }
62        "backspace" => {
63            if m.platform {
64                edit.delete_to_start();
65            } else if m.alt {
66                edit.delete_word_back();
67            } else {
68                edit.backspace();
69            }
70            return KeyOutcome::Edited;
71        }
72        "delete" => {
73            if m.platform {
74                edit.delete_to_end();
75            } else if m.alt {
76                edit.delete_word_forward();
77            } else {
78                edit.delete();
79            }
80            return KeyOutcome::Edited;
81        }
82        "k" if m.control => {
83            edit.delete_to_end();
84            return KeyOutcome::Edited;
85        }
86        "a" if m.control => {
87            edit.home();
88            return KeyOutcome::Edited;
89        }
90        "e" if m.control => {
91            edit.end();
92            return KeyOutcome::Edited;
93        }
94        _ => {}
95    }
96    // Printable input: never on Cmd/Ctrl chords (those are shortcuts);
97    // Option+key is allowed so composed glyphs land.
98    if !m.platform && !m.control {
99        if let Some(t) = ks.key_char.as_deref().filter(|t| !t.is_empty()) {
100            edit.insert(t);
101            return KeyOutcome::Edited;
102        }
103    }
104    KeyOutcome::Pass
105}