@@ -9,16 +9,13 @@ use tui::{
10use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
10use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
11use fuzzy_matcher::FuzzyMatcher;
11use fuzzy_matcher::FuzzyMatcher;
14−use std::path::PathBuf;
16use crate::ui::{Prompt, PromptEvent};
13use crate::ui::{Prompt, PromptEvent};
17use helix_core::Position;
14use helix_core::Position;
18use helix_view::Editor;
15use helix_view::Editor;
20−pub struct Picker {
17+pub struct Picker<T> {
21− files: Vec<PathBuf>,
18+ options: Vec<T>,
22 // filter: String,
19 // filter: String,
23 matcher: Box<Matcher>,
20 matcher: Box<Matcher>,
24 /// (index, score)
21 /// (index, score)
@@ -27,22 +24,17 @@ pub struct Picker {
27 cursor: usize,
24 cursor: usize,
28 // pattern: String,
25 // pattern: String,
29 prompt: Prompt,
26 prompt: Prompt,
32−impl Picker {
28+ format_fn: Box<dyn Fn(&T) -> &str>,
33− pub fn new() -> Self {
29+ callback_fn: Box<dyn Fn(&mut Editor, &T)>,
34− let files = Walk::new("./").filter_map(|entry| match entry {
36− // filter dirs, but we might need special handling for symlinks!
37− if !entry.file_type().unwrap().is_dir() {
38− Some(entry.into_path())
35+ format_fn: impl Fn(&T) -> &str + 'static,
36+ callback_fn: impl Fn(&mut Editor, &T) + 'static,
46 let prompt = Prompt::new(
38 let prompt = Prompt::new(
47 "".to_string(),
39 "".to_string(),
48 |pattern: &str| Vec::new(),
40 |pattern: &str| Vec::new(),
@@ -51,14 +43,14 @@ impl Picker {
54− const MAX: usize = 1024;
56 let mut picker = Self {
46 let mut picker = Self {
57− files: files.take(MAX).collect(),
47+ options,
58 matcher: Box::new(Matcher::default()),
48 matcher: Box::new(Matcher::default()),
59 matches: Vec::new(),
49 matches: Vec::new(),
60 cursor: 0,
50 cursor: 0,
52+ format_fn: Box::new(format_fn),
53+ callback_fn: Box::new(callback_fn),
64 // TODO: scoring on empty input should just use a fastpath
56 // TODO: scoring on empty input should just use a fastpath
@@ -70,9 +62,10 @@ impl Picker {
70 pub fn score(&mut self) {
62 pub fn score(&mut self) {
71 // need to borrow via pattern match otherwise it complains about simultaneous borrow
63 // need to borrow via pattern match otherwise it complains about simultaneous borrow
72 let Self {
64 let Self {
73− ref mut files,
65+ ref mut options,
74 ref mut matcher,
66 ref mut matcher,
75 ref mut matches,
67 ref mut matches,
77 } = *self;
70 } = *self;
@@ -80,15 +73,19 @@ impl Picker {
81 // reuse the matches allocation
74 // reuse the matches allocation
82 matches.clear();
75 matches.clear();
83− matches.extend(self.files.iter().enumerate().filter_map(|(index, path)| {
80+ .filter_map(|(index, option)| {
84− match path.to_str() {
81+ // TODO: maybe using format_fn isn't the best idea here
82+ let text = (format_fn)(option);
85− // TODO: using fuzzy_indices could give us the char idx for match highlighting
83+ // TODO: using fuzzy_indices could give us the char idx for match highlighting
86− Some(path) => matcher
84+ matcher
87− .fuzzy_match(path, pattern)
85+ .fuzzy_match(text, pattern)
88− .map(|score| (index, score)),
86+ .map(|score| (index, score))
92 matches.sort_unstable_by_key(|(_, score)| -score);
89 matches.sort_unstable_by_key(|(_, score)| -score);
94 // reset cursor position
91 // reset cursor position
@@ -101,15 +98,15 @@ impl Picker {
102 pub fn move_down(&mut self) {
99 pub fn move_down(&mut self) {
103 // TODO: len - 1
100 // TODO: len - 1
104− if self.cursor < self.files.len() {
101+ if self.cursor < self.options.len() {
105 self.cursor += 1;
102 self.cursor += 1;
109− pub fn selection(&self) -> Option<&PathBuf> {
106+ pub fn selection(&self) -> Option<&T> {
110 self.matches
107 self.matches
111 .get(self.cursor)
108 .get(self.cursor)
112− .map(|(index, _score)| &self.files[*index])
109+ .map(|(index, _score)| &self.options[*index])
@@ -118,7 +115,7 @@ impl Picker {
118// - on input change:
115// - on input change:
119// - score all the names in relation to input
116// - score all the names in relation to input
121−impl Component for Picker {
118+impl<T> Component for Picker<T> {
122 fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
119 fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
123 let key_event = match event {
120 let key_event = match event {
124 Event::Key(event) => event,
121 Event::Key(event) => event,
@@ -163,9 +160,8 @@ impl Component for Picker {
163 code: KeyCode::Enter,
160 code: KeyCode::Enter,
166− let size = cx.editor.view().unwrap().size;
167− if let Some(path) = self.selection() {
163+ if let Some(option) = self.selection() {
168− cx.editor.open(path.into(), size);
164+ (self.callback_fn)(&mut cx.editor, option);
170 return close_fn;
166 return close_fn;
@@ -238,10 +234,10 @@ impl Component for Picker {
238 let rows = inner.height - 2; // -1 for search bar
234 let rows = inner.height - 2; // -1 for search bar
240 let files = self.matches.iter().map(|(index, _score)| {
236 let files = self.matches.iter().map(|(index, _score)| {
241− (index, self.files.get(*index).unwrap()) // get_unchecked
237+ (index, self.options.get(*index).unwrap()) // get_unchecked
244− for (i, (_index, file)) in files.take(rows as usize).enumerate() {
240+ for (i, (_index, option)) in files.take(rows as usize).enumerate() {
245 if i == self.cursor {
241 if i == self.cursor {
246 surface.set_string(inner.x + 1, inner.y + 2 + i as u16, ">", selected);
242 surface.set_string(inner.x + 1, inner.y + 2 + i as u16, ">", selected);
@@ -249,7 +245,7 @@ impl Component for Picker {
249 surface.set_stringn(
245 surface.set_stringn(
250 inner.x + 3,
246 inner.x + 3,
251 inner.y + 2 + i as u16,
247 inner.y + 2 + i as u16,
252− file.strip_prefix("./").unwrap().to_str().unwrap(), // TODO: render paths without ./
248+ (self.format_fn)(option),
253 inner.width as usize - 1,
249 inner.width as usize - 1,
254 if i == self.cursor { selected } else { style },
250 if i == self.cursor { selected } else { style },