use std::collections::HashMap;
use crate::commands::{
caret_left, caret_line_end, caret_line_start, caret_right, chain, delete_backward,
delete_forward, delete_selection, join_backward, join_forward, select_all, split_block,
Command, Dispatch,
};
use crate::state::EditorState;
#[derive(Debug, Clone)]
pub struct KeyPress {
pub key: String,
pub ctrl: bool,
pub alt: bool,
pub shift: bool,
pub meta: bool,
}
impl KeyPress {
pub fn key(name: &str) -> Self {
KeyPress {
key: name.to_string(),
ctrl: false,
alt: false,
shift: false,
meta: false,
}
}
pub fn ctrl(mut self) -> Self {
self.ctrl = true;
self
}
pub fn alt(mut self) -> Self {
self.alt = true;
self
}
pub fn shift(mut self) -> Self {
self.shift = true;
self
}
pub fn meta(mut self) -> Self {
self.meta = true;
self
}
fn canonical(&self) -> String {
let mut s = String::new();
if self.alt {
s.push_str("Alt-");
}
if self.ctrl {
s.push_str("Ctrl-");
}
if self.meta {
s.push_str("Meta-");
}
if self.shift {
s.push_str("Shift-");
}
s.push_str(&self.key);
s
}
}
pub struct Keymap {
mac: bool,
bindings: HashMap<String, Command>,
}
impl Keymap {
pub fn new(mac: bool, bindings: Vec<(&str, Command)>) -> Self {
let mut map = HashMap::new();
let mut km = Keymap {
mac,
bindings: HashMap::new(),
};
for (spec, cmd) in bindings {
map.insert(km.normalize_spec(spec), cmd);
}
km.bindings = map;
km
}
fn normalize_spec(&self, spec: &str) -> String {
let parts: Vec<&str> = spec.split('-').collect();
let (mods, key) = parts.split_at(parts.len() - 1);
let (mut alt, mut ctrl, mut shift, mut meta) = (false, false, false, false);
for m in mods {
match *m {
"Mod" => {
if self.mac {
meta = true;
} else {
ctrl = true;
}
}
"Cmd" | "Meta" => meta = true,
"Ctrl" | "Control" => ctrl = true,
"Alt" | "Option" => alt = true,
"Shift" => shift = true,
other => panic!("unknown key modifier `{other}`"),
}
}
KeyPress {
key: key[0].to_string(),
ctrl,
alt,
shift,
meta,
}
.canonical()
}
pub fn handle(
&self,
state: &EditorState,
press: &KeyPress,
mut dispatch: Option<&mut Dispatch<'_>>,
) -> bool {
if let Some(cmd) = self.bindings.get(&press.canonical()) {
return cmd(state, dispatch.as_deref_mut());
}
if press.shift && shift_is_implicit(&press.key) {
let mut alt = press.clone();
alt.shift = false;
if let Some(cmd) = self.bindings.get(&alt.canonical()) {
return cmd(state, dispatch);
}
}
false
}
pub fn add(&mut self, spec: &str, command: Command) {
let canonical = self.normalize_spec(spec);
self.bindings.insert(canonical, command);
}
pub fn add_chained(&mut self, spec: &str, command: Command) {
let canonical = self.normalize_spec(spec);
match self.bindings.remove(&canonical) {
Some(existing) => {
self.bindings
.insert(canonical, chain(vec![command, existing]));
}
None => {
self.bindings.insert(canonical, command);
}
}
}
pub fn len(&self) -> usize {
self.bindings.len()
}
pub fn is_empty(&self) -> bool {
self.bindings.is_empty()
}
}
fn shift_is_implicit(key: &str) -> bool {
let mut chars = key.chars();
match (chars.next(), chars.next()) {
(Some(c), None) => !c.is_ascii_lowercase(),
_ => false,
}
}
pub fn base_keymap(mac: bool) -> Keymap {
let bindings: Vec<(&str, Command)> = vec![
("Enter", Box::new(split_block)),
(
"Backspace",
chain(vec![
Box::new(delete_selection),
Box::new(join_backward),
Box::new(delete_backward),
]),
),
(
"Delete",
chain(vec![
Box::new(delete_selection),
Box::new(join_forward),
Box::new(delete_forward),
]),
),
("Mod-a", Box::new(select_all)),
("ArrowLeft", Box::new(caret_left)),
("ArrowRight", Box::new(caret_right)),
("Home", Box::new(caret_line_start)),
("End", Box::new(caret_line_end)),
];
Keymap::new(mac, bindings)
}