add incremental search/filter

This commit is contained in:
rabite 2019-05-08 13:47:36 +02:00
parent 7d08e6b064
commit 572a217e17
7 changed files with 150 additions and 42 deletions

View File

@ -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))
}
} }

View File

@ -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();
} }

View File

@ -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(())
} }

View File

@ -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;

View File

@ -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(())
} }

View File

@ -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 {

View File

@ -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