1use crate::layout;
4use crate::oryx::Snapshot;
5
6pub fn edits(stage: usize) -> Vec<String> {
8 let mut out = Vec::new();
9 for c in layout::changes(stage) {
10 let hold =
11 layout::modifier(c.index).map_or(String::new(), |m| format!(" (keep the {m} hold)"));
12 out.push(format!("key that types {} → {}{hold}", c.from, c.to));
13 }
14 for c in layout::changes(stage).iter().filter(|c| c.right_hand()) {
15 out.push(format!(
16 "mirror layer: key that types {} → {}",
17 c.from, c.to
18 ));
19 }
20 out
21}
22
23pub fn next_stage(current: usize, url: &str) -> Vec<String> {
24 if current >= layout::last() {
25 return vec!["You're on the last stage. Nothing left to edit.".into()];
26 }
27 let next = current + 1;
28 let mut out = vec![
29 format!("Stage {next}: {}", layout::name(next)),
30 format!("Edit: {url}"),
31 ];
32 out.extend(edits(next).into_iter().map(|e| format!(" {e}")));
33 out.push(format!(
34 " vim to practise: {}",
35 layout::vim(next).join(" ")
36 ));
37 out
38}
39
40pub fn next_stage_compact(current: usize, url: &str) -> Vec<String> {
42 if current >= layout::last() {
43 return vec!["You're on the last stage. Nothing left to edit.".into()];
44 }
45 let next = current + 1;
46 let changes = layout::changes(next);
47 let swap = |c: &layout::Change| format!("{}→{}", c.from, c.to);
48 let keys: Vec<String> = changes
49 .iter()
50 .map(|c| match layout::modifier(c.index) {
51 Some(m) => format!("{} (keep {m})", swap(c)),
52 None => swap(c),
53 })
54 .collect();
55 let mirror: Vec<String> = changes
56 .iter()
57 .filter(|c| c.right_hand())
58 .map(swap)
59 .collect();
60 let mut out = vec![
61 format!("Stage {next}: {}", layout::name(next)),
62 format!("keys: {}", keys.join(" ")),
63 ];
64 if !mirror.is_empty() {
65 out.push(format!("mirror: {}", mirror.join(" ")));
66 }
67 out.push(format!("vim: {}", layout::vim(next).join(" ")));
68 out.push(url.to_string());
69 out
70}
71
72pub fn check(snap: &Snapshot, current: usize) -> Vec<String> {
74 let mut out = vec![format!(
75 "{} · revision {} · {}",
76 snap.title, snap.revision, snap.created
77 )];
78 let findings = snap.compare(current);
79 match snap.stage() {
80 Some(s) if s == current && findings.is_empty() => {
81 out.push(format!(
82 "Matches stage {current}: every key, hold and mirror key."
83 ));
84 }
85 Some(s) if s == current + 1 && snap.compare(s).is_empty() => {
86 out.push(format!(
87 "Stage {s}'s edits are all in. Flash it and climb to stage {s}."
88 ));
89 }
90 Some(s) if snap.compare(s).is_empty() => {
91 out.push(format!(
92 "Matches stage {s}, but you're practising stage {current}."
93 ));
94 }
95 _ => {
96 let target =
97 if current < layout::last() && snap.compare(current + 1).len() < findings.len() {
98 current + 1
99 } else {
100 current
101 };
102 let f = snap.compare(target);
103 out.push(format!(
104 "Closest to stage {target}, with {} difference(s):",
105 f.len()
106 ));
107 out.extend(f.iter().take(8).map(|f| format!(" {f}")));
108 if f.len() > 8 {
109 out.push(format!(" and {} more", f.len() - 8));
110 }
111 }
112 }
113 out
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn stage_two_edits_keep_holds_and_list_mirror_keys() {
122 let e = edits(2);
123 assert!(
124 e.contains(&"key that types k → o (keep the alt hold)".to_string()),
125 "{e:?}"
126 );
127 assert_eq!(e.iter().filter(|l| l.starts_with("mirror")).count(), 3);
128 }
129
130 #[test]
131 fn nothing_is_left_after_the_last_stage() {
132 assert_eq!(next_stage(layout::last(), "u").len(), 1);
133 assert_eq!(next_stage(0, "u")[1], "Edit: u");
134 }
135}