diff --git a/src/directory.rs b/src/directory.rs index a02fec5..6365cd4 100644 --- a/src/directory.rs +++ b/src/directory.rs @@ -2,6 +2,7 @@ mod display; mod item; mod constructors; +use std::ops::Range; use std::fs::{read_dir, remove_dir_all, remove_file, rename, DirBuilder, File}; use std::io::{Error, ErrorKind}; use std::path::PathBuf; @@ -19,15 +20,17 @@ use item::Item; pub struct Directory { dirs: Vec, src_name: Option, + display_start: u16, + height: u16, selection: u16, editing: bool, } impl Directory { - pub fn new(root: String) -> Result { + pub fn new(root: String, height: u16) -> Result { match read_dir(&root) { - Ok(d) => constructors::new_from_existing(root, d), + Ok(d) => constructors::new_from_existing(root, d, height), Err(e) => if e.kind() == ErrorKind::NotFound { - constructors::create_new(root) + constructors::create_new(root, height) } else { Err(e) } @@ -56,14 +59,23 @@ impl Directory { } pub fn select_prev(&mut self) { - if self.selection > 0 { - self.selection -= 1; + if self.selection == 0 { + return + } + self.selection -= 1; + if self.selection < self.display_start { + self.display_start = self.selection; } } pub fn select_next(&mut self) { - if self.selection()+1 < self.dirs.len() { - self.selection += 1; + if self.selection()+1 >= self.dirs.len() { + return; + } + + self.selection += 1; + if self.selection > self.height { + self.display_start += 1; } } @@ -101,6 +113,129 @@ impl Directory { } } + fn subtree_indices(&self, index: usize) -> Range { + let current_depth = self.selected().depth; + + let end = (index+1..self.dirs.len()) + .find(|&i| self.dirs[i].depth <= current_depth) + .unwrap_or(self.dirs.len()); + + index..end + } + + /// Get the current "subtree", where + /// a subtree is the slice of `self.dirs` + /// that are contained within the current + /// selection, inclusive of the currently + /// selected item. + /// + /// If the current selection is a file, and + /// therfore contains nothing, the returned + /// slice only contains that. + /// + /// If the current selection is a director, + /// it returns the directory and its contents + fn subtree_mut(&mut self, index: usize) -> &mut [Item] { + let indices = self.subtree_indices(index); + &mut self.dirs[indices] + } + + fn selected_tree(&mut self) -> &mut [Item] { + self.subtree_mut(self.selection()) + } + + /// decrease indent of the current item + /// (and any contained), making sure + /// not to indent too low + fn outdent(&mut self) { + let s = self.selected_mut(); + + if s.depth <= 1 { + return; + } + + for item in self.selected_tree() { + item.depth -= 1; + } + } + + /// indent the current item (and any contained), + /// making sure not to indent more than is + /// logically possible + fn indent(&mut self) { + let current_depth = self.selected().depth; + + let Some(first_applicable_parent) = self.dirs[0 .. self.selection()] + .iter() + .rev() + .filter(|item| item.depth <= current_depth) + .take_while(|item| !item.is_file) + .find(|item| { + item.depth <= current_depth + }) + else { + // no applicable parent directort, + // short circuit + return + }; + + if current_depth > first_applicable_parent.depth { + return; + } + + for item in self.selected_tree() { + item.depth += 1; + } + } + + fn siblings(&self) -> impl Iterator + '_ { + let current_depth = self.selected().depth; + + (self.selection()+1 .. self.dirs.len()) + .take_while(move |&i| self.dirs[i].depth >= current_depth) + .filter(move |&i| self.dirs[i].depth == current_depth) + } + + fn move_down(&mut self) { + let Some(index) = self.siblings().next() + else { + return + }; + + let selected_subtree = self.subtree_indices(self.selection()); + let sibling_subtree = self.subtree_indices(index); + + let delta = if selected_subtree.len() < sibling_subtree.len() { + let select_len = selected_subtree.len(); + let sibling_len = sibling_subtree.len(); + + let selected_copy = self.dirs[selected_subtree].to_owned(); + let sibling_copy = self.dirs[sibling_subtree].to_owned(); + let back_to_back = sibling_copy.into_iter().chain(selected_copy); + + let full_range = self.selection() .. self.selection()+select_len+sibling_len; + for (dst, item) in self.dirs[full_range].iter_mut().zip(back_to_back) { + *dst = item; + } + + sibling_len + } else if selected_subtree.len() == sibling_subtree.len() { + let (selected, sibling) = self.dirs.split_at_mut(index); + + let len = selected.len(); + let lhs = &mut selected[len - selected_subtree.len() .. ]; + let rhs = &mut sibling[0 .. sibling_subtree.len()]; + + lhs.swap_with_slice(rhs); + + selected_subtree.len() + } else { + todo!() + }; + + self.selection += u16::try_from(delta).unwrap_or(u16::MAX); + } + fn selected(&self) -> &Item { &self.dirs[self.selection()] } @@ -155,7 +290,11 @@ impl Directory { if self.selection() >= self.dirs.len() { self.selection -= 1; } - } + }, + KeyCode::Home => { + self.display_start = 0; + self.selection = 0; + }, _ => (), } @@ -185,6 +324,15 @@ impl Directory { KeyCode::Backspace => { self.selected_mut().name.pop(); }, + KeyCode::Left => { + self.outdent(); + }, + KeyCode::Right | KeyCode::Tab => { + self.indent(); + }, + KeyCode::Down => { + self.move_down(); + } _ => (), } @@ -192,9 +340,7 @@ impl Directory { } fn display_lines(&self) -> impl Iterator + '_ { - let iter = self.dirs.iter().enumerate(); - - iter.scan( + self.dirs.iter().enumerate().scan( Vec::new(), |indent_parts, (i, current)| { let is_last_at_this_depth = is_last_at_this_depth(&self.dirs[i+1..], current.depth); @@ -204,7 +350,7 @@ impl Directory { (TreePart::First, TreePart::Wait) }; - if i == 0 { return Some(current.name.to_owned() + "/") } + if i == 0 { return Some(current.name.clone() + "/") } if current.depth as usize > indent_parts.len() { indent_parts.push(continue_part); @@ -218,9 +364,9 @@ impl Directory { + new_part.display() + ¤t.name + if current.is_file { - "\n" + "" } else { - "/\n" + "/" }; Some(result) @@ -238,7 +384,7 @@ impl Pane for Directory { let x = u16::from(self.selected().depth) * 4 + u16::try_from(self.selected().name.len()) .unwrap_or(u16::MAX); - let y = self.selection; + let y = self.selection - self.display_start; Some((x, y)) } @@ -259,6 +405,7 @@ impl Pane for Directory { fn display(&self, output: OutputSink, area: Rect) { let rows = self.display_lines() .enumerate() + .skip(self.display_start.into()) .map(|(i, s)| { let row = ListItem::new(s); @@ -269,6 +416,7 @@ impl Pane for Directory { row } }) + .take(area.height.into()) .collect(); let list = into_list(rows); diff --git a/src/directory/constructors.rs b/src/directory/constructors.rs index 936b7f4..657bdf6 100644 --- a/src/directory/constructors.rs +++ b/src/directory/constructors.rs @@ -3,7 +3,7 @@ use super::{Directory, Item}; use std::fs::{read_dir, DirBuilder, ReadDir}; use std::io::{Error, ErrorKind}; -pub(super) fn create_new(root: String) -> Result { +pub(super) fn create_new(root: String, height: u16) -> Result { DirBuilder::new() .recursive(true) .create(&root)?; @@ -11,12 +11,14 @@ pub(super) fn create_new(root: String) -> Result { Ok(Directory { dirs: vec![Item::new(root, 0, false)], src_name: None, + display_start: 0, + height, selection: 0, editing: false, }) } -pub(super) fn new_from_existing(root: String, d: ReadDir) -> Result { +pub(super) fn new_from_existing(root: String, d: ReadDir, height: u16) -> Result { let mut dirs = vec![Item::new(root, 0, false)]; add_items(&mut dirs, 1, d)?; @@ -24,6 +26,8 @@ pub(super) fn new_from_existing(root: String, d: ReadDir) -> Result AnyResult<()> { let _redirect = Redirect::stderr(target)?; - let result = do_tui(args); + do_tui(args).map_err(Into::::into).unwrap(); remove_file(LOG_FILE)?; - result.map_err(Into::into) + Ok(()) } fn do_tui(args: Args) -> Result<(), Error> { diff --git a/src/state.rs b/src/state.rs index 0e6435c..799c761 100644 --- a/src/state.rs +++ b/src/state.rs @@ -23,7 +23,8 @@ pub struct State { } impl State { pub fn new(root: String, area: Rect) -> Result { - let dir = Directory::new(root)?; + let dir = Directory::new(root, area.height-3)?; + Ok(Self { dir, size: area,