use std::{
fs::File,
io::{BufRead, BufReader, Result},
path::Path,
};
use crate::conversion::Symbol;
#[derive(Debug, Default, Clone)]
pub struct SymbolSelector {
category: Vec<(String, usize)>,
table: Vec<String>,
cursor: Option<u8>,
}
impl SymbolSelector {
pub fn open<P: AsRef<Path>>(path: P) -> Result<SymbolSelector> {
let file = File::open(path)?;
let reader = BufReader::new(file);
SymbolSelector::new(reader)
}
pub fn new<R: BufRead>(reader: R) -> Result<SymbolSelector> {
let mut category = Vec::new();
let mut table = Vec::new();
for line in reader.lines() {
let line = line?;
if line.contains('=') {
let (cat, tab) = line.split_once('=').expect("at last one separator");
category.push((cat.to_owned(), table.len()));
table.push(tab.to_owned());
} else {
category.push((line, usize::MAX));
}
}
Ok(SymbolSelector {
category,
table,
cursor: None,
})
}
pub(crate) fn is_empty(&self) -> bool {
self.category.is_empty()
}
pub(crate) fn menu(&self) -> Vec<String> {
match self.cursor {
Some(cursor) => self.table[cursor as usize]
.chars()
.map(|c| c.to_string())
.collect(),
None => self.category.iter().map(|cat| cat.0.clone()).collect(),
}
}
pub(crate) fn select(&mut self, n: usize) -> Option<Symbol> {
match self.cursor {
None => {
if self.category.len() <= n {
return None;
}
let cat = &self.category[n];
if cat.1 == usize::MAX {
self.cursor = None;
cat.0.chars().next().map(Symbol::from)
} else {
self.cursor = Some(cat.1 as u8);
None
}
}
Some(cursor) => {
self.cursor = None;
self.table[cursor as usize].chars().nth(n).map(Symbol::from)
}
}
}
}
#[derive(Debug)]
pub(crate) struct SpecialSymbolSelector {
symbol: Symbol,
}
impl SpecialSymbolSelector {
pub(crate) fn new(symbol: Symbol) -> SpecialSymbolSelector {
SpecialSymbolSelector { symbol }
}
pub(crate) fn menu(&self) -> Vec<String> {
match self.find_category() {
Some(cat) => cat.chars().skip(1).map(|c| c.to_string()).collect(),
None => Vec::new(),
}
}
pub(crate) fn select(&self, n: usize) -> Option<Symbol> {
self.find_category()
.and_then(|cat| cat.chars().skip(1).nth(n).map(Symbol::from))
}
fn find_category(&self) -> Option<&str> {
Self::TABLE
.iter()
.find(|cat| cat.contains(self.symbol.to_char().unwrap()))
.copied()
}
const TABLE: &'static [&'static str; 55] = &[
"0ø",
"[「『《〈【〔",
"]」』》〉】〕",
"{{",
"}}",
"<,←",
">。→.",
"??¿",
"!!Ⅰ¡",
"@@Ⅱ⊕⊙㊣﹫",
"##Ⅲ﹟",
"$$Ⅳ€﹩¢∮£¥",
"%%Ⅴ",
"^︿Ⅵ﹀︽︾",
"&&Ⅶ﹠",
"**Ⅷ×※╳﹡☯☆★",
"((Ⅸ",
"))Ⅹ",
"_-—-―–←→_ ̄﹍﹉﹎﹊﹏﹋…‥¯",
"++±﹢",
"==≒≠≡≦≧﹦",
"`』『′‵",
"~~",
"::;︰﹕",
"\";",
"\'、"'…‥",
"\\\↖↘﹨",
"//÷↗↙∕",
"|↑↓∣∥|︳︴",
"AÅΑα├╠╟╞",
"BΒβ∵",
"CΧχ┘╯╝╜╛㏄℃㎝♣©",
"DΔδ◇◆┤╣╢╡♦",
"EΕε┐╮╗╓╕",
"FΦψ│║♀",
"GΓγ",
"HΗη♥",
"IΙι",
"Jφ",
"KΚκ㎞㏎",
"LΛλ㏒㏑",
"MΜμ♂ℓ㎎㏕㎜㎡",
"NΝν№",
"OΟο",
"PΠπ",
"QΘθД┌╭╔╓╒",
"RΡρ─═®",
"SΣσ∴□■┼╬╪╫∫§♠",
"TΤτθ△▲▽▼™⊿™",
"UΥυμ∪∩",
"Vν",
"WΩω┬╦╤╥",
"XΞξ┴╩╧╨",
"YΨ",
"ZΖζ└╰╚╙╘",
];
}
#[cfg(test)]
mod tests {
use std::io;
use super::SymbolSelector;
use crate::conversion::Symbol;
#[test]
fn select_level_one_leaf() {
let reader = io::Cursor::new("…\n※\n常用符號=,、。\n");
let mut sel = SymbolSelector::new(reader).expect("should parse");
assert_eq!(vec!["…", "※", "常用符號"], sel.menu());
assert_eq!(Symbol::from('…'), sel.select(0).unwrap());
}
#[test]
fn select_level_two_leaf() {
let reader = io::Cursor::new("…\n※\n常用符號=,、。\n");
let mut sel = SymbolSelector::new(reader).expect("should parse");
assert_eq!(vec!["…", "※", "常用符號"], sel.menu());
assert_eq!(None, sel.select(2));
assert_eq!(vec![",", "、", "。"], sel.menu());
assert_eq!(Symbol::from(','), sel.select(0).unwrap());
}
#[test]
fn select_empty_level_two_leaf() {
let reader = io::Cursor::new("…\n※\n常用符號=,、。\n\n");
let mut sel = SymbolSelector::new(reader).expect("should parse");
assert_eq!(vec!["…", "※", "常用符號", ""], sel.menu());
assert_eq!(None, sel.select(3));
assert_eq!(vec!["…", "※", "常用符號", ""], sel.menu());
}
}