one step backward for reorganisation

main
nick 2024-10-02 10:18:18 -04:00
parent 0ee44327f3
commit fd44a36d73
5 changed files with 193 additions and 223 deletions

View File

@ -1,38 +1,25 @@
use std::{io::Error, process::Command}; mod display;
mod item;
use crossterm::event::{read, Event, KeyCode, KeyEvent}; use crossterm::event::KeyCode;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] use crate::pane::Pane;
enum Mode {
#[default] use display::TreePart;
Default, use item::Item;
ChangingName,
GettingCommand,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct State {
dirs: Vec<Directory>,
selection: u16,
mode: Mode,
cmd_buf: String,
cmd_out: String,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Directory { pub struct Directory {
name: String, dirs: Vec<Item>,
depth: u8, selection: u16,
expanded: bool, editing: bool,
is_file: bool,
} }
impl State { impl Directory {
pub fn new(root: String) -> Self { pub fn new(root: String) -> Self {
Self { Self {
dirs: vec![Directory::new(root, 0, false)], dirs: vec![Item::new(root, 0, false)],
selection: 0, selection: 0,
mode: Mode::Default, editing: false,
cmd_buf: String::new(),
cmd_out: String::new(),
} }
} }
@ -57,16 +44,110 @@ impl State {
current_depth + 1 current_depth + 1
}; };
let new = Directory::new(name, depth, is_file); let new = Item::new(name, depth, is_file);
let index = self.selection()+1; self.dirs.insert(self.selection()+1, new);
self.dirs.insert(index, new);
} }
pub fn display(&self) -> (String, &str) { fn selected(&self) -> &Item {
&self.dirs[self.selection()]
}
fn selected_mut(&mut self) -> &mut Item {
let idx = self.selection();
&mut self.dirs[idx]
}
pub const fn selection(&self) -> usize {
self.selection as usize
}
pub fn is_editing(&self) -> bool { self.editing }
pub fn update_navigation(&mut self, key: KeyCode) -> bool {
match key {
KeyCode::Char(c) => {
match c {
'f' | 'F' => {
self.insert(String::new(), true);
self.select_next();
self.editing = true;
},
'd' | 'D' => {
self.insert(String::new(), false);
self.select_next();
self.editing = true;
},
_ => (),
};
},
KeyCode::Up => self.select_prev(),
KeyCode::Down => self.select_next(),
KeyCode::Enter => {
self.editing = true
},
KeyCode::Esc => return false,
KeyCode::Backspace => {
if self.selected().is_file {
self.dirs.remove(self.selection());
if self.selection() == self.dirs.len() {
self.selection -= 1;
}
}
}
_ => (),
}
true
}
pub fn update_edit(&mut self, key: KeyCode) -> bool {
match key {
KeyCode::Char(c) => self.selected_mut().name.push(c),
KeyCode::Enter | KeyCode::Esc if !self.selected().name.is_empty() => {
self.editing = false;
},
KeyCode::Backspace => { self.selected_mut().name.pop(); },
_ => (),
}
true
}
}
impl Pane for Directory {
const NAME: &'static str = "Dirbuilder";
fn cursor_position(&self) -> Option<(u16, u16)> {
if !self.editing {
return None;
}
let x = u16::from(self.selected().depth) * 4
+ u16::try_from(self.selected().name.len())
.expect("name too long to display")
+ 1;
let y = self.selection + 1;
Some((x, y))
}
fn lines_hint(&self) -> usize {
self.dirs.len()
}
fn update(&mut self, key: KeyCode) -> bool {
if self.editing {
self.update_edit(key)
} else {
self.update_navigation(key)
}
}
fn display(&self) -> String {
let mut iter = self.dirs.iter().enumerate(); let mut iter = self.dirs.iter().enumerate();
let Some((_, init)) = iter.by_ref() let Some((_, init)) = iter.by_ref()
.next() .next()
else { return (String::new(), "") }; else { return String::new() };
let mut indent_parts = Vec::new(); let mut indent_parts = Vec::new();
let mut result = init.name.clone() + "/\n"; let mut result = init.name.clone() + "/\n";
@ -112,139 +193,11 @@ impl State {
result.push('\n'); result.push('\n');
} }
(result, &self.cmd_out) result
}
fn selected(&self) -> &Directory {
&self.dirs[self.selection()]
}
fn selected_mut(&mut self) -> &mut Directory {
let idx = self.selection();
&mut self.dirs[idx]
}
pub const fn selection(&self) -> usize {
self.selection as usize
}
pub const fn is_editing(&self) -> bool {
matches!(self.mode, Mode::ChangingName)
}
pub fn set_cursor(&self, terminal: &mut super::MyTerminal) -> Result<(), Error> {
match self.mode {
Mode::Default => terminal.hide_cursor(),
Mode::ChangingName => {
terminal.show_cursor()?;
let x =
u16::from(self.selected().depth) * 4
+ u16::try_from(self.selected().name.len())
.expect("name too long to display")
+ 1;
let y = self.selection + 1;
terminal.set_cursor(x, y)
}
Mode::GettingCommand => {
terminal.show_cursor()?;
terminal.set_cursor(0, 0)
}
}
}
pub fn update(&mut self) -> Result<bool, Error> {
let Event::Key(KeyEvent { code, .. }) = read()? else {
return Ok(true);
};
match &mut self.mode {
Mode::Default => Ok(self.main_event(code)),
Mode::ChangingName => Ok(self.update_buffer(code)),
Mode::GettingCommand => self.update_command(code),
}
}
pub fn main_event(&mut self, key: KeyCode) -> bool {
match key {
KeyCode::Char(c) => {
match c {
'f' | 'F' => {
self.insert(String::new(), true);
self.select_next();
self.mode = Mode::ChangingName;
},
'd' | 'D' => {
self.insert(String::new(), false);
self.select_next();
self.mode = Mode::ChangingName;
},
'x' | 'X' => {
self.mode = Mode::GettingCommand;
}
_ => (),
};
},
KeyCode::Up => self.select_prev(),
KeyCode::Down => self.select_next(),
KeyCode::Enter => {
self.mode = Mode::ChangingName;
},
KeyCode::Esc => return false,
KeyCode::Backspace => {
if self.selected().is_file {
let index = self.selection();
self.dirs.remove(index);
if self.selection() == self.dirs.len() {
self.selection -= 1;
}
}
}
_ => (),
}
true
}
fn update_buffer(&mut self, key: KeyCode) -> bool {
match key {
KeyCode::Char(c) => self.selected_mut().name.push(c),
KeyCode::Enter | KeyCode::Esc if !self.selected().name.is_empty() => {
self.mode = Mode::Default;
},
KeyCode::Backspace => { self.selected_mut().name.pop(); },
_ => (),
}
true
}
fn update_command(&mut self, key: KeyCode) -> Result<bool, Error> {
match key {
KeyCode::Char(c) => self.cmd_buf.push(c),
KeyCode::Backspace => { self.cmd_buf.pop(); },
KeyCode::Enter => {
let mut args = self.cmd_buf.split(' ');
let Some(program) = args.next()
else { return Ok(true) };
let out = Command::new(program)
.args(args)
.arg(self.selected().name.as_str())
.output()?
.stdout;
self.cmd_out = unsafe { String::from_utf8_unchecked(out) };
self.mode = Mode::Default;
}
_ => (),
}
Ok(true)
} }
} }
fn is_last_at_this_depth(slice: &[Directory], target_depth: u8) -> bool { fn is_last_at_this_depth(slice: &[Item], target_depth: u8) -> bool {
for d in slice { for d in slice {
if d.depth < target_depth { return true } if d.depth < target_depth { return true }
@ -253,37 +206,3 @@ fn is_last_at_this_depth(slice: &[Directory], target_depth: u8) -> bool {
true true
} }
impl Directory {
pub const fn new(name: String, depth: u8, is_file: bool) -> Self {
Self {
name,
depth,
expanded: true,
is_file,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum TreePart {
/// `├──`
First,
/// `│ `
Wait,
/// `└──`
Last,
/// (blank)
Blank
}
impl TreePart {
/// convert to ascii art
pub const fn display(self) -> &'static str {
match self {
Self::First => "├── ",
Self::Wait => "",
Self::Last => "└── ",
Self::Blank => " ",
}
}
}

22
src/directory/display.rs Normal file
View File

@ -0,0 +1,22 @@
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum TreePart {
/// `├──`
First,
/// `│ `
Wait,
/// `└──`
Last,
/// (blank)
Blank
}
impl TreePart {
/// convert to ascii art
pub const fn display(self) -> &'static str {
match self {
Self::First => "├── ",
Self::Wait => "",
Self::Last => "└── ",
Self::Blank => " ",
}
}
}

19
src/directory/item.rs Normal file
View File

@ -0,0 +1,19 @@
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Item {
pub(crate) name: String,
pub(crate) depth: u8,
pub(crate) expanded: bool,
pub(crate) is_file: bool,
}
impl Item {
pub const fn new(name: String, depth: u8, is_file: bool) -> Self {
Self {
name,
depth,
expanded: true,
is_file,
}
}
}

View File

@ -1,16 +1,16 @@
// mod directory; mod directory;
// mod pane; mod pane;
mod state;
use crossterm::cursor::Show; use crossterm::cursor::Show;
use state::State; use crossterm::event::{read, Event, KeyEvent};
use pane::Pane;
use directory::Directory;
use std::io::{self, Error}; use std::io::{self, Error};
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use tui::backend::CrosstermBackend; use tui::backend::CrosstermBackend;
use tui::layout::Rect;
use tui::style::{Color, Style}; use tui::style::{Color, Style};
use tui::widgets::{Block, Borders, List, ListItem, Paragraph}; use tui::widgets::{Block, Borders, List, ListItem};
use tui::Terminal; use tui::Terminal;
use crossterm::{ use crossterm::{
@ -22,16 +22,13 @@ fn main() -> Result<(), Error> {
let mut terminal = MyTerminal::enter()?; let mut terminal = MyTerminal::enter()?;
let size = terminal.size()?; let size = terminal.size()?;
let mut dir = State::new("root".into()); let mut dir = Directory::new("root".into());
loop { loop {
let (diagramme, bottom_buf) = dir.display(); let diagramme = dir.display();
let bottom_height = u16::try_from(bottom_buf.lines().count())
.expect("too many lines to display");
let rows = diagramme let rows = diagramme
.lines() .lines()
.take((size.height - bottom_height) as usize)
.enumerate() .enumerate()
.map(|(i, s)| { .map(|(i, s)| {
let row = ListItem::new(s); let row = ListItem::new(s);
@ -46,29 +43,24 @@ fn main() -> Result<(), Error> {
let widget = table_vector(rows); let widget = table_vector(rows);
let top_pane = Rect {
height: size.height - bottom_height-2,
..size
};
let bottom_pane = Rect {
y: top_pane.y + top_pane.height,
height: bottom_height+2,
..top_pane
};
let bottom_widget = Paragraph::new(bottom_buf)
.block(Block::default().title("Output").borders(Borders::all()));
terminal.draw( terminal.draw(
|f| { |f| {
f.render_widget(widget, top_pane); f.render_widget(widget, size);
f.render_widget(bottom_widget, bottom_pane);
} }
)?; )?;
dir.set_cursor(&mut terminal)?; if let Some((x, y)) = dir.cursor_position() {
terminal.show_cursor()?;
terminal.set_cursor(x, y)?;
} else {
terminal.hide_cursor()?;
}
let keep_going = dir.update()?; let event = read()?;
let Event::Key(KeyEvent { code: key, .. }) = event else { continue };
let keep_going = dir.update(key);
if !keep_going { break } if !keep_going { break }
} }

18
src/pane.rs Normal file
View File

@ -0,0 +1,18 @@
use crossterm::event::KeyCode;
pub trait Pane {
const NAME: &'static str;
fn cursor_position(&self) -> Option<(u16, u16)>;
fn lines_hint(&self) -> usize;
fn display(&self) -> String;
fn update(&mut self, key: KeyCode) -> bool;
}
pub enum Message {
BeginCommand,
ExecuteCommand,
}