use crate::{ accessors, components::{ command_pump, event_pump, visibility_blocking, CommandBlocking, CommandInfo, Component, DrawableComponent, EventState, StatusTreeComponent, }, keys::{key_match, SharedKeyConfig}, queue::{InternalEvent, Queue}, strings, ui::style::SharedTheme, }; use anyhow::Result; use asyncgit::{ sync::{self, status::StatusType, RepoPathRef}, AsyncGitNotification, AsyncStatus, StatusParams, }; use crossbeam_channel::Sender; use crossterm::event::Event; use ratatui::{ layout::{Alignment, Constraint, Direction, Layout}, text::{Line, Span}, widgets::{Block, Borders, Paragraph}, }; use std::borrow::Cow; #[derive(Default, Clone, Copy, Debug)] pub struct StashingOptions { pub stash_untracked: bool, pub keep_index: bool, } pub struct Stashing { repo: RepoPathRef, index: StatusTreeComponent, visible: bool, options: StashingOptions, theme: SharedTheme, git_status: AsyncStatus, queue: Queue, key_config: SharedKeyConfig, } impl Stashing { accessors!(self, [index]); /// pub fn new( repo: &RepoPathRef, sender: &Sender, queue: &Queue, theme: SharedTheme, key_config: SharedKeyConfig, ) -> Self { Self { repo: repo.clone(), index: StatusTreeComponent::new( &strings::stashing_files_title(&key_config), true, Some(queue.clone()), theme.clone(), key_config.clone(), ), visible: false, options: StashingOptions { keep_index: false, stash_untracked: true, }, theme, git_status: AsyncStatus::new( repo.borrow().clone(), sender.clone(), ), queue: queue.clone(), key_config, } } /// pub fn update(&mut self) -> Result<()> { if self.is_visible() { self.git_status //TODO: support options .fetch(&StatusParams::new(StatusType::Both, None))?; } Ok(()) } /// pub fn anything_pending(&self) -> bool { self.git_status.is_pending() } /// pub fn update_git( &mut self, ev: AsyncGitNotification, ) -> Result<()> { if self.is_visible() && ev == AsyncGitNotification::Status { let status = self.git_status.last()?; self.index.show()?; self.index.update(&status.items)?; } Ok(()) } fn get_option_text(&self) -> Vec { let bracket_open = Span::raw(Cow::from("[")); let bracket_close = Span::raw(Cow::from("]")); let option_on = Span::styled(Cow::from("x"), self.theme.option(true)); let option_off = Span::styled(Cow::from("_"), self.theme.option(false)); vec![ Line::from(vec![ bracket_open.clone(), if self.options.stash_untracked { option_on.clone() } else { option_off.clone() }, bracket_close.clone(), Span::raw(Cow::from(" stash untracked")), ]), Line::from(vec![ bracket_open, if self.options.keep_index { option_on } else { option_off }, bracket_close, Span::raw(Cow::from(" keep index")), ]), ] } } impl DrawableComponent for Stashing { fn draw( &self, f: &mut ratatui::Frame, rect: ratatui::layout::Rect, ) -> Result<()> { let chunks = Layout::default() .direction(Direction::Horizontal) .constraints( [Constraint::Min(1), Constraint::Length(22)].as_ref(), ) .split(rect); let right_chunks = Layout::default() .direction(Direction::Vertical) .constraints( [Constraint::Length(4), Constraint::Min(1)].as_ref(), ) .split(chunks[1]); f.render_widget( Paragraph::new(self.get_option_text()) .block(Block::default().borders(Borders::ALL).title( strings::stashing_options_title(&self.key_config), )) .alignment(Alignment::Left), right_chunks[0], ); self.index.draw(f, chunks[0])?; Ok(()) } } impl Component for Stashing { fn commands( &self, out: &mut Vec, force_all: bool, ) -> CommandBlocking { if self.visible || force_all { command_pump( out, force_all, self.components().as_slice(), ); out.push(CommandInfo::new( strings::commands::stashing_save(&self.key_config), self.visible && !self.index.is_empty(), self.visible || force_all, )); out.push(CommandInfo::new( strings::commands::stashing_toggle_indexed( &self.key_config, ), self.visible, self.visible || force_all, )); out.push(CommandInfo::new( strings::commands::stashing_toggle_untracked( &self.key_config, ), self.visible, self.visible || force_all, )); } visibility_blocking(self) } fn event( &mut self, ev: &crossterm::event::Event, ) -> Result { if self.visible { if event_pump(ev, self.components_mut().as_mut_slice())? .is_consumed() { return Ok(EventState::Consumed); } if let Event::Key(k) = ev { return if key_match( k, self.key_config.keys.stashing_save, ) && !self.index.is_empty() { self.queue.push(InternalEvent::PopupStashing( self.options, )); Ok(EventState::Consumed) } else if key_match( k, self.key_config.keys.stashing_toggle_index, ) { self.options.keep_index = !self.options.keep_index; self.update()?; Ok(EventState::Consumed) } else if key_match( k, self.key_config.keys.stashing_toggle_untracked, ) { self.options.stash_untracked = !self.options.stash_untracked; self.update()?; Ok(EventState::Consumed) } else { Ok(EventState::NotConsumed) }; }; } Ok(EventState::NotConsumed) } fn is_visible(&self) -> bool { self.visible } fn hide(&mut self) { self.visible = false; } fn show(&mut self) -> Result<()> { let config_untracked_files = sync::untracked_files_config(&self.repo.borrow())?; self.options.stash_untracked = !config_untracked_files.include_none(); self.index.show()?; self.visible = true; self.update()?; Ok(()) } }