mirror of
https://github.com/TECHNOFAB11/tmux-copyrat.git
synced 2025-12-12 16:10:07 +01:00
refactor: better alphabets
This commit is contained in:
parent
53f7084303
commit
56106f4e38
2 changed files with 83 additions and 30 deletions
105
src/alphabets.rs
105
src/alphabets.rs
|
|
@ -1,8 +1,7 @@
|
|||
use crate::error;
|
||||
|
||||
const ALPHABETS: [(&'static str, &'static str); 22] = [
|
||||
("numeric", "1234567890"),
|
||||
("abcd", "abcd"),
|
||||
const ALPHABETS: [(&'static str, &'static str); 21] = [
|
||||
// ("abcd", "abcd"),
|
||||
("qwerty", "asdfqwerzxcvjklmiuopghtybn"),
|
||||
("qwerty-homerow", "asdfjklgh"),
|
||||
("qwerty-left-hand", "asdfqwerzcxv"),
|
||||
|
|
@ -23,14 +22,45 @@ const ALPHABETS: [(&'static str, &'static str); 22] = [
|
|||
("colemak-homerow", "arstneiodh"),
|
||||
("colemak-left-hand", "arstqwfpzxcv"),
|
||||
("colemak-right-hand", "neioluymjhk"),
|
||||
(
|
||||
"longest",
|
||||
"aoeuqjkxpyhtnsgcrlmwvzfidb;,~<>'@!#$%^&*~1234567890",
|
||||
),
|
||||
];
|
||||
|
||||
/// Parse a name string into `Alphabet`, used during CLI parsing.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Letters 'n' and 'N' are systematically removed to prevent conflict with
|
||||
/// navigation keys (arrows and 'n' 'N').
|
||||
pub fn parse_alphabet(src: &str) -> Result<Alphabet, error::ParseError> {
|
||||
let alphabet_pair = ALPHABETS.iter().find(|&(name, _letters)| name == &src);
|
||||
|
||||
match alphabet_pair {
|
||||
Some((_name, letters)) => {
|
||||
let letters = letters.replace(&['n', 'N'][..], "");
|
||||
Ok(Alphabet(letters.to_string()))
|
||||
}
|
||||
None => Err(error::ParseError::UnknownAlphabet),
|
||||
}
|
||||
}
|
||||
|
||||
/// Type-safe string alphabet (newtype).
|
||||
#[derive(Debug)]
|
||||
pub struct Alphabet(pub String);
|
||||
|
||||
impl Alphabet {
|
||||
/// Create `n` hints.
|
||||
/// Create `n` hints from the Alphabet.
|
||||
///
|
||||
/// An Alphabet of `m` letters can produce at most `m^2` hints. In case
|
||||
/// this limit is exceeded, this function will generate the `n` hints from
|
||||
/// an Alphabet which has more letters (50). This will ensure 2500 hints
|
||||
/// can be generated, which should cover all use cases (I think even
|
||||
/// easymotion has less).
|
||||
///
|
||||
/// If more hints are needed, unfortunately, this will keep producing
|
||||
/// empty (`""`) hints.
|
||||
///
|
||||
/// ```
|
||||
/// // The algorithm works as follows:
|
||||
|
|
@ -46,7 +76,19 @@ impl Alphabet {
|
|||
/// // pick a, generate aa ab ac ad | (a) (b) (c) (d) aa ab ac ad ba bb bc bd ca cb cc cd da db dc dd
|
||||
/// ```
|
||||
pub fn make_hints(&self, n: usize) -> Vec<String> {
|
||||
let letters: Vec<String> = self.0.chars().map(|s| s.to_string()).collect();
|
||||
// Shortcut if we have enough letters in the Alphabet.
|
||||
if self.0.len() >= n {
|
||||
return self.0.chars().take(n).map(|c| c.to_string()).collect();
|
||||
}
|
||||
|
||||
// Use the "longest" alphabet if the current alphabet cannot produce as
|
||||
// many hints as asked.
|
||||
let letters: Vec<char> = if self.0.len().pow(2) >= n {
|
||||
self.0.chars().collect()
|
||||
} else {
|
||||
let alt_alphabet = parse_alphabet("longest").unwrap();
|
||||
alt_alphabet.0.chars().collect()
|
||||
};
|
||||
|
||||
let mut lead = letters.clone();
|
||||
let mut prev: Vec<String> = Vec::new();
|
||||
|
|
@ -65,34 +107,23 @@ impl Alphabet {
|
|||
let gen: Vec<String> = letters
|
||||
.iter()
|
||||
.take(n - lead.len() - prev.len())
|
||||
.map(|s| prefix.clone() + s)
|
||||
.map(|c| format!("{}{}", prefix, c))
|
||||
.collect();
|
||||
|
||||
// Insert gen in front of prev
|
||||
prev.splice(0..0, gen);
|
||||
prev.splice(..0, gen);
|
||||
}
|
||||
|
||||
lead = lead.iter().take(n - prev.len()).cloned().collect();
|
||||
lead.append(&mut prev);
|
||||
lead
|
||||
}
|
||||
}
|
||||
// Finalize by concatenating the lead and prev components, filling
|
||||
// with "" as necessary.
|
||||
let lead: Vec<String> = lead.iter().map(|c| c.to_string()).collect();
|
||||
|
||||
/// Parse a name string into `Alphabet`, supporting the CLI.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Letters 'n' and 'N' are systematically removed to prevent conflict with
|
||||
/// navigation keys (arrows and 'n' 'N').
|
||||
pub fn parse_alphabet(src: &str) -> Result<Alphabet, error::ParseError> {
|
||||
let alphabet_pair = ALPHABETS.iter().find(|&(name, _letters)| name == &src);
|
||||
let filler: Vec<String> = std::iter::repeat("")
|
||||
.take(n - lead.len() - prev.len())
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
|
||||
match alphabet_pair {
|
||||
Some((_name, letters)) => {
|
||||
let letters = letters.replace(&['n', 'N'][..], "");
|
||||
Ok(Alphabet(letters.to_string()))
|
||||
}
|
||||
None => Err(error::ParseError::UnknownAlphabet),
|
||||
[lead, prev, filler].concat()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -124,7 +155,7 @@ mod tests {
|
|||
#[test]
|
||||
fn composed_matches_max_2() {
|
||||
let alphabet = Alphabet("ab".to_string());
|
||||
let hints = alphabet.make_hints(8);
|
||||
let hints = alphabet.make_hints(4);
|
||||
assert_eq!(hints, ["aa", "ab", "ba", "bb"]);
|
||||
}
|
||||
|
||||
|
|
@ -136,6 +167,24 @@ mod tests {
|
|||
hints,
|
||||
["a", "ba", "bb", "bc", "bd", "ca", "cb", "cc", "cd", "da", "db", "dc", "dd"]
|
||||
);
|
||||
// a (b) (c) (d) a ba bc bd ca cb cc cd da db dc dd
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matches_with_longest_alphabet() {
|
||||
let alphabet = Alphabet("ab".to_string());
|
||||
let hints = alphabet.make_hints(2500);
|
||||
assert_eq!(hints.len(), 2500);
|
||||
assert_eq!(&hints[..3], ["aa", "ao", "ae"]);
|
||||
assert_eq!(&hints[2497..], ["08", "09", "00"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matches_exceed_longest_alphabet() {
|
||||
let alphabet = Alphabet("ab".to_string());
|
||||
let hints = alphabet.make_hints(10000);
|
||||
// 2500 unique hints are produced from the longest alphabet
|
||||
// The 7500 last ones come from the filler ("" empty hints).
|
||||
assert_eq!(hints.len(), 10000);
|
||||
assert!(&hints[2500..].iter().all(|s| s == ""));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,8 +57,12 @@ pub struct CliOpt {
|
|||
/// Alphabet to draw hints from.
|
||||
///
|
||||
/// Possible values are "{A}", "{A}-homerow", "{A}-left-hand",
|
||||
/// "{A}-right-hand", where "{A}" is one of "qwerty", "azerty", "qwertz",
|
||||
/// "dvorak", "colemak". Examples: "qwerty", "dvorak-homerow".
|
||||
/// "{A}-right-hand", where "{A}" is one of "qwerty", "azerty", "qwertz"
|
||||
/// "dvorak", "colemak".
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// "qwerty", "dvorak-homerow", "azerty-right-hand".
|
||||
#[clap(short = "k", long, default_value = "dvorak",
|
||||
parse(try_from_str = alphabets::parse_alphabet))]
|
||||
alphabet: alphabets::Alphabet,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue