Skip to main content

simple_ls_rw/
simple_ls_rw.rs

1use std::{
2    io,
3    sync::{Arc, RwLock},
4};
5
6use crossterm::event::{self, Event, KeyCode, KeyEventKind};
7use portable_pty::{CommandBuilder, NativePtySystem, PtySize, PtySystem};
8use ratatui::{
9    DefaultTerminal, Frame,
10    layout::Alignment,
11    style::{Modifier, Style},
12    text::Line,
13    widgets::{Block, Borders, Paragraph},
14};
15use tui_term::widget::PseudoTerminal;
16use vt100::Screen;
17
18fn main() -> std::io::Result<()> {
19    let mut terminal = ratatui::init();
20    let result = run_app(&mut terminal);
21    ratatui::restore();
22    result
23}
24
25fn run_app(terminal: &mut DefaultTerminal) -> std::io::Result<()> {
26    let terminal_size = terminal.size()?;
27    let size = Size {
28        rows: terminal_size.height,
29        cols: terminal_size.width,
30    };
31
32    let pty_system = NativePtySystem::default();
33    let cwd = std::env::current_dir().unwrap();
34    let mut cmd = CommandBuilder::new("ls");
35    cmd.cwd(cwd);
36
37    let pair = pty_system
38        .openpty(PtySize {
39            rows: size.rows,
40            cols: size.cols,
41            pixel_width: 0,
42            pixel_height: 0,
43        })
44        .unwrap();
45    let mut child = pair.slave.spawn_command(cmd).unwrap();
46    drop(pair.slave);
47
48    let mut reader = pair.master.try_clone_reader().unwrap();
49    let parser = Arc::new(RwLock::new(vt100::Parser::new(
50        size.rows - 1,
51        size.cols - 1,
52        0,
53    )));
54
55    {
56        let parser = parser.clone();
57        std::thread::spawn(move || {
58            // Consume the output from the child
59            let mut s = String::new();
60            reader.read_to_string(&mut s).unwrap();
61            if !s.is_empty() {
62                let mut parser = parser.write().unwrap();
63                parser.process(s.as_bytes());
64            }
65        });
66    }
67
68    {
69        // Drop writer on purpose
70        let _writer = pair.master.take_writer().unwrap();
71    }
72
73    // Wait for the child to complete
74    let _child_exit_status = child.wait().unwrap();
75
76    drop(pair.master);
77
78    run(terminal, parser)
79}
80
81fn run(terminal: &mut DefaultTerminal, parser: Arc<RwLock<vt100::Parser>>) -> io::Result<()> {
82    loop {
83        terminal.draw(|f| ui(f, parser.read().unwrap().screen()))?;
84
85        if let Event::Key(key) = event::read()? {
86            if key.kind == KeyEventKind::Press {
87                if let KeyCode::Char('q') = key.code {
88                    return Ok(());
89                }
90            }
91        }
92    }
93}
94
95fn ui(f: &mut Frame, screen: &Screen) {
96    let chunks = ratatui::layout::Layout::default()
97        .direction(ratatui::layout::Direction::Vertical)
98        .margin(1)
99        .constraints(
100            [
101                ratatui::layout::Constraint::Percentage(50),
102                ratatui::layout::Constraint::Percentage(50),
103                ratatui::layout::Constraint::Min(1),
104            ]
105            .as_ref(),
106        )
107        .split(f.area());
108    let title = Line::from("[ Running: ls ]");
109    let block = Block::default()
110        .borders(Borders::ALL)
111        .title(title)
112        .style(Style::default().add_modifier(Modifier::BOLD));
113    let pseudo_term = PseudoTerminal::new(screen).block(block.clone());
114    f.render_widget(pseudo_term, chunks[0]);
115    let pseudo_term = PseudoTerminal::new(screen).block(block);
116    f.render_widget(pseudo_term, chunks[1]);
117    let block = Block::default().borders(Borders::ALL);
118    f.render_widget(block, f.area());
119    let explanation = "Press q to exit";
120    let explanation = Paragraph::new(explanation)
121        .style(Style::default().add_modifier(Modifier::BOLD | Modifier::REVERSED))
122        .alignment(Alignment::Center);
123    f.render_widget(explanation, chunks[2]);
124}
125
126#[derive(Debug, Clone)]
127struct Size {
128    cols: u16,
129    rows: u16,
130}