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,
#[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))
}
}

View File

@ -235,8 +235,10 @@ impl Files {
let file = self.files
.iter_mut()
.filter(|f| !(filter.is_some() &&
!f.name.contains(filter.as_ref().unwrap())))
.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);
file
@ -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,8 +260,10 @@ impl Files {
let show_hidden = self.show_hidden;
self.files
.iter_mut()
.filter(|f| !(filter.is_some() &&
!f.name.contains(filter.as_ref().unwrap())))
.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();
}

View File

@ -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();
let msgstr = filter.clone().unwrap_or(String::from(""));
self.show_status(&format!("Filtering with: \"{}\"", msgstr)).log();
loop {
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.show_status("No files like that! Resetting filter.").log();
self.content.set_filter(Some("".to_string()));
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();
break;
}
self.select_file(&selected_file);
Ok(())
}

View File

@ -27,7 +27,6 @@ extern crate pathbuftools;
use failure::Fail;
use std::io::Write;
use std::panic;
mod coordinates;

View File

@ -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> {
self.query = query.to_string();
self.input.clear();
self.position = 0;
self.history.reset();
self.completions.clear();
self.last_completion = None;
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.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(())
}

View File

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

View File

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