on master help sign in
picker: Factor out file picker, we want to reuse code for other pickers.
commit25aa45e76c9bec62f36a59768298e1f2ea2678bf authorBlaž Hrastnik <blaz@mxxn.io> dateFri 18 Dec 2020, 19:19 (6 years ago) parent0b63e83
3 files changed+71−43
helix-term/src/commands.rs+1−1 helix-term/src/ui/mod.rs+32 helix-term/src/ui/picker.rs+38−42
helix-term/src/commands.rs+1−1
@@ -458,7 +458,7 @@ pub fn command_mode(cx: &mut Context) {
458458}
459459pub fn file_picker(cx: &mut Context) {
460460 cx.callback = Some(Box::new(|compositor: &mut Compositor| {
461− let picker = ui::Picker::new();
461+ let picker = ui::file_picker("./");
462462 compositor.push(Box::new(picker));
463463 }));
464464}
helix-term/src/ui/mod.rs+32
@@ -14,3 +14,35 @@ pub use tui::style::{Color, Modifier, Style};
1414pub fn text_color() -> Style {
1515 Style::default().fg(Color::Rgb(219, 191, 239)) // lilac
1616}
17+
18+use std::path::PathBuf;
19+pub fn file_picker(root: &str) -> Picker<PathBuf> {
20+ use ignore::Walk;
21+ // TODO: determine root based on git root
22+ let files = Walk::new(root).filter_map(|entry| match entry {
23+ Ok(entry) => {
24+ // filter dirs, but we might need special handling for symlinks!
25+ if !entry.file_type().unwrap().is_dir() {
26+ Some(entry.into_path())
27+ } else {
28+ None
29+ }
30+ }
31+ Err(_err) => None,
32+ });
33+
34+ const MAX: usize = 1024;
35+
36+ use helix_view::Editor;
37+ Picker::new(
38+ files.take(MAX).collect(),
39+ |path: &PathBuf| {
40+ // format_fn
41+ path.strip_prefix("./").unwrap().to_str().unwrap() // TODO: render paths without ./
42+ },
43+ |editor: &mut Editor, path: &PathBuf| {
44+ let size = editor.view().unwrap().size;
45+ editor.open(path.into(), size);
46+ },
47+ )
48+}
helix-term/src/ui/picker.rs+38−42
@@ -9,16 +9,13 @@ use tui::{
99
1010use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
1111use fuzzy_matcher::FuzzyMatcher;
12−use ignore::Walk;
13−
14−use std::path::PathBuf;
1512
1613use crate::ui::{Prompt, PromptEvent};
1714use helix_core::Position;
1815use helix_view::Editor;
1916
20−pub struct Picker {
21− files: Vec<PathBuf>,
17+pub struct Picker<T> {
18+ options: Vec<T>,
2219 // filter: String,
2320 matcher: Box<Matcher>,
2421 /// (index, score)
@@ -27,22 +24,17 @@ pub struct Picker {
2724 cursor: usize,
2825 // pattern: String,
2926 prompt: Prompt,
30−}
3127
32−impl Picker {
33− pub fn new() -> Self {
34− let files = Walk::new("./").filter_map(|entry| match entry {
35− Ok(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())
39− } else {
40− None
41− }
42− }
43− Err(_err) => None,
44− });
28+ format_fn: Box<dyn Fn(&T) -> &str>,
29+ callback_fn: Box<dyn Fn(&mut Editor, &T)>,
30+}
4531
32+impl<T> Picker<T> {
33+ pub fn new(
34+ options: Vec<T>,
35+ format_fn: impl Fn(&T) -> &str + 'static,
36+ callback_fn: impl Fn(&mut Editor, &T) + 'static,
37+ ) -> Self {
4638 let prompt = Prompt::new(
4739 "".to_string(),
4840 |pattern: &str| Vec::new(),
@@ -51,14 +43,14 @@ impl Picker {
5143 },
5244 );
5345
54− const MAX: usize = 1024;
55−
5646 let mut picker = Self {
57− files: files.take(MAX).collect(),
47+ options,
5848 matcher: Box::new(Matcher::default()),
5949 matches: Vec::new(),
6050 cursor: 0,
6151 prompt,
52+ format_fn: Box::new(format_fn),
53+ callback_fn: Box::new(callback_fn),
6254 };
6355
6456 // TODO: scoring on empty input should just use a fastpath
@@ -70,9 +62,10 @@ impl Picker {
7062 pub fn score(&mut self) {
7163 // need to borrow via pattern match otherwise it complains about simultaneous borrow
7264 let Self {
73− ref mut files,
65+ ref mut options,
7466 ref mut matcher,
7567 ref mut matches,
68+ ref format_fn,
7669 ..
7770 } = *self;
7871
@@ -80,15 +73,19 @@ impl Picker {
8073
8174 // reuse the matches allocation
8275 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− }));
76+ matches.extend(
77+ self.options
78+ .iter()
79+ .enumerate()
80+ .filter_map(|(index, option)| {
81+ // TODO: maybe using format_fn isn't the best idea here
82+ let text = (format_fn)(option);
83+ // TODO: using fuzzy_indices could give us the char idx for match highlighting
84+ matcher
85+ .fuzzy_match(text, pattern)
86+ .map(|score| (index, score))
87+ }),
88+ );
9289 matches.sort_unstable_by_key(|(_, score)| -score);
9390
9491 // reset cursor position
@@ -101,15 +98,15 @@ impl Picker {
10198
10299 pub fn move_down(&mut self) {
103100 // TODO: len - 1
104− if self.cursor < self.files.len() {
101+ if self.cursor < self.options.len() {
105102 self.cursor += 1;
106103 }
107104 }
108105
109− pub fn selection(&self) -> Option<&PathBuf> {
106+ pub fn selection(&self) -> Option<&T> {
110107 self.matches
111108 .get(self.cursor)
112− .map(|(index, _score)| &self.files[*index])
109+ .map(|(index, _score)| &self.options[*index])
113110 }
114111}
115112
@@ -118,7 +115,7 @@ impl Picker {
118115// - on input change:
119116// - score all the names in relation to input
120117
121−impl Component for Picker {
118+impl<T> Component for Picker<T> {
122119 fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
123120 let key_event = match event {
124121 Event::Key(event) => event,
@@ -163,9 +160,8 @@ impl Component for Picker {
163160 code: KeyCode::Enter,
164161 ..
165162 } => {
166− let size = cx.editor.view().unwrap().size;
167− if let Some(path) = self.selection() {
168− cx.editor.open(path.into(), size);
163+ if let Some(option) = self.selection() {
164+ (self.callback_fn)(&mut cx.editor, option);
169165 }
170166 return close_fn;
171167 }
@@ -238,10 +234,10 @@ impl Component for Picker {
238234 let rows = inner.height - 2; // -1 for search bar
239235
240236 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
242238 });
243239
244− for (i, (_index, file)) in files.take(rows as usize).enumerate() {
240+ for (i, (_index, option)) in files.take(rows as usize).enumerate() {
245241 if i == self.cursor {
246242 surface.set_string(inner.x + 1, inner.y + 2 + i as u16, ">", selected);
247243 }
@@ -249,7 +245,7 @@ impl Component for Picker {
249245 surface.set_stringn(
250246 inner.x + 3,
251247 inner.y + 2 + i as u16,
252− file.strip_prefix("./").unwrap().to_str().unwrap(), // TODO: render paths without ./
248+ (self.format_fn)(option),
253249 inner.width as usize - 1,
254250 if i == self.cursor { selected } else { style },
255251 );