mirror of
https://github.com/TECHNOFAB11/tmux-copyrat.git
synced 2025-12-13 00:20:08 +01:00
refactor: wip alphabet & regexes
This commit is contained in:
parent
836d5bdc97
commit
53f7084303
4 changed files with 124 additions and 97 deletions
203
src/alphabets.rs
203
src/alphabets.rs
|
|
@ -1,116 +1,141 @@
|
|||
use crate::error;
|
||||
|
||||
const ALPHABETS: [(&'static str, &'static str); 22] = [
|
||||
("numeric", "1234567890"),
|
||||
("abcd", "abcd"),
|
||||
("qwerty", "asdfqwerzxcvjklmiuopghtybn"),
|
||||
("qwerty-homerow", "asdfjklgh"),
|
||||
("qwerty-left-hand", "asdfqwerzcxv"),
|
||||
("qwerty-right-hand", "jkluiopmyhn"),
|
||||
("azerty", "qsdfazerwxcvjklmuiopghtybn"),
|
||||
("azerty-homerow", "qsdfjkmgh"),
|
||||
("azerty-left-hand", "qsdfazerwxcv"),
|
||||
("azerty-right-hand", "jklmuiophyn"),
|
||||
("qwertz", "asdfqweryxcvjkluiopmghtzbn"),
|
||||
("qwertz-homerow", "asdfghjkl"),
|
||||
("qwertz-left-hand", "asdfqweryxcv"),
|
||||
("qwertz-right-hand", "jkluiopmhzn"),
|
||||
("dvorak", "aoeuqjkxpyhtnsgcrlmwvzfidb"),
|
||||
("dvorak-homerow", "aoeuhtnsid"),
|
||||
("dvorak-left-hand", "aoeupqjkyix"),
|
||||
("dvorak-right-hand", "htnsgcrlmwvz"),
|
||||
("colemak", "arstqwfpzxcvneioluymdhgjbk"),
|
||||
("colemak-homerow", "arstneiodh"),
|
||||
("colemak-left-hand", "arstqwfpzxcv"),
|
||||
("colemak-right-hand", "neioluymjhk"),
|
||||
("numeric", "1234567890"),
|
||||
("abcd", "abcd"),
|
||||
("qwerty", "asdfqwerzxcvjklmiuopghtybn"),
|
||||
("qwerty-homerow", "asdfjklgh"),
|
||||
("qwerty-left-hand", "asdfqwerzcxv"),
|
||||
("qwerty-right-hand", "jkluiopmyhn"),
|
||||
("azerty", "qsdfazerwxcvjklmuiopghtybn"),
|
||||
("azerty-homerow", "qsdfjkmgh"),
|
||||
("azerty-left-hand", "qsdfazerwxcv"),
|
||||
("azerty-right-hand", "jklmuiophyn"),
|
||||
("qwertz", "asdfqweryxcvjkluiopmghtzbn"),
|
||||
("qwertz-homerow", "asdfghjkl"),
|
||||
("qwertz-left-hand", "asdfqweryxcv"),
|
||||
("qwertz-right-hand", "jkluiopmhzn"),
|
||||
("dvorak", "aoeuqjkxpyhtnsgcrlmwvzfidb"),
|
||||
("dvorak-homerow", "aoeuhtnsid"),
|
||||
("dvorak-left-hand", "aoeupqjkyix"),
|
||||
("dvorak-right-hand", "htnsgcrlmwvz"),
|
||||
("colemak", "arstqwfpzxcvneioluymdhgjbk"),
|
||||
("colemak-homerow", "arstneiodh"),
|
||||
("colemak-left-hand", "arstqwfpzxcv"),
|
||||
("colemak-right-hand", "neioluymjhk"),
|
||||
];
|
||||
|
||||
// pub struct Alphabet<'a> {
|
||||
// letters: &'a str,
|
||||
// }
|
||||
|
||||
/// Type-safe string alphabet (newtype).
|
||||
#[derive(Debug)]
|
||||
pub struct Alphabet(pub String);
|
||||
|
||||
impl Alphabet {
|
||||
pub fn hints(&self, matches: usize) -> Vec<String> {
|
||||
let letters: Vec<String> = self.0.chars().map(|s| s.to_string()).collect();
|
||||
/// Create `n` hints.
|
||||
///
|
||||
/// ```
|
||||
/// // The algorithm works as follows:
|
||||
/// // --- lead ----
|
||||
/// // initial state | a b c d
|
||||
///
|
||||
/// // along as we need more hints, and still have capacity, do the following
|
||||
///
|
||||
/// // --- lead ---- --- gen --- -------------- prev ---------------
|
||||
/// // pick d, generate da db dc dd | a b c (d) da db dc dd
|
||||
/// // pick c, generate ca cb cc cd | a b (c) (d) ca cb cc cd da db dc dd
|
||||
/// // pick b, generate ba bb bc bd | a (b) (c) (d) ba bb bc bd ca cb cc cd da db dc dd
|
||||
/// // 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();
|
||||
|
||||
let mut expansion = letters.clone();
|
||||
let mut expanded: Vec<String> = Vec::new();
|
||||
let mut lead = letters.clone();
|
||||
let mut prev: Vec<String> = Vec::new();
|
||||
|
||||
loop {
|
||||
if expansion.len() + expanded.len() >= matches {
|
||||
break;
|
||||
}
|
||||
if expansion.is_empty() {
|
||||
break;
|
||||
}
|
||||
loop {
|
||||
if lead.len() + prev.len() >= n {
|
||||
break;
|
||||
}
|
||||
|
||||
let prefix = expansion.pop().expect("Ouch!");
|
||||
let sub_expansion: Vec<String> = letters
|
||||
.iter()
|
||||
.take(matches - expansion.len() - expanded.len())
|
||||
.map(|s| prefix.clone() + s)
|
||||
.collect();
|
||||
if lead.is_empty() {
|
||||
break;
|
||||
}
|
||||
let prefix = lead.pop().unwrap();
|
||||
|
||||
expanded.splice(0..0, sub_expansion);
|
||||
// generate characters pairs
|
||||
let gen: Vec<String> = letters
|
||||
.iter()
|
||||
.take(n - lead.len() - prev.len())
|
||||
.map(|s| prefix.clone() + s)
|
||||
.collect();
|
||||
|
||||
// Insert gen in front of prev
|
||||
prev.splice(0..0, gen);
|
||||
}
|
||||
|
||||
lead = lead.iter().take(n - prev.len()).cloned().collect();
|
||||
lead.append(&mut prev);
|
||||
lead
|
||||
}
|
||||
|
||||
expansion = expansion.iter().take(matches - expanded.len()).cloned().collect();
|
||||
expansion.append(&mut expanded);
|
||||
expansion
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn get_alphabet(alphabet_name: &str) -> Alphabet {
|
||||
// let alphabets: HashMap<&str, &str> = ALPHABETS.iter().cloned().collect();
|
||||
|
||||
// alphabets
|
||||
// .get(alphabet_name)
|
||||
// .expect(format!("Unknown alphabet: {}", alphabet_name).as_str());
|
||||
|
||||
// Alphabet::new(alphabets[alphabet_name])
|
||||
// }
|
||||
|
||||
/// 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 = ALPHABETS.iter().find(|&(name, _letters)| name == &src);
|
||||
match alphabet {
|
||||
Some((_name, letters)) => Ok(Alphabet(letters.to_string())),
|
||||
None => Err(error::ParseError::UnknownAlphabet),
|
||||
}
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn simple_matches() {
|
||||
let alphabet = Alphabet("abcd".to_string());
|
||||
let hints = alphabet.hints(3);
|
||||
assert_eq!(hints, ["a", "b", "c"]);
|
||||
}
|
||||
#[test]
|
||||
fn simple_matches() {
|
||||
let alphabet = Alphabet("abcd".to_string());
|
||||
let hints = alphabet.make_hints(3);
|
||||
assert_eq!(hints, ["a", "b", "c"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn composed_matches() {
|
||||
let alphabet = Alphabet("abcd".to_string());
|
||||
let hints = alphabet.hints(6);
|
||||
assert_eq!(hints, ["a", "b", "c", "da", "db", "dc"]);
|
||||
}
|
||||
#[test]
|
||||
fn composed_matches() {
|
||||
let alphabet = Alphabet("abcd".to_string());
|
||||
let hints = alphabet.make_hints(6);
|
||||
assert_eq!(hints, ["a", "b", "c", "da", "db", "dc"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn composed_matches_multiple() {
|
||||
let alphabet = Alphabet("abcd".to_string());
|
||||
let hints = alphabet.hints(8);
|
||||
assert_eq!(hints, ["a", "b", "ca", "cb", "da", "db", "dc", "dd"]);
|
||||
}
|
||||
#[test]
|
||||
fn composed_matches_multiple() {
|
||||
let alphabet = Alphabet("abcd".to_string());
|
||||
let hints = alphabet.make_hints(8);
|
||||
assert_eq!(hints, ["a", "b", "ca", "cb", "da", "db", "dc", "dd"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn composed_matches_max() {
|
||||
let alphabet = Alphabet("ab".to_string());
|
||||
let hints = alphabet.hints(8);
|
||||
assert_eq!(hints, ["aa", "ab", "ba", "bb"]);
|
||||
}
|
||||
#[test]
|
||||
fn composed_matches_max_2() {
|
||||
let alphabet = Alphabet("ab".to_string());
|
||||
let hints = alphabet.make_hints(8);
|
||||
assert_eq!(hints, ["aa", "ab", "ba", "bb"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn composed_matches_max_4() {
|
||||
let alphabet = Alphabet("abcd".to_string());
|
||||
let hints = alphabet.make_hints(13);
|
||||
assert_eq!(
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ pub struct CliOpt {
|
|||
/// 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".
|
||||
#[clap(short = "k", long, default_value = "qwerty",
|
||||
#[clap(short = "k", long, default_value = "dvorak",
|
||||
parse(try_from_str = alphabets::parse_alphabet))]
|
||||
alphabet: alphabets::Alphabet,
|
||||
|
||||
|
|
|
|||
13
src/state.rs
13
src/state.rs
|
|
@ -1,8 +1,8 @@
|
|||
use super::alphabets::Alphabet;
|
||||
use regex::Regex;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
|
||||
use crate::alphabets::Alphabet;
|
||||
use crate::regexes::{EXCLUDE_PATTERNS, PATTERNS};
|
||||
|
||||
#[derive(Clone)]
|
||||
|
|
@ -37,19 +37,19 @@ impl<'a> PartialEq for Match<'a> {
|
|||
pub struct State<'a> {
|
||||
pub lines: &'a Vec<&'a str>,
|
||||
alphabet: &'a Alphabet,
|
||||
regexp: &'a Vec<String>,
|
||||
custom_regexes: &'a Vec<String>,
|
||||
}
|
||||
|
||||
impl<'a> State<'a> {
|
||||
pub fn new(
|
||||
lines: &'a Vec<&'a str>,
|
||||
alphabet: &'a Alphabet,
|
||||
regexp: &'a Vec<String>,
|
||||
custom_regexes: &'a Vec<String>,
|
||||
) -> State<'a> {
|
||||
State {
|
||||
lines,
|
||||
alphabet,
|
||||
regexp,
|
||||
custom_regexes,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -62,7 +62,7 @@ impl<'a> State<'a> {
|
|||
.collect::<Vec<_>>();
|
||||
|
||||
let custom_patterns = self
|
||||
.regexp
|
||||
.custom_regexes
|
||||
.iter()
|
||||
.map(|regexp| ("custom", Regex::new(regexp).expect("Invalid custom regexp")))
|
||||
.collect::<Vec<_>>();
|
||||
|
|
@ -123,8 +123,7 @@ impl<'a> State<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
// let alphabet = super::alphabets::get_alphabet(self.alphabet);
|
||||
let mut hints = self.alphabet.hints(matches.len());
|
||||
let mut hints = self.alphabet.make_hints(matches.len());
|
||||
|
||||
// This looks wrong but we do a pop after
|
||||
if !reverse {
|
||||
|
|
|
|||
|
|
@ -426,6 +426,9 @@ impl<'a> View<'a> {
|
|||
event::Key::Left => self.prev(),
|
||||
event::Key::Right => self.next(),
|
||||
|
||||
event::Key::Char(_ch @ 'n') => self.next(),
|
||||
event::Key::Char(_ch @ 'N') => self.prev(),
|
||||
|
||||
// TODO: use a Trie or another data structure to determine
|
||||
// if the entered key belongs to a longer hint.
|
||||
// Attempts at finding a match with a corresponding hint.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue