From e4850e38b603c4adc99fbdcedc80de7c2dfbe775 Mon Sep 17 00:00:00 2001 From: rabite0 Date: Sun, 1 Mar 2020 22:57:09 +0100 Subject: improved navigation/turbo-cd --- src/fail.rs | 22 +++---- src/file_browser.rs | 177 +++++++++++++++++++++++++++++++++++++++++++++++++-- src/listview.rs | 104 +++++++++++++++++++++--------- src/minibuffer.rs | 31 +++++++-- src/quick_actions.rs | 13 +++- src/widget.rs | 21 ++++-- 6 files changed, 308 insertions(+), 60 deletions(-) (limited to 'src') diff --git a/src/fail.rs b/src/fail.rs index 4e793ac..519de19 100644 --- a/src/fail.rs +++ b/src/fail.rs @@ -70,10 +70,6 @@ pub enum HError { INotifyError(String), #[fail(display = "Tags not loaded yet")] TagsNotLoadedYetError, - #[fail(display = "Input cancelled!")] - MiniBufferCancelledInput, - #[fail(display = "Empty input!")] - MiniBufferEmptyInput, #[fail(display = "Undefined key: {:?}", key)] WidgetUndefinedKeyError{key: Key}, #[fail(display = "Terminal has been resized!")] @@ -107,7 +103,11 @@ pub enum HError { #[fail(display = "{}", _0)] FileError(crate::files::FileError), #[fail(display = "{}", _0)] - Nix(#[cause] nix::Error) + Nix(#[cause] nix::Error), + #[fail(display = "Refresh parent widget!")] + RefreshParent, + #[fail(display = "Refresh parent widget!")] + MiniBufferEvent(crate::minibuffer::MiniBufferEvent), } impl HError { @@ -134,12 +134,6 @@ impl HError { pub fn tags_not_loaded() -> HResult { Err(HError::TagsNotLoadedYetError) } - pub fn minibuffer_cancel() -> HResult { - Err(HError::MiniBufferCancelledInput) - } - pub fn minibuffer_empty() -> HResult { - Err(HError::MiniBufferEmptyInput) - } pub fn undefined_key(key: Key) -> HResult { Err(HError::WidgetUndefinedKeyError { key: key }) } @@ -417,6 +411,12 @@ impl From for HError { } } +impl From for HError { + fn from(e: crate::minibuffer::MiniBufferEvent) -> Self { + HError::MiniBufferEvent(e) + } +} + #[derive(Fail, Debug, Clone)] pub enum KeyBindError { diff --git a/src/file_browser.rs b/src/file_browser.rs index 3a84ee1..fcc0c6c 100644 --- a/src/file_browser.rs +++ b/src/file_browser.rs @@ -631,6 +631,13 @@ impl FileBrowser { self.draw().log(); continue; } + + if let Err(HError::RefreshParent) = bookmark { + self.refresh().log(); + self.draw().log(); + continue; + } + return bookmark; } } @@ -876,11 +883,146 @@ impl FileBrowser { } pub fn turbo_cd(&mut self) -> HResult<()> { - let dir = self.core.minibuffer("cd")?; + use crate::minibuffer::MiniBufferEvent::*; + + // Return and reset on cancel + let orig_dir = self.cwd()?.clone(); + let orig_dir_selected_file = self.selected_file()?; + let mut orig_dir_filter = self.main_widget()? + .content + .get_filter(); + + // For current dir + let mut selected_file = Some(orig_dir_selected_file.clone()); + let mut filter = Some(orig_dir_filter.clone()); + + // Helper function to restore any previous filter/selection + let dir_restore = + |s: &mut FileBrowser, filter: Option>, file: Option| { + s.main_widget_mut() + .map(|mw| { + filter.map(|f| mw.set_filter(f)); + file.map(|f| mw.select_file(&f)); + }).log(); + }; - let path = std::path::PathBuf::from(&dir); - let dir = File::new_from_path(&path.canonicalize()?)?; - self.main_widget_goto(&dir)?; + loop { + let input = self.core.minibuffer_continuous("nav"); + // dbg!(&input); + // self.refresh().log(); + // self.draw().log(); + + match input { + // While minibuffer runs it steals all events, thus explicit refresh/redraw + Err(HError::RefreshParent) => { + self.refresh().log(); + self.draw().log(); + continue; + } + Err(HError::MiniBufferEvent(event)) => { + match event { + // Done here, restore filter, but leave selection as is + Done(_) | Empty => { + dir_restore(self, filter.take(), None); + self.core.minibuffer_clear().log(); + break; + } + NewInput(input) => { + // Don't filter anything until a letter appears + if input.as_str() == "." || input.as_str() == ".." { + continue; + } + + if input.ends_with('/') { + match input.as_str() { + "../" => { + dir_restore(self, + filter.take(), + selected_file.take()); + self.go_back().log(); + self.core.minibuffer_clear().log(); + } + _ => { + let sel = self.selected_file()?; + + if sel.is_dir() { + dir_restore(self, + filter.take(), + selected_file.take()); + self.main_widget_goto(&sel)?; + self.core.minibuffer_clear().log(); + } + } + } + continue; + } + + // Save current filter, if existing, before overwriting it + // Type is Option>, because filter itself is Option<_> + if filter.is_none() { + let dir_filter = self.main_widget()? + .content + .get_filter(); + filter = Some(dir_filter); + } + + // To restore on leave/cancel + if selected_file.is_none() { + selected_file = Some(self.selected_file()?); + } + + self.main_widget_mut()? + .set_filter(Some(input)); + } + // Restore original directory and filter/selection + Cancelled => { + self.main_widget_goto(&orig_dir)?; + // Special case, because all others fail if directory isn't ready anyway + self.main_async_widget_mut()? + .widget + .on_ready(move |mw,_| { + let mw = mw?; + mw.set_filter(orig_dir_filter.take()); + mw.select_file(&orig_dir_selected_file); + Ok(()) + })?; + break; + } + CycleNext => { + // Because of filtering the selected file isn't just at n+1 + let oldpos = self.main_widget()?.get_selection(); + + let mw = self.main_widget_mut()?; + mw.move_down(); + mw.update_selected_file(oldpos); + + // Refresh preview and draw header, too + self.refresh().log(); + self.draw().log(); + + // Explicitly selected + selected_file = Some(self.selected_file()?); + } + CyclePrev => { + // Because of filtering the selected file isn't just at n-1 + let oldpos = self.main_widget()?.get_selection(); + + let mw = self.main_widget_mut()?; + mw.move_up(); + mw.update_selected_file(oldpos); + + // Refresh preview and draw header, too + self.refresh().log(); + self.draw().log(); + + // Explicitly selected + selected_file = Some(self.selected_file()?); + } + } + }, + _ => { } + } + } Ok(()) } @@ -1121,13 +1263,36 @@ impl FileBrowser { pub fn show_procview(&mut self) -> HResult<()> { self.preview_widget().map(|preview| preview.cancel_animation()).log(); - self.proc_view.lock()?.popup()?; + let procview = self.proc_view.clone(); + loop { + match procview.lock()?.popup() { + // Ignore refresh + Err(HError::RefreshParent) => continue, + Err(HError::TerminalResizedError) | + Err(HError::WidgetResizedError) => self.resize().log(), + _ => break + } + } Ok(()) } pub fn show_log(&mut self) -> HResult<()> { self.preview_widget().map(|preview| preview.cancel_animation()).log(); - self.log_view.lock()?.popup()?; + loop { + let res = self.log_view.lock()?.popup(); + + if let Err(HError::RefreshParent) = res { + continue + } + + if let Err(HError::TerminalResizedError) = res { + self.resize().log(); + continue; + } + + break + } + Ok(()) } diff --git a/src/listview.rs b/src/listview.rs index b260b6a..d728cf1 100644 --- a/src/listview.rs +++ b/src/listview.rs @@ -602,20 +602,38 @@ impl ListView // 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(); - + Err(HError::RefreshParent) => { + self.refresh().log(); continue; - }, - Err(HError::MiniBufferEmptyInput) | - Err(HError::MiniBufferCancelledInput) => { - self.select_file(&selected_file); + } + Err(HError::MiniBufferEvent(ev)) => { + use crate::minibuffer::MiniBufferEvent::*; + + match ev { + Done(_) => {} + NewInput(input) => { + let file = self.content + .find_file_with_name(&input) + .cloned(); + + file.map(|f| self.select_file(&f)); + + self.draw().log(); + + self.searching = Some(input); + + continue; + } + Empty | Cancelled => { + self.select_file(&selected_file); + } + CycleNext => { + self.search_next().log(); + } + CyclePrev => { + self.search_prev().log(); + } + } } _ => { } } @@ -688,35 +706,61 @@ impl ListView Ok(()) } + pub fn set_filter(&mut self, filter: Option) { + let prev_len = self.len(); + let selected_file = self.clone_selected_file(); + + self.content.set_filter(filter); + + // Only do something if filter changed something + if self.len() != prev_len { + self.refresh().ok(); + self.select_file(&selected_file); + // Clear away that wouldn't get drawn over + if self.len() < prev_len { + self.core.clear().ok(); + } + self.draw().ok(); + } + } + fn filter(&mut self) -> HResult<()> { + use crate::minibuffer::MiniBufferEvent::*; + let selected_file = self.selected_file().clone(); + let mut prev_filter = self.content.get_filter(); loop { let filter = self.core.minibuffer_continuous("filter"); match filter { - Err(HError::MiniBufferInputUpdated(input)) => { - self.content.set_filter(Some(input)); - self.refresh().ok(); - - 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); + Err(HError::MiniBufferEvent(event)) => { + match event { + Done(filter) => { + self.core.show_status(&format!("Filtering with: \"{}\"", + &filter)).log(); + + self.set_filter(Some(filter)); + } + NewInput(input) => { + self.set_filter(Some(input.clone())); + continue; + } + Empty => { + self.set_filter(None); + } + Cancelled => { + self.set_filter(prev_filter.take()); + self.select_file(&selected_file); + } + _ => {} + } } _ => {} } - let msgstr = filter.clone().unwrap_or(String::from("")); - self.core.show_status(&format!("Filtering with: \"{}\"", msgstr)).log(); - break; + } Ok(()) diff --git a/src/minibuffer.rs b/src/minibuffer.rs index 12cd900..de0ca91 100644 --- a/src/minibuffer.rs +++ b/src/minibuffer.rs @@ -128,6 +128,16 @@ impl History { } } +#[derive(Clone, Debug)] +pub enum MiniBufferEvent { + Done(String), + NewInput(String), + Empty, + Cancelled, + CycleNext, + CyclePrev +} + #[derive(Debug)] pub struct MiniBuffer { core: WidgetCore, @@ -171,8 +181,8 @@ impl MiniBuffer { self.core.screen()?.cursor_hide().log(); match self.popup() { - Err(HError::MiniBufferCancelledInput) => self.input_cancelled()?, - err @ Err(HError::MiniBufferInputUpdated(_)) => err?, + event @ Err(HError::MiniBufferEvent(_)) => event?, + err @ Err(HError::RefreshParent) => err?, _ => {} }; @@ -186,7 +196,6 @@ impl MiniBuffer { pub fn clear(&mut self) { self.input.clear(); self.position = 0; - self.history.reset(); self.completions.clear(); self.last_completion = None; } @@ -255,6 +264,11 @@ impl MiniBuffer { } pub fn history_up(&mut self) -> HResult<()> { + if self.query.as_str() == "nav" { + return Err(MiniBufferEvent::CyclePrev)?; + } + + if let Ok(historic) = self.history.get_prev(&self.query) { self.position = historic.len(); self.input = historic; @@ -263,6 +277,11 @@ impl MiniBuffer { } pub fn history_down(&mut self) -> HResult<()> { + if self.query.as_str() == "nav" { + return Err(MiniBufferEvent::CycleNext)?; + } + + if let Ok(historic) = self.history.get_next(&self.query) { self.position = historic.len(); self.input = historic; @@ -340,16 +359,16 @@ impl MiniBuffer { pub fn input_cancelled(&self) -> HResult<()> { self.core.show_status("Input cancelled").log(); - return HError::minibuffer_cancel() + return Err(MiniBufferEvent::Cancelled)?; } pub fn input_updated(&self) -> HResult<()> { - return HError::input_updated(self.input.clone()) + return Err(MiniBufferEvent::NewInput(self.input.clone()))?; } pub fn input_empty(&self) -> HResult<()> { self.core.show_status("Empty!").log(); - return HError::minibuffer_empty() + return Err(MiniBufferEvent::Empty)?; } } diff --git a/src/quick_actions.rs b/src/quick_actions.rs index 5a08e0e..74ff9db 100644 --- a/src/quick_actions.rs +++ b/src/quick_actions.rs @@ -277,7 +277,15 @@ pub fn open(files: Vec, act_base.map(|act| action_view.content.push(act)).ok(); act_sub.map(|act| action_view.content.push(act)).ok(); - action_view.popup() + loop { + // TODO: Handle this properly + match action_view.popup() { + Err(HError::RefreshParent) => continue, + Err(HError::WidgetResizedError) => continue, + Err(HError::TerminalResizedError) => continue, + r @ _ => break r + } + } } @@ -355,6 +363,7 @@ impl QuickAction { files: Vec, core: &WidgetCore, proc_view: Arc>) -> HResult<()> { + use crate::minibuffer::MiniBufferEvent::*;; let answers = self.queries .iter() @@ -364,7 +373,7 @@ impl QuickAction { if acc.is_err() { return acc; } match core.minibuffer(query) { - Err(HError::MiniBufferEmptyInput) => { + Err(HError::MiniBufferEvent(Empty)) => { acc.as_mut() .map(|acc| acc.push((OsString::from(query), OsString::from("")))) diff --git a/src/widget.rs b/src/widget.rs index c5c7511..e4dd677 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -139,6 +139,15 @@ impl WidgetCore { Ok(()) } + pub fn minibuffer_clear(&self) -> HResult<()> { + self.minibuffer + .lock()? + .as_mut()? + .clear(); + + Ok(()) + } + pub fn minibuffer(&self, query: &str) -> HResult { let answer = self.minibuffer .lock()? @@ -338,7 +347,11 @@ pub trait Widget { print!("\x1b_Ga=d,d=y,y={}\x1b\\", ypos+1); } let result = self.run_widget(); - self.get_core()?.clear().log(); + match result { + Err(HError::RefreshParent) => {}, + _ => self.get_core()?.clear().log() + } + self.get_core()?.get_sender().send(Events::ExclusiveEvent(None))?; result } @@ -364,17 +377,15 @@ pub trait Widget { match self.on_event(input) { err @ Err(HError::PopupFinnished) | err @ Err(HError::Quit) | - err @ Err(HError::MiniBufferCancelledInput) => err?, - err @ Err(HError::MiniBufferInputUpdated(_)) => err?, err @ Err(HError::WidgetResizedError) => err?, + event @ Err(HError::MiniBufferEvent(_)) => event?, err @ Err(_) => err.log(), Ok(_) => {} } self.get_core()?.get_sender().send(Events::RequestInput)?; } Events::WidgetReady => { - self.refresh().log(); - self.draw().log(); + return Err(HError::RefreshParent); } Events::Status(status) => { self.get_core()?.show_status(&status).log(); -- cgit v1.2.3