diff options
Diffstat (limited to 'src/tabs/status.rs')
-rw-r--r-- | src/tabs/status.rs | 1396 |
1 files changed, 697 insertions, 699 deletions
diff --git a/src/tabs/status.rs b/src/tabs/status.rs index 8625e52f..d766fcd1 100644 --- a/src/tabs/status.rs +++ b/src/tabs/status.rs @@ -1,23 +1,23 @@ use crate::{ - accessors, - components::{ - command_pump, event_pump, visibility_blocking, - ChangesComponent, CommandBlocking, CommandInfo, Component, - DiffComponent, DrawableComponent, EventState, - FileTreeItemKind, - }, - keys::SharedKeyConfig, - queue::{Action, InternalEvent, NeedsUpdate, Queue, ResetItem}, - strings, try_or_popup, - ui::style::SharedTheme, + accessors, + components::{ + command_pump, event_pump, visibility_blocking, + ChangesComponent, CommandBlocking, CommandInfo, Component, + DiffComponent, DrawableComponent, EventState, + FileTreeItemKind, + }, + keys::SharedKeyConfig, + queue::{Action, InternalEvent, NeedsUpdate, Queue, ResetItem}, + strings, try_or_popup, + ui::style::SharedTheme, }; use anyhow::Result; use asyncgit::{ - cached, - sync::BranchCompare, - sync::{self, status::StatusType, RepoState}, - AsyncDiff, AsyncGitNotification, AsyncStatus, DiffParams, - DiffType, StatusParams, CWD, + cached, + sync::BranchCompare, + sync::{self, status::StatusType, RepoState}, + AsyncDiff, AsyncGitNotification, AsyncStatus, DiffParams, + DiffType, StatusParams, CWD, }; use crossbeam_channel::Sender; use crossterm::event::Event; @@ -25,713 +25,711 @@ use itertools::Itertools; use std::convert::Into; use std::convert::TryFrom; use tui::{ - layout::{Alignment, Constraint, Direction, Layout}, - style::{Color, Style}, - widgets::Paragraph, + layout::{Alignment, Constraint, Direction, Layout}, + style::{Color, Style}, + widgets::Paragraph, }; /// what part of the screen is focused #[derive(PartialEq)] enum Focus { - WorkDir, - Diff, - Stage, + WorkDir, + Diff, + Stage, } /// focus can toggle between workdir and stage impl Focus { - const fn toggled_focus(&self) -> Self { - match self { - Self::WorkDir => Self::Stage, - Self::Stage => Self::WorkDir, - Self::Diff => Self::Diff, - } - } + const fn toggled_focus(&self) -> Self { + match self { + Self::WorkDir => Self::Stage, + Self::Stage => Self::WorkDir, + Self::Diff => Self::Diff, + } + } } /// which target are we showing a diff against #[derive(PartialEq, Copy, Clone)] enum DiffTarget { - Stage, - WorkingDir, + Stage, + WorkingDir, } pub struct Status { - visible: bool, - focus: Focus, - diff_target: DiffTarget, - index: ChangesComponent, - index_wd: ChangesComponent, - diff: DiffComponent, - git_diff: AsyncDiff, - git_status_workdir: AsyncStatus, - git_status_stage: AsyncStatus, - git_branch_state: Option<BranchCompare>, - git_branch_name: cached::BranchName, - queue: Queue, - git_action_executed: bool, - key_config: SharedKeyConfig, + visible: bool, + focus: Focus, + diff_target: DiffTarget, + index: ChangesComponent, + index_wd: ChangesComponent, + diff: DiffComponent, + git_diff: AsyncDiff, + git_status_workdir: AsyncStatus, + git_status_stage: AsyncStatus, + git_branch_state: Option<BranchCompare>, + git_branch_name: cached::BranchName, + queue: Queue, + git_action_executed: bool, + key_config: SharedKeyConfig, } impl DrawableComponent for Status { - fn draw<B: tui::backend::Backend>( - &self, - f: &mut tui::Frame<B>, - rect: tui::layout::Rect, - ) -> Result<()> { - let chunks = Layout::default() - .direction(Direction::Horizontal) - .constraints( - if self.focus == Focus::Diff { - [ - Constraint::Percentage(30), - Constraint::Percentage(70), - ] - } else { - [ - Constraint::Percentage(50), - Constraint::Percentage(50), - ] - } - .as_ref(), - ) - .split(rect); - - let left_chunks = Layout::default() - .direction(Direction::Vertical) - .constraints( - if self.diff_target == DiffTarget::WorkingDir { - [ - Constraint::Percentage(60), - Constraint::Percentage(40), - ] - } else { - [ - Constraint::Percentage(40), - Constraint::Percentage(60), - ] - } - .as_ref(), - ) - .split(chunks[0]); - - self.index_wd.draw(f, left_chunks[0])?; - self.index.draw(f, left_chunks[1])?; - self.diff.draw(f, chunks[1])?; - self.draw_branch_state(f, &left_chunks); - Self::draw_repo_state(f, left_chunks[0])?; - - Ok(()) - } + fn draw<B: tui::backend::Backend>( + &self, + f: &mut tui::Frame<B>, + rect: tui::layout::Rect, + ) -> Result<()> { + let chunks = Layout::default() + .direction(Direction::Horizontal) + .constraints( + if self.focus == Focus::Diff { + [ + Constraint::Percentage(30), + Constraint::Percentage(70), + ] + } else { + [ + Constraint::Percentage(50), + Constraint::Percentage(50), + ] + } + .as_ref(), + ) + .split(rect); + + let left_chunks = Layout::default() + .direction(Direction::Vertical) + .constraints( + if self.diff_target == DiffTarget::WorkingDir { + [ + Constraint::Percentage(60), + Constraint::Percentage(40), + ] + } else { + [ + Constraint::Percentage(40), + Constraint::Percentage(60), + ] + } + .as_ref(), + ) + .split(chunks[0]); + + self.index_wd.draw(f, left_chunks[0])?; + self.index.draw(f, left_chunks[1])?; + self.diff.draw(f, chunks[1])?; + self.draw_branch_state(f, &left_chunks); + Self::draw_repo_state(f, left_chunks[0])?; + + Ok(()) + } } impl Status { - accessors!(self, [index, index_wd, diff]); - - /// - pub fn new( - queue: &Queue, - sender: &Sender<AsyncGitNotification>, - theme: SharedTheme, - key_config: SharedKeyConfig, - ) -> Self { - Self { - queue: queue.clone(), - visible: true, - focus: Focus::WorkDir, - diff_target: DiffTarget::WorkingDir, - index_wd: ChangesComponent::new( - &strings::title_status(&key_config), - true, - true, - queue.clone(), - theme.clone(), - key_config.clone(), - ), - index: ChangesComponent::new( - &strings::title_index(&key_config), - false, - false, - queue.clone(), - theme.clone(), - key_config.clone(), - ), - diff: DiffComponent::new( - queue.clone(), - theme, - key_config.clone(), - false, - ), - git_diff: AsyncDiff::new(sender), - git_status_workdir: AsyncStatus::new(sender.clone()), - git_status_stage: AsyncStatus::new(sender.clone()), - git_action_executed: false, - git_branch_state: None, - git_branch_name: cached::BranchName::new(CWD), - key_config, - } - } - - fn draw_branch_state<B: tui::backend::Backend>( - &self, - f: &mut tui::Frame<B>, - chunks: &[tui::layout::Rect], - ) { - if let Some(branch_name) = self.git_branch_name.last() { - let ahead_behind = - if let Some(state) = &self.git_branch_state { - format!( - "\u{2191}{} \u{2193}{} ", - state.ahead, state.behind, - ) - } else { - String::new() - }; - let w = Paragraph::new(format!( - "{}{{{}}}", - ahead_behind, branch_name - )) - .alignment(Alignment::Right); - - let mut rect = if self.index_wd.focused() { - let mut rect = chunks[0]; - rect.y += rect.height.saturating_sub(1); - rect - } else { - chunks[1] - }; - - rect.x += 1; - rect.width = rect.width.saturating_sub(2); - rect.height = rect - .height - .saturating_sub(rect.height.saturating_sub(1)); - - f.render_widget(w, rect); - } - } - - fn draw_repo_state<B: tui::backend::Backend>( - f: &mut tui::Frame<B>, - r: tui::layout::Rect, - ) -> Result<()> { - if let Ok(state) = sync::repo_state(CWD) { - if state != RepoState::Clean { - let ids = - sync::mergehead_ids(CWD).unwrap_or_default(); - let ids = format!( - "({})", - ids.iter() - .map(|id| sync::CommitId::get_short_string( - id - )) - .join(",") - ); - let txt = format!("{:?} {}", state, ids); - let txt_len = u16::try_from(txt.len())?; - let w = Paragraph::new(txt) - .style(Style::default().fg(Color::Red)) - .alignment(Alignment::Left); - - let mut rect = r; - rect.x += 1; - rect.width = - rect.width.saturating_sub(2).min(txt_len); - rect.y += rect.height.saturating_sub(1); - rect.height = rect - .height - .saturating_sub(rect.height.saturating_sub(1)); - - f.render_widget(w, rect); - } - } - - Ok(()) - } - - fn can_focus_diff(&self) -> bool { - match self.focus { - Focus::WorkDir => self.index_wd.is_file_seleted(), - Focus::Stage => self.index.is_file_seleted(), - Focus::Diff => false, - } - } - - fn is_focus_on_diff(&self) -> bool { - self.focus == Focus::Diff - } - - fn switch_focus(&mut self, f: Focus) -> Result<bool> { - if self.focus != f { - self.focus = f; - - match self.focus { - Focus::WorkDir => { - self.set_diff_target(DiffTarget::WorkingDir); - self.diff.focus(false); - } - Focus::Stage => { - self.set_diff_target(DiffTarget::Stage); - self.diff.focus(false); - } - Focus::Diff => { - self.index.focus(false); - self.index_wd.focus(false); - - self.diff.focus(true); - } - }; - - self.update_diff()?; - - return Ok(true); - } - - Ok(false) - } - - fn set_diff_target(&mut self, target: DiffTarget) { - self.diff_target = target; - let is_stage = self.diff_target == DiffTarget::Stage; - - self.index_wd.focus_select(!is_stage); - self.index.focus_select(is_stage); - } - - pub fn selected_path(&self) -> Option<(String, bool)> { - let (idx, is_stage) = match self.diff_target { - DiffTarget::Stage => (&self.index, true), - DiffTarget::WorkingDir => (&self.index_wd, false), - }; - - if let Some(item) = idx.selection() { - if let FileTreeItemKind::File(i) = item.kind { - return Some((i.path, is_stage)); - } - } - None - } - - /// - pub fn update(&mut self) -> Result<()> { - self.git_branch_name.lookup().map(Some).unwrap_or(None); - - if self.is_visible() { - self.git_diff.refresh()?; - self.git_status_workdir - .fetch(&StatusParams::new(StatusType::WorkingDir))?; - self.git_status_stage - .fetch(&StatusParams::new(StatusType::Stage))?; - - self.branch_compare(); - } - - Ok(()) - } - - /// - pub fn anything_pending(&self) -> bool { - self.git_diff.is_pending() - || self.git_status_stage.is_pending() - || self.git_status_workdir.is_pending() - } - - /// - pub fn update_git( - &mut self, - ev: AsyncGitNotification, - ) -> Result<()> { - match ev { - AsyncGitNotification::Diff => self.update_diff()?, - AsyncGitNotification::Status => self.update_status()?, - AsyncGitNotification::Push - | AsyncGitNotification::Fetch - | AsyncGitNotification::CommitFiles => { - self.branch_compare(); - } - _ => (), - } - - Ok(()) - } - - fn update_status(&mut self) -> Result<()> { - let stage_status = self.git_status_stage.last()?; - self.index.set_items(&stage_status.items)?; - - let workdir_status = self.git_status_workdir.last()?; - self.index_wd.set_items(&workdir_status.items)?; - - self.update_diff()?; - - if self.git_action_executed { - self.git_action_executed = false; - - if self.focus == Focus::WorkDir - && workdir_status.items.is_empty() - && !stage_status.items.is_empty() - { - self.switch_focus(Focus::Stage)?; - } else if self.focus == Focus::Stage - && stage_status.items.is_empty() - { - self.switch_focus(Focus::WorkDir)?; - } - } - - Ok(()) - } - - /// - pub fn update_diff(&mut self) -> Result<()> { - if let Some((path, is_stage)) = self.selected_path() { - let diff_type = if is_stage { - DiffType::Stage - } else { - DiffType::WorkDir - }; - - let diff_params = DiffParams { - path: path.clone(), - diff_type, - }; - - if self.diff.current() == (path.clone(), is_stage) { - // we are already showing a diff of the right file - // maybe the diff changed (outside file change) - if let Some((params, last)) = self.git_diff.last()? { - if params == diff_params { - self.diff.update(path, is_stage, last); - } - } - } else { - // we dont show the right diff right now, so we need to request - if let Some(diff) = - self.git_diff.request(diff_params)? - { - self.diff.update(path, is_stage, diff); - } else { - self.diff.clear(true); - } - } - } else { - self.diff.clear(false); - } - - Ok(()) - } - - /// called after confirmation - pub fn reset(&mut self, item: &ResetItem) -> bool { - if let Err(e) = sync::reset_workdir(CWD, item.path.as_str()) { - self.queue.push(InternalEvent::ShowErrorMsg(format!( - "reset failed:\n{}", - e - ))); - - false - } else { - true - } - } - - pub fn last_file_moved(&mut self) -> Result<()> { - if !self.is_focus_on_diff() && self.is_visible() { - self.switch_focus(self.focus.toggled_focus())?; - } - Ok(()) - } - - fn push(&self, force: bool) { - if self.can_push() { - if let Some(branch) = self.git_branch_name.last() { - if force { - self.queue.push(InternalEvent::ConfirmAction( - Action::ForcePush(branch, force), - )); - } else { - self.queue.push(InternalEvent::Push( - branch, force, false, - )); - } - } - } - } - - fn pull(&self) { - if let Some(branch) = self.git_branch_name.last() { - self.queue.push(InternalEvent::Pull(branch)); - } - } - - fn undo_last_commit(&self) { - try_or_popup!( - self, - "undo commit failed:", - sync::utils::undo_last_commit(CWD) - ); - } - - fn branch_compare(&mut self) { - self.git_branch_state = - self.git_branch_name.last().and_then(|branch| { - sync::branch_compare_upstream(CWD, branch.as_str()) - .ok() - }); - } - - fn can_push(&self) -> bool { - self.git_branch_state - .as_ref() - .map_or(true, |state| state.ahead > 0) - } - - fn can_abort_merge() -> bool { - sync::repo_state(CWD).unwrap_or(RepoState::Clean) - == RepoState::Merge - } - - pub fn abort_merge(&self) { - try_or_popup!(self, "abort merge", sync::abort_merge(CWD)); - } - - fn commands_nav( - &self, - out: &mut Vec<CommandInfo>, - force_all: bool, - ) { - let focus_on_diff = self.is_focus_on_diff(); - out.push( - CommandInfo::new( - strings::commands::diff_focus_left(&self.key_config), - true, - (self.visible && focus_on_diff) || force_all, - ) - .order(strings::order::NAV), - ); - out.push( - CommandInfo::new( - strings::commands::diff_focus_right(&self.key_config), - self.can_focus_diff(), - (self.visible && !focus_on_diff) || force_all, - ) - .order(strings::order::NAV), - ); - out.push( - CommandInfo::new( - strings::commands::select_staging(&self.key_config), - !focus_on_diff, - (self.visible - && !focus_on_diff - && self.focus == Focus::WorkDir) - || force_all, - ) - .order(strings::order::NAV), - ); - out.push( - CommandInfo::new( - strings::commands::select_unstaged(&self.key_config), - !focus_on_diff, - (self.visible - && !focus_on_diff - && self.focus == Focus::Stage) - || force_all, - ) - .order(strings::order::NAV), - ); - } + accessors!(self, [index, index_wd, diff]); + + /// + pub fn new( + queue: &Queue, + sender: &Sender<AsyncGitNotification>, + theme: SharedTheme, + key_config: SharedKeyConfig, + ) -> Self { + Self { + queue: queue.clone(), + visible: true, + focus: Focus::WorkDir, + diff_target: DiffTarget::WorkingDir, + index_wd: ChangesComponent::new( + &strings::title_status(&key_config), + true, + true, + queue.clone(), + theme.clone(), + key_config.clone(), + ), + index: ChangesComponent::new( + &strings::title_index(&key_config), + false, + false, + queue.clone(), + theme.clone(), + key_config.clone(), + ), + diff: DiffComponent::new( + queue.clone(), + theme, + key_config.clone(), + false, + ), + git_diff: AsyncDiff::new(sender), + git_status_workdir: AsyncStatus::new(sender.clone()), + git_status_stage: AsyncStatus::new(sender.clone()), + git_action_executed: false, + git_branch_state: None, + git_branch_name: cached::BranchName::new(CWD), + key_config, + } + } + + fn draw_branch_state<B: tui::backend::Backend>( + &self, + f: &mut tui::Frame<B>, + chunks: &[tui::layout::Rect], + ) { + if let Some(branch_name) = self.git_branch_name.last() { + let ahead_behind = + if let Some(state) = &self.git_branch_state { + format!( + "\u{2191}{} \u{2193}{} ", + state.ahead, state.behind, + ) + } else { + String::new() + }; + let w = Paragraph::new(format!( + "{}{{{}}}", + ahead_behind, branch_name + )) + .alignment(Alignment::Right); + + let mut rect = if self.index_wd.focused() { + let mut rect = chunks[0]; + rect.y += rect.height.saturating_sub(1); + rect + } else { + chunks[1] + }; + + rect.x += 1; + rect.width = rect.width.saturating_sub(2); + rect.height = rect + .height + .saturating_sub(rect.height.saturating_sub(1)); + + f.render_widget(w, rect); + } + } + + fn draw_repo_state<B: tui::backend::Backend>( + f: &mut tui::Frame<B>, + r: tui::layout::Rect, + ) -> Result<()> { + if let Ok(state) = sync::repo_state(CWD) { + if state != RepoState::Clean { + let ids = + sync::mergehead_ids(CWD).unwrap_or_default(); + let ids = format!( + "({})", + ids.iter() + .map(|id| sync::CommitId::get_short_string( + id + )) + .join(",") + ); + let txt = format!("{:?} {}", state, ids); + let txt_len = u16::try_from(txt.len())?; + let w = Paragraph::new(txt) + .style(Style::default().fg(Color::Red)) + .alignment(Alignment::Left); + + let mut rect = r; + rect.x += 1; + rect.width = + rect.width.saturating_sub(2).min(txt_len); + rect.y += rect.height.saturating_sub(1); + rect.height = rect + .height + .saturating_sub(rect.height.saturating_sub(1)); + + f.render_widget(w, rect); + } + } + + Ok(()) + } + + fn can_focus_diff(&self) -> bool { + match self.focus { + Focus::WorkDir => self.index_wd.is_file_seleted(), + Focus::Stage => self.index.is_file_seleted(), + Focus::Diff => false, + } + } + + fn is_focus_on_diff(&self) -> bool { + self.focus == Focus::Diff + } + + fn switch_focus(&mut self, f: Focus) -> Result<bool> { + if self.focus != f { + self.focus = f; + + match self.focus { + Focus::WorkDir => { + self.set_diff_target(DiffTarget::WorkingDir); + self.diff.focus(false); + } + Focus::Stage => { + self.set_diff_target(DiffTarget::Stage); + self.diff.focus(false); + } + Focus::Diff => { + self.index.focus(false); + self.index_wd.focus(false); + + self.diff.focus(true); + } + }; + + self.update_diff()?; + + return Ok(true); + } + + Ok(false) + } + + fn set_diff_target(&mut self, target: DiffTarget) { + self.diff_target = target; + let is_stage = self.diff_target == DiffTarget::Stage; + + self.index_wd.focus_select(!is_stage); + self.index.focus_select(is_stage); + } + + pub fn selected_path(&self) -> Option<(String, bool)> { + let (idx, is_stage) = match self.diff_target { + DiffTarget::Stage => (&self.index, true), + DiffTarget::WorkingDir => (&self.index_wd, false), + }; + + if let Some(item) = idx.selection() { + if let FileTreeItemKind::File(i) = item.kind { + return Some((i.path, is_stage)); + } + } + None + } + + /// + pub fn update(&mut self) -> Result<()> { + self.git_branch_name.lookup().map(Some).unwrap_or(None); + + if self.is_visible() { + self.git_diff.refresh()?; + self.git_status_workdir + .fetch(&StatusParams::new(StatusType::WorkingDir))?; + self.git_status_stage + .fetch(&StatusParams::new(StatusType::Stage))?; + + self.branch_compare(); + } + + Ok(()) + } + + /// + pub fn anything_pending(&self) -> bool { + self.git_diff.is_pending() + || self.git_status_stage.is_pending() + || self.git_status_workdir.is_pending() + } + + /// + pub fn update_git( + &mut self, + ev: AsyncGitNotification, + ) -> Result<()> { + match ev { + AsyncGitNotification::Diff => self.update_diff()?, + AsyncGitNotification::Status => self.update_status()?, + AsyncGitNotification::Push + | AsyncGitNotification::Fetch + | AsyncGitNotification::CommitFiles => { + self.branch_compare(); + } + _ => (), + } + + Ok(()) + } + + fn update_status(&mut self) -> Result<()> { + let stage_status = self.git_status_stage.last()?; + self.index.set_items(&stage_status.items)?; + + let workdir_status = self.git_status_workdir.last()?; + self.index_wd.set_items(&workdir_status.items)?; + + self.update_diff()?; + + if self.git_action_executed { + self.git_action_executed = false; + + if self.focus == Focus::WorkDir + && workdir_status.items.is_empty() + && !stage_status.items.is_empty() + { + self.switch_focus(Focus::Stage)?; + } else if self.focus == Focus::Stage + && stage_status.items.is_empty() + { + self.switch_focus(Focus::WorkDir)?; + } + } + + Ok(()) + } + + /// + pub fn update_diff(&mut self) -> Result<()> { + if let Some((path, is_stage)) = self.selected_path() { + let diff_type = if is_stage { + DiffType::Stage + } else { + DiffType::WorkDir + }; + + let diff_params = DiffParams { + path: path.clone(), + diff_type, + }; + + if self.diff.current() == (path.clone(), is_stage) { + // we are already showing a diff of the right file + // maybe the diff changed (outside file change) + if let Some((params, last)) = self.git_diff.last()? { + if params == diff_params { + self.diff.update(path, is_stage, last); + } + } + } else { + // we dont show the right diff right now, so we need to request + if let Some(diff) = + self.git_diff.request(diff_params)? + { + self.diff.update(path, is_stage, diff); + } else { + self.diff.clear(true); + } + } + } else { + self.diff.clear(false); + } + + Ok(()) + } + + /// called after confirmation + pub fn reset(&mut self, item: &ResetItem) -> bool { + if let Err(e) = sync::reset_workdir(CWD, item.path.as_str()) { + self.queue.push(InternalEvent::ShowErrorMsg(format!( + "reset failed:\n{}", + e + ))); + + false + } else { + true + } + } + + pub fn last_file_moved(&mut self) -> Result<()> { + if !self.is_focus_on_diff() && self.is_visible() { + self.switch_focus(self.focus.toggled_focus())?; + } + Ok(()) + } + + fn push(&self, force: bool) { + if self.can_push() { + if let Some(branch) = self.git_branch_name.last() { + if force { + self.queue.push(InternalEvent::ConfirmAction( + Action::ForcePush(branch, force), + )); + } else { + self.queue.push(InternalEvent::Push( + branch, force, false, + )); + } + } + } + } + + fn pull(&self) { + if let Some(branch) = self.git_branch_name.last() { + self.queue.push(InternalEvent::Pull(branch)); + } + } + + fn undo_last_commit(&self) { + try_or_popup!( + self, + "undo commit failed:", + sync::utils::undo_last_commit(CWD) + ); + } + + fn branch_compare(&mut self) { + self.git_branch_state = + self.git_branch_name.last().and_then(|branch| { + sync::branch_compare_upstream(CWD, branch.as_str()) + .ok() + }); + } + + fn can_push(&self) -> bool { + self.git_branch_state + .as_ref() + .map_or(true, |state| state.ahead > 0) + } + + fn can_abort_merge() -> bool { + sync::repo_state(CWD).unwrap_or(RepoState::Clean) + == RepoState::Merge + } + + pub fn abort_merge(&self) { + try_or_popup!(self, "abort merge", sync::abort_merge(CWD)); + } + + fn commands_nav( + &self, + out: &mut Vec<CommandInfo>, + force_all: bool, + ) { + let focus_on_diff = self.is_focus_on_diff(); + out.push( + CommandInfo::new( + strings::commands::diff_focus_left(&self.key_config), + true, + (self.visible && focus_on_diff) || force_all, + ) + .order(strings::order::NAV), + ); + out.push( + CommandInfo::new( + strings::commands::diff_focus_right(&self.key_config), + self.can_focus_diff(), + (self.visible && !focus_on_diff) || force_all, + ) + .order(strings::order::NAV), + ); + out.push( + CommandInfo::new( + strings::commands::select_staging(&self.key_config), + !focus_on_diff, + (self.visible + && !focus_on_diff && self.focus == Focus::WorkDir) + || force_all, + ) + .order(strings::order::NAV), + ); + out.push( + CommandInfo::new( + strings::commands::select_unstaged(&self.key_config), + !focus_on_diff, + (self.visible + && !focus_on_diff && self.focus == Focus::Stage) + || force_all, + ) + .order(strings::order::NAV), + ); + } } impl Component for Status { - fn commands( - &self, - out: &mut Vec<CommandInfo>, - force_all: bool, - ) -> CommandBlocking { - let focus_on_diff = self.is_focus_on_diff(); - - if self.visible || force_all { - command_pump( - out, - force_all, - self.components().as_slice(), - ); - - out.push(CommandInfo::new( - strings::commands::open_branch_select_popup( - &self.key_config, - ), - true, - !focus_on_diff, - )); - - out.push(CommandInfo::new( - strings::commands::status_push(&self.key_config), - self.can_push(), - !focus_on_diff, - )); - out.push(CommandInfo::new( - strings::commands::status_force_push( - &self.key_config, - ), - true, - self.can_push() && !focus_on_diff, - )); - out.push(CommandInfo::new( - strings::commands::status_pull(&self.key_config), - true, - !focus_on_diff, - )); - - out.push(CommandInfo::new( - strings::commands::undo_commit(&self.key_config), - true, - !focus_on_diff, - )); - - out.push(CommandInfo::new( - strings::commands::abort_merge(&self.key_config), - true, - Self::can_abort_merge() || force_all, - )); - } - - { - out.push(CommandInfo::new( - strings::commands::edit_item(&self.key_config), - if focus_on_diff { - true - } else { - self.can_focus_diff() - }, - self.visible || force_all, - )); - - out.push( - CommandInfo::new( - strings::commands::select_status( - &self.key_config, - ), - true, - (self.visible && !focus_on_diff) || force_all, - ) - .hidden(), - ); - - self.commands_nav(out, force_all); - } - - visibility_blocking(self) - } - - fn event( - &mut self, - ev: crossterm::event::Event, - ) -> Result<EventState> { - if self.visible { - if event_pump(ev, self.components_mut().as_mut_slice())? - .is_consumed() - { - self.git_action_executed = true; - return Ok(EventState::Consumed); - } - - if let Event::Key(k) = ev { - return if k == self.key_config.edit_file - && (self.can_focus_diff() |