Skip to content

Commit 5b7eecf

Browse files
committedMar 23, 2025·
chore: define Keybinds at config.rs (and once remove tests about configs)
1 parent dc71146 commit 5b7eecf

File tree

4 files changed

+110
-236
lines changed

4 files changed

+110
-236
lines changed
 

‎src/config.rs

+86-169
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::collections::HashSet;
22

33
use crossterm::{
4-
event::{KeyCode, KeyEvent, KeyModifiers},
4+
event::{KeyCode, KeyModifiers},
55
style::{Attribute, Attributes, Color, ContentStyle},
66
};
77
use derive_builder::Builder;
@@ -13,7 +13,7 @@ mod content_style;
1313
use content_style::{content_style_serde, option_content_style_serde};
1414
mod duration;
1515
use duration::{duration_serde, option_duration_serde};
16-
mod event;
16+
pub mod event;
1717
use event::{EventDefSet, KeyEventDef};
1818

1919
#[derive(Serialize, Deserialize, Builder)]
@@ -104,19 +104,6 @@ pub(crate) struct Config {
104104
#[serde(with = "content_style_serde")]
105105
#[builder_field_attr(serde(default, with = "option_content_style_serde"))]
106106
pub null_value_style: ContentStyle,
107-
108-
pub move_to_tail: EventDefSet,
109-
pub move_to_head: EventDefSet,
110-
pub backward: EventDefSet,
111-
pub forward: EventDefSet,
112-
pub completion: EventDefSet,
113-
pub move_to_next_nearest: EventDefSet,
114-
pub move_to_previous_nearest: EventDefSet,
115-
pub erase: EventDefSet,
116-
pub erase_all: EventDefSet,
117-
pub erase_to_previous_nearest: EventDefSet,
118-
pub erase_to_next_nearest: EventDefSet,
119-
pub search_up: EventDefSet,
120107
}
121108

122109
impl Default for Config {
@@ -162,45 +149,11 @@ impl Default for Config {
162149
focus_active_char_style: StyleBuilder::new().bgc(Color::Magenta).build(),
163150
focus_inactive_char_style: StyleBuilder::new().build(),
164151
inactive_item_style: StyleBuilder::new().fgc(Color::Grey).build(),
165-
move_to_tail: EventDefSet::from(KeyEventDef::new(
166-
KeyCode::Char('e'),
167-
KeyModifiers::CONTROL,
168-
)),
169-
move_to_head: EventDefSet::from(KeyEventDef::new(
170-
KeyCode::Char('a'),
171-
KeyModifiers::CONTROL,
172-
)),
173-
backward: EventDefSet::from(KeyEventDef::new(KeyCode::Left, KeyModifiers::NONE)),
174-
forward: EventDefSet::from(KeyEventDef::new(KeyCode::Right, KeyModifiers::NONE)),
175-
completion: EventDefSet::from(KeyEventDef::new(KeyCode::Tab, KeyModifiers::NONE)),
176-
move_to_next_nearest: EventDefSet::from(KeyEventDef::new(
177-
KeyCode::Char('f'),
178-
KeyModifiers::ALT,
179-
)),
180-
move_to_previous_nearest: EventDefSet::from(KeyEventDef::new(
181-
KeyCode::Char('b'),
182-
KeyModifiers::ALT,
183-
)),
184-
erase: EventDefSet::from(KeyEventDef::new(KeyCode::Backspace, KeyModifiers::NONE)),
185-
erase_all: EventDefSet::from(KeyEventDef::new(
186-
KeyCode::Char('u'),
187-
KeyModifiers::CONTROL,
188-
)),
189-
erase_to_previous_nearest: EventDefSet::from(KeyEventDef::new(
190-
KeyCode::Char('w'),
191-
KeyModifiers::CONTROL,
192-
)),
193-
erase_to_next_nearest: EventDefSet::from(KeyEventDef::new(
194-
KeyCode::Char('d'),
195-
KeyModifiers::ALT,
196-
)),
197-
search_up: EventDefSet::from(KeyEventDef::new(KeyCode::Up, KeyModifiers::NONE)),
198152
}
199153
}
200154
}
201155

202156
impl ConfigFromFile {
203-
/// Load the config from a string.
204157
pub fn load_from(content: &str) -> anyhow::Result<Self> {
205158
toml::from_str(content).map_err(Into::into)
206159
}
@@ -287,149 +240,113 @@ impl Config {
287240
if let Some(val) = config.word_break_chars {
288241
self.word_break_chars = val;
289242
}
290-
if let Some(val) = config.move_to_tail {
243+
}
244+
}
245+
246+
#[derive(Clone, Serialize, Deserialize, Builder)]
247+
#[builder(derive(Serialize, Deserialize))]
248+
#[builder(name = "KeybindsFromFile")]
249+
#[serde(deny_unknown_fields)]
250+
pub struct Keybinds {
251+
pub move_to_tail: EventDefSet,
252+
pub backward: EventDefSet,
253+
pub forward: EventDefSet,
254+
pub completion: EventDefSet,
255+
pub move_to_head: EventDefSet,
256+
pub move_to_previous_nearest: EventDefSet,
257+
pub move_to_next_nearest: EventDefSet,
258+
pub erase: EventDefSet,
259+
pub erase_all: EventDefSet,
260+
pub erase_to_previous_nearest: EventDefSet,
261+
pub erase_to_next_nearest: EventDefSet,
262+
pub search_up: EventDefSet,
263+
}
264+
265+
impl Default for Keybinds {
266+
fn default() -> Self {
267+
Self {
268+
move_to_tail: EventDefSet::from(KeyEventDef::new(
269+
KeyCode::Char('e'),
270+
KeyModifiers::CONTROL,
271+
)),
272+
move_to_head: EventDefSet::from(KeyEventDef::new(
273+
KeyCode::Char('a'),
274+
KeyModifiers::CONTROL,
275+
)),
276+
backward: EventDefSet::from(KeyEventDef::new(KeyCode::Left, KeyModifiers::NONE)),
277+
forward: EventDefSet::from(KeyEventDef::new(KeyCode::Right, KeyModifiers::NONE)),
278+
completion: EventDefSet::from(KeyEventDef::new(KeyCode::Tab, KeyModifiers::NONE)),
279+
move_to_next_nearest: EventDefSet::from(KeyEventDef::new(
280+
KeyCode::Char('f'),
281+
KeyModifiers::ALT,
282+
)),
283+
move_to_previous_nearest: EventDefSet::from(KeyEventDef::new(
284+
KeyCode::Char('b'),
285+
KeyModifiers::ALT,
286+
)),
287+
erase: EventDefSet::from(KeyEventDef::new(KeyCode::Backspace, KeyModifiers::NONE)),
288+
erase_all: EventDefSet::from(KeyEventDef::new(
289+
KeyCode::Char('u'),
290+
KeyModifiers::CONTROL,
291+
)),
292+
erase_to_previous_nearest: EventDefSet::from(KeyEventDef::new(
293+
KeyCode::Char('w'),
294+
KeyModifiers::CONTROL,
295+
)),
296+
erase_to_next_nearest: EventDefSet::from(KeyEventDef::new(
297+
KeyCode::Char('d'),
298+
KeyModifiers::ALT,
299+
)),
300+
search_up: EventDefSet::from(KeyEventDef::new(KeyCode::Up, KeyModifiers::NONE)),
301+
}
302+
}
303+
}
304+
305+
impl KeybindsFromFile {
306+
/// Load the config from a string.
307+
pub fn load_from(content: &str) -> anyhow::Result<Self> {
308+
toml::from_str(content).map_err(Into::into)
309+
}
310+
}
311+
312+
impl Keybinds {
313+
pub fn patch_with(&mut self, keybinds: KeybindsFromFile) {
314+
// TODO: This is awful verbose. Can we do better?
315+
if let Some(val) = keybinds.move_to_tail {
291316
self.move_to_tail = val;
292317
}
293-
if let Some(val) = config.move_to_head {
318+
if let Some(val) = keybinds.move_to_head {
294319
self.move_to_head = val;
295320
}
296-
if let Some(val) = config.backward {
321+
if let Some(val) = keybinds.backward {
297322
self.backward = val;
298323
}
299-
if let Some(val) = config.forward {
324+
if let Some(val) = keybinds.forward {
300325
self.forward = val;
301326
}
302-
if let Some(val) = config.completion {
327+
if let Some(val) = keybinds.completion {
303328
self.completion = val;
304329
}
305-
if let Some(val) = config.move_to_next_nearest {
330+
if let Some(val) = keybinds.move_to_next_nearest {
306331
self.move_to_next_nearest = val;
307332
}
308-
if let Some(val) = config.move_to_previous_nearest {
333+
if let Some(val) = keybinds.move_to_previous_nearest {
309334
self.move_to_previous_nearest = val;
310335
}
311-
if let Some(val) = config.erase {
336+
if let Some(val) = keybinds.erase {
312337
self.erase = val;
313338
}
314-
if let Some(val) = config.erase_all {
339+
if let Some(val) = keybinds.erase_all {
315340
self.erase_all = val;
316341
}
317-
if let Some(val) = config.erase_to_previous_nearest {
342+
if let Some(val) = keybinds.erase_to_previous_nearest {
318343
self.erase_to_previous_nearest = val;
319344
}
320-
if let Some(val) = config.erase_to_next_nearest {
345+
if let Some(val) = keybinds.erase_to_next_nearest {
321346
self.erase_to_next_nearest = val;
322347
}
323-
if let Some(val) = config.search_up {
348+
if let Some(val) = keybinds.search_up {
324349
self.search_up = val;
325350
}
326351
}
327352
}
328-
329-
#[cfg(test)]
330-
mod tests {
331-
mod load_from {
332-
use super::super::*;
333-
334-
#[test]
335-
fn test() {
336-
let toml = r#"
337-
search_result_chunk_size = 10
338-
query_debounce_duration = "1000ms"
339-
resize_debounce_duration = "2s"
340-
search_load_chunk_size = 5
341-
focus_prefix = "❯ "
342-
spin_duration = "500ms"
343-
344-
[active_item_style]
345-
foreground = "green"
346-
347-
[focus_active_char_style]
348-
background = "green"
349-
underline = "red"
350-
attributes = ["Bold", "Underlined"]
351-
352-
[move_to_tail]
353-
code = { Char = "$" }
354-
modifiers = "CONTROL"
355-
"#;
356-
357-
let config = ConfigFromFile::load_from(toml).unwrap();
358-
359-
assert_eq!(config.search_result_chunk_size, Some(10));
360-
assert_eq!(
361-
config.query_debounce_duration,
362-
Some(Duration::from_millis(1000))
363-
);
364-
assert_eq!(
365-
config.resize_debounce_duration,
366-
Some(Duration::from_secs(2))
367-
);
368-
assert_eq!(config.spin_duration, Some(Duration::from_millis(500)));
369-
assert_eq!(config.search_load_chunk_size, Some(5));
370-
assert_eq!(
371-
config.active_item_style,
372-
Some(StyleBuilder::new().fgc(Color::Green).build()),
373-
);
374-
375-
assert_eq!(
376-
config.move_to_tail,
377-
Some(EventDefSet::from(KeyEventDef::new(
378-
KeyCode::Char('$'),
379-
KeyModifiers::CONTROL
380-
)))
381-
);
382-
383-
assert_eq!(config.focus_prefix, Some("❯ ".to_string()));
384-
385-
assert_eq!(
386-
config.focus_active_char_style,
387-
Some(
388-
StyleBuilder::new()
389-
.bgc(Color::Green)
390-
.ulc(Color::Red)
391-
.attrs(Attributes::from(Attribute::Bold) | Attribute::Underlined)
392-
.build()
393-
),
394-
);
395-
396-
// Check the part of the config that was not set in the toml
397-
assert_eq!(config.backward, None);
398-
assert_eq!(config.forward, None);
399-
assert_eq!(config.completion, None);
400-
}
401-
402-
#[test]
403-
fn test_with_empty() {
404-
let toml = "";
405-
let config = ConfigFromFile::load_from(toml).unwrap();
406-
407-
assert_eq!(config.search_result_chunk_size, None);
408-
assert_eq!(config.query_debounce_duration, None);
409-
assert_eq!(config.resize_debounce_duration, None);
410-
assert_eq!(config.spin_duration, None);
411-
assert_eq!(config.search_load_chunk_size, None);
412-
assert_eq!(config.active_item_style, None);
413-
assert_eq!(config.inactive_item_style, None);
414-
assert_eq!(config.focus_prefix, None);
415-
assert_eq!(config.focus_active_char_style, None);
416-
assert_eq!(config.move_to_tail, None);
417-
assert_eq!(config.move_to_head, None);
418-
}
419-
}
420-
421-
mod patch_with {
422-
use super::super::*;
423-
424-
#[test]
425-
fn test() {
426-
let mut config = Config::default();
427-
let config_from_file = ConfigFromFile {
428-
focus_prefix: Some(":)".to_string()),
429-
..Default::default()
430-
};
431-
config.patch_with(config_from_file);
432-
assert_eq!(config.focus_prefix, ":)".to_string());
433-
}
434-
}
435-
}

‎src/config/content_style.rs

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
use crossterm::{
2-
event::{KeyCode, KeyEvent, KeyModifiers},
3-
style::{Attribute, Attributes, Color, ContentStyle},
4-
};
1+
use crossterm::style::{Attribute, Attributes, Color, ContentStyle};
52
use serde::{Deserialize, Serialize};
63

74
#[derive(Serialize, Deserialize)]

‎src/editor.rs

+16-31
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ use crossterm::{
66
};
77
use promkit::{pane::Pane, style::StyleBuilder, text, text_editor, PaneFactory};
88

9-
use crate::search::IncrementalSearcher;
9+
use crate::{
10+
config::{event::Matcher, Keybinds},
11+
search::IncrementalSearcher,
12+
};
1013

1114
pub struct Editor {
1215
keybind: Keybind,
@@ -29,21 +32,6 @@ pub struct EditorTheme {
2932
pub inactive_char_style: ContentStyle,
3033
}
3134

32-
pub struct Keybinds {
33-
pub move_to_tail: KeyEvent,
34-
pub backward: KeyEvent,
35-
pub forward: KeyEvent,
36-
pub completion: KeyEvent,
37-
pub move_to_head: KeyEvent,
38-
pub move_to_previous_nearest: KeyEvent,
39-
pub move_to_next_nearest: KeyEvent,
40-
pub erase: KeyEvent,
41-
pub erase_all: KeyEvent,
42-
pub erase_to_previous_nearest: KeyEvent,
43-
pub erase_to_next_nearest: KeyEvent,
44-
pub search_up: KeyEvent,
45-
}
46-
4735
impl Editor {
4836
pub fn new(
4937
state: text_editor::State,
@@ -124,7 +112,7 @@ pub async fn edit<'a>(event: &'a Event, editor: &'a mut Editor) -> anyhow::Resul
124112
editor.guide.text = Default::default();
125113

126114
match event {
127-
key if key == &Event::Key(editor.keybinds.completion) => {
115+
key if editor.keybinds.completion.matches(key) => {
128116
let prefix = editor.state.texteditor.text_without_cursor().to_string();
129117
match editor.searcher.start_search(&prefix) {
130118
Ok(result) => match result.head_item {
@@ -158,52 +146,49 @@ pub async fn edit<'a>(event: &'a Event, editor: &'a mut Editor) -> anyhow::Resul
158146
}
159147

160148
// Move cursor.
161-
key if key == &Event::Key(editor.keybinds.backward) => {
149+
key if editor.keybinds.backward.matches(key) => {
162150
editor.state.texteditor.backward();
163151
}
164-
key if key == &Event::Key(editor.keybinds.forward) => {
152+
key if editor.keybinds.forward.matches(key) => {
165153
editor.state.texteditor.forward();
166154
}
167-
key if key == &Event::Key(editor.keybinds.move_to_head) => {
155+
key if editor.keybinds.move_to_head.matches(key) => {
168156
editor.state.texteditor.move_to_head();
169157
}
170-
171-
key if key == &Event::Key(editor.keybinds.move_to_tail) => {
158+
key if editor.keybinds.move_to_tail.matches(key) => {
172159
editor.state.texteditor.move_to_tail();
173160
}
174161

175162
// Move cursor to the nearest character.
176-
key if key == &Event::Key(editor.keybinds.move_to_previous_nearest) => {
163+
key if editor.keybinds.move_to_previous_nearest.matches(key) => {
177164
editor
178165
.state
179166
.texteditor
180167
.move_to_previous_nearest(&editor.state.word_break_chars);
181168
}
182-
183-
key if key == &Event::Key(editor.keybinds.move_to_next_nearest) => {
169+
key if editor.keybinds.move_to_next_nearest.matches(key) => {
184170
editor
185171
.state
186172
.texteditor
187173
.move_to_next_nearest(&editor.state.word_break_chars);
188174
}
189175

190176
// Erase char(s).
191-
key if key == &Event::Key(editor.keybinds.erase) => {
177+
key if editor.keybinds.erase.matches(key) => {
192178
editor.state.texteditor.erase();
193179
}
194-
key if key == &Event::Key(editor.keybinds.erase_all) => {
180+
key if editor.keybinds.erase_all.matches(key) => {
195181
editor.state.texteditor.erase_all();
196182
}
197183

198184
// Erase to the nearest character.
199-
key if key == &Event::Key(editor.keybinds.erase_to_previous_nearest) => {
185+
key if editor.keybinds.erase_to_previous_nearest.matches(key) => {
200186
editor
201187
.state
202188
.texteditor
203189
.erase_to_previous_nearest(&editor.state.word_break_chars);
204190
}
205-
206-
key if key == &Event::Key(editor.keybinds.erase_to_next_nearest) => {
191+
key if editor.keybinds.erase_to_next_nearest.matches(key) => {
207192
editor
208193
.state
209194
.texteditor
@@ -254,7 +239,7 @@ pub async fn search<'a>(event: &'a Event, editor: &'a mut Editor) -> anyhow::Res
254239
.replace(&editor.searcher.get_current_item());
255240
}
256241

257-
key if key == &Event::Key(editor.keybinds.search_up) => {
242+
key if editor.keybinds.search_up.matches(key) => {
258243
editor.searcher.up();
259244
editor
260245
.state

‎src/main.rs

+7-32
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::{
66

77
use anyhow::anyhow;
88
use clap::Parser;
9-
use config::{Config, ConfigFromFile};
9+
use config::{Config, ConfigFromFile, Keybinds, KeybindsFromFile};
1010
use crossterm::style::Attribute;
1111
use promkit::{
1212
jsonz::format::RowFormatter,
@@ -217,12 +217,14 @@ async fn main() -> anyhow::Result<()> {
217217
let args = Args::parse();
218218
let input = parse_input(&args)?;
219219

220-
let mut config = Config::default();
220+
let (mut config, mut keybinds) = (Config::default(), Keybinds::default());
221221
if let Ok(config_file) = determine_config_file(args.config_file, &config) {
222222
// Note that the configuration file absolutely exists.
223223
let content = std::fs::read_to_string(&config_file)?;
224-
let loaded = ConfigFromFile::load_from(&content)?;
225-
config.patch_with(loaded);
224+
let config_from_file = ConfigFromFile::load_from(&content)?;
225+
let keybinds_from_file = KeybindsFromFile::load_from(&content)?;
226+
config.patch_with(config_from_file);
227+
keybinds.patch_with(keybinds_from_file);
226228
}
227229

228230
let config::Config {
@@ -233,26 +235,14 @@ async fn main() -> anyhow::Result<()> {
233235
active_item_style,
234236
defocus_prefix,
235237
focus_prefix,
236-
move_to_tail,
237238
word_break_chars,
238-
backward,
239-
forward,
240239
defocus_prefix_style,
241240
defocus_active_char_style,
242241
defocus_inactive_char_style,
243242
focus_prefix_style,
244243
focus_active_char_style,
245244
focus_inactive_char_style,
246245
inactive_item_style,
247-
completion,
248-
move_to_head,
249-
move_to_next_nearest,
250-
move_to_previous_nearest,
251-
erase,
252-
erase_all,
253-
erase_to_previous_nearest,
254-
erase_to_next_nearest,
255-
search_up,
256246
spin_duration,
257247
prefix_style,
258248
active_char_style,
@@ -323,27 +313,12 @@ async fn main() -> anyhow::Result<()> {
323313

324314
let loading_suggestions_task = searcher.spawn_load_task(provider, item, search_load_chunk_size);
325315

326-
let editor_keybinds = editor::Keybinds {
327-
move_to_tail,
328-
backward,
329-
forward,
330-
completion,
331-
move_to_head,
332-
move_to_previous_nearest,
333-
move_to_next_nearest,
334-
erase,
335-
erase_all,
336-
erase_to_previous_nearest,
337-
erase_to_next_nearest,
338-
search_up,
339-
};
340-
341316
let editor = Editor::new(
342317
text_editor_state,
343318
searcher,
344319
editor_focus_theme,
345320
editor_defocus_theme,
346-
editor_keybinds,
321+
keybinds,
347322
);
348323

349324
prompt::run(

0 commit comments

Comments
 (0)
Please sign in to comment.