mirror of https://github.com/bobwen-dev/hunter
improved navigation/turbo-cd
This commit is contained in:
parent
b163a9f4b4
commit
e4850e38b6
22
src/fail.rs
22
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<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 {
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
||||
|
|
102
src/listview.rs
102
src/listview.rs
|
@ -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(())
|
||||
|
|
|
@ -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)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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(""))))
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue