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