simple_ls_chan/
simple_ls_chan.rs

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