pub mod handlers;
mod init;
pub use init::{initialize_data_sources, preflight_amend, preflight_pick_revert, validate_amend_input};
pub use init::{RebaseRecoveryInfo, detect_rebase_recovery_state};
use crate::app::{Action, reducer, Application};
use crate::input::InputEvent;
use crate::errors::ApplicationError;
use crate::config;
use crate::async_result::AsyncResult;
use crate::app::workflow::WorkflowState;
use ratatui::backend::CrosstermBackend;
use crate::ui::{BackgroundManager, font};
use std::time::Duration;
use ratatui::Terminal;
use futures::StreamExt;
pub struct HandlerContext<'a> {
pub app: &'a mut Application,
pub terminal: &'a mut Terminal<CrosstermBackend<std::io::Stdout>>,
pub bg_manager: &'a mut BackgroundManager,
}
pub async fn run(
app: &mut Application,
terminal: &mut Terminal<CrosstermBackend<std::io::Stdout>>,
) -> Result<(), ApplicationError> {
for component in app.component_manager.components.values_mut() {
component.init(&app.state).map_err(ApplicationError::ComponentInitFailed)?;
}
app.state = reducer(app.state.clone(), Action::SetRefreshing(true));
if let Err(e) = app.component_manager.update(Action::RefreshStatus, &mut app.state) {
tracing::warn!(error = %e, "Initial status refresh failed");
}
app.state = reducer(app.state.clone(), Action::SetRefreshing(false));
initialize_data_sources(app);
app.component_manager.sync_status_selection(&mut app.state);
app.state = reducer(app.state.clone(), Action::UpdateWorkflowContext);
let mut bg_manager = BackgroundManager::new();
bg_manager.set_opacity(app.state.background_opacity);
if let Some(path) = &app.state.background_image_path {
if let Err(e) = bg_manager.load(path) {
tracing::error!("Failed to load background image: {}", e);
}
}
if let Some(f) = &app.state.font_family {
font::set_font(f);
}
init::handle_rebase_recovery(app);
while app.state.running && !*app.cancel_rx.borrow() {
terminal.draw(|frame| {
if let Err(e) = app.component_manager.render(frame, &app.state) {
tracing::error!(error = %e, "Render failed");
}
bg_manager.process_buffer(frame.buffer_mut());
}).map_err(ApplicationError::IoError)?;
let event = wait_for_event(app).await;
if event.is_none() {
if *app.cancel_rx.borrow() {
break;
}
continue;
}
if let Some(input_event) = event {
let mut ctx = HandlerContext { app, terminal, bg_manager: &mut bg_manager };
if let Err(e) = handlers::handle_input_event(&mut ctx, input_event).await {
tracing::error!(error = %e, "Event handling failed");
}
}
}
Ok(())
}
async fn wait_for_event(app: &mut Application) -> Option<InputEvent> {
tokio::select! {
event = app.event_stream.next() => {
match event {
Some(Ok(e)) => Some(InputEvent::from(e)),
Some(Err(e)) => {
tracing::error!(error = %e, "Event stream error");
None
}
None => None,
}
}
async_result = app.async_rx.recv() => {
if let Some(result) = async_result {
let _ = app.handle_async_result(result);
}
None }
_ = app.cancel_rx.changed() => {
None
}
_ = tokio::time::sleep(Duration::from_millis(100)) => {
None }
}
}