From d146770db3456dffd69e37a0410aee0e1729ce96 Mon Sep 17 00:00:00 2001 From: Antonio Yang Date: Wed, 31 Jul 2024 10:31:13 +0800 Subject: [PATCH 01/16] Add flake --- flake.lock | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ flake.nix | 32 +++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 flake.lock create mode 100644 flake.nix diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..1bc587c --- /dev/null +++ b/flake.lock @@ -0,0 +1,82 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1722185531, + "narHash": "sha256-veKR07psFoJjINLC8RK4DiLniGGMgF3QMlS4tb74S6k=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "52ec9ac3b12395ad677e8b62106f0b98c1f8569d", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1722391647, + "narHash": "sha256-JTi7l1oxnatF1uX/gnGMlRnyFMtylRw4MqhCUdoN2K4=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "0fd4a5d2098faa516a9b83022aec7db766cd1de8", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..f2e17dd --- /dev/null +++ b/flake.nix @@ -0,0 +1,32 @@ +{ + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + rust-overlay = { + url = "github:oxalica/rust-overlay"; + inputs.nixpkgs.follows = "nixpkgs"; + inputs.flake-utils.follows = "flake-utils"; + }; + + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, rust-overlay, nixpkgs, flake-utils }: + flake-utils.lib.eachDefaultSystem (system: + let + overlays = [ (import rust-overlay) ]; + pkgs = import nixpkgs { + inherit system overlays; + }; + in + with pkgs; + { + devShells = rec { + default = mkShell { + buildInputs = [ + rust-bin.stable.latest.default + ]; + }; + }; + } + ); +} From b365cdaebcb645a27a36961ed7711f2cba223fe5 Mon Sep 17 00:00:00 2001 From: Antonio Yang Date: Thu, 1 Aug 2024 18:43:44 +0800 Subject: [PATCH 02/16] Add default key bind --- assets/default-keybind.toml | 28 +++++++ src/app.rs | 69 ++++++++++++------ src/event.rs | 32 ++++++++ src/keybind.rs | 142 ++++++++++++++++++++++++++++++++++++ src/lib.rs | 3 + src/view/detail.rs | 23 +++--- src/view/help.rs | 17 ++--- src/view/list.rs | 79 ++++++++++---------- src/view/refs.rs | 29 +++----- src/view/views.rs | 18 +++-- 10 files changed, 328 insertions(+), 112 deletions(-) create mode 100644 assets/default-keybind.toml create mode 100644 src/keybind.rs diff --git a/assets/default-keybind.toml b/assets/default-keybind.toml new file mode 100644 index 0000000..a471c16 --- /dev/null +++ b/assets/default-keybind.toml @@ -0,0 +1,28 @@ +Quit = ["q", "ctrl-c"] +NavigateUp = ["k", "up"] +NavigateDown = ["j", "down"] +NavigateRight = ["l", "right"] +NavigateLeft = ["h", "left"] + +# close widget or cancel progress but not quit app +CloseOrCancel = ["esc"] +HelpToggle = ["?"] +GoToTop = ["g"] +GoToBottom = ["shift-g"] +ScrollDown = ["ctrl-e"] +ScrollUp = ["ctrl-y"] +PageUp = ["ctrl-b"] +PageDown = ["ctrl-f"] +HalfPageUp = ["ctrl-u"] +HalfPageDown = ["ctrl-d"] +SelectTop = ["shift-h"] +SelectMiddle = ["shift-m"] +SelectBottom = ["shift-l"] +ShowDetails = ["enter"] +Search = ["/"] + +# copy part of information, ex: copy the short commit hash not all +ShortCopy = ["c"] +FullCopy = ["shift-c"] + +RefListToggle = ["tab"] diff --git a/src/app.rs b/src/app.rs index 11fbd1b..30ee857 100644 --- a/src/app.rs +++ b/src/app.rs @@ -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}, @@ -46,16 +46,20 @@ pub struct App<'a> { view: View<'a>, status_line: StatusLine, + keybind: &'a KeyBind, config: &'a Config, image_protocol: ImageProtocol, + insert_mode: bool, 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, @@ -94,8 +98,10 @@ impl<'a> App<'a> { repository, status_line: StatusLine::None, view, + keybind, config, image_protocol, + insert_mode: false, tx, } } @@ -111,31 +117,47 @@ impl App<'_> { 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 + AppEvent::Key(key) => { + match self.keybind.get(&key) { + Some(UserEvent::Quit) => { + self.tx.send(AppEvent::Quit); + } + Some(UserEvent::CloseOrCancel) => { + if self.insert_mode { + self.insert_mode = false; } - StatusLine::NotificationInfo(_) - | StatusLine::NotificationSuccess(_) - | StatusLine::NotificationWarn(_) => { - // Clear message and pass key input as is - self.clear_status_line(); + self.view.handle_user_event(&UserEvent::CloseOrCancel); + } + Some(ue) => { + 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; + } } - StatusLine::NotificationError(_) => { - // Clear message and cancel key input - self.clear_status_line(); - continue; + if self.insert_mode { + self.view.insert_key(key); + } else { + self.view.handle_user_event(ue); + } + } + None => { + if self.insert_mode { + self.view.insert_key(key); } } - - self.view.handle_key(key); } - }, + } AppEvent::Resize(w, h) => { let _ = (w, h); } @@ -187,6 +209,9 @@ impl App<'_> { AppEvent::NotifyError(msg) => { self.error_notification(msg); } + AppEvent::Insert => { + self.insert_mode = true; + } } } } diff --git a/src/event.rs b/src/event.rs index bb1a68b..0475524 100644 --- a/src/event.rs +++ b/src/event.rs @@ -5,6 +5,7 @@ use std::{ }; use ratatui::crossterm::event::KeyEvent; +use serde::Deserialize; pub enum AppEvent { Key(KeyEvent), @@ -25,6 +26,7 @@ pub enum AppEvent { NotifySuccess(String), NotifyWarn(String), NotifyError(String), + Insert, } #[derive(Clone)] @@ -79,3 +81,33 @@ pub fn init() -> (Sender, Receiver) { (tx, rx) } + +/// The event triggered by user's key input +#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq)] +pub enum UserEvent { + NavigateUp, + NavigateDown, + NavigateRight, + NavigateLeft, + Quit, + CloseOrCancel, + HelpToggle, + GoToTop, + GoToBottom, + GoToNext, + GoToPrevious, + ScrollUp, + ScrollDown, + PageUp, + PageDown, + HalfPageUp, + HalfPageDown, + SelectTop, + SelectMiddle, + SelectBottom, + ShowDetails, + Search, + ShortCopy, + FullCopy, + RefListToggle, +} diff --git a/src/keybind.rs b/src/keybind.rs new file mode 100644 index 0000000..22901f6 --- /dev/null +++ b/src/keybind.rs @@ -0,0 +1,142 @@ +use crate::event::UserEvent; +use serde::{de::Deserializer, Deserialize}; +use std::collections::HashMap; +use std::ops::{Deref, DerefMut}; + +use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; + +const DEFAULT_KEY_BIND: &str = include_str!("../assets/default-keybind.toml"); + +#[derive(Clone, Debug, Default)] +pub struct KeyBind(pub HashMap); + +impl Deref for KeyBind { + type Target = HashMap; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for KeyBind { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl KeyBind { + pub fn new() -> Result { + let keybind: KeyBind = + toml::from_str(DEFAULT_KEY_BIND).expect("default key bind should be correct"); + // TODO: let user patch key bind here + Ok(keybind) + } +} + +impl<'de> Deserialize<'de> for KeyBind { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let mut parsed_map = HashMap::>::deserialize(deserializer)?; + let mut key_map = HashMap::::new(); + for (user_event, key_events) in parsed_map.iter_mut() { + for key_event_str in key_events.iter_mut() { + let key_event = match parse_key_event(key_event_str) { + Ok(e) => e, + Err(s) => { + eprintln!("{s:?} is not a valid key event"); + panic!("key event incorrect!"); + } + }; + if let Some(conflict_user_event) = key_map.insert(key_event, user_event.clone()) { + eprintln!("{key_event:?} map to multiple events: {user_event:?}, {conflict_user_event:?}"); + panic!("key conflict!"); + } + } + } + + Ok(KeyBind(key_map)) + } +} + +fn parse_key_event(raw: &str) -> Result { + let raw_lower = raw.to_ascii_lowercase().replace(" ", ""); + let (remaining, modifiers) = extract_modifiers(&raw_lower); + parse_key_code_with_modifiers(remaining, modifiers) +} + +fn extract_modifiers(raw: &str) -> (&str, KeyModifiers) { + let mut modifiers = KeyModifiers::empty(); + let mut current = raw; + + loop { + match current { + rest if rest.starts_with("ctrl-") => { + modifiers.insert(KeyModifiers::CONTROL); + current = &rest[5..]; + } + rest if rest.starts_with("alt-") => { + modifiers.insert(KeyModifiers::ALT); + current = &rest[4..]; + } + rest if rest.starts_with("shift-") => { + modifiers.insert(KeyModifiers::SHIFT); + current = &rest[6..]; + } + _ => break, // break out of the loop if no known prefix is detected + }; + } + + (current, modifiers) +} + +fn parse_key_code_with_modifiers( + raw: &str, + mut modifiers: KeyModifiers, +) -> Result { + let c = match raw { + "esc" => KeyCode::Esc, + "enter" => KeyCode::Enter, + "left" => KeyCode::Left, + "right" => KeyCode::Right, + "up" => KeyCode::Up, + "down" => KeyCode::Down, + "home" => KeyCode::Home, + "end" => KeyCode::End, + "pageup" => KeyCode::PageUp, + "pagedown" => KeyCode::PageDown, + "backtab" => { + modifiers.insert(KeyModifiers::SHIFT); + KeyCode::BackTab + } + "backspace" => KeyCode::Backspace, + "delete" => KeyCode::Delete, + "insert" => KeyCode::Insert, + "f1" => KeyCode::F(1), + "f2" => KeyCode::F(2), + "f3" => KeyCode::F(3), + "f4" => KeyCode::F(4), + "f5" => KeyCode::F(5), + "f6" => KeyCode::F(6), + "f7" => KeyCode::F(7), + "f8" => KeyCode::F(8), + "f9" => KeyCode::F(9), + "f10" => KeyCode::F(10), + "f11" => KeyCode::F(11), + "f12" => KeyCode::F(12), + "space" => KeyCode::Char(' '), + "hyphen" => KeyCode::Char('-'), + "minus" => KeyCode::Char('-'), + "tab" => KeyCode::Tab, + c if c.len() == 1 => { + let mut c = c.chars().next().unwrap(); + if modifiers.contains(KeyModifiers::SHIFT) { + c = c.to_ascii_uppercase(); + } + KeyCode::Char(c) + } + _ => return Err(format!("Unable to parse {raw}")), + }; + Ok(KeyEvent::new(c, modifiers)) +} diff --git a/src/lib.rs b/src/lib.rs index 9dfcaed..bb0e697 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ mod app; mod config; mod event; mod external; +mod keybind; mod macros; mod view; mod widget; @@ -113,6 +114,7 @@ pub fn run() -> std::io::Result<()> { color_eyre::install().unwrap(); let args = Args::parse(); let config = config::Config::load(); + let key_bind = keybind::KeyBind::new().expect("default key bind should work"); let color_set = color::ColorSet::default(); let image_protocol = args.protocol.into(); @@ -133,6 +135,7 @@ pub fn run() -> std::io::Result<()> { &repository, &graph, &graph_image, + &key_bind, &config, &color_set, image_protocol, diff --git a/src/view/detail.rs b/src/view/detail.rs index 2614ede..667f7b4 100644 --- a/src/view/detail.rs +++ b/src/view/detail.rs @@ -1,5 +1,4 @@ use ratatui::{ - crossterm::event::{KeyCode, KeyEvent}, layout::{Constraint, Layout, Rect}, widgets::Clear, Frame, @@ -7,9 +6,8 @@ use ratatui::{ use crate::{ config::Config, - event::{AppEvent, Sender}, + event::{AppEvent, Sender, UserEvent}, git::{Commit, FileChange, Ref}, - key_code, key_code_char, protocol::ImageProtocol, widget::{ commit_detail::{CommitDetail, CommitDetailState}, @@ -55,27 +53,24 @@ impl<'a> DetailView<'a> { } } - pub fn handle_key(&mut self, key: KeyEvent) { - match key { - key_code_char!('q') => { - self.tx.send(AppEvent::Quit); - } - key_code_char!('j') | key_code!(KeyCode::Down) => { + pub fn handle_user_event(&mut self, event: &UserEvent) { + match event { + UserEvent::NavigateDown => { self.commit_detail_state.scroll_down(); } - key_code_char!('k') | key_code!(KeyCode::Up) => { + UserEvent::NavigateUp => { self.commit_detail_state.scroll_up(); } - key_code_char!('c') => { + UserEvent::ShortCopy => { self.copy_commit_short_hash(); } - key_code_char!('C') => { + UserEvent::FullCopy => { self.copy_commit_hash(); } - key_code_char!('?') => { + UserEvent::HelpToggle => { self.tx.send(AppEvent::OpenHelp); } - key_code!(KeyCode::Esc) | key_code!(KeyCode::Backspace) => { + UserEvent::CloseOrCancel => { self.tx.send(AppEvent::ClearDetail); // hack: reset the rendering of the image area self.tx.send(AppEvent::CloseDetail); } diff --git a/src/view/help.rs b/src/view/help.rs index 7005059..f636cd6 100644 --- a/src/view/help.rs +++ b/src/view/help.rs @@ -1,5 +1,4 @@ use ratatui::{ - crossterm::event::{KeyCode, KeyEvent}, layout::{Constraint, Layout, Rect}, style::{Color, Modifier, Stylize}, text::{Line, Span}, @@ -8,8 +7,7 @@ use ratatui::{ }; use crate::{ - event::{AppEvent, Sender}, - key_code, key_code_char, + event::{AppEvent, Sender, UserEvent}, protocol::ImageProtocol, view::View, }; @@ -45,19 +43,16 @@ impl HelpView<'_> { } } - pub fn handle_key(&mut self, key: KeyEvent) { - match key { - key_code_char!('q') => { - self.tx.send(AppEvent::Quit); - } - key_code_char!('?') | key_code!(KeyCode::Esc) | key_code!(KeyCode::Backspace) => { + pub fn handle_user_event(&mut self, event: &UserEvent) { + match event { + UserEvent::HelpToggle | UserEvent::CloseOrCancel => { self.tx.send(AppEvent::ClearHelp); // hack: reset the rendering of the image area self.tx.send(AppEvent::CloseHelp); } - key_code_char!('j') | key_code!(KeyCode::Down) => { + UserEvent::NavigateDown => { self.scroll_down(); } - key_code_char!('k') | key_code!(KeyCode::Up) => { + UserEvent::NavigateUp => { self.scroll_up(); } _ => {} diff --git a/src/view/list.rs b/src/view/list.rs index 2c5ca86..1041677 100644 --- a/src/view/list.rs +++ b/src/view/list.rs @@ -1,13 +1,8 @@ -use ratatui::{ - crossterm::event::{KeyCode, KeyEvent}, - layout::Rect, - Frame, -}; +use ratatui::{crossterm::event::KeyEvent, layout::Rect, Frame}; use crate::{ config::Config, - event::{AppEvent, Sender}, - key_code, key_code_char, + event::{AppEvent, Sender, UserEvent}, widget::commit_list::{CommitList, CommitListState, SearchState}, }; @@ -32,32 +27,34 @@ impl<'a> ListView<'a> { } } - pub fn handle_key(&mut self, key: KeyEvent) { + pub fn insert_key(&mut self, key: KeyEvent) { + self.as_mut_list_state().handle_search_input(key); + self.update_search_query(); + } + + pub fn handle_user_event(&mut self, event: &UserEvent) { if let SearchState::Searching { .. } = self.as_list_state().search_state() { - match key { - key_code!(KeyCode::Enter) => { + match event { + UserEvent::ShowDetails => { self.as_mut_list_state().apply_search(); self.update_matched_message(); } - key_code!(KeyCode::Esc) => { + UserEvent::CloseOrCancel => { self.as_mut_list_state().cancel_search(); self.clear_search_query(); } - _ => { - self.as_mut_list_state().handle_search_input(key); - self.update_search_query(); - } + _ => (), } return; } if let SearchState::Applied { .. } = self.as_list_state().search_state() { - match key { - key_code_char!('n') => { + match event { + UserEvent::GoToNext => { self.as_mut_list_state().select_next_match(); self.update_matched_message(); } - key_code_char!('N') => { + UserEvent::GoToPrevious => { self.as_mut_list_state().select_prev_match(); self.update_matched_message(); } @@ -66,70 +63,68 @@ impl<'a> ListView<'a> { // Do not return here } - match key { - key_code_char!('q') => { - self.tx.send(AppEvent::Quit); - } - key_code_char!('j') | key_code!(KeyCode::Down) => { + match event { + UserEvent::NavigateDown => { self.as_mut_list_state().select_next(); } - key_code_char!('k') | key_code!(KeyCode::Up) => { + UserEvent::NavigateUp => { self.as_mut_list_state().select_prev(); } - key_code_char!('g') => { + UserEvent::GoToTop => { self.as_mut_list_state().select_first(); } - key_code_char!('G') => { + UserEvent::GoToBottom => { self.as_mut_list_state().select_last(); } - key_code_char!('e', Ctrl) => { + UserEvent::ScrollDown => { self.as_mut_list_state().scroll_down(); } - key_code_char!('y', Ctrl) => { + UserEvent::ScrollUp => { self.as_mut_list_state().scroll_up(); } - key_code_char!('f', Ctrl) => { + UserEvent::PageDown => { self.as_mut_list_state().scroll_down_page(); } - key_code_char!('b', Ctrl) => { + UserEvent::PageUp => { self.as_mut_list_state().scroll_up_page(); } - key_code_char!('d', Ctrl) => { + UserEvent::HalfPageDown => { self.as_mut_list_state().scroll_down_half(); } - key_code_char!('u', Ctrl) => { + UserEvent::HalfPageUp => { self.as_mut_list_state().scroll_up_half(); } - key_code_char!('H') => { + UserEvent::SelectTop => { self.as_mut_list_state().select_high(); } - key_code_char!('M') => { + UserEvent::SelectMiddle => { self.as_mut_list_state().select_middle(); } - key_code_char!('L') => { + UserEvent::SelectBottom => { self.as_mut_list_state().select_low(); } - key_code_char!('c') => { + UserEvent::ShortCopy => { self.copy_commit_short_hash(); } - key_code_char!('C') => { + UserEvent::FullCopy => { self.copy_commit_hash(); } - key_code_char!('/') => { + UserEvent::Search => { self.as_mut_list_state().start_search(); + self.tx.send(AppEvent::Insert); self.update_search_query(); } - key_code_char!('?') => { + UserEvent::HelpToggle => { self.tx.send(AppEvent::OpenHelp); } - key_code!(KeyCode::Esc) => { + UserEvent::CloseOrCancel => { self.as_mut_list_state().cancel_search(); self.clear_search_query(); } - key_code!(KeyCode::Enter) => { + UserEvent::ShowDetails => { self.tx.send(AppEvent::OpenDetail); } - key_code!(KeyCode::Tab) => { + UserEvent::RefListToggle => { self.tx.send(AppEvent::OpenRefs); } _ => {} diff --git a/src/view/refs.rs b/src/view/refs.rs index 341a362..0257fd9 100644 --- a/src/view/refs.rs +++ b/src/view/refs.rs @@ -1,14 +1,12 @@ use ratatui::{ - crossterm::event::{KeyCode, KeyEvent}, layout::{Constraint, Layout, Rect}, Frame, }; use crate::{ config::Config, - event::{AppEvent, Sender}, + event::{AppEvent, Sender, UserEvent}, git::Ref, - key_code, key_code_char, widget::{ commit_list::{CommitList, CommitListState}, ref_list::{RefList, RefListState}, @@ -42,42 +40,39 @@ impl<'a> RefsView<'a> { } } - pub fn handle_key(&mut self, key: KeyEvent) { - match key { - key_code_char!('q') => { - self.tx.send(AppEvent::Quit); - } - key_code!(KeyCode::Esc) | key_code!(KeyCode::Backspace) | key_code!(KeyCode::Tab) => { + pub fn handle_user_event(&mut self, event: &UserEvent) { + match event { + UserEvent::CloseOrCancel | UserEvent::RefListToggle => { self.tx.send(AppEvent::CloseRefs); } - key_code_char!('j') | key_code!(KeyCode::Down) => { + UserEvent::NavigateDown => { self.ref_list_state.select_next(); self.update_commit_list_selected(); } - key_code_char!('k') | key_code!(KeyCode::Up) => { + UserEvent::NavigateUp => { self.ref_list_state.select_prev(); self.update_commit_list_selected(); } - key_code_char!('g') => { + UserEvent::GoToTop => { self.ref_list_state.select_first(); self.update_commit_list_selected(); } - key_code_char!('G') => { + UserEvent::GoToBottom => { self.ref_list_state.select_last(); self.update_commit_list_selected(); } - key_code_char!('l') | key_code!(KeyCode::Right) => { + UserEvent::NavigateRight => { self.ref_list_state.open_node(); self.update_commit_list_selected(); } - key_code_char!('h') | key_code!(KeyCode::Left) => { + UserEvent::NavigateLeft => { self.ref_list_state.close_node(); self.update_commit_list_selected(); } - key_code_char!('c') => { + UserEvent::ShortCopy | UserEvent::FullCopy => { self.copy_ref_name(); } - key_code_char!('?') => { + UserEvent::HelpToggle => { self.tx.send(AppEvent::OpenHelp); } _ => {} diff --git a/src/view/views.rs b/src/view/views.rs index ae7e8cf..046f149 100644 --- a/src/view/views.rs +++ b/src/view/views.rs @@ -2,7 +2,7 @@ use ratatui::{crossterm::event::KeyEvent, layout::Rect, Frame}; use crate::{ config::Config, - event::Sender, + event::{Sender, UserEvent}, git::{Commit, FileChange, Ref}, protocol::ImageProtocol, view::{detail::DetailView, help::HelpView, list::ListView, refs::RefsView}, @@ -20,13 +20,19 @@ pub enum View<'a> { } impl<'a> View<'a> { - pub fn handle_key(&mut self, key: KeyEvent) { + pub fn insert_key(&mut self, key: KeyEvent) { + if let View::List(view) = self { + view.insert_key(key) + } + } + + pub fn handle_user_event(&mut self, event: &UserEvent) { match self { View::Default => {} - View::List(view) => view.handle_key(key), - View::Detail(view) => view.handle_key(key), - View::Refs(view) => view.handle_key(key), - View::Help(view) => view.handle_key(key), + View::List(view) => view.handle_user_event(event), + View::Detail(view) => view.handle_user_event(event), + View::Refs(view) => view.handle_user_event(event), + View::Help(view) => view.handle_user_event(event), } } From 38c5be73e4e75cf03062f5196380fd89100366ae Mon Sep 17 00:00:00 2001 From: Antonio Yang Date: Sun, 4 Aug 2024 14:00:04 +0800 Subject: [PATCH 03/16] Show help from keybind --- Cargo.lock | 1 + Cargo.toml | 1 + assets/default-keybind.toml | 2 + src/app.rs | 7 +- src/event.rs | 29 ++++++- src/keybind.rs | 71 +++++++++++++++++ src/view/help.rs | 148 ++++++------------------------------ src/view/views.rs | 10 ++- 8 files changed, 141 insertions(+), 128 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eae4d7f..64497ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1786,6 +1786,7 @@ dependencies = [ "serde", "serde_json", "sha1", + "strum", "tempfile", "text-to-png", "toml", diff --git a/Cargo.toml b/Cargo.toml index 2f224e2..3e7f658 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/assets/default-keybind.toml b/assets/default-keybind.toml index a471c16..917593a 100644 --- a/assets/default-keybind.toml +++ b/assets/default-keybind.toml @@ -9,6 +9,8 @@ CloseOrCancel = ["esc"] HelpToggle = ["?"] GoToTop = ["g"] GoToBottom = ["shift-g"] +GoToNext = ["n"] +GoToPrevious = ["shift-N"] ScrollDown = ["ctrl-e"] ScrollUp = ["ctrl-y"] PageUp = ["ctrl-b"] diff --git a/src/app.rs b/src/app.rs index 30ee857..04234b7 100644 --- a/src/app.rs +++ b/src/app.rs @@ -311,7 +311,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) { diff --git a/src/event.rs b/src/event.rs index 0475524..821c248 100644 --- a/src/event.rs +++ b/src/event.rs @@ -6,6 +6,7 @@ use std::{ use ratatui::crossterm::event::KeyEvent; use serde::Deserialize; +use strum::{EnumIter, EnumMessage}; pub enum AppEvent { Key(KeyEvent), @@ -83,31 +84,57 @@ pub fn init() -> (Sender, Receiver) { } /// The event triggered by user's key input -#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq)] +#[derive(Clone, Debug, strum::Display, Deserialize, EnumIter, Eq, EnumMessage, Hash, PartialEq)] 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, + /// 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, + /// Show details ShowDetails, + /// Search Search, + /// Copy part of content ShortCopy, + /// Copy FullCopy, + /// Toggle for Reference List RefListToggle, } diff --git a/src/keybind.rs b/src/keybind.rs index 22901f6..1dc92aa 100644 --- a/src/keybind.rs +++ b/src/keybind.rs @@ -31,6 +31,14 @@ impl KeyBind { // TODO: let user patch key bind here Ok(keybind) } + + pub fn keys_for_event(&self, user_event: &UserEvent) -> Vec { + self.0 + .iter() + .filter(|(_, ue)| *ue == user_event) + .map(|(ke, _)| key_event_to_string(ke)) + .collect() + } } impl<'de> Deserialize<'de> for KeyBind { @@ -140,3 +148,66 @@ fn parse_key_code_with_modifiers( }; Ok(KeyEvent::new(c, modifiers)) } + +pub fn key_event_to_string(key_event: &KeyEvent) -> String { + let char; + let key_code = match key_event.code { + KeyCode::Backspace => "backspace", + KeyCode::Enter => "enter", + KeyCode::Left => "left", + KeyCode::Right => "right", + KeyCode::Up => "up", + KeyCode::Down => "down", + KeyCode::Home => "home", + KeyCode::End => "end", + KeyCode::PageUp => "pageup", + KeyCode::PageDown => "pagedown", + KeyCode::Tab => "tab", + KeyCode::BackTab => "backtab", + KeyCode::Delete => "delete", + KeyCode::Insert => "insert", + KeyCode::F(c) => { + char = format!("f({c})"); + &char + } + KeyCode::Char(' ') => "space", + KeyCode::Char(c) => { + char = c.to_string(); + &char + } + KeyCode::Esc => "esc", + KeyCode::Null => "", + KeyCode::CapsLock => "", + KeyCode::Menu => "", + KeyCode::ScrollLock => "", + KeyCode::Media(_) => "", + KeyCode::NumLock => "", + KeyCode::PrintScreen => "", + KeyCode::Pause => "", + KeyCode::KeypadBegin => "", + KeyCode::Modifier(_) => "", + }; + + let mut modifiers = Vec::with_capacity(3); + + if key_event.modifiers.intersects(KeyModifiers::CONTROL) { + modifiers.push("ctrl"); + } + + if key_event.modifiers.intersects(KeyModifiers::SHIFT) { + modifiers.push("shift"); + } + + if key_event.modifiers.intersects(KeyModifiers::ALT) { + modifiers.push("alt"); + } + + let mut key = modifiers.join("-"); + + if !key.is_empty() { + key.push('-'); + } + key.push_str(key_code); + + format!("<{key}>") +} diff --git a/src/view/help.rs b/src/view/help.rs index f636cd6..cfb1cad 100644 --- a/src/view/help.rs +++ b/src/view/help.rs @@ -8,10 +8,13 @@ use ratatui::{ use crate::{ event::{AppEvent, Sender, UserEvent}, + keybind::KeyBind, protocol::ImageProtocol, view::View, }; +use strum::{EnumMessage, IntoEnumIterator}; + const BLOCK_TITLE_COLOR: Color = Color::Green; const KEY_COLOR: Color = Color::Yellow; @@ -30,8 +33,13 @@ pub struct HelpView<'a> { } impl HelpView<'_> { - pub fn new(before: View, image_protocol: ImageProtocol, tx: Sender) -> HelpView { - let (help_key_lines, help_value_lines) = build_lines(); + pub fn new<'a>( + before: View<'a>, + image_protocol: ImageProtocol, + tx: Sender, + keybind: &'a KeyBind, + ) -> HelpView<'a> { + let (help_key_lines, help_value_lines) = build_lines(keybind); HelpView { before, help_key_lines, @@ -124,112 +132,28 @@ impl<'a> HelpView<'a> { } #[rustfmt::skip] -fn build_lines() -> (Vec>, Vec>) { - let (common_key_lines, common_value_lines) = build_block_lines( - "Common:", - &[ - (&["Ctrl-c", "q"], "Quit app"), - (&["?"], "Open help"), - ] - ); - let (help_key_lines, help_value_lines) = build_block_lines( - "Help:", - &[ - (&["Esc", "Backspace", "?"], "Close help"), - (&["Down", "j"], "Scroll down"), - (&["Up", "k"], "Scroll up"), - ] - ); - let (list_key_lines, list_value_lines) = build_block_lines( - "Commit List:", - &[ - (&["Down", "j"], "Move down"), - (&["Up", "k"], "Move up"), - (&["g"], "Go to top"), - (&["G"], "Go to bottom"), - (&["Ctrl-f"], "Scroll page down"), - (&["Ctrl-b"], "Scroll page up"), - (&["Ctrl-d"], "Scroll half page down"), - (&["Ctrl-u"], "Scroll half page up"), - (&["H"], "Select top of the screen"), - (&["M"], "Select middle of the screen"), - (&["L"], "Select bottom of the screen"), - (&["Enter"], "Show commit details"), - (&["Tab"], "Open refs list"), - (&["/"], "Start search"), - (&["Esc"], "Cancel search"), - (&["n"], "Go to next search match"), - (&["N"], "Go to previous search match"), - (&["c"], "Copy commit short hash"), - (&["C"], "Copy commit hash"), - ] - ); - let (detail_key_lines, detail_value_lines) = build_block_lines( - "Commit Detail:", - &[ - (&["Esc", "Backspace"], "Close commit details"), - (&["Down", "j"], "Scroll down"), - (&["Up", "k"], "Scroll up"), - (&["c"], "Copy commit short hash"), - (&["C"], "Copy commit hash"), - ] - ); - let (refs_key_lines, refs_value_lines) = build_block_lines( - "Refs List:", - &[ - (&["Esc", "Backspace", "Tab"], "Close refs list"), - (&["Down", "j"], "Move down"), - (&["Up", "k"], "Move up"), - (&["g"], "Go to top"), - (&["G"], "Go to bottom"), - (&["Right", "l"], "Open node"), - (&["Left", "h"], "Close node"), - (&["c"], "Copy ref name"), - ] - ); - - let key_lines = join_line_groups_with_empty(vec![ - common_key_lines, - help_key_lines, - list_key_lines, - detail_key_lines, - refs_key_lines, - ]); - let value_lines = join_line_groups_with_empty(vec![ - common_value_lines, - help_value_lines, - list_value_lines, - detail_value_lines, - refs_value_lines, - ]); - - (key_lines, value_lines) -} - -fn build_block_lines( - title: &'static str, - keybindings: &[(&[&'static str], &'static str)], -) -> (Vec>, Vec>) { +fn build_lines(keybind: &KeyBind) -> (Vec>, Vec>) { + let mut event_key_maps = Vec::new(); + for user_event in UserEvent::iter() { + let key_events: String = keybind.keys_for_event(&user_event).join(" "); + event_key_maps.push((user_event.get_documentation(), key_events)); + } let mut key_lines = Vec::new(); let mut value_lines = Vec::new(); - let key_title_lines = vec![Line::from(title) + let key_title_lines = vec![Line::from("Help") .fg(BLOCK_TITLE_COLOR) .add_modifier(Modifier::BOLD)]; let value_title_lines = vec![Line::from("")]; - let key_binding_lines: Vec = keybindings - .iter() - .map(|(keys, _)| { - join_span_groups_with_space( - keys.iter() - .map(|key| vec!["<".into(), key.fg(KEY_COLOR), ">".into()]) - .collect(), - ) + let key_binding_lines: Vec = event_key_maps.clone() + .into_iter() + .map(|(_, keys)| { + Line::from(Span::raw(keys)).fg(KEY_COLOR) }) .collect(); - let value_binding_lines: Vec = keybindings - .iter() - .map(|(_, value)| Line::from(*value)) + let value_binding_lines: Vec = event_key_maps + .into_iter() + .filter_map(|(user_event, _)| user_event.map(Line::from)) .collect(); key_lines.extend(key_title_lines); @@ -239,27 +163,3 @@ fn build_block_lines( (key_lines, value_lines) } - -fn join_line_groups_with_empty(line_groups: Vec>>) -> Vec> { - let mut result = Vec::new(); - let n = line_groups.len(); - for (i, lines) in line_groups.into_iter().enumerate() { - result.extend(lines); - if i < n - 1 { - result.push(Line::raw("")); - } - } - result -} - -fn join_span_groups_with_space(span_groups: Vec>>) -> Line<'static> { - let mut spans: Vec = Vec::new(); - let n = span_groups.len(); - for (i, ss) in span_groups.into_iter().enumerate() { - spans.extend(ss); - if i < n - 1 { - spans.push(Span::raw(" ")); - } - } - Line::from(spans) -} diff --git a/src/view/views.rs b/src/view/views.rs index 046f149..ee49220 100644 --- a/src/view/views.rs +++ b/src/view/views.rs @@ -4,6 +4,7 @@ use crate::{ config::Config, event::{Sender, UserEvent}, git::{Commit, FileChange, Ref}, + keybind::KeyBind, protocol::ImageProtocol, view::{detail::DetailView, help::HelpView, list::ListView, refs::RefsView}, widget::commit_list::CommitListState, @@ -79,7 +80,12 @@ impl<'a> View<'a> { View::Refs(Box::new(RefsView::new(commit_list_state, refs, config, tx))) } - pub fn of_help(before: View<'a>, image_protocol: ImageProtocol, tx: Sender) -> Self { - View::Help(Box::new(HelpView::new(before, image_protocol, tx))) + pub fn of_help( + before: View<'a>, + image_protocol: ImageProtocol, + tx: Sender, + keybind: &'a KeyBind, + ) -> Self { + View::Help(Box::new(HelpView::new(before, image_protocol, tx, keybind))) } } From feb499c09a55e70138b67ce8f14c25f741eb87d6 Mon Sep 17 00:00:00 2001 From: Antonio Yang Date: Sun, 4 Aug 2024 14:16:58 +0800 Subject: [PATCH 04/16] Add custom keybind config --- src/config.rs | 3 +++ src/keybind.rs | 20 +++++++++++++++++--- src/lib.rs | 5 +++-- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/config.rs b/src/config.rs index ffd28c3..c0ec1b1 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use serde::Deserialize; const APP_DIR_NAME: &str = "serie"; @@ -15,6 +17,7 @@ const DEFAULT_DETAIL_DATE_LOCAL: bool = true; pub struct Config { #[serde(default)] pub ui: UiConfig, + pub custom_keybind_path: Option, } #[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize)] diff --git a/src/keybind.rs b/src/keybind.rs index 1dc92aa..fdaf01f 100644 --- a/src/keybind.rs +++ b/src/keybind.rs @@ -1,7 +1,9 @@ use crate::event::UserEvent; use serde::{de::Deserializer, Deserialize}; use std::collections::HashMap; +use std::fs; use std::ops::{Deref, DerefMut}; +use std::path::PathBuf; use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; @@ -25,10 +27,22 @@ impl DerefMut for KeyBind { } impl KeyBind { - pub fn new() -> Result { - let keybind: KeyBind = + pub fn new(custom_keybind_path: Option) -> Result { + let mut keybind: KeyBind = toml::from_str(DEFAULT_KEY_BIND).expect("default key bind should be correct"); - // TODO: let user patch key bind here + + if let Some(custom_keybind_path) = custom_keybind_path { + let custom_keybind_content: String = + fs::read_to_string(custom_keybind_path).expect("custom keybind not found"); + let mut custom_keybind: KeyBind = + toml::from_str(&custom_keybind_content).expect("custom key bind should be correct"); + for (key_event, user_event) in custom_keybind.drain() { + if let Some(_old_user_event) = keybind.insert(key_event, user_event) { + // log!("{key_event}: {_old_user_event} -> {user_event}") + } + } + } + Ok(keybind) } diff --git a/src/lib.rs b/src/lib.rs index bb0e697..3d5c4bc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -113,8 +113,9 @@ fn auto_detect_best_protocol() -> protocol::ImageProtocol { pub fn run() -> std::io::Result<()> { color_eyre::install().unwrap(); let args = Args::parse(); - let config = config::Config::load(); - let key_bind = keybind::KeyBind::new().expect("default key bind should work"); + let mut config = config::Config::load(); + let key_bind = keybind::KeyBind::new(config.custom_keybind_path.take()) + .expect("default key bind should work"); let color_set = color::ColorSet::default(); let image_protocol = args.protocol.into(); From 62607310a1ec9dee6159a81a493b8417f845d600 Mon Sep 17 00:00:00 2001 From: Antonio Yang Date: Sun, 4 Aug 2024 17:56:02 +0800 Subject: [PATCH 05/16] Merge keybind config into `Config.toml` --- src/config.rs | 9 ++++++--- src/keybind.rs | 14 ++++---------- src/lib.rs | 2 +- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/config.rs b/src/config.rs index c0ec1b1..ceb6d18 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,7 +1,7 @@ -use std::path::PathBuf; - use serde::Deserialize; +use crate::keybind::KeyBind; + const APP_DIR_NAME: &str = "serie"; const CONFIG_FILE_NAME: &str = "config.toml"; @@ -17,7 +17,7 @@ const DEFAULT_DETAIL_DATE_LOCAL: bool = true; pub struct Config { #[serde(default)] pub ui: UiConfig, - pub custom_keybind_path: Option, + pub custom_keybind_patch: Option, } #[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize)] @@ -134,6 +134,7 @@ mod tests { date_local: true, }, }, + custom_keybind_patch: None, }; assert_eq!(actual, expected); } @@ -166,6 +167,7 @@ mod tests { date_local: false, }, }, + custom_keybind_patch: None, }; assert_eq!(actual, expected); } @@ -191,6 +193,7 @@ mod tests { date_local: true, }, }, + custom_keybind_patch: None, }; assert_eq!(actual, expected); } diff --git a/src/keybind.rs b/src/keybind.rs index fdaf01f..8aa8f43 100644 --- a/src/keybind.rs +++ b/src/keybind.rs @@ -1,15 +1,13 @@ use crate::event::UserEvent; use serde::{de::Deserializer, Deserialize}; use std::collections::HashMap; -use std::fs; use std::ops::{Deref, DerefMut}; -use std::path::PathBuf; use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; const DEFAULT_KEY_BIND: &str = include_str!("../assets/default-keybind.toml"); -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct KeyBind(pub HashMap); impl Deref for KeyBind { @@ -27,16 +25,12 @@ impl DerefMut for KeyBind { } impl KeyBind { - pub fn new(custom_keybind_path: Option) -> Result { + pub fn new(custom_keybind_patch: Option) -> Result { let mut keybind: KeyBind = toml::from_str(DEFAULT_KEY_BIND).expect("default key bind should be correct"); - if let Some(custom_keybind_path) = custom_keybind_path { - let custom_keybind_content: String = - fs::read_to_string(custom_keybind_path).expect("custom keybind not found"); - let mut custom_keybind: KeyBind = - toml::from_str(&custom_keybind_content).expect("custom key bind should be correct"); - for (key_event, user_event) in custom_keybind.drain() { + if let Some(mut custom_keybind_patch) = custom_keybind_patch { + for (key_event, user_event) in custom_keybind_patch.drain() { if let Some(_old_user_event) = keybind.insert(key_event, user_event) { // log!("{key_event}: {_old_user_event} -> {user_event}") } diff --git a/src/lib.rs b/src/lib.rs index 3d5c4bc..00b71d6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -114,7 +114,7 @@ pub fn run() -> std::io::Result<()> { color_eyre::install().unwrap(); let args = Args::parse(); let mut config = config::Config::load(); - let key_bind = keybind::KeyBind::new(config.custom_keybind_path.take()) + let key_bind = keybind::KeyBind::new(config.custom_keybind_patch.take()) .expect("default key bind should work"); let color_set = color::ColorSet::default(); From b1f239864f070ab7b7a3e5026b2f01ea1acabc04 Mon Sep 17 00:00:00 2001 From: Antonio Yang Date: Thu, 8 Aug 2024 14:13:34 +0800 Subject: [PATCH 06/16] Drop insert mode --- src/app.rs | 21 ++------------------- src/event.rs | 2 +- src/view/detail.rs | 3 ++- src/view/help.rs | 3 ++- src/view/list.rs | 8 +++++--- src/view/refs.rs | 3 ++- src/view/views.rs | 10 +++++----- 7 files changed, 19 insertions(+), 31 deletions(-) diff --git a/src/app.rs b/src/app.rs index 04234b7..736238d 100644 --- a/src/app.rs +++ b/src/app.rs @@ -49,7 +49,6 @@ pub struct App<'a> { keybind: &'a KeyBind, config: &'a Config, image_protocol: ImageProtocol, - insert_mode: bool, tx: Sender, } @@ -101,7 +100,6 @@ impl<'a> App<'a> { keybind, config, image_protocol, - insert_mode: false, tx, } } @@ -122,12 +120,6 @@ impl App<'_> { Some(UserEvent::Quit) => { self.tx.send(AppEvent::Quit); } - Some(UserEvent::CloseOrCancel) => { - if self.insert_mode { - self.insert_mode = false; - } - self.view.handle_user_event(&UserEvent::CloseOrCancel); - } Some(ue) => { match self.status_line { StatusLine::None | StatusLine::Input(_, _) => { @@ -145,16 +137,10 @@ impl App<'_> { continue; } } - if self.insert_mode { - self.view.insert_key(key); - } else { - self.view.handle_user_event(ue); - } + self.view.handle_event(ue, key); } None => { - if self.insert_mode { - self.view.insert_key(key); - } + self.view.handle_event(&UserEvent::Unknown, key); } } } @@ -209,9 +195,6 @@ impl App<'_> { AppEvent::NotifyError(msg) => { self.error_notification(msg); } - AppEvent::Insert => { - self.insert_mode = true; - } } } } diff --git a/src/event.rs b/src/event.rs index 821c248..ba5ca85 100644 --- a/src/event.rs +++ b/src/event.rs @@ -27,7 +27,6 @@ pub enum AppEvent { NotifySuccess(String), NotifyWarn(String), NotifyError(String), - Insert, } #[derive(Clone)] @@ -137,4 +136,5 @@ pub enum UserEvent { FullCopy, /// Toggle for Reference List RefListToggle, + Unknown, } diff --git a/src/view/detail.rs b/src/view/detail.rs index 667f7b4..a94d57a 100644 --- a/src/view/detail.rs +++ b/src/view/detail.rs @@ -1,4 +1,5 @@ use ratatui::{ + crossterm::event::KeyEvent, layout::{Constraint, Layout, Rect}, widgets::Clear, Frame, @@ -53,7 +54,7 @@ impl<'a> DetailView<'a> { } } - pub fn handle_user_event(&mut self, event: &UserEvent) { + pub fn handle_event(&mut self, event: &UserEvent, _: KeyEvent) { match event { UserEvent::NavigateDown => { self.commit_detail_state.scroll_down(); diff --git a/src/view/help.rs b/src/view/help.rs index cfb1cad..1a1d263 100644 --- a/src/view/help.rs +++ b/src/view/help.rs @@ -1,4 +1,5 @@ use ratatui::{ + crossterm::event::KeyEvent, layout::{Constraint, Layout, Rect}, style::{Color, Modifier, Stylize}, text::{Line, Span}, @@ -51,7 +52,7 @@ impl HelpView<'_> { } } - pub fn handle_user_event(&mut self, event: &UserEvent) { + pub fn handle_event(&mut self, event: &UserEvent, _: KeyEvent) { match event { UserEvent::HelpToggle | UserEvent::CloseOrCancel => { self.tx.send(AppEvent::ClearHelp); // hack: reset the rendering of the image area diff --git a/src/view/list.rs b/src/view/list.rs index 1041677..bcd0e34 100644 --- a/src/view/list.rs +++ b/src/view/list.rs @@ -32,7 +32,7 @@ impl<'a> ListView<'a> { self.update_search_query(); } - pub fn handle_user_event(&mut self, event: &UserEvent) { + pub fn handle_event(&mut self, event: &UserEvent, key: KeyEvent) { if let SearchState::Searching { .. } = self.as_list_state().search_state() { match event { UserEvent::ShowDetails => { @@ -43,7 +43,10 @@ impl<'a> ListView<'a> { self.as_mut_list_state().cancel_search(); self.clear_search_query(); } - _ => (), + _ => { + self.as_mut_list_state().handle_search_input(key); + self.update_search_query(); + } } return; } @@ -111,7 +114,6 @@ impl<'a> ListView<'a> { } UserEvent::Search => { self.as_mut_list_state().start_search(); - self.tx.send(AppEvent::Insert); self.update_search_query(); } UserEvent::HelpToggle => { diff --git a/src/view/refs.rs b/src/view/refs.rs index 0257fd9..e965649 100644 --- a/src/view/refs.rs +++ b/src/view/refs.rs @@ -1,4 +1,5 @@ use ratatui::{ + crossterm::event::KeyEvent, layout::{Constraint, Layout, Rect}, Frame, }; @@ -40,7 +41,7 @@ impl<'a> RefsView<'a> { } } - pub fn handle_user_event(&mut self, event: &UserEvent) { + pub fn handle_event(&mut self, event: &UserEvent, _: KeyEvent) { match event { UserEvent::CloseOrCancel | UserEvent::RefListToggle => { self.tx.send(AppEvent::CloseRefs); diff --git a/src/view/views.rs b/src/view/views.rs index ee49220..11b86f8 100644 --- a/src/view/views.rs +++ b/src/view/views.rs @@ -27,13 +27,13 @@ impl<'a> View<'a> { } } - pub fn handle_user_event(&mut self, event: &UserEvent) { + pub fn handle_event(&mut self, user_event: &UserEvent, key_event: KeyEvent) { match self { View::Default => {} - View::List(view) => view.handle_user_event(event), - View::Detail(view) => view.handle_user_event(event), - View::Refs(view) => view.handle_user_event(event), - View::Help(view) => view.handle_user_event(event), + View::List(view) => view.handle_event(user_event, key_event), + View::Detail(view) => view.handle_event(user_event, key_event), + View::Refs(view) => view.handle_event(user_event, key_event), + View::Help(view) => view.handle_event(user_event, key_event), } } From 04f216765018b8697bf80f7ff82e808e179671be Mon Sep 17 00:00:00 2001 From: Antonio Yang Date: Thu, 8 Aug 2024 14:31:01 +0800 Subject: [PATCH 07/16] Add UserEvent::ForceQuit --- assets/default-keybind.toml | 3 +- src/app.rs | 2 +- src/event.rs | 2 + src/view/help.rs | 3 + src/view/list.rs | 140 ++++++++++++++++++------------------ src/view/refs.rs | 3 + src/view/views.rs | 6 -- 7 files changed, 80 insertions(+), 79 deletions(-) diff --git a/assets/default-keybind.toml b/assets/default-keybind.toml index 917593a..b588a68 100644 --- a/assets/default-keybind.toml +++ b/assets/default-keybind.toml @@ -1,4 +1,5 @@ -Quit = ["q", "ctrl-c"] +ForceQuit = ["ctrl-c"] +Quit = ["q"] NavigateUp = ["k", "up"] NavigateDown = ["j", "down"] NavigateRight = ["l", "right"] diff --git a/src/app.rs b/src/app.rs index 736238d..106289a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -117,7 +117,7 @@ impl App<'_> { match rx.recv() { AppEvent::Key(key) => { match self.keybind.get(&key) { - Some(UserEvent::Quit) => { + Some(UserEvent::ForceQuit) => { self.tx.send(AppEvent::Quit); } Some(ue) => { diff --git a/src/event.rs b/src/event.rs index ba5ca85..7334884 100644 --- a/src/event.rs +++ b/src/event.rs @@ -94,6 +94,8 @@ pub enum UserEvent { NavigateRight, /// Navigate left NavigateLeft, + /// Force Quit serie without passing input into widges or views + ForceQuit, /// Quit serie Quit, /// Close widget or cancel current progress diff --git a/src/view/help.rs b/src/view/help.rs index 1a1d263..dad29fb 100644 --- a/src/view/help.rs +++ b/src/view/help.rs @@ -54,6 +54,9 @@ impl HelpView<'_> { pub fn handle_event(&mut self, event: &UserEvent, _: KeyEvent) { match event { + UserEvent::Quit => { + self.tx.send(AppEvent::Quit); + } UserEvent::HelpToggle | UserEvent::CloseOrCancel => { self.tx.send(AppEvent::ClearHelp); // hack: reset the rendering of the image area self.tx.send(AppEvent::CloseHelp); diff --git a/src/view/list.rs b/src/view/list.rs index bcd0e34..65dc84f 100644 --- a/src/view/list.rs +++ b/src/view/list.rs @@ -27,11 +27,6 @@ impl<'a> ListView<'a> { } } - pub fn insert_key(&mut self, key: KeyEvent) { - self.as_mut_list_state().handle_search_input(key); - self.update_search_query(); - } - pub fn handle_event(&mut self, event: &UserEvent, key: KeyEvent) { if let SearchState::Searching { .. } = self.as_list_state().search_state() { match event { @@ -49,6 +44,75 @@ impl<'a> ListView<'a> { } } return; + } else { + match event { + UserEvent::Quit => { + self.tx.send(AppEvent::Quit); + } + UserEvent::NavigateDown => { + self.as_mut_list_state().select_next(); + } + UserEvent::NavigateUp => { + self.as_mut_list_state().select_prev(); + } + UserEvent::GoToTop => { + self.as_mut_list_state().select_first(); + } + UserEvent::GoToBottom => { + self.as_mut_list_state().select_last(); + } + UserEvent::ScrollDown => { + self.as_mut_list_state().scroll_down(); + } + UserEvent::ScrollUp => { + self.as_mut_list_state().scroll_up(); + } + UserEvent::PageDown => { + self.as_mut_list_state().scroll_down_page(); + } + UserEvent::PageUp => { + self.as_mut_list_state().scroll_up_page(); + } + UserEvent::HalfPageDown => { + self.as_mut_list_state().scroll_down_half(); + } + UserEvent::HalfPageUp => { + self.as_mut_list_state().scroll_up_half(); + } + UserEvent::SelectTop => { + self.as_mut_list_state().select_high(); + } + UserEvent::SelectMiddle => { + self.as_mut_list_state().select_middle(); + } + UserEvent::SelectBottom => { + self.as_mut_list_state().select_low(); + } + UserEvent::ShortCopy => { + self.copy_commit_short_hash(); + } + UserEvent::FullCopy => { + self.copy_commit_hash(); + } + UserEvent::Search => { + self.as_mut_list_state().start_search(); + self.update_search_query(); + } + UserEvent::HelpToggle => { + self.tx.send(AppEvent::OpenHelp); + } + UserEvent::CloseOrCancel => { + self.as_mut_list_state().cancel_search(); + self.clear_search_query(); + } + UserEvent::ShowDetails => { + self.tx.send(AppEvent::OpenDetail); + } + UserEvent::RefListToggle => { + self.tx.send(AppEvent::OpenRefs); + } + _ => {} + } } if let SearchState::Applied { .. } = self.as_list_state().search_state() { @@ -65,72 +129,6 @@ impl<'a> ListView<'a> { } // Do not return here } - - match event { - UserEvent::NavigateDown => { - self.as_mut_list_state().select_next(); - } - UserEvent::NavigateUp => { - self.as_mut_list_state().select_prev(); - } - UserEvent::GoToTop => { - self.as_mut_list_state().select_first(); - } - UserEvent::GoToBottom => { - self.as_mut_list_state().select_last(); - } - UserEvent::ScrollDown => { - self.as_mut_list_state().scroll_down(); - } - UserEvent::ScrollUp => { - self.as_mut_list_state().scroll_up(); - } - UserEvent::PageDown => { - self.as_mut_list_state().scroll_down_page(); - } - UserEvent::PageUp => { - self.as_mut_list_state().scroll_up_page(); - } - UserEvent::HalfPageDown => { - self.as_mut_list_state().scroll_down_half(); - } - UserEvent::HalfPageUp => { - self.as_mut_list_state().scroll_up_half(); - } - UserEvent::SelectTop => { - self.as_mut_list_state().select_high(); - } - UserEvent::SelectMiddle => { - self.as_mut_list_state().select_middle(); - } - UserEvent::SelectBottom => { - self.as_mut_list_state().select_low(); - } - UserEvent::ShortCopy => { - self.copy_commit_short_hash(); - } - UserEvent::FullCopy => { - self.copy_commit_hash(); - } - UserEvent::Search => { - self.as_mut_list_state().start_search(); - self.update_search_query(); - } - UserEvent::HelpToggle => { - self.tx.send(AppEvent::OpenHelp); - } - UserEvent::CloseOrCancel => { - self.as_mut_list_state().cancel_search(); - self.clear_search_query(); - } - UserEvent::ShowDetails => { - self.tx.send(AppEvent::OpenDetail); - } - UserEvent::RefListToggle => { - self.tx.send(AppEvent::OpenRefs); - } - _ => {} - } } pub fn render(&mut self, f: &mut Frame, area: Rect) { diff --git a/src/view/refs.rs b/src/view/refs.rs index e965649..a0950c6 100644 --- a/src/view/refs.rs +++ b/src/view/refs.rs @@ -43,6 +43,9 @@ impl<'a> RefsView<'a> { pub fn handle_event(&mut self, event: &UserEvent, _: KeyEvent) { match event { + UserEvent::Quit => { + self.tx.send(AppEvent::Quit); + } UserEvent::CloseOrCancel | UserEvent::RefListToggle => { self.tx.send(AppEvent::CloseRefs); } diff --git a/src/view/views.rs b/src/view/views.rs index 11b86f8..27f20ab 100644 --- a/src/view/views.rs +++ b/src/view/views.rs @@ -21,12 +21,6 @@ pub enum View<'a> { } impl<'a> View<'a> { - pub fn insert_key(&mut self, key: KeyEvent) { - if let View::List(view) = self { - view.insert_key(key) - } - } - pub fn handle_event(&mut self, user_event: &UserEvent, key_event: KeyEvent) { match self { View::Default => {} From e8240e21b16a18deea990d892067c57347e757a5 Mon Sep 17 00:00:00 2001 From: Antonio Yang Date: Thu, 8 Aug 2024 14:41:24 +0800 Subject: [PATCH 08/16] Fix return type of Keybind::new() --- src/keybind.rs | 4 ++-- src/lib.rs | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/keybind.rs b/src/keybind.rs index 8aa8f43..358ccf3 100644 --- a/src/keybind.rs +++ b/src/keybind.rs @@ -25,7 +25,7 @@ impl DerefMut for KeyBind { } impl KeyBind { - pub fn new(custom_keybind_patch: Option) -> Result { + pub fn new(custom_keybind_patch: Option) -> Self { let mut keybind: KeyBind = toml::from_str(DEFAULT_KEY_BIND).expect("default key bind should be correct"); @@ -37,7 +37,7 @@ impl KeyBind { } } - Ok(keybind) + keybind } pub fn keys_for_event(&self, user_event: &UserEvent) -> Vec { diff --git a/src/lib.rs b/src/lib.rs index 00b71d6..5c02640 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -114,8 +114,7 @@ pub fn run() -> std::io::Result<()> { color_eyre::install().unwrap(); let args = Args::parse(); let mut config = config::Config::load(); - let key_bind = keybind::KeyBind::new(config.custom_keybind_patch.take()) - .expect("default key bind should work"); + let key_bind = keybind::KeyBind::new(config.custom_keybind_patch.take()); let color_set = color::ColorSet::default(); let image_protocol = args.protocol.into(); From 060f4c329cddc39460e3ee6cfc41fcf9d7d4fef0 Mon Sep 17 00:00:00 2001 From: Antonio Yang Date: Thu, 8 Aug 2024 14:43:33 +0800 Subject: [PATCH 09/16] Drop error print for mal keybind config --- src/keybind.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/keybind.rs b/src/keybind.rs index 358ccf3..2b0502b 100644 --- a/src/keybind.rs +++ b/src/keybind.rs @@ -61,13 +61,11 @@ impl<'de> Deserialize<'de> for KeyBind { let key_event = match parse_key_event(key_event_str) { Ok(e) => e, Err(s) => { - eprintln!("{s:?} is not a valid key event"); - panic!("key event incorrect!"); + panic!("{:?} is not a valid key event", s); } }; if let Some(conflict_user_event) = key_map.insert(key_event, user_event.clone()) { - eprintln!("{key_event:?} map to multiple events: {user_event:?}, {conflict_user_event:?}"); - panic!("key conflict!"); + panic!("{:?} map to multiple events: {:?}, {:?}", key_event, user_event, conflict_user_event); } } } From 0c94063b8608c5172ce6657ce842bd3a74985d7d Mon Sep 17 00:00:00 2001 From: Antonio Yang Date: Thu, 8 Aug 2024 14:46:06 +0800 Subject: [PATCH 10/16] Handle StatusLine regardless UserEvent --- src/app.rs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/app.rs b/src/app.rs index 106289a..7898272 100644 --- a/src/app.rs +++ b/src/app.rs @@ -113,6 +113,22 @@ impl App<'_> { ) -> std::io::Result<()> { loop { terminal.draw(|f| self.render(f))?; + 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; + } + } match rx.recv() { AppEvent::Key(key) => { @@ -121,22 +137,6 @@ impl App<'_> { self.tx.send(AppEvent::Quit); } Some(ue) => { - 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_event(ue, key); } None => { From 5d3430853615e6441809c6189cf202362870b35a Mon Sep 17 00:00:00 2001 From: Antonio Yang Date: Thu, 8 Aug 2024 14:50:49 +0800 Subject: [PATCH 11/16] Rename UserEvent::Confirm --- assets/default-keybind.toml | 2 +- src/app.rs | 22 ++++++++++------------ src/event.rs | 4 ++-- src/keybind.rs | 5 ++++- src/view/list.rs | 4 ++-- 5 files changed, 19 insertions(+), 18 deletions(-) diff --git a/assets/default-keybind.toml b/assets/default-keybind.toml index b588a68..eb5c302 100644 --- a/assets/default-keybind.toml +++ b/assets/default-keybind.toml @@ -21,7 +21,7 @@ HalfPageDown = ["ctrl-d"] SelectTop = ["shift-h"] SelectMiddle = ["shift-m"] SelectBottom = ["shift-l"] -ShowDetails = ["enter"] +Confirm = ["enter"] Search = ["/"] # copy part of information, ex: copy the short commit hash not all diff --git a/src/app.rs b/src/app.rs index 7898272..0b72ff9 100644 --- a/src/app.rs +++ b/src/app.rs @@ -131,19 +131,17 @@ impl App<'_> { } match rx.recv() { - AppEvent::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::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); } diff --git a/src/event.rs b/src/event.rs index 7334884..c046d03 100644 --- a/src/event.rs +++ b/src/event.rs @@ -128,8 +128,8 @@ pub enum UserEvent { SelectMiddle, /// Select bottom part SelectBottom, - /// Show details - ShowDetails, + /// Confirm + Confirm, /// Search Search, /// Copy part of content diff --git a/src/keybind.rs b/src/keybind.rs index 2b0502b..7da0126 100644 --- a/src/keybind.rs +++ b/src/keybind.rs @@ -65,7 +65,10 @@ impl<'de> Deserialize<'de> for KeyBind { } }; if let Some(conflict_user_event) = key_map.insert(key_event, user_event.clone()) { - panic!("{:?} map to multiple events: {:?}, {:?}", key_event, user_event, conflict_user_event); + panic!( + "{:?} map to multiple events: {:?}, {:?}", + key_event, user_event, conflict_user_event + ); } } } diff --git a/src/view/list.rs b/src/view/list.rs index 65dc84f..eaecd08 100644 --- a/src/view/list.rs +++ b/src/view/list.rs @@ -30,7 +30,7 @@ impl<'a> ListView<'a> { pub fn handle_event(&mut self, event: &UserEvent, key: KeyEvent) { if let SearchState::Searching { .. } = self.as_list_state().search_state() { match event { - UserEvent::ShowDetails => { + UserEvent::Confirm => { self.as_mut_list_state().apply_search(); self.update_matched_message(); } @@ -105,7 +105,7 @@ impl<'a> ListView<'a> { self.as_mut_list_state().cancel_search(); self.clear_search_query(); } - UserEvent::ShowDetails => { + UserEvent::Confirm => { self.tx.send(AppEvent::OpenDetail); } UserEvent::RefListToggle => { From c4eaca85ac4295f57810f0f9b41b0c1c4ca13008 Mon Sep 17 00:00:00 2001 From: Antonio Yang Date: Thu, 8 Aug 2024 14:52:57 +0800 Subject: [PATCH 12/16] Modify captal default keys --- assets/default-keybind.toml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/assets/default-keybind.toml b/assets/default-keybind.toml index eb5c302..d8c49ed 100644 --- a/assets/default-keybind.toml +++ b/assets/default-keybind.toml @@ -9,7 +9,7 @@ NavigateLeft = ["h", "left"] CloseOrCancel = ["esc"] HelpToggle = ["?"] GoToTop = ["g"] -GoToBottom = ["shift-g"] +GoToBottom = ["shift-G"] GoToNext = ["n"] GoToPrevious = ["shift-N"] ScrollDown = ["ctrl-e"] @@ -18,14 +18,14 @@ PageUp = ["ctrl-b"] PageDown = ["ctrl-f"] HalfPageUp = ["ctrl-u"] HalfPageDown = ["ctrl-d"] -SelectTop = ["shift-h"] -SelectMiddle = ["shift-m"] -SelectBottom = ["shift-l"] +SelectTop = ["shift-H"] +SelectMiddle = ["shift-M"] +SelectBottom = ["shift-L"] Confirm = ["enter"] Search = ["/"] # copy part of information, ex: copy the short commit hash not all ShortCopy = ["c"] -FullCopy = ["shift-c"] +FullCopy = ["shift-C"] RefListToggle = ["tab"] From 8bed56dd2b5f271bce18efb4a923cc26a4a6eca2 Mon Sep 17 00:00:00 2001 From: Antonio Yang Date: Fri, 9 Aug 2024 14:01:12 +0800 Subject: [PATCH 13/16] Let keybind in snake style --- assets/default-keybind.toml | 52 ++++++++++++++++++------------------- src/config.rs | 9 ++++--- src/event.rs | 1 + src/lib.rs | 2 +- 4 files changed, 33 insertions(+), 31 deletions(-) diff --git a/assets/default-keybind.toml b/assets/default-keybind.toml index d8c49ed..979c4cc 100644 --- a/assets/default-keybind.toml +++ b/assets/default-keybind.toml @@ -1,31 +1,31 @@ -ForceQuit = ["ctrl-c"] -Quit = ["q"] -NavigateUp = ["k", "up"] -NavigateDown = ["j", "down"] -NavigateRight = ["l", "right"] -NavigateLeft = ["h", "left"] +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 -CloseOrCancel = ["esc"] -HelpToggle = ["?"] -GoToTop = ["g"] -GoToBottom = ["shift-G"] -GoToNext = ["n"] -GoToPrevious = ["shift-N"] -ScrollDown = ["ctrl-e"] -ScrollUp = ["ctrl-y"] -PageUp = ["ctrl-b"] -PageDown = ["ctrl-f"] -HalfPageUp = ["ctrl-u"] -HalfPageDown = ["ctrl-d"] -SelectTop = ["shift-H"] -SelectMiddle = ["shift-M"] -SelectBottom = ["shift-L"] -Confirm = ["enter"] -Search = ["/"] +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 -ShortCopy = ["c"] -FullCopy = ["shift-C"] +short_copy = ["c"] +full_copy = ["shift-C"] -RefListToggle = ["tab"] +ref_list_toggle = ["tab"] diff --git a/src/config.rs b/src/config.rs index ceb6d18..abd5ab1 100644 --- a/src/config.rs +++ b/src/config.rs @@ -17,7 +17,8 @@ const DEFAULT_DETAIL_DATE_LOCAL: bool = true; pub struct Config { #[serde(default)] pub ui: UiConfig, - pub custom_keybind_patch: Option, + /// The user customed keybinds, please ref `assets/default-keybind.toml` + pub keybind: Option, } #[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize)] @@ -134,7 +135,7 @@ mod tests { date_local: true, }, }, - custom_keybind_patch: None, + keybind: None, }; assert_eq!(actual, expected); } @@ -167,7 +168,7 @@ mod tests { date_local: false, }, }, - custom_keybind_patch: None, + keybind: None, }; assert_eq!(actual, expected); } @@ -193,7 +194,7 @@ mod tests { date_local: true, }, }, - custom_keybind_patch: None, + keybind: None, }; assert_eq!(actual, expected); } diff --git a/src/event.rs b/src/event.rs index c046d03..00942a8 100644 --- a/src/event.rs +++ b/src/event.rs @@ -84,6 +84,7 @@ pub fn init() -> (Sender, Receiver) { /// 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 diff --git a/src/lib.rs b/src/lib.rs index 5c02640..47f1e65 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -114,7 +114,7 @@ pub fn run() -> std::io::Result<()> { color_eyre::install().unwrap(); let args = Args::parse(); let mut config = config::Config::load(); - let key_bind = keybind::KeyBind::new(config.custom_keybind_patch.take()); + let key_bind = keybind::KeyBind::new(config.keybind.take()); let color_set = color::ColorSet::default(); let image_protocol = args.protocol.into(); From 9add68fde0207caa7814546f87848edc9913e79a Mon Sep 17 00:00:00 2001 From: Antonio Yang Date: Fri, 9 Aug 2024 14:03:43 +0800 Subject: [PATCH 14/16] Fix error msg for unparsed keybind --- src/keybind.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/keybind.rs b/src/keybind.rs index 7da0126..c1b924d 100644 --- a/src/keybind.rs +++ b/src/keybind.rs @@ -61,7 +61,7 @@ impl<'de> Deserialize<'de> for KeyBind { let key_event = match parse_key_event(key_event_str) { Ok(e) => e, Err(s) => { - panic!("{:?} is not a valid key event", s); + panic!("{key_event_str:?} is not a valid key event: {s:}"); } }; if let Some(conflict_user_event) = key_map.insert(key_event, user_event.clone()) { From 845ab873ef7f3120bae3f8a6ae3e242520114c19 Mon Sep 17 00:00:00 2001 From: Antonio Yang Date: Fri, 9 Aug 2024 14:07:00 +0800 Subject: [PATCH 15/16] Modify StatusLine only after AppEvent::Key --- src/app.rs | 54 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/src/app.rs b/src/app.rs index 0b72ff9..5d34341 100644 --- a/src/app.rs +++ b/src/app.rs @@ -113,35 +113,37 @@ impl App<'_> { ) -> std::io::Result<()> { loop { terminal.draw(|f| self.render(f))?; - 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; - } - } - match rx.recv() { - AppEvent::Key(key) => match self.keybind.get(&key) { - Some(UserEvent::ForceQuit) => { - self.tx.send(AppEvent::Quit); - } - Some(ue) => { - self.view.handle_event(ue, key); + 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; + } } - None => { - self.view.handle_event(&UserEvent::Unknown, 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); } From 76c3ae28fffb52f60af581d66e6a66724c0ab158 Mon Sep 17 00:00:00 2001 From: Antonio Yang Date: Fri, 9 Aug 2024 14:07:25 +0800 Subject: [PATCH 16/16] Revert "Add flake" This reverts commit d146770db3456dffd69e37a0410aee0e1729ce96. --- flake.lock | 82 ------------------------------------------------------ flake.nix | 32 --------------------- 2 files changed, 114 deletions(-) delete mode 100644 flake.lock delete mode 100644 flake.nix diff --git a/flake.lock b/flake.lock deleted file mode 100644 index 1bc587c..0000000 --- a/flake.lock +++ /dev/null @@ -1,82 +0,0 @@ -{ - "nodes": { - "flake-utils": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1710146030, - "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "nixpkgs": { - "locked": { - "lastModified": 1722185531, - "narHash": "sha256-veKR07psFoJjINLC8RK4DiLniGGMgF3QMlS4tb74S6k=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "52ec9ac3b12395ad677e8b62106f0b98c1f8569d", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "flake-utils": "flake-utils", - "nixpkgs": "nixpkgs", - "rust-overlay": "rust-overlay" - } - }, - "rust-overlay": { - "inputs": { - "nixpkgs": [ - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1722391647, - "narHash": "sha256-JTi7l1oxnatF1uX/gnGMlRnyFMtylRw4MqhCUdoN2K4=", - "owner": "oxalica", - "repo": "rust-overlay", - "rev": "0fd4a5d2098faa516a9b83022aec7db766cd1de8", - "type": "github" - }, - "original": { - "owner": "oxalica", - "repo": "rust-overlay", - "type": "github" - } - }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/flake.nix b/flake.nix deleted file mode 100644 index f2e17dd..0000000 --- a/flake.nix +++ /dev/null @@ -1,32 +0,0 @@ -{ - inputs = { - nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; - rust-overlay = { - url = "github:oxalica/rust-overlay"; - inputs.nixpkgs.follows = "nixpkgs"; - inputs.flake-utils.follows = "flake-utils"; - }; - - flake-utils.url = "github:numtide/flake-utils"; - }; - - outputs = { self, rust-overlay, nixpkgs, flake-utils }: - flake-utils.lib.eachDefaultSystem (system: - let - overlays = [ (import rust-overlay) ]; - pkgs = import nixpkgs { - inherit system overlays; - }; - in - with pkgs; - { - devShells = rec { - default = mkShell { - buildInputs = [ - rust-bin.stable.latest.default - ]; - }; - }; - } - ); -}