use std::collections::HashMap;
use std::future::poll_fn;
use std::io::{self, Write};
use std::sync::Arc;
use std::time::Duration;
use futures_core::Stream;
use tokio::sync::mpsc::{UnboundedSender, unbounded_channel};
use crate::app::App;
use crate::input::InputEvent;
use crate::runtime::{RawModeGuard, RunOptions, Runtime};
use crate::subscription::{SubKind, Subscriptions};
use crate::task::{Cancel, Effect, MsgStream};
pub async fn run<A>(app: A) -> io::Result<A::Output>
where
A: App,
A::Msg: Clone + Send + 'static,
{
run_with(app, RunOptions::default()).await
}
pub async fn run_with<A>(app: A, options: RunOptions) -> io::Result<A::Output>
where
A: App,
A::Msg: Clone + Send + 'static,
{
let (width, height) = crossterm::terminal::size()?;
let mut runtime = Runtime::new(app, width, height);
let (tx, mut rx) = unbounded_channel::<A::Msg>();
let mut stdout = io::stdout().lock();
let _guard = RawModeGuard::enable(options.keyboard, options.screen)?;
if options.screen != crate::runtime::ScreenMode::AltScreen {
crate::runtime::normalize_start_column();
}
let (bytes, init_exit) = runtime.startup();
stdout.write_all(&bytes)?;
stdout.flush()?;
if let Some(output) = init_exit {
return Ok(output);
}
spawn_effects(runtime.take_effects(), &tx);
let mut subs = ActiveSubscriptions::new(tx.clone());
subs.sync(runtime.app().subscriptions());
let mut events = crossterm::event::EventStream::new();
loop {
let anim = runtime.animation_interval();
let (bytes, exit) = tokio::select! {
biased;
maybe_event = poll_fn(|cx| Pin::new(&mut events).poll_next(cx)) => {
use crossterm::event::{Event, KeyEventKind};
match maybe_event {
Some(Ok(Event::Key(k)))
if matches!(k.kind, KeyEventKind::Press | KeyEventKind::Repeat) =>
{
runtime.handle(InputEvent::Key(k))
}
Some(Ok(Event::Paste(s))) => runtime.handle(InputEvent::Paste(s)),
Some(Ok(Event::Resize(w, h))) => {
let mut queued = Vec::new();
while queued.len() < 64
&& crossterm::event::poll(Duration::ZERO).unwrap_or(false)
{
match crossterm::event::read() {
Ok(ev) => queued.push(ev),
Err(_) => break,
}
}
let (mut w, mut h) = (w, h);
let mut bytes = Vec::new();
let mut exit = None;
for ev in queued {
match ev {
Event::Resize(nw, nh) => (w, h) = (nw, nh),
Event::Key(k)
if matches!(
k.kind,
KeyEventKind::Press | KeyEventKind::Repeat
) =>
{
let (b, e) = runtime.handle(InputEvent::Key(k));
bytes.extend_from_slice(&b);
if e.is_some() {
exit = e;
break;
}
}
Event::Paste(s) => {
let (b, e) = runtime.handle(InputEvent::Paste(s));
bytes.extend_from_slice(&b);
if e.is_some() {
exit = e;
break;
}
}
_ => {}
}
}
if exit.is_none() {
let (resize_bytes, resize_exit) = crate::runtime::resize_with_report(
&mut runtime,
w,
h,
options.screen,
);
bytes.extend_from_slice(&resize_bytes);
exit = resize_exit;
}
(bytes, exit)
}
Some(Ok(_)) => (Vec::new(), None),
Some(Err(e)) => return Err(e),
None => (runtime.finalize(), Some(A::Output::default())),
}
}
Some(msg) = rx.recv() => {
let mut batch = vec![msg];
while batch.len() < 256 {
match rx.try_recv() {
Ok(m) => batch.push(m),
Err(_) => break,
}
}
runtime.process_batch(batch)
}
_ = sleep_opt(anim), if anim.is_some() => (runtime.present(), None),
};
spawn_effects(runtime.take_effects(), &tx);
subs.sync(runtime.app().subscriptions());
if !bytes.is_empty() {
stdout.write_all(&bytes)?;
stdout.flush()?;
}
if let Some(output) = exit {
return Ok(output);
}
}
}
async fn sleep_opt(duration: Option<Duration>) {
match duration {
Some(d) => tokio::time::sleep(d).await,
None => std::future::pending().await,
}
}
pub fn spawn_effects<Msg: Send + 'static>(effects: Vec<Effect<Msg>>, tx: &UnboundedSender<Msg>) {
for effect in effects {
let Effect::Spawn { stream, cancel } = effect;
drive_stream(stream, cancel, tx.clone());
}
}
fn drive_stream<Msg: Send + 'static>(
mut stream: MsgStream<Msg>,
cancel: Arc<Cancel>,
tx: UnboundedSender<Msg>,
) {
tokio::spawn(async move {
loop {
if cancel.is_cancelled() {
break;
}
tokio::select! {
biased;
_ = cancel.cancelled() => break,
item = poll_fn(|cx| stream.as_mut().poll_next(cx)) => match item {
Some(msg) => {
if tx.send(msg).is_err() {
break;
}
}
None => break,
},
}
}
});
}
pub struct ActiveSubscriptions<Msg> {
running: HashMap<String, RunningSub>,
tx: UnboundedSender<Msg>,
}
struct RunningSub {
cancel: Arc<Cancel>,
fingerprint: Fingerprint,
}
#[derive(PartialEq, Eq)]
enum Fingerprint {
Every(Duration),
Stream,
}
#[derive(Debug, Default, PartialEq, Eq)]
pub struct SyncReport {
pub started: Vec<String>,
pub stopped: Vec<String>,
}
impl<Msg: Send + 'static> ActiveSubscriptions<Msg> {
pub fn new(tx: UnboundedSender<Msg>) -> Self {
Self {
running: HashMap::new(),
tx,
}
}
pub fn sync(&mut self, declared: Subscriptions<Msg>) -> SyncReport {
let mut report = SyncReport::default();
let mut seen: Vec<String> = Vec::new();
for (key, kind) in declared.entries {
seen.push(key.clone());
let fingerprint = match &kind {
SubKind::Every { interval, .. } => Fingerprint::Every(*interval),
SubKind::Stream { .. } => Fingerprint::Stream,
};
match self.running.get(&key) {
Some(running) if running.fingerprint == fingerprint => {}
Some(_) => {
self.stop(&key);
report.stopped.push(key.clone());
self.start(&key, kind, fingerprint);
report.started.push(key);
}
None => {
self.start(&key, kind, fingerprint);
report.started.push(key);
}
}
}
let absent: Vec<String> = self
.running
.keys()
.filter(|k| !seen.contains(k))
.cloned()
.collect();
for key in absent {
self.stop(&key);
report.stopped.push(key);
}
report
}
fn start(&mut self, key: &str, kind: SubKind<Msg>, fingerprint: Fingerprint) {
let cancel = Arc::new(Cancel::new());
match kind {
SubKind::Every { interval, make } => {
let tx = self.tx.clone();
let cancel_task = Arc::clone(&cancel);
tokio::spawn(async move {
loop {
tokio::select! {
biased;
_ = cancel_task.cancelled() => break,
_ = tokio::time::sleep(interval) => {
if tx.send(make()).is_err() {
break;
}
}
}
}
});
}
SubKind::Stream { make } => {
drive_stream(make(), Arc::clone(&cancel), self.tx.clone());
}
}
self.running.insert(
key.to_string(),
RunningSub {
cancel,
fingerprint,
},
);
}
fn stop(&mut self, key: &str) {
if let Some(running) = self.running.remove(key) {
running.cancel.cancel();
}
}
}
impl<Msg> Drop for ActiveSubscriptions<Msg> {
fn drop(&mut self) {
for running in self.running.values() {
running.cancel.cancel();
}
}
}