summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStephan Dilly <dilly.stephan@gmail.com>2020-05-11 14:45:37 +0200
committerStephan Dilly <dilly.stephan@gmail.com>2020-05-11 17:20:34 +0200
commit0e9ba8aef6e7f18bb6deb7d7d6623ca51cbddf28 (patch)
tree94291df91f2105ad36a05a792acb962ac6c9e75f
parentfa2aabfee0f16e0517aaa364eb2158eab3966b2c (diff)
Move status tab into its own component
-rw-r--r--.vscode/launch.json13
-rw-r--r--src/app.rs372
-rw-r--r--src/components/mod.rs32
-rw-r--r--src/components/reset.rs12
-rw-r--r--src/tabs/mod.rs4
-rw-r--r--src/tabs/revlog/mod.rs (renamed from src/tabs/revlog.rs)114
-rw-r--r--src/tabs/revlog/utils.rs63
-rw-r--r--src/tabs/status.rs368
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
diff --git a/src/app.rs b/src/app.rs
index 9bfb5c68..98ea895d 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -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);
+ }