use std::collections::BTreeMap;
use anyhow::{Context, Result, anyhow};
use crate::{Action, Direction, KeyChord, Motion};
#[derive(Debug, Clone)]
pub struct Keymap {
bindings: BTreeMap<String, Action>,
}
const DEFAULTS: &[(&str, Action)] = &[
(
"left",
Action::Move {
motion: Motion::Left,
extend: false,
},
),
(
"shift+left",
Action::Move {
motion: Motion::Left,
extend: true,
},
),
(
"right",
Action::Move {
motion: Motion::Right,
extend: false,
},
),
(
"shift+right",
Action::Move {
motion: Motion::Right,
extend: true,
},
),
(
"up",
Action::Move {
motion: Motion::Up,
extend: false,
},
),
(
"shift+up",
Action::Move {
motion: Motion::Up,
extend: true,
},
),
(
"down",
Action::Move {
motion: Motion::Down,
extend: false,
},
),
(
"shift+down",
Action::Move {
motion: Motion::Down,
extend: true,
},
),
(
"ctrl+left",
Action::Move {
motion: Motion::WordLeft,
extend: false,
},
),
(
"ctrl+shift+left",
Action::Move {
motion: Motion::WordLeft,
extend: true,
},
),
(
"ctrl+right",
Action::Move {
motion: Motion::WordRight,
extend: false,
},
),
(
"ctrl+shift+right",
Action::Move {
motion: Motion::WordRight,
extend: true,
},
),
(
"home",
Action::Move {
motion: Motion::LineStart,
extend: false,
},
),
(
"shift+home",
Action::Move {
motion: Motion::LineStart,
extend: true,
},
),
(
"end",
Action::Move {
motion: Motion::LineEnd,
extend: false,
},
),
(
"shift+end",
Action::Move {
motion: Motion::LineEnd,
extend: true,
},
),
(
"pageup",
Action::Move {
motion: Motion::PageUp,
extend: false,
},
),
(
"shift+pageup",
Action::Move {
motion: Motion::PageUp,
extend: true,
},
),
(
"pagedown",
Action::Move {
motion: Motion::PageDown,
extend: false,
},
),
(
"shift+pagedown",
Action::Move {
motion: Motion::PageDown,
extend: true,
},
),
(
"ctrl+home",
Action::Move {
motion: Motion::DocumentStart,
extend: false,
},
),
(
"ctrl+shift+home",
Action::Move {
motion: Motion::DocumentStart,
extend: true,
},
),
(
"ctrl+end",
Action::Move {
motion: Motion::DocumentEnd,
extend: false,
},
),
(
"ctrl+shift+end",
Action::Move {
motion: Motion::DocumentEnd,
extend: true,
},
),
(
"backspace",
Action::Delete {
direction: Direction::Backward,
by_word: false,
},
),
(
"ctrl+backspace",
Action::Delete {
direction: Direction::Backward,
by_word: true,
},
),
(
"delete",
Action::Delete {
direction: Direction::Forward,
by_word: false,
},
),
(
"ctrl+delete",
Action::Delete {
direction: Direction::Forward,
by_word: true,
},
),
("enter", Action::InsertNewline),
("ctrl+z", Action::Undo),
("ctrl+y", Action::Redo),
("ctrl+a", Action::SelectAll),
("ctrl+l", Action::SelectLine),
("esc", Action::CollapseSelections),
("ctrl+alt+up", Action::AddCursor(Direction::Backward)),
("ctrl+alt+down", Action::AddCursor(Direction::Forward)),
("ctrl+s", Action::Save),
("ctrl+q", Action::Quit),
("tab", Action::FocusNext),
("ctrl+f", Action::SearchOpen),
("f3", Action::SearchNext),
("shift+f3", Action::SearchPrevious),
("ctrl+h", Action::ReplaceOpen),
];
impl Keymap {
pub fn default_bindings() -> Self {
Self {
bindings: DEFAULTS
.iter()
.map(|(chord, action)| ((*chord).to_string(), *action))
.collect(),
}
}
pub fn lookup(&self, chord: &KeyChord) -> Option<Action> {
self.bindings.get(&chord.canonical).copied()
}
pub fn bindings_for(&self, action: Action) -> Vec<&str> {
self.bindings
.iter()
.filter(|(_, a)| **a == action)
.map(|(chord, _)| chord.as_str())
.collect()
}
pub fn merge_toml(&mut self, src: &str) -> Result<()> {
let table: BTreeMap<String, String> =
toml::from_str(src).context("parsing the keybinding table")?;
let mut staged: Vec<(String, Option<Action>)> = Vec::new();
for (chord, action_name) in table {
if action_name.is_empty() {
staged.push((chord, None));
continue;
}
let action = Action::from_name(&action_name)
.ok_or_else(|| anyhow!("{chord} is bound to an unknown action: {action_name}"))?;
staged.push((chord, Some(action)));
}
for (chord, action) in staged {
match action {
Some(action) => {
self.bindings.insert(chord, action);
}
None => {
self.bindings.remove(&chord);
}
}
}
Ok(())
}
}
impl Default for Keymap {
fn default() -> Self {
Self::default_bindings()
}
}