chewing/editor/zhuyin_layout/
hsu.rs

1//! Hsu keyboard layout
2
3use crate::{
4    editor::keyboard::KeyCode,
5    syl,
6    zhuyin::{Bopomofo, BopomofoKind, Syllable},
7};
8
9use super::{KeyBehavior, KeyEvent, SyllableEditor};
10
11/// TODO: docs
12#[derive(Debug, Clone, Copy)]
13pub struct Hsu {
14    syllable: Syllable,
15}
16
17impl Hsu {
18    /// TODO: docs
19    pub fn new() -> Hsu {
20        Hsu {
21            syllable: Default::default(),
22        }
23    }
24
25    ///
26    /// tone key is hsu_end_key
27    ///  KeyCode::S -> Bopomofo::TONE5
28    ///  KeyCode::D -> Bopomofo::TONE2
29    ///  KeyCode::F -> Bopomofo::TONE3
30    ///  KeyCode::J -> Bopomofo::TONE4
31    ///  KeyCode::Space -> Bopomofo::TONE1
32    ///
33    fn is_hsu_end_key(&self, key: KeyEvent) -> bool {
34        // TODO allow customize end key mapping
35        match key.code {
36            KeyCode::S | KeyCode::D | KeyCode::F | KeyCode::J | KeyCode::Space => {
37                !self.syllable.is_empty()
38            }
39            _ => false,
40        }
41    }
42    fn has_initial_or_medial(&self) -> bool {
43        self.syllable.has_initial() || self.syllable.has_medial()
44    }
45
46    const ALT_TABLE: &'static [(Syllable, &'static [Syllable])] = &[
47        (syl![Bopomofo::C], &[syl![Bopomofo::EI]]),
48        (syl![Bopomofo::I], &[syl![Bopomofo::EH]]),
49        (syl![Bopomofo::S], &[syl![Bopomofo::TONE5]]),
50        (syl![Bopomofo::D], &[syl![Bopomofo::TONE2]]),
51        (syl![Bopomofo::F], &[syl![Bopomofo::TONE3]]),
52        (syl![Bopomofo::E], &[syl![Bopomofo::G]]),
53        (syl![Bopomofo::O], &[syl![Bopomofo::H]]),
54        (
55            syl![Bopomofo::ZH],
56            &[syl![Bopomofo::J], syl![Bopomofo::TONE4]],
57        ),
58        (syl![Bopomofo::ANG], &[syl![Bopomofo::K]]),
59        (
60            syl![Bopomofo::ER],
61            &[syl![Bopomofo::L], syl![Bopomofo::ENG]],
62        ),
63        (syl![Bopomofo::SH], &[syl![Bopomofo::X]]),
64        (syl![Bopomofo::CH], &[syl![Bopomofo::Q]]),
65        (syl![Bopomofo::EN], &[syl![Bopomofo::N]]),
66        (syl![Bopomofo::AN], &[syl![Bopomofo::M]]),
67    ];
68}
69
70impl Default for Hsu {
71    fn default() -> Self {
72        Self::new()
73    }
74}
75
76impl SyllableEditor for Hsu {
77    fn key_press(&mut self, key: KeyEvent) -> KeyBehavior {
78        if self.is_hsu_end_key(key) {
79            if !self.syllable.has_medial() && !self.syllable.has_rime() {
80                if let Some(key) = self.syllable.initial() {
81                    match key {
82                        Bopomofo::J => {
83                            self.syllable.update(Bopomofo::ZH);
84                        }
85                        Bopomofo::Q => {
86                            self.syllable.update(Bopomofo::CH);
87                        }
88                        Bopomofo::X => {
89                            self.syllable.update(Bopomofo::SH);
90                        }
91                        Bopomofo::H => {
92                            self.syllable.remove_initial();
93                            self.syllable.update(Bopomofo::O);
94                        }
95                        Bopomofo::G => {
96                            self.syllable.remove_initial();
97                            self.syllable.update(Bopomofo::E);
98                        }
99                        Bopomofo::M => {
100                            self.syllable.remove_initial();
101                            self.syllable.update(Bopomofo::AN);
102                        }
103                        Bopomofo::N => {
104                            self.syllable.remove_initial();
105                            self.syllable.update(Bopomofo::EN);
106                        }
107                        Bopomofo::K => {
108                            self.syllable.remove_initial();
109                            self.syllable.update(Bopomofo::ANG);
110                        }
111                        Bopomofo::L => {
112                            self.syllable.remove_initial();
113                            self.syllable.update(Bopomofo::ER);
114                        }
115                        _ => (),
116                    }
117                }
118            }
119
120            // fuzzy ㄍㄧ to ㄐㄧ and ㄍㄩ to ㄐㄩ
121            match (self.syllable.initial(), self.syllable.medial()) {
122                (Some(Bopomofo::G), Some(Bopomofo::I))
123                | (Some(Bopomofo::G), Some(Bopomofo::IU)) => {
124                    self.syllable.update(Bopomofo::J);
125                }
126                _ => (),
127            }
128
129            match key.code {
130                // KeyCode::Space => Some(Bopomofo::TONE1),
131                KeyCode::D => self.syllable.update(Bopomofo::TONE2),
132                KeyCode::F => self.syllable.update(Bopomofo::TONE3),
133                KeyCode::J => self.syllable.update(Bopomofo::TONE4),
134                KeyCode::S => self.syllable.update(Bopomofo::TONE5),
135                _ => {
136                    self.syllable.remove_tone();
137                }
138            };
139            KeyBehavior::Commit
140        } else {
141            let bopomofo = match key.code {
142                KeyCode::A => {
143                    if self.has_initial_or_medial() {
144                        Bopomofo::EI
145                    } else {
146                        Bopomofo::C
147                    }
148                }
149                KeyCode::B => Bopomofo::B,
150                KeyCode::C => Bopomofo::SH,
151                KeyCode::D => Bopomofo::D,
152                KeyCode::E => {
153                    if self.syllable.has_medial() {
154                        Bopomofo::EH
155                    } else {
156                        Bopomofo::I
157                    }
158                }
159                KeyCode::F => Bopomofo::F,
160                KeyCode::G => {
161                    if self.has_initial_or_medial() {
162                        Bopomofo::E
163                    } else {
164                        Bopomofo::G
165                    }
166                }
167                KeyCode::H => {
168                    if self.has_initial_or_medial() {
169                        Bopomofo::O
170                    } else {
171                        Bopomofo::H
172                    }
173                }
174                KeyCode::I => Bopomofo::AI,
175                KeyCode::J => Bopomofo::ZH,
176                KeyCode::K => {
177                    if self.has_initial_or_medial() {
178                        Bopomofo::ANG
179                    } else {
180                        Bopomofo::K
181                    }
182                }
183                KeyCode::L => {
184                    if self.has_initial_or_medial() {
185                        Bopomofo::ENG
186                    } else {
187                        Bopomofo::L
188                    }
189                }
190                KeyCode::M => {
191                    if self.has_initial_or_medial() {
192                        Bopomofo::AN
193                    } else {
194                        Bopomofo::M
195                    }
196                }
197                KeyCode::N => {
198                    if self.has_initial_or_medial() {
199                        Bopomofo::EN
200                    } else {
201                        Bopomofo::N
202                    }
203                }
204                KeyCode::O => Bopomofo::OU,
205                KeyCode::P => Bopomofo::P,
206                KeyCode::R => Bopomofo::R,
207                KeyCode::S => Bopomofo::S,
208                KeyCode::T => Bopomofo::T,
209                KeyCode::U => Bopomofo::IU,
210                KeyCode::V => Bopomofo::CH,
211                KeyCode::W => Bopomofo::AU,
212                KeyCode::X => Bopomofo::U,
213                KeyCode::Y => Bopomofo::A,
214                KeyCode::Z => Bopomofo::Z,
215                _ => return KeyBehavior::NoWord,
216            };
217            let kind = bopomofo.kind();
218
219            // fuzzy ㄍㄧ to ㄐㄧ and ㄍㄩ to ㄐㄩ
220            match (self.syllable.initial(), self.syllable.medial()) {
221                (Some(Bopomofo::G), Some(Bopomofo::I))
222                | (Some(Bopomofo::G), Some(Bopomofo::IU)) => {
223                    self.syllable.update(Bopomofo::J);
224                }
225                _ => (),
226            }
227
228            // ㄐㄑㄒ must be followed by ㄧ or ㄩ. If not, convert them to ㄓㄔㄕ
229            if (kind == BopomofoKind::Medial && bopomofo == Bopomofo::U)
230                || (kind == BopomofoKind::Rime && self.syllable.medial().is_none())
231            {
232                match self.syllable.initial() {
233                    Some(Bopomofo::J) => {
234                        self.syllable.update(Bopomofo::ZH);
235                    }
236                    Some(Bopomofo::Q) => {
237                        self.syllable.update(Bopomofo::CH);
238                    }
239                    Some(Bopomofo::X) => {
240                        self.syllable.update(Bopomofo::SH);
241                    }
242                    _ => (),
243                }
244            }
245
246            // Likeweise, when ㄓㄔㄕ is followed by ㄧ or ㄩ, convert them to ㄐㄑㄒ
247            if bopomofo == Bopomofo::I || bopomofo == Bopomofo::IU {
248                match self.syllable.initial() {
249                    Some(Bopomofo::ZH) => {
250                        self.syllable.update(Bopomofo::J);
251                    }
252                    Some(Bopomofo::CH) => {
253                        self.syllable.update(Bopomofo::Q);
254                    }
255                    Some(Bopomofo::SH) => {
256                        self.syllable.update(Bopomofo::X);
257                    }
258                    _ => (),
259                }
260            }
261
262            self.syllable.update(bopomofo);
263
264            KeyBehavior::Absorb
265        }
266    }
267
268    fn is_empty(&self) -> bool {
269        self.syllable.is_empty()
270    }
271
272    fn remove_last(&mut self) {
273        self.syllable.pop();
274    }
275
276    fn clear(&mut self) {
277        self.syllable.clear();
278    }
279
280    fn read(&self) -> Syllable {
281        self.syllable
282    }
283
284    fn alt_syllables(&self, syl: Syllable) -> &[Syllable] {
285        for entry in Self::ALT_TABLE {
286            if entry.0 == syl {
287                return entry.1;
288            }
289        }
290        &[]
291    }
292
293    fn clone(&self) -> Box<dyn SyllableEditor> {
294        Box::new(Clone::clone(self))
295    }
296}
297
298#[cfg(test)]
299mod test {
300
301    use crate::{
302        editor::{
303            keyboard::{KeyCode, KeyboardLayout, Qwerty},
304            zhuyin_layout::SyllableEditor,
305        },
306        zhuyin::Bopomofo,
307    };
308
309    use super::Hsu;
310
311    #[test]
312    fn cen() {
313        let mut hsu = Hsu::new();
314        let keyboard = Qwerty;
315        hsu.key_press(keyboard.map(KeyCode::C));
316        hsu.key_press(keyboard.map(KeyCode::E));
317        hsu.key_press(keyboard.map(KeyCode::N));
318        hsu.key_press(keyboard.map(KeyCode::Space));
319        let result = hsu.read();
320        assert_eq!(result.initial(), Some(Bopomofo::X));
321        assert_eq!(result.medial(), Some(Bopomofo::I));
322        assert_eq!(result.rime(), Some(Bopomofo::EN));
323    }
324
325    #[test]
326    fn convert_n_to_en() {
327        let mut hsu = Hsu::new();
328        let keyboard = Qwerty;
329        hsu.key_press(keyboard.map(KeyCode::N));
330        hsu.key_press(keyboard.map(KeyCode::F));
331        let result = hsu.read();
332        assert_eq!(result.rime(), Some(Bopomofo::EN));
333    }
334}