commands work
parent
e1ef2d9a18
commit
4f9b52151c
|
@ -0,0 +1,44 @@
|
||||||
|
use tui::widgets::{Block, Borders, Paragraph};
|
||||||
|
|
||||||
|
use crate::pane::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct Command {
|
||||||
|
buf: String
|
||||||
|
}
|
||||||
|
impl Command {
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self { buf: String::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn buf(&self) -> &str { &self.buf }
|
||||||
|
}
|
||||||
|
impl Pane for Command {
|
||||||
|
fn cursor_position(&self) -> Option<(u16, u16)> {
|
||||||
|
let x = u16::try_from(self.buf.len())
|
||||||
|
.expect("too big to display");
|
||||||
|
let y = 0;
|
||||||
|
Some((x, y))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lines_hint(&self) -> usize {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display(&self, output: OutputSink, area: Rect) {
|
||||||
|
let widget = Paragraph::new(self.buf.as_str())
|
||||||
|
.block(Block::default().title("Command").borders(Borders::all()));
|
||||||
|
|
||||||
|
output.render_widget(widget, area);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, key: KeyCode) -> Message {
|
||||||
|
match key {
|
||||||
|
KeyCode::Char(c) => { self.buf.push(c); Message::Nothing },
|
||||||
|
KeyCode::Backspace => { self.buf.pop(); Message::Nothing },
|
||||||
|
KeyCode::Esc => Message::CancelCommand,
|
||||||
|
KeyCode::Enter => Message::ExecuteCommand,
|
||||||
|
_ => Message::Nothing,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
use std::io::Error;
|
||||||
|
|
||||||
|
use tui::widgets::{Block, Borders, Paragraph};
|
||||||
|
|
||||||
|
use crate::pane::prelude::*;
|
||||||
|
|
||||||
|
pub struct CommandOutput {
|
||||||
|
contents: Result<Option<String>, Error>,
|
||||||
|
}
|
||||||
|
impl CommandOutput {
|
||||||
|
pub fn new(src: Result<Vec<u8>, Error>) -> Self {
|
||||||
|
Self {
|
||||||
|
contents: src.map(|s| String::from_utf8(s).ok())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn should_display(&self) -> bool {
|
||||||
|
match &self.contents {
|
||||||
|
Ok(Some(s)) => ! s.is_empty(),
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Pane for CommandOutput {
|
||||||
|
fn cursor_position(&self) -> Option<(u16, u16)> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lines_hint(&self) -> usize {
|
||||||
|
match self.contents.as_ref() {
|
||||||
|
Ok(Some(s)) => s.lines().count(),
|
||||||
|
Ok(None) => 0,
|
||||||
|
Err(_) => 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display(&self, output: OutputSink, area: Rect) {
|
||||||
|
let widget = match self.contents.as_ref() {
|
||||||
|
Ok(None) => Paragraph::new("<BINARY>"),
|
||||||
|
Ok(Some(s)) => Paragraph::new(s.as_str()),
|
||||||
|
Err(e) => Paragraph::new(e.to_string()),
|
||||||
|
}.block(Block::default().title("Output").borders(Borders::all()));
|
||||||
|
|
||||||
|
output.render_widget(widget, area);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, _: KeyCode) -> Message {
|
||||||
|
unimplemented!("Output does not receive input")
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,11 @@
|
||||||
mod display;
|
mod display;
|
||||||
mod item;
|
mod item;
|
||||||
|
|
||||||
use crossterm::event::KeyCode;
|
use tui::widgets::{Block, Borders, List, ListItem};
|
||||||
use tui::{layout::Rect, style::{Color, Style}, widgets::{Block, Borders, List, ListItem}};
|
use tui::style::{Color, Style};
|
||||||
|
use tui::layout::Rect;
|
||||||
|
|
||||||
use crate::pane::{Message, OutputSink, Pane};
|
use crate::pane::prelude::*;
|
||||||
|
|
||||||
use display::TreePart;
|
use display::TreePart;
|
||||||
use item::Item;
|
use item::Item;
|
||||||
|
@ -125,9 +126,8 @@ 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())
|
||||||
.expect("name too long to display")
|
.expect("name too long to display");
|
||||||
+ 1;
|
let y = self.selection;
|
||||||
let y = self.selection + 1;
|
|
||||||
Some((x, y))
|
Some((x, y))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
17
src/main.rs
17
src/main.rs
|
@ -1,11 +1,13 @@
|
||||||
|
mod command;
|
||||||
|
mod command_output;
|
||||||
mod directory;
|
mod directory;
|
||||||
mod pane;
|
mod pane;
|
||||||
mod terminal;
|
|
||||||
mod state;
|
mod state;
|
||||||
|
mod terminal;
|
||||||
|
|
||||||
use pane::Message;
|
|
||||||
use terminal::Terminal;
|
|
||||||
use state::State;
|
use state::State;
|
||||||
|
use terminal::Terminal;
|
||||||
|
use pane::prelude::*;
|
||||||
|
|
||||||
use std::io::Error;
|
use std::io::Error;
|
||||||
|
|
||||||
|
@ -15,10 +17,10 @@ fn main() -> Result<(), Error> {
|
||||||
let mut terminal = Terminal::new()?;
|
let mut terminal = Terminal::new()?;
|
||||||
let area = terminal.size()?;
|
let area = terminal.size()?;
|
||||||
|
|
||||||
let mut state = State::new();
|
let mut state = State::new(area);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
state.render(&mut terminal, area)?;
|
state.render(&mut terminal)?;
|
||||||
|
|
||||||
if let Some((x, y)) = state.cursor_position() {
|
if let Some((x, y)) = state.cursor_position() {
|
||||||
terminal.show_cursor()?;
|
terminal.show_cursor()?;
|
||||||
|
@ -33,8 +35,9 @@ fn main() -> Result<(), Error> {
|
||||||
match state.update(key) {
|
match state.update(key) {
|
||||||
Message::Nothing => (),
|
Message::Nothing => (),
|
||||||
Message::Exit => break,
|
Message::Exit => break,
|
||||||
Message::BeginCommand => todo!("begin command"),
|
Message::BeginCommand => state.begin_command(),
|
||||||
Message::ExecuteCommand => todo!("execute command"),
|
Message::ExecuteCommand => state.execute_command(),
|
||||||
|
Message::CancelCommand => state.cancel_command(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
14
src/pane.rs
14
src/pane.rs
|
@ -1,7 +1,8 @@
|
||||||
use std::io::Stdout;
|
use std::io::Stdout;
|
||||||
|
|
||||||
use crossterm::event::KeyCode;
|
use crossterm::event::KeyCode;
|
||||||
use tui::{backend::CrosstermBackend, layout::Rect, Frame};
|
use tui::{layout::Rect, Frame};
|
||||||
|
use tui::backend::CrosstermBackend;
|
||||||
|
|
||||||
pub trait Pane {
|
pub trait Pane {
|
||||||
fn cursor_position(&self) -> Option<(u16, u16)>;
|
fn cursor_position(&self) -> Option<(u16, u16)>;
|
||||||
|
@ -20,5 +21,16 @@ pub enum Message {
|
||||||
Nothing,
|
Nothing,
|
||||||
Exit,
|
Exit,
|
||||||
BeginCommand,
|
BeginCommand,
|
||||||
|
CancelCommand,
|
||||||
ExecuteCommand,
|
ExecuteCommand,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod prelude {
|
||||||
|
pub use super::{
|
||||||
|
OutputSink,
|
||||||
|
Message,
|
||||||
|
Pane,
|
||||||
|
};
|
||||||
|
pub use tui::layout::Rect;
|
||||||
|
pub use crossterm::event::KeyCode;
|
||||||
|
}
|
123
src/state.rs
123
src/state.rs
|
@ -1,40 +1,137 @@
|
||||||
|
use std::hint::unreachable_unchecked;
|
||||||
use std::io::Error;
|
use std::io::Error;
|
||||||
|
use std::process::Command as Executable;
|
||||||
|
|
||||||
use crossterm::event::KeyCode;
|
use crossterm::event::KeyCode;
|
||||||
use tui::layout::Rect;
|
use tui::layout::Rect;
|
||||||
|
|
||||||
use crate::pane::{Message, Pane};
|
use crate::command::Command;
|
||||||
|
use crate::command_output::CommandOutput;
|
||||||
use crate::directory::Directory;
|
use crate::directory::Directory;
|
||||||
|
use crate::pane::{Message, Pane};
|
||||||
use crate::terminal::Terminal;
|
use crate::terminal::Terminal;
|
||||||
|
|
||||||
|
enum Either<T, U> {
|
||||||
|
Cmd(T),
|
||||||
|
CmdOut(U),
|
||||||
|
}
|
||||||
|
|
||||||
pub struct State {
|
pub struct State {
|
||||||
panes: Vec<Box<dyn Pane>>,
|
dir: Directory,
|
||||||
selected: usize,
|
size: Rect,
|
||||||
|
command: Option<Either<Command, CommandOutput>>
|
||||||
}
|
}
|
||||||
impl State {
|
impl State {
|
||||||
pub fn new() -> Self {
|
pub fn new(area: Rect) -> Self {
|
||||||
|
let dir = Directory::new("root".into());
|
||||||
Self {
|
Self {
|
||||||
panes: vec![ Box::new( Directory::new("root".into()) ) ],
|
dir,
|
||||||
selected: 0,
|
size: area,
|
||||||
|
command: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(&self, terminal: &mut Terminal, area: Rect) -> Result<(), Error> {
|
pub fn render(&self, terminal: &mut Terminal) -> Result<(), Error> {
|
||||||
|
let command_size = match &self.command {
|
||||||
|
None => 0,
|
||||||
|
Some(e) => match e {
|
||||||
|
Either::Cmd(c) => c.lines_hint(),
|
||||||
|
Either::CmdOut(o) => o.lines_hint(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let (dir_area, bottom_area) = if command_size > 0 {
|
||||||
|
(Rect {
|
||||||
|
height: self.size.height - command_size as u16 - 2,
|
||||||
|
..self.size
|
||||||
|
}, Rect {
|
||||||
|
y: self.size.height - command_size as u16 - 2,
|
||||||
|
height: command_size as u16 + 2,
|
||||||
|
..self.size
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
(self.size, self.size)
|
||||||
|
};
|
||||||
|
|
||||||
terminal.draw(
|
terminal.draw(
|
||||||
|output|
|
|output| {
|
||||||
self.panes
|
self.dir.display(output, dir_area);
|
||||||
.iter()
|
if let Some(command) = self.command.as_ref() {
|
||||||
.for_each(|p| p.display(output, area))
|
match command {
|
||||||
|
Either::Cmd(cmd) => cmd.display(output, bottom_area),
|
||||||
|
Either::CmdOut(cmd) => cmd.display(output, bottom_area),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fn current_pane(&self) -> &dyn Pane {
|
||||||
|
match self.command.as_ref() {
|
||||||
|
None => &self.dir,
|
||||||
|
Some(either) => match either {
|
||||||
|
Either::Cmd(c) => c,
|
||||||
|
Either::CmdOut(o) => o,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn cursor_position(&self) -> Option<(u16, u16)> {
|
pub fn cursor_position(&self) -> Option<(u16, u16)> {
|
||||||
self.panes[self.selected].cursor_position()
|
let pane = self.current_pane();
|
||||||
|
|
||||||
|
let (x, y) = pane.cursor_position()?;
|
||||||
|
let new_x = x + 1;
|
||||||
|
let dy = if let Some(Either::Cmd(c)) = &self.command {
|
||||||
|
self.size.height - c.lines_hint() as u16 - 2
|
||||||
|
} else {
|
||||||
|
self.size.y
|
||||||
|
};
|
||||||
|
let new_y = y + dy + 1;
|
||||||
|
|
||||||
|
Some((new_x, new_y))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, key: KeyCode) -> Message {
|
pub fn update(&mut self, key: KeyCode) -> Message {
|
||||||
self.panes[self.selected].update(key)
|
match self.command.as_mut() {
|
||||||
|
None => self.dir.update(key),
|
||||||
|
Some(either) => match either {
|
||||||
|
Either::Cmd(c) => c.update(key),
|
||||||
|
Either::CmdOut(_) => { self.close_window(); Message::Nothing },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn begin_command(&mut self) {
|
||||||
|
self.command = Some(Either::Cmd(Command::new()));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn execute_command(&mut self) {
|
||||||
|
let Some(Either::Cmd(cmd)) = self.command.take()
|
||||||
|
else { unsafe { unreachable_unchecked() } };
|
||||||
|
|
||||||
|
let mut iter = cmd.buf()
|
||||||
|
.split(' ');
|
||||||
|
|
||||||
|
let prog_name = iter.next().unwrap();
|
||||||
|
let output = Executable::new(prog_name)
|
||||||
|
.args(iter)
|
||||||
|
.output();
|
||||||
|
|
||||||
|
let command_output = CommandOutput::new(output.map(|o| o.stdout));
|
||||||
|
if !command_output.should_display() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.command = Some(Either::CmdOut(command_output));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cancel_command(&mut self) {
|
||||||
|
self.command = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn close_window(&mut self) {
|
||||||
|
self.command = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue