Move status tab into its own component

This commit is contained in:
Stephan Dilly 2020-05-11 14:45:37 +02:00
parent fa2aabfee0
commit 0e9ba8aef6
8 changed files with 559 additions and 421 deletions

13
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,13 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "(OSX) Launch",
"type": "lldb",
"request": "launch",
"program": "${workspaceRoot}/target/debug/gitui",
"args": [],
"cwd": "${workspaceRoot}",
}
]
}

View 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;
@ -28,56 +25,17 @@ use tui::{
Frame,
};
///
#[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,
help: HelpComponent,
msg: MsgComponent,
reset: ResetComponent,
commit: CommitComponent,
help: HelpComponent,
index: ChangesComponent,
index_wd: ChangesComponent,
diff: DiffComponent,
msg: MsgComponent,
git_diff: AsyncDiff,
git_status: AsyncStatus,
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()),
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()),
do_quit: false,
current_commands: Vec::new(),
help: HelpComponent::default(),
msg: MsgComponent::default(),
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);
}
}

View File

@ -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,

View File

@ -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,
};
}
}

View File

@ -1,5 +1,5 @@
mod revlog;
//TODO: tab traits?
mod status;
pub use revlog::Revlog;
pub use status::Status;

View File

@ -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 {

63
src/tabs/revlog/utils.rs Normal file
View File

@ -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
}
}

368
src/tabs/status.rs Normal file
View File

@ -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);
}
};
self.update_diff();
return true;
}
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) {
self.git_diff.refresh();
self.git_status.fetch(current_tick());
}
///
pub fn anything_pending(&self) -> bool {
self.git_diff.is_pending() || self.git_status.is_pending()
}
///
pub fn update_git(&mut self, ev: AsyncNotification) {
match ev {
AsyncNotification::Diff => self.update_diff(),
AsyncNotification::Status => self.update_status(),
_ => (),
}
}
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();
}
///
pub 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();
}
}
}
impl Component for Status {
fn commands(
&self,
out: &mut Vec<CommandInfo>,
force_all: bool,
) -> CommandBlocking {
for c in self.components() {
if c.commands(out, force_all)
!= CommandBlocking::PassingOn
&& !force_all
{
break;
}
}
{
let focus_on_diff = self.focus == Focus::Diff;
out.push(CommandInfo::new(
commands::STATUS_FOCUS_LEFT,
true,
(self.visible && focus_on_diff) || force_all,
));
out.push(CommandInfo::new(
commands::STATUS_FOCUS_RIGHT,
self.can_focus_diff(),
(self.visible && !focus_on_diff) || force_all,
));
}
out.push(
CommandInfo::new(
commands::COMMIT_OPEN,
!self.index.is_empty(),
(self.visible && self.offer_open_commit_cmd())
|| force_all,
)
.order(-1),
);
out.push(
CommandInfo::new(
commands::SELECT_STATUS,
true,
(self.visible && self.focus == Focus::Diff)
|| force_all,
)
.hidden(),
);
out.push(
CommandInfo::new(
commands::SELECT_STAGING,
true,
(self.visible && self.focus == Focus::WorkDir)
|| force_all,
)
.order(-2),
);
out.push(
CommandInfo::new(
commands::SELECT_UNSTAGED,
true,
(self.visible && self.focus == Focus::Stage)
|| force_all,
)
.order(-2),
);
if self.visible {
CommandBlocking::Blocking
} else {
CommandBlocking::PassingOn
}
}
fn event(&mut self, ev: crossterm::event::Event) -> bool {
if self.visible {
let conusmed =
event_pump(ev, self.components_mut().as_mut_slice());
if conusmed {
return true;
}
if let Event::Key(k) = ev {
return 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,
})
}
_ => false,
};
}
}
false
}
fn is_visible(&self) -> bool {
self.visible
}
fn hide(&mut self) {
self.visible = false;
}
fn show(&mut self) {
self.visible = true;
}
}