Skip to main content

editor/editor/
keys.rs

1//! The keymap: every action the editor answers to, and the chords bound to it.
2//!
3//! Separate from the surface because it is configuration rather than behaviour
4//! — an app that wants a different keymap replaces this call, not the editor.
5
6use gpui::{App, KeyBinding, actions};
7
8use crate::editor::{CONTEXT, image};
9
10actions!(
11    bezel_editor,
12    [
13        Backspace,
14        Delete,
15        KillLine,
16        DeleteWordLeft,
17        DeleteWordRight,
18        DeleteToHome,
19        Left,
20        Right,
21        Up,
22        Down,
23        Home,
24        End,
25        SelectLeft,
26        SelectRight,
27        SelectUp,
28        SelectDown,
29        SelectHome,
30        SelectEnd,
31        SelectAll,
32        WordLeft,
33        WordRight,
34        SelectWordLeft,
35        SelectWordRight,
36        SplitBlock,
37        Indent,
38        Outdent,
39        Dismiss,
40        Copy,
41        Cut,
42        Paste,
43        Undo,
44        Redo,
45        ToggleBold,
46        ToggleItalic,
47        ToggleStrike,
48        ToggleCode,
49        MoveBlockUp,
50        MoveBlockDown,
51        DuplicateBlock,
52        RemoveBlock,
53        ConfirmUrl,
54        CancelUrl,
55    ]
56);
57
58/// Install the editor's key bindings. Scoped to the editor's own key context,
59/// so binding `tab` here does not make `tab` mean "indent" for the whole app.
60///
61/// The chords are [`ui::input::TextField`]'s, because a document is not the place to
62/// invent a second set: what `alt-left` does in a search box is what a reader
63/// expects it to do here.
64pub fn init(cx: &mut App) {
65    let ctx = Some(CONTEXT);
66    cx.bind_keys([
67        KeyBinding::new("backspace", Backspace, ctx),
68        KeyBinding::new("delete", Delete, ctx),
69        KeyBinding::new("left", Left, ctx),
70        KeyBinding::new("right", Right, ctx),
71        KeyBinding::new("up", Up, ctx),
72        KeyBinding::new("down", Down, ctx),
73        KeyBinding::new("home", Home, ctx),
74        KeyBinding::new("end", End, ctx),
75        KeyBinding::new("shift-left", SelectLeft, ctx),
76        KeyBinding::new("shift-right", SelectRight, ctx),
77        KeyBinding::new("shift-up", SelectUp, ctx),
78        KeyBinding::new("shift-down", SelectDown, ctx),
79        KeyBinding::new("shift-home", SelectHome, ctx),
80        KeyBinding::new("shift-end", SelectEnd, ctx),
81        KeyBinding::new("enter", SplitBlock, ctx),
82        KeyBinding::new("tab", Indent, ctx),
83        KeyBinding::new("shift-tab", Outdent, ctx),
84        KeyBinding::new("escape", Dismiss, ctx),
85    ]);
86
87    // The URL prompt's own context, because the field holds focus while it is
88    // open and the document behind it must not answer the same two keys.
89    let prompt = Some(image::PROMPT_CONTEXT);
90    cx.bind_keys([
91        KeyBinding::new("enter", ConfirmUrl, prompt),
92        KeyBinding::new("escape", CancelUrl, prompt),
93    ]);
94
95    // `MoveBlockUp`, `MoveBlockDown`, `DuplicateBlock` and `RemoveBlock` are
96    // deliberately unbound. Every chord that fits is already taken by something
97    // standard — `cmd-shift-up`/`down` select to the ends of a document on
98    // macOS, `alt-up`/`down` move by paragraph — and shadowing one of those in
99    // a text surface is worse than reaching the block menu for it. They are
100    // actions so an app can bind what suits its own keymap.
101    //
102    // The emacs kill ring is unbound for the same reason and stays unbuilt with
103    // it: `ctrl-w`, `alt-w` and `ctrl-y` collide with `cmd-x`, `cmd-c`, `cmd-v`
104    // and `cmd-w`, so a kill deletes and `cmd-x` is how text travels.
105
106    #[cfg(target_os = "macos")]
107    cx.bind_keys([
108        KeyBinding::new("cmd-a", SelectAll, ctx),
109        KeyBinding::new("cmd-c", Copy, ctx),
110        KeyBinding::new("cmd-x", Cut, ctx),
111        KeyBinding::new("cmd-v", Paste, ctx),
112        KeyBinding::new("cmd-z", Undo, ctx),
113        KeyBinding::new("cmd-shift-z", Redo, ctx),
114        KeyBinding::new("cmd-b", ToggleBold, ctx),
115        KeyBinding::new("cmd-i", ToggleItalic, ctx),
116        KeyBinding::new("cmd-e", ToggleCode, ctx),
117        KeyBinding::new("cmd-shift-x", ToggleStrike, ctx),
118        // cmd = line, option = word: the macOS convention.
119        KeyBinding::new("cmd-left", Home, ctx),
120        KeyBinding::new("cmd-right", End, ctx),
121        KeyBinding::new("cmd-shift-left", SelectHome, ctx),
122        KeyBinding::new("cmd-shift-right", SelectEnd, ctx),
123        KeyBinding::new("alt-left", WordLeft, ctx),
124        KeyBinding::new("alt-right", WordRight, ctx),
125        KeyBinding::new("alt-shift-left", SelectWordLeft, ctx),
126        KeyBinding::new("alt-shift-right", SelectWordRight, ctx),
127        // The emacs bindings macOS honours in every native text field.
128        KeyBinding::new("ctrl-a", Home, ctx),
129        KeyBinding::new("ctrl-e", End, ctx),
130        KeyBinding::new("ctrl-b", Left, ctx),
131        KeyBinding::new("ctrl-f", Right, ctx),
132        KeyBinding::new("ctrl-n", Down, ctx),
133        KeyBinding::new("ctrl-p", Up, ctx),
134        KeyBinding::new("ctrl-h", Backspace, ctx),
135        KeyBinding::new("ctrl-d", Delete, ctx),
136        // `ctrl-k` is the one chord emacs and AppKit spell the same way; the
137        // rest are what option and cmd already mean for motion, deleting.
138        KeyBinding::new("ctrl-k", KillLine, ctx),
139        KeyBinding::new("alt-backspace", DeleteWordLeft, ctx),
140        KeyBinding::new("alt-delete", DeleteWordRight, ctx),
141        KeyBinding::new("cmd-backspace", DeleteToHome, ctx),
142    ]);
143
144    #[cfg(not(target_os = "macos"))]
145    cx.bind_keys([
146        KeyBinding::new("ctrl-a", SelectAll, ctx),
147        KeyBinding::new("ctrl-c", Copy, ctx),
148        KeyBinding::new("ctrl-x", Cut, ctx),
149        KeyBinding::new("ctrl-v", Paste, ctx),
150        KeyBinding::new("ctrl-z", Undo, ctx),
151        KeyBinding::new("ctrl-shift-z", Redo, ctx),
152        KeyBinding::new("ctrl-b", ToggleBold, ctx),
153        KeyBinding::new("ctrl-i", ToggleItalic, ctx),
154        KeyBinding::new("ctrl-e", ToggleCode, ctx),
155        KeyBinding::new("ctrl-shift-x", ToggleStrike, ctx),
156        // ctrl = word on Windows/Linux, where there is no line modifier.
157        KeyBinding::new("ctrl-left", WordLeft, ctx),
158        KeyBinding::new("ctrl-right", WordRight, ctx),
159        KeyBinding::new("ctrl-shift-left", SelectWordLeft, ctx),
160        KeyBinding::new("ctrl-shift-right", SelectWordRight, ctx),
161        KeyBinding::new("ctrl-backspace", DeleteWordLeft, ctx),
162        KeyBinding::new("ctrl-delete", DeleteWordRight, ctx),
163        // `ctrl-k` stays free here: GTK entries kill the line with it, Windows
164        // reads it as "insert link", and a chord with two meanings is one this
165        // library does not get to claim.
166    ]);
167}