Skip to content

Commit

Permalink
Support key binds mapping (#21)
Browse files Browse the repository at this point in the history
* Add flake

* Add default key bind

* Show help from keybind

* Add custom keybind config

* Merge keybind config into `Config.toml`

* Drop insert mode

* Add UserEvent::ForceQuit

* Fix return type of Keybind::new()

* Drop error print for mal keybind config

* Handle StatusLine regardless UserEvent

* Rename UserEvent::Confirm

* Modify captal default keys

* Let keybind in snake style

* Fix error msg for unparsed keybind

* Modify StatusLine only after AppEvent::Key

* Revert "Add flake"

This reverts commit d146770.
  • Loading branch information
yanganto authored Aug 9, 2024
1 parent 04ccd88 commit db1db65
Show file tree
Hide file tree
Showing 13 changed files with 515 additions and 280 deletions.
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) => {
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

0 comments on commit db1db65

Please sign in to comment.