Skip to main content

pixelactions_core/
chord.rs

1//! Reading a key chord: `cmd+shift+s` → modifiers, then the key.
2//!
3//! This is pure string work, so it lives here rather than beside the
4//! injector that consumes it. Keeping it in the binary meant it was
5//! reachable only from a macOS-gated module, which made it dead code on
6//! every other platform — the sort of thing a Mac-only workflow never
7//! notices and a cross-platform CI leg catches immediately.
8
9/// Why a chord could not be read.
10#[derive(Debug, thiserror::Error, PartialEq, Eq)]
11pub enum ChordError {
12    #[error("chord {0:?} names no key — expected something like \"cmd+s\" or \"enter\"")]
13    Empty(String),
14}
15
16/// Split a chord into its modifiers and its final key: `cmd+shift+s` →
17/// `(["cmd", "shift"], "s")`.
18///
19/// Whitespace around the parts is tolerated because chords are
20/// hand-written in flow files, where `cmd + s` is a reasonable thing to
21/// type. An empty chord is an error rather than a no-op: a step that
22/// presses nothing is a typo, not an instruction.
23pub fn split(chord: &str) -> Result<(Vec<&str>, &str), ChordError> {
24    let mut parts: Vec<&str> = chord
25        .split('+')
26        .map(str::trim)
27        .filter(|part| !part.is_empty())
28        .collect();
29    let key = parts
30        .pop()
31        .ok_or_else(|| ChordError::Empty(chord.to_string()))?;
32    Ok((parts, key))
33}
34
35#[cfg(test)]
36mod tests {
37    use super::*;
38
39    #[test]
40    fn chords_split_into_modifiers_and_a_key() {
41        let (modifiers, key) = split("cmd+shift+s").expect("valid");
42        assert_eq!(modifiers, vec!["cmd", "shift"]);
43        assert_eq!(key, "s");
44    }
45
46    #[test]
47    fn a_bare_key_has_no_modifiers() {
48        let (modifiers, key) = split("enter").expect("valid");
49        assert!(modifiers.is_empty());
50        assert_eq!(key, "enter");
51    }
52
53    #[test]
54    fn whitespace_around_chord_parts_is_tolerated() {
55        let (modifiers, key) = split("cmd + s").expect("valid");
56        assert_eq!(modifiers, vec!["cmd"]);
57        assert_eq!(key, "s");
58    }
59
60    #[test]
61    fn an_empty_chord_is_an_error() {
62        assert!(split("").is_err());
63        assert!(split("+").is_err());
64        assert!(split("  ").is_err());
65    }
66
67    #[test]
68    fn the_error_quotes_the_chord_it_could_not_read() {
69        let error = split("+").expect_err("empty");
70        assert!(error.to_string().contains("\"+\""), "{error}");
71    }
72}