simple_ls_controller/
simple_ls_controller.rs

1use std::io::{self, BufWriter};
2
3use crossterm::{
4    event::{self, Event, KeyCode, KeyEventKind},
5    execute,
6    terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
7};
8use portable_pty::CommandBuilder;
9use ratatui::{
10    backend::{Backend, CrosstermBackend},
11    layout::Alignment,
12    style::{Modifier, Style},
13    text::Line,
14    widgets::{Block, Borders, Paragraph},
15    Frame, Terminal,
16};
17use tui_term::{controller::Controller, widget::PseudoTerminal};
18use vt100::Screen;
19
20fn main() -> std::io::Result<()> {
21    let (mut terminal, size) = setup_terminal().unwrap();
22
23    // Subtract the borders from the size
24    let size = tui_term::controller::Size::new(size.cols - 2, size.rows, 0, 0);
25
26    let mut cmd = CommandBuilder::new("ls");
27    if let Ok(cwd) = std::env::current_dir() {
28        cmd.cwd(cwd);
29    }
30
31    let mut controller = Controller::new(cmd, Some(size));
32    controller.run();
33    let screen = controller.screen();
34
35    run(&mut terminal, screen)?;
36
37    cleanup_terminal(&mut terminal).unwrap();
38    Ok(())
39}
40
41fn run<B: Backend>(terminal: &mut Terminal<B>, screen: Option<vt100::Screen>) -> io::Result<()> {
42    loop {
43        if let Some(ref screen) = screen {
44            terminal.draw(|f| ui(f, &screen))?;
45        }
46
47        if let Event::Key(key) = event::read()? {
48            if key.kind == KeyEventKind::Press {
49                if let KeyCode::Char('q') = key.code {
50                    return Ok(());
51                }
52            }
53        }
54    }
55}
56
57fn ui(f: &mut Frame, screen: &Screen) {
58    let chunks = ratatui::layout::Layout::default()
59        .direction(ratatui::layout::Direction::Vertical)
60        .margin(1)
61        .constraints(
62            [
63                ratatui::layout::Constraint::Percentage(100),
64                ratatui::layout::Constraint::Min(1),
65            ]
66            .as_ref(),
67        )
68        .split(f.area());
69    let title = Line::from("[ Running: ls ]");
70    let block = Block::default()
71        .borders(Borders::ALL)
72        .title(title)
73        .style(Style::default().add_modifier(Modifier::BOLD));
74    let pseudo_term = PseudoTerminal::new(screen)
75        .cursor(tui_term::widget::Cursor::default().visibility(false))
76        .block(block.clone());
77    f.render_widget(pseudo_term, chunks[0]);
78    let explanation = "Press q to exit";
79    let explanation = Paragraph::new(explanation)
80        .style(Style::default().add_modifier(Modifier::BOLD | Modifier::REVERSED))
81        .alignment(Alignment::Center);
82    f.render_widget(explanation, chunks[1]);
83}
84
85fn setup_terminal() -> io::Result<(Terminal<CrosstermBackend<BufWriter<io::Stdout>>>, Size)> {
86    enable_raw_mode()?;
87    let stdout = io::stdout();
88    let backend = CrosstermBackend::new(BufWriter::new(stdout));
89    let mut terminal = Terminal::new(backend)?;
90    let initial_size = terminal.size()?;
91    let size = Size {
92        rows: initial_size.height,
93        cols: initial_size.width,
94    };
95    execute!(terminal.backend_mut(), EnterAlternateScreen)?;
96    Ok((terminal, size))
97}
98
99fn cleanup_terminal(
100    terminal: &mut Terminal<CrosstermBackend<BufWriter<io::Stdout>>>,
101) -> io::Result<()> {
102    execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
103    disable_raw_mode()?;
104    terminal.show_cursor()?;
105    terminal.clear()?;
106    Ok(())
107}
108
109#[derive(Debug, Clone)]
110struct Size {
111    cols: u16,
112    rows: u16,
113}