use crate::app::component::actionhandler::Action;
use crate::app::ui::action::{AppAction, HelpAction, ListAction, TextEntryAction};
use crate::app::ui::browser::BrowserAction;
use crate::app::ui::browser::artistsearch::search_panel::BrowserArtistsAction;
use crate::app::ui::browser::artistsearch::songs_panel::BrowserArtistSongsAction;
use crate::app::ui::browser::playlistsearch::search_panel::BrowserPlaylistsAction;
use crate::app::ui::browser::playlistsearch::songs_panel::BrowserPlaylistSongsAction;
use crate::app::ui::browser::shared_components::{BrowserSearchAction, FilterAction, SortAction};
use crate::app::ui::browser::songsearch::BrowserSongsAction;
use crate::app::ui::logger::LoggerAction;
use crate::app::ui::playlist::PlaylistAction::{self, ViewBrowser};
use crate::keyaction::{KeyAction, KeyActionVisibility};
use crate::keybind::Keybind;
use anyhow::{Context, Error, Result};
use crossterm::event::KeyModifiers;
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
use std::collections::BTreeMap;
use std::collections::btree_map::Entry;
use std::convert::Infallible;
use std::str::FromStr;
pub type Keymap<A> = BTreeMap<Keybind, KeyActionTree<A>>;
fn merge_keymaps<A: Action>(this: &mut Keymap<A>, other: Keymap<A>) {
for (other_key, other_tree) in other {
let entry = this.entry(other_key);
match entry {
Entry::Vacant(e) => {
e.insert(other_tree);
}
Entry::Occupied(mut e) => {
let this_tree = e.get_mut();
this_tree.merge(other_tree);
}
}
}
}
fn remove_action_from_keymap<A: Action + PartialEq>(this: &mut Keymap<A>, action: &A) {
this.retain(|_, v| match v {
KeyActionTree::Key(ka) => &ka.action != action,
KeyActionTree::Mode { keys, .. } => {
remove_action_from_keymap(keys, action);
!keys.is_empty()
}
})
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum KeyStringTree {
#[serde(deserialize_with = "crate::core::string_or_struct")]
Key(KeyAction<String>),
Mode(BTreeMap<Keybind, KeyStringTree>),
}
#[derive(Clone, Debug, PartialEq)]
pub enum KeyActionTree<A> {
Key(KeyAction<A>),
Mode {
name: Option<String>,
keys: Keymap<A>,
},
}
#[derive(Debug, PartialEq)]
pub struct YoutuiKeymap {
pub global: BTreeMap<Keybind, KeyActionTree<AppAction>>,
pub playlist: BTreeMap<Keybind, KeyActionTree<AppAction>>,
pub browser: BTreeMap<Keybind, KeyActionTree<AppAction>>,
pub browser_artists: BTreeMap<Keybind, KeyActionTree<AppAction>>,
pub browser_playlists: BTreeMap<Keybind, KeyActionTree<AppAction>>,
pub browser_search: BTreeMap<Keybind, KeyActionTree<AppAction>>,
pub browser_songs: BTreeMap<Keybind, KeyActionTree<AppAction>>,
pub browser_artist_songs: BTreeMap<Keybind, KeyActionTree<AppAction>>,
pub browser_playlist_songs: BTreeMap<Keybind, KeyActionTree<AppAction>>,
pub help: BTreeMap<Keybind, KeyActionTree<AppAction>>,
pub sort: BTreeMap<Keybind, KeyActionTree<AppAction>>,
pub filter: BTreeMap<Keybind, KeyActionTree<AppAction>>,
pub text_entry: BTreeMap<Keybind, KeyActionTree<AppAction>>,
pub list: BTreeMap<Keybind, KeyActionTree<AppAction>>,
pub log: BTreeMap<Keybind, KeyActionTree<AppAction>>,
}
#[derive(Default, Debug, Serialize, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct YoutuiKeymapIR {
pub global: BTreeMap<Keybind, KeyStringTree>,
pub playlist: BTreeMap<Keybind, KeyStringTree>,
pub browser: BTreeMap<Keybind, KeyStringTree>,
pub browser_artists: BTreeMap<Keybind, KeyStringTree>,
pub browser_playlists: BTreeMap<Keybind, KeyStringTree>,
pub browser_search: BTreeMap<Keybind, KeyStringTree>,
pub browser_songs: BTreeMap<Keybind, KeyStringTree>,
pub browser_artist_songs: BTreeMap<Keybind, KeyStringTree>,
pub browser_playlist_songs: BTreeMap<Keybind, KeyStringTree>,
pub help: BTreeMap<Keybind, KeyStringTree>,
pub sort: BTreeMap<Keybind, KeyStringTree>,
pub filter: BTreeMap<Keybind, KeyStringTree>,
pub text_entry: BTreeMap<Keybind, KeyStringTree>,
pub list: BTreeMap<Keybind, KeyStringTree>,
pub log: BTreeMap<Keybind, KeyStringTree>,
}
#[derive(PartialEq, Debug, Serialize, Deserialize, Default)]
#[serde(default)]
pub struct YoutuiModeNamesIR {
global: BTreeMap<Keybind, ModeNameEnum>,
playlist: BTreeMap<Keybind, ModeNameEnum>,
browser: BTreeMap<Keybind, ModeNameEnum>,
browser_artists: BTreeMap<Keybind, ModeNameEnum>,
browser_playlists: BTreeMap<Keybind, ModeNameEnum>,
browser_search: BTreeMap<Keybind, ModeNameEnum>,
browser_songs: BTreeMap<Keybind, ModeNameEnum>,
browser_artist_songs: BTreeMap<Keybind, ModeNameEnum>,
browser_playlist_songs: BTreeMap<Keybind, ModeNameEnum>,
help: BTreeMap<Keybind, ModeNameEnum>,
sort: BTreeMap<Keybind, ModeNameEnum>,
filter: BTreeMap<Keybind, ModeNameEnum>,
text_entry: BTreeMap<Keybind, ModeNameEnum>,
list: BTreeMap<Keybind, ModeNameEnum>,
log: BTreeMap<Keybind, ModeNameEnum>,
}
impl Default for YoutuiKeymap {
fn default() -> Self {
Self {
global: default_global_keybinds(),
playlist: default_playlist_keybinds(),
browser: default_browser_keybinds(),
browser_artists: default_browser_artists_keybinds(),
browser_search: default_browser_search_keybinds(),
browser_songs: default_browser_songs_keybinds(),
browser_artist_songs: default_browser_artist_songs_keybinds(),
help: default_help_keybinds(),
sort: default_sort_keybinds(),
filter: default_filter_keybinds(),
text_entry: default_text_entry_keybinds(),
list: default_list_keybinds(),
log: default_log_keybinds(),
browser_playlists: default_browser_playlists_keybinds(),
browser_playlist_songs: default_browser_playlist_songs_keybinds(),
}
}
}
impl YoutuiKeymap {
pub fn try_from_stringy(keys: YoutuiKeymapIR, mode_names: YoutuiModeNamesIR) -> Result<Self> {
let YoutuiKeymapIR {
global,
playlist,
browser,
browser_artists,
browser_search,
browser_songs,
help,
sort,
filter,
text_entry,
list,
log,
browser_artist_songs,
browser_playlists,
browser_playlist_songs,
} = keys;
let YoutuiModeNamesIR {
global: mut global_mode_names,
playlist: mut playlist_mode_names,
browser: mut browser_mode_names,
browser_artists: mut browser_artists_mode_names,
browser_search: mut browser_search_mode_names,
browser_songs: mut browser_songs_mode_names,
help: mut help_mode_names,
sort: mut sort_mode_names,
filter: mut filter_mode_names,
text_entry: mut text_entry_mode_names,
list: mut list_mode_names,
log: mut log_mode_names,
browser_artist_songs: mut browser_artist_songs_mode_names,
browser_playlists: mut browser_playlists_mode_names,
browser_playlist_songs: mut browser_playlist_songs_mode_names,
} = mode_names;
let global = global
.into_iter()
.map(move |(k, v)| {
let v = KeyActionTree::try_from_stringy(&k, v, Some(&mut global_mode_names))?;
Ok((k, v))
})
.collect::<Result<BTreeMap<_, _>>>()
.context("Global keybinds parse failed")?;
let playlist = playlist
.into_iter()
.map(|(k, v)| {
let v = KeyActionTree::try_from_stringy(&k, v, Some(&mut playlist_mode_names))?;
Ok((k, v))
})
.collect::<Result<BTreeMap<_, _>>>()
.context("Playlist keybinds parse failed")?;
let browser = browser
.into_iter()
.map(|(k, v)| {
let v = KeyActionTree::try_from_stringy(&k, v, Some(&mut browser_mode_names))?;
Ok((k, v))
})
.collect::<Result<BTreeMap<_, _>>>()
.context("Browser keybinds parse failed")?;
let browser_artists = browser_artists
.into_iter()
.map(|(k, v)| {
let v =
KeyActionTree::try_from_stringy(&k, v, Some(&mut browser_artists_mode_names))?;
Ok((k, v))
})
.collect::<Result<BTreeMap<_, _>>>()
.context("Browser artists keybinds parse failed")?;
let browser_playlists = browser_playlists
.into_iter()
.map(|(k, v)| {
let v = KeyActionTree::try_from_stringy(
&k,
v,
Some(&mut browser_playlists_mode_names),
)?;
Ok((k, v))
})
.collect::<Result<BTreeMap<_, _>>>()
.context("Browser playlists keybinds parse failed")?;
let browser_search = browser_search
.into_iter()
.map(|(k, v)| {
let v =
KeyActionTree::try_from_stringy(&k, v, Some(&mut browser_search_mode_names))?;
Ok((k, v))
})
.collect::<Result<BTreeMap<_, _>>>()
.context("Browser search keybinds parse failed")?;
let browser_songs = browser_songs
.into_iter()
.map(|(k, v)| {
let v =
KeyActionTree::try_from_stringy(&k, v, Some(&mut browser_songs_mode_names))?;
Ok((k, v))
})
.collect::<Result<BTreeMap<_, _>>>()
.context("Browser songs keybinds parse failed")?;
let browser_artist_songs = browser_artist_songs
.into_iter()
.map(|(k, v)| {
let v = KeyActionTree::try_from_stringy(
&k,
v,
Some(&mut browser_artist_songs_mode_names),
)?;
Ok((k, v))
})
.collect::<Result<BTreeMap<_, _>>>()
.context("Browser artist songs keybinds parse failed")?;
let browser_playlist_songs = browser_playlist_songs
.into_iter()
.map(|(k, v)| {
let v = KeyActionTree::try_from_stringy(
&k,
v,
Some(&mut browser_playlist_songs_mode_names),
)?;
Ok((k, v))
})
.collect::<Result<BTreeMap<_, _>>>()
.context("Browser playlist songs keybinds parse failed")?;
let text_entry = text_entry
.into_iter()
.map(|(k, v)| {
let v = KeyActionTree::try_from_stringy(&k, v, Some(&mut text_entry_mode_names))?;
Ok((k, v))
})
.collect::<Result<BTreeMap<_, _>>>()
.context("Text entry keybinds parse failed")?;
let help = help
.into_iter()
.map(|(k, v)| {
let v = KeyActionTree::try_from_stringy(&k, v, Some(&mut help_mode_names))?;
Ok((k, v))
})
.collect::<Result<BTreeMap<_, _>>>()
.context("Help keybinds parse failed")?;
let sort = sort
.into_iter()
.map(|(k, v)| {
let v = KeyActionTree::try_from_stringy(&k, v, Some(&mut sort_mode_names))?;
Ok((k, v))
})
.collect::<Result<BTreeMap<_, _>>>()
.context("Sort keybinds parse failed")?;
let filter = filter
.into_iter()
.map(|(k, v)| {
let v = KeyActionTree::try_from_stringy(&k, v, Some(&mut filter_mode_names))?;
Ok((k, v))
})
.collect::<Result<BTreeMap<_, _>>>()
.context("Filter keybinds parse failed")?;
let list = list
.into_iter()
.map(|(k, v)| {
let v = KeyActionTree::try_from_stringy(&k, v, Some(&mut list_mode_names))?;
Ok((k, v))
})
.collect::<Result<BTreeMap<_, _>>>()
.context("List keybinds parse failed")?;
let log = log
.into_iter()
.map(|(k, v)| {
let v = KeyActionTree::try_from_stringy(&k, v, Some(&mut log_mode_names))?;
Ok((k, v))
})
.collect::<Result<BTreeMap<_, _>>>()
.context("Log keybinds parse failed")?;
let mut keymap = YoutuiKeymap::default();
merge_keymaps(&mut keymap.global, global);
merge_keymaps(&mut keymap.playlist, playlist);
merge_keymaps(&mut keymap.browser, browser);
merge_keymaps(&mut keymap.browser_artists, browser_artists);
merge_keymaps(&mut keymap.browser_playlists, browser_playlists);
merge_keymaps(&mut keymap.browser_search, browser_search);
merge_keymaps(&mut keymap.browser_songs, browser_songs);
merge_keymaps(&mut keymap.browser_artist_songs, browser_artist_songs);
merge_keymaps(&mut keymap.browser_playlist_songs, browser_playlist_songs);
merge_keymaps(&mut keymap.text_entry, text_entry);
merge_keymaps(&mut keymap.help, help);
merge_keymaps(&mut keymap.sort, sort);
merge_keymaps(&mut keymap.filter, filter);
merge_keymaps(&mut keymap.list, list);
merge_keymaps(&mut keymap.log, log);
remove_action_from_keymap(&mut keymap.global, &AppAction::NoOp);
remove_action_from_keymap(&mut keymap.playlist, &AppAction::NoOp);
remove_action_from_keymap(&mut keymap.browser, &AppAction::NoOp);
remove_action_from_keymap(&mut keymap.browser_artists, &AppAction::NoOp);
remove_action_from_keymap(&mut keymap.browser_playlists, &AppAction::NoOp);
remove_action_from_keymap(&mut keymap.browser_search, &AppAction::NoOp);
remove_action_from_keymap(&mut keymap.browser_songs, &AppAction::NoOp);
remove_action_from_keymap(&mut keymap.browser_artist_songs, &AppAction::NoOp);
remove_action_from_keymap(&mut keymap.browser_playlist_songs, &AppAction::NoOp);
remove_action_from_keymap(&mut keymap.text_entry, &AppAction::NoOp);
remove_action_from_keymap(&mut keymap.help, &AppAction::NoOp);
remove_action_from_keymap(&mut keymap.sort, &AppAction::NoOp);
remove_action_from_keymap(&mut keymap.filter, &AppAction::NoOp);
remove_action_from_keymap(&mut keymap.list, &AppAction::NoOp);
remove_action_from_keymap(&mut keymap.log, &AppAction::NoOp);
Ok(keymap)
}
#[cfg(test)]
pub fn try_from_stringy_exact(
keys: YoutuiKeymapIR,
mode_names: YoutuiModeNamesIR,
) -> Result<Self> {
let YoutuiKeymapIR {
global,
playlist,
browser,
browser_artists,
browser_search,
browser_songs,
help,
sort,
filter,
text_entry,
list,
log,
browser_artist_songs,
browser_playlists,
browser_playlist_songs,
} = keys;
let YoutuiModeNamesIR {
global: mut global_mode_names,
playlist: mut playlist_mode_names,
browser: mut browser_mode_names,
browser_artists: mut browser_artists_mode_names,
browser_search: mut browser_search_mode_names,
browser_songs: mut browser_songs_mode_names,
help: mut help_mode_names,
sort: mut sort_mode_names,
filter: mut filter_mode_names,
text_entry: mut text_entry_mode_names,
list: mut list_mode_names,
log: mut log_mode_names,
browser_artist_songs: mut browser_artist_songs_mode_names,
browser_playlists: mut browser_playlists_mode_names,
browser_playlist_songs: mut browser_playlist_songs_mode_names,
} = mode_names;
let global = global
.into_iter()
.map(move |(k, v)| {
let v = KeyActionTree::try_from_stringy(&k, v, Some(&mut global_mode_names))?;
Ok((k, v))
})
.collect::<Result<BTreeMap<_, _>>>()
.context("Global keybinds parse failed")?;
let playlist = playlist
.into_iter()
.map(|(k, v)| {
let v = KeyActionTree::try_from_stringy(&k, v, Some(&mut playlist_mode_names))?;
Ok((k, v))
})
.collect::<Result<BTreeMap<_, _>>>()
.context("Playlist keybinds parse failed")?;
let browser = browser
.into_iter()
.map(|(k, v)| {
let v = KeyActionTree::try_from_stringy(&k, v, Some(&mut browser_mode_names))?;
Ok((k, v))
})
.collect::<Result<BTreeMap<_, _>>>()
.context("Browser keybinds parse failed")?;
let browser_artists = browser_artists
.into_iter()
.map(|(k, v)| {
let v =
KeyActionTree::try_from_stringy(&k, v, Some(&mut browser_artists_mode_names))?;
Ok((k, v))
})
.collect::<Result<BTreeMap<_, _>>>()
.context("Browser artists keybinds parse failed")?;
let browser_playlists = browser_playlists
.into_iter()
.map(|(k, v)| {
let v = KeyActionTree::try_from_stringy(
&k,
v,
Some(&mut browser_playlists_mode_names),
)?;
Ok((k, v))
})
.collect::<Result<BTreeMap<_, _>>>()
.context("Browser playlists keybinds parse failed")?;
let browser_search = browser_search
.into_iter()
.map(|(k, v)| {
let v =
KeyActionTree::try_from_stringy(&k, v, Some(&mut browser_search_mode_names))?;
Ok((k, v))
})
.collect::<Result<BTreeMap<_, _>>>()
.context("Browser search keybinds parse failed")?;
let browser_songs = browser_songs
.into_iter()
.map(|(k, v)| {
let v =
KeyActionTree::try_from_stringy(&k, v, Some(&mut browser_songs_mode_names))?;
Ok((k, v))
})
.collect::<Result<BTreeMap<_, _>>>()
.context("Browser songs keybinds parse failed")?;
let browser_artist_songs = browser_artist_songs
.into_iter()
.map(|(k, v)| {
let v = KeyActionTree::try_from_stringy(
&k,
v,
Some(&mut browser_artist_songs_mode_names),
)?;
Ok((k, v))
})
.collect::<Result<BTreeMap<_, _>>>()
.context("Browser artist songs keybinds parse failed")?;
let browser_playlist_songs = browser_playlist_songs
.into_iter()
.map(|(k, v)| {
let v = KeyActionTree::try_from_stringy(
&k,
v,
Some(&mut browser_playlist_songs_mode_names),
)?;
Ok((k, v))
})
.collect::<Result<BTreeMap<_, _>>>()
.context("Browser playlist songs keybinds parse failed")?;
let text_entry = text_entry
.into_iter()
.map(|(k, v)| {
let v = KeyActionTree::try_from_stringy(&k, v, Some(&mut text_entry_mode_names))?;
Ok((k, v))
})
.collect::<Result<BTreeMap<_, _>>>()
.context("Text entry keybinds parse failed")?;
let help = help
.into_iter()
.map(|(k, v)| {
let v = KeyActionTree::try_from_stringy(&k, v, Some(&mut help_mode_names))?;
Ok((k, v))
})
.collect::<Result<BTreeMap<_, _>>>()
.context("Help keybinds parse failed")?;
let sort = sort
.into_iter()
.map(|(k, v)| {
let v = KeyActionTree::try_from_stringy(&k, v, Some(&mut sort_mode_names))?;
Ok((k, v))
})
.collect::<Result<BTreeMap<_, _>>>()
.context("Sort keybinds parse failed")?;
let filter = filter
.into_iter()
.map(|(k, v)| {
let v = KeyActionTree::try_from_stringy(&k, v, Some(&mut filter_mode_names))?;
Ok((k, v))
})
.collect::<Result<BTreeMap<_, _>>>()
.context("Filter keybinds parse failed")?;
let list = list
.into_iter()
.map(|(k, v)| {
let v = KeyActionTree::try_from_stringy(&k, v, Some(&mut list_mode_names))?;
Ok((k, v))
})
.collect::<Result<BTreeMap<_, _>>>()
.context("List keybinds parse failed")?;
let log = log
.into_iter()
.map(|(k, v)| {
let v = KeyActionTree::try_from_stringy(&k, v, Some(&mut log_mode_names))?;
Ok((k, v))
})
.collect::<Result<BTreeMap<_, _>>>()
.context("Log keybinds parse failed")?;
Ok(YoutuiKeymap {
global,
playlist,
browser,
browser_artists,
browser_search,
browser_songs,
browser_artist_songs,
help,
sort,
filter,
text_entry,
list,
log,
browser_playlists,
browser_playlist_songs,
})
}
}
impl<A: Action> KeyActionTree<A> {
pub fn new_key(action: A) -> Self {
Self::Key(KeyAction {
action,
visibility: Default::default(),
})
}
pub fn new_key_with_visibility(action: A, visibility: KeyActionVisibility) -> Self {
Self::Key(KeyAction { action, visibility })
}
pub fn new_mode<I>(binds: I, name: String) -> Self
where
I: IntoIterator<Item = (Keybind, KeyActionTree<A>)>,
{
Self::Mode {
keys: FromIterator::from_iter(binds),
name: Some(name),
}
}
fn merge(&mut self, other: KeyActionTree<A>) {
match self {
KeyActionTree::Key(_) => *self = other,
KeyActionTree::Mode {
name: this_name,
keys: keys_this,
} => match other {
KeyActionTree::Key(key_action) => *self = KeyActionTree::Key(key_action),
KeyActionTree::Mode {
name: other_name,
keys: keys_other,
} => {
if other_name.is_some() {
*this_name = other_name;
}
merge_keymaps(keys_this, keys_other);
}
},
}
}
fn try_from_stringy(
key: &Keybind,
stringy: KeyStringTree,
mode_names: Option<&mut BTreeMap<Keybind, ModeNameEnum>>,
) -> Result<Self>
where
A: TryFrom<String, Error = anyhow::Error>,
{
let new: KeyActionTree<A> = match stringy {
KeyStringTree::Key(k) => KeyActionTree::Key(k.try_map(TryInto::try_into)?),
KeyStringTree::Mode(m) => {
let mode_name_enum = mode_names.and_then(|m| m.remove(key));
let (mut next_modes, cur_mode_name) = match mode_name_enum {
Some(ModeNameEnum::Submode { name, keys }) => (Some(keys), name),
Some(ModeNameEnum::Name(name)) => (None, Some(name)),
None => (None, None),
};
KeyActionTree::Mode {
keys: m
.into_iter()
.map(|(k, a)| {
let v = KeyActionTree::try_from_stringy(&k, a, next_modes.as_mut())?;
Ok::<_, Error>((k, v))
})
.collect::<std::result::Result<_, _>>()?,
name: cur_mode_name,
}
}
};
Ok(new)
}
pub fn get_visibility(&self) -> KeyActionVisibility {
match self {
KeyActionTree::Key(k) => k.visibility,
KeyActionTree::Mode { .. } => KeyActionVisibility::default(),
}
}
pub fn get_context(&self) -> Cow<'_, str> {
match self {
KeyActionTree::Key(k) => k.action.context(),
KeyActionTree::Mode { keys, .. } => keys
.iter()
.next()
.map(|(_, kt)| kt.get_context())
.unwrap_or_default(),
}
}
}
impl<A> KeyAction<A> {
fn try_map<U, E>(
self,
f: impl FnOnce(A) -> std::result::Result<U, E>,
) -> std::result::Result<KeyAction<U>, E> {
let Self { action, visibility } = self;
Ok(KeyAction {
action: f(action)?,
visibility,
})
}
}
impl FromStr for KeyAction<String> {
type Err = Infallible;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
Ok(KeyAction {
action: s.to_string(),
visibility: Default::default(),
})
}
}
#[derive(PartialEq, Debug, Serialize, Deserialize)]
pub enum ModeNameEnum {
Submode {
name: Option<String>,
keys: BTreeMap<Keybind, ModeNameEnum>,
},
#[serde(untagged)]
Name(String),
}
fn default_global_keybinds() -> BTreeMap<Keybind, KeyActionTree<AppAction>> {
FromIterator::from_iter([
(
Keybind::new_unmodified(crossterm::event::KeyCode::Char('+')),
KeyActionTree::new_key(AppAction::VolUp),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Char('-')),
KeyActionTree::new_key(AppAction::VolDown),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Char('>')),
KeyActionTree::new_key(AppAction::NextSong),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Char('<')),
KeyActionTree::new_key(AppAction::PrevSong),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Char(']')),
KeyActionTree::new_key(AppAction::SeekForward),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Char('[')),
KeyActionTree::new_key(AppAction::SeekBack),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::F(1)),
KeyActionTree::new_key_with_visibility(
AppAction::ToggleHelp,
KeyActionVisibility::Global,
),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::F(10)),
KeyActionTree::new_key_with_visibility(AppAction::Quit, KeyActionVisibility::Global),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::F(12)),
KeyActionTree::new_key_with_visibility(
AppAction::ViewLogs,
KeyActionVisibility::Global,
),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Char(' ')),
KeyActionTree::new_key_with_visibility(
AppAction::PlayPause,
KeyActionVisibility::Global,
),
),
(
Keybind::new(crossterm::event::KeyCode::Char('c'), KeyModifiers::CONTROL),
KeyActionTree::new_key(AppAction::Quit),
),
])
}
fn default_playlist_keybinds() -> BTreeMap<Keybind, KeyActionTree<AppAction>> {
FromIterator::from_iter([
(
Keybind::new_unmodified(crossterm::event::KeyCode::F(5)),
KeyActionTree::new_key_with_visibility(
AppAction::Playlist(ViewBrowser),
KeyActionVisibility::Global,
),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Enter),
KeyActionTree::new_mode(
[
(
Keybind::new_unmodified(crossterm::event::KeyCode::Enter),
KeyActionTree::new_key(AppAction::Playlist(PlaylistAction::PlaySelected)),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Char('d')),
KeyActionTree::new_key(AppAction::Playlist(PlaylistAction::DeleteSelected)),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Char('D')),
KeyActionTree::new_key(AppAction::Playlist(PlaylistAction::DeleteAll)),
),
],
"Playlist Action".into(),
),
),
])
}
fn default_browser_keybinds() -> BTreeMap<Keybind, KeyActionTree<AppAction>> {
FromIterator::from_iter([
(
Keybind::new_unmodified(crossterm::event::KeyCode::F(5)),
KeyActionTree::new_key_with_visibility(
AppAction::Browser(BrowserAction::ViewPlaylist),
KeyActionVisibility::Global,
),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::F(2)),
KeyActionTree::new_key_with_visibility(
AppAction::Browser(BrowserAction::Search),
KeyActionVisibility::Global,
),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Left),
KeyActionTree::new_key(AppAction::Browser(BrowserAction::Left)),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::F(6)),
KeyActionTree::new_key_with_visibility(
AppAction::Browser(BrowserAction::ChangeSearchType),
KeyActionVisibility::Global,
),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Right),
KeyActionTree::new_key(AppAction::Browser(BrowserAction::Right)),
),
])
}
fn default_browser_artists_keybinds() -> BTreeMap<Keybind, KeyActionTree<AppAction>> {
FromIterator::from_iter([(
Keybind::new_unmodified(crossterm::event::KeyCode::Enter),
KeyActionTree::new_key(AppAction::BrowserArtists(
BrowserArtistsAction::DisplaySelectedArtistAlbums,
)),
)])
}
fn default_browser_playlists_keybinds() -> BTreeMap<Keybind, KeyActionTree<AppAction>> {
FromIterator::from_iter([(
Keybind::new_unmodified(crossterm::event::KeyCode::Enter),
KeyActionTree::new_key(AppAction::BrowserPlaylists(
BrowserPlaylistsAction::DisplaySelectedPlaylist,
)),
)])
}
fn default_browser_search_keybinds() -> BTreeMap<Keybind, KeyActionTree<AppAction>> {
FromIterator::from_iter([
(
Keybind::new_unmodified(crossterm::event::KeyCode::Down),
KeyActionTree::new_key(AppAction::BrowserSearch(
BrowserSearchAction::NextSearchSuggestion,
)),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Up),
KeyActionTree::new_key(AppAction::BrowserSearch(
BrowserSearchAction::PrevSearchSuggestion,
)),
),
])
}
fn default_browser_artist_songs_keybinds() -> BTreeMap<Keybind, KeyActionTree<AppAction>> {
FromIterator::from_iter([
(
Keybind::new_unmodified(crossterm::event::KeyCode::F(3)),
KeyActionTree::new_key_with_visibility(
AppAction::BrowserArtistSongs(BrowserArtistSongsAction::Filter),
KeyActionVisibility::Global,
),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::F(4)),
KeyActionTree::new_key_with_visibility(
AppAction::BrowserArtistSongs(BrowserArtistSongsAction::Sort),
KeyActionVisibility::Global,
),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Enter),
KeyActionTree::new_mode(
[
(
Keybind::new_unmodified(crossterm::event::KeyCode::Char(' ')),
KeyActionTree::new_key(AppAction::BrowserArtistSongs(
BrowserArtistSongsAction::AddSongToPlaylist,
)),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Char('p')),
KeyActionTree::new_key(AppAction::BrowserArtistSongs(
BrowserArtistSongsAction::PlaySongs,
)),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Char('a')),
KeyActionTree::new_key(AppAction::BrowserArtistSongs(
BrowserArtistSongsAction::PlayAlbum,
)),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Enter),
KeyActionTree::new_key(AppAction::BrowserArtistSongs(
BrowserArtistSongsAction::PlaySong,
)),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Char('P')),
KeyActionTree::new_key(AppAction::BrowserArtistSongs(
BrowserArtistSongsAction::AddSongsToPlaylist,
)),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Char('A')),
KeyActionTree::new_key(AppAction::BrowserArtistSongs(
BrowserArtistSongsAction::AddAlbumToPlaylist,
)),
),
],
"Play".into(),
),
),
])
}
fn default_browser_playlist_songs_keybinds() -> BTreeMap<Keybind, KeyActionTree<AppAction>> {
FromIterator::from_iter([
(
Keybind::new_unmodified(crossterm::event::KeyCode::F(3)),
KeyActionTree::new_key_with_visibility(
AppAction::BrowserPlaylistSongs(BrowserPlaylistSongsAction::Filter),
KeyActionVisibility::Global,
),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::F(4)),
KeyActionTree::new_key_with_visibility(
AppAction::BrowserPlaylistSongs(BrowserPlaylistSongsAction::Sort),
KeyActionVisibility::Global,
),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Enter),
KeyActionTree::new_mode(
[
(
Keybind::new_unmodified(crossterm::event::KeyCode::Char(' ')),
KeyActionTree::new_key(AppAction::BrowserPlaylistSongs(
BrowserPlaylistSongsAction::AddSongToPlaylist,
)),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Char('p')),
KeyActionTree::new_key(AppAction::BrowserPlaylistSongs(
BrowserPlaylistSongsAction::PlaySongs,
)),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Enter),
KeyActionTree::new_key(AppAction::BrowserPlaylistSongs(
BrowserPlaylistSongsAction::PlaySong,
)),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Char('P')),
KeyActionTree::new_key(AppAction::BrowserPlaylistSongs(
BrowserPlaylistSongsAction::AddSongsToPlaylist,
)),
),
],
"Play".into(),
),
),
])
}
fn default_browser_songs_keybinds() -> BTreeMap<Keybind, KeyActionTree<AppAction>> {
FromIterator::from_iter([
(
Keybind::new_unmodified(crossterm::event::KeyCode::F(3)),
KeyActionTree::new_key_with_visibility(
AppAction::BrowserSongs(BrowserSongsAction::Filter),
KeyActionVisibility::Global,
),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::F(4)),
KeyActionTree::new_key_with_visibility(
AppAction::BrowserSongs(BrowserSongsAction::Sort),
KeyActionVisibility::Global,
),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Enter),
KeyActionTree::new_mode(
[
(
Keybind::new_unmodified(crossterm::event::KeyCode::Char(' ')),
KeyActionTree::new_key(AppAction::BrowserSongs(
BrowserSongsAction::AddSongToPlaylist,
)),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Char('p')),
KeyActionTree::new_key(AppAction::BrowserSongs(
BrowserSongsAction::PlaySongs,
)),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Enter),
KeyActionTree::new_key(AppAction::BrowserSongs(
BrowserSongsAction::PlaySong,
)),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Char('P')),
KeyActionTree::new_key(AppAction::BrowserSongs(
BrowserSongsAction::AddSongsToPlaylist,
)),
),
],
"Play".into(),
),
),
])
}
fn default_help_keybinds() -> BTreeMap<Keybind, KeyActionTree<AppAction>> {
FromIterator::from_iter([
(
Keybind::new_unmodified(crossterm::event::KeyCode::Esc),
KeyActionTree::new_key_with_visibility(
AppAction::Help(HelpAction::Close),
KeyActionVisibility::Hidden,
),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::F(1)),
KeyActionTree::new_key_with_visibility(
AppAction::Help(HelpAction::Close),
KeyActionVisibility::Global,
),
),
])
}
fn default_sort_keybinds() -> BTreeMap<Keybind, KeyActionTree<AppAction>> {
FromIterator::from_iter([
(
Keybind::new_unmodified(crossterm::event::KeyCode::Enter),
KeyActionTree::new_key_with_visibility(
AppAction::Sort(SortAction::SortSelectedAsc),
KeyActionVisibility::Global,
),
),
(
Keybind::new(crossterm::event::KeyCode::Enter, KeyModifiers::ALT),
KeyActionTree::new_key_with_visibility(
AppAction::Sort(SortAction::SortSelectedDesc),
KeyActionVisibility::Global,
),
),
(
Keybind::new(crossterm::event::KeyCode::F(4), KeyModifiers::ALT),
KeyActionTree::new_key_with_visibility(
AppAction::Sort(SortAction::ClearSort),
KeyActionVisibility::Global,
),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Esc),
KeyActionTree::new_key_with_visibility(
AppAction::Sort(SortAction::Close),
KeyActionVisibility::Hidden,
),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::F(4)),
KeyActionTree::new_key_with_visibility(
AppAction::Sort(SortAction::Close),
KeyActionVisibility::Global,
),
),
])
}
fn default_filter_keybinds() -> BTreeMap<Keybind, KeyActionTree<AppAction>> {
FromIterator::from_iter([
(
Keybind::new_unmodified(crossterm::event::KeyCode::Esc),
KeyActionTree::new_key_with_visibility(
AppAction::Filter(FilterAction::Close),
KeyActionVisibility::Hidden,
),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::F(3)),
KeyActionTree::new_key_with_visibility(
AppAction::Filter(FilterAction::Close),
KeyActionVisibility::Global,
),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Enter),
KeyActionTree::new_key_with_visibility(
AppAction::Filter(FilterAction::Apply),
KeyActionVisibility::Global,
),
),
(
Keybind::new(crossterm::event::KeyCode::F(3), KeyModifiers::ALT),
KeyActionTree::new_key_with_visibility(
AppAction::Filter(FilterAction::ClearFilter),
KeyActionVisibility::Global,
),
),
])
}
fn default_text_entry_keybinds() -> BTreeMap<Keybind, KeyActionTree<AppAction>> {
FromIterator::from_iter([
(
Keybind::new_unmodified(crossterm::event::KeyCode::Enter),
KeyActionTree::new_key_with_visibility(
AppAction::TextEntry(TextEntryAction::Submit),
KeyActionVisibility::Hidden,
),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Left),
KeyActionTree::new_key_with_visibility(
AppAction::TextEntry(TextEntryAction::Left),
KeyActionVisibility::Hidden,
),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Right),
KeyActionTree::new_key_with_visibility(
AppAction::TextEntry(TextEntryAction::Right),
KeyActionVisibility::Hidden,
),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Backspace),
KeyActionTree::new_key_with_visibility(
AppAction::TextEntry(TextEntryAction::Backspace),
KeyActionVisibility::Hidden,
),
),
])
}
fn default_log_keybinds() -> BTreeMap<Keybind, KeyActionTree<AppAction>> {
FromIterator::from_iter([
(
Keybind::new_unmodified(crossterm::event::KeyCode::F(5)),
KeyActionTree::new_key_with_visibility(
AppAction::Log(LoggerAction::ViewBrowser),
KeyActionVisibility::Global,
),
),
(
Keybind::new(crossterm::event::KeyCode::Left, KeyModifiers::SHIFT),
KeyActionTree::new_key(AppAction::Log(LoggerAction::ReduceCaptured)),
),
(
Keybind::new(crossterm::event::KeyCode::Right, KeyModifiers::SHIFT),
KeyActionTree::new_key(AppAction::Log(LoggerAction::IncreaseCaptured)),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Left),
KeyActionTree::new_key(AppAction::Log(LoggerAction::ReduceShown)),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Right),
KeyActionTree::new_key(AppAction::Log(LoggerAction::IncreaseShown)),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Up),
KeyActionTree::new_key(AppAction::Log(LoggerAction::Up)),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Down),
KeyActionTree::new_key(AppAction::Log(LoggerAction::Down)),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::PageUp),
KeyActionTree::new_key(AppAction::Log(LoggerAction::PageUp)),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::PageDown),
KeyActionTree::new_key(AppAction::Log(LoggerAction::PageDown)),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Char('t')),
KeyActionTree::new_key(AppAction::Log(LoggerAction::ToggleHideFiltered)),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Esc),
KeyActionTree::new_key(AppAction::Log(LoggerAction::ExitPageMode)),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Char('f')),
KeyActionTree::new_key(AppAction::Log(LoggerAction::ToggleTargetFocus)),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Char('h')),
KeyActionTree::new_key(AppAction::Log(LoggerAction::ToggleTargetSelector)),
),
])
}
fn default_list_keybinds() -> BTreeMap<Keybind, KeyActionTree<AppAction>> {
FromIterator::from_iter([
(
Keybind::new_unmodified(crossterm::event::KeyCode::Up),
KeyActionTree::new_key_with_visibility(
AppAction::List(ListAction::Up),
KeyActionVisibility::Hidden,
),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Down),
KeyActionTree::new_key_with_visibility(
AppAction::List(ListAction::Down),
KeyActionVisibility::Hidden,
),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::PageUp),
KeyActionTree::new_key(AppAction::List(ListAction::PageUp)),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::PageDown),
KeyActionTree::new_key(AppAction::List(ListAction::PageDown)),
),
])
}
#[cfg(test)]
mod tests {
use super::{KeyActionTree, merge_keymaps};
use crate::app::ui::action::AppAction;
use crate::app::ui::browser::artistsearch::search_panel::BrowserArtistsAction;
use crate::app::ui::browser::artistsearch::songs_panel::BrowserArtistSongsAction;
use crate::config::keymap::{Keymap, remove_action_from_keymap};
use crate::keybind::Keybind;
#[test]
fn test_add_key() {
let mut keys = Keymap::from_iter([(
Keybind::new_unmodified(crossterm::event::KeyCode::Enter),
KeyActionTree::new_key(AppAction::BrowserArtists(
BrowserArtistsAction::DisplaySelectedArtistAlbums,
)),
)]);
let to_add = FromIterator::from_iter([(
Keybind::new_unmodified(crossterm::event::KeyCode::Up),
KeyActionTree::new_key(AppAction::Quit),
)]);
merge_keymaps(&mut keys, to_add);
let expected = FromIterator::from_iter([
(
Keybind::new_unmodified(crossterm::event::KeyCode::Enter),
KeyActionTree::new_key(AppAction::BrowserArtists(
BrowserArtistsAction::DisplaySelectedArtistAlbums,
)),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Up),
KeyActionTree::new_key(AppAction::Quit),
),
]);
pretty_assertions::assert_eq!(keys, expected);
}
#[test]
fn test_add_key_overrides_old() {
let mut keys = Keymap::from_iter([(
Keybind::new_unmodified(crossterm::event::KeyCode::Enter),
KeyActionTree::new_key(AppAction::Quit),
)]);
let to_add = FromIterator::from_iter([(
Keybind::new_unmodified(crossterm::event::KeyCode::Enter),
KeyActionTree::new_key(AppAction::NoOp),
)]);
merge_keymaps(&mut keys, to_add);
let expected = FromIterator::from_iter([(
Keybind::new_unmodified(crossterm::event::KeyCode::Enter),
KeyActionTree::new_key(AppAction::NoOp),
)]);
pretty_assertions::assert_eq!(keys, expected);
}
#[test]
fn test_add_mode() {
let mut keys = Keymap::from_iter([(
Keybind::new_unmodified(crossterm::event::KeyCode::Enter),
KeyActionTree::new_key(AppAction::BrowserArtists(
BrowserArtistsAction::DisplaySelectedArtistAlbums,
)),
)]);
let to_add = FromIterator::from_iter([(
Keybind::new_unmodified(crossterm::event::KeyCode::Up),
KeyActionTree::new_mode(
[(
Keybind::new_unmodified(crossterm::event::KeyCode::Up),
KeyActionTree::new_key(AppAction::Quit),
)],
"New Modename".into(),
),
)]);
merge_keymaps(&mut keys, to_add);
let expected = Keymap::from_iter([
(
Keybind::new_unmodified(crossterm::event::KeyCode::Enter),
KeyActionTree::new_key(AppAction::BrowserArtists(
BrowserArtistsAction::DisplaySelectedArtistAlbums,
)),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Up),
KeyActionTree::new_mode(
[(
Keybind::new_unmodified(crossterm::event::KeyCode::Up),
KeyActionTree::new_key(AppAction::Quit),
)],
"New Modename".into(),
),
),
]);
pretty_assertions::assert_eq!(keys, expected);
}
#[test]
fn test_add_key_to_mode() {
let mut keys = Keymap::from_iter([(
Keybind::new_unmodified(crossterm::event::KeyCode::Enter),
KeyActionTree::new_mode(
[(
Keybind::new_unmodified(crossterm::event::KeyCode::Char(' ')),
KeyActionTree::new_key(AppAction::BrowserArtistSongs(
BrowserArtistSongsAction::AddSongToPlaylist,
)),
)],
"Play".into(),
),
)]);
let to_add = FromIterator::from_iter([(
Keybind::new_unmodified(crossterm::event::KeyCode::Enter),
KeyActionTree::new_mode(
[(
Keybind::new_unmodified(crossterm::event::KeyCode::Up),
KeyActionTree::new_key(AppAction::Quit),
)],
"New Modename".into(),
),
)]);
merge_keymaps(&mut keys, to_add);
let expected = Keymap::from_iter([(
Keybind::new_unmodified(crossterm::event::KeyCode::Enter),
KeyActionTree::new_mode(
[
(
Keybind::new_unmodified(crossterm::event::KeyCode::Char(' ')),
KeyActionTree::new_key(AppAction::BrowserArtistSongs(
BrowserArtistSongsAction::AddSongToPlaylist,
)),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Up),
KeyActionTree::new_key(AppAction::Quit),
),
],
"New Modename".into(),
),
)]);
pretty_assertions::assert_eq!(keys, expected);
}
#[test]
fn test_remove_action() {
let mut keys = Keymap::from_iter([
(
Keybind::new_unmodified(crossterm::event::KeyCode::Enter),
KeyActionTree::new_key(AppAction::BrowserArtists(
BrowserArtistsAction::DisplaySelectedArtistAlbums,
)),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Up),
KeyActionTree::new_key(AppAction::Quit),
),
]);
remove_action_from_keymap(&mut keys, &AppAction::Quit);
let expected = FromIterator::from_iter([(
Keybind::new_unmodified(crossterm::event::KeyCode::Enter),
KeyActionTree::new_key(AppAction::BrowserArtists(
BrowserArtistsAction::DisplaySelectedArtistAlbums,
)),
)]);
pretty_assertions::assert_eq!(keys, expected);
}
#[test]
fn test_remove_action_from_mode() {
let mut keys = Keymap::from_iter([(
Keybind::new_unmodified(crossterm::event::KeyCode::Up),
KeyActionTree::new_mode(
[
(
Keybind::new_unmodified(crossterm::event::KeyCode::Up),
KeyActionTree::new_key(AppAction::Quit),
),
(
Keybind::new_unmodified(crossterm::event::KeyCode::Enter),
KeyActionTree::new_key(AppAction::BrowserArtists(
BrowserArtistsAction::DisplaySelectedArtistAlbums,
)),
),
],
"New Modename".into(),
),
)]);
remove_action_from_keymap(&mut keys, &AppAction::Quit);
let expected = Keymap::from_iter([(
Keybind::new_unmodified(crossterm::event::KeyCode::Up),
KeyActionTree::new_mode(
[(
Keybind::new_unmodified(crossterm::event::KeyCode::Enter),
KeyActionTree::new_key(AppAction::BrowserArtists(
BrowserArtistsAction::DisplaySelectedArtistAlbums,
)),
)],
"New Modename".into(),
),
)]);
pretty_assertions::assert_eq!(keys, expected);
}
#[test]
fn test_remove_action_removes_mode() {
let mut keys = Keymap::from_iter([(
Keybind::new_unmodified(crossterm::event::KeyCode::Up),
KeyActionTree::new_mode(
[(
Keybind::new_unmodified(crossterm::event::KeyCode::Up),
KeyActionTree::new_key(AppAction::Quit),
)],
"New Modename".into(),
),
)]);
remove_action_from_keymap(&mut keys, &AppAction::Quit);
let expected = Keymap::from_iter([]);
pretty_assertions::assert_eq!(keys, expected);
}
}