1use std::collections::BTreeMap;
8
9use anyhow::{Context, Result, anyhow};
10
11use crate::{Action, Direction, KeyChord, Motion};
12
13#[derive(Debug, Clone)]
14pub struct Keymap {
15 bindings: BTreeMap<String, Action>,
19}
20
21const DEFAULTS: &[(&str, Action)] = &[
24 (
25 "left",
26 Action::Move {
27 motion: Motion::Left,
28 extend: false,
29 },
30 ),
31 (
32 "shift+left",
33 Action::Move {
34 motion: Motion::Left,
35 extend: true,
36 },
37 ),
38 (
39 "right",
40 Action::Move {
41 motion: Motion::Right,
42 extend: false,
43 },
44 ),
45 (
46 "shift+right",
47 Action::Move {
48 motion: Motion::Right,
49 extend: true,
50 },
51 ),
52 (
53 "up",
54 Action::Move {
55 motion: Motion::Up,
56 extend: false,
57 },
58 ),
59 (
60 "shift+up",
61 Action::Move {
62 motion: Motion::Up,
63 extend: true,
64 },
65 ),
66 (
67 "down",
68 Action::Move {
69 motion: Motion::Down,
70 extend: false,
71 },
72 ),
73 (
74 "shift+down",
75 Action::Move {
76 motion: Motion::Down,
77 extend: true,
78 },
79 ),
80 (
81 "ctrl+left",
82 Action::Move {
83 motion: Motion::WordLeft,
84 extend: false,
85 },
86 ),
87 (
88 "ctrl+shift+left",
89 Action::Move {
90 motion: Motion::WordLeft,
91 extend: true,
92 },
93 ),
94 (
95 "ctrl+right",
96 Action::Move {
97 motion: Motion::WordRight,
98 extend: false,
99 },
100 ),
101 (
102 "ctrl+shift+right",
103 Action::Move {
104 motion: Motion::WordRight,
105 extend: true,
106 },
107 ),
108 (
109 "home",
110 Action::Move {
111 motion: Motion::LineStart,
112 extend: false,
113 },
114 ),
115 (
116 "shift+home",
117 Action::Move {
118 motion: Motion::LineStart,
119 extend: true,
120 },
121 ),
122 (
123 "end",
124 Action::Move {
125 motion: Motion::LineEnd,
126 extend: false,
127 },
128 ),
129 (
130 "shift+end",
131 Action::Move {
132 motion: Motion::LineEnd,
133 extend: true,
134 },
135 ),
136 (
137 "pageup",
138 Action::Move {
139 motion: Motion::PageUp,
140 extend: false,
141 },
142 ),
143 (
144 "shift+pageup",
145 Action::Move {
146 motion: Motion::PageUp,
147 extend: true,
148 },
149 ),
150 (
151 "pagedown",
152 Action::Move {
153 motion: Motion::PageDown,
154 extend: false,
155 },
156 ),
157 (
158 "shift+pagedown",
159 Action::Move {
160 motion: Motion::PageDown,
161 extend: true,
162 },
163 ),
164 (
165 "ctrl+home",
166 Action::Move {
167 motion: Motion::DocumentStart,
168 extend: false,
169 },
170 ),
171 (
172 "ctrl+shift+home",
173 Action::Move {
174 motion: Motion::DocumentStart,
175 extend: true,
176 },
177 ),
178 (
179 "ctrl+end",
180 Action::Move {
181 motion: Motion::DocumentEnd,
182 extend: false,
183 },
184 ),
185 (
186 "ctrl+shift+end",
187 Action::Move {
188 motion: Motion::DocumentEnd,
189 extend: true,
190 },
191 ),
192 (
193 "backspace",
194 Action::Delete {
195 direction: Direction::Backward,
196 by_word: false,
197 },
198 ),
199 (
200 "ctrl+backspace",
201 Action::Delete {
202 direction: Direction::Backward,
203 by_word: true,
204 },
205 ),
206 (
207 "delete",
208 Action::Delete {
209 direction: Direction::Forward,
210 by_word: false,
211 },
212 ),
213 (
214 "ctrl+delete",
215 Action::Delete {
216 direction: Direction::Forward,
217 by_word: true,
218 },
219 ),
220 ("enter", Action::InsertNewline),
221 ("ctrl+z", Action::Undo),
222 ("ctrl+y", Action::Redo),
223 ("ctrl+a", Action::SelectAll),
224 ("ctrl+l", Action::SelectLine),
225 ("esc", Action::CollapseSelections),
226 ("ctrl+alt+up", Action::AddCursor(Direction::Backward)),
227 ("ctrl+alt+down", Action::AddCursor(Direction::Forward)),
228 ("ctrl+s", Action::Save),
229 ("ctrl+q", Action::Quit),
230 ("tab", Action::FocusNext),
231 ("ctrl+f", Action::SearchOpen),
232 ("f3", Action::SearchNext),
233 ("shift+f3", Action::SearchPrevious),
234 ("ctrl+h", Action::ReplaceOpen),
235];
236
237impl Keymap {
238 pub fn default_bindings() -> Self {
239 Self {
240 bindings: DEFAULTS
241 .iter()
242 .map(|(chord, action)| ((*chord).to_string(), *action))
243 .collect(),
244 }
245 }
246
247 pub fn lookup(&self, chord: &KeyChord) -> Option<Action> {
248 self.bindings.get(&chord.canonical).copied()
249 }
250
251 pub fn bindings_for(&self, action: Action) -> Vec<&str> {
253 self.bindings
254 .iter()
255 .filter(|(_, a)| **a == action)
256 .map(|(chord, _)| chord.as_str())
257 .collect()
258 }
259
260 pub fn merge_toml(&mut self, src: &str) -> Result<()> {
266 let table: BTreeMap<String, String> =
267 toml::from_str(src).context("parsing the keybinding table")?;
268
269 let mut staged: Vec<(String, Option<Action>)> = Vec::new();
270 for (chord, action_name) in table {
271 if action_name.is_empty() {
272 staged.push((chord, None));
275 continue;
276 }
277 let action = Action::from_name(&action_name)
278 .ok_or_else(|| anyhow!("{chord} is bound to an unknown action: {action_name}"))?;
279 staged.push((chord, Some(action)));
280 }
281
282 for (chord, action) in staged {
283 match action {
284 Some(action) => {
285 self.bindings.insert(chord, action);
286 }
287 None => {
288 self.bindings.remove(&chord);
289 }
290 }
291 }
292 Ok(())
293 }
294}
295
296impl Default for Keymap {
297 fn default() -> Self {
298 Self::default_bindings()
299 }
300}