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}