summaryrefslogtreecommitdiffstats
path: root/src/verb_conf.rs
blob: 03002d3f9d742fb1f9d7d942fb71f845179e8d7a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
use crossterm::input::KeyEvent;
use regex::Regex;

use crate::errors::ConfError;

/// what's needed to handle a verb
#[derive(Debug)]
pub struct VerbConf {
    pub shortcut: Option<String>,
    pub invocation: String,
    pub key: Option<KeyEvent>,
    pub execution: String,
    pub description: Option<String>,
    pub from_shell: Option<bool>,
    pub leave_broot: Option<bool>,
    pub confirm: Option<bool>,
}

fn bad_key(raw: &str) -> Result<KeyEvent, ConfError> {
    Err(ConfError::InvalidKey {
        raw: raw.to_owned(),
    })
}

/// parse a string as a keyboard key definition.
///
/// Note that some mappings allowed by crossterm aren't
/// parsed because we don't want to let the user override
/// the related behaviors.
pub fn parse_key(raw: &str) -> Result<KeyEvent, ConfError> {
    let key_regex = regex!(
        r"(?x)
        ^
        (?P<major>([a-zA-Z]+|\^))
        (?:\W?(?P<minor>\w\d?)\W?)?
        $
        "
    );
    match key_regex.captures(raw) {
        Some(c) => Ok(
            match (
                c.name("major")
                    .unwrap()
                    .as_str()
                    .to_ascii_lowercase()
                    .as_ref(),
                c.name("minor"),
            ) {
                ("left", None) => KeyEvent::Left,
                ("right", None) => KeyEvent::Right,
                ("up", None) => KeyEvent::Up,
                ("down", None) => KeyEvent::Down,
                ("home", None) => KeyEvent::Home,
                ("end", None) => KeyEvent::End,
                ("pageup", None) => KeyEvent::PageUp,
                ("pagedown", None) => KeyEvent::PageDown,
                ("backtab", None) => KeyEvent::BackTab,
                ("delete", None) => KeyEvent::Delete,
                ("insert", None) => KeyEvent::Insert,
                ("f", Some(minor)) => match minor.as_str().parse() {
                    Ok(digit) => KeyEvent::F(digit),
                    _ => bad_key(raw)?,
                },
                ("alt", Some(minor)) => {
                    KeyEvent::Alt(minor.as_str().chars().next().unwrap().to_ascii_lowercase())
                }
                ("ctrl", Some(minor)) | ("^", Some(minor)) => {
                    KeyEvent::Ctrl(minor.as_str().chars().next().unwrap().to_ascii_lowercase())
                }
                // other possible mappings are disabled as they would break basic behaviors of broot
                _ => bad_key(raw)?,
            },
        ),
        None => bad_key(raw),
    }
}

#[cfg(test)]
mod key_parsing_tests {

    use crossterm::input::KeyEvent::*;

    use crate::verb_conf::*;

    fn check_ok(raw: &str, key: KeyEvent) {
        let parsed = parse_key(raw);
        assert!(parsed.is_ok(), "failed to parse {:?} as key", raw);
        assert_eq!(parsed.unwrap(), key);
    }

    #[test]
    fn check_key_parsing() {
        check_ok("left", Left);
        check_ok("right", Right);
        check_ok("Home", Home);
        check_ok("f1", F(1));
        check_ok("F2", F(2));
        check_ok("F-3", F(3));
        check_ok("F(4)", F(4));
        check_ok("F 12", F(12));
        check_ok("F(12)", F(12));
        assert!(parse_key("F(a)").is_err(), "should not have parsed F(a)");
        check_ok("Up", Up);
        check_ok("down", Down);
        check_ok("insert", Insert);
        check_ok("alt(4)", Alt('4'));
        check_ok("alt-D", Alt('d'));
        check_ok("ctrl-q", Ctrl('q'));
        check_ok("ctrl-Q", Ctrl('q'));
        check_ok("ctrl Q", Ctrl('q'));
        check_ok("^Q", Ctrl('q'));
    }
}