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