1use crate::{
4 interaction::Pos, keyboard_conv, DofError, DofErrorInner as DErr, Key, Keyboard, Layer, Result,
5};
6use serde::{Deserialize, Serialize};
7use serde_with::{serde_as, DisplayFromStr};
8use std::{collections::BTreeMap, iter, str::FromStr};
9
10#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
13pub struct ComboKey {
14 key: Key,
15 nth: usize,
16}
17
18impl ComboKey {
19 fn new(s: &str) -> Self {
20 let key = s.parse().unwrap();
21
22 Self { key, nth: 0 }
23 }
24
25 fn new_nth(s: &str, nth: usize) -> Self {
26 let key = s.parse().unwrap();
27
28 Self { key, nth }
29 }
30}
31
32impl FromStr for ComboKey {
33 type Err = DofError;
34
35 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
36 let ck = match s.len() {
37 0 => return Err(DErr::EmptyComboKey.into()),
38 1 | 2 => Self::new(s),
39 _ => match s.chars().rev().position(|c| c == '-') {
40 Some(p) => {
41 let (key, num) = s.split_at(s.len() - p - 1);
42 let num = &num[1..];
43
44 match num.parse::<usize>() {
45 Ok(nth) => Self::new_nth(key, nth.saturating_sub(1)),
46 Err(_) => Self::new(s),
47 }
48 }
49 None => Self::new(s),
50 },
51 };
52
53 Ok(ck)
54 }
55}
56
57impl std::fmt::Display for ComboKey {
58 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59 match self.nth {
60 0 => write!(f, "{}", self.key),
61 nth => write!(f, "{}-{}", self.key, nth),
62 }
63 }
64}
65
66keyboard_conv!(ComboKey, ComboKeyStrAsRow);
67
68#[serde_as]
71#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
72pub struct ParseCombos(
73 #[serde_as(as = "BTreeMap<_, BTreeMap<ComboKeyStrAsRow, DisplayFromStr>>")]
74 pub BTreeMap<String, BTreeMap<Vec<ComboKey>, Key>>,
75);
76
77impl ParseCombos {
78 pub(crate) fn into_pos_layers(self, layers: &BTreeMap<String, Layer>) -> Result<Combos> {
80 let layers = layers
81 .iter()
82 .map(|(name, layer)| {
83 let layer = layer
84 .rows()
85 .enumerate()
86 .flat_map(|(i, row)| {
87 row.iter()
88 .enumerate()
89 .map(move |(j, key)| (Pos::new(i, j), key))
90 })
91 .collect::<Vec<_>>();
92 (name.as_str(), layer)
93 })
94 .collect::<BTreeMap<_, _>>();
95
96 self.0
97 .into_iter()
98 .flat_map(|(layer_name, combos)| {
99 let layer = layers.get(layer_name.as_str()).map(|l| l.as_slice());
100 iter::repeat((layer_name, layer)).zip(combos)
101 })
102 .map(|((layer_name, layer), (combo, output))| {
103 let l = layer.ok_or_else(|| {
104 DErr::UnknownComboLayer(layer_name.clone(), combo_to_str(&combo))
105 })?;
106
107 combo
108 .iter()
109 .map(|ck| {
110 l.iter()
111 .filter_map(|(pos, key)| (**key == ck.key).then_some(*pos))
112 .nth(ck.nth)
113 .ok_or_else(|| {
114 DErr::InvalidKeyIndex(
115 combo_to_str(&combo),
116 ck.key.to_string(),
117 ck.nth,
118 )
119 .into()
120 })
121 })
122 .collect::<Result<Vec<_>>>()
123 .map(|combo| (layer_name, (combo, output)))
124 })
125 .try_fold(
126 BTreeMap::new(),
127 |mut acc: BTreeMap<_, Vec<_>>, layer_combo| match layer_combo {
128 Ok((layer_name, combo)) => {
129 acc.entry(layer_name).or_default().push(combo);
130 Ok(acc)
131 }
132 Err(e) => Err(e),
133 },
134 )
135 .map(Combos)
136 }
137}
138
139#[derive(Clone, Debug, Default, PartialEq)]
143pub struct Combos(pub BTreeMap<String, Vec<(Vec<Pos>, Key)>>);
144
145impl Combos {
146 pub(crate) fn into_parse_combos(self, layers: &BTreeMap<String, Layer>) -> Option<ParseCombos> {
147 if self.0.is_empty() {
148 return None;
149 }
150
151 let parse_combos = self
152 .0
153 .into_iter()
154 .map(|(layer_label, combos)| {
155 let layer = &layers.get(&layer_label).unwrap().0;
156
157 let layer_combos = combos
158 .into_iter()
159 .map(move |(combo, key)| {
160 let combo = combo
161 .into_iter()
162 .map(|pos| {
163 let key = layer[pos.row()][pos.col()].clone();
164 let nth = layer[..(pos.row() + 1)]
165 .iter()
166 .flat_map(move |row| &row[..(pos.col() + 1)])
167 .filter(|k| k == &&key)
168 .count();
169 let nth = match nth {
170 0 | 1 => 0,
171 n => n,
172 };
173 ComboKey::new_nth(&key.to_string(), nth)
174 })
175 .collect::<Vec<_>>();
176 (combo, key)
177 })
178 .collect();
179 (layer_label, layer_combos)
180 })
181 .collect();
182
183 Some(ParseCombos(parse_combos))
184 }
185}
186
187fn combo_to_str(combos: &[ComboKey]) -> String {
188 if combos.is_empty() {
189 String::new()
190 } else {
191 combos
192 .iter()
193 .take(combos.len() - 1)
194 .map(|c| format!("{c} "))
195 .chain([combos.last().unwrap().to_string()])
196 .collect::<String>()
197 }
198}
199
200#[cfg(test)]
201pub(crate) fn ck(key: Key, nth: usize) -> ComboKey {
202 ComboKey { key, nth }
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208 use crate::{Key::*, SpecialKey::*};
209
210 #[test]
211 fn parse_combos() {
212 let json = r#"
213 {
214 "main": {
215 "a b": "x"
216 },
217 "edge-cases": {
218 "-1 1-": "6",
219 "--1": "d",
220 "---": "X",
221 "🦀-12": "rpt",
222 "a-1 b-2 c-3 ~-4 rpt-5": "*"
223 }
224 }
225 "#;
226
227 let parse =
228 serde_json::from_str::<ParseCombos>(json).expect("couldn't parse combos json: ");
229
230 let reference = ParseCombos(BTreeMap::from([
231 (
232 "main".to_string(),
233 BTreeMap::from([(vec![ck(Char('a'), 0), ck(Char('b'), 0)], Char('x'))]),
234 ),
235 (
236 "edge-cases".to_string(),
237 BTreeMap::from([
238 (
239 vec![ck(Word("-1".into()), 0), ck(Word("1-".into()), 0)],
240 Char('6'),
241 ),
242 (vec![ck(Char('-'), 0)], Char('d')),
243 (vec![ck(Word("---".into()), 0)], Char('X')),
244 (vec![ck(Char('🦀'), 11)], Special(Repeat)),
245 (
246 vec![
247 ck(Char('a'), 0),
248 ck(Char('b'), 1),
249 ck(Char('c'), 2),
250 ck(Empty, 3),
251 ck(Special(Repeat), 4),
252 ],
253 Transparent,
254 ),
255 ]),
256 ),
257 ]));
258
259 assert_eq!(parse, reference);
260 }
261
262 #[test]
263 fn to_combos_simple() {
264 let json = r#"
265 {
266 "main": {
267 "a b": "x",
268 "e-2 b e": "rpt"
269 }
270 }
271 "#;
272
273 let parse =
274 serde_json::from_str::<ParseCombos>(json).expect("couldn't parse combos json: ");
275
276 let layers = BTreeMap::from_iter([(
277 "main".to_owned(),
278 vec![vec![Char('a'), Char('e'), Char('b'), Char('c'), Char('e')]].into(),
279 )]);
280
281 let combos = parse.into_pos_layers(&layers);
282
283 assert_eq!(
284 combos,
285 Ok(Combos(BTreeMap::from_iter([(
286 "main".to_owned(),
287 vec![
288 (vec![Pos::new(0, 0), Pos::new(0, 2)], Char('x')),
289 (
290 vec![Pos::new(0, 4), Pos::new(0, 2), Pos::new(0, 1)],
291 Special(Repeat)
292 )
293 ]
294 )])))
295 );
296
297 let parse_combos = combos.unwrap().into_parse_combos(&layers);
298
299 let s = serde_json::to_string(&parse_combos).unwrap();
300
301 println!("{s}")
302 }
303}