Skip to main content

scooter_core/config/
keys.rs

1use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
2
3use crate::keyboard::{KeyCode, KeyEvent, KeyModifiers};
4
5impl<'de> Deserialize<'de> for KeyEvent {
6    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
7    where
8        D: Deserializer<'de>,
9    {
10        let s = String::deserialize(deserializer)?;
11        s.parse().map_err(de::Error::custom)
12    }
13}
14
15/// Deserialize either a single `KeyEvent` or a Vec of `KeyEvents` into a Vec
16fn deserialize_key_or_keys<'de, D>(deserializer: D) -> Result<Vec<KeyEvent>, D::Error>
17where
18    D: Deserializer<'de>,
19{
20    #[derive(Deserialize)]
21    #[serde(untagged)]
22    enum OneOrMany {
23        One(String),
24        Many(Vec<String>),
25    }
26
27    let keys = match OneOrMany::deserialize(deserializer)? {
28        OneOrMany::One(s) => vec![s],
29        OneOrMany::Many(v) => v,
30    };
31
32    keys.into_iter()
33        .map(|s| {
34            s.parse::<KeyEvent>()
35                .map_err(|e| de::Error::custom(format!("Invalid key binding '{s}': {e}")))
36        })
37        .collect()
38}
39
40/// Serialize a Vec of `KeyEvent`s, using a single value if the vec has one element
41fn serialize_key_or_keys<S>(keys: &Vec<KeyEvent>, serializer: S) -> Result<S::Ok, S::Error>
42where
43    S: Serializer,
44{
45    if keys.len() == 1 {
46        keys[0].serialize(serializer)
47    } else {
48        keys.serialize(serializer)
49    }
50}
51
52/// Wrapper type for key bindings that can be specified as either a single key or multiple keys
53#[derive(Debug, Clone, PartialEq)]
54pub struct Keys(Vec<KeyEvent>);
55
56impl Keys {
57    pub fn new(keys: Vec<KeyEvent>) -> Self {
58        Self(keys)
59    }
60}
61
62impl<'de> Deserialize<'de> for Keys {
63    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
64    where
65        D: Deserializer<'de>,
66    {
67        deserialize_key_or_keys(deserializer).map(Keys)
68    }
69}
70
71impl Serialize for Keys {
72    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
73    where
74        S: Serializer,
75    {
76        serialize_key_or_keys(&self.0, serializer)
77    }
78}
79
80impl std::ops::Deref for Keys {
81    type Target = Vec<KeyEvent>;
82
83    fn deref(&self) -> &Self::Target {
84        &self.0
85    }
86}
87
88impl<'a> IntoIterator for &'a Keys {
89    type Item = &'a KeyEvent;
90    type IntoIter = std::slice::Iter<'a, KeyEvent>;
91
92    fn into_iter(self) -> Self::IntoIter {
93        self.0.iter()
94    }
95}
96
97/// Helper macro for creating `Keys` from a vec of `KeyEvent`s
98#[macro_export]
99macro_rules! keys {
100    ($($item:expr),* $(,)?) => {
101        $crate::config::Keys::new(vec![$($item),*])
102    };
103}
104
105#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq)]
106#[serde(deny_unknown_fields)]
107pub struct KeysConfig {
108    #[serde(default)]
109    /// Commands available on all screens
110    pub general: KeysGeneral,
111    #[serde(default)]
112    /// Commands available on the search screen
113    pub search: KeysSearch,
114    #[serde(default)]
115    /// Commands available on the replacement-in-progress screen
116    pub performing_replacement: KeysPerformingReplacement,
117    #[serde(default)]
118    /// Commands available on the results screen
119    pub results: KeysResults,
120}
121
122#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
123#[serde(deny_unknown_fields, default)]
124pub struct KeysGeneral {
125    /// Exit scooter
126    pub quit: Keys,
127    /// Cancel in-progress operations, reset fields to default values and return to search screen
128    pub reset: Keys,
129    /// Show the help menu containing keymaps
130    pub show_help_menu: Keys,
131}
132
133impl Default for KeysGeneral {
134    fn default() -> Self {
135        Self {
136            quit: keys![KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL)],
137            reset: keys![KeyEvent::new(KeyCode::Char('r'), KeyModifiers::CONTROL)],
138            show_help_menu: keys![KeyEvent::new(KeyCode::Char('h'), KeyModifiers::CONTROL)],
139        }
140    }
141}
142
143#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
144#[serde(deny_unknown_fields, default)]
145pub struct KeysSearch {
146    /// Toggle wrapping of lines that don't fit within the width of the preview
147    pub toggle_preview_wrapping: Keys,
148    /// Toggle inclusion of hidden files and directories, such as those whose name starts with a dot (.)
149    pub toggle_hidden_files: Keys,
150    /// Toggle multiline search mode, which allows patterns to match across line boundaries
151    pub toggle_multiline: Keys,
152    /// Toggle interpretation of escape sequences in replacement text (\n becomes newline, \t becomes tab, \\ becomes backslash)
153    pub toggle_interpret_escape_sequences: Keys,
154    #[serde(default)]
155    /// Commands available on the search screen, when the search fields are focussed
156    pub fields: KeysSearchFocusFields,
157    #[serde(default)]
158    /// Commands available on the search screen, when the search results are focussed
159    pub results: KeysSearchFocusResults,
160}
161
162impl Default for KeysSearch {
163    fn default() -> Self {
164        Self {
165            toggle_preview_wrapping: keys![KeyEvent::new(
166                KeyCode::Char('l'),
167                KeyModifiers::CONTROL
168            )],
169            toggle_hidden_files: keys![KeyEvent::new(KeyCode::Char('t'), KeyModifiers::CONTROL)],
170            toggle_multiline: keys![KeyEvent::new(KeyCode::Char('m'), KeyModifiers::ALT)],
171            toggle_interpret_escape_sequences: keys![KeyEvent::new(
172                KeyCode::Char('e'),
173                KeyModifiers::ALT
174            )],
175            fields: KeysSearchFocusFields::default(),
176            results: KeysSearchFocusResults::default(),
177        }
178    }
179}
180
181#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
182#[serde(deny_unknown_fields, default)]
183pub struct KeysSearchFocusFields {
184    /// Allow editing of fields that were populated using CLI args, such as `--search_text foo`. (Note that you can use the `disable_prepopulated_fields` config option to change the default behaviour.)
185    pub unlock_prepopulated_fields: Keys,
186    /// Trigger a search
187    pub trigger_search: Keys,
188    /// Focus on the next field
189    pub focus_next_field: Keys,
190    /// Focus on the previous field
191    pub focus_previous_field: Keys,
192}
193
194impl Default for KeysSearchFocusFields {
195    fn default() -> Self {
196        Self {
197            unlock_prepopulated_fields: keys![KeyEvent::new(KeyCode::Char('u'), KeyModifiers::ALT)],
198            trigger_search: keys![KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE)],
199            focus_next_field: keys![KeyEvent::new(KeyCode::Tab, KeyModifiers::NONE)],
200            focus_previous_field: keys![KeyEvent::new(KeyCode::Tab, KeyModifiers::SHIFT)],
201        }
202    }
203}
204
205#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
206#[serde(deny_unknown_fields, default)]
207pub struct KeysSearchFocusResults {
208    /// Trigger a replacement
209    pub trigger_replacement: Keys,
210    /// Move focus back to the search fields
211    pub back_to_fields: Keys,
212    /// Open the currently selected search result in your editor. The editor command can be overriden using the `editor_open` section of your config.
213    pub open_in_editor: Keys,
214
215    /// Navigate to the search result below
216    pub move_down: Keys,
217    /// Navigate to the search result above
218    pub move_up: Keys,
219    /// Navigate to the search result half a page below
220    pub move_down_half_page: Keys,
221    /// Navigate to the search result half a page above
222    pub move_up_half_page: Keys,
223    /// Navigate to the search result a page below
224    pub move_down_full_page: Keys,
225    /// Navigate to the search result a page above
226    pub move_up_full_page: Keys,
227    /// Navigate to the first search result
228    pub move_top: Keys,
229    /// Navigate to the last search result
230    pub move_bottom: Keys,
231
232    /// Toggle whether the currently highlighted result will be replaced or ignored
233    pub toggle_selected_inclusion: Keys,
234    /// Toggle whether all results will be replaced or ignored
235    pub toggle_all_selected: Keys,
236    /// Toggle whether multiselect mode is enabled
237    pub toggle_multiselect_mode: Keys,
238
239    /// Flip the direction of the multiselect selection
240    pub flip_multiselect_direction: Keys,
241}
242
243impl Default for KeysSearchFocusResults {
244    fn default() -> Self {
245        Self {
246            trigger_replacement: keys![KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE)],
247            back_to_fields: keys![
248                KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE),
249                KeyEvent::new(KeyCode::Char('o'), KeyModifiers::CONTROL),
250            ],
251            open_in_editor: keys![KeyEvent::new(KeyCode::Char('e'), KeyModifiers::NONE)],
252
253            move_down: keys![
254                KeyEvent::new(KeyCode::Char('j'), KeyModifiers::NONE),
255                KeyEvent::new(KeyCode::Down, KeyModifiers::NONE),
256                KeyEvent::new(KeyCode::Char('n'), KeyModifiers::CONTROL),
257            ],
258            move_up: keys![
259                KeyEvent::new(KeyCode::Char('k'), KeyModifiers::NONE),
260                KeyEvent::new(KeyCode::Up, KeyModifiers::NONE),
261                KeyEvent::new(KeyCode::Char('p'), KeyModifiers::CONTROL),
262            ],
263            move_down_half_page: keys![KeyEvent::new(KeyCode::Char('d'), KeyModifiers::CONTROL)],
264            move_down_full_page: keys![
265                KeyEvent::new(KeyCode::Char('f'), KeyModifiers::CONTROL),
266                KeyEvent::new(KeyCode::PageDown, KeyModifiers::NONE),
267            ],
268            move_up_half_page: keys![KeyEvent::new(KeyCode::Char('u'), KeyModifiers::CONTROL)],
269            move_up_full_page: keys![
270                KeyEvent::new(KeyCode::Char('b'), KeyModifiers::CONTROL),
271                KeyEvent::new(KeyCode::PageUp, KeyModifiers::NONE),
272            ],
273            move_top: keys![KeyEvent::new(KeyCode::Char('g'), KeyModifiers::NONE)],
274            move_bottom: keys![KeyEvent::new(KeyCode::Char('G'), KeyModifiers::NONE)],
275
276            toggle_selected_inclusion: keys![KeyEvent::new(KeyCode::Char(' '), KeyModifiers::NONE)],
277            toggle_all_selected: keys![KeyEvent::new(KeyCode::Char('a'), KeyModifiers::NONE)],
278            toggle_multiselect_mode: keys![KeyEvent::new(KeyCode::Char('v'), KeyModifiers::NONE)],
279
280            flip_multiselect_direction: keys![KeyEvent::new(KeyCode::Char(';'), KeyModifiers::ALT)],
281        }
282    }
283}
284
285#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
286#[serde(deny_unknown_fields, default)]
287#[derive(Default)]
288pub struct KeysPerformingReplacement {}
289
290#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
291#[serde(deny_unknown_fields, default)]
292pub struct KeysResults {
293    /// Navigate to the error below
294    pub scroll_errors_down: Keys,
295    /// Navigate to the error above
296    pub scroll_errors_up: Keys,
297    /// Exit scooter. This is in addition to the `quit` command in the `general` section.
298    pub quit: Keys,
299}
300
301impl Default for KeysResults {
302    fn default() -> Self {
303        Self {
304            scroll_errors_down: keys![
305                KeyEvent::new(KeyCode::Char('j'), KeyModifiers::NONE),
306                KeyEvent::new(KeyCode::Down, KeyModifiers::NONE),
307                KeyEvent::new(KeyCode::Char('n'), KeyModifiers::CONTROL),
308            ],
309            scroll_errors_up: keys![
310                KeyEvent::new(KeyCode::Char('k'), KeyModifiers::NONE),
311                KeyEvent::new(KeyCode::Up, KeyModifiers::NONE),
312                KeyEvent::new(KeyCode::Char('p'), KeyModifiers::CONTROL),
313            ],
314            quit: keys![
315                KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE),
316                KeyEvent::new(KeyCode::Char('q'), KeyModifiers::NONE),
317            ],
318        }
319    }
320}