mod utils;
mod renderers;
mod event_handlers;
mod shortcut_handler;
mod rendering;
mod diff_sync;
use crate::components::{Component, StatusPane, BranchesPane, LogPane, StashPane, TagsPane, ReflogPane, DiffPane, HelpPane};
use crate::app::{AppState, Action, reducer};
use crate::app::state::{FocusPane, DiffMode};
use crate::errors::ComponentError;
use crate::input::InputEvent;
use crate::services::GitService;
use crate::palette::PaletteHandler;
use ratatui::Frame;
use ratatui::layout::{Constraint, Direction, Layout, Rect};
use tracing::{debug, warn};
use std::collections::HashMap;
use std::sync::Arc;
pub struct ComponentManager {
pub components: HashMap<FocusPane, Box<dyn Component>>,
pub(super) active_component: FocusPane,
pub(super) git_service: Arc<GitService>,
pub(super) diff: DiffPane,
pub(super) help: HelpPane,
pub(super) palette_handler: PaletteHandler,
pub(super) shortcut_handler: shortcut_handler::ShortcutHandler,
}
impl ComponentManager {
pub fn new(git_service: Arc<GitService>) -> Self {
let mut components: HashMap<FocusPane, Box<dyn Component>> = HashMap::new();
components.insert(
FocusPane::Status,
Box::new(StatusPane::new(git_service.clone())),
);
components.insert(
FocusPane::Branches,
Box::new(BranchesPane::new(git_service.clone())),
);
components.insert(
FocusPane::Log,
Box::new(LogPane::new(git_service.clone())),
);
components.insert(
FocusPane::Stash,
Box::new(StashPane::new(git_service.clone())),
);
components.insert(
FocusPane::Tags,
Box::new(TagsPane::new(git_service.clone())),
);
components.insert(
FocusPane::Reflog,
Box::new(ReflogPane::new(git_service.clone())),
);
Self {
components,
active_component: FocusPane::Status,
git_service: git_service.clone(),
diff: DiffPane::new(),
help: HelpPane::new(),
palette_handler: PaletteHandler::new(),
shortcut_handler: shortcut_handler::ShortcutHandler::new(),
}
}
pub(crate) fn active_mut(&mut self) -> Option<&mut Box<dyn Component>> {
self.components.get_mut(&self.active_component)
}
pub fn switch_to(&mut self, pane: FocusPane) {
self.active_component = pane;
}
pub fn handle_event(
&mut self,
event: InputEvent,
state: &AppState,
) -> Result<Option<Action>, ComponentError> {
event_handlers::handle_event(self, event, state)
}
pub fn update(
&mut self,
action: Action,
state: &mut AppState,
) -> Result<(), ComponentError> {
match action {
Action::StatusUp | Action::StatusDown | Action::StatusPageUp | Action::StatusPageDown | Action::StatusTop | Action::StatusBottom => {
if let Some(component) = self.components.get_mut(&FocusPane::Status) {
component.update(action.clone(), state)?;
self.sync_status_selection(state);
}
return Ok(());
}
Action::SetStatusEntries(_) | Action::SetSelectedPath(_) | Action::ToggleDiffMode => {
if let Some(component) = self.components.get_mut(&FocusPane::Status) {
component.update(action.clone(), state)?;
}
self.sync_status_selection(state);
return Ok(());
}
Action::FocusNext => {
self.switch_to(state.focus);
return Ok(());
}
Action::FocusPrev => {
self.switch_to(state.focus);
return Ok(());
}
_ => {
if let Some(target) = self.target_pane(&action) {
if let Some(component) = self.components.get_mut(&target) {
component.update(action, state)?;
}
} else {
if let Some(component) = self.components.get_mut(&self.active_component) {
component.update(action, state)?;
}
}
}
}
Ok(())
}
pub fn render(&mut self, frame: &mut Frame, state: &AppState) -> Result<(), ComponentError> {
rendering::render(self, frame, state)
}
fn next_pane(&self) -> FocusPane {
match self.active_component {
FocusPane::Status => FocusPane::Branches,
FocusPane::Branches => FocusPane::Log,
FocusPane::Log => FocusPane::Stash,
FocusPane::Stash => FocusPane::Tags,
FocusPane::Tags => FocusPane::Reflog,
FocusPane::Reflog => FocusPane::Status,
}
}
fn prev_pane(&self) -> FocusPane {
match self.active_component {
FocusPane::Status => FocusPane::Reflog,
FocusPane::Branches => FocusPane::Status,
FocusPane::Log => FocusPane::Branches,
FocusPane::Stash => FocusPane::Log,
FocusPane::Tags => FocusPane::Stash,
FocusPane::Reflog => FocusPane::Tags,
}
}
fn target_pane(&self, action: &Action) -> Option<FocusPane> {
match action {
Action::RefreshStatus
| Action::StatusUp
| Action::StatusDown
| Action::StatusPageUp
| Action::StatusPageDown
| Action::StatusTop
| Action::StatusBottom
| Action::SetStatusEntries(_)
| Action::SetSelectedPath(_)
| Action::ToggleDiffMode => Some(FocusPane::Status),
Action::RefreshBranches
| Action::BranchUp
| Action::BranchDown
| Action::SetBranches(_) => Some(FocusPane::Branches),
Action::RefreshCommits
| Action::CommitUp
| Action::CommitDown
| Action::SetCommits(_)
| Action::SetCommitDetail(_)
| Action::ToggleLogGraph
| Action::SetLogGraphText(_) => Some(FocusPane::Log),
Action::RefreshStashes | Action::StashUp | Action::StashDown | Action::SetStashes(_) => {
Some(FocusPane::Stash)
}
Action::RefreshTags | Action::TagUp | Action::TagDown | Action::SetTags(_) => {
Some(FocusPane::Tags)
}
Action::RefreshReflog | Action::ReflogUp | Action::ReflogDown | Action::SetReflog(_) => {
Some(FocusPane::Reflog)
}
_ => None,
}
}
pub(crate) fn sync_status_selection(&self, state: &mut AppState) {
diff_sync::sync_status_selection(self, state)
}
}