From adf925083d29bc13923765d110380777dcf2b181 Mon Sep 17 00:00:00 2001 From: Stephan Dilly Date: Tue, 24 Mar 2020 14:39:00 +0100 Subject: [PATCH] first test for a help popup --- README.md | 2 + src/app.rs | 154 +++++++++++++++++++++++++------------- src/components/command.rs | 46 ++++++++++++ src/components/commit.rs | 26 +++---- src/components/diff.rs | 13 ++-- src/components/help.rs | 85 +++++++++++++++++++++ src/components/index.rs | 15 ++-- src/components/mod.rs | 10 +-- src/keys.rs | 1 + src/strings.rs | 8 +- src/ui/mod.rs | 13 ++++ 11 files changed, 282 insertions(+), 91 deletions(-) create mode 100644 src/components/command.rs create mode 100644 src/components/help.rs diff --git a/README.md b/README.md index f3472dea..ece64078 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,8 @@ gitui # 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 updated when some hunks of the same file where diffed in unstaged before * [ ] help command diff --git a/src/app.rs b/src/app.rs index 78c630bd..b4cc9fe6 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,7 +1,7 @@ use crate::{ components::{ CommandInfo, CommitComponent, Component, DiffComponent, - IndexComponent, + HelpComponent, IndexComponent, }, keys, strings, }; @@ -42,6 +42,7 @@ pub struct App { diff_target: DiffTarget, do_quit: bool, commit: CommitComponent, + help: HelpComponent, index: IndexComponent, index_wd: IndexComponent, diff: DiffComponent, @@ -58,6 +59,7 @@ impl App { diff_target: DiffTarget::WorkingDir, do_quit: false, commit: CommitComponent::default(), + help: HelpComponent::default(), index_wd: IndexComponent::new( strings::TITLE_STATUS, true, @@ -124,17 +126,10 @@ impl App { self.index.draw(f, left_chunks[1]); self.diff.draw(f, chunks[1]); - let mut cmds = self.commit.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.draw_commands(f, chunks_main[2], self.commands()); self.commit.draw(f, f.size()); + self.help.draw(f, f.size()); } /// @@ -146,6 +141,9 @@ impl App { self.update(); } return; + } else if self.help.is_visible() { + self.help.event(ev); + return; } if !self.commit.is_visible() { @@ -169,6 +167,7 @@ impl App { keys::FOCUS_STATUS => { self.switch_focus(Focus::Status) } + keys::OPEN_HELP => self.help.show(), keys::FOCUS_STAGE => { self.switch_focus(Focus::Stage) } @@ -222,6 +221,7 @@ impl App { self.index.update(&status.stage); self.index_wd.update(&status.work_dir); self.update_diff(); + self.help.set_cmds(self.commands()); } /// @@ -230,8 +230,9 @@ impl App { } } +// private impls impl App { - pub fn update_diff(&mut self) { + fn update_diff(&mut self) { let (idx, is_stage) = match self.diff_target { DiffTarget::Stage => (&self.index, true), DiffTarget::WorkingDir => (&self.index_wd, false), @@ -255,43 +256,88 @@ impl App { } fn commands(&self) -> Vec { - let mut res = Vec::new(); - if !self.commit.is_visible() { - res.push(CommandInfo { - name: strings::COMMIT_CMD_OPEN.to_string(), - enabled: !self.index.is_empty(), - }); + let mut res = self.commit.commands(); + res.extend(self.help.commands()); + + res.extend(self.index.commands()); + 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 = self.index_wd.selection().is_some(); - res.push(CommandInfo { - name: strings::CMD_STATUS_STAGE.to_string(), - enabled: some_selection, - }); - res.push(CommandInfo { - name: strings::CMD_STATUS_RESET.to_string(), - enabled: some_selection, - }); - } else if self.index.focused() { - res.push(CommandInfo { - name: strings::CMD_STATUS_UNSTAGE.to_string(), - enabled: self.index.selection().is_some(), - }); + res.push(CommandInfo::new( + strings::CMD_STATUS_STAGE, + some_selection, + main_cmds_index_wd_focused_availale, + )); + res.push(CommandInfo::new( + strings::CMD_STATUS_RESET, + some_selection, + main_cmds_index_wd_focused_availale, + )); + + 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 { - strings::CMD_STATUS_LEFT.to_string() - } else { - strings::CMD_STATUS_RIGHT.to_string() - }, - enabled: true, - }); - res.push(CommandInfo { - name: strings::CMD_STATUS_QUIT.to_string(), - enabled: true, - }); + { + let focus_on_stage = self.focus == Focus::Stage; + let focus_not_diff = self.focus != Focus::Diff; + res.push(CommandInfo::new_hidden( + strings::CMD_STATUS_FOCUS_UNSTAGED, + true, + main_cmds_available + && focus_on_stage + && !focus_not_diff, + )); + 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 @@ -315,15 +361,19 @@ impl App { Style::default().fg(Color::DarkGray).bg(Color::Blue); let texts = cmds .iter() - .map(|c| { - Text::Styled( - Cow::from(c.name.clone()), - if c.enabled { - style_enabled - } else { - style_disabled - }, - ) + .filter_map(|c| { + if c.show_in_quickbar() { + Some(Text::Styled( + Cow::from(c.name.clone()), + if c.enabled { + style_enabled + } else { + style_disabled + }, + )) + } else { + None + } }) .collect::>(); diff --git a/src/components/command.rs b/src/components/command.rs new file mode 100644 index 00000000..7c352a71 --- /dev/null +++ b/src/components/command.rs @@ -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 + } +} diff --git a/src/components/commit.rs b/src/components/commit.rs index 933a47d6..7e30003f 100644 --- a/src/components/commit.rs +++ b/src/components/commit.rs @@ -44,20 +44,18 @@ impl Component for CommitComponent { } fn commands(&self) -> Vec { - if !self.visible { - vec![] - } else { - vec![ - CommandInfo { - name: strings::COMMIT_CMD_ENTER.to_string(), - enabled: self.can_commit(), - }, - CommandInfo { - name: strings::COMMIT_CMD_CLOSE.to_string(), - enabled: true, - }, - ] - } + vec![ + CommandInfo::new( + strings::COMMIT_CMD_ENTER, + self.can_commit(), + self.visible, + ), + CommandInfo::new( + strings::COMMIT_CMD_CLOSE, + true, + self.visible, + ), + ] } fn event(&mut self, ev: Event) -> bool { diff --git a/src/components/diff.rs b/src/components/diff.rs index d86dd585..a7b5dc2e 100644 --- a/src/components/diff.rs +++ b/src/components/diff.rs @@ -108,14 +108,11 @@ impl Component for DiffComponent { } fn commands(&self) -> Vec { - if self.focused { - return vec![CommandInfo { - name: strings::DIFF_CMD_SCROLL.to_string(), - enabled: self.can_scroll(), - }]; - } - - Vec::new() + vec![CommandInfo::new( + strings::CMD_SCROLL, + self.can_scroll(), + self.focused, + )] } fn event(&mut self, ev: Event) -> bool { diff --git a/src/components/help.rs b/src/components/help.rs new file mode 100644 index 00000000..b37a8365 --- /dev/null +++ b/src/components/help.rs @@ -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, + visible: bool, +} + +impl Component for HelpComponent { + fn draw(&self, f: &mut Frame, _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::>(); + + 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 { + 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) { + self.cmds = cmds; + } +} diff --git a/src/components/index.rs b/src/components/index.rs index 1de7b1c7..22f65665 100644 --- a/src/components/index.rs +++ b/src/components/index.rs @@ -1,6 +1,6 @@ use crate::{ components::{CommandInfo, Component}, - ui, + strings, ui, }; use asyncgit::{hash, StatusItem, StatusItemType}; use crossterm::event::{Event, KeyCode}; @@ -126,14 +126,11 @@ impl Component for IndexComponent { } fn commands(&self) -> Vec { - if self.focused { - return vec![CommandInfo { - name: "Scroll [↑↓]".to_string(), - enabled: self.items.len() > 1, - }]; - } - - Vec::new() + vec![CommandInfo::new( + strings::CMD_SCROLL, + self.items.len() > 1, + self.focused, + )] } fn event(&mut self, ev: Event) -> bool { diff --git a/src/components/mod.rs b/src/components/mod.rs index 2f61fd3e..4e43eb57 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -1,19 +1,17 @@ use crossterm::event::Event; use tui::{backend::Backend, layout::Rect, Frame}; +mod command; mod commit; mod diff; +mod help; mod index; +pub use command::CommandInfo; pub use commit::CommitComponent; pub use diff::DiffComponent; +pub use help::HelpComponent; pub use index::IndexComponent; -/// -pub struct CommandInfo { - pub name: String, - pub enabled: bool, -} - /// pub trait Component { /// diff --git a/src/keys.rs b/src/keys.rs index 94f828f3..b9562000 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -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_2: KeyEvent = no_mod(KeyCode::Char('q')); pub const OPEN_COMMIT: KeyEvent = no_mod(KeyCode::Char('c')); +pub const OPEN_HELP: KeyEvent = no_mod(KeyCode::Char('h')); diff --git a/src/strings.rs b/src/strings.rs index 705be7b2..bd7cf531 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -5,18 +5,22 @@ pub static TITLE_INDEX: &str = "Staged Changes [2]"; pub static TAB_STATUS: &str = "Status"; 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_UNSTAGE: &str = "Unstage File [enter]"; pub static CMD_STATUS_RESET: &str = "Reset File [D]"; 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_RIGHT: &str = "Diff [→]"; pub static CMD_SPLITTER: &str = " "; - -pub static DIFF_CMD_SCROLL: &str = "Scroll [↑↓]"; +pub static CMD_SCROLL: &str = "Scroll [↑↓]"; pub static COMMIT_TITLE: &str = "Commit"; pub static COMMIT_MSG: &str = "type commit message.."; pub static COMMIT_CMD_OPEN: &str = "Commit [c]"; pub static COMMIT_CMD_ENTER: &str = "Commit [enter]"; pub static COMMIT_CMD_CLOSE: &str = "Close [esc]"; + +pub static HELP_TITLE: &str = "Help"; diff --git a/src/ui/mod.rs b/src/ui/mod.rs index c902b785..d707e346 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -43,6 +43,19 @@ pub fn centered_rect( .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>( f: &mut Frame, r: Rect,