feat: use a Trie for hint check

This commit is contained in:
graelo 2020-05-31 22:45:36 +02:00
parent 2e9b5fb7be
commit be698ab741
4 changed files with 59 additions and 26 deletions

7
Cargo.lock generated
View file

@ -65,6 +65,7 @@ version = "0.1.0"
dependencies = [
"clap 3.0.0-beta.1 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
"sequence_trie 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"termion 1.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -195,6 +196,11 @@ name = "regex-syntax"
version = "0.6.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "sequence_trie"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "strsim"
version = "0.10.0"
@ -361,6 +367,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
"checksum regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a6020f034922e3194c711b82a627453881bc4682166cabb07134a10c26ba7692"
"checksum regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)" = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae"
"checksum sequence_trie 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1ee22067b7ccd072eeb64454b9c6e1b33b61cd0d49e895fd48676a184580e0c3"
"checksum strsim 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
"checksum syn 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)" = "95b5f192649e48a5302a13f2feb224df883b98933222369e4b3b0fe2a5447269"
"checksum syn-mid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a"

View file

@ -12,6 +12,7 @@ license = "MIT"
termion = "1.5"
regex = "1.3.1"
clap = { version = "3.0.0-beta.1", features = ["suggestions", "color", "wrap_help", "term_size"]}
sequence_trie = "0.3.6"
[[bin]]
name = "copyrat"

View file

@ -1,4 +1,5 @@
use regex::Regex;
use sequence_trie::SequenceTrie;
use std::collections::HashMap;
use std::fmt;
@ -70,6 +71,8 @@ impl<'a> State<'a> {
}
}
/// Returns a vector of `Match`es, each corresponding to a pattern match
/// in the lines, its location (x, y), and associated hint.
pub fn matches(&self, unique: bool) -> Vec<Match<'a>> {
let mut raw_matches = self.raw_matches();
@ -86,6 +89,9 @@ impl<'a> State<'a> {
matches
}
/// Internal function that searches the state lines for pattern matches.
/// Returns a vector of `RawMatch`es (text, location, pattern id) without
/// an associated hint. The hint is attached to `Match`, not to `RawMatch`.
fn raw_matches(&self) -> Vec<RawMatch<'a>> {
let mut matches = Vec::new();
@ -219,6 +225,24 @@ impl<'a> State<'a> {
result
}
/// Builds a `SequenceTrie` that helps determine if a sequence of keys
/// entered by the user corresponds to a match. This kind of lookup
/// directly returns a reference to the corresponding `Match` if any.
pub fn build_lookup_trie(matches: &'a Vec<Match<'a>>) -> SequenceTrie<char, usize> {
let mut trie = SequenceTrie::new();
for (index, mat) in matches.iter().enumerate() {
let hint_chars = mat.hint.chars().collect::<Vec<char>>();
// no need to insert twice the same hint
if trie.get(&hint_chars).is_none() {
trie.insert_owned(hint_chars, index);
}
}
trie
}
}
#[cfg(test)]

View file

@ -1,15 +1,17 @@
use super::{colors, state};
use crate::error::ParseError;
use clap::Clap;
use sequence_trie::SequenceTrie;
use std::char;
use std::io;
use std::str::FromStr;
use termion::{self, color, cursor, event, style};
use crate::error::ParseError;
use crate::{colors, state};
pub struct View<'a> {
state: &'a mut state::State<'a>,
matches: Vec<state::Match<'a>>,
lookup_trie: SequenceTrie<char, usize>,
focus_index: usize,
hint_alignment: &'a HintAlignment,
rendering_colors: &'a ViewColors,
@ -117,11 +119,13 @@ impl<'a> View<'a> {
hint_style: Option<HintStyle>,
) -> View<'a> {
let matches = state.matches(unique_hint);
let lookup_trie = state::State::build_lookup_trie(&matches);
let focus_index = if state.reverse { matches.len() - 1 } else { 0 };
View {
state,
matches,
lookup_trie,
focus_index,
hint_alignment,
rendering_colors,
@ -385,15 +389,6 @@ impl<'a> View<'a> {
let mut typed_hint = String::new();
// TODO: simplify
let longest_hint = self
.matches
.iter()
.map(|m| &m.hint)
.max_by(|x, y| x.len().cmp(&y.len()))
.unwrap()
.clone();
self.full_render(writer);
loop {
@ -447,21 +442,26 @@ impl<'a> View<'a> {
typed_hint.push_str(&lower_key);
// Find the match that corresponds to the entered key.
let found = self.matches
.iter()
// Avoid cloning typed_hint for comparison.
.find(|&mat| &mat.hint == &typed_hint);
match found {
Some(mat) => {
match self
.lookup_trie
.get_node(&typed_hint.chars().collect::<Vec<char>>())
{
None => {
// An unknown key was entered.
return Event::Exit;
}
Some(node) => {
if node.is_leaf() {
// The last key of a hint was entered.
let match_index = node.value().expect("By construction, the Lookup Trie should have a value for each leaf.");
let mat = self.matches.get(*match_index).expect("By construction, the value in a leaf should correspond to an existing hint.");
let text = mat.text.to_string();
let uppercased = key != lower_key;
return Event::Match((text, uppercased));
}
None => {
if typed_hint.len() >= longest_hint.len() {
break;
} else {
// The prefix of a hint was entered, but we
// still need more keys.
continue;
}
}
}
@ -779,6 +779,7 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - ";
let view = View {
state: &mut state,
matches: vec![], // no matches
lookup_trie: SequenceTrie::new(),
focus_index: 0,
hint_alignment: &hint_alignment,
rendering_colors: &rendering_colors,