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)]
enum Mode {
#[default]
Default,
ChangingName,
GettingCommand,
}
use crate::pane::Pane;
use display::TreePart;
use item::Item;
#[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)]
pub struct Directory {
name: String,
depth: u8,
expanded: bool,
is_file: bool,
dirs: Vec<Item>,
selection: u16,
editing: bool,
}
impl State {
impl Directory {
pub fn new(root: String) -> Self {
Self {
dirs: vec![Directory::new(root, 0, false)],
dirs: vec![Item::new(root, 0, false)],
selection: 0,
mode: Mode::Default,
cmd_buf: String::new(),
cmd_out: String::new(),
editing: false,
}
}
@ -57,16 +44,110 @@ impl State {
current_depth + 1
};
let new = Directory::new(name, depth, is_file);
let index = self.selection()+1;
self.dirs.insert(index, new);
let new = Item::new(name, depth, is_file);
self.dirs.insert(self.selection()+1, 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 Some((_, init)) = iter.by_ref()
.next()
else { return (String::new(), "") };
else { return String::new() };
let mut indent_parts = Vec::new();
let mut result = init.name.clone() + "/\n";
@ -111,140 +192,12 @@ impl State {
}
result.push('\n');
}
(result, &self.cmd_out)
}
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)
result
}
}
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 {
if d.depth < target_depth { return true }
@ -252,38 +205,4 @@ fn is_last_at_this_depth(slice: &[Directory], target_depth: u8) -> bool {
}
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 pane;
mod directory;
mod pane;
mod state;
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::ops::{Deref, DerefMut};
use tui::backend::CrosstermBackend;
use tui::layout::Rect;
use tui::style::{Color, Style};
use tui::widgets::{Block, Borders, List, ListItem, Paragraph};
use tui::widgets::{Block, Borders, List, ListItem};
use tui::Terminal;
use crossterm::{
@ -22,16 +22,13 @@ fn main() -> Result<(), Error> {
let mut terminal = MyTerminal::enter()?;
let size = terminal.size()?;
let mut dir = State::new("root".into());
let mut dir = Directory::new("root".into());
loop {
let (diagramme, bottom_buf) = dir.display();
let bottom_height = u16::try_from(bottom_buf.lines().count())
.expect("too many lines to display");
let diagramme = dir.display();
let rows = diagramme
.lines()
.take((size.height - bottom_height) as usize)
.enumerate()
.map(|(i, s)| {
let row = ListItem::new(s);
@ -46,29 +43,24 @@ fn main() -> Result<(), Error> {
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(
|f| {
f.render_widget(widget, top_pane);
f.render_widget(bottom_widget, bottom_pane);
f.render_widget(widget, size);
}
)?;
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 }
}

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,
}