simple_ls_controller/
simple_ls_controller.rs1use 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 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}