mirror of https://github.com/bobwen-dev/hunter
add incremental search/filter
This commit is contained in:
parent
7d08e6b064
commit
572a217e17
|
@ -91,6 +91,8 @@ pub enum HError {
|
|||
WidgetNoFilesError,
|
||||
#[fail(display = "Invalid line in settings file: {}", _0)]
|
||||
ConfigLineError(String),
|
||||
#[fail(display = "New input in Minibuffer")]
|
||||
MiniBufferInputUpdated(String),
|
||||
}
|
||||
|
||||
impl HError {
|
||||
|
@ -176,6 +178,12 @@ impl HError {
|
|||
pub fn no_files<T>() -> HResult<T> {
|
||||
Err(HError::WidgetNoFilesError)
|
||||
}
|
||||
|
||||
pub fn input_updated<T>(input: String) -> HResult<T> {
|
||||
Err(HError::MiniBufferInputUpdated(input))
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
32
src/files.rs
32
src/files.rs
|
@ -235,7 +235,9 @@ impl Files {
|
|||
|
||||
let file = self.files
|
||||
.iter_mut()
|
||||
.filter(|f| !(filter.is_some() &&
|
||||
.filter(|f|
|
||||
f.kind == Kind::Placeholder ||
|
||||
!(filter.is_some() &&
|
||||
!f.name.contains(filter.as_ref().unwrap())))
|
||||
.filter(|f| !(!show_hidden && f.name.starts_with(".")))
|
||||
.nth(index);
|
||||
|
@ -245,8 +247,10 @@ impl Files {
|
|||
pub fn get_files(&self) -> Vec<&File> {
|
||||
self.files
|
||||
.iter()
|
||||
.filter(|f| !(self.filter.is_some() &&
|
||||
!f.name.contains(self.filter.as_ref().unwrap())))
|
||||
.filter(|f|
|
||||
f.kind == Kind::Placeholder ||
|
||||
(!(self.filter.is_some() &&
|
||||
!f.name.contains(self.filter.as_ref().unwrap()))))
|
||||
.filter(|f| !(!self.show_hidden && f.name.starts_with(".")))
|
||||
.collect()
|
||||
}
|
||||
|
@ -256,7 +260,9 @@ impl Files {
|
|||
let show_hidden = self.show_hidden;
|
||||
self.files
|
||||
.iter_mut()
|
||||
.filter(|f| !(filter.is_some() &&
|
||||
.filter(|f|
|
||||
f.kind == Kind::Placeholder ||
|
||||
!(filter.is_some() &&
|
||||
!f.name.contains(filter.as_ref().unwrap())))
|
||||
.filter(|f| !(!show_hidden && f.name.starts_with(".")))
|
||||
.collect()
|
||||
|
@ -344,7 +350,7 @@ impl Files {
|
|||
self.show_hidden = !self.show_hidden;
|
||||
self.set_dirty();
|
||||
|
||||
if self.show_hidden == true {
|
||||
if self.show_hidden == true && self.len() > 1 {
|
||||
self.remove_placeholder();
|
||||
}
|
||||
}
|
||||
|
@ -433,6 +439,13 @@ impl Files {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn find_file_with_name(&self, name: &str) -> Option<&File> {
|
||||
self.get_files()
|
||||
.iter()
|
||||
.find(|f| f.name.to_lowercase().contains(name))
|
||||
.cloned()
|
||||
}
|
||||
|
||||
pub fn find_file_with_path(&mut self, path: &Path) -> Option<&mut File> {
|
||||
self.files.iter_mut().find(|file| file.path == path)
|
||||
}
|
||||
|
@ -495,6 +508,15 @@ impl Files {
|
|||
|
||||
pub fn set_filter(&mut self, filter: Option<String>) {
|
||||
self.filter = filter;
|
||||
|
||||
// Do this first, so we know len() == 0 needs a placeholder
|
||||
self.remove_placeholder();
|
||||
|
||||
if self.len() == 0 {
|
||||
let placeholder = File::new_placeholder(&self.directory.path).unwrap();
|
||||
self.files.push(placeholder);
|
||||
}
|
||||
|
||||
self.set_dirty();
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ use unicode_width::UnicodeWidthStr;
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::files::{File, Files};
|
||||
use crate::fail::{HResult, ErrorLog};
|
||||
use crate::fail::{HResult, HError, ErrorLog};
|
||||
use crate::term;
|
||||
use crate::widget::{Widget, WidgetCore};
|
||||
use crate::dirty::Dirtyable;
|
||||
|
@ -380,17 +380,35 @@ impl ListView<Files>
|
|||
}
|
||||
|
||||
fn search_file(&mut self) -> HResult<()> {
|
||||
let name = self.minibuffer("search")?;
|
||||
let file = self.content.files.iter().find(|file| {
|
||||
if file.name.to_lowercase().contains(&name) {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})?.clone();
|
||||
let selected_file = self.clone_selected_file();
|
||||
|
||||
self.select_file(&file);
|
||||
self.searching = Some(name);
|
||||
loop {
|
||||
let input = self.minibuffer_continuous("search");
|
||||
|
||||
match input {
|
||||
Ok(input) => {
|
||||
// Only set this, search is on-the-fly
|
||||
self.searching = Some(input);
|
||||
}
|
||||
Err(HError::MiniBufferInputUpdated(input)) => {
|
||||
let file = self.content
|
||||
.find_file_with_name(&input)
|
||||
.cloned();
|
||||
|
||||
file.map(|f| self.select_file(&f));
|
||||
|
||||
self.draw().log();
|
||||
|
||||
continue;
|
||||
},
|
||||
Err(HError::MiniBufferEmptyInput) |
|
||||
Err(HError::MiniBufferCancelledInput) => {
|
||||
self.select_file(&selected_file);
|
||||
}
|
||||
_ => { }
|
||||
}
|
||||
break;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -446,6 +464,7 @@ impl ListView<Files>
|
|||
}).cloned();
|
||||
|
||||
self.reverse_sort();
|
||||
self.clear_status().log();
|
||||
|
||||
if let Some(file) = file {
|
||||
let file = file.clone();
|
||||
|
@ -453,25 +472,41 @@ impl ListView<Files>
|
|||
} else {
|
||||
self.show_status("Reached last search result!").log();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn filter(&mut self) -> HResult<()> {
|
||||
let filter = self.minibuffer("filter").ok();
|
||||
let selected_file = self.selected_file().clone();
|
||||
|
||||
loop {
|
||||
let filter = self.minibuffer_continuous("filter");
|
||||
|
||||
match filter {
|
||||
Err(HError::MiniBufferInputUpdated(input)) => {
|
||||
self.content.set_filter(Some(input));
|
||||
self.refresh().ok();
|
||||
|
||||
self.select_file(&selected_file);
|
||||
self.draw().ok();
|
||||
|
||||
continue;
|
||||
}
|
||||
Err(HError::MiniBufferEmptyInput) |
|
||||
Err(HError::MiniBufferCancelledInput) => {
|
||||
self.content.set_filter(None);
|
||||
self.refresh().ok();
|
||||
self.select_file(&selected_file);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let msgstr = filter.clone().unwrap_or(String::from(""));
|
||||
self.show_status(&format!("Filtering with: \"{}\"", msgstr)).log();
|
||||
|
||||
self.content.set_filter(filter);
|
||||
|
||||
if self.content.len() == 0 {
|
||||
self.show_status("No files like that! Resetting filter.").log();
|
||||
self.content.set_filter(Some("".to_string()));
|
||||
break;
|
||||
}
|
||||
|
||||
self.select_file(&selected_file);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,6 @@ extern crate pathbuftools;
|
|||
|
||||
use failure::Fail;
|
||||
|
||||
use std::io::Write;
|
||||
use std::panic;
|
||||
|
||||
mod coordinates;
|
||||
|
|
|
@ -135,7 +135,8 @@ pub struct MiniBuffer {
|
|||
position: usize,
|
||||
history: History,
|
||||
completions: Vec<String>,
|
||||
last_completion: Option<String>
|
||||
last_completion: Option<String>,
|
||||
continuous: bool
|
||||
}
|
||||
|
||||
impl MiniBuffer {
|
||||
|
@ -152,30 +153,43 @@ impl MiniBuffer {
|
|||
position: 0,
|
||||
history: History::new(),
|
||||
completions: vec![],
|
||||
last_completion: None
|
||||
last_completion: None,
|
||||
continuous: false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn query(&mut self, query: &str) -> HResult<String> {
|
||||
pub fn query(&mut self, query: &str, cont: bool) -> HResult<String> {
|
||||
self.continuous = cont;
|
||||
|
||||
if !cont || self.query != query {
|
||||
self.query = query.to_string();
|
||||
self.input.clear();
|
||||
self.position = 0;
|
||||
self.history.reset();
|
||||
self.completions.clear();
|
||||
self.last_completion = None;
|
||||
|
||||
self.clear();
|
||||
}
|
||||
|
||||
self.screen()?.cursor_hide().log();
|
||||
|
||||
match self.popup() {
|
||||
Err(HError::MiniBufferCancelledInput) => self.input_cancelled()?,
|
||||
err @ Err(HError::MiniBufferInputUpdated(_)) => err?,
|
||||
_ => {}
|
||||
};
|
||||
|
||||
if self.input == "" { self.input_empty()?; }
|
||||
if self.input == "" {
|
||||
self.clear();
|
||||
self.input_empty()?; }
|
||||
|
||||
Ok(self.input.clone())
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.input.clear();
|
||||
self.position = 0;
|
||||
self.history.reset();
|
||||
self.completions.clear();
|
||||
self.last_completion = None;
|
||||
}
|
||||
|
||||
pub fn complete(&mut self) -> HResult<()> {
|
||||
if !self.input.ends_with(" ") {
|
||||
if !self.completions.is_empty() {
|
||||
|
@ -325,6 +339,10 @@ impl MiniBuffer {
|
|||
return HError::minibuffer_cancel()
|
||||
}
|
||||
|
||||
pub fn input_updated(&self) -> HResult<()> {
|
||||
return HError::input_updated(self.input.clone())
|
||||
}
|
||||
|
||||
pub fn input_empty(&self) -> HResult<()> {
|
||||
self.show_status("Empty!").log();
|
||||
return HError::minibuffer_empty()
|
||||
|
@ -413,8 +431,11 @@ impl Widget for MiniBuffer {
|
|||
}
|
||||
|
||||
fn on_key(&mut self, key: Key) -> HResult<()> {
|
||||
let prev_input = self.input.clone();
|
||||
|
||||
match key {
|
||||
Key::Esc | Key::Ctrl('c') => {
|
||||
self.clear();
|
||||
self.input_cancelled()?;
|
||||
},
|
||||
Key::Char('\n') => {
|
||||
|
@ -468,6 +489,11 @@ impl Widget for MiniBuffer {
|
|||
}
|
||||
_ => { }
|
||||
}
|
||||
|
||||
if self.continuous && prev_input != self.input {
|
||||
self.input_updated()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -583,9 +583,7 @@ impl Previewer {
|
|||
|
||||
let files = cached_files.wait()?;
|
||||
|
||||
let len = files.len();
|
||||
|
||||
if len == 0 || is_stale(&stale)? { return Previewer::preview_failed(&file) }
|
||||
if is_stale(&stale)? { return Previewer::preview_failed(&file) }
|
||||
|
||||
let mut file_list = ListView::new(&core, files);
|
||||
if let Some(selection) = selection {
|
||||
|
|
|
@ -23,6 +23,7 @@ pub enum Events {
|
|||
InputEvent(Event),
|
||||
WidgetReady,
|
||||
TerminalResized,
|
||||
InputUpdated(String),
|
||||
ExclusiveEvent(Option<Mutex<Option<Sender<Events>>>>),
|
||||
InputEnabled(bool),
|
||||
RequestInput,
|
||||
|
@ -270,6 +271,7 @@ pub trait Widget {
|
|||
err @ Err(HError::PopupFinnished) |
|
||||
err @ Err(HError::Quit) |
|
||||
err @ Err(HError::MiniBufferCancelledInput) => err?,
|
||||
err @ Err(HError::MiniBufferInputUpdated(_)) => err?,
|
||||
err @ Err(HError::WidgetResizedError) => err?,
|
||||
err @ Err(_) => err.log(),
|
||||
Ok(_) => {}
|
||||
|
@ -290,6 +292,9 @@ pub trait Widget {
|
|||
_ => {}
|
||||
}
|
||||
}
|
||||
Events::InputUpdated(input) => {
|
||||
HError::input_updated(input)?
|
||||
}
|
||||
Events::ConfigLoaded => {
|
||||
self.get_core_mut()?.config.write()?.take_async().log();
|
||||
}
|
||||
|
@ -445,7 +450,22 @@ pub trait Widget {
|
|||
}
|
||||
|
||||
fn minibuffer(&self, query: &str) -> HResult<String> {
|
||||
let answer = self.get_core()?.minibuffer.lock()?.as_mut()?.query(query);
|
||||
let answer = self.get_core()?
|
||||
.minibuffer
|
||||
.lock()?
|
||||
.as_mut()?
|
||||
.query(query, false);
|
||||
let mut screen = self.screen()?;
|
||||
screen.cursor_hide().log();
|
||||
answer
|
||||
}
|
||||
|
||||
fn minibuffer_continuous(&self, query: &str) -> HResult<String> {
|
||||
let answer = self.get_core()?
|
||||
.minibuffer
|
||||
.lock()?
|
||||
.as_mut()?
|
||||
.query(query, true);
|
||||
let mut screen = self.screen()?;
|
||||
screen.cursor_hide().log();
|
||||
answer
|
||||
|
|
Loading…
Reference in New Issue