use std::{ env::{self}, fs, io, path::PathBuf, }; use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind}; use ratatui::{ DefaultTerminal, Frame, buffer::Buffer, layout::{Constraint, Direction, Layout, Rect}, style::{Color, Stylize}, widgets::{Block, Borders, List, ListState, Padding, StatefulWidget}, }; type E = std::io::Error; struct App<'a> { window: Window<'a>, preview: Preview<'a>, exit: bool, } impl App<'_> { fn run(&mut self, terminal: &mut DefaultTerminal) -> io::Result<()> { while !self.exit { terminal.draw(|frame| self.draw(frame))?; self.handle_events()?; } Ok(()) } fn draw(&mut self, frame: &mut Frame) { let main_area = Layout::default() .direction(Direction::Horizontal) .constraints(vec![Constraint::Percentage(50), Constraint::Percentage(50)]) .split(frame.area()); self.window.render(main_area[0], frame.buffer_mut()); self.preview.render(main_area[1], frame.buffer_mut()); } fn handle_events(&mut self) -> io::Result<()> { match event::read()? { Event::Key(key_event) if key_event.kind == KeyEventKind::Press => { self.handle_key_events(key_event) } _ => {} }; Ok(()) } fn handle_key_events(&mut self, key_event: KeyEvent) { match key_event.code { KeyCode::Char('q') | KeyCode::Esc => self.exit(), KeyCode::Down => { self.window.state.select_next(); self.preview.the_final_stretch(); } KeyCode::Up => { self.window.state.select_previous(); self.preview.the_final_stretch(); } KeyCode::Left => { if &self.window.absolute_path != "/" { self.preview.absolute_path = self.window.absolute_path.clone(); } self.window.absolute_path.pop(); self.window.update(); self.preview.state = self.window.state; self.preview.muhehe(); } KeyCode::Right => { if let Some(val) = self.window.state.selected() { let current_dir = self .window .prepare_updated_list() .expect("You should always be in a valid directory"); self.window.absolute_path.push(current_dir[val].clone()); let output = self.window.update(); if !output { self.window.absolute_path.pop(); } } } _ => {} } } fn exit(&mut self) { self.exit = true; } } struct Window<'a> { widget: WindowWidget<'a>, absolute_path: PathBuf, state: ListState, } impl Window<'_> { fn new(absolute_path: PathBuf) -> Result { Ok(Window { widget: WindowWidget::new(Self::prepare_list(&absolute_path)?), absolute_path, state: ListState::default().with_selected(Some(0)), }) } fn render(&mut self, area: Rect, buf: &mut Buffer) { self.widget.render(area, buf, &mut self.state); } fn prepare_list(absolute_path: &PathBuf) -> Result, E> { let entries = fs::read_dir(absolute_path)?; let mut prepared_list = Vec::::new(); for item in entries { match item { Ok(entry) => { let path_string = entry.path().display().to_string(); prepared_list.push(path_string); } Err(err) => return Err(err), } } Ok(prepared_list) } fn prepare_updated_list(&self) -> Result, E> { Self::prepare_list(&self.absolute_path) } fn update(&mut self) -> bool { let values = self.prepare_updated_list(); if let Ok(val) = values { self.widget.update(val); self.state.select_first(); return true; } false } } struct WindowWidget<'a> { list: List<'a>, } impl WindowWidget<'_> { fn update(&mut self, prepared_list: Vec) { let block = Block::new() .borders(Borders::ALL) .padding(Padding::symmetric(1, 1)) .fg(Color::Blue); self.list = List::new(prepared_list) .block(block) .highlight_style(Color::Cyan); } fn new(prepared_list: Vec) -> Self { let mut window_widget = WindowWidget { list: List::new(Vec::::new()), }; window_widget.update(prepared_list); window_widget } } impl StatefulWidget for &mut WindowWidget<'_> { type State = ListState; fn render(self, area: Rect, buf: &mut Buffer, state: &mut ListState) { StatefulWidget::render(&self.list, area, buf, state); } } struct Preview<'a> { widget: PreviewWidget<'a>, absolute_path: PathBuf, state: ListState, } impl Preview<'_> { fn the_final_stretch(&mut self) { self.absolute_path.push( Self::prepare_list(&self.absolute_path).unwrap()[self.state.selected().unwrap()] .clone(), ); self.widget .update(Self::prepare_list(&self.absolute_path).unwrap()); } fn muhehe(&mut self) { self.widget .update(Self::prepare_list(&self.absolute_path).unwrap()); } fn prepare_list(absolute_path: &PathBuf) -> Result, E> { let entries = fs::read_dir(absolute_path)?; let mut prepared_list = Vec::::new(); for item in entries { match item { Ok(entry) => { let path_string = entry.path().display().to_string(); prepared_list.push(path_string); } Err(err) => return Err(err), } } Ok(prepared_list) } fn render(&mut self, area: Rect, buf: &mut Buffer) { self.widget.render(area, buf, &mut ListState::default()); } } struct PreviewWidget<'a> { list: List<'a>, } impl PreviewWidget<'_> { fn update(&mut self, prepared_list: Vec) { let block = Block::new() .borders(Borders::ALL) .padding(Padding::symmetric(1, 1)) .fg(Color::Blue); self.list = List::new(prepared_list) .block(block) .highlight_style(Color::Cyan); } fn new(prepared_list: Vec) -> Self { let mut preview_widget = PreviewWidget { list: List::new(Vec::::new()), }; preview_widget.update(prepared_list); preview_widget } } impl StatefulWidget for &mut PreviewWidget<'_> { type State = ListState; fn render(self, area: Rect, buf: &mut Buffer, state: &mut ListState) { StatefulWidget::render(&self.list, area, buf, state); } } fn main() -> io::Result<()> { let mut terminal = ratatui::init(); let dir = Window::new(env::current_dir()?)?; let view = env::current_dir()?; let app_result = App { window: dir, preview: Preview { widget: PreviewWidget::new(Preview::prepare_list(&view).unwrap()), absolute_path: view, state: ListState::default(), }, exit: false, } .run(&mut terminal); ratatui::restore(); app_result }