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