feat: wire evil-keys dispatcher into event loop and action handlers
Replace 3 hardcoded keys with full trie-based dispatch. Dispatcher created in main.rs, handle_action() implements all 18 AppAction variants including GotoFirst/GotoLast per focus pane, NextTab/PrevTab cycling, and HalfPage scroll. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/claude-agent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
Generated
+147
-5
@@ -151,6 +151,21 @@ version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
|
||||
dependencies = [
|
||||
"bit-vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-vec"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.11.1"
|
||||
@@ -329,6 +344,15 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "evil-keys"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"crossterm",
|
||||
"indexmap 2.14.0",
|
||||
"proptest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "eyre"
|
||||
version = "0.6.12"
|
||||
@@ -413,6 +437,18 @@ dependencies = [
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi 5.3.0",
|
||||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.4.2"
|
||||
@@ -421,7 +457,7 @@ checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"r-efi 6.0.0",
|
||||
"wasip2",
|
||||
"wasip3",
|
||||
]
|
||||
@@ -780,6 +816,15 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.37.3"
|
||||
@@ -900,6 +945,25 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proptest"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744"
|
||||
dependencies = [
|
||||
"bit-set",
|
||||
"bit-vec",
|
||||
"bitflags",
|
||||
"num-traits",
|
||||
"rand 0.9.4",
|
||||
"rand_chacha 0.9.0",
|
||||
"rand_xorshift",
|
||||
"regex-syntax",
|
||||
"rusty-fork",
|
||||
"tempfile",
|
||||
"unarray",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prost"
|
||||
version = "0.13.5"
|
||||
@@ -952,6 +1016,12 @@ dependencies = [
|
||||
"prost",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "1.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.45"
|
||||
@@ -961,6 +1031,12 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "6.0.0"
|
||||
@@ -974,8 +1050,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
"rand_chacha 0.3.1",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
|
||||
dependencies = [
|
||||
"rand_chacha 0.9.0",
|
||||
"rand_core 0.9.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -985,7 +1071,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core 0.9.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -997,6 +1093,24 @@ dependencies = [
|
||||
"getrandom 0.2.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
|
||||
dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_xorshift"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a"
|
||||
dependencies = [
|
||||
"rand_core 0.9.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ratatui"
|
||||
version = "0.29.0"
|
||||
@@ -1094,6 +1208,18 @@ version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "rusty-fork"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"quick-error",
|
||||
"tempfile",
|
||||
"wait-timeout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.23"
|
||||
@@ -1424,7 +1550,7 @@ dependencies = [
|
||||
"indexmap 1.9.3",
|
||||
"pin-project",
|
||||
"pin-project-lite",
|
||||
"rand",
|
||||
"rand 0.8.6",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
@@ -1524,6 +1650,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"color-eyre",
|
||||
"crossterm",
|
||||
"evil-keys",
|
||||
"insta",
|
||||
"nix",
|
||||
"prost",
|
||||
@@ -1535,6 +1662,12 @@ dependencies = [
|
||||
"tonic-build",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unarray"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
@@ -1588,6 +1721,15 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||
|
||||
[[package]]
|
||||
name = "wait-timeout"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "want"
|
||||
version = "0.3.1"
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
[workspace]
|
||||
members = ["crates/evil-keys"]
|
||||
|
||||
[package]
|
||||
name = "ui-agregator"
|
||||
version = "0.1.0"
|
||||
@@ -10,6 +13,7 @@ color-eyre = "0.6"
|
||||
nix = { version = "0.29", features = ["fs"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_yaml = "0.9"
|
||||
evil-keys = { path = "crates/evil-keys" }
|
||||
|
||||
tonic = "0.12"
|
||||
prost = "0.13"
|
||||
|
||||
@@ -2,16 +2,100 @@
|
||||
|
||||
use crossterm::event::MouseButton;
|
||||
use ratatui::widgets::ListState;
|
||||
use tokio::sync::mpsc::Sender;
|
||||
|
||||
use crate::application::app_state::App;
|
||||
use crate::data::{Artist, Track};
|
||||
use crate::domain::conversions::{convert_album, convert_artist, convert_track};
|
||||
use crate::domain::navigation::Tab;
|
||||
use crate::grpc::GrpcResponse;
|
||||
use crate::domain::navigation::{ModalKind, Tab};
|
||||
use crate::grpc::{GrpcRequest, GrpcResponse};
|
||||
use crate::input::AppAction;
|
||||
use crate::ui::library::{LibraryFocus, LibraryState};
|
||||
use crate::ui::notifications::NotifKind;
|
||||
|
||||
impl App {
|
||||
pub fn handle_action(&mut self, action: AppAction, count: usize, tx: &Sender<GrpcRequest>) {
|
||||
match action {
|
||||
AppAction::MoveDown => {
|
||||
for _ in 0..count {
|
||||
self.library.move_down();
|
||||
}
|
||||
}
|
||||
AppAction::MoveUp => {
|
||||
for _ in 0..count {
|
||||
self.library.move_up();
|
||||
}
|
||||
}
|
||||
AppAction::FocusLeft => self.library.focus_left(),
|
||||
AppAction::FocusRight => self.library.focus_right(),
|
||||
AppAction::CycleFocus => self.library.cycle_focus(),
|
||||
AppAction::GotoFirst => match self.library.focus {
|
||||
LibraryFocus::Artists => self.library.artist_state.select(Some(0)),
|
||||
LibraryFocus::Albums => self.library.album_state.select(Some(0)),
|
||||
LibraryFocus::Tracks => self.library.track_state.select(Some(0)),
|
||||
},
|
||||
AppAction::GotoLast => match self.library.focus {
|
||||
LibraryFocus::Artists => {
|
||||
let last = self.library.artists.len().saturating_sub(1);
|
||||
self.library.artist_state.select(Some(last));
|
||||
}
|
||||
LibraryFocus::Albums => {
|
||||
if let Some(artist) = self.library.selected_artist() {
|
||||
let last = artist.albums.len().saturating_sub(1);
|
||||
self.library.album_state.select(Some(last));
|
||||
}
|
||||
}
|
||||
LibraryFocus::Tracks => {
|
||||
let last = self.library.tracks.len().saturating_sub(1);
|
||||
self.library.track_state.select(Some(last));
|
||||
}
|
||||
},
|
||||
AppAction::HalfPageDown => {
|
||||
for _ in 0..15 {
|
||||
self.library.move_down();
|
||||
}
|
||||
}
|
||||
AppAction::HalfPageUp => {
|
||||
for _ in 0..15 {
|
||||
self.library.move_up();
|
||||
}
|
||||
}
|
||||
AppAction::NextTab => {
|
||||
let idx = self.tab.index();
|
||||
let next = (idx + 1) % Tab::ALL.len();
|
||||
self.tab = Tab::ALL[next];
|
||||
}
|
||||
AppAction::PrevTab => {
|
||||
let idx = self.tab.index();
|
||||
let prev = if idx == 0 {
|
||||
Tab::ALL.len() - 1
|
||||
} else {
|
||||
idx - 1
|
||||
};
|
||||
self.tab = Tab::ALL[prev];
|
||||
}
|
||||
AppAction::GotoTab(i) => {
|
||||
if let Some(tab) = Tab::ALL.get(i) {
|
||||
self.tab = *tab;
|
||||
}
|
||||
}
|
||||
AppAction::Quit => self.running = false,
|
||||
AppAction::Refresh => {
|
||||
self.library.clear_cache();
|
||||
let _ = tx.try_send(GrpcRequest::GetArtists);
|
||||
}
|
||||
AppAction::ShowHelp => self.modal = Some(ModalKind::Help),
|
||||
AppAction::ToggleNotifications => {
|
||||
self.notifications_open = !self.notifications_open;
|
||||
if self.notifications_open {
|
||||
self.notifications_scroll = 0;
|
||||
self.notifications_expanded = None;
|
||||
}
|
||||
}
|
||||
AppAction::Escape => self.handle_escape(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_escape(&mut self) {
|
||||
if self.notifications_open {
|
||||
self.notifications_open = false;
|
||||
@@ -138,8 +222,10 @@ impl App {
|
||||
let albums = self.library.albums_inner_area;
|
||||
let tracks = self.library.tracks_inner_area;
|
||||
|
||||
if x >= artists.x && x < artists.x + artists.width
|
||||
&& y >= artists.y && y < artists.y + artists.height
|
||||
if x >= artists.x
|
||||
&& x < artists.x + artists.width
|
||||
&& y >= artists.y
|
||||
&& y < artists.y + artists.height
|
||||
{
|
||||
let row = (y - artists.y) as usize;
|
||||
if row < self.library.artists.len() {
|
||||
@@ -148,8 +234,10 @@ impl App {
|
||||
self.library.track_state.select(Some(0));
|
||||
self.library.focus = LibraryFocus::Artists;
|
||||
}
|
||||
} else if x >= albums.x && x < albums.x + albums.width
|
||||
&& y >= albums.y && y < albums.y + albums.height
|
||||
} else if x >= albums.x
|
||||
&& x < albums.x + albums.width
|
||||
&& y >= albums.y
|
||||
&& y < albums.y + albums.height
|
||||
{
|
||||
let row = (y - albums.y) as usize;
|
||||
if let Some(artist) = self.library.selected_artist()
|
||||
@@ -159,8 +247,10 @@ impl App {
|
||||
self.library.track_state.select(Some(0));
|
||||
self.library.focus = LibraryFocus::Albums;
|
||||
}
|
||||
} else if x >= tracks.x && x < tracks.x + tracks.width
|
||||
&& y >= tracks.y && y < tracks.y + tracks.height
|
||||
} else if x >= tracks.x
|
||||
&& x < tracks.x + tracks.width
|
||||
&& y >= tracks.y
|
||||
&& y < tracks.y + tracks.height
|
||||
{
|
||||
let row = (y - tracks.y) as usize;
|
||||
if row < self.library.tracks.len() {
|
||||
|
||||
@@ -5,6 +5,7 @@ pub mod data;
|
||||
pub mod domain;
|
||||
pub mod grpc;
|
||||
pub mod infrastructure;
|
||||
pub mod input;
|
||||
pub mod presentation;
|
||||
pub mod proto;
|
||||
pub mod theme;
|
||||
|
||||
+27
-9
@@ -11,11 +11,13 @@ use crossterm::{
|
||||
},
|
||||
terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
|
||||
};
|
||||
use evil_keys::{DispatchResult, Dispatcher};
|
||||
use ratatui::prelude::*;
|
||||
|
||||
use ui_agregator::app::App;
|
||||
use ui_agregator::config::Config;
|
||||
use ui_agregator::grpc::{GrpcRequest, spawn_grpc_worker};
|
||||
use ui_agregator::input::{build_insert_keymap, build_normal_keymap};
|
||||
|
||||
const TICK_RATE: Duration = Duration::from_millis(100);
|
||||
|
||||
@@ -59,6 +61,16 @@ async fn run() -> Result<()> {
|
||||
let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?;
|
||||
let mut app = App::new();
|
||||
|
||||
let mut dispatcher = Dispatcher::new();
|
||||
dispatcher
|
||||
.add_mode("normal", build_normal_keymap())
|
||||
.unwrap();
|
||||
dispatcher
|
||||
.add_mode("insert", build_insert_keymap())
|
||||
.unwrap();
|
||||
dispatcher.set_active("normal").unwrap();
|
||||
dispatcher.set_timeout(Duration::from_millis(1000));
|
||||
|
||||
let (grpc_tx, mut grpc_rx) = spawn_grpc_worker(config.grpc_addr());
|
||||
|
||||
if grpc_tx.try_send(GrpcRequest::GetArtists).is_err() {
|
||||
@@ -78,18 +90,23 @@ async fn run() -> Result<()> {
|
||||
|
||||
if event::poll(TICK_RATE)? {
|
||||
match event::read()? {
|
||||
Event::Key(key) if key.kind == KeyEventKind::Press => {
|
||||
if key.modifiers.contains(KeyModifiers::CONTROL)
|
||||
Event::Key(key) => {
|
||||
if key.kind == KeyEventKind::Press
|
||||
&& key.modifiers.contains(KeyModifiers::CONTROL)
|
||||
&& key.code == KeyCode::Char('c')
|
||||
{
|
||||
app.running = false;
|
||||
} else if key.code == KeyCode::Esc {
|
||||
app.handle_escape();
|
||||
} else if key.code == KeyCode::Char('r')
|
||||
&& key.modifiers.contains(KeyModifiers::CONTROL)
|
||||
{
|
||||
app.library.clear_cache();
|
||||
let _ = grpc_tx.try_send(GrpcRequest::GetArtists);
|
||||
} else {
|
||||
match dispatcher.dispatch(key) {
|
||||
DispatchResult::Matched { action, count } => {
|
||||
app.handle_action(action, count, &grpc_tx);
|
||||
}
|
||||
DispatchResult::Pending
|
||||
| DispatchResult::Cancelled
|
||||
| DispatchResult::CountAccumulated
|
||||
| DispatchResult::Ignored
|
||||
| DispatchResult::NotFound => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::Mouse(mouse) => match mouse.kind {
|
||||
@@ -107,6 +124,7 @@ async fn run() -> Result<()> {
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
dispatcher.check_timeout();
|
||||
app.handle_tick();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user