Skip to main content

simple_ls_chan/
simple_ls_chan.rs

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