use std::{
io::{self, Read},
sync::{Arc, RwLock},
time::Duration,
};
use crossterm::event::{self, Event, KeyCode, KeyEventKind};
use portable_pty::{CommandBuilder, NativePtySystem, PtySize, PtySystem};
use ratatui::{
DefaultTerminal, Frame,
layout::Alignment,
style::{Modifier, Style},
text::Line,
widgets::{Block, Borders, Paragraph},
};
use tui_term::widget::PseudoTerminal;
use vt100::Screen;
fn main() -> std::io::Result<()> {
let mut terminal = ratatui::init();
let result = run_app(&mut terminal);
ratatui::restore();
result
}
fn run_app(terminal: &mut DefaultTerminal) -> std::io::Result<()> {
let pty_system = NativePtySystem::default();
let cwd = std::env::current_dir().unwrap();
let mut cmd = CommandBuilder::new("top");
cmd.cwd(cwd);
let pair = pty_system
.openpty(PtySize {
rows: 24,
cols: 80,
pixel_width: 0,
pixel_height: 0,
})
.unwrap();
std::thread::spawn(move || {
let mut child = pair.slave.spawn_command(cmd).unwrap();
let _child_exit_status = child.wait().unwrap();
drop(pair.slave);
});
let mut reader = pair.master.try_clone_reader().unwrap();
let parser = Arc::new(RwLock::new(vt100::Parser::new(24, 80, 0)));
{
let parser = parser.clone();
std::thread::spawn(move || {
let mut buf = [0u8; 8192];
let mut processed_buf = Vec::new();
loop {
let size = reader.read(&mut buf).unwrap();
if size == 0 {
break;
}
if size > 0 {
processed_buf.extend_from_slice(&buf[..size]);
let mut parser = parser.write().unwrap();
parser.process(&processed_buf);
processed_buf.clear();
}
}
});
}
{
let _writer = pair.master.take_writer().unwrap();
}
drop(pair.master);
run(terminal, parser)
}
fn run(terminal: &mut DefaultTerminal, parser: Arc<RwLock<vt100::Parser>>) -> io::Result<()> {
loop {
terminal.draw(|f| ui(f, parser.read().unwrap().screen()))?;
if event::poll(Duration::from_millis(10))? {
if let Event::Key(key) = event::read()? {
if key.kind == KeyEventKind::Press {
if let KeyCode::Char('q') = key.code {
return Ok(());
}
}
}
}
}
}
fn ui(f: &mut Frame, screen: &Screen) {
let chunks = ratatui::layout::Layout::default()
.direction(ratatui::layout::Direction::Vertical)
.margin(1)
.constraints(
[
ratatui::layout::Constraint::Percentage(0),
ratatui::layout::Constraint::Percentage(100),
ratatui::layout::Constraint::Min(1),
]
.as_ref(),
)
.split(f.area());
let title = Line::from("[ Running: top ]");
let block = Block::default()
.borders(Borders::ALL)
.title(title)
.style(Style::default().add_modifier(Modifier::BOLD));
let pseudo_term = PseudoTerminal::new(screen).block(block);
f.render_widget(pseudo_term, chunks[1]);
let block = Block::default().borders(Borders::ALL);
f.render_widget(block, f.area());
let explanation = "Press q to exit".to_string();
let explanation = Paragraph::new(explanation)
.style(Style::default().add_modifier(Modifier::BOLD | Modifier::REVERSED))
.alignment(Alignment::Center);
f.render_widget(explanation, chunks[2]);
}