improved navigation/turbo-cd

This commit is contained in:
rabite0 2020-03-01 22:57:09 +01:00
parent b163a9f4b4
commit e4850e38b6
6 changed files with 307 additions and 59 deletions

View File

@ -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<T>() -> HResult<T> {
Err(HError::TagsNotLoadedYetError)
}
pub fn minibuffer_cancel<T>() -> HResult<T> {
Err(HError::MiniBufferCancelledInput)
}
pub fn minibuffer_empty<T>() -> HResult<T> {
Err(HError::MiniBufferEmptyInput)
}
pub fn undefined_key<T>(key: Key) -> HResult<T> {
Err(HError::WidgetUndefinedKeyError { key: key })
}
@ -417,6 +411,12 @@ impl From<KeyBindError> for HError {
}
}
impl From<crate::minibuffer::MiniBufferEvent> for HError {
fn from(e: crate::minibuffer::MiniBufferEvent) -> Self {
HError::MiniBufferEvent(e)
}
}
#[derive(Fail, Debug, Clone)]
pub enum KeyBindError {

View File

@ -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::*;
let path = std::path::PathBuf::from(&dir);
let dir = File::new_from_path(&path.canonicalize()?)?;
self.main_widget_goto(&dir)?;
// 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<Option<String>>, file: Option<File>| {
s.main_widget_mut()
.map(|mw| {
filter.map(|f| mw.set_filter(f));
file.map(|f| mw.select_file(&f));
}).log();
};
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<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(())
}

View File

@ -602,20 +602,38 @@ impl ListView<Files>
// 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<Files>
Ok(())
}
pub fn set_filter(&mut self, filter: Option<String>) {
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();
Err(HError::MiniBufferEvent(event)) => {
match event {
Done(filter) => {
self.core.show_status(&format!("Filtering with: \"{}\"",
&filter)).log();
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);
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(())

View File

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

View File

@ -277,7 +277,15 @@ pub fn open(files: Vec<File>,
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<File>,
core: &WidgetCore,
proc_view: Arc<Mutex<ProcView>>) -> 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(""))))

View File

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