feat: backward/forward (#230)

This commit is contained in:
Nguyễn Đức Toàn 2023-10-01 14:49:07 +07:00 committed by GitHub
parent e5340a2beb
commit e7eb459787
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 124 additions and 10 deletions

View File

@ -86,5 +86,5 @@ pub async fn clipboard_set(s: impl AsRef<std::ffi::OsStr>) -> Result<()> {
let result = let result =
tokio::task::spawn_blocking(move || set_clipboard(formats::Unicode, s.to_string_lossy())); 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"))
} }

View File

@ -0,0 +1,82 @@
pub struct Backstack<T: Eq> {
cursor: usize,
stack: Vec<T>,
}
impl<T: Eq> Backstack<T> {
pub fn new(item: T) -> Self { Self { cursor: 0, stack: vec![item] } }
pub fn push(&mut self, item: T) {
if self.stack[self.cursor] == item {
return;
}
self.cursor += 1;
if self.cursor == self.stack.len() {
self.stack.push(item);
} else {
self.stack[self.cursor] = item;
self.stack.truncate(self.cursor + 1);
}
// Only keep 30 items before the cursor, the cleanup threshold is 60
if self.stack.len() > 60 {
let start = self.cursor.saturating_sub(30);
self.stack.drain(..start);
self.cursor -= start;
}
}
#[cfg(test)]
#[inline]
pub fn current(&self) -> &T { &self.stack[self.cursor] }
pub fn shift_backward(&mut self) -> Option<&T> {
if self.cursor > 0 {
self.cursor -= 1;
Some(&self.stack[self.cursor])
} else {
None
}
}
pub fn shift_forward(&mut self) -> Option<&T> {
if self.cursor + 1 == self.stack.len() {
None
} else {
self.cursor += 1;
Some(&self.stack[self.cursor])
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_backstack() {
let mut backstack = Backstack::<u32>::new(1);
assert_eq!(backstack.current(), &1);
backstack.push(2);
backstack.push(3);
assert_eq!(backstack.current(), &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!(backstack.current(), &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!(backstack.current(), &4);
assert_eq!(backstack.shift_forward(), None);
assert_eq!(backstack.shift_backward(), Some(&2));
}
}

View File

@ -1,3 +1,4 @@
mod backstack;
mod finder; mod finder;
mod folder; mod folder;
mod manager; mod manager;
@ -7,6 +8,7 @@ mod tab;
mod tabs; mod tabs;
mod watcher; mod watcher;
pub use backstack::*;
pub use finder::*; pub use finder::*;
pub use folder::*; pub use folder::*;
pub use manager::*; pub use manager::*;

View File

@ -6,7 +6,7 @@ use shared::{Debounce, Defer, InputError, Url};
use tokio::{pin, task::JoinHandle}; use tokio::{pin, task::JoinHandle};
use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt}; use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt};
use super::{Finder, Folder, Mode, Preview, PreviewLock}; use super::{Backstack, Finder, Folder, Mode, Preview, PreviewLock};
use crate::{emit, external::{self, FzfOpt, ZoxideOpt}, files::{File, FilesOp, FilesSorter}, input::InputOpt, Event, Step, BLOCKER}; use crate::{emit, external::{self, FzfOpt, ZoxideOpt}, files::{File, FilesOp, FilesSorter}, input::InputOpt, Event, Step, BLOCKER};
pub struct Tab { pub struct Tab {
@ -14,6 +14,7 @@ pub struct Tab {
pub(super) current: Folder, pub(super) current: Folder,
pub(super) parent: Option<Folder>, pub(super) parent: Option<Folder>,
pub(super) backstack: Backstack<Url>,
pub(super) history: BTreeMap<Url, Folder>, pub(super) history: BTreeMap<Url, Folder>,
pub(super) preview: Preview, pub(super) preview: Preview,
@ -29,9 +30,10 @@ impl From<Url> for Tab {
Self { Self {
mode: Default::default(), mode: Default::default(),
current: Folder::from(url), current: Folder::from(url.clone()),
parent, parent,
backstack: Backstack::new(url),
history: Default::default(), history: Default::default(),
preview: Default::default(), preview: Default::default(),
@ -87,6 +89,7 @@ impl Tab {
true true
} }
// TODO: change to sync, and remove `Event::Cd`
pub async fn cd(&mut self, mut target: Url) -> bool { pub async fn cd(&mut self, mut target: Url) -> bool {
let Ok(file) = File::from(target.clone()).await else { let Ok(file) = File::from(target.clone()).await else {
return false; return false;
@ -98,6 +101,7 @@ impl Tab {
target = target.parent_url().unwrap(); target = target.parent_url().unwrap();
} }
// Already in target
if self.current.cwd == target { if self.current.cwd == target {
if hovered.map(|h| self.current.hover_force(h)) == Some(true) { if hovered.map(|h| self.current.hover_force(h)) == Some(true) {
emit!(Hover); emit!(Hover);
@ -105,23 +109,31 @@ impl Tab {
return false; return false;
} }
// Take parent to history
if let Some(rep) = self.parent.take() { if let Some(rep) = self.parent.take() {
self.history.insert(rep.cwd.clone(), rep); self.history.insert(rep.cwd.clone(), rep);
} }
// Current
let rep = self.history_new(&target); let rep = self.history_new(&target);
let rep = mem::replace(&mut self.current, rep); let rep = mem::replace(&mut self.current, rep);
if rep.cwd.is_regular() { if rep.cwd.is_regular() {
self.history.insert(rep.cwd.clone(), rep); self.history.insert(rep.cwd.clone(), rep);
} }
// Parent
if let Some(parent) = target.parent_url() { if let Some(parent) = target.parent_url() {
self.parent = Some(self.history_new(&parent)); self.parent = Some(self.history_new(&parent));
} }
// Hover the file
if let Some(h) = hovered { if let Some(h) = hovered {
self.current.hover_force(h); self.current.hover_force(h);
} }
// Backstack
self.backstack.push(target.clone());
emit!(Refresh); emit!(Refresh);
true true
} }
@ -146,17 +158,22 @@ impl Tab {
return false; return false;
} }
// Current
let rep = self.history_new(hovered.url()); let rep = self.history_new(hovered.url());
let rep = mem::replace(&mut self.current, rep); let rep = mem::replace(&mut self.current, rep);
if rep.cwd.is_regular() { if rep.cwd.is_regular() {
self.history.insert(rep.cwd.clone(), rep); self.history.insert(rep.cwd.clone(), rep);
} }
// Parent
if let Some(rep) = self.parent.take() { if let Some(rep) = self.parent.take() {
self.history.insert(rep.cwd.clone(), rep); self.history.insert(rep.cwd.clone(), rep);
} }
self.parent = Some(self.history_new(&hovered.parent().unwrap())); self.parent = Some(self.history_new(&hovered.parent().unwrap()));
// Backstack
self.backstack.push(hovered.url_owned());
emit!(Refresh); emit!(Refresh);
true true
} }
@ -174,6 +191,7 @@ impl Tab {
return false; return false;
}; };
// Parent
if let Some(rep) = self.parent.take() { if let Some(rep) = self.parent.take() {
self.history.insert(rep.cwd.clone(), rep); self.history.insert(rep.cwd.clone(), rep);
} }
@ -181,21 +199,33 @@ impl Tab {
self.parent = Some(self.history_new(&parent)); self.parent = Some(self.history_new(&parent));
} }
// Current
let rep = self.history_new(&current); let rep = self.history_new(&current);
let rep = mem::replace(&mut self.current, rep); let rep = mem::replace(&mut self.current, rep);
if rep.cwd.is_regular() { if rep.cwd.is_regular() {
self.history.insert(rep.cwd.clone(), rep); self.history.insert(rep.cwd.clone(), rep);
} }
// Backstack
self.backstack.push(current);
emit!(Refresh); emit!(Refresh);
true true
} }
// TODO pub fn back(&mut self) -> bool {
pub fn back(&mut self) -> bool { false } if let Some(url) = self.backstack.shift_backward().cloned() {
futures::executor::block_on(self.cd(url));
}
false
}
// TODO pub fn forward(&mut self) -> bool {
pub fn forward(&mut self) -> bool { false } if let Some(url) = self.backstack.shift_forward().cloned() {
futures::executor::block_on(self.cd(url));
}
false
}
pub fn select(&mut self, state: Option<bool>) -> bool { pub fn select(&mut self, state: Option<bool>) -> bool {
if let Some(ref hovered) = self.current.hovered { if let Some(ref hovered) = self.current.hovered {

View File

@ -1 +1 @@
{"language":"en","words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp"," Überzug"," Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT"],"version":"0.2","flagWords":[]} {"version":"0.2","words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp"," Überzug"," Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","backstack"],"flagWords":[],"language":"en"}