Customize key binds (#234)

* customizable key config
* provide example vim key config
* automatically show correct key binding in bottom cmd-bar
This commit is contained in:
Antonio Yang 2020-08-27 00:23:53 +08:00 committed by GitHub
parent d8bd4721ef
commit a95ffd7bcc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 1643 additions and 921 deletions

1
.gitignore vendored
View File

@ -3,3 +3,4 @@
.DS_Store .DS_Store
/.idea/ /.idea/
flamegraph.svg flamegraph.svg
KEY_CONFIG.md

1
Cargo.lock generated
View File

@ -316,6 +316,7 @@ dependencies = [
"libc", "libc",
"mio", "mio",
"parking_lot 0.10.2", "parking_lot 0.10.2",
"serde",
"signal-hook", "signal-hook",
"winapi", "winapi",
] ]

View File

@ -21,7 +21,7 @@ keywords = [
[dependencies] [dependencies]
scopetime = { path = "./scopetime", version = "0.1" } scopetime = { path = "./scopetime", version = "0.1" }
asyncgit = { path = "./asyncgit", version = "0.9" } asyncgit = { path = "./asyncgit", version = "0.9" }
crossterm = "0.17" crossterm = { version = "0.17", features = [ "serde" ] }
clap = { version = "2.33", default-features = false } clap = { version = "2.33", default-features = false }
tui = { version = "0.9", default-features = false, features = ['crossterm'] } tui = { version = "0.9", default-features = false, features = ['crossterm'] }
bytesize = { version = "1.0.1", default-features = false} bytesize = { version = "1.0.1", default-features = false}

15
KEY_CONFIG.md Normal file
View File

@ -0,0 +1,15 @@
# Key Config
Default using arrow key to navigate the gitui and Ctrl + C to quit the program
The first time Gitui will create `key_config.ron` file automatically.
You can change the every single key bindings of the program as what you like.
The config file format is [Ron format](https://github.com/ron-rs/ron).
And the path differs depending on the operating system:
* `$HOME/Library/Preferences/gitui/key_config.ron` (mac)
* `$XDG_CONFIG_HOME/gitui/key_config.ron` (linux using XDG)
* `$HOME/.config/gitui/key_config.ron` (linux)
Here is a [vim style key config](assets/vim_style_key_config.ron) with `h`, `j`, `k`, `l` to navigate and `Ctrl + C` to leave.
You can use it to replace `key_config.ron` and get a vim style setting.

View File

@ -135,3 +135,6 @@ However, you can customize everything to your liking: See [Themes](THEMES.md).
- [tig](https://github.com/jonas/tig) - [tig](https://github.com/jonas/tig)
- [GitUp](https://github.com/git-up/GitUp) - [GitUp](https://github.com/git-up/GitUp)
- It would be nice to come up with a way to have the map view available in a terminal tool - It would be nice to come up with a way to have the map view available in a terminal tool
# Key Bindings
You can customize every keybing to your liking: See [Key Config](KEY_CONFIG.md).

View File

@ -0,0 +1,66 @@
// bit for modifiers
// bits: 0 None
// bits: 1 SHIFT
// bits: 2 CONTROL
(
tab_status: ( code: Char('1'), modifiers: ( bits: 0,),),
tab_log: ( code: Char('2'), modifiers: ( bits: 0,),),
tab_stashing: ( code: Char('3'), modifiers: ( bits: 0,),),
tab_stashes: ( code: Char('4'), modifiers: ( bits: 0,),),
tab_toggle: ( code: Tab, modifiers: ( bits: 0,),),
tab_toggle_reverse: ( code: BackTab, modifiers: ( bits: 0,),),
tab_toggle_reverse_windows: ( code: BackTab, modifiers: ( bits: 1,),),
focus_workdir: ( code: Char('w'), modifiers: ( bits: 0,),),
focus_stage: ( code: Char('s'), modifiers: ( bits: 0,),),
focus_right: ( code: Char('l'), modifiers: ( bits: 0,),),
focus_left: ( code: Char('h'), modifiers: ( bits: 0,),),
focus_above: ( code: Char('k'), modifiers: ( bits: 0,),),
focus_below: ( code: Char('j'), modifiers: ( bits: 0,),),
exit: ( code: Char('c'), modifiers: ( bits: 2,),),
exit_popup: ( code: Esc, modifiers: ( bits: 0,),),
close_msg: ( code: Enter, modifiers: ( bits: 0,),),
open_commit: ( code: Char('c'), modifiers: ( bits: 0,),),
open_commit_editor: ( code: Char('E'), modifiers: ( bits: 0,),),
open_help: ( code: F(1), modifiers: ( bits: 0,),),
move_left: ( code: Char('h'), modifiers: ( bits: 0,),),
move_right: ( code: Char('l'), modifiers: ( bits: 0,),),
home: ( code: Home, modifiers: ( bits: 0,),),
end: ( code: End, modifiers: ( bits: 0,),),
move_up: ( code: Char('k'), modifiers: ( bits: 0,),),
move_down: ( code: Char('j'), modifiers: ( bits: 0,),),
page_up: ( code: Char('u'), modifiers: ( bits: 2,),),
page_down: ( code: Char('d'), modifiers: ( bits: 2,),),
shift_up: ( code: Char('K'), modifiers: ( bits: 0,),),
shift_down: ( code: Char('J'), modifiers: ( bits: 0,),),
enter: ( code: Enter, modifiers: ( bits: 0,),),
edit_file: ( code: Char('I'), modifiers: ( bits: 0,),),
status_stage_file: ( code: Enter, modifiers: ( bits: 0,),),
status_stage_all: ( code: Char('a'), modifiers: ( bits: 0,),),
status_reset_file: ( code: Char('U'), modifiers: ( bits: 0,),),
diff_reset_hunk: ( code: Enter, modifiers: ( bits: 0,),),
status_ignore_file: ( code: Char('i'), modifiers: ( bits: 0,),),
stashing_save: ( code: Char('w'), modifiers: ( bits: 0,),),
stashing_toggle_untracked: ( code: Char('u'), modifiers: ( bits: 0,),),
stashing_toggle_index: ( code: Char('m'), modifiers: ( bits: 0,),),
stash_apply: ( code: Enter, modifiers: ( bits: 0,),),
stash_open: ( code: Char('l'), modifiers: ( bits: 0,),),
stash_drop: ( code: Char('D'), modifiers: ( bits: 0,),),
cmd_bar_toggle: ( code: Char('.'), modifiers: ( bits: 0,),),
log_commit_details: ( code: Enter, modifiers: ( bits: 0,),),
log_tag_commit: ( code: Char('t'), modifiers: ( bits: 0,),),
commit_amend: ( code: Char('A'), modifiers: ( bits: 0,),),
copy: ( code: Char('y'), modifiers: ( bits: 0,),),
)

View File

@ -8,9 +8,9 @@ use crate::{
ResetComponent, StashMsgComponent, TagCommitComponent, ResetComponent, StashMsgComponent, TagCommitComponent,
}, },
input::{Input, InputEvent, InputState}, input::{Input, InputEvent, InputState},
keys, keys::{KeyConfig, SharedKeyConfig},
queue::{Action, InternalEvent, NeedsUpdate, Queue}, queue::{Action, InternalEvent, NeedsUpdate, Queue},
strings::{self, commands, order}, strings::{self, order},
tabs::{Revlog, StashList, Stashing, Status}, tabs::{Revlog, StashList, Stashing, Status},
ui::style::{SharedTheme, Theme}, ui::style::{SharedTheme, Theme},
}; };
@ -49,6 +49,7 @@ pub struct App {
stashlist_tab: StashList, stashlist_tab: StashList,
queue: Queue, queue: Queue,
theme: SharedTheme, theme: SharedTheme,
key_config: SharedKeyConfig,
input: Input, input: Input,
// "Flags" // "Flags"
@ -66,45 +67,77 @@ impl App {
let queue = Queue::default(); let queue = Queue::default();
let theme = Rc::new(Theme::init()); let theme = Rc::new(Theme::init());
let key_config = Rc::new(KeyConfig::init());
Self { Self {
input, input,
reset: ResetComponent::new(queue.clone(), theme.clone()), reset: ResetComponent::new(
queue.clone(),
theme.clone(),
key_config.clone(),
),
commit: CommitComponent::new( commit: CommitComponent::new(
queue.clone(), queue.clone(),
theme.clone(), theme.clone(),
key_config.clone(),
), ),
stashmsg_popup: StashMsgComponent::new( stashmsg_popup: StashMsgComponent::new(
queue.clone(), queue.clone(),
theme.clone(), theme.clone(),
key_config.clone(),
), ),
inspect_commit_popup: InspectCommitComponent::new( inspect_commit_popup: InspectCommitComponent::new(
&queue, &queue,
sender, sender,
theme.clone(), theme.clone(),
key_config.clone(),
), ),
external_editor_popup: ExternalEditorComponent::new( external_editor_popup: ExternalEditorComponent::new(
theme.clone(), theme.clone(),
key_config.clone(),
), ),
tag_commit_popup: TagCommitComponent::new( tag_commit_popup: TagCommitComponent::new(
queue.clone(), queue.clone(),
theme.clone(), theme.clone(),
key_config.clone(),
), ),
do_quit: false, do_quit: false,
cmdbar: RefCell::new(CommandBar::new(theme.clone())), cmdbar: RefCell::new(CommandBar::new(
help: HelpComponent::new(theme.clone()), theme.clone(),
msg: MsgComponent::new(theme.clone()), key_config.clone(),
)),
help: HelpComponent::new(
theme.clone(),
key_config.clone(),
),
msg: MsgComponent::new(theme.clone(), key_config.clone()),
tab: 0, tab: 0,
revlog: Revlog::new(&queue, sender, theme.clone()), revlog: Revlog::new(
status_tab: Status::new(&queue, sender, theme.clone()), &queue,
sender,
theme.clone(),
key_config.clone(),
),
status_tab: Status::new(
&queue,
sender,
theme.clone(),
key_config.clone(),
),
stashing_tab: Stashing::new( stashing_tab: Stashing::new(
sender, sender,
&queue, &queue,
theme.clone(), theme.clone(),
key_config.clone(),
),
stashlist_tab: StashList::new(
&queue,
theme.clone(),
key_config.clone(),
), ),
stashlist_tab: StashList::new(&queue, theme.clone()),
queue, queue,
theme, theme,
key_config,
requires_redraw: Cell::new(false), requires_redraw: Cell::new(false),
file_to_open: None, file_to_open: None,
} }
@ -160,30 +193,26 @@ impl App {
if event_pump(ev, self.components_mut().as_mut_slice())? { if event_pump(ev, self.components_mut().as_mut_slice())? {
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 = if k == self.key_config.tab_toggle {
keys::TAB_TOGGLE => { self.toggle_tabs(false)?;
self.toggle_tabs(false)?; NeedsUpdate::COMMANDS
NeedsUpdate::COMMANDS } else if k == self.key_config.tab_toggle_reverse
} || k == self.key_config.tab_toggle_reverse_windows
keys::TAB_TOGGLE_REVERSE {
| keys::TAB_TOGGLE_REVERSE_WINDOWS => { self.toggle_tabs(true)?;
self.toggle_tabs(true)?; NeedsUpdate::COMMANDS
NeedsUpdate::COMMANDS } else if k == self.key_config.tab_status
} || k == self.key_config.tab_log
keys::TAB_1 || k == self.key_config.tab_stashing
| keys::TAB_2 || k == self.key_config.tab_stashes
| keys::TAB_3 {
| keys::TAB_4 => { self.switch_tab(k)?;
self.switch_tab(k)?; NeedsUpdate::COMMANDS
NeedsUpdate::COMMANDS } else if k == self.key_config.cmd_bar_toggle {
} self.cmdbar.borrow_mut().toggle_more();
NeedsUpdate::empty()
keys::CMD_BAR_TOGGLE => { } else {
self.cmdbar.borrow_mut().toggle_more(); NeedsUpdate::empty()
NeedsUpdate::empty()
}
_ => NeedsUpdate::empty(),
}; };
flags.insert(new_flags); flags.insert(new_flags);
@ -312,7 +341,7 @@ impl App {
fn check_quit_key(&mut self, ev: Event) -> bool { fn check_quit_key(&mut self, ev: Event) -> bool {
if let Event::Key(e) = ev { if let Event::Key(e) = ev {
if let keys::EXIT = e { if e == self.key_config.exit {
self.do_quit = true; self.do_quit = true;
return true; return true;
} }
@ -341,12 +370,14 @@ impl App {
} }
fn switch_tab(&mut self, k: KeyEvent) -> Result<()> { fn switch_tab(&mut self, k: KeyEvent) -> Result<()> {
match k { if k == self.key_config.tab_status {
keys::TAB_1 => self.set_tab(0)?, self.set_tab(0)?
keys::TAB_2 => self.set_tab(1)?, } else if k == self.key_config.tab_log {
keys::TAB_3 => self.set_tab(2)?, self.set_tab(1)?
keys::TAB_4 => self.set_tab(3)?, } else if k == self.key_config.tab_stashing {
_ => (), self.set_tab(2)?
} else if k == self.key_config.tab_stashes {
self.set_tab(3)?
} }
Ok(()) Ok(())
@ -458,7 +489,7 @@ impl App {
res.push( res.push(
CommandInfo::new( CommandInfo::new(
commands::TOGGLE_TABS, strings::commands::toggle_tabs(&self.key_config),
true, true,
!self.any_popup_visible(), !self.any_popup_visible(),
) )
@ -466,7 +497,9 @@ impl App {
); );
res.push( res.push(
CommandInfo::new( CommandInfo::new(
commands::TOGGLE_TABS_DIRECT, strings::commands::toggle_tabs_direct(
&self.key_config,
),
true, true,
!self.any_popup_visible(), !self.any_popup_visible(),
) )
@ -475,7 +508,7 @@ impl App {
res.push( res.push(
CommandInfo::new( CommandInfo::new(
commands::QUIT, strings::commands::quit(&self.key_config),
true, true,
!self.any_popup_visible(), !self.any_popup_visible(),
) )
@ -531,10 +564,10 @@ impl App {
}); });
let tabs = &[ let tabs = &[
strings::TAB_STATUS, strings::tab_status(&self.key_config),
strings::TAB_LOG, strings::tab_log(&self.key_config),
strings::TAB_STASHING, strings::tab_stashing(&self.key_config),
strings::TAB_STASHES, strings::tab_stashes(&self.key_config),
]; ];
f.render_widget( f.render_widget(
@ -547,7 +580,7 @@ impl App {
.titles(tabs) .titles(tabs)
.style(self.theme.tab(false)) .style(self.theme.tab(false))
.highlight_style(self.theme.tab(true)) .highlight_style(self.theme.tab(true))
.divider(strings::TAB_DIVIDER) .divider(&strings::tab_divider(&self.key_config))
.select(self.tab), .select(self.tab),
r, r,
); );

View File

@ -1,5 +1,6 @@
use crate::{ use crate::{
components::CommandInfo, strings, ui::style::SharedTheme, components::CommandInfo, keys::SharedKeyConfig, strings,
ui::style::SharedTheme,
}; };
use std::borrow::Cow; use std::borrow::Cow;
use tui::{ use tui::{
@ -27,6 +28,7 @@ pub struct CommandBar {
draw_list: Vec<DrawListEntry>, draw_list: Vec<DrawListEntry>,
cmd_infos: Vec<CommandInfo>, cmd_infos: Vec<CommandInfo>,
theme: SharedTheme, theme: SharedTheme,
key_config: SharedKeyConfig,
lines: u16, lines: u16,
width: u16, width: u16,
expandable: bool, expandable: bool,
@ -36,11 +38,15 @@ pub struct CommandBar {
const MORE_WIDTH: u16 = 11; const MORE_WIDTH: u16 = 11;
impl CommandBar { impl CommandBar {
pub const fn new(theme: SharedTheme) -> Self { pub const fn new(
theme: SharedTheme,
key_config: SharedKeyConfig,
) -> Self {
Self { Self {
draw_list: Vec::new(), draw_list: Vec::new(),
cmd_infos: Vec::new(), cmd_infos: Vec::new(),
theme, theme,
key_config,
lines: 0, lines: 0,
width: 0, width: 0,
expandable: false, expandable: false,
@ -58,7 +64,8 @@ impl CommandBar {
fn is_multiline(&self, width: u16) -> bool { fn is_multiline(&self, width: u16) -> bool {
let mut line_width = 0_usize; let mut line_width = 0_usize;
for c in &self.cmd_infos { for c in &self.cmd_infos {
let entry_w = UnicodeWidthStr::width(c.text.name); let entry_w =
UnicodeWidthStr::width(c.text.name.as_str());
if line_width + entry_w > width as usize { if line_width + entry_w > width as usize {
return true; return true;
@ -83,7 +90,8 @@ impl CommandBar {
let mut lines = 1_u16; let mut lines = 1_u16;
for c in &self.cmd_infos { for c in &self.cmd_infos {
let entry_w = UnicodeWidthStr::width(c.text.name); let entry_w =
UnicodeWidthStr::width(c.text.name.as_str());
if line_width + entry_w > width as usize { if line_width + entry_w > width as usize {
self.draw_list.push(DrawListEntry::LineBreak); self.draw_list.push(DrawListEntry::LineBreak);
@ -131,7 +139,9 @@ impl CommandBar {
} }
pub fn draw<B: Backend>(&self, f: &mut Frame<B>, r: Rect) { pub fn draw<B: Backend>(&self, f: &mut Frame<B>, r: Rect) {
let splitter = Text::Raw(Cow::from(strings::CMD_SPLITTER)); let splitter = Text::Raw(Cow::from(strings::cmd_splitter(
&self.key_config,
)));
let texts = self let texts = self
.draw_list .draw_list

View File

@ -5,7 +5,7 @@ use super::{
}; };
use crate::{ use crate::{
components::{CommandInfo, Component}, components::{CommandInfo, Component},
keys, keys::SharedKeyConfig,
queue::{Action, InternalEvent, NeedsUpdate, Queue, ResetItem}, queue::{Action, InternalEvent, NeedsUpdate, Queue, ResetItem},
strings, try_or_popup, strings, try_or_popup,
ui::style::SharedTheme, ui::style::SharedTheme,
@ -14,7 +14,6 @@ use anyhow::Result;
use asyncgit::{cached, sync, StatusItem, StatusItemType, CWD}; use asyncgit::{cached, sync, StatusItem, StatusItemType, CWD};
use crossterm::event::Event; use crossterm::event::Event;
use std::path::Path; use std::path::Path;
use strings::commands;
use tui::{backend::Backend, layout::Rect, Frame}; use tui::{backend::Backend, layout::Rect, Frame};
/// ///
@ -24,9 +23,10 @@ pub struct ChangesComponent {
is_working_dir: bool, is_working_dir: bool,
queue: Queue, queue: Queue,
branch_name: cached::BranchName, branch_name: cached::BranchName,
key_config: SharedKeyConfig,
} }
impl ChangesComponent { impl<'a> ChangesComponent {
/// ///
pub fn new( pub fn new(
title: &str, title: &str,
@ -34,6 +34,7 @@ impl ChangesComponent {
is_working_dir: bool, is_working_dir: bool,
queue: Queue, queue: Queue,
theme: SharedTheme, theme: SharedTheme,
key_config: SharedKeyConfig,
) -> Self { ) -> Self {
Self { Self {
title: title.into(), title: title.into(),
@ -42,10 +43,12 @@ impl ChangesComponent {
focus, focus,
Some(queue.clone()), Some(queue.clone()),
theme, theme,
key_config.clone(),
), ),
is_working_dir, is_working_dir,
queue, queue,
branch_name: cached::BranchName::new(CWD), branch_name: cached::BranchName::new(CWD),
key_config,
} }
} }
@ -206,39 +209,39 @@ impl Component for ChangesComponent {
if self.is_working_dir { if self.is_working_dir {
out.push(CommandInfo::new( out.push(CommandInfo::new(
commands::STAGE_ALL, strings::commands::stage_all(&self.key_config),
some_selection, some_selection,
self.focused(), self.focused(),
)); ));
out.push(CommandInfo::new( out.push(CommandInfo::new(
commands::STAGE_ITEM, strings::commands::stage_item(&self.key_config),
some_selection, some_selection,
self.focused(), self.focused(),
)); ));
out.push(CommandInfo::new( out.push(CommandInfo::new(
commands::RESET_ITEM, strings::commands::reset_item(&self.key_config),
some_selection, some_selection,
self.focused(), self.focused(),
)); ));
out.push(CommandInfo::new( out.push(CommandInfo::new(
commands::IGNORE_ITEM, strings::commands::ignore_item(&self.key_config),
some_selection, some_selection,
self.focused(), self.focused(),
)); ));
} else { } else {
out.push(CommandInfo::new( out.push(CommandInfo::new(
commands::UNSTAGE_ITEM, strings::commands::unstage_item(&self.key_config),
some_selection, some_selection,
self.focused(), self.focused(),
)); ));
out.push(CommandInfo::new( out.push(CommandInfo::new(
commands::UNSTAGE_ALL, strings::commands::unstage_all(&self.key_config),
some_selection, some_selection,
self.focused(), self.focused(),
)); ));
out.push( out.push(
CommandInfo::new( CommandInfo::new(
commands::COMMIT_OPEN, strings::commands::commit_open(&self.key_config),
!self.is_empty(), !self.is_empty(),
self.focused() || force_all, self.focused() || force_all,
) )
@ -256,57 +259,49 @@ impl Component for ChangesComponent {
if self.focused() { if self.focused() {
if let Event::Key(e) = ev { if let Event::Key(e) = ev {
return match e { return if e == self.key_config.open_commit
keys::OPEN_COMMIT && !self.is_working_dir
if !self.is_working_dir && !self.is_empty()
&& !self.is_empty() => {
{ self.queue
self.queue .borrow_mut()
.borrow_mut() .push_back(InternalEvent::OpenCommit);
.push_back(InternalEvent::OpenCommit); Ok(true)
Ok(true) } else if e == self.key_config.status_stage_file {
} try_or_popup!(
keys::STATUS_STAGE_FILE => { self,
"staging error:",
self.index_add_remove()
);
self.queue.borrow_mut().push_back(
InternalEvent::Update(NeedsUpdate::ALL),
);
Ok(true)
} else if e == self.key_config.status_stage_all
&& !self.is_empty()
{
if self.is_working_dir {
try_or_popup!( try_or_popup!(
self, self,
"staging error:", "staging error:",
self.index_add_remove() self.index_add_all()
); );
} else {
self.queue.borrow_mut().push_back( self.stage_remove_all()?;
InternalEvent::Update(NeedsUpdate::ALL),
);
Ok(true)
} }
Ok(true)
keys::STATUS_STAGE_ALL if !self.is_empty() => { } else if e == self.key_config.status_reset_file
if self.is_working_dir { && self.is_working_dir
try_or_popup!( {
self, Ok(self.dispatch_reset_workdir())
"staging error:", } else if e == self.key_config.status_ignore_file
self.index_add_all() && self.is_working_dir
); && !self.is_empty()
} else { {
self.stage_remove_all()?; Ok(self.add_to_ignore())
} } else {
Ok(false)
Ok(true)
}
keys::STATUS_RESET_FILE
if self.is_working_dir =>
{
Ok(self.dispatch_reset_workdir())
}
keys::STATUS_IGNORE_FILE
if self.is_working_dir
&& !self.is_empty() =>
{
Ok(self.add_to_ignore())
}
_ => Ok(false),
}; };
} }
} }

View File

@ -1,8 +1,8 @@
/// ///
#[derive(Copy, Clone, PartialEq, PartialOrd, Ord, Eq)] #[derive(Clone, PartialEq, PartialOrd, Ord, Eq)]
pub struct CommandText { pub struct CommandText {
/// ///
pub name: &'static str, pub name: String,
/// ///
pub desc: &'static str, pub desc: &'static str,
/// ///
@ -14,7 +14,7 @@ pub struct CommandText {
impl CommandText { impl CommandText {
/// ///
pub const fn new( pub const fn new(
name: &'static str, name: String,
desc: &'static str, desc: &'static str,
group: &'static str, group: &'static str,
) -> Self { ) -> Self {
@ -77,7 +77,7 @@ impl CommandInfo {
} }
/// ///
pub fn print(&self, out: &mut String) { pub fn print(&self, out: &mut String) {
out.push_str(self.text.name); out.push_str(&self.text.name);
} }
/// ///
pub fn show_in_quickbar(&self) -> bool { pub fn show_in_quickbar(&self) -> bool {

View File

@ -4,9 +4,10 @@ use super::{
ExternalEditorComponent, ExternalEditorComponent,
}; };
use crate::{ use crate::{
get_app_config_path, keys, get_app_config_path,
keys::SharedKeyConfig,
queue::{InternalEvent, NeedsUpdate, Queue}, queue::{InternalEvent, NeedsUpdate, Queue},
strings::{self, commands}, strings,
ui::style::SharedTheme, ui::style::SharedTheme,
}; };
use anyhow::Result; use anyhow::Result;
@ -26,6 +27,7 @@ pub struct CommitComponent {
input: TextInputComponent, input: TextInputComponent,
amend: Option<CommitId>, amend: Option<CommitId>,
queue: Queue, queue: Queue,
key_config: SharedKeyConfig,
} }
impl DrawableComponent for CommitComponent { impl DrawableComponent for CommitComponent {
@ -50,19 +52,21 @@ impl Component for CommitComponent {
if self.is_visible() || force_all { if self.is_visible() || force_all {
out.push(CommandInfo::new( out.push(CommandInfo::new(
commands::COMMIT_ENTER, strings::commands::commit_enter(&self.key_config),
self.can_commit(), self.can_commit(),
true, true,
)); ));
out.push(CommandInfo::new( out.push(CommandInfo::new(
commands::COMMIT_AMEND, strings::commands::commit_amend(&self.key_config),
self.can_amend(), self.can_amend(),
true, true,
)); ));
out.push(CommandInfo::new( out.push(CommandInfo::new(
commands::COMMIT_OPEN_EDITOR, strings::commands::commit_open_editor(
&self.key_config,
),
true, true,
true, true,
)); ));
@ -78,25 +82,19 @@ impl Component for CommitComponent {
} }
if let Event::Key(e) = ev { if let Event::Key(e) = ev {
match e { if e == self.key_config.enter && self.can_commit() {
keys::ENTER if self.can_commit() => { self.commit()?;
self.commit()?; } else if e == self.key_config.commit_amend
} && self.can_amend()
{
keys::COMMIT_AMEND if self.can_amend() => { self.amend()?;
self.amend()?; } else if e == self.key_config.open_commit_editor {
} self.queue.borrow_mut().push_back(
InternalEvent::OpenExternalEditor(None),
keys::OPEN_COMMIT_EDITOR => { );
self.queue.borrow_mut().push_back( self.hide();
InternalEvent::OpenExternalEditor(None), } else {
); }
self.hide();
}
_ => (),
};
// stop key event propagation // stop key event propagation
return Ok(true); return Ok(true);
} }
@ -117,7 +115,8 @@ impl Component for CommitComponent {
self.amend = None; self.amend = None;
self.input.clear(); self.input.clear();
self.input.set_title(strings::COMMIT_TITLE.into()); self.input
.set_title(strings::commit_title(&self.key_config));
self.input.show()?; self.input.show()?;
Ok(()) Ok(())
@ -126,15 +125,21 @@ impl Component for CommitComponent {
impl CommitComponent { impl CommitComponent {
/// ///
pub fn new(queue: Queue, theme: SharedTheme) -> Self { pub fn new(
queue: Queue,
theme: SharedTheme,
key_config: SharedKeyConfig,
) -> Self {
Self { Self {
queue, queue,
amend: None, amend: None,
input: TextInputComponent::new( input: TextInputComponent::new(
theme, theme,
key_config.clone(),
"", "",
strings::COMMIT_MSG, &strings::commit_msg(&key_config),
), ),
key_config,
} }
} }
@ -150,7 +155,10 @@ impl CommitComponent {
"{}\n", "{}\n",
self.input.get_text() self.input.get_text()
))?; ))?;
file.write_all(strings::COMMIT_EDITOR_MSG.as_bytes())?; file.write_all(
strings::commit_editor_msg(&self.key_config)
.as_bytes(),
)?;
} }
ExternalEditorComponent::open_file_in_editor(&config_path)?; ExternalEditorComponent::open_file_in_editor(&config_path)?;
@ -251,7 +259,8 @@ impl CommitComponent {
let details = sync::get_commit_details(CWD, id)?; let details = sync::get_commit_details(CWD, id)?;
self.input.set_title(strings::COMMIT_TITLE_AMEND.into()); self.input
.set_title(strings::commit_title_amend(&self.key_config));
if let Some(msg) = details.message { if let Some(msg) = details.message {
self.input.set_text(msg.combine()); self.input.set_text(msg.combine());

View File

@ -3,8 +3,8 @@ use crate::{
dialog_paragraph, utils::time_to_string, CommandBlocking, dialog_paragraph, utils::time_to_string, CommandBlocking,
CommandInfo, Component, DrawableComponent, ScrollType, CommandInfo, Component, DrawableComponent, ScrollType,
}, },
keys, keys::SharedKeyConfig,
strings::{self, commands, order}, strings::{self, order},
ui::style::SharedTheme, ui::style::SharedTheme,
}; };
use anyhow::Result; use anyhow::Result;
@ -24,6 +24,13 @@ use tui::{
Frame, Frame,
}; };
enum Detail {
Author,
Date,
Commiter,
Sha,
}
pub struct DetailsComponent { pub struct DetailsComponent {
data: Option<CommitDetails>, data: Option<CommitDetails>,
tags: Vec<String>, tags: Vec<String>,
@ -31,6 +38,7 @@ pub struct DetailsComponent {
focused: bool, focused: bool,
current_size: Cell<(u16, u16)>, current_size: Cell<(u16, u16)>,
scroll_top: Cell<usize>, scroll_top: Cell<usize>,
key_config: SharedKeyConfig,
} }
type WrappedCommitMessage<'a> = type WrappedCommitMessage<'a> =
@ -38,7 +46,11 @@ type WrappedCommitMessage<'a> =
impl DetailsComponent { impl DetailsComponent {
/// ///
pub const fn new(theme: SharedTheme, focused: bool) -> Self { pub const fn new(
theme: SharedTheme,
key_config: SharedKeyConfig,
focused: bool,
) -> Self {
Self { Self {
data: None, data: None,
tags: Vec::new(), tags: Vec::new(),
@ -46,6 +58,7 @@ impl DetailsComponent {
focused, focused,
current_size: Cell::new((0, 0)), current_size: Cell::new((0, 0)),
scroll_top: Cell::new(0), scroll_top: Cell::new(0),
key_config,
} }
} }
@ -147,15 +160,41 @@ impl DetailsComponent {
.collect() .collect()
} }
fn style_detail(&self, field: &Detail) -> Text {
match field {
Detail::Author => Text::Styled(
Cow::from(strings::commit::details_author(
&self.key_config,
)),
self.theme.text(false, false),
),
Detail::Date => Text::Styled(
Cow::from(strings::commit::details_date(
&self.key_config,
)),
self.theme.text(false, false),
),
Detail::Commiter => Text::Styled(
Cow::from(strings::commit::details_committer(
&self.key_config,
)),
self.theme.text(false, false),
),
Detail::Sha => Text::Styled(
Cow::from(strings::commit::details_tags(
&self.key_config,
)),
self.theme.text(false, false),
),
}
}
fn get_text_info(&self) -> Vec<Text> { fn get_text_info(&self) -> Vec<Text> {
let new_line = Text::Raw(Cow::from("\n")); let new_line = Text::Raw(Cow::from("\n"));
if let Some(ref data) = self.data { if let Some(ref data) = self.data {
let mut res = vec![ let mut res = vec![
Text::Styled( self.style_detail(&Detail::Author),
Cow::from(strings::commit::DETAILS_AUTHOR),
self.theme.text(false, false),
),
Text::Styled( Text::Styled(
Cow::from(format!( Cow::from(format!(
"{} <{}>", "{} <{}>",
@ -164,10 +203,7 @@ impl DetailsComponent {
self.theme.text(true, false), self.theme.text(true, false),
), ),
new_line.clone(), new_line.clone(),
Text::Styled( self.style_detail(&Detail::Date),
Cow::from(strings::commit::DETAILS_DATE),
self.theme.text(false, false),
),
Text::Styled( Text::Styled(
Cow::from(time_to_string( Cow::from(time_to_string(
data.author.time, data.author.time,
@ -180,10 +216,7 @@ impl DetailsComponent {
if let Some(ref committer) = data.committer { if let Some(ref committer) = data.committer {
res.extend(vec![ res.extend(vec![
Text::Styled( self.style_detail(&Detail::Commiter),
Cow::from(strings::commit::DETAILS_COMMITTER),
self.theme.text(false, false),
),
Text::Styled( Text::Styled(
Cow::from(format!( Cow::from(format!(
"{} <{}>", "{} <{}>",
@ -192,10 +225,7 @@ impl DetailsComponent {
self.theme.text(true, false), self.theme.text(true, false),
), ),
new_line.clone(), new_line.clone(),
Text::Styled( self.style_detail(&Detail::Date),
Cow::from(strings::commit::DETAILS_DATE),
self.theme.text(false, false),
),
Text::Styled( Text::Styled(
Cow::from(time_to_string( Cow::from(time_to_string(
committer.time, committer.time,
@ -209,7 +239,9 @@ impl DetailsComponent {
res.extend(vec![ res.extend(vec![
Text::Styled( Text::Styled(
Cow::from(strings::commit::DETAILS_SHA), Cow::from(strings::commit::details_sha(
&self.key_config,
)),
self.theme.text(false, false), self.theme.text(false, false),
), ),
Text::Styled( Text::Styled(
@ -220,11 +252,7 @@ impl DetailsComponent {
]); ]);
if !self.tags.is_empty() { if !self.tags.is_empty() {
res.push(Text::Styled( res.push(self.style_detail(&Detail::Sha));
Cow::from(strings::commit::DETAILS_TAGS),
self.theme.text(false, false),
));
res.extend( res.extend(
self.tags self.tags
.iter() .iter()
@ -295,7 +323,9 @@ impl DrawableComponent for DetailsComponent {
f.render_widget( f.render_widget(
dialog_paragraph( dialog_paragraph(
strings::commit::DETAILS_INFO_TITLE, &strings::commit::details_info_title(
&self.key_config,
),
self.get_text_info().iter(), self.get_text_info().iter(),
&self.theme, &self.theme,
false, false,
@ -319,7 +349,9 @@ impl DrawableComponent for DetailsComponent {
f.render_widget( f.render_widget(
dialog_paragraph( dialog_paragraph(
strings::commit::DETAILS_MESSAGE_TITLE, &strings::commit::details_message_title(
&self.key_config,
),
wrapped_lines.iter(), wrapped_lines.iter(),
&self.theme, &self.theme,
self.focused, self.focused,
@ -344,7 +376,9 @@ impl Component for DetailsComponent {
out.push( out.push(
CommandInfo::new( CommandInfo::new(
commands::NAVIGATE_COMMIT_MESSAGE, strings::commands::navigate_commit_message(
&self.key_config,
),
number_of_lines > 0, number_of_lines > 0,
self.focused || force_all, self.focused || force_all,
) )
@ -357,20 +391,20 @@ impl Component for DetailsComponent {
fn event(&mut self, event: Event) -> Result<bool> { fn event(&mut self, event: Event) -> Result<bool> {
if self.focused { if self.focused {
if let Event::Key(e) = event { if let Event::Key(e) = event {
return match e { return if e == self.key_config.move_up {
keys::MOVE_UP => { self.move_scroll_top(ScrollType::Up)
self.move_scroll_top(ScrollType::Up) } else if e == self.key_config.move_down {
} self.move_scroll_top(ScrollType::Down)
keys::MOVE_DOWN => { } else if e == self.key_config.home
self.move_scroll_top(ScrollType::Down) || e == self.key_config.shift_up
} {
keys::HOME | keys::SHIFT_UP => { self.move_scroll_top(ScrollType::Home)
self.move_scroll_top(ScrollType::Home) } else if e == self.key_config.end
} || e == self.key_config.shift_down
keys::END | keys::SHIFT_DOWN => { {
self.move_scroll_top(ScrollType::End) self.move_scroll_top(ScrollType::End)
} } else {
_ => Ok(false), Ok(false)
}; };
} }
} }

View File

@ -5,7 +5,8 @@ use super::{
Component, DrawableComponent, FileTreeComponent, Component, DrawableComponent, FileTreeComponent,
}; };
use crate::{ use crate::{
accessors, keys, queue::Queue, strings, ui::style::SharedTheme, accessors, keys::SharedKeyConfig, queue::Queue, strings,
ui::style::SharedTheme,
}; };
use anyhow::Result; use anyhow::Result;
use asyncgit::{ use asyncgit::{
@ -26,6 +27,7 @@ pub struct CommitDetailsComponent {
file_tree: FileTreeComponent, file_tree: FileTreeComponent,
git_commit_files: AsyncCommitFiles, git_commit_files: AsyncCommitFiles,
visible: bool, visible: bool,
key_config: SharedKeyConfig,
} }
impl CommitDetailsComponent { impl CommitDetailsComponent {
@ -36,17 +38,24 @@ impl CommitDetailsComponent {
queue: &Queue, queue: &Queue,
sender: &Sender<AsyncNotification>, sender: &Sender<AsyncNotification>,
theme: SharedTheme, theme: SharedTheme,
key_config: SharedKeyConfig,
) -> Self { ) -> Self {
Self { Self {
details: DetailsComponent::new(theme.clone(), false), details: DetailsComponent::new(
theme.clone(),
key_config.clone(),
false,
),
git_commit_files: AsyncCommitFiles::new(sender), git_commit_files: AsyncCommitFiles::new(sender),
file_tree: FileTreeComponent::new( file_tree: FileTreeComponent::new(
"", "",
false, false,
Some(queue.clone()), Some(queue.clone()),
theme, theme,
key_config.clone(),
), ),
visible: false, visible: false,
key_config,
} }
} }
@ -55,7 +64,7 @@ impl CommitDetailsComponent {
format!( format!(
"{} {}", "{} {}",
strings::commit::DETAILS_FILES_TITLE, strings::commit::details_files_title(&self.key_config),
files_count files_count
) )
} }
@ -148,22 +157,20 @@ impl Component for CommitDetailsComponent {
if self.focused() { if self.focused() {
if let Event::Key(e) = ev { if let Event::Key(e) = ev {
return match e { return if e == self.key_config.focus_below
keys::FOCUS_BELOW if (self.details.focused()) => { && self.details.focused()
self.details.focus(false); {
self.file_tree.focus(true); self.details.focus(false);
self.file_tree.focus(true);
return Ok(true); Ok(true)
} } else if e == self.key_config.focus_above
keys::FOCUS_ABOVE && self.file_tree.focused()
if (self.file_tree.focused()) => {
{ self.file_tree.focus(false);
self.file_tree.focus(false); self.details.focus(true);
self.details.focus(true); Ok(true)
} else {
return Ok(true); Ok(false)
}
_ => Ok(false),
}; };
} }
} }

View File

@ -4,8 +4,8 @@ use crate::{
CommandBlocking, CommandInfo, Component, DrawableComponent, CommandBlocking, CommandInfo, Component, DrawableComponent,
ScrollType, ScrollType,
}, },
keys, keys::SharedKeyConfig,
strings::commands, strings,
ui::calc_scroll_top, ui::calc_scroll_top,
ui::style::{SharedTheme, Theme}, ui::style::{SharedTheme, Theme},
}; };
@ -37,11 +37,16 @@ pub struct CommitList {
current_size: Cell<(u16, u16)>, current_size: Cell<(u16, u16)>,
scroll_top: Cell<usize>, scroll_top: Cell<usize>,
theme: SharedTheme, theme: SharedTheme,
key_config: SharedKeyConfig,
} }
impl CommitList { impl CommitList {
/// ///
pub fn new(title: &str, theme: SharedTheme) -> Self { pub fn new(
title: &str,
theme: SharedTheme,
key_config: SharedKeyConfig,
) -> Self {
Self { Self {
items: ItemBatch::default(), items: ItemBatch::default(),
selection: 0, selection: 0,
@ -52,6 +57,7 @@ impl CommitList {
current_size: Cell::new((0, 0)), current_size: Cell::new((0, 0)),
scroll_top: Cell::new(0), scroll_top: Cell::new(0),
theme, theme,
key_config,
title: String::from(title), title: String::from(title),
} }
} }
@ -172,10 +178,10 @@ impl CommitList {
self.scroll_state.1 = speed.min(SCROLL_SPEED_MAX); self.scroll_state.1 = speed.min(SCROLL_SPEED_MAX);
} }
fn add_entry<'a>( fn add_entry<'b>(
e: &'a LogEntry, e: &'b LogEntry,
selected: bool, selected: bool,
txt: &mut Vec<Text<'a>>, txt: &mut Vec<Text<'b>>,
tags: Option<String>, tags: Option<String>,
theme: &Theme, theme: &Theme,
width: usize, width: usize,
@ -331,28 +337,25 @@ impl DrawableComponent for CommitList {
impl Component for CommitList { impl Component for CommitList {
fn event(&mut self, ev: Event) -> Result<bool> { fn event(&mut self, ev: Event) -> Result<bool> {
if let Event::Key(k) = ev { if let Event::Key(k) = ev {
let selection_changed = match k { let selection_changed = if k == self.key_config.move_up {
keys::MOVE_UP => { self.move_selection(ScrollType::Up)?
self.move_selection(ScrollType::Up)? } else if k == self.key_config.move_down {
} self.move_selection(ScrollType::Down)?
keys::MOVE_DOWN => { } else if k == self.key_config.shift_up
self.move_selection(ScrollType::Down)? || k == self.key_config.home
} {
keys::SHIFT_UP | keys::HOME => { self.move_selection(ScrollType::Home)?
self.move_selection(ScrollType::Home)? } else if k == self.key_config.shift_down
} || k == self.key_config.end
keys::SHIFT_DOWN | keys::END => { {
self.move_selection(ScrollType::End)? self.move_selection(ScrollType::End)?
} } else if k == self.key_config.page_up {
keys::PAGE_UP => { self.move_selection(ScrollType::PageUp)?
self.move_selection(ScrollType::PageUp)? } else if k == self.key_config.page_down {
} self.move_selection(ScrollType::PageDown)?
keys::PAGE_DOWN => { } else {
self.move_selection(ScrollType::PageDown)? false
}
_ => false,
}; };
return Ok(selection_changed); return Ok(selection_changed);
} }
@ -365,7 +368,7 @@ impl Component for CommitList {
_force_all: bool, _force_all: bool,
) -> CommandBlocking { ) -> CommandBlocking {
out.push(CommandInfo::new( out.push(CommandInfo::new(
commands::SCROLL, strings::commands::scroll(&self.key_config),
self.selected_entry().is_some(), self.selected_entry().is_some(),
true, true,
)); ));

View File

@ -3,10 +3,9 @@ use super::{
}; };
use crate::{ use crate::{
components::{CommandInfo, Component}, components::{CommandInfo, Component},
keys, keys::SharedKeyConfig,
queue::{Action, InternalEvent, NeedsUpdate, Queue, ResetItem}, queue::{Action, InternalEvent, NeedsUpdate, Queue, ResetItem},
strings::{self, commands}, strings, try_or_popup,
try_or_popup,
ui::{calc_scroll_top, style::SharedTheme}, ui::{calc_scroll_top, style::SharedTheme},
}; };
use asyncgit::{hash, sync, DiffLine, DiffLineType, FileDiff, CWD}; use asyncgit::{hash, sync, DiffLine, DiffLineType, FileDiff, CWD};
@ -106,6 +105,7 @@ pub struct DiffComponent {
scroll_top: Cell<usize>, scroll_top: Cell<usize>,
queue: Queue, queue: Queue,
theme: SharedTheme, theme: SharedTheme,
key_config: SharedKeyConfig,
is_immutable: bool, is_immutable: bool,
} }
@ -114,6 +114,7 @@ impl DiffComponent {
pub fn new( pub fn new(
queue: Queue, queue: Queue,
theme: SharedTheme, theme: SharedTheme,
key_config: SharedKeyConfig,
is_immutable: bool, is_immutable: bool,
) -> Self { ) -> Self {
Self { Self {
@ -127,6 +128,7 @@ impl DiffComponent {
selection: Selection::Single(0), selection: Selection::Single(0),
scroll_top: Cell::new(0), scroll_top: Cell::new(0),
theme, theme,
key_config,
is_immutable, is_immutable,
} }
} }
@ -556,12 +558,15 @@ impl DrawableComponent for DiffComponent {
self.selection.get_end(), self.selection.get_end(),
)); ));
let title = let title = format!(
format!("{}{}", strings::TITLE_DIFF, self.current.path); "{}{}",
strings::title_diff(&self.key_config),
self.current.path
);
let txt = if self.pending { let txt = if self.pending {
vec![Text::Styled( vec![Text::Styled(
Cow::from(strings::LOADING_TEXT), Cow::from(strings::loading_text(&self.key_config)),
self.theme.text(false, false), self.theme.text(false, false),
)] )]
} else { } else {
@ -590,20 +595,20 @@ impl Component for DiffComponent {
_force_all: bool, _force_all: bool,
) -> CommandBlocking { ) -> CommandBlocking {
out.push(CommandInfo::new( out.push(CommandInfo::new(
commands::SCROLL, strings::commands::scroll(&self.key_config),
self.can_scroll(), self.can_scroll(),
self.focused, self.focused,
)); ));
out.push(CommandInfo::new( out.push(CommandInfo::new(
commands::COPY, strings::commands::copy(&self.key_config),
true, true,
self.focused, self.focused,
)); ));
out.push( out.push(
CommandInfo::new( CommandInfo::new(
commands::DIFF_HOME_END, strings::commands::diff_home_end(&self.key_config),
self.can_scroll(), self.can_scroll(),
self.focused, self.focused,
) )
@ -612,17 +617,17 @@ impl Component for DiffComponent {
if !self.is_immutable { if !self.is_immutable {
out.push(CommandInfo::new( out.push(CommandInfo::new(
commands::DIFF_HUNK_REMOVE, strings::commands::diff_hunk_remove(&self.key_config),
self.selected_hunk.is_some(), self.selected_hunk.is_some(),
self.focused && self.is_stage(), self.focused && self.is_stage(),
)); ));
out.push(CommandInfo::new( out.push(CommandInfo::new(
commands::DIFF_HUNK_ADD, strings::commands::diff_hunk_add(&self.key_config),
self.selected_hunk.is_some(), self.selected_hunk.is_some(),
self.focused && !self.is_stage(), self.focused && !self.is_stage(),
)); ));
out.push(CommandInfo::new( out.push(CommandInfo::new(
commands::DIFF_HUNK_REVERT, strings::commands::diff_hunk_revert(&self.key_config),
self.selected_hunk.is_some(), self.selected_hunk.is_some(),
self.focused && !self.is_stage(), self.focused && !self.is_stage(),
)); ));
@ -634,64 +639,56 @@ impl Component for DiffComponent {
fn event(&mut self, ev: Event) -> Result<bool> { fn event(&mut self, ev: Event) -> Result<bool> {
if self.focused { if self.focused {
if let Event::Key(e) = ev { if let Event::Key(e) = ev {
return match e { return if e == self.key_config.move_down {
keys::MOVE_DOWN => { self.move_selection(ScrollType::Down)?;
self.move_selection(ScrollType::Down)?; Ok(true)
Ok(true) } else if e == self.key_config.shift_down {
self.modify_selection(Direction::Down)?;
Ok(true)
} else if e == self.key_config.shift_up {
self.modify_selection(Direction::Up)?;
Ok(true)
} else if e == self.key_config.end {
self.move_selection(ScrollType::End)?;
Ok(true)
} else if e == self.key_config.home {
self.move_selection(ScrollType::Home)?;
Ok(true)
} else if e == self.key_config.move_up {
self.move_selection(ScrollType::Up)?;
Ok(true)
} else if e == self.key_config.page_up {
self.move_selection(ScrollType::PageUp)?;
Ok(true)
} else if e == self.key_config.page_down {
self.move_selection(ScrollType::PageDown)?;
Ok(true)
} else if e == self.key_config.enter
&& !self.is_immutable
{
if self.current.is_stage {
self.unstage_hunk()?;
} else {
self.stage_hunk()?;
} }
keys::SHIFT_DOWN => { Ok(true)
self.modify_selection(Direction::Down)?; } else if e == self.key_config.diff_reset_hunk
Ok(true) && !self.is_immutable
} && !self.is_stage()
keys::SHIFT_UP => { {
self.modify_selection(Direction::Up)?; if let Some(diff) = &self.diff {
Ok(true) if diff.untracked {
} self.reset_untracked()?;
keys::END => {
self.move_selection(ScrollType::End)?;
Ok(true)
}
keys::HOME => {
self.move_selection(ScrollType::Home)?;
Ok(true)
}
keys::MOVE_UP => {
self.move_selection(ScrollType::Up)?;
Ok(true)
}
keys::PAGE_UP => {
self.move_selection(ScrollType::PageUp)?;
Ok(true)
}
keys::PAGE_DOWN => {
self.move_selection(ScrollType::PageDown)?;
Ok(true)
}
keys::ENTER if !self.is_immutable => {
if self.current.is_stage {
self.unstage_hunk()?;
} else { } else {
self.stage_hunk()?; self.reset_hunk()?;
} }
Ok(true)
} }
keys::DIFF_RESET_HUNK Ok(true)
if !self.is_immutable && !self.is_stage() => } else if e == self.key_config.copy {
{ self.copy_selection()?;
if let Some(diff) = &self.diff { Ok(true)
if diff.untracked { } else {
self.reset_untracked()?; Ok(false)
} else {
self.reset_hunk()?;
}
}
Ok(true)
}
keys::COPY => {
self.copy_selection()?;
Ok(true)
}
_ => Ok(false),
}; };
} }
} }

View File

@ -3,6 +3,7 @@ use crate::{
visibility_blocking, CommandBlocking, CommandInfo, Component, visibility_blocking, CommandBlocking, CommandInfo, Component,
DrawableComponent, DrawableComponent,
}, },
keys::SharedKeyConfig,
strings, strings,
ui::{self, style::SharedTheme}, ui::{self, style::SharedTheme},
}; };
@ -27,14 +28,19 @@ use tui::{
pub struct ExternalEditorComponent { pub struct ExternalEditorComponent {
visible: bool, visible: bool,
theme: SharedTheme, theme: SharedTheme,
key_config: SharedKeyConfig,
} }
impl ExternalEditorComponent { impl ExternalEditorComponent {
/// ///
pub fn new(theme: SharedTheme) -> Self { pub fn new(
theme: SharedTheme,
key_config: SharedKeyConfig,
) -> Self {
Self { Self {
visible: false, visible: false,
theme, theme,
key_config,
} }
} }
@ -93,8 +99,9 @@ impl DrawableComponent for ExternalEditorComponent {
_rect: Rect, _rect: Rect,
) -> Result<()> { ) -> Result<()> {
if self.visible { if self.visible {
let txt = let txt = vec![Text::Raw(
vec![Text::Raw(strings::MSG_OPENING_EDITOR.into())]; strings::msg_opening_editor(&self.key_config).into(),
)];
let area = ui::centered_rect_absolute(25, 3, f.size()); let area = ui::centered_rect_absolute(25, 3, f.size());
f.render_widget(Clear, area); f.render_widget(Clear, area);

View File

@ -7,9 +7,9 @@ use super::{
}; };
use crate::{ use crate::{
components::{CommandInfo, Component}, components::{CommandInfo, Component},
keys, keys::SharedKeyConfig,
queue::{InternalEvent, NeedsUpdate, Queue}, queue::{InternalEvent, NeedsUpdate, Queue},
strings::{self, commands, order}, strings::{self, order},
ui, ui,
ui::style::SharedTheme, ui::style::SharedTheme,
}; };
@ -29,6 +29,7 @@ pub struct FileTreeComponent {
show_selection: bool, show_selection: bool,
queue: Option<Queue>, queue: Option<Queue>,
theme: SharedTheme, theme: SharedTheme,
key_config: SharedKeyConfig,
scroll_top: Cell<usize>, scroll_top: Cell<usize>,
} }
@ -39,6 +40,7 @@ impl FileTreeComponent {
focus: bool, focus: bool,
queue: Option<Queue>, queue: Option<Queue>,
theme: SharedTheme, theme: SharedTheme,
key_config: SharedKeyConfig,
) -> Self { ) -> Self {
Self { Self {
title: title.to_string(), title: title.to_string(),
@ -48,6 +50,7 @@ impl FileTreeComponent {
show_selection: focus, show_selection: focus,
queue, queue,
theme, theme,
key_config,
scroll_top: Cell::new(0), scroll_top: Cell::new(0),
pending: true, pending: true,
} }
@ -134,12 +137,12 @@ impl FileTreeComponent {
changed changed
} }
fn item_to_text<'a>( fn item_to_text<'b>(
item: &FileTreeItem, item: &FileTreeItem,
width: u16, width: u16,
selected: bool, selected: bool,
theme: &'a SharedTheme, theme: &'b SharedTheme,
) -> Option<Text<'a>> { ) -> Option<Text<'b>> {
let indent_str = if item.info.indent == 0 { let indent_str = if item.info.indent == 0 {
String::from("") String::from("")
} else { } else {
@ -223,7 +226,7 @@ impl DrawableComponent for FileTreeComponent {
) -> Result<()> { ) -> Result<()> {
if self.pending { if self.pending {
let items = vec![Text::Styled( let items = vec![Text::Styled(
Cow::from(strings::LOADING_TEXT), Cow::from(strings::loading_text(&self.key_config)),
self.theme.text(false, false), self.theme.text(false, false),
)]; )];
@ -309,7 +312,7 @@ impl Component for FileTreeComponent {
) -> CommandBlocking { ) -> CommandBlocking {
out.push( out.push(
CommandInfo::new( CommandInfo::new(
commands::NAVIGATE_TREE, strings::commands::navigate_tree(&self.key_config),
!self.is_empty(), !self.is_empty(),
self.focused || force_all, self.focused || force_all,
) )
@ -322,26 +325,24 @@ impl Component for FileTreeComponent {
fn event(&mut self, ev: Event) -> Result<bool> { fn event(&mut self, ev: Event) -> Result<bool> {
if self.focused { if self.focused {
if let Event::Key(e) = ev { if let Event::Key(e) = ev {
return match e { return if e == self.key_config.move_down {
keys::MOVE_DOWN => { Ok(self.move_selection(MoveSelection::Down))
Ok(self.move_selection(MoveSelection::Down)) } else if e == self.key_config.move_up {
} Ok(self.move_selection(MoveSelection::Up))
keys::MOVE_UP => { } else if e == self.key_config.home
Ok(self.move_selection(MoveSelection::Up)) || e == self.key_config.shift_up
} {
keys::HOME | keys::SHIFT_UP => { Ok(self.move_selection(MoveSelection::Home))
Ok(self.move_selection(MoveSelection::Home)) } else if e == self.key_config.end
} || e == self.key_config.shift_down
keys::END | keys::SHIFT_DOWN => { {
Ok(self.move_selection(MoveSelection::End)) Ok(self.move_selection(MoveSelection::End))
} } else if e == self.key_config.move_left {
keys::MOVE_LEFT => { Ok(self.move_selection(MoveSelection::Left))
Ok(self.move_selection(MoveSelection::Left)) } else if e == self.key_config.move_right {
} Ok(self.move_selection(MoveSelection::Right))
keys::MOVE_RIGHT => { } else {
Ok(self.move_selection(MoveSelection::Right)) Ok(false)
}
_ => Ok(false),
}; };
} }
} }

View File

@ -2,12 +2,7 @@ use super::{
visibility_blocking, CommandBlocking, CommandInfo, Component, visibility_blocking, CommandBlocking, CommandInfo, Component,
DrawableComponent, DrawableComponent,
}; };
use crate::{ use crate::{keys::SharedKeyConfig, strings, ui, version::Version};
keys,
strings::{self, commands},
ui,
version::Version,
};
use asyncgit::hash; use asyncgit::hash;
use crossterm::event::Event; use crossterm::event::Event;
use itertools::Itertools; use itertools::Itertools;
@ -29,6 +24,7 @@ pub struct HelpComponent {
visible: bool, visible: bool,
selection: u16, selection: u16,
theme: SharedTheme, theme: SharedTheme,
key_config: SharedKeyConfig,
} }
impl DrawableComponent for HelpComponent { impl DrawableComponent for HelpComponent {
@ -49,7 +45,7 @@ impl DrawableComponent for HelpComponent {
f.render_widget(Clear, area); f.render_widget(Clear, area);
f.render_widget( f.render_widget(
Block::default() Block::default()
.title(strings::HELP_TITLE) .title(&strings::help_title(&self.key_config))
.borders(Borders::ALL) .borders(Borders::ALL)
.border_type(BorderType::Thick), .border_type(BorderType::Thick),
area, area,
@ -104,10 +100,14 @@ impl Component for HelpComponent {
} }
if self.visible { if self.visible {
out.push(CommandInfo::new(commands::SCROLL, true, true)); out.push(CommandInfo::new(
strings::commands::scroll(&self.key_config),
true,
true,
));
out.push(CommandInfo::new( out.push(CommandInfo::new(
commands::CLOSE_POPUP, strings::commands::close_popup(&self.key_config),
true, true,
true, true,
)); ));
@ -115,8 +115,12 @@ impl Component for HelpComponent {
if !self.visible || force_all { if !self.visible || force_all {
out.push( out.push(
CommandInfo::new(commands::HELP_OPEN, true, true) CommandInfo::new(
.order(99), strings::commands::help_open(&self.key_config),
true,
true,
)
.order(99),
); );
} }
@ -126,18 +130,24 @@ impl Component for HelpComponent {
fn event(&mut self, ev: Event) -> Result<bool> { fn event(&mut self, ev: Event) -> Result<bool> {
if self.visible { if self.visible {
if let Event::Key(e) = ev { if let Event::Key(e) = ev {
match e { if e == self.key_config.exit_popup {
keys::EXIT_POPUP => self.hide(), self.hide()
keys::MOVE_DOWN => self.move_selection(true), } else if e == self.key_config.move_down {
keys::MOVE_UP => self.move_selection(false), self.move_selection(true)
_ => (), } else if e == self.key_config.move_up {
self.move_selection(false)
} else {
} }
} }
Ok(true) Ok(true)
} else if let Event::Key(keys::OPEN_HELP) = ev { } else if let Event::Key(k) = ev {
self.show()?; if k == self.key_config.open_help {
Ok(true) self.show()?;
Ok(true)
} else {
Ok(false)
}
} else { } else {
Ok(false) Ok(false)
} }
@ -159,12 +169,16 @@ impl Component for HelpComponent {
} }
impl HelpComponent { impl HelpComponent {
pub const fn new(theme: SharedTheme) -> Self { pub const fn new(
theme: SharedTheme,
key_config: SharedKeyConfig,
) -> Self {
Self { Self {
cmds: vec![], cmds: vec![],
visible: false, visible: false,
selection: 0, selection: 0,
theme, theme,
key_config,
} }
} }
/// ///
@ -173,8 +187,8 @@ impl HelpComponent {
.into_iter() .into_iter()
.filter(|e| !e.text.hide_help) .filter(|e| !e.text.hide_help)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
self.cmds.sort_by_key(|e| e.text); self.cmds.sort_by_key(|e| e.text.clone());
self.cmds.dedup_by_key(|e| e.text); self.cmds.dedup_by_key(|e| e.text.clone());
self.cmds.sort_by_key(|e| hash(&e.text.group)); self.cmds.sort_by_key(|e| hash(&e.text.group));
} }

View File

@ -4,7 +4,7 @@ use super::{
DrawableComponent, DrawableComponent,
}; };
use crate::{ use crate::{
accessors, keys, queue::Queue, strings::commands, accessors, keys::SharedKeyConfig, queue::Queue, strings,
ui::style::SharedTheme, ui::style::SharedTheme,
}; };
use anyhow::Result; use anyhow::Result;
@ -28,6 +28,7 @@ pub struct InspectCommitComponent {
details: CommitDetailsComponent, details: CommitDetailsComponent,
git_diff: AsyncDiff, git_diff: AsyncDiff,
visible: bool, visible: bool,
key_config: SharedKeyConfig,
} }
impl DrawableComponent for InspectCommitComponent { impl DrawableComponent for InspectCommitComponent {
@ -78,18 +79,22 @@ impl Component for InspectCommitComponent {
); );
out.push( out.push(
CommandInfo::new(commands::CLOSE_POPUP, true, true) CommandInfo::new(
.order(1), strings::commands::close_popup(&self.key_config),
true,
true,
)
.order(1),
); );
out.push(CommandInfo::new( out.push(CommandInfo::new(
commands::DIFF_FOCUS_RIGHT, strings::commands::diff_focus_right(&self.key_config),
self.can_focus_diff(), self.can_focus_diff(),
!self.diff.focused() || force_all, !self.diff.focused() || force_all,
)); ));
out.push(CommandInfo::new( out.push(CommandInfo::new(
commands::DIFF_FOCUS_LEFT, strings::commands::diff_focus_left(&self.key_config),
true, true,
self.diff.focused() || force_all, self.diff.focused() || force_all,
)); ));
@ -105,19 +110,19 @@ impl Component for InspectCommitComponent {
} }
if let Event::Key(e) = ev { if let Event::Key(e) = ev {
match e { if e == self.key_config.exit_popup {
keys::EXIT_POPUP => { self.hide();
self.hide(); } else if e == self.key_config.focus_right
} && self.can_focus_diff()
keys::FOCUS_RIGHT if self.can_focus_diff() => { {
self.details.focus(false); self.details.focus(false);
self.diff.focus(true); self.diff.focus(true);
} } else if e == self.key_config.focus_left
keys::FOCUS_LEFT if self.diff.focused() => { && self.diff.focused()
self.details.focus(true); {
self.diff.focus(false); self.details.focus(true);
} self.diff.focus(false);
_ => (), } else {
} }
// stop key event propagation // stop key event propagation
@ -152,18 +157,26 @@ impl InspectCommitComponent {
queue: &Queue, queue: &Queue,
sender: &Sender<AsyncNotification>, sender: &Sender<AsyncNotification>,
theme: SharedTheme, theme: SharedTheme,
key_config: SharedKeyConfig,
) -> Self { ) -> Self {
Self { Self {
details: CommitDetailsComponent::new( details: CommitDetailsComponent::new(
queue, queue,
sender, sender,
theme.clone(), theme.clone(),
key_config.clone(),
),
diff: DiffComponent::new(
queue.clone(),
theme,
key_config.clone(),
true,
), ),
diff: DiffComponent::new(queue.clone(), theme, true),
commit_id: None, commit_id: None,
tags: None, tags: None,
git_diff: AsyncDiff::new(sender.clone()), git_diff: AsyncDiff::new(sender.clone()),
visible: false, visible: false,
key_config,
} }
} }

View File

@ -2,11 +2,7 @@ use super::{
visibility_blocking, CommandBlocking, CommandInfo, Component, visibility_blocking, CommandBlocking, CommandInfo, Component,
DrawableComponent, DrawableComponent,
}; };
use crate::{ use crate::{keys::SharedKeyConfig, strings, ui};
keys,
strings::{self, commands},
ui,
};
use crossterm::event::Event; use crossterm::event::Event;
use std::borrow::Cow; use std::borrow::Cow;
use tui::{ use tui::{
@ -21,6 +17,7 @@ pub struct MsgComponent {
msg: String, msg: String,
visible: bool, visible: bool,
theme: SharedTheme, theme: SharedTheme,
key_config: SharedKeyConfig,
} }
use anyhow::Result; use anyhow::Result;
@ -42,7 +39,9 @@ impl DrawableComponent for MsgComponent {
Paragraph::new(txt.iter()) Paragraph::new(txt.iter())
.block( .block(
Block::default() Block::default()
.title(strings::MSG_TITLE_ERROR) .title(&strings::msg_title_error(
&self.key_config,
))
.title_style(self.theme.text_danger()) .title_style(self.theme.text_danger())
.borders(Borders::ALL) .borders(Borders::ALL)
.border_type(BorderType::Thick), .border_type(BorderType::Thick),
@ -63,7 +62,7 @@ impl Component for MsgComponent {
_force_all: bool, _force_all: bool,
) -> CommandBlocking { ) -> CommandBlocking {
out.push(CommandInfo::new( out.push(CommandInfo::new(
commands::CLOSE_MSG, strings::commands::close_msg(&self.key_config),
true, true,
self.visible, self.visible,
)); ));
@ -74,7 +73,7 @@ impl Component for MsgComponent {
fn event(&mut self, ev: Event) -> Result<bool> { fn event(&mut self, ev: Event) -> Result<bool> {
if self.visible { if self.visible {
if let Event::Key(e) = ev { if let Event::Key(e) = ev {
if let keys::CLOSE_MSG = e { if e == self.key_config.close_msg {
self.hide(); self.hide();
} }
} }
@ -100,11 +99,15 @@ impl Component for MsgComponent {
} }
impl MsgComponent { impl MsgComponent {
pub const fn new(theme: SharedTheme) -> Self { pub const fn new(
theme: SharedTheme,
key_config: SharedKeyConfig,
) -> Self {
Self { Self {
msg: String::new(), msg: String::new(),
visible: false, visible: false,
theme, theme,
key_config,
} }
} }
/// ///

View File

@ -3,9 +3,9 @@ use crate::{
popup_paragraph, visibility_blocking, CommandBlocking, popup_paragraph, visibility_blocking, CommandBlocking,
CommandInfo, Component, DrawableComponent, CommandInfo, Component, DrawableComponent,
}, },
keys::SharedKeyConfig,
queue::{Action, InternalEvent, Queue}, queue::{Action, InternalEvent, Queue},
strings::{self, commands}, strings, ui,
ui,
}; };
use anyhow::Result; use anyhow::Result;
use crossterm::event::{Event, KeyCode}; use crossterm::event::{Event, KeyCode};
@ -24,6 +24,7 @@ pub struct ResetComponent {
visible: bool, visible: bool,
queue: Queue, queue: Queue,
theme: SharedTheme, theme: SharedTheme,
key_config: SharedKeyConfig,
} }
impl DrawableComponent for ResetComponent { impl DrawableComponent for ResetComponent {
@ -43,7 +44,12 @@ impl DrawableComponent for ResetComponent {
let area = ui::centered_rect(30, 20, f.size()); let area = ui::centered_rect(30, 20, f.size());
f.render_widget(Clear, area); f.render_widget(Clear, area);
f.render_widget( f.render_widget(
popup_paragraph(title, txt.iter(), &self.theme, true), popup_paragraph(
&title,
txt.iter(),
&self.theme,
true,
),
area, area,
); );
} }
@ -59,12 +65,12 @@ impl Component for ResetComponent {
_force_all: bool, _force_all: bool,
) -> CommandBlocking { ) -> CommandBlocking {
out.push(CommandInfo::new( out.push(CommandInfo::new(
commands::RESET_CONFIRM, strings::commands::reset_confirm(&self.key_config),
true, true,
self.visible, self.visible,
)); ));
out.push(CommandInfo::new( out.push(CommandInfo::new(
commands::CLOSE_POPUP, strings::commands::close_popup(&self.key_config),
true, true,
self.visible, self.visible,
)); ));
@ -111,12 +117,17 @@ impl Component for ResetComponent {
impl ResetComponent { impl ResetComponent {
/// ///
pub fn new(queue: Queue, theme: SharedTheme) -> Self { pub fn new(
queue: Queue,
theme: SharedTheme,
key_config: SharedKeyConfig,
) -> Self {
Self { Self {
target: None, target: None,
visible: false, visible: false,
queue, queue,
theme, theme,
key_config,
} }
} }
/// ///
@ -137,24 +148,26 @@ impl ResetComponent {
self.hide(); self.hide();
} }
fn get_text(&self) -> (&str, &str) { fn get_text(&self) -> (String, String) {
if let Some(ref a) = self.target { if let Some(ref a) = self.target {
return match a { return match a {
Action::Reset(_) => ( Action::Reset(_) => (
strings::CONFIRM_TITLE_RESET, strings::confirm_title_reset(&self.key_config),
strings::CONFIRM_MSG_RESET, strings::confirm_msg_reset(&self.key_config),
), ),
Action::StashDrop(_) => ( Action::StashDrop(_) => (
strings::CONFIRM_TITLE_STASHDROP, strings::confirm_title_stashdrop(
strings::CONFIRM_MSG_STASHDROP, &self.key_config,
),
strings::confirm_msg_stashdrop(&self.key_config),
), ),
Action::ResetHunk(_, _) => ( Action::ResetHunk(_, _) => (
strings::CONFIRM_TITLE_RESET, strings::confirm_title_reset(&self.key_config),
strings::CONFIRM_MSG_RESETHUNK, strings::confirm_msg_resethunk(&self.key_config),
), ),
}; };
} }
("", "") ("".to_string(), "".to_string())
} }
} }

View File

@ -3,8 +3,9 @@ use super::{
CommandBlocking, CommandInfo, Component, DrawableComponent, CommandBlocking, CommandInfo, Component, DrawableComponent,
}; };
use crate::{ use crate::{
keys::SharedKeyConfig,
queue::{InternalEvent, NeedsUpdate, Queue}, queue::{InternalEvent, NeedsUpdate, Queue},
strings::{self, commands}, strings,
tabs::StashingOptions, tabs::StashingOptions,
ui::style::SharedTheme, ui::style::SharedTheme,
}; };
@ -17,6 +18,7 @@ pub struct StashMsgComponent {
options: StashingOptions, options: StashingOptions,
input: TextInputComponent, input: TextInputComponent,
queue: Queue, queue: Queue,
key_config: SharedKeyConfig,
} }
impl DrawableComponent for StashMsgComponent { impl DrawableComponent for StashMsgComponent {
@ -41,7 +43,9 @@ impl Component for StashMsgComponent {
self.input.commands(out, force_all); self.input.commands(out, force_all);
out.push(CommandInfo::new( out.push(CommandInfo::new(
commands::STASHING_CONFIRM_MSG, strings::commands::stashing_confirm_msg(
&self.key_config,
),
true, true,
true, true,
)); ));
@ -119,15 +123,21 @@ impl Component for StashMsgComponent {
impl StashMsgComponent { impl StashMsgComponent {
/// ///
pub fn new(queue: Queue, theme: SharedTheme) -> Self { pub fn new(
queue: Queue,
theme: SharedTheme,
key_config: SharedKeyConfig,
) -> Self {
Self { Self {
options: StashingOptions::default(), options: StashingOptions::default(),
queue, queue,
input: TextInputComponent::new( input: TextInputComponent::new(
theme, theme,
strings::STASH_POPUP_TITLE, key_config.clone(),
strings::STASH_POPUP_MSG, &strings::stash_popup_title(&key_config),
&strings::stash_popup_msg(&key_config),
), ),
key_config,
} }
} }

View File

@ -3,8 +3,9 @@ use super::{
CommandBlocking, CommandInfo, Component, DrawableComponent, CommandBlocking, CommandInfo, Component, DrawableComponent,
}; };
use crate::{ use crate::{
keys::SharedKeyConfig,
queue::{InternalEvent, NeedsUpdate, Queue}, queue::{InternalEvent, NeedsUpdate, Queue},
strings::{self, commands}, strings,
ui::style::SharedTheme, ui::style::SharedTheme,
}; };
use anyhow::Result; use anyhow::Result;
@ -19,6 +20,7 @@ pub struct TagCommitComponent {
input: TextInputComponent, input: TextInputComponent,
commit_id: Option<CommitId>, commit_id: Option<CommitId>,
queue: Queue, queue: Queue,
key_config: SharedKeyConfig,
} }
impl DrawableComponent for TagCommitComponent { impl DrawableComponent for TagCommitComponent {
@ -43,7 +45,9 @@ impl Component for TagCommitComponent {
self.input.commands(out, force_all); self.input.commands(out, force_all);
out.push(CommandInfo::new( out.push(CommandInfo::new(
commands::TAG_COMMIT_CONFIRM_MSG, strings::commands::tag_commit_confirm_msg(
&self.key_config,
),
true, true,
true, true,
)); ));
@ -86,15 +90,21 @@ impl Component for TagCommitComponent {
impl TagCommitComponent { impl TagCommitComponent {
/// ///
pub fn new(queue: Queue, theme: SharedTheme) -> Self { pub fn new(
queue: Queue,
theme: SharedTheme,
key_config: SharedKeyConfig,
) -> Self {
Self { Self {
queue, queue,
input: TextInputComponent::new( input: TextInputComponent::new(
theme, theme,
strings::TAG_COMMIT_POPUP_TITLE, key_config.clone(),
strings::TAG_COMMIT_POPUP_MSG, &strings::tag_commit_popup_title(&key_config),
&strings::tag_commit_popup_msg(&key_config),
), ),
commit_id: None, commit_id: None,
key_config,
} }
} }

View File

@ -3,7 +3,8 @@ use crate::{
popup_paragraph, visibility_blocking, CommandBlocking, popup_paragraph, visibility_blocking, CommandBlocking,
CommandInfo, Component, DrawableComponent, CommandInfo, Component, DrawableComponent,
}, },
strings::commands, keys::SharedKeyConfig,
strings,
ui::{self, style::SharedTheme}, ui::{self, style::SharedTheme},
}; };
use anyhow::Result; use anyhow::Result;
@ -23,6 +24,7 @@ pub struct TextInputComponent {
msg: String, msg: String,
visible: bool, visible: bool,
theme: SharedTheme, theme: SharedTheme,
key_config: SharedKeyConfig,
cursor_position: usize, cursor_position: usize,
} }
@ -30,6 +32,7 @@ impl TextInputComponent {
/// ///
pub fn new( pub fn new(
theme: SharedTheme, theme: SharedTheme,
key_config: SharedKeyConfig,
title: &str, title: &str,
default_msg: &str, default_msg: &str,
) -> Self { ) -> Self {
@ -37,6 +40,7 @@ impl TextInputComponent {
msg: String::default(), msg: String::default(),
visible: false, visible: false,
theme, theme,
key_config,
title: title.to_string(), title: title.to_string(),
default_msg: default_msg.to_string(), default_msg: default_msg.to_string(),
cursor_position: 0, cursor_position: 0,
@ -196,7 +200,7 @@ impl Component for TextInputComponent {
) -> CommandBlocking { ) -> CommandBlocking {
out.push( out.push(
CommandInfo::new( CommandInfo::new(
commands::CLOSE_POPUP, strings::commands::close_popup(&self.key_config),
true, true,
self.visible, self.visible,
) )
@ -274,8 +278,12 @@ mod tests {
#[test] #[test]
fn test_smoke() { fn test_smoke() {
let mut comp = let mut comp = TextInputComponent::new(
TextInputComponent::new(SharedTheme::default(), "", ""); SharedTheme::default(),
SharedKeyConfig::default(),
"",
"",
);
comp.set_text(String::from("a\nb")); comp.set_text(String::from("a\nb"));
@ -298,8 +306,12 @@ mod tests {
#[test] #[test]
fn test_visualize_newline() { fn test_visualize_newline() {
let mut comp = let mut comp = TextInputComponent::new(
TextInputComponent::new(SharedTheme::default(), "", ""); SharedTheme::default(),
SharedKeyConfig::default(),
"",
"",
);
comp.set_text(String::from("a\nb")); comp.set_text(String::from("a\nb"));

View File

@ -1,74 +1,260 @@
use crate::get_app_config_path;
use anyhow::Result;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use ron::{
de::from_bytes,
ser::{to_string_pretty, PrettyConfig},
};
use serde::{Deserialize, Serialize};
use std::{
fs::File,
io::{Read, Write},
path::PathBuf,
rc::Rc,
};
const fn no_mod(code: KeyCode) -> KeyEvent { pub type SharedKeyConfig = Rc<KeyConfig>;
KeyEvent {
code, #[derive(Serialize, Deserialize, Debug)]
modifiers: KeyModifiers::empty(), pub struct KeyConfig {
pub tab_status: KeyEvent,
pub tab_log: KeyEvent,
pub tab_stashing: KeyEvent,
pub tab_stashes: KeyEvent,
pub tab_toggle: KeyEvent,
pub tab_toggle_reverse: KeyEvent,
pub tab_toggle_reverse_windows: KeyEvent,
pub focus_workdir: KeyEvent,
pub focus_stage: KeyEvent,
pub focus_right: KeyEvent,
pub focus_left: KeyEvent,
pub focus_above: KeyEvent,
pub focus_below: KeyEvent,
pub exit: KeyEvent,
pub exit_popup: KeyEvent,
pub close_msg: KeyEvent,
pub open_commit: KeyEvent,
pub open_commit_editor: KeyEvent,
pub open_help: KeyEvent,
pub move_left: KeyEvent,
pub move_right: KeyEvent,
pub home: KeyEvent,
pub end: KeyEvent,
pub move_up: KeyEvent,
pub move_down: KeyEvent,
pub page_down: KeyEvent,
pub page_up: KeyEvent,
pub shift_up: KeyEvent,
pub shift_down: KeyEvent,
pub enter: KeyEvent,
pub edit_file: KeyEvent,
pub status_stage_file: KeyEvent,
pub status_stage_all: KeyEvent,
pub status_reset_file: KeyEvent,
pub diff_reset_hunk: KeyEvent,
pub status_ignore_file: KeyEvent,
pub stashing_save: KeyEvent,
pub stashing_toggle_untracked: KeyEvent,
pub stashing_toggle_index: KeyEvent,
pub stash_apply: KeyEvent,
pub stash_open: KeyEvent,
pub stash_drop: KeyEvent,
pub cmd_bar_toggle: KeyEvent,
pub log_commit_details: KeyEvent,
pub log_tag_commit: KeyEvent,
pub commit_amend: KeyEvent,
pub copy: KeyEvent,
}
#[rustfmt::skip]
impl Default for KeyConfig {
fn default() -> Self {
Self {
tab_status: KeyEvent { code: KeyCode::Char('1'), modifiers: KeyModifiers::empty()},
tab_log: KeyEvent { code: KeyCode::Char('2'), modifiers: KeyModifiers::empty()},
tab_stashing: KeyEvent { code: KeyCode::Char('3'), modifiers: KeyModifiers::empty()},
tab_stashes: KeyEvent { code: KeyCode::Char('4'), modifiers: KeyModifiers::empty()},
tab_toggle: KeyEvent { code: KeyCode::Tab, modifiers: KeyModifiers::empty()},
tab_toggle_reverse: KeyEvent { code: KeyCode::BackTab, modifiers: KeyModifiers::empty()},
tab_toggle_reverse_windows: KeyEvent { code: KeyCode::BackTab, modifiers: KeyModifiers::SHIFT},
focus_workdir: KeyEvent { code: KeyCode::Char('w'), modifiers: KeyModifiers::empty()},
focus_stage: KeyEvent { code: KeyCode::Char('s'), modifiers: KeyModifiers::empty()},
focus_right: KeyEvent { code: KeyCode::Right, modifiers: KeyModifiers::empty()},
focus_left: KeyEvent { code: KeyCode::Left, modifiers: KeyModifiers::empty()},
focus_above: KeyEvent { code: KeyCode::Up, modifiers: KeyModifiers::empty()},
focus_below: KeyEvent { code: KeyCode::Down, modifiers: KeyModifiers::empty()},
exit: KeyEvent { code: KeyCode::Char('c'), modifiers: KeyModifiers::CONTROL},
exit_popup: KeyEvent { code: KeyCode::Esc, modifiers: KeyModifiers::empty()},
close_msg: KeyEvent { code: KeyCode::Enter, modifiers: KeyModifiers::empty()},
open_commit: KeyEvent { code: KeyCode::Char('c'), modifiers: KeyModifiers::empty()},
open_commit_editor: KeyEvent { code: KeyCode::Char('e'), modifiers:KeyModifiers::CONTROL},
open_help: KeyEvent { code: KeyCode::Char('h'), modifiers: KeyModifiers::empty()},
move_left: KeyEvent { code: KeyCode::Left, modifiers: KeyModifiers::empty()},
move_right: KeyEvent { code: KeyCode::Right, modifiers: KeyModifiers::empty()},
home: KeyEvent { code: KeyCode::Home, modifiers: KeyModifiers::empty()},
end: KeyEvent { code: KeyCode::End, modifiers: KeyModifiers::empty()},
move_up: KeyEvent { code: KeyCode::Up, modifiers: KeyModifiers::empty()},
move_down: KeyEvent { code: KeyCode::Down, modifiers: KeyModifiers::empty()},
page_down: KeyEvent { code: KeyCode::PageDown, modifiers: KeyModifiers::empty()},
page_up: KeyEvent { code: KeyCode::PageUp, modifiers: KeyModifiers::empty()},
shift_up: KeyEvent { code: KeyCode::Up, modifiers: KeyModifiers::SHIFT},
shift_down: KeyEvent { code: KeyCode::Down, modifiers: KeyModifiers::SHIFT},
enter: KeyEvent { code: KeyCode::Enter, modifiers: KeyModifiers::empty()},
edit_file: KeyEvent { code: KeyCode::Char('e'), modifiers: KeyModifiers::empty()},
status_stage_file: KeyEvent { code: KeyCode::Enter, modifiers: KeyModifiers::empty()},
status_stage_all: KeyEvent { code: KeyCode::Char('a'), modifiers: KeyModifiers::empty()},
status_reset_file: KeyEvent { code: KeyCode::Char('D'), modifiers: KeyModifiers::SHIFT,},
diff_reset_hunk: KeyEvent { code: KeyCode::Enter, modifiers: KeyModifiers::empty()},
status_ignore_file: KeyEvent { code: KeyCode::Char('i'), modifiers: KeyModifiers::empty()},
stashing_save: KeyEvent { code: KeyCode::Char('s'), modifiers: KeyModifiers::empty()},
stashing_toggle_untracked: KeyEvent { code: KeyCode::Char('u'), modifiers: KeyModifiers::empty()},
stashing_toggle_index: KeyEvent { code: KeyCode::Char('i'), modifiers: KeyModifiers::empty()},
stash_apply: KeyEvent { code: KeyCode::Enter, modifiers: KeyModifiers::empty()},
stash_open: KeyEvent { code: KeyCode::Right, modifiers: KeyModifiers::empty()},
stash_drop: KeyEvent { code: KeyCode::Char('D'), modifiers: KeyModifiers::SHIFT},
cmd_bar_toggle: KeyEvent { code: KeyCode::Char('.'), modifiers: KeyModifiers::empty()},
log_commit_details: KeyEvent { code: KeyCode::Enter, modifiers: KeyModifiers::empty()},
log_tag_commit: KeyEvent { code: KeyCode::Char('t'), modifiers: KeyModifiers::empty()},
commit_amend: KeyEvent { code: KeyCode::Char('a'), modifiers: KeyModifiers::CONTROL},
copy: KeyEvent { code: KeyCode::Char('y'), modifiers: KeyModifiers::empty()},
}
}
}
impl KeyConfig {
fn save(&self) -> Result<()> {
let config_file = Self::get_config_file()?;
let mut file = File::create(config_file)?;
let data = to_string_pretty(self, PrettyConfig::default())?;
file.write_all(data.as_bytes())?;
Ok(())
}
fn get_config_file() -> Result<PathBuf> {
let app_home = get_app_config_path()?;
Ok(app_home.join("key_config.ron"))
}
fn read_file(config_file: PathBuf) -> Result<Self> {
let mut f = File::open(config_file)?;
let mut buffer = Vec::new();
f.read_to_end(&mut buffer)?;
Ok(from_bytes(&buffer)?)
}
fn init_internal() -> Result<Self> {
let file = Self::get_config_file()?;
if file.exists() {
Ok(Self::read_file(file)?)
} else {
let def = Self::default();
if def.save().is_err() {
log::warn!(
"failed to store default key config to disk."
)
}
Ok(def)
}
}
pub fn init() -> Self {
Self::init_internal().unwrap_or_default()
} }
} }
const fn with_mod( // The hint follows apple design
code: KeyCode, // http://xahlee.info/comp/unicode_computing_symbols.html
modifiers: KeyModifiers, pub fn get_hint(ev: KeyEvent) -> String {
) -> KeyEvent { match ev.code {
KeyEvent { code, modifiers } KeyCode::Char(c) => {
format!("{}{}", get_modifier_hint(ev.modifiers), c)
}
KeyCode::Enter => {
format!("{}\u{23ce}", get_modifier_hint(ev.modifiers)) //⏎
}
KeyCode::Left => {
format!("{}\u{2190}", get_modifier_hint(ev.modifiers)) //←
}
KeyCode::Right => {
format!("{}\u{2192}", get_modifier_hint(ev.modifiers)) //→
}
KeyCode::Up => {
format!("{}\u{2191}", get_modifier_hint(ev.modifiers)) //↑
}
KeyCode::Down => {
format!("{}\u{2193}", get_modifier_hint(ev.modifiers)) //↓
}
KeyCode::Backspace => {
format!("{}\u{232b}", get_modifier_hint(ev.modifiers)) //⌫
}
KeyCode::Home => {
format!("{}\u{2912}", get_modifier_hint(ev.modifiers)) //⤒
}
KeyCode::End => {
format!("{}\u{2913}", get_modifier_hint(ev.modifiers)) //⤓
}
KeyCode::PageUp => {
format!("{}\u{21de}", get_modifier_hint(ev.modifiers)) //⇞
}
KeyCode::PageDown => {
format!("{}\u{21df}", get_modifier_hint(ev.modifiers)) //⇟
}
KeyCode::Tab => {
format!("{}\u{21e5}", get_modifier_hint(ev.modifiers)) //⇥
}
KeyCode::BackTab => {
format!("{}\u{21e4}", get_modifier_hint(ev.modifiers)) //⇤
}
KeyCode::Delete => {
format!("{}\u{2326}", get_modifier_hint(ev.modifiers)) //⌦
}
KeyCode::Insert => {
format!("{}\u{2380}", get_modifier_hint(ev.modifiers)) //⎀
}
KeyCode::Esc => {
format!("{}\u{238b}", get_modifier_hint(ev.modifiers)) //⎋
}
KeyCode::F(u) => {
format!("{}F{}", get_modifier_hint(ev.modifiers), u)
}
KeyCode::Null => get_modifier_hint(ev.modifiers).to_string(),
}
} }
pub const TAB_1: KeyEvent = no_mod(KeyCode::Char('1')); fn get_modifier_hint(modifier: KeyModifiers) -> String {
pub const TAB_2: KeyEvent = no_mod(KeyCode::Char('2')); match modifier {
pub const TAB_3: KeyEvent = no_mod(KeyCode::Char('3')); KeyModifiers::CONTROL => "^".to_string(),
pub const TAB_4: KeyEvent = no_mod(KeyCode::Char('4')); KeyModifiers::SHIFT => {
pub const TAB_TOGGLE: KeyEvent = no_mod(KeyCode::Tab); "\u{21e7}".to_string() //⇧
pub const TAB_TOGGLE_REVERSE: KeyEvent = no_mod(KeyCode::BackTab); }
//TODO: https://github.com/extrawurst/gitui/issues/112 KeyModifiers::ALT => {
pub const TAB_TOGGLE_REVERSE_WINDOWS: KeyEvent = "\u{2325}".to_string() //⌥
with_mod(KeyCode::BackTab, KeyModifiers::SHIFT); }
pub const FOCUS_WORKDIR: KeyEvent = no_mod(KeyCode::Char('w')); _ => "".to_string(),
pub const FOCUS_STAGE: KeyEvent = no_mod(KeyCode::Char('s')); }
pub const FOCUS_RIGHT: KeyEvent = no_mod(KeyCode::Right); }
pub const FOCUS_LEFT: KeyEvent = no_mod(KeyCode::Left);
pub const FOCUS_ABOVE: KeyEvent = no_mod(KeyCode::Up); #[cfg(test)]
pub const FOCUS_BELOW: KeyEvent = no_mod(KeyCode::Down); mod tests {
pub const EXIT: KeyEvent = use super::{get_hint, KeyConfig};
with_mod(KeyCode::Char('c'), KeyModifiers::CONTROL); use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
pub const EXIT_POPUP: KeyEvent = no_mod(KeyCode::Esc);
pub const CLOSE_MSG: KeyEvent = no_mod(KeyCode::Enter); #[test]
pub const OPEN_COMMIT: KeyEvent = no_mod(KeyCode::Char('c')); fn test_get_hint() {
pub const OPEN_COMMIT_EDITOR: KeyEvent = let h = get_hint(KeyEvent {
with_mod(KeyCode::Char('e'), KeyModifiers::CONTROL); code: KeyCode::Char('c'),
pub const OPEN_HELP: KeyEvent = no_mod(KeyCode::Char('h')); modifiers: KeyModifiers::CONTROL,
pub const MOVE_LEFT: KeyEvent = no_mod(KeyCode::Left); });
pub const MOVE_RIGHT: KeyEvent = no_mod(KeyCode::Right); assert_eq!(h, "^c");
pub const HOME: KeyEvent = no_mod(KeyCode::Home); }
pub const END: KeyEvent = no_mod(KeyCode::End);
pub const MOVE_UP: KeyEvent = no_mod(KeyCode::Up); #[test]
pub const MOVE_DOWN: KeyEvent = no_mod(KeyCode::Down); fn test_load_vim_style_example() {
pub const PAGE_DOWN: KeyEvent = no_mod(KeyCode::PageDown); assert_eq!(
pub const PAGE_UP: KeyEvent = no_mod(KeyCode::PageUp); KeyConfig::read_file(
pub const SHIFT_UP: KeyEvent = "assets/vim_style_key_config.ron".into()
with_mod(KeyCode::Up, KeyModifiers::SHIFT); )
pub const SHIFT_DOWN: KeyEvent = .is_ok(),
with_mod(KeyCode::Down, KeyModifiers::SHIFT); true
pub const ENTER: KeyEvent = no_mod(KeyCode::Enter); );
pub const COPY: KeyEvent = no_mod(KeyCode::Char('y')); }
pub const EDIT_FILE: KeyEvent = no_mod(KeyCode::Char('e')); }
pub const STATUS_STAGE_FILE: KeyEvent = no_mod(KeyCode::Enter);
pub const STATUS_STAGE_ALL: KeyEvent = no_mod(KeyCode::Char('a'));
pub const STATUS_RESET_FILE: KeyEvent =
with_mod(KeyCode::Char('D'), KeyModifiers::SHIFT);
pub const DIFF_RESET_HUNK: KeyEvent = STATUS_RESET_FILE;
pub const STATUS_IGNORE_FILE: KeyEvent = no_mod(KeyCode::Char('i'));
pub const STASHING_SAVE: KeyEvent = no_mod(KeyCode::Char('s'));
pub const STASHING_TOGGLE_UNTRACKED: KeyEvent =
no_mod(KeyCode::Char('u'));
pub const STASHING_TOGGLE_INDEX: KeyEvent =
no_mod(KeyCode::Char('i'));
pub const STASH_APPLY: KeyEvent = no_mod(KeyCode::Enter);
pub const STASH_OPEN: KeyEvent = no_mod(KeyCode::Right);
pub const STASH_DROP: KeyEvent =
with_mod(KeyCode::Char('D'), KeyModifiers::SHIFT);
pub const CMD_BAR_TOGGLE: KeyEvent = no_mod(KeyCode::Char('.'));
pub const LOG_COMMIT_DETAILS: KeyEvent = no_mod(KeyCode::Enter);
pub const LOG_TAG_COMMIT: KeyEvent = no_mod(KeyCode::Char('t'));
pub const COMMIT_AMEND: KeyEvent =
with_mod(KeyCode::Char('a'), KeyModifiers::CONTROL);

View File

@ -1,63 +1,155 @@
pub static TITLE_STATUS: &str = "Unstaged Changes [w]"; use crate::keys::{get_hint, SharedKeyConfig};
pub static TITLE_DIFF: &str = "Diff: ";
pub static TITLE_INDEX: &str = "Staged Changes [s]";
pub static TAB_STATUS: &str = "Status [1]";
pub static TAB_LOG: &str = "Log [2]";
pub static TAB_STASHING: &str = "Stashing [3]";
pub static TAB_STASHES: &str = "Stashes [4]";
pub static TAB_DIVIDER: &str = " | ";
pub static CMD_SPLITTER: &str = " ";
pub static MSG_OPENING_EDITOR: &str = "opening editor...";
pub static MSG_TITLE_ERROR: &str = "Error";
pub static COMMIT_TITLE: &str = "Commit";
pub static COMMIT_TITLE_AMEND: &str = "Commit (Amend)";
pub static COMMIT_MSG: &str = "type commit message..";
pub static COMMIT_EDITOR_MSG: &str = r##"
# Edit your commit message
# Lines starting with '#' will be ignored"##;
pub static STASH_POPUP_TITLE: &str = "Stash";
pub static STASH_POPUP_MSG: &str = "type name (optional)";
pub static CONFIRM_TITLE_RESET: &str = "Reset";
pub static CONFIRM_TITLE_STASHDROP: &str = "Drop";
pub static CONFIRM_MSG_RESET: &str = "confirm file reset?";
pub static CONFIRM_MSG_STASHDROP: &str = "confirm stash drop?";
pub static CONFIRM_MSG_RESETHUNK: &str = "confirm reset hunk?";
pub static LOG_TITLE: &str = "Commit";
pub static TAG_COMMIT_POPUP_TITLE: &str = "Tag";
pub static TAG_COMMIT_POPUP_MSG: &str = "type tag";
pub static STASHLIST_TITLE: &str = "Stashes";
pub static HELP_TITLE: &str = "Help: all commands";
pub static STASHING_FILES_TITLE: &str = "Files to Stash";
pub static STASHING_OPTIONS_TITLE: &str = "Options";
pub static LOADING_TEXT: &str = "Loading ...";
pub mod commit {
pub static DETAILS_AUTHOR: &str = "Author: ";
pub static DETAILS_COMMITTER: &str = "Committer: ";
pub static DETAILS_SHA: &str = "SHA: ";
pub static DETAILS_DATE: &str = "Date: ";
pub static DETAILS_TAGS: &str = "Tags: ";
pub static DETAILS_INFO_TITLE: &str = "Info";
pub static DETAILS_MESSAGE_TITLE: &str = "Message";
pub static DETAILS_FILES_TITLE: &str = "Files:";
}
pub mod order { pub mod order {
pub static NAV: i8 = 1; pub static NAV: i8 = 1;
} }
pub fn title_status(key_config: &SharedKeyConfig) -> String {
format!(
"Unstaged Changes [{}]",
get_hint(key_config.focus_workdir)
)
}
pub fn title_diff(_key_config: &SharedKeyConfig) -> String {
"Diff: ".to_string()
}
pub fn title_index(key_config: &SharedKeyConfig) -> String {
format!("Staged Changes [{}]", get_hint(key_config.focus_stage))
}
pub fn tab_status(key_config: &SharedKeyConfig) -> String {
format!("Status [{}]", get_hint(key_config.tab_status))
}
pub fn tab_log(key_config: &SharedKeyConfig) -> String {
format!("Log [{}]", get_hint(key_config.tab_log))
}
pub fn tab_stashing(key_config: &SharedKeyConfig) -> String {
format!("Stashing [{}]", get_hint(key_config.tab_stashing))
}
pub fn tab_stashes(key_config: &SharedKeyConfig) -> String {
format!("Stashes [{}]", get_hint(key_config.tab_stashes))
}
pub fn tab_divider(_key_config: &SharedKeyConfig) -> String {
" | ".to_string()
}
pub fn cmd_splitter(_key_config: &SharedKeyConfig) -> String {
" ".to_string()
}
pub fn msg_opening_editor(_key_config: &SharedKeyConfig) -> String {
"opening editor...".to_string()
}
pub fn msg_title_error(_key_config: &SharedKeyConfig) -> String {
"Error".to_string()
}
pub fn commit_title(_key_config: &SharedKeyConfig) -> String {
"Commit".to_string()
}
pub fn commit_title_amend(_key_config: &SharedKeyConfig) -> String {
"Commit (Amend)".to_string()
}
pub fn commit_msg(_key_config: &SharedKeyConfig) -> String {
"type commit message..".to_string()
}
pub fn commit_editor_msg(_key_config: &SharedKeyConfig) -> String {
r##"
# Edit your commit message
# Lines starting with '#' will be ignored"##
.to_string()
}
pub fn stash_popup_title(_key_config: &SharedKeyConfig) -> String {
"Stash".to_string()
}
pub fn stash_popup_msg(_key_config: &SharedKeyConfig) -> String {
"type name (optional)".to_string()
}
pub fn confirm_title_reset(_key_config: &SharedKeyConfig) -> String {
"Reset".to_string()
}
pub fn confirm_title_stashdrop(
_key_config: &SharedKeyConfig,
) -> String {
"Drop".to_string()
}
pub fn confirm_msg_reset(_key_config: &SharedKeyConfig) -> String {
"confirm file reset?".to_string()
}
pub fn confirm_msg_stashdrop(
_key_config: &SharedKeyConfig,
) -> String {
"confirm stash drop?".to_string()
}
pub fn confirm_msg_resethunk(
_key_config: &SharedKeyConfig,
) -> String {
"confirm reset hunk?".to_string()
}
pub fn log_title(_key_config: &SharedKeyConfig) -> String {
"Commit".to_string()
}
pub fn tag_commit_popup_title(
_key_config: &SharedKeyConfig,
) -> String {
"Tag".to_string()
}
pub fn tag_commit_popup_msg(_key_config: &SharedKeyConfig) -> String {
"type tag".to_string()
}
pub fn stashlist_title(_key_config: &SharedKeyConfig) -> String {
"Stashes".to_string()
}
pub fn help_title(_key_config: &SharedKeyConfig) -> String {
"Help: all commands".to_string()
}
pub fn stashing_files_title(_key_config: &SharedKeyConfig) -> String {
"Files to Stash".to_string()
}
pub fn stashing_options_title(
_key_config: &SharedKeyConfig,
) -> String {
"Options".to_string()
}
pub fn loading_text(_key_config: &SharedKeyConfig) -> String {
"Loading ...".to_string()
}
pub mod commit {
use crate::keys::SharedKeyConfig;
pub fn details_author(_key_config: &SharedKeyConfig) -> String {
"Author: ".to_string()
}
pub fn details_committer(
_key_config: &SharedKeyConfig,
) -> String {
"Committer: ".to_string()
}
pub fn details_sha(_key_config: &SharedKeyConfig) -> String {
"Sha: ".to_string()
}
pub fn details_date(_key_config: &SharedKeyConfig) -> String {
"Date: ".to_string()
}
pub fn details_tags(_key_config: &SharedKeyConfig) -> String {
"Tags: ".to_string()
}
pub fn details_info_title(
_key_config: &SharedKeyConfig,
) -> String {
"Info".to_string()
}
pub fn details_message_title(
_key_config: &SharedKeyConfig,
) -> String {
"Message".to_string()
}
pub fn details_files_title(
_key_config: &SharedKeyConfig,
) -> String {
"Files:".to_string()
}
}
pub mod commands { pub mod commands {
use crate::components::CommandText; use crate::components::CommandText;
use crate::keys::{get_hint, SharedKeyConfig};
static CMD_GROUP_GENERAL: &str = "-- General --"; static CMD_GROUP_GENERAL: &str = "-- General --";
static CMD_GROUP_DIFF: &str = "-- Diff --"; static CMD_GROUP_DIFF: &str = "-- Diff --";
@ -67,256 +159,425 @@ pub mod commands {
static CMD_GROUP_STASHES: &str = "-- Stashes --"; static CMD_GROUP_STASHES: &str = "-- Stashes --";
static CMD_GROUP_LOG: &str = "-- Log --"; static CMD_GROUP_LOG: &str = "-- Log --";
/// pub fn toggle_tabs(key_config: &SharedKeyConfig) -> CommandText {
pub static TOGGLE_TABS: CommandText = CommandText::new(
"Next [tab]",
"switch to next tab",
CMD_GROUP_GENERAL,
);
///
pub static TOGGLE_TABS_DIRECT: CommandText = CommandText::new(
"Tab [1234]",
"switch top level tabs directly",
CMD_GROUP_GENERAL,
);
///
pub static HELP_OPEN: CommandText = CommandText::new(
"Help [h]",
"open this help screen",
CMD_GROUP_GENERAL,
);
///
pub static NAVIGATE_COMMIT_MESSAGE: CommandText =
CommandText::new( CommandText::new(
"Nav [\u{2191}\u{2193}]", format!("Next [{}]", get_hint(key_config.tab_toggle)),
"switch to next tab",
CMD_GROUP_GENERAL,
)
}
pub fn toggle_tabs_direct(
key_config: &SharedKeyConfig,
) -> CommandText {
CommandText::new(
format!(
"Tab [{}{}{}{}]",
get_hint(key_config.tab_status),
get_hint(key_config.tab_log),
get_hint(key_config.tab_stashing),
get_hint(key_config.tab_stashes),
),
"switch top level tabs directly",
CMD_GROUP_GENERAL,
)
}
pub fn help_open(key_config: &SharedKeyConfig) -> CommandText {
CommandText::new(
format!("Help [{}]", get_hint(key_config.open_help)),
"open this help screen",
CMD_GROUP_GENERAL,
)
}
pub fn navigate_commit_message(
key_config: &SharedKeyConfig,
) -> CommandText {
CommandText::new(
format!(
"Nav [{}{}]",
get_hint(key_config.move_up),
get_hint(key_config.move_down)
),
"navigate commit message", "navigate commit message",
CMD_GROUP_GENERAL, CMD_GROUP_GENERAL,
); )
/// }
pub static NAVIGATE_TREE: CommandText = CommandText::new( pub fn navigate_tree(
"Nav [\u{2190}\u{2191}\u{2192}\u{2193}]", key_config: &SharedKeyConfig,
"navigate tree view", ) -> CommandText {
CMD_GROUP_GENERAL,
);
///
pub static SCROLL: CommandText = CommandText::new(
"Scroll [\u{2191}\u{2193}]",
"scroll up or down in focused view",
CMD_GROUP_GENERAL,
);
///
pub static COPY: CommandText = CommandText::new(
"Copy [y]",
"copy selected lines to clipboard",
CMD_GROUP_DIFF,
);
///
pub static DIFF_HOME_END: CommandText = CommandText::new(
"Jump up/down [home,end,\u{2191} up,\u{2193} down]",
"scroll to top or bottom of diff",
CMD_GROUP_DIFF,
);
///
pub static DIFF_HUNK_ADD: CommandText = CommandText::new(
"Add hunk [enter]",
"adds selected hunk to stage",
CMD_GROUP_DIFF,
);
///
pub static DIFF_HUNK_REVERT: CommandText = CommandText::new(
"Revert hunk [D]",
"reverts selected hunk",
CMD_GROUP_DIFF,
);
///
pub static DIFF_HUNK_REMOVE: CommandText = CommandText::new(
"Remove hunk [enter]",
"removes selected hunk from stage",
CMD_GROUP_DIFF,
);
///
pub static CLOSE_POPUP: CommandText = CommandText::new(
"Close [esc]",
"close overlay (e.g commit, help)",
CMD_GROUP_GENERAL,
);
///
pub static CLOSE_MSG: CommandText = CommandText::new(
"Close [enter]",
"close msg popup (e.g msg)",
CMD_GROUP_GENERAL,
)
.hide_help();
///
pub static SELECT_STAGING: CommandText = CommandText::new(
"To stage [s]",
"focus/select staging area",
CMD_GROUP_GENERAL,
);
///
pub static SELECT_STATUS: CommandText = CommandText::new(
"To files [1,2]",
"focus/select file tree of staged or unstaged files",
CMD_GROUP_GENERAL,
);
///
pub static SELECT_UNSTAGED: CommandText = CommandText::new(
"To unstaged [w]",
"focus/select unstaged area",
CMD_GROUP_GENERAL,
);
///
pub static COMMIT_OPEN: CommandText = CommandText::new(
"Commit [c]",
"open commit popup (available in non-empty stage)",
CMD_GROUP_COMMIT,
);
///
pub static COMMIT_OPEN_EDITOR: CommandText = CommandText::new(
"Open editor [^e]",
"open commit editor (available in non-empty stage)",
CMD_GROUP_COMMIT,
);
///
pub static COMMIT_ENTER: CommandText = CommandText::new(
"Commit [enter]",
"commit (available when commit message is non-empty)",
CMD_GROUP_COMMIT,
);
///
pub static COMMIT_AMEND: CommandText = CommandText::new(
"Amend [^a]",
"amend last commit",
CMD_GROUP_COMMIT,
);
///
pub static EDIT_ITEM: CommandText = CommandText::new(
"Edit Item [e]",
"edit the currently selected file in an external editor",
CMD_GROUP_CHANGES,
);
///
pub static STAGE_ITEM: CommandText = CommandText::new(
"Stage Item [enter]",
"stage currently selected file or entire path",
CMD_GROUP_CHANGES,
);
///
pub static STAGE_ALL: CommandText = CommandText::new(
"Stage All [a]",
"stage all changes (in unstaged files)",
CMD_GROUP_CHANGES,
);
///
pub static UNSTAGE_ITEM: CommandText = CommandText::new(
"Unstage Item [enter]",
"unstage currently selected file or entire path",
CMD_GROUP_CHANGES,
);
///
pub static UNSTAGE_ALL: CommandText = CommandText::new(
"Unstage all [a]",
"unstage all files (in staged files)",
CMD_GROUP_CHANGES,
);
///
pub static RESET_ITEM: CommandText = CommandText::new(
"Reset Item [D]",
"revert changes in selected file or entire path",
CMD_GROUP_CHANGES,
);
///
pub static IGNORE_ITEM: CommandText = CommandText::new(
"Ignore [i]",
"Add file or path to .gitignore",
CMD_GROUP_CHANGES,
);
///
pub static DIFF_FOCUS_LEFT: CommandText = CommandText::new(
"Back [\u{2190}]", //←
"view and select changed files",
CMD_GROUP_GENERAL,
);
///
pub static DIFF_FOCUS_RIGHT: CommandText = CommandText::new(
"Diff [\u{2192}]", //→
"inspect file diff",
CMD_GROUP_GENERAL,
);
///
pub static QUIT: CommandText = CommandText::new(
"Quit [^c]",
"quit gitui application",
CMD_GROUP_GENERAL,
);
///
pub static RESET_CONFIRM: CommandText = CommandText::new(
"Confirm [enter]",
"resets the file in question",
CMD_GROUP_GENERAL,
);
///
pub static STASHING_SAVE: CommandText = CommandText::new(
"Save [s]",
"opens stash name input popup",
CMD_GROUP_STASHING,
);
///
pub static STASHING_TOGGLE_INDEXED: CommandText =
CommandText::new( CommandText::new(
"Toggle Staged [i]", format!(
"Nav [{}{}{}{}]",
get_hint(key_config.move_up),
get_hint(key_config.move_down),
get_hint(key_config.move_right),
get_hint(key_config.move_left)
),
"navigate tree view",
CMD_GROUP_GENERAL,
)
}
pub fn scroll(key_config: &SharedKeyConfig) -> CommandText {
CommandText::new(
format!(
"Scroll [{}{}]",
get_hint(key_config.focus_above),
get_hint(key_config.focus_below)
),
"scroll up or down in focused view",
CMD_GROUP_GENERAL,
)
}
pub fn copy(key_config: &SharedKeyConfig) -> CommandText {
CommandText::new(
format!("Copy [{}]", get_hint(key_config.copy),),
"copy selected lines to clipboard",
CMD_GROUP_DIFF,
)
}
pub fn diff_home_end(
key_config: &SharedKeyConfig,
) -> CommandText {
CommandText::new(
format!(
"Jump up/down [{},{},{},{}]",
get_hint(key_config.home),
get_hint(key_config.end),
get_hint(key_config.move_up),
get_hint(key_config.move_down)
),
"scroll to top or bottom of diff",
CMD_GROUP_DIFF,
)
}
pub fn diff_hunk_add(
key_config: &SharedKeyConfig,
) -> CommandText {
CommandText::new(
format!(
"Add hunk [{}]",
get_hint(key_config.diff_reset_hunk),
),
"adds selected hunk to stage",
CMD_GROUP_DIFF,
)
}
pub fn diff_hunk_revert(
key_config: &SharedKeyConfig,
) -> CommandText {
CommandText::new(
format!(
"Revert hunk [{}]",
get_hint(key_config.status_reset_file),
),
"reverts selected hunk",
CMD_GROUP_DIFF,
)
}
pub fn diff_hunk_remove(
key_config: &SharedKeyConfig,
) -> CommandText {
CommandText::new(
format!(
"Remove hunk [{}]",
get_hint(key_config.close_msg),
),
"removes selected hunk from stage",
CMD_GROUP_DIFF,
)
}
pub fn close_popup(key_config: &SharedKeyConfig) -> CommandText {
CommandText::new(
format!("Close [{}]", get_hint(key_config.exit_popup),),
"close overlay (e.g commit, help)",
CMD_GROUP_GENERAL,
)
}
pub fn close_msg(key_config: &SharedKeyConfig) -> CommandText {
CommandText::new(
format!("Close [{}]", get_hint(key_config.close_msg),),
"close msg popup (e.g msg)",
CMD_GROUP_GENERAL,
)
.hide_help()
}
pub fn select_staging(
key_config: &SharedKeyConfig,
) -> CommandText {
CommandText::new(
format!(
"To stage [{}]",
get_hint(key_config.focus_stage),
),
"focus/select staging area",
CMD_GROUP_GENERAL,
)
}
pub fn select_status(
key_config: &SharedKeyConfig,
) -> CommandText {
CommandText::new(
format!(
"To files [{},{}]",
get_hint(key_config.tab_status),
get_hint(key_config.tab_log),
),
"focus/select file tree of staged or unstaged files",
CMD_GROUP_GENERAL,
)
}
pub fn select_unstaged(
key_config: &SharedKeyConfig,
) -> CommandText {
CommandText::new(
format!(
"To unstaged [{}]",
get_hint(key_config.focus_workdir),
),
"focus/select unstaged area",
CMD_GROUP_GENERAL,
)
}
pub fn commit_open(key_config: &SharedKeyConfig) -> CommandText {
CommandText::new(
format!("Commit [{}]", get_hint(key_config.open_commit),),
"open commit popup (available in non-empty stage)",
CMD_GROUP_COMMIT,
)
}
pub fn commit_open_editor(
key_config: &SharedKeyConfig,
) -> CommandText {
CommandText::new(
format!(
"Open editor [{}]",
get_hint(key_config.open_commit_editor),
),
"open commit editor (available in non-empty stage)",
CMD_GROUP_COMMIT,
)
}
pub fn commit_enter(key_config: &SharedKeyConfig) -> CommandText {
CommandText::new(
format!("Commit [{}]", get_hint(key_config.enter),),
"commit (available when commit message is non-empty)",
CMD_GROUP_COMMIT,
)
}
pub fn commit_amend(key_config: &SharedKeyConfig) -> CommandText {
CommandText::new(
format!("Amend [{}]", get_hint(key_config.commit_amend),),
"amend last commit",
CMD_GROUP_COMMIT,
)
}
pub fn edit_item(key_config: &SharedKeyConfig) -> CommandText {
CommandText::new(
format!("Edit Item [{}]", get_hint(key_config.edit_file),),
"edit the currently selected file in an external editor",
CMD_GROUP_CHANGES,
)
}
pub fn stage_item(key_config: &SharedKeyConfig) -> CommandText {
CommandText::new(
format!(
"Stage Item [{}]",
get_hint(key_config.stash_apply),
),
"stage currently selected file or entire path",
CMD_GROUP_CHANGES,
)
}
pub fn stage_all(key_config: &SharedKeyConfig) -> CommandText {
CommandText::new(
format!(
"Stage All [{}]",
get_hint(key_config.status_stage_all),
),
"stage all changes (in unstaged files)",
CMD_GROUP_CHANGES,
)
}
pub fn unstage_item(key_config: &SharedKeyConfig) -> CommandText {
CommandText::new(
format!(
"Unstage Item [{}]",
get_hint(key_config.stash_apply),
),
"unstage currently selected file or entire path",
CMD_GROUP_CHANGES,
)
}
pub fn unstage_all(key_config: &SharedKeyConfig) -> CommandText {
CommandText::new(
format!(
"Unstage all [{}]",
get_hint(key_config.status_stage_all),
),
"unstage all files (in staged files)",
CMD_GROUP_CHANGES,
)
}
pub fn reset_item(key_config: &SharedKeyConfig) -> CommandText {
CommandText::new(
format!(
"Reset Item [{}]",
get_hint(key_config.stash_drop),
),
"revert changes in selected file or entire path",
CMD_GROUP_CHANGES,
)
}
pub fn ignore_item(key_config: &SharedKeyConfig) -> CommandText {
CommandText::new(
format!(
"Ignore [{}]",
get_hint(key_config.status_ignore_file),
),
"Add file or path to .gitignore",
CMD_GROUP_CHANGES,
)
}
pub fn diff_focus_left(
key_config: &SharedKeyConfig,
) -> CommandText {
CommandText::new(
format!("Back [{}]", get_hint(key_config.focus_left),),
"view and select changed files",
CMD_GROUP_GENERAL,
)
}
pub fn diff_focus_right(
key_config: &SharedKeyConfig,
) -> CommandText {
CommandText::new(
format!("Diff [{}]", get_hint(key_config.focus_right),),
"inspect file diff",
CMD_GROUP_GENERAL,
)
}
pub fn quit(key_config: &SharedKeyConfig) -> CommandText {
CommandText::new(
format!("Quit [{}]", get_hint(key_config.exit),),
"quit gitui application",
CMD_GROUP_GENERAL,
)
}
pub fn reset_confirm(
key_config: &SharedKeyConfig,
) -> CommandText {
CommandText::new(
format!("Confirm [{}]", get_hint(key_config.close_msg),),
"resets the file in question",
CMD_GROUP_GENERAL,
)
}
pub fn stashing_save(
key_config: &SharedKeyConfig,
) -> CommandText {
CommandText::new(
format!("Save [{}]", get_hint(key_config.stashing_save),),
"opens stash name input popup",
CMD_GROUP_STASHING,
)
}
pub fn stashing_toggle_indexed(
key_config: &SharedKeyConfig,
) -> CommandText {
CommandText::new(
format!(
"Toggle Staged [{}]",
get_hint(key_config.stashing_toggle_index),
),
"toggle including staged files into stash", "toggle including staged files into stash",
CMD_GROUP_STASHING, CMD_GROUP_STASHING,
); )
/// }
pub static STASHING_TOGGLE_UNTRACKED: CommandText = pub fn stashing_toggle_untracked(
key_config: &SharedKeyConfig,
) -> CommandText {
CommandText::new( CommandText::new(
"Toggle Untracked [u]", format!(
"Toggle Untracked [{}]",
get_hint(key_config.stashing_toggle_untracked),
),
"toggle including untracked files into stash", "toggle including untracked files into stash",
CMD_GROUP_STASHING, CMD_GROUP_STASHING,
); )
/// }
pub static STASHING_CONFIRM_MSG: CommandText = CommandText::new( pub fn stashing_confirm_msg(
"Stash [enter]", key_config: &SharedKeyConfig,
"save files to stash", ) -> CommandText {
CMD_GROUP_STASHING, CommandText::new(
); format!("Stash [{}]", get_hint(key_config.close_msg),),
/// "save files to stash",
pub static STASHLIST_APPLY: CommandText = CommandText::new( CMD_GROUP_STASHING,
"Apply [enter]", )
"apply selected stash", }
CMD_GROUP_STASHES, pub fn stashlist_apply(
); key_config: &SharedKeyConfig,
/// ) -> CommandText {
pub static STASHLIST_DROP: CommandText = CommandText::new( CommandText::new(
"Drop [D]", format!("Apply [{}]", get_hint(key_config.stash_apply),),
"drop selected stash", "apply selected stash",
CMD_GROUP_STASHES, CMD_GROUP_STASHES,
); )
/// }
pub static STASHLIST_INSPECT: CommandText = CommandText::new( pub fn stashlist_drop(
"Inspect [\u{2192}]", //→ key_config: &SharedKeyConfig,
"open stash commit details (allows to diff files)", ) -> CommandText {
CMD_GROUP_STASHES, CommandText::new(
); format!("Drop [{}]", get_hint(key_config.stash_drop),),
"drop selected stash",
/// CMD_GROUP_STASHES,
pub static LOG_DETAILS_TOGGLE: CommandText = CommandText::new( )
"Details [enter]", }
"open details of selected commit", pub fn stashlist_inspect(
CMD_GROUP_LOG, key_config: &SharedKeyConfig,
); ) -> CommandText {
/// CommandText::new(
pub static LOG_DETAILS_OPEN: CommandText = CommandText::new( format!("Inspect [{}]", get_hint(key_config.focus_right),),
"Inspect [\u{2192}]", //→ "open stash commit details (allows to diff files)",
"inspect selected commit in detail", CMD_GROUP_STASHES,
CMD_GROUP_LOG, )
); }
/// pub fn log_details_toggle(
pub static LOG_TAG_COMMIT: CommandText = key_config: &SharedKeyConfig,
CommandText::new("Tag [t]", "tag commit", CMD_GROUP_LOG); ) -> CommandText {
/// CommandText::new(
pub static TAG_COMMIT_CONFIRM_MSG: CommandText = format!(
CommandText::new("Tag [enter]", "tag commit", CMD_GROUP_LOG); "Details Inspect [{}]",
get_hint(key_config.log_commit_details),
),
"open details of selected commit",
CMD_GROUP_LOG,
)
}
pub fn log_details_open(
key_config: &SharedKeyConfig,
) -> CommandText {
CommandText::new(
format!("Inspect [{}]", get_hint(key_config.focus_right),),
"inspect selected commit in detail",
CMD_GROUP_LOG,
)
}
pub fn log_tag_commit(
key_config: &SharedKeyConfig,
) -> CommandText {
CommandText::new(
format!("Tag [{}]", get_hint(key_config.log_tag_commit),),
"tag commit",
CMD_GROUP_LOG,
)
}
pub fn tag_commit_confirm_msg(
key_config: &SharedKeyConfig,
) -> CommandText {
CommandText::new(
format!("Tag [{}]", get_hint(key_config.close_msg),),
"tag commit",
CMD_GROUP_LOG,
)
}
} }

View File

@ -4,9 +4,9 @@ use crate::{
CommitDetailsComponent, CommitList, Component, CommitDetailsComponent, CommitList, Component,
DrawableComponent, DrawableComponent,
}, },
keys, keys::SharedKeyConfig,
queue::{InternalEvent, Queue}, queue::{InternalEvent, Queue},
strings::{self, commands}, strings,
ui::style::SharedTheme, ui::style::SharedTheme,
}; };
use anyhow::Result; use anyhow::Result;
@ -36,6 +36,7 @@ pub struct Revlog {
queue: Queue, queue: Queue,
visible: bool, visible: bool,
branch_name: cached::BranchName, branch_name: cached::BranchName,
key_config: SharedKeyConfig,
} }
impl Revlog { impl Revlog {
@ -44,6 +45,7 @@ impl Revlog {
queue: &Queue, queue: &Queue,
sender: &Sender<AsyncNotification>, sender: &Sender<AsyncNotification>,
theme: SharedTheme, theme: SharedTheme,
key_config: SharedKeyConfig,
) -> Self { ) -> Self {
Self { Self {
queue: queue.clone(), queue: queue.clone(),
@ -51,12 +53,18 @@ impl Revlog {
queue, queue,
sender, sender,
theme.clone(), theme.clone(),
key_config.clone(),
),
list: CommitList::new(
&strings::log_title(&key_config),
theme,
key_config.clone(),
), ),
list: CommitList::new(strings::LOG_TITLE, theme),
git_log: AsyncLog::new(sender), git_log: AsyncLog::new(sender),
git_tags: AsyncTags::new(sender), git_tags: AsyncTags::new(sender),
visible: false, visible: false,
branch_name: cached::BranchName::new(CWD), branch_name: cached::BranchName::new(CWD),
key_config,
} }
} }
@ -199,48 +207,35 @@ impl Component for Revlog {
if event_used { if event_used {
self.update()?; self.update()?;
return Ok(true); return Ok(true);
} else { } else if let Event::Key(k) = ev {
match ev { if k == self.key_config.log_commit_details {
Event::Key(keys::LOG_COMMIT_DETAILS) => { self.commit_details.toggle_visible()?;
self.commit_details.toggle_visible()?; self.update()?;
self.update()?; return Ok(true);
return Ok(true); } else if k == self.key_config.log_tag_commit {
} return if let Some(id) = self.selected_commit() {
self.queue
Event::Key(keys::LOG_TAG_COMMIT) => { .borrow_mut()
return if let Some(id) = .push_back(InternalEvent::TagCommit(id));
self.selected_commit() Ok(true)
{ } else {
self.queue.borrow_mut().push_back( Ok(false)
InternalEvent::TagCommit(id), };
); } else if k == self.key_config.focus_right
Ok(true) && self.commit_details.is_visible()
} else { {
Ok(false) return if let Some(id) = self.selected_commit() {
}; self.queue.borrow_mut().push_back(
} InternalEvent::InspectCommit(
id,
Event::Key(keys::FOCUS_RIGHT) self.selected_commit_tags(&Some(id)),
if self.commit_details.is_visible() => ),
{ );
return if let Some(id) = Ok(true)
self.selected_commit() } else {
{ Ok(false)
self.queue.borrow_mut().push_back( };
InternalEvent::InspectCommit( } else {
id,
self.selected_commit_tags(&Some(
id,
)),
),
);
Ok(true)
} else {
Ok(false)
};
}
_ => (),
} }
} }
} }
@ -258,20 +253,20 @@ impl Component for Revlog {
} }
out.push(CommandInfo::new( out.push(CommandInfo::new(
commands::LOG_DETAILS_TOGGLE, strings::commands::log_details_toggle(&self.key_config),
true, true,
self.visible, self.visible,
)); ));
out.push(CommandInfo::new( out.push(CommandInfo::new(
commands::LOG_DETAILS_OPEN, strings::commands::log_details_open(&self.key_config),
true, true,
(self.visible && self.commit_details.is_visible()) (self.visible && self.commit_details.is_visible())
|| force_all, || force_all,
)); ));
out.push(CommandInfo::new( out.push(CommandInfo::new(
commands::LOG_TAG_COMMIT, strings::commands::log_tag_commit(&self.key_config),
true, true,
self.visible || force_all, self.visible || force_all,
)); ));

View File

@ -5,9 +5,9 @@ use crate::{
CommandBlocking, CommandInfo, Component, DrawableComponent, CommandBlocking, CommandInfo, Component, DrawableComponent,
FileTreeComponent, FileTreeComponent,
}, },
keys, keys::SharedKeyConfig,
queue::{InternalEvent, Queue}, queue::{InternalEvent, Queue},
strings::{self, commands}, strings,
ui::style::SharedTheme, ui::style::SharedTheme,
}; };
use anyhow::Result; use anyhow::Result;
@ -36,6 +36,7 @@ pub struct Stashing {
theme: SharedTheme, theme: SharedTheme,
git_status: AsyncStatus, git_status: AsyncStatus,
queue: Queue, queue: Queue,
key_config: SharedKeyConfig,
} }
impl Stashing { impl Stashing {
@ -46,13 +47,15 @@ impl Stashing {
sender: &Sender<AsyncNotification>, sender: &Sender<AsyncNotification>,
queue: &Queue, queue: &Queue,
theme: SharedTheme, theme: SharedTheme,
key_config: SharedKeyConfig,
) -> Self { ) -> Self {
Self { Self {
index: FileTreeComponent::new( index: FileTreeComponent::new(
strings::STASHING_FILES_TITLE, &strings::stashing_files_title(&key_config),
true, true,
Some(queue.clone()), Some(queue.clone()),
theme.clone(), theme.clone(),
key_config.clone(),
), ),
visible: false, visible: false,
options: StashingOptions { options: StashingOptions {
@ -62,6 +65,7 @@ impl Stashing {
theme, theme,
git_status: AsyncStatus::new(sender.clone()), git_status: AsyncStatus::new(sender.clone()),
queue: queue.clone(), queue: queue.clone(),
key_config,
} }
} }
@ -149,11 +153,11 @@ impl DrawableComponent for Stashing {
f.render_widget( f.render_widget(
Paragraph::new(self.get_option_text().iter()) Paragraph::new(self.get_option_text().iter())
.block( .block(Block::default().borders(Borders::ALL).title(
Block::default() &strings::stashing_options_title(
.borders(Borders::ALL) &self.key_config,
.title(strings::STASHING_OPTIONS_TITLE), ),
) ))
.alignment(Alignment::Left), .alignment(Alignment::Left),
right_chunks[0], right_chunks[0],
); );
@ -178,17 +182,21 @@ impl Component for Stashing {
); );
out.push(CommandInfo::new( out.push(CommandInfo::new(
commands::STASHING_SAVE, strings::commands::stashing_save(&self.key_config),
self.visible && !self.index.is_empty(), self.visible && !self.index.is_empty(),
self.visible || force_all, self.visible || force_all,
)); ));
out.push(CommandInfo::new( out.push(CommandInfo::new(
commands::STASHING_TOGGLE_INDEXED, strings::commands::stashing_toggle_indexed(
&self.key_config,
),
self.visible, self.visible,
self.visible || force_all, self.visible || force_all,
)); ));
out.push(CommandInfo::new( out.push(CommandInfo::new(
commands::STASHING_TOGGLE_UNTRACKED, strings::commands::stashing_toggle_untracked(
&self.key_config,
),
self.visible, self.visible,
self.visible || force_all, self.visible || force_all,
)); ));
@ -204,31 +212,30 @@ impl Component for Stashing {
} }
if let Event::Key(k) = ev { if let Event::Key(k) = ev {
return match k { return if k == self.key_config.stashing_save
keys::STASHING_SAVE if !self.index.is_empty() => { && !self.index.is_empty()
self.queue.borrow_mut().push_back( {
InternalEvent::PopupStashing( self.queue.borrow_mut().push_back(
self.options, InternalEvent::PopupStashing(self.options),
), );
);
Ok(true) Ok(true)
} } else if k == self.key_config.stashing_toggle_index {
keys::STASHING_TOGGLE_INDEX => { self.options.keep_index =
self.options.keep_index = !self.options.keep_index;
!self.options.keep_index; self.update()?;
self.update()?; Ok(true)
Ok(true) } else if k
} == self.key_config.stashing_toggle_untracked
keys::STASHING_TOGGLE_UNTRACKED => { {
self.options.stash_untracked = self.options.stash_untracked =
!self.options.stash_untracked; !self.options.stash_untracked;
self.update()?; self.update()?;
Ok(true) Ok(true)
} } else {
_ => Ok(false), Ok(false)
}; };
} };
} }
Ok(false) Ok(false)

View File

@ -3,9 +3,9 @@ use crate::{
visibility_blocking, CommandBlocking, CommandInfo, visibility_blocking, CommandBlocking, CommandInfo,
CommitList, Component, DrawableComponent, CommitList, Component, DrawableComponent,
}, },
keys, keys::SharedKeyConfig,
queue::{Action, InternalEvent, Queue}, queue::{Action, InternalEvent, Queue},
strings::{self, commands}, strings,
ui::style::SharedTheme, ui::style::SharedTheme,
}; };
use anyhow::Result; use anyhow::Result;
@ -19,15 +19,25 @@ pub struct StashList {
list: CommitList, list: CommitList,
visible: bool, visible: bool,
queue: Queue, queue: Queue,
key_config: SharedKeyConfig,
} }
impl StashList { impl StashList {
/// ///
pub fn new(queue: &Queue, theme: SharedTheme) -> Self { pub fn new(
queue: &Queue,
theme: SharedTheme,
key_config: SharedKeyConfig,
) -> Self {
Self { Self {
visible: false, visible: false,
list: CommitList::new(strings::STASHLIST_TITLE, theme), list: CommitList::new(
&strings::stashlist_title(&key_config),
theme,
key_config.clone(),
),
queue: queue.clone(), queue: queue.clone(),
key_config,
} }
} }
@ -111,17 +121,19 @@ impl Component for StashList {
let selection_valid = let selection_valid =
self.list.selected_entry().is_some(); self.list.selected_entry().is_some();
out.push(CommandInfo::new( out.push(CommandInfo::new(
commands::STASHLIST_APPLY, strings::commands::stashlist_apply(&self.key_config),
selection_valid, selection_valid,
true, true,
)); ));
out.push(CommandInfo::new( out.push(CommandInfo::new(
commands::STASHLIST_DROP, strings::commands::stashlist_drop(&self.key_config),
selection_valid, selection_valid,
true, true,
)); ));
out.push(CommandInfo::new( out.push(CommandInfo::new(
commands::STASHLIST_INSPECT, strings::commands::stashlist_inspect(
&self.key_config,
),
selection_valid, selection_valid,
true, true,
)); ));
@ -137,13 +149,14 @@ impl Component for StashList {
} }
if let Event::Key(k) = ev { if let Event::Key(k) = ev {
match k { if k == self.key_config.stash_apply {
keys::STASH_APPLY => self.apply_stash(), self.apply_stash()
keys::STASH_DROP => self.drop_stash(), } else if k == self.key_config.stash_drop {
keys::STASH_OPEN => self.inspect(), self.drop_stash()
} else if k == self.key_config.stash_open {
_ => (), self.inspect()
}; } else {
}
} }
} }

View File

@ -5,9 +5,9 @@ use crate::{
ChangesComponent, CommandBlocking, CommandInfo, Component, ChangesComponent, CommandBlocking, CommandInfo, Component,
DiffComponent, DrawableComponent, FileTreeItemKind, DiffComponent, DrawableComponent, FileTreeItemKind,
}, },
keys, keys::SharedKeyConfig,
queue::{InternalEvent, Queue, ResetItem}, queue::{InternalEvent, Queue, ResetItem},
strings::{self, commands, order}, strings::{self, order},
ui::style::SharedTheme, ui::style::SharedTheme,
}; };
use anyhow::Result; use anyhow::Result;
@ -47,6 +47,7 @@ pub struct Status {
git_status_stage: AsyncStatus, git_status_stage: AsyncStatus,
queue: Queue, queue: Queue,
git_action_executed: bool, git_action_executed: bool,
key_config: SharedKeyConfig,
} }
impl DrawableComponent for Status { impl DrawableComponent for Status {
@ -107,6 +108,7 @@ impl Status {
queue: &Queue, queue: &Queue,
sender: &Sender<AsyncNotification>, sender: &Sender<AsyncNotification>,
theme: SharedTheme, theme: SharedTheme,
key_config: SharedKeyConfig,
) -> Self { ) -> Self {
Self { Self {
queue: queue.clone(), queue: queue.clone(),
@ -114,24 +116,32 @@ impl Status {
focus: Focus::WorkDir, focus: Focus::WorkDir,
diff_target: DiffTarget::WorkingDir, diff_target: DiffTarget::WorkingDir,
index_wd: ChangesComponent::new( index_wd: ChangesComponent::new(
strings::TITLE_STATUS, &strings::title_status(&key_config),
true, true,
true, true,
queue.clone(), queue.clone(),
theme.clone(), theme.clone(),
key_config.clone(),
), ),
index: ChangesComponent::new( index: ChangesComponent::new(
strings::TITLE_INDEX, &strings::title_index(&key_config),
false, false,
false, false,
queue.clone(), queue.clone(),
theme.clone(), theme.clone(),
key_config.clone(),
),
diff: DiffComponent::new(
queue.clone(),
theme,
key_config.clone(),
false,
), ),
diff: DiffComponent::new(queue.clone(), theme, false),
git_diff: AsyncDiff::new(sender.clone()), git_diff: AsyncDiff::new(sender.clone()),
git_status_workdir: AsyncStatus::new(sender.clone()), git_status_workdir: AsyncStatus::new(sender.clone()),
git_status_stage: AsyncStatus::new(sender.clone()), git_status_stage: AsyncStatus::new(sender.clone()),
git_action_executed: false, git_action_executed: false,
key_config,
} }
} }
@ -332,7 +342,7 @@ impl Component for Status {
{ {
let focus_on_diff = self.focus == Focus::Diff; let focus_on_diff = self.focus == Focus::Diff;
out.push(CommandInfo::new( out.push(CommandInfo::new(
commands::EDIT_ITEM, strings::commands::edit_item(&self.key_config),
if focus_on_diff { if focus_on_diff {
true true
} else { } else {
@ -341,12 +351,12 @@ impl Component for Status {
self.visible || force_all, self.visible || force_all,
)); ));
out.push(CommandInfo::new( out.push(CommandInfo::new(
commands::DIFF_FOCUS_LEFT, strings::commands::diff_focus_left(&self.key_config),
true, true,
(self.visible && focus_on_diff) || force_all, (self.visible && focus_on_diff) || force_all,
)); ));
out.push(CommandInfo::new( out.push(CommandInfo::new(
commands::DIFF_FOCUS_RIGHT, strings::commands::diff_focus_right(&self.key_config),
self.can_focus_diff(), self.can_focus_diff(),
(self.visible && !focus_on_diff) || force_all, (self.visible && !focus_on_diff) || force_all,
)); ));
@ -354,7 +364,7 @@ impl Component for Status {
out.push( out.push(
CommandInfo::new( CommandInfo::new(
commands::SELECT_STATUS, strings::commands::select_status(&self.key_config),
true, true,
(self.visible && self.focus == Focus::Diff) (self.visible && self.focus == Focus::Diff)
|| force_all, || force_all,
@ -364,7 +374,7 @@ impl Component for Status {
out.push( out.push(
CommandInfo::new( CommandInfo::new(
commands::SELECT_STAGING, strings::commands::select_staging(&self.key_config),
true, true,
(self.visible && self.focus == Focus::WorkDir) (self.visible && self.focus == Focus::WorkDir)
|| force_all, || force_all,
@ -374,7 +384,7 @@ impl Component for Status {
out.push( out.push(
CommandInfo::new( CommandInfo::new(
commands::SELECT_UNSTAGED, strings::commands::select_unstaged(&self.key_config),
true, true,
(self.visible && self.focus == Focus::Stage) (self.visible && self.focus == Focus::Stage)
|| force_all, || force_all,
@ -393,50 +403,43 @@ impl Component for Status {
} }
if let Event::Key(k) = ev { if let Event::Key(k) = ev {
return match k { return if k == self.key_config.focus_workdir {
keys::FOCUS_WORKDIR => { self.switch_focus(Focus::WorkDir)
self.switch_focus(Focus::WorkDir) } else if k == self.key_config.focus_stage {
self.switch_focus(Focus::Stage)
} else if k == self.key_config.edit_file
&& (self.can_focus_diff()
|| self.focus == Focus::Diff)
{
if let Some((path, _)) = self.selected_path() {
self.queue.borrow_mut().push_back(
InternalEvent::OpenExternalEditor(Some(
path,
)),
);
} }
keys::FOCUS_STAGE => { Ok(true)
self.switch_focus(Focus::Stage) } else if k == self.key_config.focus_right
} && self.can_focus_diff()
keys::EDIT_FILE {
if self.can_focus_diff() self.switch_focus(Focus::Diff)
|| self.focus == Focus::Diff => } else if k == self.key_config.focus_left {
{ self.switch_focus(match self.diff_target {
if let Some((path, _)) = self.selected_path() DiffTarget::Stage => Focus::Stage,
{ DiffTarget::WorkingDir => Focus::WorkDir,
self.queue.borrow_mut().push_back( })
InternalEvent::OpenExternalEditor( } else if k == self.key_config.move_down
Some(path), && self.focus == Focus::WorkDir
), && !self.index.is_empty()
); {
} self.switch_focus(Focus::Stage)
Ok(true) } else if k == self.key_config.move_up
} && self.focus == Focus::Stage
keys::FOCUS_RIGHT if self.can_focus_diff() => { && !self.index_wd.is_empty()
self.switch_focus(Focus::Diff) {
} self.switch_focus(Focus::WorkDir)
keys::FOCUS_LEFT => { } else {
self.switch_focus(match self.diff_target { Ok(false)
DiffTarget::Stage => Focus::Stage,
DiffTarget::WorkingDir => Focus::WorkDir,
})
}
keys::MOVE_DOWN
if self.focus == Focus::WorkDir
&& !self.index.is_empty() =>
{
self.switch_focus(Focus::Stage)
}
keys::MOVE_UP
if self.focus == Focus::Stage
&& !self.index_wd.is_empty() =>
{
self.switch_focus(Focus::WorkDir)
}
_ => Ok(false),
}; };
} }
} }