picker: Factor out file picker, we want to reuse code for other pickers.

pull/7/head
Blaž Hrastnik 4 years ago
parent 0b63e838e0
commit 25aa45e76c

@ -458,7 +458,7 @@ pub fn command_mode(cx: &mut Context) {
} }
pub fn file_picker(cx: &mut Context) { pub fn file_picker(cx: &mut Context) {
cx.callback = Some(Box::new(|compositor: &mut Compositor| { cx.callback = Some(Box::new(|compositor: &mut Compositor| {
let picker = ui::Picker::new(); let picker = ui::file_picker("./");
compositor.push(Box::new(picker)); compositor.push(Box::new(picker));
})); }));
} }

@ -14,3 +14,35 @@ pub use tui::style::{Color, Modifier, Style};
pub fn text_color() -> Style { pub fn text_color() -> Style {
Style::default().fg(Color::Rgb(219, 191, 239)) // lilac Style::default().fg(Color::Rgb(219, 191, 239)) // lilac
} }
use std::path::PathBuf;
pub fn file_picker(root: &str) -> Picker<PathBuf> {
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);
},
)
}

@ -9,16 +9,13 @@ use tui::{
use fuzzy_matcher::skim::SkimMatcherV2 as Matcher; use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
use fuzzy_matcher::FuzzyMatcher; use fuzzy_matcher::FuzzyMatcher;
use ignore::Walk;
use std::path::PathBuf;
use crate::ui::{Prompt, PromptEvent}; use crate::ui::{Prompt, PromptEvent};
use helix_core::Position; use helix_core::Position;
use helix_view::Editor; use helix_view::Editor;
pub struct Picker { pub struct Picker<T> {
files: Vec<PathBuf>, options: Vec<T>,
// filter: String, // filter: String,
matcher: Box<Matcher>, matcher: Box<Matcher>,
/// (index, score) /// (index, score)
@ -27,22 +24,17 @@ pub struct Picker {
cursor: usize, cursor: usize,
// pattern: String, // pattern: String,
prompt: Prompt, prompt: Prompt,
}
impl Picker { format_fn: Box<dyn Fn(&T) -> &str>,
pub fn new() -> Self { callback_fn: Box<dyn Fn(&mut Editor, &T)>,
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,
});
impl<T> Picker<T> {
pub fn new(
options: Vec<T>,
format_fn: impl Fn(&T) -> &str + 'static,
callback_fn: impl Fn(&mut Editor, &T) + 'static,
) -> Self {
let prompt = Prompt::new( let prompt = Prompt::new(
"".to_string(), "".to_string(),
|pattern: &str| Vec::new(), |pattern: &str| Vec::new(),
@ -51,14 +43,14 @@ impl Picker {
}, },
); );
const MAX: usize = 1024;
let mut picker = Self { let mut picker = Self {
files: files.take(MAX).collect(), options,
matcher: Box::new(Matcher::default()), matcher: Box::new(Matcher::default()),
matches: Vec::new(), matches: Vec::new(),
cursor: 0, cursor: 0,
prompt, prompt,
format_fn: Box::new(format_fn),
callback_fn: Box::new(callback_fn),
}; };
// TODO: scoring on empty input should just use a fastpath // TODO: scoring on empty input should just use a fastpath
@ -70,9 +62,10 @@ impl Picker {
pub fn score(&mut self) { pub fn score(&mut self) {
// need to borrow via pattern match otherwise it complains about simultaneous borrow // need to borrow via pattern match otherwise it complains about simultaneous borrow
let Self { let Self {
ref mut files, ref mut options,
ref mut matcher, ref mut matcher,
ref mut matches, ref mut matches,
ref format_fn,
.. ..
} = *self; } = *self;
@ -80,15 +73,19 @@ impl Picker {
// reuse the matches allocation // reuse the matches allocation
matches.clear(); matches.clear();
matches.extend(self.files.iter().enumerate().filter_map(|(index, path)| { matches.extend(
match path.to_str() { 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 // TODO: using fuzzy_indices could give us the char idx for match highlighting
Some(path) => matcher matcher
.fuzzy_match(path, pattern) .fuzzy_match(text, pattern)
.map(|score| (index, score)), .map(|score| (index, score))
None => None, }),
} );
}));
matches.sort_unstable_by_key(|(_, score)| -score); matches.sort_unstable_by_key(|(_, score)| -score);
// reset cursor position // reset cursor position
@ -101,15 +98,15 @@ impl Picker {
pub fn move_down(&mut self) { pub fn move_down(&mut self) {
// TODO: len - 1 // TODO: len - 1
if self.cursor < self.files.len() { if self.cursor < self.options.len() {
self.cursor += 1; self.cursor += 1;
} }
} }
pub fn selection(&self) -> Option<&PathBuf> { pub fn selection(&self) -> Option<&T> {
self.matches self.matches
.get(self.cursor) .get(self.cursor)
.map(|(index, _score)| &self.files[*index]) .map(|(index, _score)| &self.options[*index])
} }
} }
@ -118,7 +115,7 @@ impl Picker {
// - on input change: // - on input change:
// - score all the names in relation to input // - score all the names in relation to input
impl Component for Picker { impl<T> Component for Picker<T> {
fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult { fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
let key_event = match event { let key_event = match event {
Event::Key(event) => event, Event::Key(event) => event,
@ -163,9 +160,8 @@ impl Component for Picker {
code: KeyCode::Enter, code: KeyCode::Enter,
.. ..
} => { } => {
let size = cx.editor.view().unwrap().size; if let Some(option) = self.selection() {
if let Some(path) = self.selection() { (self.callback_fn)(&mut cx.editor, option);
cx.editor.open(path.into(), size);
} }
return close_fn; return close_fn;
} }
@ -238,10 +234,10 @@ impl Component for Picker {
let rows = inner.height - 2; // -1 for search bar let rows = inner.height - 2; // -1 for search bar
let files = self.matches.iter().map(|(index, _score)| { 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 { if i == self.cursor {
surface.set_string(inner.x + 1, inner.y + 2 + i as u16, ">", selected); surface.set_string(inner.x + 1, inner.y + 2 + i as u16, ">", selected);
} }
@ -249,7 +245,7 @@ impl Component for Picker {
surface.set_stringn( surface.set_stringn(
inner.x + 3, inner.x + 3,
inner.y + 2 + i as u16, 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, inner.width as usize - 1,
if i == self.cursor { selected } else { style }, if i == self.cursor { selected } else { style },
); );

Loading…
Cancel
Save