Skip to main content

typ_core/
action.rs

1//! The named vocabulary of editing operations.
2//!
3//! Every editing primitive in TYPE is an `Action` with explicit arguments.
4//! Three consumers depend on that: the keymap, the command palette, and the
5//! opt-in vim layer. A primitive reachable only from a `handle_key` arm is
6//! invisible to all three, so no key handler may mutate a buffer directly.
7
8/// Which way an operation runs.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub enum Direction {
11    Backward,
12    Forward,
13}
14
15/// Where a motion lands.
16///
17/// Motions carry no "extend" flag themselves — that is an argument of
18/// `Action::Move`, so every motion is automatically available in both forms
19/// rather than being listed twice and drifting apart.
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
21pub enum Motion {
22    Left,
23    Right,
24    Up,
25    Down,
26    WordLeft,
27    WordRight,
28    LineStart,
29    LineEnd,
30    PageUp,
31    PageDown,
32    DocumentStart,
33    DocumentEnd,
34}
35
36impl Motion {
37    pub const ALL: &'static [Motion] = &[
38        Motion::Left,
39        Motion::Right,
40        Motion::Up,
41        Motion::Down,
42        Motion::WordLeft,
43        Motion::WordRight,
44        Motion::LineStart,
45        Motion::LineEnd,
46        Motion::PageUp,
47        Motion::PageDown,
48        Motion::DocumentStart,
49        Motion::DocumentEnd,
50    ];
51}
52
53/// A named editing operation.
54///
55/// `InsertChar` is deliberately absent from `ALL` and unnameable: typed text
56/// arrives as a key event, not as a binding, and a bindable "insert this
57/// character" would be a text-substitution feature wearing a keybinding's
58/// clothes.
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
60pub enum Action {
61    Move { motion: Motion, extend: bool },
62    Delete { direction: Direction, by_word: bool },
63    InsertNewline,
64    InsertChar(char),
65    Undo,
66    Redo,
67    SelectAll,
68    SelectLine,
69    CollapseSelections,
70    AddCursor(Direction),
71    Save,
72    Quit,
73    FocusNext,
74    SearchOpen,
75    SearchNext,
76    SearchPrevious,
77    ReplaceOpen,
78}
79
80impl Action {
81    /// Every action a config file may name, in a stable order.
82    pub const ALL: &'static [Action] = &[
83        Action::Move {
84            motion: Motion::Left,
85            extend: false,
86        },
87        Action::Move {
88            motion: Motion::Left,
89            extend: true,
90        },
91        Action::Move {
92            motion: Motion::Right,
93            extend: false,
94        },
95        Action::Move {
96            motion: Motion::Right,
97            extend: true,
98        },
99        Action::Move {
100            motion: Motion::Up,
101            extend: false,
102        },
103        Action::Move {
104            motion: Motion::Up,
105            extend: true,
106        },
107        Action::Move {
108            motion: Motion::Down,
109            extend: false,
110        },
111        Action::Move {
112            motion: Motion::Down,
113            extend: true,
114        },
115        Action::Move {
116            motion: Motion::WordLeft,
117            extend: false,
118        },
119        Action::Move {
120            motion: Motion::WordLeft,
121            extend: true,
122        },
123        Action::Move {
124            motion: Motion::WordRight,
125            extend: false,
126        },
127        Action::Move {
128            motion: Motion::WordRight,
129            extend: true,
130        },
131        Action::Move {
132            motion: Motion::LineStart,
133            extend: false,
134        },
135        Action::Move {
136            motion: Motion::LineStart,
137            extend: true,
138        },
139        Action::Move {
140            motion: Motion::LineEnd,
141            extend: false,
142        },
143        Action::Move {
144            motion: Motion::LineEnd,
145            extend: true,
146        },
147        Action::Move {
148            motion: Motion::PageUp,
149            extend: false,
150        },
151        Action::Move {
152            motion: Motion::PageUp,
153            extend: true,
154        },
155        Action::Move {
156            motion: Motion::PageDown,
157            extend: false,
158        },
159        Action::Move {
160            motion: Motion::PageDown,
161            extend: true,
162        },
163        Action::Move {
164            motion: Motion::DocumentStart,
165            extend: false,
166        },
167        Action::Move {
168            motion: Motion::DocumentStart,
169            extend: true,
170        },
171        Action::Move {
172            motion: Motion::DocumentEnd,
173            extend: false,
174        },
175        Action::Move {
176            motion: Motion::DocumentEnd,
177            extend: true,
178        },
179        Action::Delete {
180            direction: Direction::Backward,
181            by_word: false,
182        },
183        Action::Delete {
184            direction: Direction::Backward,
185            by_word: true,
186        },
187        Action::Delete {
188            direction: Direction::Forward,
189            by_word: false,
190        },
191        Action::Delete {
192            direction: Direction::Forward,
193            by_word: true,
194        },
195        Action::InsertNewline,
196        Action::Undo,
197        Action::Redo,
198        Action::SelectAll,
199        Action::SelectLine,
200        Action::CollapseSelections,
201        Action::AddCursor(Direction::Backward),
202        Action::AddCursor(Direction::Forward),
203        Action::Save,
204        Action::Quit,
205        Action::FocusNext,
206        Action::SearchOpen,
207        Action::SearchNext,
208        Action::SearchPrevious,
209        Action::ReplaceOpen,
210    ];
211
212    pub fn name(&self) -> &'static str {
213        match self {
214            // The name is a compile-time pairing rather than a runtime
215            // `format!`, so it returns `&'static str` and compares without
216            // allocating on every keymap lookup.
217            Action::Move { motion, extend } => match (motion, extend) {
218                (Motion::Left, false) => "move_left",
219                (Motion::Left, true) => "extend_left",
220                (Motion::Right, false) => "move_right",
221                (Motion::Right, true) => "extend_right",
222                (Motion::Up, false) => "move_up",
223                (Motion::Up, true) => "extend_up",
224                (Motion::Down, false) => "move_down",
225                (Motion::Down, true) => "extend_down",
226                (Motion::WordLeft, false) => "move_word_left",
227                (Motion::WordLeft, true) => "extend_word_left",
228                (Motion::WordRight, false) => "move_word_right",
229                (Motion::WordRight, true) => "extend_word_right",
230                (Motion::LineStart, false) => "move_line_start",
231                (Motion::LineStart, true) => "extend_line_start",
232                (Motion::LineEnd, false) => "move_line_end",
233                (Motion::LineEnd, true) => "extend_line_end",
234                (Motion::PageUp, false) => "move_page_up",
235                (Motion::PageUp, true) => "extend_page_up",
236                (Motion::PageDown, false) => "move_page_down",
237                (Motion::PageDown, true) => "extend_page_down",
238                (Motion::DocumentStart, false) => "move_document_start",
239                (Motion::DocumentStart, true) => "extend_document_start",
240                (Motion::DocumentEnd, false) => "move_document_end",
241                (Motion::DocumentEnd, true) => "extend_document_end",
242            },
243            Action::Delete {
244                direction: Direction::Backward,
245                by_word: false,
246            } => "delete_backward",
247            Action::Delete {
248                direction: Direction::Backward,
249                by_word: true,
250            } => "delete_word_backward",
251            Action::Delete {
252                direction: Direction::Forward,
253                by_word: false,
254            } => "delete_forward",
255            Action::Delete {
256                direction: Direction::Forward,
257                by_word: true,
258            } => "delete_word_forward",
259            Action::InsertNewline => "insert_newline",
260            // Never returned by `from_name`; see the type docs.
261            Action::InsertChar(_) => "insert_char_literal",
262            Action::Undo => "undo",
263            Action::Redo => "redo",
264            Action::SelectAll => "select_all",
265            Action::SelectLine => "select_line",
266            Action::CollapseSelections => "collapse_selections",
267            Action::AddCursor(Direction::Backward) => "add_cursor_above",
268            Action::AddCursor(Direction::Forward) => "add_cursor_below",
269            Action::Save => "save",
270            Action::Quit => "quit",
271            Action::FocusNext => "focus_next",
272            Action::SearchOpen => "search_open",
273            Action::SearchNext => "search_next",
274            Action::SearchPrevious => "search_previous",
275            Action::ReplaceOpen => "replace_open",
276        }
277    }
278
279    /// Look an action up by the name a config file uses.
280    ///
281    /// Linear over ~40 entries and called once per keymap load, never per
282    /// keypress — a map would be more code for no measurable gain.
283    pub fn from_name(name: &str) -> Option<Action> {
284        Action::ALL.iter().copied().find(|a| a.name() == name)
285    }
286}