From 572a217e17b0cc2c30cd94b57e5a94ede75e0707 Mon Sep 17 00:00:00 2001 From: rabite Date: Wed, 8 May 2019 13:47:36 +0200 Subject: add incremental search/filter --- src/fail.rs | 8 ++++++ src/files.rs | 36 ++++++++++++++++++++------ src/listview.rs | 75 ++++++++++++++++++++++++++++++++++++++++--------------- src/main.rs | 1 - src/minibuffer.rs | 46 ++++++++++++++++++++++++++-------- src/preview.rs | 4 +-- src/widget.rs | 22 +++++++++++++++- 7 files changed, 150 insertions(+), 42 deletions(-) diff --git a/src/fail.rs b/src/fail.rs index 15a8b2c..d34ed76 100644 --- a/src/fail.rs +++ b/src/fail.rs @@ -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() -> HResult { Err(HError::WidgetNoFilesError) } + + pub fn input_updated(input: String) -> HResult { + Err(HError::MiniBufferInputUpdated(input)) + } + + } diff --git a/src/files.rs b/src/files.rs index a3cd0ab..a191873 100644 --- a/src/files.rs +++ b/src/files.rs @@ -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) { 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(); } diff --git a/src/listview.rs b/src/listview.rs index ca84e84..3eac914 100644 --- a/src/listview.rs +++ b/src/listview.rs @@ -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 } 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 }).cloned(); self.reverse_sort(); + self.clear_status().log(); if let Some(file) = file { let file = file.clone(); @@ -453,24 +472,40 @@ impl ListView } 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(); - self.select_file(&selected_file); + 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; + } Ok(()) } diff --git a/src/main.rs b/src/main.rs index 355c07d..03dcfd4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,7 +27,6 @@ extern crate pathbuftools; use failure::Fail; -use std::io::Write; use std::panic; mod coordinates; diff --git a/src/minibuffer.rs b/src/minibuffer.rs index 6cc0081..a8fb410 100644 --- a/src/minibuffer.rs +++ b/src/minibuffer.rs @@ -135,7 +135,8 @@ pub struct MiniBuffer { position: usize, history: History, completions: Vec, - last_completion: Option + last_completion: Option, + 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 { - 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 { + 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(()) } diff --git a/src/preview.rs b/src/preview.rs index 17372d0..b833c00 100644 --- a/src/preview.rs +++ b/src/preview.rs @@ -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 { diff --git a/src/widget.rs b/src/widget.rs index 2f1d54c..e4e529c 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -23,6 +23,7 @@ pub enum Events { InputEvent(Event), WidgetReady, TerminalResized, + InputUpdated(String), ExclusiveEvent(Option>>>), 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 { - 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 { + let answer = self.get_core()? + .minibuffer + .lock()? + .as_mut()? + .query(query, true); let mut screen = self.screen()?; screen.cursor_hide().log(); answer -- cgit v1.2.3