Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support key binds mapping #21

Merged
merged 16 commits into from
Aug 9, 2024
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ semver = "1.0.23"
serde = { version = "1.0.204", features = ["derive"] }
serde_json = "1.0.122"
sha1 = "0.10.6"
strum = "0.26.3"
toml = "0.8.19"
tui-input = "0.9.0"
tui-tree-widget = "0.21.0"
Expand Down
31 changes: 31 additions & 0 deletions assets/default-keybind.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
force_quit = ["ctrl-c"]
quit = ["q"]
navigate_up = ["k", "up"]
navigate_down = ["j", "down"]
navigate_right = ["l", "right"]
navigate_left = ["h", "left"]

# close widget or cancel progress but not quit app
close_or_cancel = ["esc"]
help_toggle = ["?"]
go_to_top = ["g"]
go_to_bottom = ["shift-G"]
go_to_next = ["n"]
go_to_previous = ["shift-N"]
scroll_down = ["ctrl-e"]
scroll_up = ["ctrl-y"]
page_up = ["ctrl-b"]
page_down = ["ctrl-f"]
half_page_up = ["ctrl-u"]
half_page_down = ["ctrl-d"]
select_top = ["shift-H"]
select_middle = ["shift-M"]
select_bottom = ["shift-L"]
confirm = ["enter"]
search = ["/"]

# copy part of information, ex: copy the short commit hash not all
short_copy = ["c"]
full_copy = ["shift-C"]

ref_list_toggle = ["tab"]
65 changes: 39 additions & 26 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ use ratatui::{
use crate::{
color::ColorSet,
config::Config,
event::{AppEvent, Receiver, Sender},
event::{AppEvent, Receiver, Sender, UserEvent},
external::copy_to_clipboard,
git::Repository,
graph::{Graph, GraphImage},
key_code_char,
keybind::KeyBind,
protocol::ImageProtocol,
view::View,
widget::commit_list::{CommitInfo, CommitListState},
Expand Down Expand Up @@ -46,16 +46,19 @@ pub struct App<'a> {
view: View<'a>,
status_line: StatusLine,

keybind: &'a KeyBind,
config: &'a Config,
image_protocol: ImageProtocol,
tx: Sender,
}

impl<'a> App<'a> {
#[allow(clippy::too_many_arguments)]
pub fn new(
repository: &'a Repository,
graph: &'a Graph,
graph_image: &'a GraphImage,
keybind: &'a KeyBind,
config: &'a Config,
color_set: &'a ColorSet,
image_protocol: ImageProtocol,
Expand Down Expand Up @@ -94,6 +97,7 @@ impl<'a> App<'a> {
repository,
status_line: StatusLine::None,
view,
keybind,
config,
image_protocol,
tx,
Expand All @@ -109,33 +113,37 @@ impl App<'_> {
) -> std::io::Result<()> {
loop {
terminal.draw(|f| self.render(f))?;

match rx.recv() {
AppEvent::Key(key) => match key {
key_code_char!('c', Ctrl) => {
return Ok(());
}
_ => {
match self.status_line {
StatusLine::None | StatusLine::Input(_, _) => {
// do nothing
}
StatusLine::NotificationInfo(_)
| StatusLine::NotificationSuccess(_)
| StatusLine::NotificationWarn(_) => {
// Clear message and pass key input as is
self.clear_status_line();
}
StatusLine::NotificationError(_) => {
// Clear message and cancel key input
self.clear_status_line();
continue;
}
AppEvent::Key(key) => {
match self.status_line {
StatusLine::None | StatusLine::Input(_, _) => {
// do nothing
}
StatusLine::NotificationInfo(_)
| StatusLine::NotificationSuccess(_)
| StatusLine::NotificationWarn(_) => {
// Clear message and pass key input as is
self.clear_status_line();
}
StatusLine::NotificationError(_) => {
// Clear message and cancel key input
self.clear_status_line();
continue;
}
}

self.view.handle_key(key);
match self.keybind.get(&key) {
Some(UserEvent::ForceQuit) => {
self.tx.send(AppEvent::Quit);
}
Some(ue) => {
yanganto marked this conversation as resolved.
Show resolved Hide resolved
self.view.handle_event(ue, key);
}
None => {
self.view.handle_event(&UserEvent::Unknown, key);
}
}
},
}
AppEvent::Resize(w, h) => {
let _ = (w, h);
}
Expand Down Expand Up @@ -286,7 +294,12 @@ impl App<'_> {

fn open_help(&mut self) {
let before_view = std::mem::take(&mut self.view);
self.view = View::of_help(before_view, self.image_protocol, self.tx.clone());
self.view = View::of_help(
before_view,
self.image_protocol,
self.tx.clone(),
self.keybind,
);
}

fn close_help(&mut self) {
Expand Down
7 changes: 7 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use serde::Deserialize;

use crate::keybind::KeyBind;

const APP_DIR_NAME: &str = "serie";
const CONFIG_FILE_NAME: &str = "config.toml";

Expand All @@ -15,6 +17,8 @@ const DEFAULT_DETAIL_DATE_LOCAL: bool = true;
pub struct Config {
#[serde(default)]
pub ui: UiConfig,
/// The user customed keybinds, please ref `assets/default-keybind.toml`
pub keybind: Option<KeyBind>,
}

#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize)]
Expand Down Expand Up @@ -131,6 +135,7 @@ mod tests {
date_local: true,
},
},
keybind: None,
};
assert_eq!(actual, expected);
}
Expand Down Expand Up @@ -163,6 +168,7 @@ mod tests {
date_local: false,
},
},
keybind: None,
};
assert_eq!(actual, expected);
}
Expand All @@ -188,6 +194,7 @@ mod tests {
date_local: true,
},
},
keybind: None,
};
assert_eq!(actual, expected);
}
Expand Down
62 changes: 62 additions & 0 deletions src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use std::{
};

use ratatui::crossterm::event::KeyEvent;
use serde::Deserialize;
use strum::{EnumIter, EnumMessage};

pub enum AppEvent {
Key(KeyEvent),
Expand Down Expand Up @@ -79,3 +81,63 @@ pub fn init() -> (Sender, Receiver) {

(tx, rx)
}

/// The event triggered by user's key input
#[derive(Clone, Debug, strum::Display, Deserialize, EnumIter, Eq, EnumMessage, Hash, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum UserEvent {
// NOTE User Event should have document, else the enum item will be hidden in the help page
/// Navigate up
NavigateUp,
/// Navigate down
NavigateDown,
/// Navigate right
NavigateRight,
/// Navigate left
NavigateLeft,
/// Force Quit serie without passing input into widges or views
ForceQuit,
/// Quit serie
Quit,
/// Close widget or cancel current progress
CloseOrCancel,
/// Toggle Help page
HelpToggle,
/// Go to top
GoToTop,
/// Go to bottom
GoToBottom,
/// Go to next item
GoToNext,
/// Go to previous item
GoToPrevious,
/// Scroll one line up
ScrollUp,
/// Scroll one line down
ScrollDown,
/// Scroll one page up
PageUp,
/// Scroll one page down
PageDown,
/// Scroll half page up
HalfPageUp,
/// Scroll half page down
HalfPageDown,
/// Select top part
SelectTop,
/// Select middle part
SelectMiddle,
/// Select bottom part
SelectBottom,
/// Confirm
Confirm,
/// Search
Search,
/// Copy part of content
ShortCopy,
/// Copy
FullCopy,
/// Toggle for Reference List
RefListToggle,
Unknown,
}
Loading