1use crossterm::event::{self, Event as CtEvent, KeyEvent};
4use std::time::Duration;
5use tokio::sync::mpsc;
6use tracing::warn;
7
8use crate::setup::state::OrgEntry;
9use crate::types::{OpSummary, OwnedRepo};
10
11use super::app::{CheckEntry, Operation, RepoEntry};
12
13#[derive(Debug)]
15pub enum AppEvent {
16 Terminal(KeyEvent),
18 Resize(u16, u16),
20 Backend(BackendMessage),
22 Tick,
24}
25
26#[derive(Debug, Clone)]
28pub enum BackendMessage {
29 OrgsDiscovered(usize),
31 OrgStarted(String),
33 OrgComplete(String, usize),
35 DiscoveryComplete(Vec<OwnedRepo>),
37 DiscoveryError(String),
39 SetupOrgsDiscovered(Vec<OrgEntry>),
41 SetupOrgsError(String),
43 OperationStarted {
45 operation: Operation,
46 total: usize,
47 to_clone: usize,
48 to_sync: usize,
49 },
50 RepoStarted { repo_name: String },
52 RepoProgress {
54 repo_name: String,
55 success: bool,
56 skipped: bool,
57 message: String,
58 had_updates: bool,
60 is_clone: bool,
62 new_commits: Option<u32>,
64 skip_reason: Option<String>,
66 },
67 RepoCommitLog {
69 repo_name: String,
70 commits: Vec<String>,
71 },
72 OperationComplete(OpSummary),
74 OperationError(String),
76 StatusResults(Vec<RepoEntry>),
78 SetupCheckResults(Vec<CheckEntry>),
80 DefaultWorkspaceUpdated(Option<String>),
82 DefaultWorkspaceError(String),
84 CheckResults(Vec<CheckEntry>),
86}
87
88pub fn spawn_event_loop(
91 tick_rate: Duration,
92) -> (
93 mpsc::UnboundedReceiver<AppEvent>,
94 mpsc::UnboundedSender<AppEvent>,
95) {
96 let (tx, rx) = mpsc::unbounded_channel();
97 let event_tx = tx.clone();
98
99 tokio::task::spawn_blocking(move || {
101 loop {
102 let has_event = match event::poll(tick_rate) {
103 Ok(v) => v,
104 Err(e) => {
105 warn!(error = %e, "Terminal poll failed; stopping event loop");
106 break;
107 }
108 };
109
110 if has_event {
111 if let Ok(ev) = event::read() {
112 let app_event = match ev {
113 CtEvent::Key(key) => AppEvent::Terminal(key),
114 CtEvent::Resize(w, h) => AppEvent::Resize(w, h),
115 _ => continue,
116 };
117 if event_tx.send(app_event).is_err() {
118 break;
119 }
120 }
121 } else {
122 if event_tx.send(AppEvent::Tick).is_err() {
124 break;
125 }
126 }
127 }
128 });
129
130 (rx, tx)
131}
132
133#[cfg(test)]
134#[path = "event_tests.rs"]
135mod tests;