use std::time::{Duration, Instant};
use crossterm::event::{Event, EventStream, KeyEvent, MouseEvent};
use futures_util::StreamExt as _;
use tokio::sync::mpsc::UnboundedSender;
use tokio::task::JoinHandle;
use crate::mcp::McpRequest;
#[derive(Debug)]
pub enum AppEvent {
Key(KeyEvent),
Mouse(MouseEvent),
Resize,
Focus(bool),
Tick,
RepoChanged,
Enriched(Box<crate::app::enrich::EnrichOutcome>),
FileLoaded {
result: Box<Result<crate::app::file::FileView, String>>,
span: Option<(u32, u32)>,
reload: bool,
token: u64,
},
DeclaredKinds {
kinds: std::collections::HashMap<String, diffler_core::classify::Kind>,
token: u64,
},
RepoStats {
stats: Box<diffler_core::stats::RepoStats>,
token: u64,
},
WalkthroughAnchors {
contents: std::collections::HashMap<String, String>,
pin_broken: bool,
token: u64,
},
RefreshDone(Box<Result<diffler_core::review::Refreshed, String>>),
RediffDone {
result: Box<Result<diffler_core::review::Refreshed, String>>,
request: crate::app::RediffRequest,
},
Lens {
token: u64,
lens: Box<crate::app::Lens>,
},
ImagePreview {
token: u64,
preview: Box<crate::app::image::ImagePreview>,
},
Mcp(McpRequest),
McpWaiting {
until: Instant,
},
GitDone {
label: String,
ok: bool,
output: String,
},
CiRuns(Vec<crate::ci::CiRun>),
PrComments {
number: u64,
comments: Vec<crate::ci::PrComment>,
pr: Option<crate::ci::PullRequest>,
},
PrPosted {
post: Box<crate::app::pr::PrPost>,
result: Result<Option<crate::ci::PrComment>, String>,
},
CiPrs(Vec<crate::ci::PullRequest>),
CiPr(Option<crate::ci::PullRequest>),
PrCreated(Box<Result<crate::ci::PullRequest, String>>),
CiRunDetail(crate::ci::RunDetail),
CiExtras(crate::ci::RunExtras),
CiLog {
text: String,
steps: Vec<crate::ci::LogStepMeta>,
next_offset: u64,
done: bool,
},
CiError(String),
CiPrsError(String),
Quit,
}
pub(crate) const TICK: Duration = Duration::from_millis(250);
pub fn spawn_event_loop(tx: UnboundedSender<AppEvent>) -> JoinHandle<()> {
tokio::spawn(async move {
let mut events = EventStream::new();
let mut tick = tokio::time::interval(TICK);
tick.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
loop {
let event = tokio::select! {
_ = tick.tick() => Some(AppEvent::Tick),
event = events.next() => match event {
Some(Ok(Event::Key(key))) => Some(AppEvent::Key(key)),
Some(Ok(Event::Mouse(mouse))) => Some(AppEvent::Mouse(mouse)),
Some(Ok(Event::Resize(_, _))) => Some(AppEvent::Resize),
Some(Ok(Event::FocusGained)) => Some(AppEvent::Focus(true)),
Some(Ok(Event::FocusLost)) => Some(AppEvent::Focus(false)),
Some(Ok(_)) => None,
Some(Err(_)) | None => {
let _ = tx.send(AppEvent::Quit);
return;
}
},
};
if let Some(event) = event
&& tx.send(event).is_err()
{
return;
}
}
})
}