first test for a help popup

This commit is contained in:
Stephan Dilly 2020-03-24 14:39:00 +01:00
parent 5d819ccd15
commit adf925083d
11 changed files with 282 additions and 91 deletions

View File

@ -50,6 +50,8 @@ gitui
# todo for 0.1 (first release) # todo for 0.1 (first release)
* [ ] fix: no diff of untracked file in a subdir
* [ ] fix: dont show scrol option when any popup open
* [ ] fix: diff is not updated when changed * [ ] fix: diff is not updated when changed
* [ ] fix: diff is updated when some hunks of the same file where diffed in unstaged before * [ ] fix: diff is updated when some hunks of the same file where diffed in unstaged before
* [ ] help command * [ ] help command

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
components::{ components::{
CommandInfo, CommitComponent, Component, DiffComponent, CommandInfo, CommitComponent, Component, DiffComponent,
IndexComponent, HelpComponent, IndexComponent,
}, },
keys, strings, keys, strings,
}; };
@ -42,6 +42,7 @@ pub struct App {
diff_target: DiffTarget, diff_target: DiffTarget,
do_quit: bool, do_quit: bool,
commit: CommitComponent, commit: CommitComponent,
help: HelpComponent,
index: IndexComponent, index: IndexComponent,
index_wd: IndexComponent, index_wd: IndexComponent,
diff: DiffComponent, diff: DiffComponent,
@ -58,6 +59,7 @@ impl App {
diff_target: DiffTarget::WorkingDir, diff_target: DiffTarget::WorkingDir,
do_quit: false, do_quit: false,
commit: CommitComponent::default(), commit: CommitComponent::default(),
help: HelpComponent::default(),
index_wd: IndexComponent::new( index_wd: IndexComponent::new(
strings::TITLE_STATUS, strings::TITLE_STATUS,
true, true,
@ -124,17 +126,10 @@ impl App {
self.index.draw(f, left_chunks[1]); self.index.draw(f, left_chunks[1]);
self.diff.draw(f, chunks[1]); self.diff.draw(f, chunks[1]);
let mut cmds = self.commit.commands(); self.draw_commands(f, chunks_main[2], self.commands());
if !self.commit.is_visible() {
cmds.extend(self.index.commands());
cmds.extend(self.index_wd.commands());
cmds.extend(self.diff.commands());
}
cmds.extend(self.commands());
self.draw_commands(f, chunks_main[2], cmds);
self.commit.draw(f, f.size()); self.commit.draw(f, f.size());
self.help.draw(f, f.size());
} }
/// ///
@ -146,6 +141,9 @@ impl App {
self.update(); self.update();
} }
return; return;
} else if self.help.is_visible() {
self.help.event(ev);
return;
} }
if !self.commit.is_visible() { if !self.commit.is_visible() {
@ -169,6 +167,7 @@ impl App {
keys::FOCUS_STATUS => { keys::FOCUS_STATUS => {
self.switch_focus(Focus::Status) self.switch_focus(Focus::Status)
} }
keys::OPEN_HELP => self.help.show(),
keys::FOCUS_STAGE => { keys::FOCUS_STAGE => {
self.switch_focus(Focus::Stage) self.switch_focus(Focus::Stage)
} }
@ -222,6 +221,7 @@ impl App {
self.index.update(&status.stage); self.index.update(&status.stage);
self.index_wd.update(&status.work_dir); self.index_wd.update(&status.work_dir);
self.update_diff(); self.update_diff();
self.help.set_cmds(self.commands());
} }
/// ///
@ -230,8 +230,9 @@ impl App {
} }
} }
// private impls
impl App { impl App {
pub fn update_diff(&mut self) { fn update_diff(&mut self) {
let (idx, is_stage) = match self.diff_target { let (idx, is_stage) = match self.diff_target {
DiffTarget::Stage => (&self.index, true), DiffTarget::Stage => (&self.index, true),
DiffTarget::WorkingDir => (&self.index_wd, false), DiffTarget::WorkingDir => (&self.index_wd, false),
@ -255,43 +256,88 @@ impl App {
} }
fn commands(&self) -> Vec<CommandInfo> { fn commands(&self) -> Vec<CommandInfo> {
let mut res = Vec::new(); let mut res = self.commit.commands();
if !self.commit.is_visible() { res.extend(self.help.commands());
res.push(CommandInfo {
name: strings::COMMIT_CMD_OPEN.to_string(), res.extend(self.index.commands());
enabled: !self.index.is_empty(), res.extend(self.index_wd.commands());
}); res.extend(self.diff.commands());
{
let main_cmds_available =
!self.commit.is_visible() && !self.help.is_visible();
res.push(CommandInfo::new(
strings::COMMIT_CMD_OPEN,
!self.index.is_empty(),
main_cmds_available,
));
{
let main_cmds_index_wd_focused_availale =
main_cmds_available && self.index_wd.focused();
if self.index_wd.focused() {
let some_selection = let some_selection =
self.index_wd.selection().is_some(); self.index_wd.selection().is_some();
res.push(CommandInfo { res.push(CommandInfo::new(
name: strings::CMD_STATUS_STAGE.to_string(), strings::CMD_STATUS_STAGE,
enabled: some_selection, some_selection,
}); main_cmds_index_wd_focused_availale,
res.push(CommandInfo { ));
name: strings::CMD_STATUS_RESET.to_string(), res.push(CommandInfo::new(
enabled: some_selection, strings::CMD_STATUS_RESET,
}); some_selection,
} else if self.index.focused() { main_cmds_index_wd_focused_availale,
res.push(CommandInfo { ));
name: strings::CMD_STATUS_UNSTAGE.to_string(),
enabled: self.index.selection().is_some(), res.push(CommandInfo::new(
}); strings::CMD_STATUS_UNSTAGE,
self.index.selection().is_some(),
main_cmds_available && self.index.focused(),
));
} }
res.push(CommandInfo { {
name: if self.focus == Focus::Diff { let focus_on_stage = self.focus == Focus::Stage;
strings::CMD_STATUS_LEFT.to_string() let focus_not_diff = self.focus != Focus::Diff;
} else { res.push(CommandInfo::new_hidden(
strings::CMD_STATUS_RIGHT.to_string() strings::CMD_STATUS_FOCUS_UNSTAGED,
}, true,
enabled: true, main_cmds_available
}); && focus_on_stage
res.push(CommandInfo { && !focus_not_diff,
name: strings::CMD_STATUS_QUIT.to_string(), ));
enabled: true, res.push(CommandInfo::new_hidden(
}); strings::CMD_STATUS_FOCUS_STAGED,
true,
main_cmds_available
&& !focus_on_stage
&& !focus_not_diff,
));
}
{
let focus_on_diff = self.focus == Focus::Diff;
res.push(CommandInfo::new(
strings::CMD_STATUS_LEFT,
true,
main_cmds_available && focus_on_diff,
));
res.push(CommandInfo::new(
strings::CMD_STATUS_RIGHT,
true,
main_cmds_available && !focus_on_diff,
));
}
res.push(CommandInfo::new(
strings::CMD_STATUS_HELP,
true,
main_cmds_available,
));
res.push(CommandInfo::new(
strings::CMD_STATUS_QUIT,
true,
main_cmds_available,
));
} }
res res
@ -315,15 +361,19 @@ impl App {
Style::default().fg(Color::DarkGray).bg(Color::Blue); Style::default().fg(Color::DarkGray).bg(Color::Blue);
let texts = cmds let texts = cmds
.iter() .iter()
.map(|c| { .filter_map(|c| {
Text::Styled( if c.show_in_quickbar() {
Some(Text::Styled(
Cow::from(c.name.clone()), Cow::from(c.name.clone()),
if c.enabled { if c.enabled {
style_enabled style_enabled
} else { } else {
style_disabled style_disabled
}, },
) ))
} else {
None
}
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();

46
src/components/command.rs Normal file
View File

@ -0,0 +1,46 @@
///
pub struct CommandInfo {
///
pub name: String,
///
// pub keys:
/// available but not active in the context
pub enabled: bool,
/// will show up in the quick bar
pub quick_bar: bool,
/// available in current app state
pub available: bool,
}
impl CommandInfo {
///
pub fn new(name: &str, enabled: bool, available: bool) -> Self {
Self {
name: name.to_string(),
enabled,
quick_bar: true,
available,
}
}
///
pub fn new_hidden(
name: &str,
enabled: bool,
available: bool,
) -> Self {
Self {
name: name.to_string(),
enabled,
quick_bar: false,
available,
}
}
///
pub fn print(&self, out: &mut String) {
out.push_str(self.name.as_str());
}
///
pub fn show_in_quickbar(&self) -> bool {
self.quick_bar && self.available
}
}

View File

@ -44,21 +44,19 @@ impl Component for CommitComponent {
} }
fn commands(&self) -> Vec<CommandInfo> { fn commands(&self) -> Vec<CommandInfo> {
if !self.visible {
vec![]
} else {
vec![ vec![
CommandInfo { CommandInfo::new(
name: strings::COMMIT_CMD_ENTER.to_string(), strings::COMMIT_CMD_ENTER,
enabled: self.can_commit(), self.can_commit(),
}, self.visible,
CommandInfo { ),
name: strings::COMMIT_CMD_CLOSE.to_string(), CommandInfo::new(
enabled: true, strings::COMMIT_CMD_CLOSE,
}, true,
self.visible,
),
] ]
} }
}
fn event(&mut self, ev: Event) -> bool { fn event(&mut self, ev: Event) -> bool {
if let Event::Key(e) = ev { if let Event::Key(e) = ev {

View File

@ -108,14 +108,11 @@ impl Component for DiffComponent {
} }
fn commands(&self) -> Vec<CommandInfo> { fn commands(&self) -> Vec<CommandInfo> {
if self.focused { vec![CommandInfo::new(
return vec![CommandInfo { strings::CMD_SCROLL,
name: strings::DIFF_CMD_SCROLL.to_string(), self.can_scroll(),
enabled: self.can_scroll(), self.focused,
}]; )]
}
Vec::new()
} }
fn event(&mut self, ev: Event) -> bool { fn event(&mut self, ev: Event) -> bool {

85
src/components/help.rs Normal file
View File

@ -0,0 +1,85 @@
use super::{CommandInfo, Component};
use crate::{strings, ui};
use crossterm::event::{Event, KeyCode};
use std::borrow::Cow;
use tui::{
backend::Backend,
layout::{Alignment, Rect},
widgets::{Block, Borders, Paragraph, Text, Widget},
Frame,
};
#[derive(Default)]
pub struct HelpComponent {
cmds: Vec<CommandInfo>,
visible: bool,
}
impl Component for HelpComponent {
fn draw<B: Backend>(&self, f: &mut Frame<B>, _rect: Rect) {
if self.visible {
let txt = self
.cmds
.iter()
.map(|e| {
let mut out = String::new();
e.print(&mut out);
out.push('\n');
Text::Raw(Cow::from(out))
})
.collect::<Vec<_>>();
ui::Clear::new(
Paragraph::new(txt.iter())
.block(
Block::default()
.title(strings::HELP_TITLE)
.borders(Borders::ALL),
)
.alignment(Alignment::Left),
)
.render(f, ui::centered_rect_absolute(60, 20, f.size()));
}
}
fn commands(&self) -> Vec<CommandInfo> {
vec![CommandInfo::new(
strings::COMMIT_CMD_CLOSE,
true,
self.visible,
)]
}
fn event(&mut self, ev: Event) -> bool {
if let Event::Key(e) = ev {
return match e.code {
KeyCode::Esc => {
self.hide();
true
}
_ => false,
};
}
false
}
fn is_visible(&self) -> bool {
self.visible
}
fn hide(&mut self) {
self.visible = false
}
fn show(&mut self) {
self.visible = true
}
}
impl HelpComponent {
///
pub fn set_cmds(&mut self, cmds: Vec<CommandInfo>) {
self.cmds = cmds;
}
}

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
components::{CommandInfo, Component}, components::{CommandInfo, Component},
ui, strings, ui,
}; };
use asyncgit::{hash, StatusItem, StatusItemType}; use asyncgit::{hash, StatusItem, StatusItemType};
use crossterm::event::{Event, KeyCode}; use crossterm::event::{Event, KeyCode};
@ -126,14 +126,11 @@ impl Component for IndexComponent {
} }
fn commands(&self) -> Vec<CommandInfo> { fn commands(&self) -> Vec<CommandInfo> {
if self.focused { vec![CommandInfo::new(
return vec![CommandInfo { strings::CMD_SCROLL,
name: "Scroll [↑↓]".to_string(), self.items.len() > 1,
enabled: self.items.len() > 1, self.focused,
}]; )]
}
Vec::new()
} }
fn event(&mut self, ev: Event) -> bool { fn event(&mut self, ev: Event) -> bool {

View File

@ -1,19 +1,17 @@
use crossterm::event::Event; use crossterm::event::Event;
use tui::{backend::Backend, layout::Rect, Frame}; use tui::{backend::Backend, layout::Rect, Frame};
mod command;
mod commit; mod commit;
mod diff; mod diff;
mod help;
mod index; mod index;
pub use command::CommandInfo;
pub use commit::CommitComponent; pub use commit::CommitComponent;
pub use diff::DiffComponent; pub use diff::DiffComponent;
pub use help::HelpComponent;
pub use index::IndexComponent; pub use index::IndexComponent;
///
pub struct CommandInfo {
pub name: String,
pub enabled: bool,
}
/// ///
pub trait Component { pub trait Component {
/// ///

View File

@ -16,3 +16,4 @@ pub const STATUS_STAGE_FILE: KeyEvent = no_mod(KeyCode::Enter);
pub const EXIT_1: KeyEvent = no_mod(KeyCode::Esc); pub const EXIT_1: KeyEvent = no_mod(KeyCode::Esc);
pub const EXIT_2: KeyEvent = no_mod(KeyCode::Char('q')); pub const EXIT_2: KeyEvent = no_mod(KeyCode::Char('q'));
pub const OPEN_COMMIT: KeyEvent = no_mod(KeyCode::Char('c')); pub const OPEN_COMMIT: KeyEvent = no_mod(KeyCode::Char('c'));
pub const OPEN_HELP: KeyEvent = no_mod(KeyCode::Char('h'));

View File

@ -5,18 +5,22 @@ pub static TITLE_INDEX: &str = "Staged Changes [2]";
pub static TAB_STATUS: &str = "Status"; pub static TAB_STATUS: &str = "Status";
pub static TAB_DIVIDER: &str = " | "; pub static TAB_DIVIDER: &str = " | ";
pub static CMD_STATUS_FOCUS_UNSTAGED: &str = "Unstaged [1]";
pub static CMD_STATUS_FOCUS_STAGED: &str = "Staged [2]";
pub static CMD_STATUS_STAGE: &str = "Stage File [enter]"; pub static CMD_STATUS_STAGE: &str = "Stage File [enter]";
pub static CMD_STATUS_UNSTAGE: &str = "Unstage File [enter]"; pub static CMD_STATUS_UNSTAGE: &str = "Unstage File [enter]";
pub static CMD_STATUS_RESET: &str = "Reset File [D]"; pub static CMD_STATUS_RESET: &str = "Reset File [D]";
pub static CMD_STATUS_QUIT: &str = "Quit [esc,q]"; pub static CMD_STATUS_QUIT: &str = "Quit [esc,q]";
pub static CMD_STATUS_HELP: &str = "Help [h]";
pub static CMD_STATUS_LEFT: &str = "Back [←]"; pub static CMD_STATUS_LEFT: &str = "Back [←]";
pub static CMD_STATUS_RIGHT: &str = "Diff [→]"; pub static CMD_STATUS_RIGHT: &str = "Diff [→]";
pub static CMD_SPLITTER: &str = " "; pub static CMD_SPLITTER: &str = " ";
pub static CMD_SCROLL: &str = "Scroll [↑↓]";
pub static DIFF_CMD_SCROLL: &str = "Scroll [↑↓]";
pub static COMMIT_TITLE: &str = "Commit"; pub static COMMIT_TITLE: &str = "Commit";
pub static COMMIT_MSG: &str = "type commit message.."; pub static COMMIT_MSG: &str = "type commit message..";
pub static COMMIT_CMD_OPEN: &str = "Commit [c]"; pub static COMMIT_CMD_OPEN: &str = "Commit [c]";
pub static COMMIT_CMD_ENTER: &str = "Commit [enter]"; pub static COMMIT_CMD_ENTER: &str = "Commit [enter]";
pub static COMMIT_CMD_CLOSE: &str = "Close [esc]"; pub static COMMIT_CMD_CLOSE: &str = "Close [esc]";
pub static HELP_TITLE: &str = "Help";

View File

@ -43,6 +43,19 @@ pub fn centered_rect(
.split(popup_layout[1])[1] .split(popup_layout[1])[1]
} }
pub fn centered_rect_absolute(
width: u16,
height: u16,
r: Rect,
) -> Rect {
Rect::new(
(r.width - width) / 2,
(r.height - height) / 2,
width,
height,
)
}
pub fn draw_list<'b, B: Backend, L>( pub fn draw_list<'b, B: Backend, L>(
f: &mut Frame<B>, f: &mut Frame<B>,
r: Rect, r: Rect,