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
/.idea/
flamegraph.svg
KEY_CONFIG.md

1
Cargo.lock generated
View File

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

View File

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

View File

@ -1,5 +1,6 @@
use crate::{
components::CommandInfo, strings, ui::style::SharedTheme,
components::CommandInfo, keys::SharedKeyConfig, strings,
ui::style::SharedTheme,
};
use std::borrow::Cow;
use tui::{
@ -27,6 +28,7 @@ pub struct CommandBar {
draw_list: Vec<DrawListEntry>,
cmd_infos: Vec<CommandInfo>,
theme: SharedTheme,
key_config: SharedKeyConfig,
lines: u16,
width: u16,
expandable: bool,
@ -36,11 +38,15 @@ pub struct CommandBar {
const MORE_WIDTH: u16 = 11;
impl CommandBar {
pub const fn new(theme: SharedTheme) -> Self {
pub const fn new(
theme: SharedTheme,
key_config: SharedKeyConfig,
) -> Self {
Self {
draw_list: Vec::new(),
cmd_infos: Vec::new(),
theme,
key_config,
lines: 0,
width: 0,
expandable: false,
@ -58,7 +64,8 @@ impl CommandBar {
fn is_multiline(&self, width: u16) -> bool {
let mut line_width = 0_usize;
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 {
return true;
@ -83,7 +90,8 @@ impl CommandBar {
let mut lines = 1_u16;
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 {
self.draw_list.push(DrawListEntry::LineBreak);
@ -131,7 +139,9 @@ impl CommandBar {
}
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
.draw_list

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,8 @@ use crate::{
popup_paragraph, visibility_blocking, CommandBlocking,
CommandInfo, Component, DrawableComponent,
},
strings::commands,
keys::SharedKeyConfig,
strings,
ui::{self, style::SharedTheme},
};
use anyhow::Result;
@ -23,6 +24,7 @@ pub struct TextInputComponent {
msg: String,
visible: bool,
theme: SharedTheme,
key_config: SharedKeyConfig,
cursor_position: usize,
}
@ -30,6 +32,7 @@ impl TextInputComponent {
///
pub fn new(
theme: SharedTheme,
key_config: SharedKeyConfig,
title: &str,
default_msg: &str,
) -> Self {
@ -37,6 +40,7 @@ impl TextInputComponent {
msg: String::default(),
visible: false,
theme,
key_config,
title: title.to_string(),
default_msg: default_msg.to_string(),
cursor_position: 0,
@ -196,7 +200,7 @@ impl Component for TextInputComponent {
) -> CommandBlocking {
out.push(
CommandInfo::new(
commands::CLOSE_POPUP,
strings::commands::close_popup(&self.key_config),
true,
self.visible,
)
@ -274,8 +278,12 @@ mod tests {
#[test]
fn test_smoke() {
let mut comp =
TextInputComponent::new(SharedTheme::default(), "", "");
let mut comp = TextInputComponent::new(
SharedTheme::default(),
SharedKeyConfig::default(),
"",
"",
);
comp.set_text(String::from("a\nb"));
@ -298,8 +306,12 @@ mod tests {
#[test]
fn test_visualize_newline() {
let mut comp =
TextInputComponent::new(SharedTheme::default(), "", "");
let mut comp = TextInputComponent::new(
SharedTheme::default(),
SharedKeyConfig::default(),
"",
"",
);
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 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 {
KeyEvent {
code,
modifiers: KeyModifiers::empty(),
pub type SharedKeyConfig = Rc<KeyConfig>;
#[derive(Serialize, Deserialize, Debug)]
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(
code: KeyCode,
modifiers: KeyModifiers,
) -> KeyEvent {
KeyEvent { code, modifiers }
// The hint follows apple design
// http://xahlee.info/comp/unicode_computing_symbols.html
pub fn get_hint(ev: KeyEvent) -> String {
match ev.code {
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'));
pub const TAB_2: KeyEvent = no_mod(KeyCode::Char('2'));
pub const TAB_3: KeyEvent = no_mod(KeyCode::Char('3'));
pub const TAB_4: KeyEvent = no_mod(KeyCode::Char('4'));
pub const TAB_TOGGLE: KeyEvent = no_mod(KeyCode::Tab);
pub const TAB_TOGGLE_REVERSE: KeyEvent = no_mod(KeyCode::BackTab);
//TODO: https://github.com/extrawurst/gitui/issues/112
pub const TAB_TOGGLE_REVERSE_WINDOWS: KeyEvent =
with_mod(KeyCode::BackTab, KeyModifiers::SHIFT);
pub const FOCUS_WORKDIR: KeyEvent = no_mod(KeyCode::Char('w'));
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);
pub const FOCUS_BELOW: KeyEvent = no_mod(KeyCode::Down);
pub const EXIT: KeyEvent =
with_mod(KeyCode::Char('c'), KeyModifiers::CONTROL);
pub const EXIT_POPUP: KeyEvent = no_mod(KeyCode::Esc);
pub const CLOSE_MSG: KeyEvent = no_mod(KeyCode::Enter);
pub const OPEN_COMMIT: KeyEvent = no_mod(KeyCode::Char('c'));
pub const OPEN_COMMIT_EDITOR: KeyEvent =
with_mod(KeyCode::Char('e'), KeyModifiers::CONTROL);
pub const OPEN_HELP: KeyEvent = no_mod(KeyCode::Char('h'));
pub const MOVE_LEFT: KeyEvent = no_mod(KeyCode::Left);
pub const MOVE_RIGHT: KeyEvent = no_mod(KeyCode::Right);
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);
pub const MOVE_DOWN: KeyEvent = no_mod(KeyCode::Down);
pub const PAGE_DOWN: KeyEvent = no_mod(KeyCode::PageDown);
pub const PAGE_UP: KeyEvent = no_mod(KeyCode::PageUp);
pub const SHIFT_UP: KeyEvent =
with_mod(KeyCode::Up, KeyModifiers::SHIFT);
pub const SHIFT_DOWN: KeyEvent =
with_mod(KeyCode::Down, KeyModifiers::SHIFT);
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);
fn get_modifier_hint(modifier: KeyModifiers) -> String {
match modifier {
KeyModifiers::CONTROL => "^".to_string(),
KeyModifiers::SHIFT => {
"\u{21e7}".to_string() //⇧
}
KeyModifiers::ALT => {
"\u{2325}".to_string() //⌥
}
_ => "".to_string(),
}
}
#[cfg(test)]
mod tests {
use super::{get_hint, KeyConfig};
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
#[test]
fn test_get_hint() {
let h = get_hint(KeyEvent {
code: KeyCode::Char('c'),
modifiers: KeyModifiers::CONTROL,
});
assert_eq!(h, "^c");
}
#[test]
fn test_load_vim_style_example() {
assert_eq!(
KeyConfig::read_file(
"assets/vim_style_key_config.ron".into()
)
.is_ok(),
true
);
}
}

View File

@ -1,63 +1,155 @@
pub static TITLE_STATUS: &str = "Unstaged Changes [w]";
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:";
}
use crate::keys::{get_hint, SharedKeyConfig};
pub mod order {
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 {
use crate::components::CommandText;
use crate::keys::{get_hint, SharedKeyConfig};
static CMD_GROUP_GENERAL: &str = "-- General --";
static CMD_GROUP_DIFF: &str = "-- Diff --";
@ -67,256 +159,425 @@ pub mod commands {
static CMD_GROUP_STASHES: &str = "-- Stashes --";
static CMD_GROUP_LOG: &str = "-- Log --";
///
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 =
pub fn toggle_tabs(key_config: &SharedKeyConfig) -> CommandText {
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",
CMD_GROUP_GENERAL,
);
///
pub static NAVIGATE_TREE: CommandText = CommandText::new(
"Nav [\u{2190}\u{2191}\u{2192}\u{2193}]",
"navigate tree view",
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 =
)
}
pub fn navigate_tree(
key_config: &SharedKeyConfig,
) -> CommandText {
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",
CMD_GROUP_STASHING,
);
///
pub static STASHING_TOGGLE_UNTRACKED: CommandText =
)
}
pub fn stashing_toggle_untracked(
key_config: &SharedKeyConfig,
) -> CommandText {
CommandText::new(
"Toggle Untracked [u]",
format!(
"Toggle Untracked [{}]",
get_hint(key_config.stashing_toggle_untracked),
),
"toggle including untracked files into stash",
CMD_GROUP_STASHING,
);
///
pub static STASHING_CONFIRM_MSG: CommandText = CommandText::new(
"Stash [enter]",
"save files to stash",
CMD_GROUP_STASHING,
);
///
pub static STASHLIST_APPLY: CommandText = CommandText::new(
"Apply [enter]",
"apply selected stash",
CMD_GROUP_STASHES,
);
///
pub static STASHLIST_DROP: CommandText = CommandText::new(
"Drop [D]",
"drop selected stash",
CMD_GROUP_STASHES,
);
///
pub static STASHLIST_INSPECT: CommandText = CommandText::new(
"Inspect [\u{2192}]", //→
"open stash commit details (allows to diff files)",
CMD_GROUP_STASHES,
);
///
pub static LOG_DETAILS_TOGGLE: CommandText = CommandText::new(
"Details [enter]",
"open details of selected commit",
CMD_GROUP_LOG,
);
///
pub static LOG_DETAILS_OPEN: CommandText = CommandText::new(
"Inspect [\u{2192}]", //→
"inspect selected commit in detail",
CMD_GROUP_LOG,
);
///
pub static LOG_TAG_COMMIT: CommandText =
CommandText::new("Tag [t]", "tag commit", CMD_GROUP_LOG);
///
pub static TAG_COMMIT_CONFIRM_MSG: CommandText =
CommandText::new("Tag [enter]", "tag commit", CMD_GROUP_LOG);
)
}
pub fn stashing_confirm_msg(
key_config: &SharedKeyConfig,
) -> CommandText {
CommandText::new(
format!("Stash [{}]", get_hint(key_config.close_msg),),
"save files to stash",
CMD_GROUP_STASHING,
)
}
pub fn stashlist_apply(
key_config: &SharedKeyConfig,
) -> CommandText {
CommandText::new(
format!("Apply [{}]", get_hint(key_config.stash_apply),),
"apply selected stash",
CMD_GROUP_STASHES,
)
}
pub fn stashlist_drop(
key_config: &SharedKeyConfig,
) -> CommandText {
CommandText::new(
format!("Drop [{}]", get_hint(key_config.stash_drop),),
"drop selected stash",
CMD_GROUP_STASHES,
)
}
pub fn stashlist_inspect(
key_config: &SharedKeyConfig,
) -> CommandText {
CommandText::new(
format!("Inspect [{}]", get_hint(key_config.focus_right),),
"open stash commit details (allows to diff files)",
CMD_GROUP_STASHES,
)
}
pub fn log_details_toggle(
key_config: &SharedKeyConfig,
) -> CommandText {
CommandText::new(
format!(
"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,
DrawableComponent,
},
keys,
keys::SharedKeyConfig,
queue::{InternalEvent, Queue},
strings::{self, commands},
strings,
ui::style::SharedTheme,
};
use anyhow::Result;
@ -36,6 +36,7 @@ pub struct Revlog {
queue: Queue,
visible: bool,
branch_name: cached::BranchName,
key_config: SharedKeyConfig,
}
impl Revlog {
@ -44,6 +45,7 @@ impl Revlog {
queue: &Queue,
sender: &Sender<AsyncNotification>,
theme: SharedTheme,
key_config: SharedKeyConfig,
) -> Self {
Self {
queue: queue.clone(),
@ -51,12 +53,18 @@ impl Revlog {
queue,
sender,
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_tags: AsyncTags::new(sender),
visible: false,
branch_name: cached::BranchName::new(CWD),
key_config,
}
}
@ -199,48 +207,35 @@ impl Component for Revlog {
if event_used {
self.update()?;
return Ok(true);
} else {
match ev {
Event::Key(keys::LOG_COMMIT_DETAILS) => {
self.commit_details.toggle_visible()?;
self.update()?;
return Ok(true);
}
Event::Key(keys::LOG_TAG_COMMIT) => {
return if let Some(id) =
self.selected_commit()
{
self.queue.borrow_mut().push_back(
InternalEvent::TagCommit(id),
);
Ok(true)
} else {
Ok(false)
};
}
Event::Key(keys::FOCUS_RIGHT)
if self.commit_details.is_visible() =>
{
return if let Some(id) =
self.selected_commit()
{
self.queue.borrow_mut().push_back(
InternalEvent::InspectCommit(
id,
self.selected_commit_tags(&Some(
id,
)),
),
);
Ok(true)
} else {
Ok(false)
};
}
_ => (),
} else if let Event::Key(k) = ev {
if k == self.key_config.log_commit_details {
self.commit_details.toggle_visible()?;
self.update()?;
return Ok(true);
} else if k == self.key_config.log_tag_commit {
return if let Some(id) = self.selected_commit() {
self.queue
.borrow_mut()
.push_back(InternalEvent::TagCommit(id));
Ok(true)
} else {
Ok(false)
};
} else if k == self.key_config.focus_right
&& self.commit_details.is_visible()
{
return if let Some(id) = self.selected_commit() {
self.queue.borrow_mut().push_back(
InternalEvent::InspectCommit(
id,
self.selected_commit_tags(&Some(id)),
),
);
Ok(true)
} else {
Ok(false)
};
} else {
}
}
}
@ -258,20 +253,20 @@ impl Component for Revlog {
}
out.push(CommandInfo::new(
commands::LOG_DETAILS_TOGGLE,
strings::commands::log_details_toggle(&self.key_config),
true,
self.visible,
));
out.push(CommandInfo::new(
commands::LOG_DETAILS_OPEN,
strings::commands::log_details_open(&self.key_config),
true,
(self.visible && self.commit_details.is_visible())
|| force_all,
));
out.push(CommandInfo::new(
commands::LOG_TAG_COMMIT,
strings::commands::log_tag_commit(&self.key_config),
true,
self.visible || force_all,
));

View File

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

View File

@ -3,9 +3,9 @@ use crate::{
visibility_blocking, CommandBlocking, CommandInfo,
CommitList, Component, DrawableComponent,
},
keys,
keys::SharedKeyConfig,
queue::{Action, InternalEvent, Queue},
strings::{self, commands},
strings,
ui::style::SharedTheme,
};
use anyhow::Result;
@ -19,15 +19,25 @@ pub struct StashList {
list: CommitList,
visible: bool,
queue: Queue,
key_config: SharedKeyConfig,
}
impl StashList {
///
pub fn new(queue: &Queue, theme: SharedTheme) -> Self {
pub fn new(
queue: &Queue,
theme: SharedTheme,
key_config: SharedKeyConfig,
) -> Self {
Self {
visible: false,
list: CommitList::new(strings::STASHLIST_TITLE, theme),
list: CommitList::new(
&strings::stashlist_title(&key_config),
theme,
key_config.clone(),
),
queue: queue.clone(),
key_config,
}
}
@ -111,17 +121,19 @@ impl Component for StashList {
let selection_valid =
self.list.selected_entry().is_some();
out.push(CommandInfo::new(
commands::STASHLIST_APPLY,
strings::commands::stashlist_apply(&self.key_config),
selection_valid,
true,
));
out.push(CommandInfo::new(
commands::STASHLIST_DROP,
strings::commands::stashlist_drop(&self.key_config),
selection_valid,
true,
));
out.push(CommandInfo::new(
commands::STASHLIST_INSPECT,
strings::commands::stashlist_inspect(
&self.key_config,
),
selection_valid,
true,
));
@ -137,13 +149,14 @@ impl Component for StashList {
}
if let Event::Key(k) = ev {
match k {
keys::STASH_APPLY => self.apply_stash(),
keys::STASH_DROP => self.drop_stash(),
keys::STASH_OPEN => self.inspect(),
_ => (),
};
if k == self.key_config.stash_apply {
self.apply_stash()
} else if k == self.key_config.stash_drop {
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,
DiffComponent, DrawableComponent, FileTreeItemKind,
},
keys,
keys::SharedKeyConfig,
queue::{InternalEvent, Queue, ResetItem},
strings::{self, commands, order},
strings::{self, order},
ui::style::SharedTheme,
};
use anyhow::Result;
@ -47,6 +47,7 @@ pub struct Status {
git_status_stage: AsyncStatus,
queue: Queue,
git_action_executed: bool,
key_config: SharedKeyConfig,
}
impl DrawableComponent for Status {
@ -107,6 +108,7 @@ impl Status {
queue: &Queue,
sender: &Sender<AsyncNotification>,
theme: SharedTheme,
key_config: SharedKeyConfig,
) -> Self {
Self {
queue: queue.clone(),
@ -114,24 +116,32 @@ impl Status {
focus: Focus::WorkDir,
diff_target: DiffTarget::WorkingDir,
index_wd: ChangesComponent::new(
strings::TITLE_STATUS,
&strings::title_status(&key_config),
true,
true,
queue.clone(),
theme.clone(),
key_config.clone(),
),
index: ChangesComponent::new(
strings::TITLE_INDEX,
&strings::title_index(&key_config),
false,
false,
queue.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_status_workdir: AsyncStatus::new(sender.clone()),
git_status_stage: AsyncStatus::new(sender.clone()),
git_action_executed: false,
key_config,
}
}
@ -332,7 +342,7 @@ impl Component for Status {
{
let focus_on_diff = self.focus == Focus::Diff;
out.push(CommandInfo::new(
commands::EDIT_ITEM,
strings::commands::edit_item(&self.key_config),
if focus_on_diff {
true
} else {
@ -341,12 +351,12 @@ impl Component for Status {
self.visible || force_all,
));
out.push(CommandInfo::new(
commands::DIFF_FOCUS_LEFT,
strings::commands::diff_focus_left(&self.key_config),
true,
(self.visible && focus_on_diff) || force_all,
));
out.push(CommandInfo::new(
commands::DIFF_FOCUS_RIGHT,
strings::commands::diff_focus_right(&self.key_config),
self.can_focus_diff(),
(self.visible && !focus_on_diff) || force_all,
));
@ -354,7 +364,7 @@ impl Component for Status {
out.push(
CommandInfo::new(
commands::SELECT_STATUS,
strings::commands::select_status(&self.key_config),
true,
(self.visible && self.focus == Focus::Diff)
|| force_all,
@ -364,7 +374,7 @@ impl Component for Status {
out.push(
CommandInfo::new(
commands::SELECT_STAGING,
strings::commands::select_staging(&self.key_config),
true,
(self.visible && self.focus == Focus::WorkDir)
|| force_all,
@ -374,7 +384,7 @@ impl Component for Status {
out.push(
CommandInfo::new(
commands::SELECT_UNSTAGED,
strings::commands::select_unstaged(&self.key_config),
true,
(self.visible && self.focus == Focus::Stage)
|| force_all,
@ -393,50 +403,43 @@ impl Component for Status {
}
if let Event::Key(k) = ev {
return match k {
keys::FOCUS_WORKDIR => {
self.switch_focus(Focus::WorkDir)
return if k == self.key_config.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 => {
self.switch_focus(Focus::Stage)
}
keys::EDIT_FILE
if 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),
),
);
}
Ok(true)
}
keys::FOCUS_RIGHT if self.can_focus_diff() => {
self.switch_focus(Focus::Diff)
}
keys::FOCUS_LEFT => {
self.switch_focus(match self.diff_target {
DiffTarget::Stage => Focus::Stage,
DiffTarget::WorkingDir => Focus::WorkDir,
})
}
keys::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),
Ok(true)
} else if k == self.key_config.focus_right
&& self.can_focus_diff()
{
self.switch_focus(Focus::Diff)
} else if k == self.key_config.focus_left {
self.switch_focus(match self.diff_target {
DiffTarget::Stage => Focus::Stage,
DiffTarget::WorkingDir => Focus::WorkDir,
})
} else if k == self.key_config.move_down
&& self.focus == Focus::WorkDir
&& !self.index.is_empty()
{
self.switch_focus(Focus::Stage)
} else if k == self.key_config.move_up
&& self.focus == Focus::Stage
&& !self.index_wd.is_empty()
{
self.switch_focus(Focus::WorkDir)
} else {
Ok(false)
};
}
}