diff --git a/.gitignore b/.gitignore index 715365c3bb..827a02d93f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ .DS_Store /.idea/ flamegraph.svg +KEY_CONFIG.md diff --git a/Cargo.lock b/Cargo.lock index ba793a39c0..9affbc87eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -327,6 +327,7 @@ dependencies = [ "libc", "mio", "parking_lot 0.10.2", + "serde", "signal-hook", "winapi", ] diff --git a/Cargo.toml b/Cargo.toml index 6cf3802546..007bbc0ec0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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} diff --git a/KEY_CONFIG.md b/KEY_CONFIG.md new file mode 100644 index 0000000000..d8f1bdc08e --- /dev/null +++ b/KEY_CONFIG.md @@ -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. diff --git a/README.md b/README.md index eab8fa08e8..5ada4e5479 100644 --- a/README.md +++ b/README.md @@ -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). diff --git a/assets/vim_style_key_config.ron b/assets/vim_style_key_config.ron new file mode 100644 index 0000000000..84dce37601 --- /dev/null +++ b/assets/vim_style_key_config.ron @@ -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,),), +) diff --git a/src/app.rs b/src/app.rs index a66f5d90cd..c2a3dee9ca 100644 --- a/src/app.rs +++ b/src/app.rs @@ -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, ); diff --git a/src/cmdbar.rs b/src/cmdbar.rs index 8ccfe5e0fe..4496b00dcd 100644 --- a/src/cmdbar.rs +++ b/src/cmdbar.rs @@ -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, cmd_infos: Vec, 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(&self, f: &mut Frame, 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 diff --git a/src/components/changes.rs b/src/components/changes.rs index d6a56bd301..cd7c754c7f 100644 --- a/src/components/changes.rs +++ b/src/components/changes.rs @@ -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.queue.borrow_mut().push_back( - InternalEvent::Update(NeedsUpdate::ALL), + self.index_add_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) }; } } diff --git a/src/components/command.rs b/src/components/command.rs index bd336cb89f..8c6a6e078a 100644 --- a/src/components/command.rs +++ b/src/components/command.rs @@ -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 { diff --git a/src/components/commit.rs b/src/components/commit.rs index c5876f9839..72ce7cf34f 100644 --- a/src/components/commit.rs +++ b/src/components/commit.rs @@ -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, 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()); diff --git a/src/components/commit_details/details.rs b/src/components/commit_details/details.rs index 44b0a798fc..59eb4793d6 100644 --- a/src/components/commit_details/details.rs +++ b/src/components/commit_details/details.rs @@ -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, tags: Vec, @@ -31,6 +38,7 @@ pub struct DetailsComponent { focused: bool, current_size: Cell<(u16, u16)>, scroll_top: Cell, + 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 { 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 { 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) }; } } diff --git a/src/components/commit_details/mod.rs b/src/components/commit_details/mod.rs index 044f680481..9d977e3f5b 100644 --- a/src/components/commit_details/mod.rs +++ b/src/components/commit_details/mod.rs @@ -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, 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) }; } } diff --git a/src/components/commitlist.rs b/src/components/commitlist.rs index 198b65da11..66cdf66309 100644 --- a/src/components/commitlist.rs +++ b/src/components/commitlist.rs @@ -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, 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>, + txt: &mut Vec>, tags: Option, theme: &Theme, width: usize, @@ -331,28 +337,25 @@ impl DrawableComponent for CommitList { impl Component for CommitList { fn event(&mut self, ev: Event) -> Result { 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, )); diff --git a/src/components/diff.rs b/src/components/diff.rs index 71d357e007..f338701069 100644 --- a/src/components/diff.rs +++ b/src/components/diff.rs @@ -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, 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 { if self.focused { if let Event::Key(e) = ev { - return match e { - keys::MOVE_DOWN => { - self.move_selection(ScrollType::Down)?; - Ok(true) - } - 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) + 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::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) }; } } diff --git a/src/components/externaleditor.rs b/src/components/externaleditor.rs index 39d7e41c98..7792ac3394 100644 --- a/src/components/externaleditor.rs +++ b/src/components/externaleditor.rs @@ -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); diff --git a/src/components/filetree.rs b/src/components/filetree.rs index 254f217361..56a77e5eed 100644 --- a/src/components/filetree.rs +++ b/src/components/filetree.rs @@ -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, theme: SharedTheme, + key_config: SharedKeyConfig, scroll_top: Cell, } @@ -39,6 +40,7 @@ impl FileTreeComponent { focus: bool, queue: Option, 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> { + theme: &'b SharedTheme, + ) -> Option> { 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 { 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) }; } } diff --git a/src/components/help.rs b/src/components/help.rs index b6b9507d1f..e3e63a671d 100644 --- a/src/components/help.rs +++ b/src/components/help.rs @@ -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 { 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::>(); - 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)); } diff --git a/src/components/inspect_commit.rs b/src/components/inspect_commit.rs index 56e3d9e141..00a8028813 100644 --- a/src/components/inspect_commit.rs +++ b/src/components/inspect_commit.rs @@ -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, 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, } } diff --git a/src/components/msg.rs b/src/components/msg.rs index 643c4a0231..075fda2923 100644 --- a/src/components/msg.rs +++ b/src/components/msg.rs @@ -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 { 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, } } /// diff --git a/src/components/reset.rs b/src/components/reset.rs index 90ce98dd93..350848862f 100644 --- a/src/components/reset.rs +++ b/src/components/reset.rs @@ -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()) } } diff --git a/src/components/stashmsg.rs b/src/components/stashmsg.rs index 28cd52c380..bb9b5fd134 100644 --- a/src/components/stashmsg.rs +++ b/src/components/stashmsg.rs @@ -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, } } diff --git a/src/components/tag_commit.rs b/src/components/tag_commit.rs index e956726f58..58f2671561 100644 --- a/src/components/tag_commit.rs +++ b/src/components/tag_commit.rs @@ -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, 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, } } diff --git a/src/components/textinput.rs b/src/components/textinput.rs index 67c8b98f8c..651a3f7e46 100644 --- a/src/components/textinput.rs +++ b/src/components/textinput.rs @@ -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")); diff --git a/src/keys.rs b/src/keys.rs index 3c7020d8d3..00b9b46da4 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -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; + +#[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 { + let app_home = get_app_config_path()?; + Ok(app_home.join("key_config.ron")) + } + + fn read_file(config_file: PathBuf) -> Result { + 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 { + 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() + } +} + +// 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(), } } -const fn with_mod( - code: KeyCode, - modifiers: KeyModifiers, -) -> KeyEvent { - KeyEvent { code, modifiers } +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(), + } } -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); +#[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 + ); + } +} diff --git a/src/strings.rs b/src/strings.rs index 0f3ef7ea78..14626335ef 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -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]"; +use crate::keys::{get_hint, SharedKeyConfig}; -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 mod order { + pub static NAV: i8 = 1; +} -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##" +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"##; -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:"; +# 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 order { - pub static NAV: i8 = 1; +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 = - CommandText::new( - "Nav [\u{2191}\u{2193}]", + pub fn toggle_tabs(key_config: &SharedKeyConfig) -> CommandText { + CommandText::new( + 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 fn navigate_tree( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + 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 static STASHING_SAVE: CommandText = CommandText::new( - "Save [s]", - "opens stash name input popup", - CMD_GROUP_STASHING, - ); - /// - pub static STASHING_TOGGLE_INDEXED: CommandText = - CommandText::new( - "Toggle Staged [i]", + 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, + ) + } } diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index f69a22c650..633e157c9e 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -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, 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, )); diff --git a/src/tabs/stashing.rs b/src/tabs/stashing.rs index a32591942a..0bb0541880 100644 --- a/src/tabs/stashing.rs +++ b/src/tabs/stashing.rs @@ -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, 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, - ), - ); - - 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), + return if k == self.key_config.stashing_save + && !self.index.is_empty() + { + self.queue.borrow_mut().push_back( + InternalEvent::PopupStashing(self.options), + ); + + 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) diff --git a/src/tabs/stashlist.rs b/src/tabs/stashlist.rs index 521c2fa6b3..87f6b2b968 100644 --- a/src/tabs/stashlist.rs +++ b/src/tabs/stashlist.rs @@ -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 { + } } } diff --git a/src/tabs/status.rs b/src/tabs/status.rs index 4aaae6ec60..42194e32a4 100644 --- a/src/tabs/status.rs +++ b/src/tabs/status.rs @@ -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, 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) - } - 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) + 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, + )), + ); } - _ => 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) }; } }