diff --git a/src/app.rs b/src/app.rs index b77fe13..56d2e2b 100644 --- a/src/app.rs +++ b/src/app.rs @@ -46,6 +46,7 @@ pub enum Action { Cut, HistoryNext, HistoryPrevious, + LocationUp, MoveToTrash, NewFile, NewFolder, @@ -71,6 +72,7 @@ impl Action { Action::Cut => Message::Cut(entity_opt), Action::HistoryNext => Message::TabMessage(None, tab::Message::GoNext), Action::HistoryPrevious => Message::TabMessage(None, tab::Message::GoPrevious), + Action::LocationUp => Message::TabMessage(None, tab::Message::LocationUp), Action::MoveToTrash => Message::MoveToTrash(entity_opt), Action::NewFile => Message::NewFile(entity_opt), Action::NewFolder => Message::NewFolder(entity_opt), diff --git a/src/key_bind.rs b/src/key_bind.rs index f1a33b1..96e0c1f 100644 --- a/src/key_bind.rs +++ b/src/key_bind.rs @@ -57,6 +57,7 @@ pub fn key_binds() -> HashMap { bind!([Ctrl], X, Cut); bind!([Alt], Right, HistoryNext); bind!([Alt], Left, HistoryPrevious); + bind!([Alt], Up, LocationUp); bind!([Ctrl], V, Paste); bind!([Ctrl], A, SelectAll); bind!([Ctrl], W, TabClose); diff --git a/src/tab.rs b/src/tab.rs index 5476e79..0c8fdf1 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -23,7 +23,7 @@ use std::{ time::{Duration, Instant}, }; -use crate::{fl, mime_icon::mime_icon}; +use crate::{fl, home_dir, mime_icon::mime_icon}; const DOUBLE_CLICK_DURATION: Duration = Duration::from_millis(500); //TODO: configurable @@ -384,6 +384,7 @@ pub enum Message { GoNext, GoPrevious, Location(Location), + LocationUp, RightClick(usize), View(View), } @@ -622,6 +623,22 @@ impl Tab { Message::Location(location) => { cd = Some(location); } + Message::LocationUp => { + // Sets location to the path's parent + // Does nothing if path is root or location is Trash + if let Location::Path(ref path) = self.location { + // Canonicalize is needed because parent() can return an empty path for + // relative paths which would fail to open. + // Canonicalizing the path should do the right thing of evaluating the path + // so that the parent successfully moves up the hierarchy. + // If it fails (i.e. the path doesn't exist for some reason) then it returns + // to home. + let mut path = path.canonicalize().unwrap_or_else(|_| home_dir()); + if path.pop() { + cd = Some(Location::Path(path)); + } + } + } Message::RightClick(click_i) => { if let Some(ref mut items) = self.items_opt { if !items.get(click_i).map_or(false, |x| x.selected) { @@ -1297,4 +1314,23 @@ mod tests { Ok(()) } + + #[test] + fn tab_locationup_moves_up_hierarchy() -> io::Result<()> { + let fs = simple_fs(0, NUM_NESTED, NUM_DIRS, 0, NAME_LEN)?; + let path = fs.path(); + let mut next_dir = filter_dirs(path)? + .next() + .expect("should be at least one directory"); + + let mut tab = Tab::new(Location::Path(next_dir.clone())); + // This will eventually yield false once root is hit + while next_dir.pop() { + debug!("Emitting Message::LocationUp",); + tab.update(Message::LocationUp, Modifiers::empty()); + assert_eq_tab_path(&tab, &next_dir); + } + + Ok(()) + } }