eazygit 0.5.1

A fast TUI for Git with staging, conflicts, rebase, and palette-first UX
Documentation
use crate::components::Component;
use crate::services::GitService;
use crate::app::{AppState, Action, reducer};
use crate::errors::ComponentError;
use crate::input::InputEvent;
use crate::ui::style;
use ratatui::Frame;
use ratatui::layout::Rect;
use ratatui::widgets::{List, ListItem};
use ratatui::style::Style;
use crossterm::event::{KeyCode, KeyEventKind};
use std::sync::Arc;

pub struct StashPane {
    git_service: Arc<GitService>,
}

impl StashPane {
    pub fn new(git_service: Arc<GitService>) -> Self {
        Self { git_service }
    }
}

impl Component for StashPane {
    fn handle_event(
        &mut self,
        event: InputEvent,
        state: &AppState,
    ) -> Result<Option<Action>, ComponentError> {
        match event {
            InputEvent::Key(key) if key.kind == KeyEventKind::Press => {
                match key.code {
                    KeyCode::Esc => {
                        Ok(Some(Action::FocusPrev)) // Go back to Status pane
                    }
                    KeyCode::Char('k') | KeyCode::Up => {
                        Ok(Some(Action::StashUp))
                    }
                    KeyCode::Char('j') | KeyCode::Down => {
                        Ok(Some(Action::StashDown))
                    }
                    KeyCode::Enter => {
                        // Apply stash
                        if let Some(stash) = state.stashes.get(state.stash_selected) {
                            Ok(Some(Action::ApplyStash(stash.name.clone())))
                        } else {
                            Ok(None)
                        }
                    }
                    KeyCode::Char('P') => {
                        // Pop stash
                        if let Some(stash) = state.stashes.get(state.stash_selected) {
                            Ok(Some(Action::PopStash(stash.name.clone())))
                        } else {
                            Ok(None)
                        }
                    }
                    KeyCode::Char('D') => {
                        // Delete stash
                        if let Some(stash) = state.stashes.get(state.stash_selected) {
                            Ok(Some(Action::DeleteStash(stash.name.clone())))
                        } else {
                            Ok(None)
                        }
                    }
                    _ => Ok(None),
                }
            }
            _ => Ok(None),
        }
    }
    
    fn update(
        &mut self,
        action: Action,
        state: &mut AppState,
    ) -> Result<(), ComponentError> {
        match action {
            Action::RefreshStashes => {
                // Refresh stashes from git
                let path = state.repo_path.clone();
                match self.git_service.stash_list(&path) {
                    Ok(stashes) => {
                        *state = reducer(state.clone(), Action::SetStashes(stashes));
                    }
                    Err(e) => {
                        tracing::warn!(error = %e, "stash list error");
                    }
                }
            }
            _ => {}
        }
        Ok(())
    }
    
    fn render(&mut self, frame: &mut Frame, area: Rect, state: &AppState) {
        let theme = &state.theme;
        let selection_style = style::selection(theme);

        let items: Vec<ListItem> = if state.stashes.is_empty() {
            vec![ListItem::new("No stashes").style(style::text(theme, crate::ui::style::Emphasis::Muted))]
        } else {
            state.stashes
                .iter()
                .enumerate()
                .map(|(i, stash)| {
                    let style = if i == state.stash_selected {
                        selection_style
                    } else {
                        Style::default().fg(theme.untracked_color())
                    };
                    
                    ListItem::new(format!("{} - {}", stash.name, stash.message))
                        .style(style)
                })
                .collect()
        };
        
        let list = List::new(items)
            .style(style::body_style(theme))
            .block(style::pane_block(theme, "Stashes", false));
        
        frame.render_widget(list, area);
    }
    
    fn name(&self) -> &'static str {
        "StashPane"
    }
}