aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBlaž Hrastnik2020-12-18 10:19:50 +0000
committerBlaž Hrastnik2020-12-18 10:19:50 +0000
commit25aa45e76c9bec62f36a59768298e1f2ea2678bf (patch)
tree41b876f9bb067e5f199fe53ecb78eeb57d09719b
parent0b63e838e0ffe9a36a2cb51790f464d634b843af (diff)
picker: Factor out file picker, we want to reuse code for other pickers.
-rw-r--r--helix-term/src/commands.rs2
-rw-r--r--helix-term/src/ui/mod.rs32
-rw-r--r--helix-term/src/ui/picker.rs80
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<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);
+ },
+ )
+}
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<PathBuf>,
+pub struct Picker<T> {
+ options: Vec<T>,
// filter: String,
matcher: Box<Matcher>,
/// (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<dyn Fn(&T) -> &str>,
+ callback_fn: Box<dyn Fn(&mut Editor, &T)>,
+}
+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(
"".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<T> Component for Picker<T> {
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 },
);