half a step forward for organisation

main
nick 2024-10-02 12:28:14 -04:00
parent fd44a36d73
commit e1ef2d9a18
6 changed files with 169 additions and 116 deletions

View File

@ -2,8 +2,9 @@ mod display;
mod item; mod item;
use crossterm::event::KeyCode; use crossterm::event::KeyCode;
use tui::{layout::Rect, style::{Color, Style}, widgets::{Block, Borders, List, ListItem}};
use crate::pane::Pane; use crate::pane::{Message, OutputSink, Pane};
use display::TreePart; use display::TreePart;
use item::Item; use item::Item;
@ -61,9 +62,7 @@ impl Directory {
self.selection as usize self.selection as usize
} }
pub fn is_editing(&self) -> bool { self.editing } pub fn update_navigation(&mut self, key: KeyCode) -> Message {
pub fn update_navigation(&mut self, key: KeyCode) -> bool {
match key { match key {
KeyCode::Char(c) => { KeyCode::Char(c) => {
match c { match c {
@ -77,15 +76,18 @@ impl Directory {
self.select_next(); self.select_next();
self.editing = true; self.editing = true;
}, },
'x' | 'X' => {
return Message::BeginCommand;
}
_ => (), _ => (),
}; };
}, },
KeyCode::Up => self.select_prev(), KeyCode::Up => self.select_prev(),
KeyCode::Down => self.select_next(), KeyCode::Down => self.select_next(),
KeyCode::Enter => { KeyCode::Enter => {
self.editing = true self.editing = true;
}, },
KeyCode::Esc => return false, KeyCode::Esc => return Message::Exit,
KeyCode::Backspace => { KeyCode::Backspace => {
if self.selected().is_file { if self.selected().is_file {
self.dirs.remove(self.selection()); self.dirs.remove(self.selection());
@ -97,10 +99,10 @@ impl Directory {
_ => (), _ => (),
} }
true Message::Nothing
} }
pub fn update_edit(&mut self, key: KeyCode) -> bool { pub fn update_edit(&mut self, key: KeyCode) -> Message {
match key { match key {
KeyCode::Char(c) => self.selected_mut().name.push(c), KeyCode::Char(c) => self.selected_mut().name.push(c),
KeyCode::Enter | KeyCode::Esc if !self.selected().name.is_empty() => { KeyCode::Enter | KeyCode::Esc if !self.selected().name.is_empty() => {
@ -110,14 +112,12 @@ impl Directory {
_ => (), _ => (),
} }
true Message::Nothing
} }
} }
impl Pane for Directory { impl Pane for Directory {
const NAME: &'static str = "Dirbuilder";
fn cursor_position(&self) -> Option<(u16, u16)> { fn cursor_position(&self) -> Option<(u16, u16)> {
if !self.editing { if !self.editing {
return None; return None;
@ -135,7 +135,7 @@ impl Pane for Directory {
self.dirs.len() self.dirs.len()
} }
fn update(&mut self, key: KeyCode) -> bool { fn update(&mut self, key: KeyCode) -> Message {
if self.editing { if self.editing {
self.update_edit(key) self.update_edit(key)
} else { } else {
@ -143,11 +143,11 @@ impl Pane for Directory {
} }
} }
fn display(&self) -> String { fn display(&self, output: OutputSink, area: Rect) {
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 };
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";
@ -193,7 +193,24 @@ impl Pane for Directory {
result.push('\n'); result.push('\n');
} }
result let rows = result
.lines()
.enumerate()
.map(|(i, s)| {
let row = ListItem::new(s);
if i == self.selection() {
let color = if self.editing { Color::Rgb(197, 110, 31) } else { Color::White };
row.style(Style::default().fg(Color::Black).bg(color))
} else {
row
}
})
.collect();
let list = into_list(rows);
output.render_widget(list, area);
} }
} }
@ -206,3 +223,11 @@ fn is_last_at_this_depth(slice: &[Item], target_depth: u8) -> bool {
true true
} }
fn into_list(rows: Vec<ListItem<'_>>) -> List<'_> {
List::new(rows)
// You can set the style of the entire Table.
.style(Style::default().fg(Color::White))
// As any other widget, a Table can be wrapped in a Block.
.block(Block::default().title("Dirbuilder").borders(Borders::all()))
}

View File

@ -1,10 +1,10 @@
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Item { pub struct Item {
pub(crate) name: String, pub(super) name: String,
pub(crate) depth: u8, pub(super) depth: u8,
pub(crate) expanded: bool, pub(super) expanded: bool,
pub(crate) is_file: bool, pub(super) is_file: bool,
} }
impl Item { impl Item {

View File

@ -1,114 +1,42 @@
mod directory; mod directory;
mod pane; mod pane;
mod terminal;
mod state;
use pane::Message;
use terminal::Terminal;
use state::State;
use std::io::Error;
use crossterm::cursor::Show;
use crossterm::event::{read, Event, KeyEvent}; 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::style::{Color, Style};
use tui::widgets::{Block, Borders, List, ListItem};
use tui::Terminal;
use crossterm::{
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
fn main() -> Result<(), Error> { fn main() -> Result<(), Error> {
let mut terminal = MyTerminal::enter()?; let mut terminal = Terminal::new()?;
let size = terminal.size()?; let area = terminal.size()?;
let mut dir = Directory::new("root".into()); let mut state = State::new();
loop { loop {
let diagramme = dir.display(); state.render(&mut terminal, area)?;
let rows = diagramme if let Some((x, y)) = state.cursor_position() {
.lines()
.enumerate()
.map(|(i, s)| {
let row = ListItem::new(s);
if i == dir.selection() {
row.style(Style::default().fg(Color::Black).bg(if dir.is_editing() { Color::Rgb(197, 110, 31) } else { Color::White }))
} else {
row
}
})
.collect();
let widget = table_vector(rows);
terminal.draw(
|f| {
f.render_widget(widget, size);
}
)?;
if let Some((x, y)) = dir.cursor_position() {
terminal.show_cursor()?; terminal.show_cursor()?;
terminal.set_cursor(x, y)?; terminal.set_cursor(x, y)?;
} else { } else {
terminal.hide_cursor()?; terminal.hide_cursor()?;
} }
let event = read()?; let Event::Key(KeyEvent { code: key, .. }) = read()?
else { continue };
let Event::Key(KeyEvent { code: key, .. }) = event else { continue }; match state.update(key) {
Message::Nothing => (),
let keep_going = dir.update(key); Message::Exit => break,
if !keep_going { break } Message::BeginCommand => todo!("begin command"),
Message::ExecuteCommand => todo!("execute command"),
}
} }
Ok(()) Ok(())
} }
fn table_vector(rows: Vec<ListItem<'_>>) -> List<'_> {
List::new(rows)
// You can set the style of the entire Table.
.style(Style::default().fg(Color::White))
// As any other widget, a Table can be wrapped in a Block.
.block(Block::default().title("Dirbuilder").borders(Borders::all()))
}
struct MyTerminal(Terminal<CrosstermBackend<io::Stdout>>);
impl MyTerminal {
/// Create and initialise a `Terminal`
fn enter() -> Result<Self, io::Error> {
enable_raw_mode()?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen, Show)?;
let backend = CrosstermBackend::new(stdout);
let terminal = Terminal::new(backend)?;
Ok(Self(terminal))
}
}
impl Drop for MyTerminal {
fn drop(&mut self) {
let _ = disable_raw_mode();
let _ = execute!(
self.0.backend_mut(),
LeaveAlternateScreen
);
let _ = self.0.show_cursor();
}
}
impl Deref for MyTerminal {
type Target = Terminal<CrosstermBackend<io::Stdout>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for MyTerminal {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

View File

@ -1,18 +1,24 @@
use std::io::Stdout;
use crossterm::event::KeyCode; use crossterm::event::KeyCode;
use tui::{backend::CrosstermBackend, layout::Rect, Frame};
pub trait Pane { pub trait Pane {
const NAME: &'static str;
fn cursor_position(&self) -> Option<(u16, u16)>; fn cursor_position(&self) -> Option<(u16, u16)>;
fn lines_hint(&self) -> usize; fn lines_hint(&self) -> usize;
fn display(&self) -> String; fn display(&self, output: OutputSink, area: Rect);
fn update(&mut self, key: KeyCode) -> bool; fn update(&mut self, key: KeyCode) -> Message;
} }
pub type OutputSink<'a, 'b> = &'a mut Frame<'b, CrosstermBackend<Stdout>>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Message { pub enum Message {
Nothing,
Exit,
BeginCommand, BeginCommand,
ExecuteCommand, ExecuteCommand,
} }

40
src/state.rs Normal file
View File

@ -0,0 +1,40 @@
use std::io::Error;
use crossterm::event::KeyCode;
use tui::layout::Rect;
use crate::pane::{Message, Pane};
use crate::directory::Directory;
use crate::terminal::Terminal;
pub struct State {
panes: Vec<Box<dyn Pane>>,
selected: usize,
}
impl State {
pub fn new() -> Self {
Self {
panes: vec![ Box::new( Directory::new("root".into()) ) ],
selected: 0,
}
}
pub fn render(&self, terminal: &mut Terminal, area: Rect) -> Result<(), Error> {
terminal.draw(
|output|
self.panes
.iter()
.for_each(|p| p.display(output, area))
)?;
Ok(())
}
pub fn cursor_position(&self) -> Option<(u16, u16)> {
self.panes[self.selected].cursor_position()
}
pub fn update(&mut self, key: KeyCode) -> Message {
self.panes[self.selected].update(key)
}
}

54
src/terminal.rs Normal file
View File

@ -0,0 +1,54 @@
use std::{
io::{stdout, Error, Stdout},
ops::{Deref, DerefMut},
};
use crossterm::{
cursor::Show,
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}
};
use tui::{backend::CrosstermBackend, Terminal as TuiTerminal};
pub struct Terminal(TuiTerminal<CrosstermBackend<Stdout>>);
impl Terminal {
/// Create and initialise a `Terminal`
pub fn new() -> Result<Self, Error> {
enable_raw_mode()?;
let mut stdout = stdout();
execute!(stdout, EnterAlternateScreen, Show)?;
let backend = CrosstermBackend::new(stdout);
let terminal = TuiTerminal::new(backend)?;
Ok(Self(terminal))
}
}
// this is Drop::drop and not, say, an exit function
// so that if it panics or something, we can be decently
// confident that we returned to normal mode.
impl Drop for Terminal {
fn drop(&mut self) {
// have to ignore all these errors because
// Drop can't do anything useful with them
let _ = execute!(
self.0.backend_mut(),
LeaveAlternateScreen
);
let _ = disable_raw_mode();
let _ = self.0.show_cursor();
}
}
impl Deref for Terminal {
type Target = TuiTerminal<CrosstermBackend<Stdout>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for Terminal {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}