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 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<Item>,
src_name: Option<PathBuf>,
display_start: u16,
height: u16,
selection: u16,
editing: bool,
}
impl Directory {
pub fn new(root: String) -> Result<Self, Error> {
pub fn new(root: String, height: u16) -> Result<Self, Error> {
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<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 {
&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<Item = String> + '_ {
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()
+ &current.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);

View File

@ -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<Directory, Error> {
pub(super) fn create_new(root: String, height: u16) -> Result<Directory, Error> {
DirBuilder::new()
.recursive(true)
.create(&root)?;
@ -11,12 +11,14 @@ pub(super) fn create_new(root: String) -> Result<Directory, Error> {
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<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)];
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 {
dirs,
src_name: None,
display_start: 0,
height,
selection: 0,
editing: false,
})

View File

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

View File

@ -23,7 +23,8 @@ pub struct State {
}
impl State {
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 {
dir,
size: area,