use {
crate::*,
crokey::*,
schemars::{
JsonSchema,
Schema,
SchemaGenerator,
json_schema,
},
serde::Deserialize,
std::{
borrow::Cow,
collections::{
HashMap,
hash_map,
},
fmt,
},
};
#[derive(Clone, Deserialize)]
pub struct KeyBindings {
#[serde(flatten)]
map: HashMap<KeyCombination, Action>,
}
impl Default for KeyBindings {
fn default() -> Self {
let mut bindings = Self {
map: HashMap::default(),
};
bindings.set(key!('?'), Action::Help);
bindings.set(key!(h), Action::Help);
bindings.set(key!(ctrl - c), Action::Quit);
bindings.set(key!(ctrl - q), Action::Quit);
bindings.set(key!(alt - i), Action::DismissTopItem);
bindings.set(key!(alt - t), Action::DismissTop);
bindings.set(key!(alt - u), Action::OpenUndismissMenu);
bindings.set(key!(q), Action::Quit);
bindings.set(key!(F5), Action::Refresh);
bindings.set(key!(s), Action::ToggleSummary);
bindings.set(key!(w), Action::ToggleWrap);
bindings.set(key!(b), Action::ToggleBacktrace("1"));
bindings.set(key!(Home), Action::Scroll(ScrollCommand::Top));
bindings.set(key!(End), Action::Scroll(ScrollCommand::Bottom));
bindings.set(key!(Up), Action::Scroll(ScrollCommand::Lines(-1)));
bindings.set(key!(Down), Action::Scroll(ScrollCommand::Lines(1)));
bindings.set(key!(PageUp), Action::Scroll(ScrollCommand::pages(-1)));
bindings.set(key!(PageDown), Action::Scroll(ScrollCommand::pages(1)));
bindings.set(key!(Space), Action::Scroll(ScrollCommand::MilliPages(800)));
bindings.set(key!(f), Action::ScopeToFailures);
bindings.set(key!(esc), Action::Back);
bindings.set(key!(ctrl - d), JobRef::Default);
bindings.set(key!(i), JobRef::Initial);
bindings.set(key!(p), Action::TogglePause);
bindings.set(key!('/'), Action::FocusSearch);
bindings.set(key!(':'), Action::FocusGoto);
bindings.set(key!(enter), Action::Validate);
bindings.set(key!(tab), Action::NextMatch);
bindings.set(key!(backtab), Action::PreviousMatch);
bindings.set(key!(shift - backtab), Action::PreviousMatch);
bindings.set(key!(ctrl - j), Action::OpenJobsMenu);
bindings.set(key!(a), JobRef::from_job_name("check-all"));
bindings.set(key!(c), JobRef::from_job_name("clippy"));
bindings.set(key!(d), JobRef::from_job_name("doc-open"));
bindings.set(key!(t), JobRef::from_job_name("test"));
bindings.set(key!(n), JobRef::from_job_name("nextest"));
bindings.set(key!(r), JobRef::from_job_name("run"));
bindings
}
}
impl KeyBindings {
pub fn set<A: Into<Action>>(
&mut self,
ck: KeyCombination,
action: A,
) {
self.map.insert(ck, action.into());
}
pub fn add_vim_keys(&mut self) {
self.set(key!(g), Action::Scroll(ScrollCommand::Top));
self.set(key!(shift - g), Action::Scroll(ScrollCommand::Bottom));
self.set(key!(k), Action::Scroll(ScrollCommand::Lines(-1)));
self.set(key!(j), Action::Scroll(ScrollCommand::Lines(1)));
}
pub fn add_all(
&mut self,
other: &KeyBindings,
) {
for (ck, action) in &other.map {
self.map.insert(*ck, action.clone());
}
}
pub fn get(
&self,
key: KeyCombination,
) -> Option<&Action> {
self.map.get(&key)
}
pub fn shortest_key_for(
&self,
action: &Action,
) -> Option<KeyCombination> {
self.shortest_key(|a| a == action)
}
pub fn shortest_key<F>(
&self,
filter: F,
) -> Option<KeyCombination>
where
F: Fn(&Action) -> bool,
{
let mut shortest: Option<(KeyCombination, String)> = None;
for (&ck, action) in &self.map {
if filter(action) {
let s = ck.to_string();
match &shortest {
Some(previous) if previous.1.len() < s.len() => {}
_ => {
shortest = Some((ck, s));
}
}
}
}
shortest.map(|o| o.0)
}
pub fn build_reverse_map(&self) -> HashMap<&Action, Vec<KeyCombination>> {
let mut reverse_map = HashMap::new();
for (ck, action) in &self.map {
reverse_map.entry(action).or_insert_with(Vec::new).push(*ck);
}
reverse_map
}
pub fn iter(&self) -> hash_map::Iter<'_, KeyCombination, Action> {
self.map.iter()
}
}
impl<'a> IntoIterator for &'a KeyBindings {
type Item = (&'a KeyCombination, &'a Action);
type IntoIter = hash_map::Iter<'a, KeyCombination, Action>;
fn into_iter(self) -> Self::IntoIter {
self.map.iter()
}
}
impl fmt::Debug for KeyBindings {
fn fmt(
&self,
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
let mut ds = f.debug_struct("KeyBindings");
for (kc, action) in &self.map {
ds.field(&kc.to_string(), &action.to_string());
}
ds.finish()
}
}
impl JsonSchema for KeyBindings {
fn schema_name() -> Cow<'static, str> {
"KeyBindings".into()
}
fn schema_id() -> Cow<'static, str> {
concat!(module_path!(), "::KeyBindings").into()
}
fn json_schema(_gen: &mut SchemaGenerator) -> Schema {
json_schema!({
"type": "object",
"description": "Mapping from key combinations to actions.",
})
}
fn inline_schema() -> bool {
false
}
}
#[test]
fn test_deserialize_keybindings() {
#[derive(Deserialize)]
struct Config {
keybindings: KeyBindings,
}
let toml = r#"
[keybindings]
Ctrl-U = "internal:scroll-pages(-.5)"
Ctrl-d = "internal:scroll-page(1)"
alt-q = "internal:quit"
ctrl-q = "quit"
alt-p = "job:previous"
ctrl-p = "play-sound"
ctrl-shift-p = "play-sound(volume=100)"
cmd-e = "quit"
"#;
let conf = toml::from_str::<Config>(toml).unwrap();
assert_eq!(
conf.keybindings.get(key!(ctrl - u)),
Some(&Action::Scroll(ScrollCommand::MilliPages(-500))),
);
assert_eq!(
conf.keybindings.get(key!(ctrl - d)),
Some(&Action::Scroll(ScrollCommand::MilliPages(1000))),
);
assert_eq!(conf.keybindings.get(key!(z)), None,);
assert_eq!(conf.keybindings.get(key!(alt - q)), Some(&Action::Quit),);
assert_eq!(
conf.keybindings.get(key!(alt - p)),
Some(&Action::Job(JobRef::Previous)),
);
assert_eq!(conf.keybindings.get(key!(cmd - e)), Some(&Action::Quit),);
}