summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrabite <rabite@posteo.de>2019-05-08 13:47:36 +0200
committerrabite <rabite@posteo.de>2019-05-08 13:48:46 +0200
commit572a217e17b0cc2c30cd94b57e5a94ede75e0707 (patch)
treefcf713135acb4516d4f935565d6b836eba525c21
parent7d08e6b064a0def48df927cd19572b1964aedcc8 (diff)
add incremental search/filter
-rw-r--r--src/fail.rs8
-rw-r--r--src/files.rs36
-rw-r--r--src/listview.rs75
-rw-r--r--src/main.rs1
-rw-r--r--src/minibuffer.rs46
-rw-r--r--src/preview.rs4
-rw-r--r--src/widget.rs22
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<T>() -> HResult<T> {
Err(HError::WidgetNoFilesError)
}
+
+ pub fn input_updated<T>(input: String) -> HResult<T> {
+ 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<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();
}
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<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,24 +472,40 @@ 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();
- 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<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(())
}
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<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