234 lines
7.8 KiB
Rust
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(())
|
|
}
|
|
}
|