Skip to main content

fret_runtime/keymap/
load.rs

1use crate::{CommandId, KeyChord, WhenExpr};
2use fret_core::{KeyCode, Modifiers};
3
4use super::wire::{KeySpecV1, KeymapFileAny, KeymapFileV1, KeysAny};
5use super::{Binding, Keymap, KeymapError, KeymapLoadOptions, PlatformFilter, WhenValidationMode};
6
7impl Keymap {
8    pub fn from_bytes(bytes: &[u8]) -> Result<Self, KeymapError> {
9        Self::from_bytes_with_options(bytes, KeymapLoadOptions::default())
10    }
11
12    pub fn from_bytes_with_options(
13        bytes: &[u8],
14        options: KeymapLoadOptions,
15    ) -> Result<Self, KeymapError> {
16        let parsed: KeymapFileAny =
17            serde_json::from_slice(bytes).map_err(|source| KeymapError::ParseFailed { source })?;
18        Self::from_any(parsed, options)
19    }
20
21    pub fn from_v1(file: KeymapFileV1) -> Result<Self, KeymapError> {
22        Self::from_v1_with_options(file, KeymapLoadOptions::default())
23    }
24
25    pub fn from_v1_with_options(
26        file: KeymapFileV1,
27        options: KeymapLoadOptions,
28    ) -> Result<Self, KeymapError> {
29        if file.keymap_version != 1 {
30            return Err(KeymapError::UnsupportedVersion(file.keymap_version));
31        }
32
33        let mut out = Keymap::empty();
34        for (index, b) in file.bindings.into_iter().enumerate() {
35            let platform = match b.platform.as_deref().unwrap_or("all") {
36                "all" => PlatformFilter::All,
37                "macos" => PlatformFilter::Macos,
38                "windows" => PlatformFilter::Windows,
39                "linux" => PlatformFilter::Linux,
40                "web" => PlatformFilter::Web,
41                other => {
42                    return Err(KeymapError::UnknownPlatform {
43                        index,
44                        value: other.into(),
45                    });
46                }
47            };
48
49            let chord = parse_keys(index, b.keys)?;
50
51            let when = if let Some(when) = b.when.as_deref() {
52                Some(parse_when(index, when, options.when_validation)?)
53            } else {
54                None
55            };
56
57            let command = b.command.map(CommandId::new);
58
59            out.push_binding(Binding {
60                platform,
61                sequence: vec![chord],
62                when,
63                command,
64            });
65        }
66
67        Ok(out)
68    }
69
70    fn from_any(file: KeymapFileAny, options: KeymapLoadOptions) -> Result<Self, KeymapError> {
71        match file.keymap_version {
72            1 => {
73                let mut out = Keymap::empty();
74                for (index, b) in file.bindings.into_iter().enumerate() {
75                    let platform = match b.platform.as_deref().unwrap_or("all") {
76                        "all" => PlatformFilter::All,
77                        "macos" => PlatformFilter::Macos,
78                        "windows" => PlatformFilter::Windows,
79                        "linux" => PlatformFilter::Linux,
80                        "web" => PlatformFilter::Web,
81                        other => {
82                            return Err(KeymapError::UnknownPlatform {
83                                index,
84                                value: other.into(),
85                            });
86                        }
87                    };
88
89                    let KeysAny::Single(keys) = b.keys else {
90                        return Err(KeymapError::UnsupportedVersion(1));
91                    };
92
93                    let chord = parse_keys(index, keys)?;
94
95                    let when = if let Some(when) = b.when.as_deref() {
96                        Some(parse_when(index, when, options.when_validation)?)
97                    } else {
98                        None
99                    };
100
101                    let command = b.command.map(CommandId::new);
102
103                    out.push_binding(Binding {
104                        platform,
105                        sequence: vec![chord],
106                        when,
107                        command,
108                    });
109                }
110                Ok(out)
111            }
112            2 => {
113                let mut out = Keymap::empty();
114                for (index, b) in file.bindings.into_iter().enumerate() {
115                    let platform = match b.platform.as_deref().unwrap_or("all") {
116                        "all" => PlatformFilter::All,
117                        "macos" => PlatformFilter::Macos,
118                        "windows" => PlatformFilter::Windows,
119                        "linux" => PlatformFilter::Linux,
120                        "web" => PlatformFilter::Web,
121                        other => {
122                            return Err(KeymapError::UnknownPlatform {
123                                index,
124                                value: other.into(),
125                            });
126                        }
127                    };
128
129                    let key_specs = match b.keys {
130                        KeysAny::Single(keys) => vec![keys],
131                        KeysAny::Sequence(seq) => seq,
132                    };
133                    if key_specs.is_empty() {
134                        return Err(KeymapError::EmptyKeys { index });
135                    }
136
137                    let mut sequence: Vec<KeyChord> = Vec::with_capacity(key_specs.len());
138                    for keys in key_specs {
139                        sequence.push(parse_keys(index, keys)?);
140                    }
141
142                    let when = if let Some(when) = b.when.as_deref() {
143                        Some(parse_when(index, when, options.when_validation)?)
144                    } else {
145                        None
146                    };
147
148                    let command = b.command.map(CommandId::new);
149
150                    out.push_binding(Binding {
151                        platform,
152                        sequence,
153                        when,
154                        command,
155                    });
156                }
157                Ok(out)
158            }
159            other => Err(KeymapError::UnsupportedVersion(other)),
160        }
161    }
162}
163
164fn parse_keys(index: usize, keys: KeySpecV1) -> Result<KeyChord, KeymapError> {
165    let key: KeyCode = keys.key.parse().map_err(|_| KeymapError::UnknownKeyToken {
166        index,
167        token: keys.key.clone(),
168    })?;
169
170    let mut mods = Modifiers::default();
171    for m in keys.mods {
172        let token = m.to_ascii_lowercase();
173        match token.as_str() {
174            "shift" => mods.shift = true,
175            "ctrl" | "control" => mods.ctrl = true,
176            "alt" | "option" => mods.alt = true,
177            "altgr" | "alt_gr" | "altgraph" => mods.alt_gr = true,
178            "meta" | "cmd" | "command" => mods.meta = true,
179            other => {
180                return Err(KeymapError::UnknownModifier {
181                    index,
182                    value: other.into(),
183                });
184            }
185        }
186    }
187    Ok(KeyChord::new(key, mods))
188}
189
190fn parse_when(index: usize, when: &str, mode: WhenValidationMode) -> Result<WhenExpr, KeymapError> {
191    let expr =
192        WhenExpr::parse(when).map_err(|e| KeymapError::WhenParseFailed { index, error: e })?;
193    if mode == WhenValidationMode::Strict {
194        expr.validate()
195            .map_err(|e| KeymapError::WhenValidationFailed {
196                index,
197                error: e.to_string(),
198            })?;
199    }
200    Ok(expr)
201}