on master help sign in
picker: Implement fuzzy search.
commitedfd3933dbdfe30e05533beff625e0ddd446f41f authorBlaž Hrastnik <blaz@mxxn.io> dateFri 18 Dec 2020, 16:43 (6 years ago) parent4f9cde2
1 file changed+57−10
helix-term/src/ui/picker.rs+57−10
helix-term/src/ui/picker.rs+57−10
@@ -21,6 +21,8 @@ pub struct Picker {
2121 files: Vec<PathBuf>,
2222 // filter: String,
2323 matcher: Box<Matcher>,
24+ /// (index, score)
25+ matches: Vec<(usize, i64)>,
2426
2527 cursor: usize,
2628 // pattern: String,
@@ -51,20 +53,46 @@ impl Picker {
5153
5254 const MAX: usize = 1024;
5355
54− Self {
56+ let mut picker = Self {
5557 files: files.take(MAX).collect(),
5658 matcher: Box::new(Matcher::default()),
59+ matches: Vec::new(),
5760 cursor: 0,
5861 prompt,
59− }
62+ };
63+
64+ // TODO: scoring on empty input should just use a fastpath
65+ picker.score();
66+
67+ picker
6068 }
6169
62− pub fn score(&mut self, pattern: &str) {
63− self.files.iter().filter_map(|path| match path.to_str() {
64− // TODO: using fuzzy_indices could give us the char idx for match highlighting
65− Some(path) => (self.matcher.fuzzy_match(path, pattern)),
66− None => None,
67− });
70+ pub fn score(&mut self) {
71+ // need to borrow via pattern match otherwise it complains about simultaneous borrow
72+ let Self {
73+ ref mut files,
74+ ref mut matcher,
75+ ref mut matches,
76+ ..
77+ } = *self;
78+
79+ let pattern = &self.prompt.line;
80+
81+ // reuse the matches allocation
82+ matches.clear();
83+ matches.extend(self.files.iter().enumerate().filter_map(|(index, path)| {
84+ match path.to_str() {
85+ // TODO: using fuzzy_indices could give us the char idx for match highlighting
86+ Some(path) => matcher
87+ .fuzzy_match(path, pattern)
88+ .map(|score| (index, score)),
89+ None => None,
90+ }
91+ }));
92+ matches.sort_unstable_by_key(|(_, score)| -score);
93+
94+ // reset cursor position
95+ self.cursor = 0;
6896 }
6997
7098 pub fn move_up(&mut self) {
@@ -77,6 +105,12 @@ impl Picker {
77105 self.cursor += 1;
78106 }
79107 }
108+
109+ pub fn selection(&self) -> Option<&PathBuf> {
110+ self.matches
111+ .get(self.cursor)
112+ .map(|(index, _score)| &self.files[*index])
113+ }
80114}
81115
82116// process:
@@ -125,7 +159,15 @@ impl Component for Picker {
125159 } => {
126160 return close_fn;
127161 }
128− _ => return self.prompt.handle_event(event, cx),
162+ _ => {
163+ match self.prompt.handle_event(event, cx) {
164+ EventResult::Consumed(_) => {
165+ // TODO: recalculate only if pattern changed
166+ self.score();
167+ }
168+ _ => (),
169+ }
170+ }
129171 }
130172
131173 EventResult::Consumed(None)
@@ -184,7 +226,12 @@ impl Component for Picker {
184226 let selected = Style::default().fg(Color::Rgb(255, 255, 255));
185227
186228 let rows = inner.height - 2; // -1 for search bar
187− for (i, file) in self.files.iter().take(rows as usize).enumerate() {
229+
230+ let files = self.matches.iter().map(|(index, _score)| {
231+ (index, self.files.get(*index).unwrap()) // get_unchecked
232+ });
233+
234+ for (i, (_index, file)) in files.take(rows as usize).enumerate() {
188235 if i == self.cursor {
189236 surface.set_string(inner.x + 1, inner.y + 2 + i as u16, ">", selected);
190237 }