Fix previous broken refactor key into helix-view

Need to be used for autoinfo

Revert "Revert "Refactor key into helix-view""

This reverts commit 10f9f72232.
pull/371/head
Ivan Tham 3 years ago committed by Blaž Hrastnik
parent 15ae2e7ef1
commit 10548bf0e3

@ -44,6 +44,7 @@ pub struct Application {
compositor: Compositor,
editor: Editor,
// TODO should be separate to take only part of the config
config: Config,
theme_loader: Arc<theme::Loader>,
@ -54,7 +55,7 @@ pub struct Application {
}
impl Application {
pub fn new(mut args: Args, config: Config) -> Result<Self, Error> {
pub fn new(mut args: Args, mut config: Config) -> Result<Self, Error> {
use helix_view::editor::Action;
let mut compositor = Compositor::new()?;
let size = compositor.size();
@ -87,7 +88,7 @@ impl Application {
let mut editor = Editor::new(size, theme_loader.clone(), syn_loader.clone());
let mut editor_view = Box::new(ui::EditorView::new(config.keymaps.clone()));
let mut editor_view = Box::new(ui::EditorView::new(std::mem::take(&mut config.keys)));
compositor.push(editor_view);
if !args.files.is_empty() {

@ -15,6 +15,7 @@ use helix_core::{
use helix_view::{
document::{IndentStyle, Mode},
input::{KeyCode, KeyEvent},
view::{View, PADDING},
Document, DocumentId, Editor, ViewId,
};
@ -42,8 +43,8 @@ use std::{
path::{Path, PathBuf},
};
use crossterm::event::{KeyCode, KeyEvent};
use once_cell::sync::Lazy;
use serde::de::{self, Deserialize, Deserializer};
pub struct Context<'a> {
pub selected_register: helix_view::RegisterSelection,
@ -260,6 +261,48 @@ impl Command {
);
}
impl fmt::Debug for Command {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Command(name, _) = self;
f.debug_tuple("Command").field(name).finish()
}
}
impl fmt::Display for Command {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Command(name, _) = self;
f.write_str(name)
}
}
impl std::str::FromStr for Command {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Command::COMMAND_LIST
.iter()
.copied()
.find(|cmd| cmd.0 == s)
.ok_or_else(|| anyhow!("No command named '{}'", s))
}
}
impl<'de> Deserialize<'de> for Command {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
s.parse().map_err(de::Error::custom)
}
}
impl PartialEq for Command {
fn eq(&self, other: &Self) -> bool {
self.name() == other.name()
}
}
fn move_char_left(cx: &mut Context) {
let count = cx.count();
let (view, doc) = current!(cx.editor);
@ -3456,29 +3499,3 @@ fn surround_delete(cx: &mut Context) {
}
})
}
impl fmt::Display for Command {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Command(name, _) = self;
f.write_str(name)
}
}
impl std::str::FromStr for Command {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Command::COMMAND_LIST
.iter()
.copied()
.find(|cmd| cmd.0 == s)
.ok_or_else(|| anyhow!("No command named '{}'", s))
}
}
impl fmt::Debug for Command {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Command(name, _) = self;
f.debug_tuple("Command").field(name).finish()
}
}

@ -1,47 +1,62 @@
use anyhow::{Error, Result};
use serde::Deserialize;
use std::collections::HashMap;
use serde::{de::Error as SerdeError, Deserialize, Serialize};
use crate::commands::Command;
use crate::keymap::Keymaps;
use crate::keymap::{parse_keymaps, Keymaps};
#[derive(Default)]
#[derive(Debug, Default, Clone, PartialEq, Deserialize)]
pub struct Config {
pub theme: Option<String>,
#[serde(default)]
pub lsp: LspConfig,
pub keymaps: Keymaps,
#[serde(default)]
pub keys: Keymaps,
}
#[derive(Default, Serialize, Deserialize)]
#[derive(Debug, Default, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct LspConfig {
pub display_messages: bool,
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
struct TomlConfig {
theme: Option<String>,
#[serde(default)]
lsp: LspConfig,
keys: Option<HashMap<String, HashMap<String, String>>>,
}
#[test]
fn parsing_keymaps_config_file() {
use helix_core::hashmap;
use helix_view::document::Mode;
use helix_view::input::{KeyCode, KeyEvent, KeyModifiers};
let sample_keymaps = r#"
[keys.insert]
y = "move_line_down"
S-C-a = "delete_selection"
[keys.normal]
A-F12 = "move_next_word_end"
"#;
impl<'de> Deserialize<'de> for Config {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let config = TomlConfig::deserialize(deserializer)?;
Ok(Self {
theme: config.theme,
lsp: config.lsp,
keymaps: config
.keys
.map(|r| parse_keymaps(&r))
.transpose()
.map_err(|e| D::Error::custom(format!("Error deserializing keymap: {}", e)))?
.unwrap_or_else(Keymaps::default),
})
}
assert_eq!(
toml::from_str::<Config>(sample_keymaps).unwrap(),
Config {
keys: Keymaps(hashmap! {
Mode::Insert => hashmap! {
KeyEvent {
code: KeyCode::Char('y'),
modifiers: KeyModifiers::NONE,
} => Command::move_line_down,
KeyEvent {
code: KeyCode::Char('a'),
modifiers: KeyModifiers::SHIFT | KeyModifiers::CONTROL,
} => Command::delete_selection,
},
Mode::Normal => hashmap! {
KeyEvent {
code: KeyCode::F(12),
modifiers: KeyModifiers::ALT,
} => Command::move_next_word_end,
},
}),
..Default::default()
}
);
}

@ -1,8 +1,11 @@
use crate::commands;
pub use crate::commands::Command;
use crate::config::Config;
use anyhow::{anyhow, Error, Result};
use helix_core::hashmap;
use helix_view::document::Mode;
use helix_view::input::{KeyCode, KeyEvent, KeyModifiers};
use serde::Deserialize;
use std::{
collections::HashMap,
fmt::Display,
@ -99,14 +102,6 @@ use std::{
// D] = last diagnostic
// }
// #[cfg(feature = "term")]
pub use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
#[derive(Clone, Debug)]
pub struct Keymap(pub HashMap<KeyEvent, Command>);
#[derive(Clone, Debug)]
pub struct Keymaps(pub HashMap<Mode, Keymap>);
#[macro_export]
macro_rules! key {
($key:ident) => {
@ -141,9 +136,27 @@ macro_rules! alt {
};
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(transparent)]
pub struct Keymaps(pub HashMap<Mode, HashMap<KeyEvent, Command>>);
impl Deref for Keymaps {
type Target = HashMap<Mode, HashMap<KeyEvent, Command>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for Keymaps {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl Default for Keymaps {
fn default() -> Self {
let normal = Keymap(hashmap!(
fn default() -> Keymaps {
let normal = hashmap!(
key!('h') => Command::move_char_left,
key!('j') => Command::move_line_down,
key!('k') => Command::move_line_up,
@ -265,12 +278,12 @@ impl Default for Keymaps {
key!('z') => Command::view_mode,
key!('"') => Command::select_register,
));
);
// TODO: decide whether we want normal mode to also be select mode (kakoune-like), or whether
// we keep this separate select mode. More keys can fit into normal mode then, but it's weird
// because some selection operations can now be done from normal mode, some from select mode.
let mut select = normal.clone();
select.0.extend(
select.extend(
hashmap!(
key!('h') => Command::extend_char_left,
key!('j') => Command::extend_line_down,
@ -303,7 +316,7 @@ impl Default for Keymaps {
// TODO: select could be normal mode with some bindings merged over
Mode::Normal => normal,
Mode::Select => select,
Mode::Insert => Keymap(hashmap!(
Mode::Insert => hashmap!(
key!(Esc) => Command::normal_mode as Command,
key!(Backspace) => Command::delete_char_backward,
key!(Delete) => Command::delete_char_forward,
@ -319,309 +332,66 @@ impl Default for Keymaps {
key!(End) => Command::move_line_end,
ctrl!('x') => Command::completion,
ctrl!('w') => Command::delete_word_backward,
)),
),
))
}
}
// Newtype wrapper over keys to allow toml serialization/parsing
#[derive(Debug, PartialEq, PartialOrd, Clone, Copy, Hash)]
pub struct RepresentableKeyEvent(pub KeyEvent);
impl Display for RepresentableKeyEvent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self(key) = self;
f.write_fmt(format_args!(
"{}{}{}",
if key.modifiers.contains(KeyModifiers::SHIFT) {
"S-"
} else {
""
},
if key.modifiers.contains(KeyModifiers::ALT) {
"A-"
} else {
""
},
if key.modifiers.contains(KeyModifiers::CONTROL) {
"C-"
} else {
""
},
))?;
match key.code {
KeyCode::Backspace => f.write_str("backspace")?,
KeyCode::Enter => f.write_str("ret")?,
KeyCode::Left => f.write_str("left")?,
KeyCode::Right => f.write_str("right")?,
KeyCode::Up => f.write_str("up")?,
KeyCode::Down => f.write_str("down")?,
KeyCode::Home => f.write_str("home")?,
KeyCode::End => f.write_str("end")?,
KeyCode::PageUp => f.write_str("pageup")?,
KeyCode::PageDown => f.write_str("pagedown")?,
KeyCode::Tab => f.write_str("tab")?,
KeyCode::BackTab => f.write_str("backtab")?,
KeyCode::Delete => f.write_str("del")?,
KeyCode::Insert => f.write_str("ins")?,
KeyCode::Null => f.write_str("null")?,
KeyCode::Esc => f.write_str("esc")?,
KeyCode::Char('<') => f.write_str("lt")?,
KeyCode::Char('>') => f.write_str("gt")?,
KeyCode::Char('+') => f.write_str("plus")?,
KeyCode::Char('-') => f.write_str("minus")?,
KeyCode::Char(';') => f.write_str("semicolon")?,
KeyCode::Char('%') => f.write_str("percent")?,
KeyCode::F(i) => f.write_fmt(format_args!("F{}", i))?,
KeyCode::Char(c) => f.write_fmt(format_args!("{}", c))?,
};
Ok(())
}
}
impl FromStr for RepresentableKeyEvent {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut tokens: Vec<_> = s.split('-').collect();
let code = match tokens.pop().ok_or_else(|| anyhow!("Missing key code"))? {
"backspace" => KeyCode::Backspace,
"space" => KeyCode::Char(' '),
"ret" => KeyCode::Enter,
"lt" => KeyCode::Char('<'),
"gt" => KeyCode::Char('>'),
"plus" => KeyCode::Char('+'),
"minus" => KeyCode::Char('-'),
"semicolon" => KeyCode::Char(';'),
"percent" => KeyCode::Char('%'),
"left" => KeyCode::Left,
"right" => KeyCode::Right,
"up" => KeyCode::Down,
"home" => KeyCode::Home,
"end" => KeyCode::End,
"pageup" => KeyCode::PageUp,
"pagedown" => KeyCode::PageDown,
"tab" => KeyCode::Tab,
"backtab" => KeyCode::BackTab,
"del" => KeyCode::Delete,
"ins" => KeyCode::Insert,
"null" => KeyCode::Null,
"esc" => KeyCode::Esc,
single if single.len() == 1 => KeyCode::Char(single.chars().next().unwrap()),
function if function.len() > 1 && function.starts_with('F') => {
let function: String = function.chars().skip(1).collect();
let function = str::parse::<u8>(&function)?;
(function > 0 && function < 13)
.then(|| KeyCode::F(function))
.ok_or_else(|| anyhow!("Invalid function key '{}'", function))?
}
invalid => return Err(anyhow!("Invalid key code '{}'", invalid)),
};
let mut modifiers = KeyModifiers::empty();
for token in tokens {
let flag = match token {
"S" => KeyModifiers::SHIFT,
"A" => KeyModifiers::ALT,
"C" => KeyModifiers::CONTROL,
_ => return Err(anyhow!("Invalid key modifier '{}-'", token)),
};
if modifiers.contains(flag) {
return Err(anyhow!("Repeated key modifier '{}-'", token));
}
modifiers.insert(flag);
}
Ok(RepresentableKeyEvent(KeyEvent { code, modifiers }))
}
}
pub fn parse_keymaps(toml_keymaps: &HashMap<String, HashMap<String, String>>) -> Result<Keymaps> {
let mut keymaps = Keymaps::default();
for (mode, map) in toml_keymaps {
let mode = Mode::from_str(&mode)?;
for (key, command) in map {
let key = str::parse::<RepresentableKeyEvent>(&key)?;
let command = str::parse::<Command>(&command)?;
keymaps.0.get_mut(&mode).unwrap().0.insert(key.0, command);
}
}
Ok(keymaps)
}
impl Deref for Keymap {
type Target = HashMap<KeyEvent, Command>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Deref for Keymaps {
type Target = HashMap<Mode, Keymap>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for Keymap {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl DerefMut for Keymaps {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
/// Merge default config keys with user overwritten keys for custom
/// user config.
pub fn merge_keys(mut config: Config) -> Config {
let mut delta = std::mem::take(&mut config.keys);
for (mode, keys) in &mut *config.keys {
keys.extend(delta.remove(mode).unwrap_or_default());
}
config
}
#[cfg(test)]
mod test {
use crate::config::Config;
use super::*;
impl PartialEq for Command {
fn eq(&self, other: &Self) -> bool {
self.name() == other.name()
}
}
#[test]
fn parsing_keymaps_config_file() {
let sample_keymaps = r#"
[keys.insert]
y = "move_line_down"
S-C-a = "delete_selection"
[keys.normal]
A-F12 = "move_next_word_end"
"#;
let config: Config = toml::from_str(sample_keymaps).unwrap();
assert_eq!(
*config
.keymaps
.0
.get(&Mode::Insert)
.unwrap()
.0
.get(&KeyEvent {
code: KeyCode::Char('y'),
modifiers: KeyModifiers::NONE
})
.unwrap(),
Command::move_line_down
);
assert_eq!(
*config
.keymaps
.0
.get(&Mode::Insert)
.unwrap()
.0
.get(&KeyEvent {
code: KeyCode::Char('a'),
modifiers: KeyModifiers::SHIFT | KeyModifiers::CONTROL
})
.unwrap(),
Command::delete_selection
);
assert_eq!(
*config
.keymaps
.0
.get(&Mode::Normal)
.unwrap()
.0
.get(&KeyEvent {
code: KeyCode::F(12),
modifiers: KeyModifiers::ALT
})
.unwrap(),
Command::move_next_word_end
);
}
#[test]
fn parsing_unmodified_keys() {
assert_eq!(
str::parse::<RepresentableKeyEvent>("backspace").unwrap(),
RepresentableKeyEvent(KeyEvent {
code: KeyCode::Backspace,
modifiers: KeyModifiers::NONE
})
);
assert_eq!(
str::parse::<RepresentableKeyEvent>("left").unwrap(),
RepresentableKeyEvent(KeyEvent {
code: KeyCode::Left,
modifiers: KeyModifiers::NONE
})
);
assert_eq!(
str::parse::<RepresentableKeyEvent>(",").unwrap(),
RepresentableKeyEvent(KeyEvent {
code: KeyCode::Char(','),
modifiers: KeyModifiers::NONE
})
);
assert_eq!(
str::parse::<RepresentableKeyEvent>("w").unwrap(),
RepresentableKeyEvent(KeyEvent {
code: KeyCode::Char('w'),
#[test]
fn merge_partial_keys() {
let config = Config {
keys: Keymaps(hashmap! {
Mode::Normal => hashmap! {
KeyEvent {
code: KeyCode::Char('i'),
modifiers: KeyModifiers::NONE,
} => Command::normal_mode,
KeyEvent { // key that does not exist
code: KeyCode::Char('无'),
modifiers: KeyModifiers::NONE,
} => Command::insert_mode,
},
}),
..Default::default()
};
let merged_config = merge_keys(config.clone());
assert_ne!(config, merged_config);
assert_eq!(
*merged_config
.keys
.0
.get(&Mode::Normal)
.unwrap()
.get(&KeyEvent {
code: KeyCode::Char('i'),
modifiers: KeyModifiers::NONE
})
);
assert_eq!(
str::parse::<RepresentableKeyEvent>("F12").unwrap(),
RepresentableKeyEvent(KeyEvent {
code: KeyCode::F(12),
.unwrap(),
Command::normal_mode
);
assert_eq!(
*merged_config
.keys
.0
.get(&Mode::Normal)
.unwrap()
.get(&KeyEvent {
code: KeyCode::Char('无'),
modifiers: KeyModifiers::NONE
})
);
}
fn parsing_modified_keys() {
assert_eq!(
str::parse::<RepresentableKeyEvent>("S-minus").unwrap(),
RepresentableKeyEvent(KeyEvent {
code: KeyCode::Char('-'),
modifiers: KeyModifiers::SHIFT
})
);
assert_eq!(
str::parse::<RepresentableKeyEvent>("C-A-S-F12").unwrap(),
RepresentableKeyEvent(KeyEvent {
code: KeyCode::F(12),
modifiers: KeyModifiers::SHIFT | KeyModifiers::CONTROL | KeyModifiers::ALT
})
);
assert_eq!(
str::parse::<RepresentableKeyEvent>("S-C-2").unwrap(),
RepresentableKeyEvent(KeyEvent {
code: KeyCode::F(2),
modifiers: KeyModifiers::SHIFT | KeyModifiers::CONTROL
})
);
}
#[test]
fn parsing_nonsensical_keys_fails() {
assert!(str::parse::<RepresentableKeyEvent>("F13").is_err());
assert!(str::parse::<RepresentableKeyEvent>("F0").is_err());
assert!(str::parse::<RepresentableKeyEvent>("aaa").is_err());
assert!(str::parse::<RepresentableKeyEvent>("S-S-a").is_err());
assert!(str::parse::<RepresentableKeyEvent>("C-A-S-C-1").is_err());
assert!(str::parse::<RepresentableKeyEvent>("FU").is_err());
assert!(str::parse::<RepresentableKeyEvent>("123").is_err());
assert!(str::parse::<RepresentableKeyEvent>("S--").is_err());
}
.unwrap(),
Command::insert_mode
);
assert!(merged_config.keys.0.get(&Mode::Normal).unwrap().len() > 1);
assert!(merged_config.keys.0.get(&Mode::Insert).unwrap().len() > 0);
}

@ -1,10 +1,10 @@
use anyhow::{Context, Error, Result};
use helix_term::application::Application;
use helix_term::args::Args;
use helix_term::config::Config;
use helix_term::keymap::merge_keys;
use std::path::PathBuf;
use anyhow::{Context, Result};
fn setup_logging(logpath: PathBuf, verbosity: u64) -> Result<()> {
let mut base_config = fern::Dispatch::new();
@ -84,12 +84,11 @@ FLAGS:
std::fs::create_dir_all(&conf_dir).ok();
}
let config = std::fs::read_to_string(conf_dir.join("config.toml"))
.ok()
.map(|s| toml::from_str(&s))
.transpose()?
.or_else(|| Some(Config::default()))
.unwrap();
let config = match std::fs::read_to_string(conf_dir.join("config.toml")) {
Ok(config) => merge_keys(toml::from_str(&config)?),
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Config::default(),
Err(err) => return Err(Error::new(err)),
};
setup_logging(logpath, args.verbosity).context("failed to initialize logging")?;

@ -12,12 +12,13 @@ use helix_core::{
LineEnding, Position, Range,
};
use helix_lsp::LspProgressMap;
use helix_view::input::{KeyCode, KeyEvent, KeyModifiers};
use helix_view::{document::Mode, Document, Editor, Theme, View};
use std::borrow::Cow;
use crossterm::{
cursor,
event::{read, Event, EventStream, KeyCode, KeyEvent, KeyModifiers},
event::{read, Event, EventStream},
};
use tui::{
backend::CrosstermBackend,
@ -659,7 +660,8 @@ impl Component for EditorView {
cx.editor.resize(Rect::new(0, 0, width, height - 1));
EventResult::Consumed(None)
}
Event::Key(mut key) => {
Event::Key(key) => {
let mut key = KeyEvent::from(key);
canonicalize_key(&mut key);
// clear status
cx.editor.status_msg = None;

@ -1,5 +1,7 @@
use anyhow::{anyhow, Context, Error};
use serde::de::{self, Deserialize, Deserializer};
use std::cell::Cell;
use std::collections::HashMap;
use std::fmt::Display;
use std::future::Future;
use std::path::{Component, Path, PathBuf};
@ -17,8 +19,6 @@ use helix_core::{
use crate::{DocumentId, Theme, ViewId};
use std::collections::HashMap;
const BUF_SIZE: usize = 8192;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
@ -28,6 +28,40 @@ pub enum Mode {
Insert,
}
impl Display for Mode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Mode::Normal => f.write_str("normal"),
Mode::Select => f.write_str("select"),
Mode::Insert => f.write_str("insert"),
}
}
}
impl FromStr for Mode {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"normal" => Ok(Mode::Normal),
"select" => Ok(Mode::Select),
"insert" => Ok(Mode::Insert),
_ => Err(anyhow!("Invalid mode '{}'", s)),
}
}
}
// toml deserializer doesn't seem to recognize string as enum
impl<'de> Deserialize<'de> for Mode {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
s.parse().map_err(de::Error::custom)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum IndentStyle {
Tabs,
@ -97,29 +131,6 @@ impl fmt::Debug for Document {
}
}
impl Display for Mode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Mode::Normal => f.write_str("normal"),
Mode::Select => f.write_str("select"),
Mode::Insert => f.write_str("insert"),
}
}
}
impl FromStr for Mode {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"normal" => Ok(Mode::Normal),
"select" => Ok(Mode::Select),
"insert" => Ok(Mode::Insert),
_ => Err(anyhow!("Invalid mode '{}'", s)),
}
}
}
// The documentation and implementation of this function should be up-to-date with
// its sibling function, `to_writer()`.
//

@ -0,0 +1,226 @@
//! Input event handling, currently backed by crossterm.
use anyhow::{anyhow, Error};
use crossterm::event;
use serde::de::{self, Deserialize, Deserializer};
use std::fmt;
pub use crossterm::event::{KeyCode, KeyModifiers};
/// Represents a key event.
// We use a newtype here because we want to customize Deserialize and Display.
#[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Copy, Hash)]
pub struct KeyEvent {
pub code: KeyCode,
pub modifiers: KeyModifiers,
}
impl fmt::Display for KeyEvent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"{}{}{}",
if self.modifiers.contains(KeyModifiers::SHIFT) {
"S-"
} else {
""
},
if self.modifiers.contains(KeyModifiers::ALT) {
"A-"
} else {
""
},
if self.modifiers.contains(KeyModifiers::CONTROL) {
"C-"
} else {
""
},
))?;
match self.code {
KeyCode::Backspace => f.write_str("backspace")?,
KeyCode::Enter => f.write_str("ret")?,
KeyCode::Left => f.write_str("left")?,
KeyCode::Right => f.write_str("right")?,
KeyCode::Up => f.write_str("up")?,
KeyCode::Down => f.write_str("down")?,
KeyCode::Home => f.write_str("home")?,
KeyCode::End => f.write_str("end")?,
KeyCode::PageUp => f.write_str("pageup")?,
KeyCode::PageDown => f.write_str("pagedown")?,
KeyCode::Tab => f.write_str("tab")?,
KeyCode::BackTab => f.write_str("backtab")?,
KeyCode::Delete => f.write_str("del")?,
KeyCode::Insert => f.write_str("ins")?,
KeyCode::Null => f.write_str("null")?,
KeyCode::Esc => f.write_str("esc")?,
KeyCode::Char('<') => f.write_str("lt")?,
KeyCode::Char('>') => f.write_str("gt")?,
KeyCode::Char('+') => f.write_str("plus")?,
KeyCode::Char('-') => f.write_str("minus")?,
KeyCode::Char(';') => f.write_str("semicolon")?,
KeyCode::Char('%') => f.write_str("percent")?,
KeyCode::F(i) => f.write_fmt(format_args!("F{}", i))?,
KeyCode::Char(c) => f.write_fmt(format_args!("{}", c))?,
};
Ok(())
}
}
impl std::str::FromStr for KeyEvent {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut tokens: Vec<_> = s.split('-').collect();
let code = match tokens.pop().ok_or_else(|| anyhow!("Missing key code"))? {
"backspace" => KeyCode::Backspace,
"space" => KeyCode::Char(' '),
"ret" => KeyCode::Enter,
"lt" => KeyCode::Char('<'),
"gt" => KeyCode::Char('>'),
"plus" => KeyCode::Char('+'),
"minus" => KeyCode::Char('-'),
"semicolon" => KeyCode::Char(';'),
"percent" => KeyCode::Char('%'),
"left" => KeyCode::Left,
"right" => KeyCode::Right,
"up" => KeyCode::Down,
"home" => KeyCode::Home,
"end" => KeyCode::End,
"pageup" => KeyCode::PageUp,
"pagedown" => KeyCode::PageDown,
"tab" => KeyCode::Tab,
"backtab" => KeyCode::BackTab,
"del" => KeyCode::Delete,
"ins" => KeyCode::Insert,
"null" => KeyCode::Null,
"esc" => KeyCode::Esc,
single if single.len() == 1 => KeyCode::Char(single.chars().next().unwrap()),
function if function.len() > 1 && function.starts_with('F') => {
let function: String = function.chars().skip(1).collect();
let function = str::parse::<u8>(&function)?;
(function > 0 && function < 13)
.then(|| KeyCode::F(function))
.ok_or_else(|| anyhow!("Invalid function key '{}'", function))?
}
invalid => return Err(anyhow!("Invalid key code '{}'", invalid)),
};
let mut modifiers = KeyModifiers::empty();
for token in tokens {
let flag = match token {
"S" => KeyModifiers::SHIFT,
"A" => KeyModifiers::ALT,
"C" => KeyModifiers::CONTROL,
_ => return Err(anyhow!("Invalid key modifier '{}-'", token)),
};
if modifiers.contains(flag) {
return Err(anyhow!("Repeated key modifier '{}-'", token));
}
modifiers.insert(flag);
}
Ok(KeyEvent { code, modifiers })
}
}
impl<'de> Deserialize<'de> for KeyEvent {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
s.parse().map_err(de::Error::custom)
}
}
impl From<event::KeyEvent> for KeyEvent {
fn from(event::KeyEvent { code, modifiers }: event::KeyEvent) -> KeyEvent {
KeyEvent { code, modifiers }
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn parsing_unmodified_keys() {
assert_eq!(
str::parse::<KeyEvent>("backspace").unwrap(),
KeyEvent {
code: KeyCode::Backspace,
modifiers: KeyModifiers::NONE
}
);
assert_eq!(
str::parse::<KeyEvent>("left").unwrap(),
KeyEvent {
code: KeyCode::Left,
modifiers: KeyModifiers::NONE
}
);
assert_eq!(
str::parse::<KeyEvent>(",").unwrap(),
KeyEvent {
code: KeyCode::Char(','),
modifiers: KeyModifiers::NONE
}
);
assert_eq!(
str::parse::<KeyEvent>("w").unwrap(),
KeyEvent {
code: KeyCode::Char('w'),
modifiers: KeyModifiers::NONE
}
);
assert_eq!(
str::parse::<KeyEvent>("F12").unwrap(),
KeyEvent {
code: KeyCode::F(12),
modifiers: KeyModifiers::NONE
}
);
}
#[test]
fn parsing_modified_keys() {
assert_eq!(
str::parse::<KeyEvent>("S-minus").unwrap(),
KeyEvent {
code: KeyCode::Char('-'),
modifiers: KeyModifiers::SHIFT
}
);
assert_eq!(
str::parse::<KeyEvent>("C-A-S-F12").unwrap(),
KeyEvent {
code: KeyCode::F(12),
modifiers: KeyModifiers::SHIFT | KeyModifiers::CONTROL | KeyModifiers::ALT
}
);
assert_eq!(
str::parse::<KeyEvent>("S-C-2").unwrap(),
KeyEvent {
code: KeyCode::Char('2'),
modifiers: KeyModifiers::SHIFT | KeyModifiers::CONTROL
}
);
}
#[test]
fn parsing_nonsensical_keys_fails() {
assert!(str::parse::<KeyEvent>("F13").is_err());
assert!(str::parse::<KeyEvent>("F0").is_err());
assert!(str::parse::<KeyEvent>("aaa").is_err());
assert!(str::parse::<KeyEvent>("S-S-a").is_err());
assert!(str::parse::<KeyEvent>("C-A-S-C-1").is_err());
assert!(str::parse::<KeyEvent>("FU").is_err());
assert!(str::parse::<KeyEvent>("123").is_err());
assert!(str::parse::<KeyEvent>("S--").is_err());
}
}

@ -4,14 +4,16 @@ pub mod macros;
pub mod clipboard;
pub mod document;
pub mod editor;
pub mod input;
pub mod register_selection;
pub mod theme;
pub mod tree;
pub mod view;
use slotmap::new_key_type;
new_key_type! { pub struct DocumentId; }
new_key_type! { pub struct ViewId; }
slotmap::new_key_type! {
pub struct DocumentId;
pub struct ViewId;
}
pub use document::Document;
pub use editor::Editor;

Loading…
Cancel
Save