simple_ls_chan/
simple_ls_chan.rs1use 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 let mut s = String::new();
52 reader.read_to_string(&mut s).unwrap();
53 tx.send(s).unwrap();
54 });
55
56 {
57 let _writer = pair.master.take_writer().unwrap();
59 }
60
61 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}