aided display with large directory structures, half implemented moving files visually

main
nick 2024-10-10 12:51:45 -04:00
parent a0d1e3f835
commit d4a28340d8
4 changed files with 173 additions and 20 deletions

View File

@ -2,6 +2,7 @@ mod display;
mod item; mod item;
mod constructors; mod constructors;
use std::ops::Range;
use std::fs::{read_dir, remove_dir_all, remove_file, rename, DirBuilder, File}; use std::fs::{read_dir, remove_dir_all, remove_file, rename, DirBuilder, File};
use std::io::{Error, ErrorKind}; use std::io::{Error, ErrorKind};
use std::path::PathBuf; use std::path::PathBuf;
@ -19,15 +20,17 @@ use item::Item;
pub struct Directory { pub struct Directory {
dirs: Vec<Item>, dirs: Vec<Item>,
src_name: Option<PathBuf>, src_name: Option<PathBuf>,
display_start: u16,
height: u16,
selection: u16, selection: u16,
editing: bool, editing: bool,
} }
impl Directory { impl Directory {
pub fn new(root: String) -> Result<Self, Error> { pub fn new(root: String, height: u16) -> Result<Self, Error> {
match read_dir(&root) { 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 { Err(e) => if e.kind() == ErrorKind::NotFound {
constructors::create_new(root) constructors::create_new(root, height)
} else { } else {
Err(e) Err(e)
} }
@ -56,14 +59,23 @@ impl Directory {
} }
pub fn select_prev(&mut self) { pub fn select_prev(&mut self) {
if self.selection > 0 { if self.selection == 0 {
return
}
self.selection -= 1; self.selection -= 1;
if self.selection < self.display_start {
self.display_start = self.selection;
} }
} }
pub fn select_next(&mut self) { pub fn select_next(&mut self) {
if self.selection()+1 < self.dirs.len() { if self.selection()+1 >= self.dirs.len() {
return;
}
self.selection += 1; 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<usize> {
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<Item = usize> + '_ {
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 { fn selected(&self) -> &Item {
&self.dirs[self.selection()] &self.dirs[self.selection()]
} }
@ -155,7 +290,11 @@ impl Directory {
if self.selection() >= self.dirs.len() { if self.selection() >= self.dirs.len() {
self.selection -= 1; self.selection -= 1;
} }
} },
KeyCode::Home => {
self.display_start = 0;
self.selection = 0;
},
_ => (), _ => (),
} }
@ -185,6 +324,15 @@ impl Directory {
KeyCode::Backspace => { KeyCode::Backspace => {
self.selected_mut().name.pop(); 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<Item = String> + '_ { fn display_lines(&self) -> impl Iterator<Item = String> + '_ {
let iter = self.dirs.iter().enumerate(); self.dirs.iter().enumerate().scan(
iter.scan(
Vec::new(), Vec::new(),
|indent_parts, (i, current)| { |indent_parts, (i, current)| {
let is_last_at_this_depth = is_last_at_this_depth(&self.dirs[i+1..], current.depth); 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) (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() { if current.depth as usize > indent_parts.len() {
indent_parts.push(continue_part); indent_parts.push(continue_part);
@ -218,9 +364,9 @@ impl Directory {
+ new_part.display() + new_part.display()
+ &current.name + &current.name
+ if current.is_file { + if current.is_file {
"\n" ""
} else { } else {
"/\n" "/"
}; };
Some(result) Some(result)
@ -238,7 +384,7 @@ impl Pane for Directory {
let x = u16::from(self.selected().depth) * 4 let x = u16::from(self.selected().depth) * 4
+ u16::try_from(self.selected().name.len()) + u16::try_from(self.selected().name.len())
.unwrap_or(u16::MAX); .unwrap_or(u16::MAX);
let y = self.selection; let y = self.selection - self.display_start;
Some((x, y)) Some((x, y))
} }
@ -259,6 +405,7 @@ impl Pane for Directory {
fn display(&self, output: OutputSink, area: Rect) { fn display(&self, output: OutputSink, area: Rect) {
let rows = self.display_lines() let rows = self.display_lines()
.enumerate() .enumerate()
.skip(self.display_start.into())
.map(|(i, s)| { .map(|(i, s)| {
let row = ListItem::new(s); let row = ListItem::new(s);
@ -269,6 +416,7 @@ impl Pane for Directory {
row row
} }
}) })
.take(area.height.into())
.collect(); .collect();
let list = into_list(rows); let list = into_list(rows);

View File

@ -3,7 +3,7 @@ use super::{Directory, Item};
use std::fs::{read_dir, DirBuilder, ReadDir}; use std::fs::{read_dir, DirBuilder, ReadDir};
use std::io::{Error, ErrorKind}; use std::io::{Error, ErrorKind};
pub(super) fn create_new(root: String) -> Result<Directory, Error> { pub(super) fn create_new(root: String, height: u16) -> Result<Directory, Error> {
DirBuilder::new() DirBuilder::new()
.recursive(true) .recursive(true)
.create(&root)?; .create(&root)?;
@ -11,12 +11,14 @@ pub(super) fn create_new(root: String) -> Result<Directory, Error> {
Ok(Directory { Ok(Directory {
dirs: vec![Item::new(root, 0, false)], dirs: vec![Item::new(root, 0, false)],
src_name: None, src_name: None,
display_start: 0,
height,
selection: 0, selection: 0,
editing: false, editing: false,
}) })
} }
pub(super) fn new_from_existing(root: String, d: ReadDir) -> Result<Directory, Error> { pub(super) fn new_from_existing(root: String, d: ReadDir, height: u16) -> Result<Directory, Error> {
let mut dirs = vec![Item::new(root, 0, false)]; let mut dirs = vec![Item::new(root, 0, false)];
add_items(&mut dirs, 1, d)?; add_items(&mut dirs, 1, d)?;
@ -24,6 +26,8 @@ pub(super) fn new_from_existing(root: String, d: ReadDir) -> Result<Directory, E
Ok(Directory { Ok(Directory {
dirs, dirs,
src_name: None, src_name: None,
display_start: 0,
height,
selection: 0, selection: 0,
editing: false, editing: false,
}) })

View File

@ -43,11 +43,11 @@ fn main() -> AnyResult<()> {
let _redirect = Redirect::stderr(target)?; let _redirect = Redirect::stderr(target)?;
let result = do_tui(args); do_tui(args).map_err(Into::<AnyError>::into).unwrap();
remove_file(LOG_FILE)?; remove_file(LOG_FILE)?;
result.map_err(Into::into) Ok(())
} }
fn do_tui(args: Args) -> Result<(), Error> { fn do_tui(args: Args) -> Result<(), Error> {

View File

@ -23,7 +23,8 @@ pub struct State {
} }
impl State { impl State {
pub fn new(root: String, area: Rect) -> Result<Self, Error> { pub fn new(root: String, area: Rect) -> Result<Self, Error> {
let dir = Directory::new(root)?; let dir = Directory::new(root, area.height-3)?;
Ok(Self { Ok(Self {
dir, dir,
size: area, size: area,