From 38299ab5be45a1f9ed7e863560bb2b4902d46daa Mon Sep 17 00:00:00 2001 From: Nguyen Duc Toan Date: Sat, 30 Sep 2023 13:32:07 +0700 Subject: [PATCH] feat: backward/forward --- app/src/app.rs | 4 +- app/src/executor.rs | 2 +- core/src/event.rs | 6 +-- core/src/external/clipboard.rs | 2 +- core/src/manager/backstack.rs | 69 ++++++++++++++++++++++++++++++++++ core/src/manager/mod.rs | 1 + core/src/manager/tab.rs | 42 +++++++++++++++------ 7 files changed, 107 insertions(+), 19 deletions(-) create mode 100644 core/src/manager/backstack.rs diff --git a/app/src/app.rs b/app/src/app.rs index 761d09171..4be526934 100644 --- a/app/src/app.rs +++ b/app/src/app.rs @@ -119,9 +119,9 @@ impl App { let manager = &mut self.cx.manager; let tasks = &mut self.cx.tasks; match event { - Event::Cd(url) => { + Event::Cd(url, backstack_push) => { futures::executor::block_on(async { - manager.active_mut().cd(expand_url(url)).await; + manager.active_mut().cd(expand_url(url), backstack_push).await; }); } Event::Refresh => { diff --git a/app/src/executor.rs b/app/src/executor.rs index d4dd0b213..c617cd2d7 100644 --- a/app/src/executor.rs +++ b/app/src/executor.rs @@ -78,7 +78,7 @@ impl Executor { if exec.named.contains_key("interactive") { cx.manager.active_mut().cd_interactive(url) } else { - emit!(Cd(url)); + emit!(Cd(url, true)); false } } diff --git a/core/src/event.rs b/core/src/event.rs index 5537c50a0..3aab3c374 100644 --- a/core/src/event.rs +++ b/core/src/event.rs @@ -21,7 +21,7 @@ pub enum Event { Call(Vec, KeymapLayer), // Manager - Cd(Url), + Cd(Url, bool), Refresh, Files(FilesOp), Pages(usize), @@ -71,8 +71,8 @@ macro_rules! emit { $crate::Event::Call($exec, $layer).emit(); }; - (Cd($url:expr)) => { - $crate::Event::Cd($url).emit(); + (Cd($url:expr, $backstack_push:expr)) => { + $crate::Event::Cd($url, $backstack_push).emit(); }; (Files($op:expr)) => { $crate::Event::Files($op).emit(); diff --git a/core/src/external/clipboard.rs b/core/src/external/clipboard.rs index 0560d6f00..86e770b5d 100644 --- a/core/src/external/clipboard.rs +++ b/core/src/external/clipboard.rs @@ -86,5 +86,5 @@ pub async fn clipboard_set(s: impl AsRef) -> Result<()> { let result = tokio::task::spawn_blocking(move || set_clipboard(formats::Unicode, s.to_string_lossy())); - Ok(result.await?.map_err(|_| anyhow!("failed to set clipboard"))?) + result.await?.map_err(|_| anyhow!("failed to set clipboard")) } diff --git a/core/src/manager/backstack.rs b/core/src/manager/backstack.rs new file mode 100644 index 000000000..bf2681eab --- /dev/null +++ b/core/src/manager/backstack.rs @@ -0,0 +1,69 @@ +pub struct BackStack { + current: usize, + stack: Vec, +} + +impl BackStack { + pub fn new(item: T) -> Self { Self { current: 0, stack: vec![item] } } + + pub fn shift_backward(&mut self) -> Option<&T> { + if self.current > 0 { + self.current -= 1; + Some(&self.stack[self.current]) + } else { + None + } + } + + pub fn shift_forward(&mut self) -> Option<&T> { + if self.current + 1 == self.stack.len() { + None + } else { + self.current += 1; + Some(&self.stack[self.current]) + } + } + + pub fn push(&mut self, item: T) { + self.current += 1; + if self.current == self.stack.len() { + self.stack.push(item); + } else { + self.stack[self.current] = item; + self.stack.truncate(self.current + 1); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn get_current(backstack: &BackStack) -> &T { &backstack.stack[backstack.current] } + + #[test] + fn test_backstack() { + let mut backstack = BackStack::::new(1); + assert_eq!(get_current(&backstack), &1); + + backstack.push(2); + backstack.push(3); + assert_eq!(get_current(&backstack), &3); + + assert_eq!(backstack.shift_backward(), Some(&2)); + assert_eq!(backstack.shift_backward(), Some(&1)); + assert_eq!(backstack.shift_backward(), None); + assert_eq!(backstack.shift_backward(), None); + assert_eq!(get_current(&backstack), &1); + assert_eq!(backstack.shift_forward(), Some(&2)); + assert_eq!(backstack.shift_forward(), Some(&3)); + assert_eq!(backstack.shift_forward(), None); + + backstack.shift_backward(); + backstack.push(4); + + assert_eq!(get_current(&backstack), &4); + assert_eq!(backstack.shift_forward(), None); + assert_eq!(backstack.shift_backward(), Some(&2)); + } +} diff --git a/core/src/manager/mod.rs b/core/src/manager/mod.rs index afa145490..6ece3cb87 100644 --- a/core/src/manager/mod.rs +++ b/core/src/manager/mod.rs @@ -1,3 +1,4 @@ +mod backstack; mod finder; mod folder; mod manager; diff --git a/core/src/manager/tab.rs b/core/src/manager/tab.rs index 295e8c65e..f6e996ec9 100644 --- a/core/src/manager/tab.rs +++ b/core/src/manager/tab.rs @@ -6,7 +6,7 @@ use shared::{Debounce, Defer, InputError, Url}; use tokio::{pin, task::JoinHandle}; use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt}; -use super::{Finder, Folder, Mode, Preview, PreviewLock}; +use super::{backstack::BackStack, Finder, Folder, Mode, Preview, PreviewLock}; use crate::{emit, external::{self, FzfOpt, ZoxideOpt}, files::{File, FilesOp, FilesSorter}, input::InputOpt, Event, Step, BLOCKER}; pub struct Tab { @@ -14,8 +14,9 @@ pub struct Tab { pub(super) current: Folder, pub(super) parent: Option, - pub(super) history: BTreeMap, - pub(super) preview: Preview, + pub(super) backstack: BackStack, + pub(super) history: BTreeMap, + pub(super) preview: Preview, finder: Option, search: Option>>, @@ -29,9 +30,10 @@ impl From for Tab { Self { mode: Default::default(), - current: Folder::from(url), + current: Folder::from(url.clone()), parent, + backstack: BackStack::new(url), history: Default::default(), preview: Default::default(), @@ -87,7 +89,7 @@ impl Tab { true } - pub async fn cd(&mut self, mut target: Url) -> bool { + pub async fn cd(&mut self, mut target: Url, backstack_push: bool) -> bool { let Ok(file) = File::from(target.clone()).await else { return false; }; @@ -109,6 +111,10 @@ impl Tab { self.history.insert(rep.cwd.clone(), rep); } + if backstack_push { + self.backstack.push(target.clone()); + } + let rep = self.history_new(&target); let rep = mem::replace(&mut self.current, rep); if rep.cwd.is_regular() { @@ -132,7 +138,7 @@ impl Tab { emit!(Input(InputOpt::top("Change directory:").with_value(target.to_string_lossy()))); if let Some(Ok(s)) = result.recv().await { - emit!(Cd(Url::from(s))); + emit!(Cd(Url::from(s), true)); } }); false @@ -157,6 +163,8 @@ impl Tab { } self.parent = Some(self.history_new(&hovered.parent().unwrap())); + self.backstack.push(self.current.cwd.clone()); + emit!(Refresh); true } @@ -187,15 +195,25 @@ impl Tab { self.history.insert(rep.cwd.clone(), rep); } + self.backstack.push(self.current.cwd.clone()); + emit!(Refresh); true } - // TODO - pub fn back(&mut self) -> bool { false } + pub fn back(&mut self) -> bool { + if let Some(url) = self.backstack.shift_backward() { + emit!(Cd(url.clone(), false)); + } + false + } - // TODO - pub fn forward(&mut self) -> bool { false } + pub fn forward(&mut self) -> bool { + if let Some(url) = self.backstack.shift_forward() { + emit!(Cd(url.clone(), false)); + } + false + } pub fn select(&mut self, state: Option) -> bool { if let Some(ref hovered) = self.current.hovered { @@ -309,7 +327,7 @@ impl Tab { let mut first = true; while let Some(chunk) = rx.next().await { if first { - emit!(Cd(cwd.clone())); + emit!(Cd(cwd.clone(), true)); first = false; } emit!(Files(FilesOp::Part(cwd.clone(), ticket, chunk))); @@ -345,7 +363,7 @@ impl Tab { if global { external::fzf(FzfOpt { cwd }) } else { external::zoxide(ZoxideOpt { cwd }) }?; if let Ok(target) = rx.await? { - emit!(Cd(target)); + emit!(Cd(target, true)); } Ok::<(), Error>(()) });