lib-nested/lib-nested-tty/src/terminal.rs

234 lines
7.8 KiB
Rust

use {
r3vi::{
view::{
channel::{queue_channel, set_channel, ChannelReceiver, ChannelSender},
Observer, OuterViewPort,
grid::*,
index::*,
}
},
crate::atom::{TerminalStyle},
crate::{TerminalView},
async_std::{stream::StreamExt, task},
cgmath::{Point2, Vector2},
signal_hook,
signal_hook_async_std::Signals,
std::sync::RwLock,
std::{
collections::HashSet,
io::{stdin, stdout, Write},
sync::Arc,
},
termion::{
input::{MouseTerminal, TermRead},
raw::IntoRawMode,
},
};
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum TerminalEvent {
Resize(Vector2<i16>),
Input(termion::event::Event),
}
pub struct Terminal {
writer: Arc<TermOutWriter>,
_observer: Arc<RwLock<TermOutObserver>>,
events: ChannelReceiver<Vec<TerminalEvent>>,
_signal_handle: signal_hook_async_std::Handle,
}
impl Terminal {
pub fn new(port: OuterViewPort<dyn TerminalView>) -> Self {
let (dirty_pos_tx, dirty_pos_rx) = set_channel();
let writer = Arc::new(TermOutWriter {
out: RwLock::new(MouseTerminal::from(stdout().into_raw_mode().unwrap())),
dirty_pos_rx,
view: port.get_view_arc(),
});
let observer = Arc::new(RwLock::new(TermOutObserver {
dirty_pos_tx,
writer: writer.clone(),
}));
port.add_observer(observer.clone());
let (event_tx, event_rx) = queue_channel();
let input_tx = event_tx.clone();
std::thread::spawn(move || {
for event in stdin().events() {
input_tx.send(TerminalEvent::Input(event.unwrap()));
}
});
// send initial teriminal size
let (w, h) = termion::terminal_size().unwrap();
event_tx.send(TerminalEvent::Resize(Vector2::new(w as i16, h as i16)));
// and again on SIGWINCH
let signals = Signals::new(&[signal_hook::consts::signal::SIGWINCH]).unwrap();
let handle = signals.handle();
task::spawn(async move {
let mut signals = signals.fuse();
while let Some(signal) = signals.next().await {
match signal {
signal_hook::consts::signal::SIGWINCH => {
let (w, h) = termion::terminal_size().unwrap();
event_tx.send(TerminalEvent::Resize(Vector2::new(w as i16, h as i16)));
}
_ => unreachable!(),
}
}
});
Terminal {
writer,
_observer: observer,
events: event_rx,
_signal_handle: handle,
}
}
pub fn get_writer(&self) -> Arc<TermOutWriter> {
self.writer.clone()
}
pub async fn next_event(&mut self) -> TerminalEvent {
self.events.next().await.unwrap()
}
}
struct TermOutObserver {
dirty_pos_tx: ChannelSender<HashSet<Point2<i16>>>,
writer: Arc<TermOutWriter>,
}
impl TermOutObserver {
fn send_area(&mut self, area: IndexArea<Point2<i16>>) {
match area {
IndexArea::Empty => {}
IndexArea::Full => {
let (w, h) = termion::terminal_size().unwrap();
for pos in
GridWindowIterator::from(Point2::new(0, 0)..Point2::new(w as i16, h as i16))
{
self.dirty_pos_tx.send(pos);
}
}
IndexArea::Range(r) => {
for pos in GridWindowIterator::from(r) {
self.dirty_pos_tx.send(pos);
}
}
IndexArea::Set(v) => {
for pos in v {
self.dirty_pos_tx.send(pos);
}
}
}
}
}
impl Observer<dyn TerminalView> for TermOutObserver {
fn reset(&mut self, view: Option<Arc<dyn TerminalView>>) {
self.writer.reset();
if let Some(view) = view {
self.send_area(view.area());
}
}
fn notify(&mut self, area: &IndexArea<Point2<i16>>) {
self.send_area(area.clone());
}
}
pub struct TermOutWriter {
out: RwLock<MouseTerminal<termion::raw::RawTerminal<std::io::Stdout>>>,
dirty_pos_rx: ChannelReceiver<HashSet<Point2<i16>>>,
view: Arc<RwLock<Option<Arc<dyn TerminalView>>>>,
}
impl TermOutWriter {
fn reset(&self) {
let mut out = self.out.write().unwrap();
write!(out, "{}", termion::clear::All).ok();
}
}
impl TermOutWriter {
pub async fn show(&self) -> std::io::Result<()> {
// init
write!(
self.out.write().unwrap(),
"{}{}{}",
termion::cursor::Hide,
termion::cursor::Goto(1, 1),
termion::style::Reset
)?;
let mut cur_pos = Point2::<i16>::new(0, 0);
let mut cur_style = TerminalStyle::default();
// draw atoms until view port is destroyed
while let Some(dirty_pos) = self.dirty_pos_rx.recv().await {
let (w, _h) = termion::terminal_size().unwrap();
if let Some(view) = self.view.read().unwrap().as_ref() {
let mut out = self.out.write().unwrap();
let d = dirty_pos
.into_iter()
.filter(|p| p.x >= 0 && p.y >= 0 && p.x < w as i16 && p.y < w as i16); //.collect::<Vec<_>>();
/*
d.sort_by(|a,b| {
if a.y < b.y {
std::cmp::Ordering::Less
} else if a.y == b.y {
a.x.cmp(&b.x)
} else {
std::cmp::Ordering::Greater
}
});
*/
for pos in d {
if pos != cur_pos {
write!(
out,
"{}",
termion::cursor::Goto(pos.x as u16 + 1, pos.y as u16 + 1)
)?;
}
if let Some(atom) = view.get(&pos) {
if cur_style != atom.style {
cur_style = atom.style;
write!(out, "{}", termion::style::Reset)?;
write!(out, "{}", atom.style)?;
}
write!(out, "{}", atom.c.unwrap_or(' '))?;
} else {
write!(out, "{} ", termion::style::Reset)?;
cur_style = TerminalStyle::default();
}
cur_pos = pos + Vector2::new(1, 0);
}
out.flush()?;
}
}
// restore conventional terminal settings
let mut out = self.out.write().unwrap();
write!(out, "{}", termion::cursor::Show)?;
out.flush()?;
std::io::Result::Ok(())
}
}