aided display with large directory structures, half implemented moving files visually
parent
a0d1e3f835
commit
d4a28340d8
174
src/directory.rs
174
src/directory.rs
|
@ -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()
|
||||||
+ ¤t.name
|
+ ¤t.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);
|
||||||
|
|
|
@ -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,
|
||||||
})
|
})
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue