breed/src/keybinds.rs

231 lines
6.5 KiB
Rust

/*
* Copyright (c) 2023 Marceline Cramer
* Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
use std::collections::HashMap;
use crossterm::event::KeyCode;
use toml::{Table, Value};
use crate::actions::{load_actions, Action};
pub type Key = KeyCode;
pub type KeyMap = HashMap<Key, Keybind>;
pub struct Keybinds {
pub normal: ModeKeys,
pub insert: ModeKeys,
pub visual: ModeKeys,
}
impl Default for Keybinds {
fn default() -> Self {
use crate::actions::*;
use KeyCode::*;
let basic_nav = &[
(Esc, normal_mode as Action),
(Left, move_char_left),
(Down, move_line_down),
(Up, move_line_up),
(Right, move_char_right),
(PageUp, page_up),
(PageDown, page_down),
(Home, goto_line_start),
(End, goto_line_end),
];
let goto_keys = &[
(Char('h'), goto_line_start as Action),
(Char('l'), goto_line_end),
(Char('s'), goto_first_nonwhitespace),
(Char('g'), goto_file_start),
(Char('e'), goto_file_end),
];
let normalish_keys = [
(Char(':'), command_mode as Action),
(Char('$'), goto_line_end),
(Char('0'), goto_line_start),
(Char('^'), goto_first_nonwhitespace),
(Char('h'), move_char_left),
(Char('j'), move_line_down),
(Char('k'), move_line_up),
(Char('l'), move_char_right),
(Char('w'), move_next_word_start),
(Char('b'), move_prev_word_start),
(Char('e'), move_next_word_end),
(Char('W'), move_next_long_word_start),
(Char('B'), move_prev_long_word_start),
(Char('E'), move_next_long_word_end),
(Char('i'), insert_mode),
(Char('a'), append_mode),
(Char('I'), insert_at_line_start),
(Char('A'), insert_at_line_end),
(Char('o'), open_below),
(Char('O'), open_above),
(Char('u'), undo),
(Char('U'), redo),
(Char('d'), delete_char_forward),
]
.iter()
.chain(basic_nav);
let normalish_submodes: HashMap<Key, KeyMap> =
[(Char('g'), key_map_from_iter(goto_keys))].into();
let insert_keys = [
(Backspace, delete_char_backward as Action),
(Delete, delete_char_forward),
(Enter, insert_newline),
]
.iter()
.chain(basic_nav);
Self {
normal: ModeKeys {
submodes: normalish_submodes.clone(),
map: key_map_from_iter(normalish_keys.clone()),
},
insert: ModeKeys {
submodes: [].into(),
map: key_map_from_iter(insert_keys),
},
visual: ModeKeys {
submodes: normalish_submodes,
map: key_map_from_iter(normalish_keys.clone()),
},
}
}
}
#[derive(Clone, Default)]
pub struct ModeKeys {
pub submodes: HashMap<Key, KeyMap>,
pub map: KeyMap,
}
impl ModeKeys {
pub fn apply_table(values: Table) -> Result<Self, String> {
let mut keys = Self::default();
for (key, value) in values {
let key = parse_key(&key)?;
match value {
Value::Table(table) => {
let map = parse_key_map(table)?;
keys.submodes.insert(key, map);
}
Value::String(keybind) => {
let bind = Keybind::try_from(keybind.as_str())?;
keys.map.insert(key, bind);
}
value => return Err(format!("Expected string or table but got {:?}", value)),
}
}
Ok(keys)
}
}
#[derive(Clone)]
pub enum Keybind {
Action(Action),
Command(String),
}
impl TryFrom<Value> for Keybind {
type Error = String;
fn try_from(value: Value) -> Result<Self, String> {
value
.as_str()
.ok_or_else(|| format!("Expected string but got {:?}", value))?
.try_into()
}
}
impl TryFrom<&str> for Keybind {
type Error = String;
fn try_from(keybind: &str) -> Result<Self, String> {
if keybind.starts_with(':') {
Ok(Keybind::Command(keybind[1..].to_string()))
} else {
// TODO static once cell
let actions = load_actions();
actions
.get(keybind)
.map(|cb| Keybind::Action(cb.clone()))
.ok_or_else(|| format!("Couldn't find action {:?}", keybind))
}
}
}
pub fn key_map_from_iter<'a, T>(iter: T) -> KeyMap
where
T: IntoIterator<Item = &'a (Key, Action)>,
{
let mut map = KeyMap::new();
for (key, action) in iter {
map.insert(*key, Keybind::Action(*action));
}
map
}
pub fn parse_key_map(values: Table) -> Result<KeyMap, String> {
let mut map = KeyMap::with_capacity(values.len());
for (key, value) in values {
let key = parse_key(&key)?;
let bind = Keybind::try_from(value)?;
map.insert(key, bind);
}
Ok(map)
}
pub fn parse_key(key: &str) -> Result<Key, String> {
use KeyCode::*;
let key = match key {
key if key.len() == 1 => Char(key.chars().nth(0).unwrap()),
"backspace" => Backspace,
"space" => Char(' '),
"ret" => Enter,
"minus" => Char('-'),
"left" => Left,
"right" => Right,
"up" => Up,
"down" => Down,
"home" => Home,
"end" => End,
"pageup" => PageUp,
"pagedown" => PageDown,
"tab" => Tab,
"del" => Delete,
"ins" => Insert,
"null" => Null,
"esc" => Esc,
key => return Err(format!("Unrecognized key name {:?}", key)),
};
Ok(key)
}