mirror of
https://github.com/gitui-org/gitui
synced 2026-05-24 09:28:21 +00:00
Move status tab into its own component
This commit is contained in:
parent
fa2aabfee0
commit
0e9ba8aef6
8 changed files with 559 additions and 421 deletions
13
.vscode/launch.json
vendored
Normal file
13
.vscode/launch.json
vendored
Normal 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}",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
374
src/app.rs
374
src/app.rs
|
|
@ -1,19 +1,16 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
|
accessors,
|
||||||
components::{
|
components::{
|
||||||
ChangesComponent, CommandBlocking, CommandInfo,
|
event_pump, CommandBlocking, CommandInfo, CommitComponent,
|
||||||
CommitComponent, Component, DiffComponent, DrawableComponent,
|
Component, DrawableComponent, HelpComponent, MsgComponent,
|
||||||
FileTreeItemKind, HelpComponent, MsgComponent,
|
|
||||||
ResetComponent,
|
ResetComponent,
|
||||||
},
|
},
|
||||||
keys,
|
keys,
|
||||||
queue::{InternalEvent, NeedsUpdate, Queue},
|
queue::{InternalEvent, NeedsUpdate, Queue},
|
||||||
strings,
|
strings,
|
||||||
tabs::Revlog,
|
tabs::{Revlog, Status},
|
||||||
};
|
|
||||||
use asyncgit::{
|
|
||||||
current_tick, sync, AsyncDiff, AsyncNotification, AsyncStatus,
|
|
||||||
DiffParams, CWD,
|
|
||||||
};
|
};
|
||||||
|
use asyncgit::{sync, AsyncNotification, CWD};
|
||||||
use crossbeam_channel::Sender;
|
use crossbeam_channel::Sender;
|
||||||
use crossterm::event::Event;
|
use crossterm::event::Event;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
|
@ -28,56 +25,17 @@ use tui::{
|
||||||
Frame,
|
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 {
|
pub struct App {
|
||||||
focus: Focus,
|
|
||||||
diff_target: DiffTarget,
|
|
||||||
do_quit: bool,
|
do_quit: bool,
|
||||||
|
help: HelpComponent,
|
||||||
|
msg: MsgComponent,
|
||||||
reset: ResetComponent,
|
reset: ResetComponent,
|
||||||
commit: CommitComponent,
|
commit: CommitComponent,
|
||||||
help: HelpComponent,
|
|
||||||
index: ChangesComponent,
|
|
||||||
index_wd: ChangesComponent,
|
|
||||||
diff: DiffComponent,
|
|
||||||
msg: MsgComponent,
|
|
||||||
git_diff: AsyncDiff,
|
|
||||||
git_status: AsyncStatus,
|
|
||||||
current_commands: Vec<CommandInfo>,
|
current_commands: Vec<CommandInfo>,
|
||||||
tab: usize,
|
tab: usize,
|
||||||
revlog: Revlog,
|
revlog: Revlog,
|
||||||
|
status_tab: Status,
|
||||||
queue: Queue,
|
queue: Queue,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,31 +45,15 @@ impl App {
|
||||||
pub fn new(sender: &Sender<AsyncNotification>) -> Self {
|
pub fn new(sender: &Sender<AsyncNotification>) -> Self {
|
||||||
let queue = Queue::default();
|
let queue = Queue::default();
|
||||||
Self {
|
Self {
|
||||||
focus: Focus::WorkDir,
|
|
||||||
diff_target: DiffTarget::WorkingDir,
|
|
||||||
do_quit: false,
|
|
||||||
reset: ResetComponent::new(queue.clone()),
|
reset: ResetComponent::new(queue.clone()),
|
||||||
commit: CommitComponent::new(queue.clone()),
|
commit: CommitComponent::new(queue.clone()),
|
||||||
help: HelpComponent::default(),
|
do_quit: false,
|
||||||
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(),
|
current_commands: Vec::new(),
|
||||||
|
help: HelpComponent::default(),
|
||||||
|
msg: MsgComponent::default(),
|
||||||
tab: 0,
|
tab: 0,
|
||||||
revlog: Revlog::new(&sender),
|
revlog: Revlog::new(&sender),
|
||||||
|
status_tab: Status::new(&sender, &queue),
|
||||||
queue,
|
queue,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -142,7 +84,7 @@ impl App {
|
||||||
);
|
);
|
||||||
|
|
||||||
if self.tab == 0 {
|
if self.tab == 0 {
|
||||||
self.draw_status_tab(f, chunks_main[1]);
|
self.status_tab.draw(f, chunks_main[1]);
|
||||||
} else {
|
} else {
|
||||||
self.revlog.draw(f, chunks_main[1]);
|
self.revlog.draw(f, chunks_main[1]);
|
||||||
}
|
}
|
||||||
|
|
@ -162,40 +104,23 @@ impl App {
|
||||||
|
|
||||||
let mut flags = NeedsUpdate::empty();
|
let mut flags = NeedsUpdate::empty();
|
||||||
|
|
||||||
let event_used = if self.tab == 0 {
|
if event_pump(ev, self.components_mut().as_mut_slice()) {
|
||||||
Self::event_pump(ev, self.components_mut().as_mut_slice())
|
|
||||||
} else {
|
|
||||||
self.revlog.event(ev)
|
|
||||||
};
|
|
||||||
|
|
||||||
if event_used {
|
|
||||||
flags.insert(NeedsUpdate::COMMANDS);
|
flags.insert(NeedsUpdate::COMMANDS);
|
||||||
} else if let Event::Key(k) = ev {
|
} else if let Event::Key(k) = ev {
|
||||||
let new_flags = match k {
|
let new_flags = match k {
|
||||||
keys::FOCUS_WORKDIR => {
|
//TODO: move into status tab
|
||||||
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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
keys::OPEN_COMMIT
|
keys::OPEN_COMMIT
|
||||||
if !self.index.is_empty()
|
if self.status_tab.offer_open_commit_cmd() =>
|
||||||
&& self.offer_open_commit_cmd() =>
|
|
||||||
{
|
{
|
||||||
self.commit.show();
|
self.commit.show();
|
||||||
NeedsUpdate::COMMANDS
|
NeedsUpdate::COMMANDS
|
||||||
}
|
}
|
||||||
|
|
||||||
keys::TAB_TOGGLE => {
|
keys::TAB_TOGGLE => {
|
||||||
self.toggle_tabs();
|
self.toggle_tabs();
|
||||||
NeedsUpdate::COMMANDS
|
NeedsUpdate::COMMANDS
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => NeedsUpdate::empty(),
|
_ => NeedsUpdate::empty(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -211,28 +136,31 @@ impl App {
|
||||||
self.update();
|
self.update();
|
||||||
}
|
}
|
||||||
if flags.contains(NeedsUpdate::DIFF) {
|
if flags.contains(NeedsUpdate::DIFF) {
|
||||||
self.update_diff();
|
self.status_tab.update_diff();
|
||||||
}
|
}
|
||||||
if flags.contains(NeedsUpdate::COMMANDS) {
|
if flags.contains(NeedsUpdate::COMMANDS) {
|
||||||
self.update_commands();
|
self.update_commands();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO: do we need this?
|
||||||
///
|
///
|
||||||
pub fn update(&mut self) {
|
pub fn update(&mut self) {
|
||||||
trace!("update");
|
trace!("update");
|
||||||
|
self.status_tab.update();
|
||||||
self.git_diff.refresh();
|
|
||||||
self.git_status.fetch(current_tick());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
pub fn update_git(&mut self, ev: AsyncNotification) {
|
pub fn update_git(&mut self, ev: AsyncNotification) {
|
||||||
trace!("update_git: {:?}", ev);
|
trace!("update_git: {:?}", ev);
|
||||||
|
|
||||||
|
self.status_tab.update_git(ev);
|
||||||
|
|
||||||
match ev {
|
match ev {
|
||||||
AsyncNotification::Diff => self.update_diff(),
|
AsyncNotification::Diff => (),
|
||||||
AsyncNotification::Status => self.update_status(),
|
|
||||||
AsyncNotification::Log => self.revlog.update(),
|
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 {
|
pub fn any_work_pending(&self) -> bool {
|
||||||
self.git_diff.is_pending()
|
self.status_tab.anything_pending()
|
||||||
|| self.git_status.is_pending()
|
|
||||||
|| self.revlog.any_work_pending()
|
|| self.revlog.any_work_pending()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// private impls
|
// private impls
|
||||||
impl App {
|
impl App {
|
||||||
components!(
|
accessors!(self, [msg, reset, commit, help, revlog, status_tab]);
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_quit(&mut self, ev: Event) {
|
fn check_quit(&mut self, ev: Event) {
|
||||||
if let Event::Key(e) = ev {
|
if let Event::Key(e) = ev {
|
||||||
|
|
@ -309,35 +193,20 @@ impl App {
|
||||||
self.tab %= 2;
|
self.tab %= 2;
|
||||||
|
|
||||||
if self.tab == 1 {
|
if self.tab == 1 {
|
||||||
|
self.status_tab.hide();
|
||||||
self.revlog.show();
|
self.revlog.show();
|
||||||
} else {
|
} else {
|
||||||
|
self.status_tab.show();
|
||||||
self.revlog.hide();
|
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) {
|
fn update_commands(&mut self) {
|
||||||
self.help.set_cmds(self.commands(true));
|
self.help.set_cmds(self.commands(true));
|
||||||
self.current_commands = self.commands(false);
|
self.current_commands = self.commands(false);
|
||||||
self.current_commands.sort_by_key(|e| e.order);
|
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 {
|
fn process_queue(&mut self) -> NeedsUpdate {
|
||||||
let mut flags = NeedsUpdate::empty();
|
let mut flags = NeedsUpdate::empty();
|
||||||
loop {
|
loop {
|
||||||
|
|
@ -379,7 +248,9 @@ impl App {
|
||||||
flags.insert(NeedsUpdate::COMMANDS);
|
flags.insert(NeedsUpdate::COMMANDS);
|
||||||
}
|
}
|
||||||
InternalEvent::AddHunk(hash) => {
|
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 is_stage {
|
||||||
if sync::unstage_hunk(CWD, path, hash) {
|
if sync::unstage_hunk(CWD, path, hash) {
|
||||||
flags.insert(NeedsUpdate::ALL);
|
flags.insert(NeedsUpdate::ALL);
|
||||||
|
|
@ -402,23 +273,13 @@ impl App {
|
||||||
fn commands(&self, force_all: bool) -> Vec<CommandInfo> {
|
fn commands(&self, force_all: bool) -> Vec<CommandInfo> {
|
||||||
let mut res = Vec::new();
|
let mut res = Vec::new();
|
||||||
|
|
||||||
if self.revlog.is_visible() {
|
for c in self.components() {
|
||||||
self.revlog.commands(&mut res, force_all);
|
if c.commands(&mut res, force_all)
|
||||||
} else {
|
!= CommandBlocking::PassingOn
|
||||||
for c in self.components() {
|
&& !force_all
|
||||||
if c.commands(&mut res, force_all)
|
{
|
||||||
!= CommandBlocking::PassingOn
|
break;
|
||||||
&& !force_all
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: move into status tab component
|
|
||||||
self.add_commands_status_tab(
|
|
||||||
&mut res,
|
|
||||||
!self.any_popup_visible(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
res.push(
|
res.push(
|
||||||
|
|
@ -442,80 +303,6 @@ impl App {
|
||||||
res
|
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 {
|
fn any_popup_visible(&self) -> bool {
|
||||||
self.commit.is_visible()
|
self.commit.is_visible()
|
||||||
|| self.help.is_visible()
|
|| self.help.is_visible()
|
||||||
|
|
@ -532,52 +319,6 @@ impl App {
|
||||||
self.msg.draw(f, size);
|
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>(
|
fn draw_commands<B: Backend>(
|
||||||
f: &mut Frame<B>,
|
f: &mut Frame<B>,
|
||||||
r: Rect,
|
r: Rect,
|
||||||
|
|
@ -617,39 +358,4 @@ impl App {
|
||||||
r,
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,38 @@ pub use help::HelpComponent;
|
||||||
pub use msg::MsgComponent;
|
pub use msg::MsgComponent;
|
||||||
pub use reset::ResetComponent;
|
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)]
|
#[derive(Copy, Clone)]
|
||||||
pub enum ScrollType {
|
pub enum ScrollType {
|
||||||
Up,
|
Up,
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use crate::{
|
||||||
strings, ui,
|
strings, ui,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crossterm::event::{Event, KeyCode};
|
use crossterm::event::{Event, KeyCode, KeyModifiers};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use strings::commands;
|
use strings::commands;
|
||||||
use tui::{
|
use tui::{
|
||||||
|
|
@ -74,16 +74,24 @@ impl Component for ResetComponent {
|
||||||
if self.visible {
|
if self.visible {
|
||||||
if let Event::Key(e) = ev {
|
if let Event::Key(e) = ev {
|
||||||
return match e.code {
|
return match e.code {
|
||||||
|
KeyCode::Char(c) => {
|
||||||
|
// ignore and early out on ctrl+c
|
||||||
|
!(c == 'c'
|
||||||
|
&& e.modifiers
|
||||||
|
.contains(KeyModifiers::CONTROL))
|
||||||
|
}
|
||||||
|
|
||||||
KeyCode::Esc => {
|
KeyCode::Esc => {
|
||||||
self.hide();
|
self.hide();
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyCode::Enter => {
|
KeyCode::Enter => {
|
||||||
self.confirm();
|
self.confirm();
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => false,
|
_ => true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
mod revlog;
|
mod revlog;
|
||||||
|
mod status;
|
||||||
//TODO: tab traits?
|
|
||||||
|
|
||||||
pub use revlog::Revlog;
|
pub use revlog::Revlog;
|
||||||
|
pub use status::Status;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
mod utils;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
components::{
|
components::{
|
||||||
CommandBlocking, CommandInfo, Component, ScrollType,
|
CommandBlocking, CommandInfo, Component, ScrollType,
|
||||||
|
|
@ -6,11 +8,10 @@ use crate::{
|
||||||
strings::commands,
|
strings::commands,
|
||||||
};
|
};
|
||||||
use asyncgit::{sync, AsyncLog, AsyncNotification, CWD};
|
use asyncgit::{sync, AsyncLog, AsyncNotification, CWD};
|
||||||
use chrono::prelude::*;
|
|
||||||
use crossbeam_channel::Sender;
|
use crossbeam_channel::Sender;
|
||||||
use crossterm::event::Event;
|
use crossterm::event::Event;
|
||||||
use std::{borrow::Cow, cmp, convert::TryFrom, time::Instant};
|
use std::{borrow::Cow, cmp, convert::TryFrom, time::Instant};
|
||||||
use sync::{CommitInfo, Tags};
|
use sync::Tags;
|
||||||
use tui::{
|
use tui::{
|
||||||
backend::Backend,
|
backend::Backend,
|
||||||
layout::{Alignment, Rect},
|
layout::{Alignment, Rect},
|
||||||
|
|
@ -18,30 +19,7 @@ use tui::{
|
||||||
widgets::{Block, Borders, Paragraph, Text},
|
widgets::{Block, Borders, Paragraph, Text},
|
||||||
Frame,
|
Frame,
|
||||||
};
|
};
|
||||||
|
use utils::{ItemBatch, LogEntry};
|
||||||
#[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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const COLOR_SELECTION_BG: Color = Color::Blue;
|
const COLOR_SELECTION_BG: Color = Color::Blue;
|
||||||
|
|
||||||
|
|
@ -64,42 +42,6 @@ const STYLE_MSG_SELECTED: Style =
|
||||||
|
|
||||||
static ELEMENTS_PER_LINE: usize = 10;
|
static ELEMENTS_PER_LINE: usize = 10;
|
||||||
static SLICE_SIZE: usize = 1200;
|
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 {
|
pub struct Revlog {
|
||||||
|
|
@ -320,26 +262,28 @@ impl Revlog {
|
||||||
|
|
||||||
impl Component for Revlog {
|
impl Component for Revlog {
|
||||||
fn event(&mut self, ev: Event) -> bool {
|
fn event(&mut self, ev: Event) -> bool {
|
||||||
if let Event::Key(k) = ev {
|
if self.visible {
|
||||||
return match k {
|
if let Event::Key(k) = ev {
|
||||||
keys::MOVE_UP => {
|
return match k {
|
||||||
self.move_selection(ScrollType::Up);
|
keys::MOVE_UP => {
|
||||||
true
|
self.move_selection(ScrollType::Up);
|
||||||
}
|
true
|
||||||
keys::MOVE_DOWN => {
|
}
|
||||||
self.move_selection(ScrollType::Down);
|
keys::MOVE_DOWN => {
|
||||||
true
|
self.move_selection(ScrollType::Down);
|
||||||
}
|
true
|
||||||
keys::SHIFT_UP | keys::HOME => {
|
}
|
||||||
self.move_selection(ScrollType::Home);
|
keys::SHIFT_UP | keys::HOME => {
|
||||||
true
|
self.move_selection(ScrollType::Home);
|
||||||
}
|
true
|
||||||
keys::SHIFT_DOWN | keys::END => {
|
}
|
||||||
self.move_selection(ScrollType::End);
|
keys::SHIFT_DOWN | keys::END => {
|
||||||
true
|
self.move_selection(ScrollType::End);
|
||||||
}
|
true
|
||||||
_ => false,
|
}
|
||||||
};
|
_ => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
false
|
false
|
||||||
|
|
@ -356,7 +300,11 @@ impl Component for Revlog {
|
||||||
self.visible || force_all,
|
self.visible || force_all,
|
||||||
));
|
));
|
||||||
|
|
||||||
CommandBlocking::PassingOn
|
if self.visible {
|
||||||
|
CommandBlocking::Blocking
|
||||||
|
} else {
|
||||||
|
CommandBlocking::PassingOn
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_visible(&self) -> bool {
|
fn is_visible(&self) -> bool {
|
||||||
63
src/tabs/revlog/utils.rs
Normal file
63
src/tabs/revlog/utils.rs
Normal 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
368
src/tabs/status.rs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue