diff options
author | Stephan Dilly <dilly.stephan@gmail.com> | 2020-05-11 14:45:37 +0200 |
---|---|---|
committer | Stephan Dilly <dilly.stephan@gmail.com> | 2020-05-11 17:20:34 +0200 |
commit | 0e9ba8aef6e7f18bb6deb7d7d6623ca51cbddf28 (patch) | |
tree | 94291df91f2105ad36a05a792acb962ac6c9e75f | |
parent | fa2aabfee0f16e0517aaa364eb2158eab3966b2c (diff) |
Move status tab into its own component
-rw-r--r-- | .vscode/launch.json | 13 | ||||
-rw-r--r-- | src/app.rs | 372 | ||||
-rw-r--r-- | src/components/mod.rs | 32 | ||||
-rw-r--r-- | src/components/reset.rs | 12 | ||||
-rw-r--r-- | src/tabs/mod.rs | 4 | ||||
-rw-r--r-- | src/tabs/revlog/mod.rs (renamed from src/tabs/revlog.rs) | 114 | ||||
-rw-r--r-- | src/tabs/revlog/utils.rs | 63 | ||||
-rw-r--r-- | src/tabs/status.rs | 368 |
8 files changed, 558 insertions, 420 deletions
diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..4f8d5082 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,13 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "(OSX) Launch", + "type": "lldb", + "request": "launch", + "program": "${workspaceRoot}/target/debug/gitui", + "args": [], + "cwd": "${workspaceRoot}", + } + ] +}
\ No newline at end of file @@ -1,19 +1,16 @@ use crate::{ + accessors, components::{ - ChangesComponent, CommandBlocking, CommandInfo, - CommitComponent, Component, DiffComponent, DrawableComponent, - FileTreeItemKind, HelpComponent, MsgComponent, + event_pump, CommandBlocking, CommandInfo, CommitComponent, + Component, DrawableComponent, HelpComponent, MsgComponent, ResetComponent, }, keys, queue::{InternalEvent, NeedsUpdate, Queue}, strings, - tabs::Revlog, -}; -use asyncgit::{ - current_tick, sync, AsyncDiff, AsyncNotification, AsyncStatus, - DiffParams, CWD, + tabs::{Revlog, Status}, }; +use asyncgit::{sync, AsyncNotification, CWD}; use crossbeam_channel::Sender; use crossterm::event::Event; use itertools::Itertools; @@ -29,55 +26,16 @@ use tui::{ }; /// -#[derive(PartialEq)] -enum DiffTarget { - Stage, - WorkingDir, -} - -/// -#[derive(PartialEq)] -enum Focus { - WorkDir, - Diff, - Stage, -} - -/// allows generating code to make sure -/// we always enumerate all components in both getter functions -macro_rules! components { - ($self:ident, [$($element:ident),+]) => { - fn components(& $self) -> Vec<&dyn Component> { - vec![ - $(&$self.$element,)+ - ] - } - - fn components_mut(&mut $self) -> Vec<&mut dyn Component> { - vec![ - $(&mut $self.$element,)+ - ] - } - }; -} - -/// pub struct App { - focus: Focus, - diff_target: DiffTarget, do_quit: bool, - reset: ResetComponent, - commit: CommitComponent, help: HelpComponent, - index: ChangesComponent, - index_wd: ChangesComponent, - diff: DiffComponent, msg: MsgComponent, - git_diff: AsyncDiff, - git_status: AsyncStatus, + reset: ResetComponent, + commit: CommitComponent, current_commands: Vec<CommandInfo>, tab: usize, revlog: Revlog, + status_tab: Status, queue: Queue, } @@ -87,31 +45,15 @@ impl App { pub fn new(sender: &Sender<AsyncNotification>) -> Self { let queue = Queue::default(); Self { - focus: Focus::WorkDir, - diff_target: DiffTarget::WorkingDir, - do_quit: false, reset: ResetComponent::new(queue.clone()), commit: CommitComponent::new(queue.clone()), + do_quit: false, + current_commands: Vec::new(), help: HelpComponent::default(), - index_wd: ChangesComponent::new( - strings::TITLE_STATUS, - true, - true, - queue.clone(), - ), - index: ChangesComponent::new( - strings::TITLE_INDEX, - false, - false, - queue.clone(), - ), - diff: DiffComponent::new(queue.clone()), msg: MsgComponent::default(), - git_diff: AsyncDiff::new(sender.clone()), - git_status: AsyncStatus::new(sender.clone()), - current_commands: Vec::new(), tab: 0, revlog: Revlog::new(&sender), + status_tab: Status::new(&sender, &queue), queue, } } @@ -142,7 +84,7 @@ impl App { ); if self.tab == 0 { - self.draw_status_tab(f, chunks_main[1]); + self.status_tab.draw(f, chunks_main[1]); } else { self.revlog.draw(f, chunks_main[1]); } @@ -162,40 +104,23 @@ impl App { let mut flags = NeedsUpdate::empty(); - let event_used = if self.tab == 0 { - Self::event_pump(ev, self.components_mut().as_mut_slice()) - } else { - self.revlog.event(ev) - }; - - if event_used { + if event_pump(ev, self.components_mut().as_mut_slice()) { flags.insert(NeedsUpdate::COMMANDS); } else if let Event::Key(k) = ev { let new_flags = match k { - keys::FOCUS_WORKDIR => { - self.switch_focus(Focus::WorkDir) - } - keys::FOCUS_STAGE => self.switch_focus(Focus::Stage), - keys::FOCUS_RIGHT if self.can_focus_diff() => { - self.switch_focus(Focus::Diff) - } - keys::FOCUS_LEFT => { - self.switch_focus(match self.diff_target { - DiffTarget::Stage => Focus::Stage, - DiffTarget::WorkingDir => Focus::WorkDir, - }) - } + //TODO: move into status tab keys::OPEN_COMMIT - if !self.index.is_empty() - && self.offer_open_commit_cmd() => + if self.status_tab.offer_open_commit_cmd() => { self.commit.show(); NeedsUpdate::COMMANDS } + keys::TAB_TOGGLE => { self.toggle_tabs(); NeedsUpdate::COMMANDS } + _ => NeedsUpdate::empty(), }; @@ -211,28 +136,31 @@ impl App { self.update(); } if flags.contains(NeedsUpdate::DIFF) { - self.update_diff(); + self.status_tab.update_diff(); } if flags.contains(NeedsUpdate::COMMANDS) { self.update_commands(); } } + //TODO: do we need this? /// pub fn update(&mut self) { trace!("update"); - - self.git_diff.refresh(); - self.git_status.fetch(current_tick()); + self.status_tab.update(); } /// pub fn update_git(&mut self, ev: AsyncNotification) { trace!("update_git: {:?}", ev); + + self.status_tab.update_git(ev); + match ev { - AsyncNotification::Diff => self.update_diff(), - AsyncNotification::Status => self.update_status(), + AsyncNotification::Diff => (), AsyncNotification::Log => self.revlog.update(), + //TODO: is that needed? + AsyncNotification::Status => self.update_commands(), } } @@ -243,58 +171,14 @@ impl App { /// pub fn any_work_pending(&self) -> bool { - self.git_diff.is_pending() - || self.git_status.is_pending() + self.status_tab.anything_pending() || self.revlog.any_work_pending() } } // private impls impl App { - components!( - self, - [msg, reset, commit, help, index, index_wd, diff] - ); - - fn update_diff(&mut self) { - if let Some((path, is_stage)) = self.selected_path() { - let diff_params = DiffParams(path.clone(), is_stage); - - 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(); - } - } - } else { - self.diff.clear(); - } - } - - 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 - } + accessors!(self, [msg, reset, commit, help, revlog, status_tab]); fn check_quit(&mut self, ev: Event) { if let Event::Key(e) = ev { @@ -309,35 +193,20 @@ impl App { self.tab %= 2; if self.tab == 1 { + self.status_tab.hide(); self.revlog.show(); } else { + self.status_tab.show(); self.revlog.hide(); } } - fn can_focus_diff(&self) -> bool { - match self.focus { - Focus::WorkDir => self.index_wd.is_file_seleted(), - Focus::Stage => self.index.is_file_seleted(), - _ => false, - } - } - fn update_commands(&mut self) { self.help.set_cmds(self.commands(true)); self.current_commands = self.commands(false); self.current_commands.sort_by_key(|e| e.order); } - fn update_status(&mut self) { - let status = self.git_status.last(); - self.index.update(&status.stage); - self.index_wd.update(&status.work_dir); - - self.update_diff(); - self.update_commands(); - } - fn process_queue(&mut self) -> NeedsUpdate { let mut flags = NeedsUpdate::empty(); loop { @@ -379,7 +248,9 @@ impl App { flags.insert(NeedsUpdate::COMMANDS); } InternalEvent::AddHunk(hash) => { - if let Some((path, is_stage)) = self.selected_path() { + if let Some((path, is_stage)) = + self.status_tab.selected_path() + { if is_stage { if sync::unstage_hunk(CWD, path, hash) { flags.insert(NeedsUpdate::ALL); @@ -402,23 +273,13 @@ impl App { fn commands(&self, force_all: bool) -> Vec<CommandInfo> { let mut res = Vec::new(); - if self.revlog.is_visible() { - self.revlog.commands(&mut res, force_all); - } else { - for c in self.components() { - if c.commands(&mut res, force_all) - != CommandBlocking::PassingOn - && !force_all - { - break; - } + for c in self.components() { + if c.commands(&mut res, force_all) + != CommandBlocking::PassingOn + && !force_all + { + break; } - - //TODO: move into status tab component - self.add_commands_status_tab( - &mut res, - !self.any_popup_visible(), - ); } res.push( @@ -442,80 +303,6 @@ impl App { res } - fn offer_open_commit_cmd(&self) -> bool { - !self.commit.is_visible() - && self.diff_target == DiffTarget::Stage - } - - fn event_pump( - ev: Event, - components: &mut [&mut dyn Component], - ) -> bool { - for c in components { - if c.event(ev) { - return true; - } - } - - false - } - - fn add_commands_status_tab( - &self, - res: &mut Vec<CommandInfo>, - main_cmds_available: bool, - ) { - { - let focus_on_diff = self.focus == Focus::Diff; - res.push(CommandInfo::new( - commands::STATUS_FOCUS_LEFT, - true, - main_cmds_available && focus_on_diff, - )); - res.push(CommandInfo::new( - commands::STATUS_FOCUS_RIGHT, - self.can_focus_diff(), - main_cmds_available && !focus_on_diff, - )); - } - - res.push( - CommandInfo::new( - commands::COMMIT_OPEN, - !self.index.is_empty(), - main_cmds_available && self.offer_open_commit_cmd(), - ) - .order(-1), - ); - - res.push( - CommandInfo::new( - commands::SELECT_STATUS, - true, - main_cmds_available && self.focus == Focus::Diff, - ) - .hidden(), - ); - - res.push( - CommandInfo::new( - commands::SELECT_STAGING, - true, - main_cmds_available && self.focus == Focus::WorkDir, - ) - .order(-2), - ); - - res.push( - CommandInfo::new( - commands::SELECT_UNSTAGED, - true, - main_cmds_available && self.focus == Focus::Stage, - ) - .order(-2), - ); - } - fn any_popup_visible(&self) -> bool { self.commit.is_visible() || self.help.is_visible() @@ -532,52 +319,6 @@ impl App { self.msg.draw(f, size); } - fn draw_status_tab<B: Backend>( - &self, - f: &mut Frame<B>, - area: Rect, - ) { - 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(area); - - 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]); - } - fn draw_commands<B: Backend>( f: &mut Frame<B>, r: Rect, @@ -617,39 +358,4 @@ impl App { r, ); } - - fn switch_focus(&mut self, f: Focus) -> NeedsUpdate { - if self.focus == f { - NeedsUpdate::empty() - } else { - 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); - } - }; - - NeedsUpdate::DIFF | NeedsUpdate::COMMANDS - } - } - - 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); - } } diff --git a/src/components/mod.rs b/src/components/mod.rs index 5193260e..dd3ea927 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -19,6 +19,38 @@ pub use help::HelpComponent; pub use msg::MsgComponent; pub use reset::ResetComponent; +/// allows generating code to make sure +/// we always enumerate all components in both getter functions +#[macro_export] +macro_rules! accessors { + ($self:ident, [$($element:ident),+]) => { + fn components(& $self) -> Vec<&dyn Component> { + vec![ + $(&$self.$element,)+ + ] + } + + fn components_mut(&mut $self) -> Vec<&mut dyn Component> { + vec![ + $(&mut $self.$element,)+ + ] + } + }; +} + +pub fn event_pump( + ev: Event, + components: &mut [&mut dyn Component], +) -> bool { + for c in components { + if c.event(ev) { + return true; + } + } + + false +} + #[derive(Copy, Clone)] pub enum ScrollType { Up, diff --git a/src/components/reset.rs b/src/components/reset.rs index d161ca02..491a7c08 100644 --- a/src/components/reset.rs +++ b/src/components/reset.rs @@ -7,7 +7,7 @@ use crate::{ strings, ui, }; -use crossterm::event::{Event, KeyCode}; +use crossterm::event::{Event, KeyCode, KeyModifiers}; use std::borrow::Cow; use strings::commands; use tui::{ @@ -74,16 +74,24 @@ impl Component for ResetComponent { if self.visible { if let Event::Key(e) = ev { return match e.code { + KeyCode::Char(c) => { + // ignore and early out on ctrl+c + !(c == 'c' + && e.modifiers + .contains(KeyModifiers::CONTROL)) + } + KeyCode::Esc => { self.hide(); true } + KeyCode::Enter => { self.confirm(); true } - _ => false, + _ => true, }; } } diff --git a/src/tabs/mod.rs b/src/tabs/mod.rs index 5312baeb..c2054113 100644 --- a/src/tabs/mod.rs +++ b/src/tabs/mod.rs @@ -1,5 +1,5 @@ mod revlog; - -//TODO: tab traits? +mod status; pub use revlog::Revlog; +pub use status::Status; diff --git a/src/tabs/revlog.rs b/src/tabs/revlog/mod.rs index 53bd0610..6a49878d 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog/mod.rs @@ -1,3 +1,5 @@ +mod utils; + use crate::{ components::{ CommandBlocking, CommandInfo, Component, ScrollType, @@ -6,11 +8,10 @@ use crate::{ strings::commands, }; use asyncgit::{sync, AsyncLog, AsyncNotification, CWD}; -use chrono::prelude::*; use crossbeam_channel::Sender; use crossterm::event::Event; use std::{borrow::Cow, cmp, convert::TryFrom, time::Instant}; -use sync::{CommitInfo, Tags}; +use sync::Tags; use tui::{ backend::Backend, layout::{Alignment, Rect}, @@ -18,30 +19,7 @@ use tui::{ widgets::{Block, Borders, Paragraph, Text}, Frame, }; - -#[derive(Default)] -struct LogEntry { - time: String, - author: String, - msg: String, - hash: String, -} - -impl From<CommitInfo> for LogEntry { - fn from(c: CommitInfo) -> Self { - let time = - DateTime::<Local>::from(DateTime::<Utc>::from_utc( - NaiveDateTime::from_timestamp(c.time, 0), - Utc, - )); - Self { - author: c.author, - msg: c.message, - time: time.format("%Y-%m-%d %H:%M:%S").to_string(), - hash: c.hash, - } - } -} +use utils::{ItemBatch, LogEntry}; const COLOR_SELECTION_BG: Color = Color::Blue; @@ -64,42 +42,6 @@ const STYLE_MSG_SELECTED: Style = static ELEMENTS_PER_LINE: usize = 10; static SLICE_SIZE: usize = 1200; -static SLICE_OFFSET_RELOAD_THRESHOLD: usize = 100; - -/// -#[derive(Default)] -struct ItemBatch { - index_offset: usize, - items: Vec<LogEntry>, -} - -impl ItemBatch { - fn last_idx(&self) -> usize { - self.index_offset + self.items.len() - } - - fn set_items( - &mut self, - start_index: usize, - commits: Vec<CommitInfo>, - ) { - self.items.clear(); - self.items.extend(commits.into_iter().map(LogEntry::from)); - self.index_offset = start_index; - } - - fn needs_data(&self, idx: usize, idx_max: usize) -> bool { - let want_min = - idx.saturating_sub(SLICE_OFFSET_RELOAD_THRESHOLD); - let want_max = idx - .saturating_add(SLICE_OFFSET_RELOAD_THRESHOLD) - .min(idx_max); - - let needs_data_top = want_min < self.index_offset; - let needs_data_bottom = want_max > self.last_idx(); - needs_data_bottom || needs_data_top - } -} /// pub struct Revlog { @@ -320,26 +262,28 @@ impl Revlog { impl Component for Revlog { fn event(&mut self, ev: Event) -> bool { - if let Event::Key(k) = ev { - return match k { - keys::MOVE_UP => { - self.move_selection(ScrollType::Up); - true - } - keys::MOVE_DOWN => { - self.move_selection(ScrollType::Down); - true - } - keys::SHIFT_UP | keys::HOME => { - self.move_selection(ScrollType::Home); - true - } - keys::SHIFT_DOWN | keys::END => { - self.move_selection(ScrollType::End); - true - } - _ => false, - }; + if self.visible { + if let Event::Key(k) = ev { + return match k { + keys::MOVE_UP => { + self.move_selection(ScrollType::Up); + true + } + keys::MOVE_DOWN => { + self.move_selection(ScrollType::Down); + true + } + keys::SHIFT_UP | keys::HOME => { + self.move_selection(ScrollType::Home); + true + } + keys::SHIFT_DOWN | keys::END => { + self.move_selection(ScrollType::End); + true + } + _ => false, + }; + } } false @@ -356,7 +300,11 @@ impl Component for Revlog { self.visible || force_all, )); - CommandBlocking::PassingOn + if self.visible { + CommandBlocking::Blocking + } else { + CommandBlocking::PassingOn + } } fn is_visible(&self) -> bool { diff --git a/src/tabs/revlog/utils.rs b/src/tabs/revlog/utils.rs new file mode 100644 index 00000000..5a40f1e8 --- /dev/null +++ b/src/tabs/revlog/utils.rs @@ -0,0 +1,63 @@ +use asyncgit::sync::CommitInfo; +use chrono::prelude::*; + +static SLICE_OFFSET_RELOAD_THRESHOLD: usize = 100; + +#[derive(Default)] +pub(super) struct LogEntry { + pub time: String, + pub author: String, + pub msg: String, + pub hash: String, +} + +impl From<CommitInfo> for LogEntry { + fn from(c: CommitInfo) -> Self { + let time = + DateTime::<Local>::from(DateTime::<Utc>::from_utc( + NaiveDateTime::from_timestamp(c.time, 0), + Utc, + )); + Self { + author: c.author, + msg: c.message, + time: time.format("%Y-%m-%d %H:%M:%S").to_string(), + hash: c.hash, + } + } +} + +/// +#[derive(Default)] +pub(super) struct ItemBatch { + pub index_offset: usize, + pub items: Vec<LogEntry>, +} + +impl ItemBatch { + fn last_idx(&self) -> usize { + self.index_offset + self.items.len() + } + + pub fn set_items( + &mut self, + start_index: usize, + commits: Vec<CommitInfo>, + ) { + self.items.clear(); + self.items.extend(commits.into_iter().map(LogEntry::from)); + self.index_offset = start_index; + } + + pub fn needs_data(&self, idx: usize, idx_max: usize) -> bool { + let want_min = + idx.saturating_sub(SLICE_OFFSET_RELOAD_THRESHOLD); + let want_max = idx + .saturating_add(SLICE_OFFSET_RELOAD_THRESHOLD) + .min(idx_max); + + let needs_data_top = want_min < self.index_offset; + let needs_data_bottom = want_max > self.last_idx(); + needs_data_bottom || needs_data_top + } +} diff --git a/src/tabs/status.rs b/src/tabs/status.rs new file mode 100644 index 00000000..f065cc80 --- /dev/null +++ b/src/tabs/status.rs @@ -0,0 +1,368 @@ +use crate::{ + accessors, + components::{ + event_pump, ChangesComponent, CommandBlocking, CommandInfo, + Component, DiffComponent, DrawableComponent, + FileTreeItemKind, + }, + keys, + queue::Queue, + strings, +}; +use asyncgit::{ + current_tick, AsyncDiff, AsyncNotification, AsyncStatus, + DiffParams, +}; +use crossbeam_channel::Sender; +use crossterm::event::Event; +use strings::commands; +use tui::layout::{Constraint, Direction, Layout}; + +/// +#[derive(PartialEq)] +enum Focus { + WorkDir, + Diff, + Stage, +} + +/// +#[derive(PartialEq, Copy, Clone)] +enum DiffTarget { + Stage, + WorkingDir, +} + +pub struct Status { + visible: bool, + focus: Focus, + diff_target: DiffTarget, + index: ChangesComponent, + index_wd: ChangesComponent, + diff: DiffComponent, + git_diff: AsyncDiff, + git_status: AsyncStatus, +} + +impl DrawableComponent for Status { + fn draw<B: tui::backend::Backend>( + &self, + f: &mut tui::Frame<B>, + rect: tui::layout::Rect, + ) { + 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]); + } +} + +impl Status { + accessors!(self, [index, index_wd, diff]); + + /// + pub fn new( + sender: &Sender<AsyncNotification>, + queue: &Queue, + ) -> Self { + Self { + visible: true, + focus: Focus::WorkDir, + diff_target: DiffTarget::WorkingDir, + index_wd: ChangesComponent::new( + strings::TITLE_STATUS, + true, + true, + queue.clone(), + ), + index: ChangesComponent::new( + strings::TITLE_INDEX, + false, + false, + queue.clone(), + ), + diff: DiffComponent::new(queue.clone()), + git_diff: AsyncDiff::new(sender.clone()), + git_status: AsyncStatus::new(sender.clone()), + } + } + + fn can_focus_diff(&self) -> bool { + match self.focus { + Focus::WorkDir => self.index_wd.is_file_seleted(), + Focus::Stage => self.index.is_file_seleted(), + _ => false, + } + } + + //TODO: unpub + pub fn offer_open_commit_cmd(&self) -> bool { + self.visible + && self.diff_target == DiffTarget::Stage + && !self.index.is_empty() + } + + fn switch_focus(&mut self, f: Focus) -> 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); + } |