#[macro_use]
extern crate log;
extern crate pseudoterm;
extern crate termit_ui;
extern crate vte;
use pseudoterm::*;
use std::fs::File;
use std::io::{self, Read, Write};
use std::iter::once;
use std::process::{Child, Command};
use std::thread::sleep;
use std::time::Duration;
use termit_ui::geometry::*;
use termit_ui::graphics::Drawing;
use termit_ui::input::{GrinInput, Key, KeyEvent};
use termit_ui::screen::Screen;
use termit_ui::widget::Painter;
use termit_ui::widget::Widget;
pub struct Temu {
master: File,
child: Child,
size: Point,
sizer: WinsizeSetter,
parser: vte::Parser,
buffer: VTBuffer,
cursor_x: X,
cursor_y: Y,
screen: Screen,
errors: Vec<io::Error>,
}
impl Drop for Temu {
fn drop(&mut self) {
self.kill()
}
}
impl Widget for Temu {
fn render(&self, painter: &mut Painter) -> io::Result<()> {
for d in self.screen.into_iter() {
painter.paint(d.clone())?
}
self.errors
.first()
.map(|e| Err(io::Error::new(e.kind(), format!("{}", e))))
.unwrap_or(Ok(()))
}
fn arrange(&mut self, window: Window) {
if window.size == self.size {
return;
}
self.size = window.size;
self.sizer
.set(Winsize {
cols: self.size.x.to_primitive(),
rows: self.size.y.to_primitive(),
})
.unwrap_or_else(|e| self.errors.push(e));
self.cursor_x = X::min(self.cursor_x, self.size.x);
self.cursor_y = Y::min(self.cursor_y, self.size.y);
self.screen
.resize(self.size)
.unwrap_or_else(|e| self.errors.push(e));
}
fn consume(&mut self, input: Vec<GrinInput>) -> Vec<GrinInput> {
self.errors.clear();
let mut utf8 = [0u8; 4];
for i in input {
match i {
GrinInput::Key(KeyEvent {
key: Key::Char(c), ..
}) => {
self.master
.write(c.encode_utf8(&mut utf8).as_bytes())
.unwrap_or_else(|e| (0, self.errors.push(e)).0);
}
_ => {}
}
}
self.master.flush().unwrap_or_else(|e| self.errors.push(e));
self.react().unwrap_or_else(|e| self.errors.push(e));
for vt in self.buffer.buffer.drain(0..) {
match vt {
VTOut::Print(txt) => {
let d = Drawing::new()
.with_text(txt)
.left(self.cursor_x.to_primitive())
.top(self.cursor_y.to_primitive());
let len = d.actual_width();
self.screen.draw(d);
self.cursor_x = self.cursor_x + len;
}
VTOut::Execute(13) => {
self.cursor_y = self.cursor_y + y(1);
}
VTOut::Execute(10) => {
self.cursor_x = x(0);
}
_ => {}
}
}
vec![]
}
}
impl Temu {
pub fn open(mut cmd: &mut Command) -> io::Result<Temu> {
let size = point(x(80), y(20));
let (master, slave) = pseudoterm::openpty(
&OpenptyOptions::new()
.with_size(Winsize {
cols: size.x.to_primitive(),
rows: size.y.to_primitive(),
})
.with_nonblocking(true),
)?;
let child = pseudoterm::prepare_cmd(slave, &mut cmd)?.spawn()?;
let sizer = WinsizeSetter::new(&master)?;
let parser = vte::Parser::new();
let buffer = VTBuffer { buffer: vec![] };
Ok(Temu {
master,
size,
sizer,
child,
parser,
buffer,
cursor_x: x(0),
cursor_y: y(0),
screen: Screen::new(size),
errors: vec![],
})
}
pub fn react(&mut self) -> io::Result<()> {
let Temu {
ref mut master,
ref mut sizer,
ref mut parser,
ref mut buffer,
ref mut child,
..
} = self;
sizer.set(Winsize { cols: 70, rows: 10 })?;
sleep(Duration::from_millis(100));
for b in master.bytes() {
match b {
Ok(b) => {
parser.advance(buffer, b);
}
Err(e) => match e.kind() {
io::ErrorKind::WouldBlock => {
break;
}
_ => {
let status = child.try_wait();
warn!("Error on child output. Child status: {:?}", status);
break;
}
},
}
}
for o in buffer.buffer.iter() {
trace!("VTOut::{:?}", o);
}
Ok(())
}
fn kill(&mut self) {
self.child
.kill()
.unwrap_or_else(|e| warn!("Couldn't kill the child, unfortunately! {}", e));
}
}
struct VTBuffer {
buffer: Vec<VTOut>,
}
#[derive(Debug)]
enum VTOut {
Print(String),
Execute(u8),
OSC(Vec<Vec<u8>>),
DCS {
params: Vec<i64>,
intermediates: Vec<u8>,
ignore: bool,
data: Vec<u8>,
},
CSI {
params: Vec<i64>,
intermediates: Vec<u8>,
ignore: bool,
c: char,
},
ESC {
params: Vec<i64>,
intermediates: Vec<u8>,
ignore: bool,
b: u8,
},
}
impl vte::Perform for VTBuffer {
fn print(&mut self, c: char) {
if let Some(VTOut::Print(ref mut s)) = self.buffer.last_mut() {
s.push(c);
return;
}
self.buffer.push(VTOut::Print(once(c).collect()))
}
fn execute(&mut self, byte: u8) {
self.buffer.push(VTOut::Execute(byte))
}
fn hook(&mut self, params: &[i64], intermediates: &[u8], ignore: bool) {
self.buffer.push(VTOut::DCS {
params: params.into(),
intermediates: intermediates.into(),
ignore,
data: vec![],
})
}
fn put(&mut self, byte: u8) {
if let Some(VTOut::DCS { ref mut data, .. }) = self.buffer.last_mut() {
data.push(byte);
} else {
warn!("put() without hook() or after unhook() or on ignored DCS")
}
}
fn unhook(&mut self) {
if let Some(VTOut::DCS { .. }) = self.buffer.last_mut() {
} else {
warn!("unhook() without hook()")
}
}
fn osc_dispatch(&mut self, params: &[&[u8]]) {
self.buffer
.push(VTOut::OSC(params.iter().map(|v| Vec::from(*v)).collect()))
}
fn csi_dispatch(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, c: char) {
self.buffer.push(VTOut::CSI {
params: params.into(),
intermediates: intermediates.into(),
ignore,
c,
})
}
fn esc_dispatch(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, b: u8) {
self.buffer.push(VTOut::ESC {
params: params.into(),
intermediates: intermediates.into(),
ignore,
b,
})
}
}