half a step forward for organisation
parent
fd44a36d73
commit
e1ef2d9a18
|
@ -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()))
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
114
src/main.rs
114
src/main.rs
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
14
src/pane.rs
14
src/pane.rs
|
@ -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,
|
||||||
}
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue