diff --git a/Cargo.lock b/Cargo.lock index e972d7b..51c7756 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 23e7a2d..ab11bcb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/state.rs b/src/state.rs index 3808aff..a4a8e18 100644 --- a/src/state.rs +++ b/src/state.rs @@ -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> { 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> { 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>) -> SequenceTrie { + let mut trie = SequenceTrie::new(); + + for (index, mat) in matches.iter().enumerate() { + let hint_chars = mat.hint.chars().collect::>(); + + // no need to insert twice the same hint + if trie.get(&hint_chars).is_none() { + trie.insert_owned(hint_chars, index); + } + } + + trie + } } #[cfg(test)] diff --git a/src/view.rs b/src/view.rs index 2348856..e6dd3c3 100644 --- a/src/view.rs +++ b/src/view.rs @@ -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>, + lookup_trie: SequenceTrie, focus_index: usize, hint_alignment: &'a HintAlignment, rendering_colors: &'a ViewColors, @@ -117,11 +119,13 @@ impl<'a> View<'a> { hint_style: Option, ) -> 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) => { - let text = mat.text.to_string(); - let uppercased = key != lower_key; - return Event::Match((text, uppercased)); - } + match self + .lookup_trie + .get_node(&typed_hint.chars().collect::>()) + { None => { - if typed_hint.len() >= longest_hint.len() { - break; + // 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)); + } 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,