use std::hint::unreachable_unchecked; use std::io::Error; use std::process::Command as Executable; use crossterm::event::KeyCode; use tui::layout::Rect; use crate::command::Command; use crate::command_output::CommandOutput; use crate::directory::Directory; use crate::pane::{Message, Pane}; use crate::terminal::Terminal; enum Either { Cmd(T), CmdOut(U), } pub struct State { dir: Directory, size: Rect, command: Option> } impl State { pub fn new(area: Rect) -> Self { let dir = Directory::new("root".into()); Self { dir, size: area, command: None, } } 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( |output| { self.dir.display(output, dir_area); if let Some(command) = self.command.as_ref() { match command { Either::Cmd(cmd) => cmd.display(output, bottom_area), Either::CmdOut(cmd) => cmd.display(output, bottom_area), } } } )?; 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)> { 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 { 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; } }