Add simple keybinds system

This commit is contained in:
mars 2023-04-13 00:04:21 -04:00
parent 18604f3ddd
commit 5f1e76e912
3 changed files with 249 additions and 61 deletions

View File

@ -24,17 +24,22 @@ use crossbeam_channel::{unbounded, Receiver, RecvError, Sender};
use notify::{Event, EventKind, RecommendedWatcher, Watcher};
use parking_lot::Mutex;
use crate::keybinds::Keybinds;
use crate::theme::{StyleStore, Theme};
pub struct Config {
/// Whether or not to wrap lines when moving the cursor left and right.
pub move_linewrap: bool,
/// Key bindings.
pub keybinds: Keybinds,
}
impl Default for Config {
fn default() -> Self {
Self {
move_linewrap: true,
keybinds: Default::default(),
}
}
}

202
src/keybinds.rs Normal file
View File

@ -0,0 +1,202 @@
/*
* Copyright (c) 2023 Marceline Cramer
* 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_newline),
];
let normalish_keys = &[
(Char(':'), command_mode as Action),
(Char('h'), move_char_left),
(Char('j'), move_line_down),
(Char('k'), move_line_up),
(Char('l'), move_char_right),
(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),
]
.iter()
.chain(basic_nav);
let insert_keys = &[
(Backspace, delete_char_backward as Action),
(Delete, delete_char_forward),
(Enter, insert_newline),
]
.iter()
.chain(basic_nav);
Self {
normal: normalish_keys.clone().into(),
insert: insert_keys.clone().into(),
visual: normalish_keys.clone().into(),
}
}
}
#[derive(Clone, Default)]
pub struct ModeKeys {
pub minor_modes: HashMap<Key, KeyMap>,
pub map: KeyMap,
}
impl<'a, T> From<T> for ModeKeys
where
T: IntoIterator<Item = &'a (Key, Action)>,
{
fn from(iter: T) -> Self {
let minor_modes = HashMap::new();
let mut map = KeyMap::new();
for (key, action) in iter {
map.insert(*key, Keybind::Action(*action));
}
Self { minor_modes, map }
}
}
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.minor_modes.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 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)
}

View File

@ -33,6 +33,7 @@ use crossterm::{
event::{Event, KeyCode, KeyEvent},
terminal, QueueableCommand, Result,
};
use keybinds::Keybind;
use parking_lot::Mutex;
use ropey::Rope;
use yacexits::{exit, EX_DATAERR, EX_UNAVAILABLE};
@ -40,9 +41,11 @@ use yacexits::{exit, EX_DATAERR, EX_UNAVAILABLE};
mod actions;
mod buffer;
mod config;
mod keybinds;
mod theme;
use buffer::Buffer;
use config::Config;
use theme::StyleStore;
#[derive(Copy, Clone, Debug, Default)]
@ -103,6 +106,7 @@ pub enum Direction {
pub struct State {
pub styles: Arc<Mutex<StyleStore>>,
pub config: Config,
pub buffer: Buffer,
pub cursor: Cursor,
pub file: Option<OsString>,
@ -124,6 +128,7 @@ impl State {
Ok(Self {
styles,
config: Default::default(),
buffer,
cursor: Cursor::default(),
file: file_name,
@ -170,7 +175,9 @@ impl State {
// draw buffer
let buffer_rows = if show_status_bar { rows - 1 } else { rows };
let lr_width = self.buffer.draw(cols, buffer_rows, self.scroll, self.cursor, out)?;
let lr_width = self
.buffer
.draw(cols, buffer_rows, self.scroll, self.cursor, out)?;
// draw cursor
let cursor_pos = set_cursor_pos.unwrap_or_else(|| {
@ -211,28 +218,16 @@ impl State {
fn on_normal_event(&mut self, event: Event, mut state: NormalState) {
// reset the error from the last event
state.error = None;
self.mode = Mode::Normal(state);
match event {
Event::Key(KeyEvent { code, .. }) => match code {
KeyCode::Char(':') => {
self.mode = Mode::Command(Default::default());
Event::Key(KeyEvent { code, .. }) => {
if let Some(keybind) = self.config.keybinds.visual.map.get(&code) {
self.execute_keybind(keybind.clone());
}
KeyCode::Char('v') => {
self.mode = Mode::Visual;
}
KeyCode::Char('i') => actions::insert_mode(self),
KeyCode::Char('a') => actions::append_mode(self),
KeyCode::Char('o') => actions::open_below(self),
KeyCode::Char('O') => actions::open_above(self),
code => self.on_any_key(code),
},
}
event => self.on_any_event(event),
}
match self.mode {
Mode::Normal(_) => self.mode = Mode::Normal(state),
_ => {}
}
}
fn on_command_event(&mut self, event: Event, mut state: CommandState) {
@ -264,7 +259,11 @@ impl State {
self.mode = Mode::Normal(NormalState { error });
return;
}
code => return self.on_any_key(code),
KeyCode::Esc => {
self.mode = Mode::default();
return;
}
_ => {}
},
event => return self.on_any_event(event),
}
@ -274,43 +273,25 @@ impl State {
fn on_visual_event(&mut self, event: Event) {
match event {
Event::Key(KeyEvent { code, .. }) => match code {
KeyCode::Esc => {
self.mode = Mode::default();
Event::Key(KeyEvent { code, .. }) => {
if let Some(keybind) = self.config.keybinds.visual.map.get(&code) {
self.execute_keybind(keybind.clone());
}
code => self.on_any_key(code),
},
}
event => self.on_any_event(event),
}
}
fn on_insert_event(&mut self, event: Event, state: InsertState) {
fn on_insert_event(&mut self, event: Event, _state: InsertState) {
match event {
Event::Key(KeyEvent { code, .. }) => match code {
KeyCode::Char(c) => {
self.buffer.insert_char(self.cursor, c);
self.move_cursor(Direction::Right)
}
KeyCode::Backspace => {
self.move_cursor(Direction::Left);
self.buffer.remove(self.cursor);
}
KeyCode::Delete => {
self.buffer.remove(self.cursor);
}
KeyCode::Enter => {
self.buffer.insert_char(self.cursor, '\n');
self.cursor.line += 1;
self.cursor.column = 0;
}
KeyCode::Esc => {
if state.append {
self.move_cursor(Direction::Left);
Event::Key(KeyEvent { code, .. }) => match self.config.keybinds.insert.map.get(&code) {
Some(keybind) => self.execute_keybind(keybind.clone()),
None => {
if let KeyCode::Char(c) = code {
self.buffer.insert_char(self.cursor, c);
self.move_cursor(Direction::Right);
}
self.mode = Mode::default();
}
code => self.on_any_key(code),
},
event => self.on_any_event(event),
}
@ -321,18 +302,6 @@ impl State {
Event::Resize(cols, rows) => {
self.size = (cols as usize, rows as usize);
}
Event::Key(KeyEvent { code, .. }) => self.on_any_key(code),
_ => {}
}
}
fn on_any_key(&mut self, code: KeyCode) {
match code {
KeyCode::Esc => self.mode = Mode::default(),
KeyCode::Char('h') | KeyCode::Left => actions::move_char_left(self),
KeyCode::Char('j') | KeyCode::Down => actions::move_line_down(self),
KeyCode::Char('k') | KeyCode::Up => actions::move_line_up(self),
KeyCode::Char('l') | KeyCode::Right => actions::move_char_right(self),
_ => {}
}
}
@ -359,7 +328,19 @@ impl State {
self.write_buffer(handle).map_err(|err| format!("{}", err))
}
fn execute_command(mut self, command: &str) -> std::result::Result<(), String> {
fn execute_keybind(&mut self, keybind: Keybind) {
match keybind {
Keybind::Action(action) => action(self),
Keybind::Command(command) => {
let result = self.execute_command(&command);
let error = result.err();
self.mode = Mode::Normal(NormalState { error });
return;
}
}
}
fn execute_command(&mut self, command: &str) -> std::result::Result<(), String> {
let command_parts = command.split(' ').collect::<Vec<&str>>();
let command = match command_parts.get(0) {