/* * Copyright (c) 2023 Marceline Cramer * Copyright (c) 2023 Emma Tebibyte * 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; 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 = [(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, pub map: KeyMap, } impl ModeKeys { pub fn apply_table(values: Table) -> Result { 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 for Keybind { type Error = String; fn try_from(value: Value) -> Result { 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 { 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, { 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 { 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 { 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) }