From 25aa45e76c9bec62f36a59768298e1f2ea2678bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Fri, 18 Dec 2020 19:19:50 +0900 Subject: [PATCH] picker: Factor out file picker, we want to reuse code for other pickers. --- helix-term/src/commands.rs | 2 +- helix-term/src/ui/mod.rs | 32 +++++++++++++++ helix-term/src/ui/picker.rs | 80 ++++++++++++++++++------------------- 3 files changed, 71 insertions(+), 43 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 4b246721..5f8f63f1 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -458,7 +458,7 @@ pub fn command_mode(cx: &mut Context) { } pub fn file_picker(cx: &mut Context) { cx.callback = Some(Box::new(|compositor: &mut Compositor| { - let picker = ui::Picker::new(); + let picker = ui::file_picker("./"); compositor.push(Box::new(picker)); })); } diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index cb79a1d1..b778f531 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -14,3 +14,35 @@ pub use tui::style::{Color, Modifier, Style}; pub fn text_color() -> Style { Style::default().fg(Color::Rgb(219, 191, 239)) // lilac } + +use std::path::PathBuf; +pub fn file_picker(root: &str) -> Picker { + use ignore::Walk; + // TODO: determine root based on git root + let files = Walk::new(root).filter_map(|entry| match entry { + Ok(entry) => { + // filter dirs, but we might need special handling for symlinks! + if !entry.file_type().unwrap().is_dir() { + Some(entry.into_path()) + } else { + None + } + } + Err(_err) => None, + }); + + const MAX: usize = 1024; + + use helix_view::Editor; + Picker::new( + files.take(MAX).collect(), + |path: &PathBuf| { + // format_fn + path.strip_prefix("./").unwrap().to_str().unwrap() // TODO: render paths without ./ + }, + |editor: &mut Editor, path: &PathBuf| { + let size = editor.view().unwrap().size; + editor.open(path.into(), size); + }, + ) +} diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 3a3c648f..0a12cff9 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -9,16 +9,13 @@ use tui::{ use fuzzy_matcher::skim::SkimMatcherV2 as Matcher; use fuzzy_matcher::FuzzyMatcher; -use ignore::Walk; - -use std::path::PathBuf; use crate::ui::{Prompt, PromptEvent}; use helix_core::Position; use helix_view::Editor; -pub struct Picker { - files: Vec, +pub struct Picker { + options: Vec, // filter: String, matcher: Box, /// (index, score) @@ -27,22 +24,17 @@ pub struct Picker { cursor: usize, // pattern: String, prompt: Prompt, -} -impl Picker { - pub fn new() -> Self { - let files = Walk::new("./").filter_map(|entry| match entry { - Ok(entry) => { - // filter dirs, but we might need special handling for symlinks! - if !entry.file_type().unwrap().is_dir() { - Some(entry.into_path()) - } else { - None - } - } - Err(_err) => None, - }); + format_fn: Box &str>, + callback_fn: Box, +} +impl Picker { + pub fn new( + options: Vec, + format_fn: impl Fn(&T) -> &str + 'static, + callback_fn: impl Fn(&mut Editor, &T) + 'static, + ) -> Self { let prompt = Prompt::new( "".to_string(), |pattern: &str| Vec::new(), @@ -51,14 +43,14 @@ impl Picker { }, ); - const MAX: usize = 1024; - let mut picker = Self { - files: files.take(MAX).collect(), + options, matcher: Box::new(Matcher::default()), matches: Vec::new(), cursor: 0, prompt, + format_fn: Box::new(format_fn), + callback_fn: Box::new(callback_fn), }; // TODO: scoring on empty input should just use a fastpath @@ -70,9 +62,10 @@ impl Picker { pub fn score(&mut self) { // need to borrow via pattern match otherwise it complains about simultaneous borrow let Self { - ref mut files, + ref mut options, ref mut matcher, ref mut matches, + ref format_fn, .. } = *self; @@ -80,15 +73,19 @@ impl Picker { // reuse the matches allocation matches.clear(); - matches.extend(self.files.iter().enumerate().filter_map(|(index, path)| { - match path.to_str() { - // TODO: using fuzzy_indices could give us the char idx for match highlighting - Some(path) => matcher - .fuzzy_match(path, pattern) - .map(|score| (index, score)), - None => None, - } - })); + matches.extend( + self.options + .iter() + .enumerate() + .filter_map(|(index, option)| { + // TODO: maybe using format_fn isn't the best idea here + let text = (format_fn)(option); + // TODO: using fuzzy_indices could give us the char idx for match highlighting + matcher + .fuzzy_match(text, pattern) + .map(|score| (index, score)) + }), + ); matches.sort_unstable_by_key(|(_, score)| -score); // reset cursor position @@ -101,15 +98,15 @@ impl Picker { pub fn move_down(&mut self) { // TODO: len - 1 - if self.cursor < self.files.len() { + if self.cursor < self.options.len() { self.cursor += 1; } } - pub fn selection(&self) -> Option<&PathBuf> { + pub fn selection(&self) -> Option<&T> { self.matches .get(self.cursor) - .map(|(index, _score)| &self.files[*index]) + .map(|(index, _score)| &self.options[*index]) } } @@ -118,7 +115,7 @@ impl Picker { // - on input change: // - score all the names in relation to input -impl Component for Picker { +impl Component for Picker { fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult { let key_event = match event { Event::Key(event) => event, @@ -163,9 +160,8 @@ impl Component for Picker { code: KeyCode::Enter, .. } => { - let size = cx.editor.view().unwrap().size; - if let Some(path) = self.selection() { - cx.editor.open(path.into(), size); + if let Some(option) = self.selection() { + (self.callback_fn)(&mut cx.editor, option); } return close_fn; } @@ -238,10 +234,10 @@ impl Component for Picker { let rows = inner.height - 2; // -1 for search bar let files = self.matches.iter().map(|(index, _score)| { - (index, self.files.get(*index).unwrap()) // get_unchecked + (index, self.options.get(*index).unwrap()) // get_unchecked }); - for (i, (_index, file)) in files.take(rows as usize).enumerate() { + for (i, (_index, option)) in files.take(rows as usize).enumerate() { if i == self.cursor { surface.set_string(inner.x + 1, inner.y + 2 + i as u16, ">", selected); } @@ -249,7 +245,7 @@ impl Component for Picker { surface.set_stringn( inner.x + 3, inner.y + 2 + i as u16, - file.strip_prefix("./").unwrap().to_str().unwrap(), // TODO: render paths without ./ + (self.format_fn)(option), inner.width as usize - 1, if i == self.cursor { selected } else { style }, );