231 lines
6.5 KiB
Rust
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)
|
|
}
|