use log::trace;
use portable_pty::{native_pty_system, Child, CommandBuilder, MasterPty, PtySize};
use std::{
io::{self, BufRead, BufReader, Write},
time::Duration,
};
use termit::prelude::*;
use unicode_segmentation::UnicodeSegmentation;
#[async_std::main]
async fn main() -> io::Result<()> {
env_logger::init();
let exe = std::env::args().nth(1).unwrap_or("top".to_owned());
let exe = exe.as_str();
let mut app = start(exe);
let mut ui = Stack::south_east_boxes(vec![])
.add_box(
Canvas::new(String::new())
.front(Color::red(true))
.bind_property(|w| w.content_mut(), |m: &mut AppState| &mut m.temu.title)
.left(2)
.right(2)
.width(exe.graphemes(true).count() as u16)
.top(1)
.height(1),
)
.add_box(ChildApp::default().top(1).left(2).right(2).width(90));
let mut termit = Terminal::try_system_default()?
.into_termit::<NoAppEvent>()
.enter_raw_mode()?
.handle_focus_events(true)?
.enable_line_wrap(false)?;
while !app.is_terminating {
termit.step(&mut app, &mut ui).await?;
let buff = app.reader.fill_buf().expect("read buff");
let input = std::str::from_utf8(buff);
trace!("command output: {:?}", input);
if buff.is_empty() {
termit
.refresh_time_sender()
.send_blocking(Duration::from_millis(100))
.expect("refresh interval update");
} else {
app.temu.write_all(buff).expect("write buff");
let consumed = buff.len();
app.reader.consume(consumed);
app.temu.flush().expect("temu flush");
termit
.refresh_time_sender()
.send_blocking(Duration::from_millis(10))
.expect("refresh interval update");
}
if let Some(_exit) = app.child.try_wait().expect("try wait for child") {
app.is_terminating = true;
}
}
let status = app.child.wait().expect("wait for child");
debug_assert!(status.success(), "child status {:?}", status);
Ok(())
}
struct AppState {
is_terminating: bool,
master: Box<dyn MasterPty>,
child: Box<dyn Child>,
reader: Box<dyn BufRead>,
writer: Box<dyn Write + Send>,
temu: TerminalEmulator,
}
fn start(exe: impl AsRef<str>) -> AppState {
let pair = native_pty_system()
.openpty(PtySize {
rows: 0,
cols: 0,
pixel_width: 0,
pixel_height: 0,
})
.expect("pty");
let exe = exe.as_ref();
let mut cmd = CommandBuilder::new(exe);
cmd.env("TERM", "xterm");
cmd.env("COLORTERM", "truecolor");
AppState {
is_terminating: false,
child: pair.slave.spawn_command(cmd).expect(exe),
reader: Box::new(BufReader::with_capacity(
1024 * 1024 * 8,
pair.master.try_clone_reader().expect("reader"),
)),
writer: pair.master.take_writer().expect("writer"),
master: pair.master,
temu: TerminalEmulator::new(exe),
}
}
#[derive(Default)]
struct ChildApp;
impl AnchorPlacementEnabled for ChildApp {}
impl<A: AppEvent> Widget<AppState, A> for ChildApp {
fn update(
&mut self,
model: &mut AppState,
input: &Event<A>,
screen: &mut Screen,
painter: &Painter,
) -> Window {
if let Event::Key(event) = input {
if event.keycode == KeyCode::Char('q') {
model.writer.write_all(b"q").expect("write q");
model.writer.flush().expect("flush temu");
}
}
if painter.scope().size() != model.temu.size() {
model.temu.resize(painter.scope().size());
model
.master
.resize(PtySize {
cols: painter.scope().width,
rows: painter.scope().height,
pixel_width: painter.scope().width.saturating_mul(10),
pixel_height: painter.scope().height.saturating_mul(16),
})
.expect("resize pty");
}
painter.copy(model.temu.symbols(), screen);
painter.scope()
}
}