Revert "Refactor key into helix-view"
Did not use defaults when custom keymap was used
This reverts commit ca806d4f85
.
This commit is contained in:
parent
11f20af25f
commit
10f9f72232
9 changed files with 415 additions and 389 deletions
|
@ -1,7 +1,7 @@
|
||||||
use helix_lsp::{lsp, LspProgressMap};
|
use helix_lsp::{lsp, LspProgressMap};
|
||||||
use helix_view::{document::Mode, Document, Editor, Theme, View};
|
use helix_view::{document::Mode, Document, Editor, Theme, View};
|
||||||
|
|
||||||
use crate::{args::Args, compositor::Compositor, config::Config, ui};
|
use crate::{args::Args, compositor::Compositor, config::Config, keymap::Keymaps, ui};
|
||||||
|
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ impl Application {
|
||||||
let size = compositor.size();
|
let size = compositor.size();
|
||||||
let mut editor = Editor::new(size);
|
let mut editor = Editor::new(size);
|
||||||
|
|
||||||
let mut editor_view = Box::new(ui::EditorView::new(config.keys));
|
let mut editor_view = Box::new(ui::EditorView::new(config.keymaps));
|
||||||
compositor.push(editor_view);
|
compositor.push(editor_view);
|
||||||
|
|
||||||
if !args.files.is_empty() {
|
if !args.files.is_empty() {
|
||||||
|
|
|
@ -11,7 +11,6 @@ use helix_core::{
|
||||||
|
|
||||||
use helix_view::{
|
use helix_view::{
|
||||||
document::{IndentStyle, Mode},
|
document::{IndentStyle, Mode},
|
||||||
input::{KeyCode, KeyEvent},
|
|
||||||
view::{View, PADDING},
|
view::{View, PADDING},
|
||||||
Document, DocumentId, Editor, ViewId,
|
Document, DocumentId, Editor, ViewId,
|
||||||
};
|
};
|
||||||
|
@ -39,8 +38,8 @@ use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crossterm::event::{KeyCode, KeyEvent};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use serde::de::{self, Deserialize, Deserializer};
|
|
||||||
|
|
||||||
pub struct Context<'a> {
|
pub struct Context<'a> {
|
||||||
pub selected_register: helix_view::RegisterSelection,
|
pub selected_register: helix_view::RegisterSelection,
|
||||||
|
@ -253,48 +252,6 @@ 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) {
|
fn move_char_left(cx: &mut Context) {
|
||||||
let count = cx.count();
|
let count = cx.count();
|
||||||
let (view, doc) = current!(cx.editor);
|
let (view, doc) = current!(cx.editor);
|
||||||
|
@ -3085,3 +3042,29 @@ fn right_bracket_mode(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,9 +1,10 @@
|
||||||
use serde::Deserialize;
|
use anyhow::{Error, Result};
|
||||||
|
use std::{collections::HashMap, str::FromStr};
|
||||||
|
|
||||||
use crate::commands::Command;
|
use serde::{de::Error as SerdeError, Deserialize, Serialize};
|
||||||
use crate::keymap::Keymaps;
|
|
||||||
|
use crate::keymap::{parse_keymaps, Keymaps};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Deserialize)]
|
|
||||||
pub struct GlobalConfig {
|
pub struct GlobalConfig {
|
||||||
pub lsp_progress: bool,
|
pub lsp_progress: bool,
|
||||||
}
|
}
|
||||||
|
@ -14,50 +15,35 @@ impl Default for GlobalConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, PartialEq, Deserialize)]
|
#[derive(Default)]
|
||||||
#[serde(default)]
|
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub global: GlobalConfig,
|
pub global: GlobalConfig,
|
||||||
pub keys: Keymaps,
|
pub keymaps: Keymaps,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[derive(Serialize, Deserialize)]
|
||||||
fn parsing_keymaps_config_file() {
|
#[serde(rename_all = "kebab-case")]
|
||||||
use helix_core::hashmap;
|
struct TomlConfig {
|
||||||
use helix_view::document::Mode;
|
lsp_progress: Option<bool>,
|
||||||
use helix_view::input::{KeyCode, KeyEvent, KeyModifiers};
|
keys: Option<HashMap<String, HashMap<String, String>>>,
|
||||||
|
}
|
||||||
|
|
||||||
let sample_keymaps = r#"
|
impl<'de> Deserialize<'de> for Config {
|
||||||
[keys.insert]
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
y = "move_line_down"
|
where
|
||||||
S-C-a = "delete_selection"
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
[keys.normal]
|
let config = TomlConfig::deserialize(deserializer)?;
|
||||||
A-F12 = "move_next_word_end"
|
Ok(Self {
|
||||||
"#;
|
global: GlobalConfig {
|
||||||
|
lsp_progress: config.lsp_progress.unwrap_or(true),
|
||||||
assert_eq!(
|
|
||||||
toml::from_str::<Config>(sample_keymaps).unwrap(),
|
|
||||||
Config {
|
|
||||||
global: Default::default(),
|
|
||||||
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,
|
|
||||||
},
|
},
|
||||||
|
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),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,6 @@ pub use crate::commands::Command;
|
||||||
use anyhow::{anyhow, Error, Result};
|
use anyhow::{anyhow, Error, Result};
|
||||||
use helix_core::hashmap;
|
use helix_core::hashmap;
|
||||||
use helix_view::document::Mode;
|
use helix_view::document::Mode;
|
||||||
use helix_view::input::{KeyCode, KeyEvent, KeyModifiers};
|
|
||||||
use serde::Deserialize;
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fmt::Display,
|
fmt::Display,
|
||||||
|
@ -101,6 +99,14 @@ use std::{
|
||||||
// D] = last diagnostic
|
// 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_export]
|
||||||
macro_rules! key {
|
macro_rules! key {
|
||||||
($key:ident) => {
|
($key:ident) => {
|
||||||
|
@ -135,21 +141,9 @@ macro_rules! alt {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, 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 Default for Keymaps {
|
impl Default for Keymaps {
|
||||||
fn default() -> Keymaps {
|
fn default() -> Self {
|
||||||
let normal = hashmap!(
|
let normal = Keymap(hashmap!(
|
||||||
key!('h') => Command::move_char_left,
|
key!('h') => Command::move_char_left,
|
||||||
key!('j') => Command::move_line_down,
|
key!('j') => Command::move_line_down,
|
||||||
key!('k') => Command::move_line_up,
|
key!('k') => Command::move_line_up,
|
||||||
|
@ -283,12 +277,12 @@ impl Default for Keymaps {
|
||||||
key!('z') => Command::view_mode,
|
key!('z') => Command::view_mode,
|
||||||
|
|
||||||
key!('"') => Command::select_register,
|
key!('"') => Command::select_register,
|
||||||
);
|
));
|
||||||
// TODO: decide whether we want normal mode to also be select mode (kakoune-like), or whether
|
// 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
|
// 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.
|
// because some selection operations can now be done from normal mode, some from select mode.
|
||||||
let mut select = normal.clone();
|
let mut select = normal.clone();
|
||||||
select.extend(
|
select.0.extend(
|
||||||
hashmap!(
|
hashmap!(
|
||||||
key!('h') => Command::extend_char_left,
|
key!('h') => Command::extend_char_left,
|
||||||
key!('j') => Command::extend_line_down,
|
key!('j') => Command::extend_line_down,
|
||||||
|
@ -321,7 +315,7 @@ impl Default for Keymaps {
|
||||||
// TODO: select could be normal mode with some bindings merged over
|
// TODO: select could be normal mode with some bindings merged over
|
||||||
Mode::Normal => normal,
|
Mode::Normal => normal,
|
||||||
Mode::Select => select,
|
Mode::Select => select,
|
||||||
Mode::Insert => hashmap!(
|
Mode::Insert => Keymap(hashmap!(
|
||||||
key!(Esc) => Command::normal_mode as Command,
|
key!(Esc) => Command::normal_mode as Command,
|
||||||
key!(Backspace) => Command::delete_char_backward,
|
key!(Backspace) => Command::delete_char_backward,
|
||||||
key!(Delete) => Command::delete_char_forward,
|
key!(Delete) => Command::delete_char_forward,
|
||||||
|
@ -337,7 +331,309 @@ impl Default for Keymaps {
|
||||||
key!(End) => Command::move_line_end,
|
key!(End) => Command::move_line_end,
|
||||||
ctrl!('x') => Command::completion,
|
ctrl!('x') => Command::completion,
|
||||||
ctrl!('w') => Command::delete_word_backward,
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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'),
|
||||||
|
modifiers: KeyModifiers::NONE
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
str::parse::<RepresentableKeyEvent>("F12").unwrap(),
|
||||||
|
RepresentableKeyEvent(KeyEvent {
|
||||||
|
code: KeyCode::F(12),
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
use anyhow::{Context, Error, Result};
|
|
||||||
use helix_term::application::Application;
|
use helix_term::application::Application;
|
||||||
use helix_term::args::Args;
|
use helix_term::args::Args;
|
||||||
use helix_term::config::Config;
|
use helix_term::config::Config;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
|
||||||
fn setup_logging(logpath: PathBuf, verbosity: u64) -> Result<()> {
|
fn setup_logging(logpath: PathBuf, verbosity: u64) -> Result<()> {
|
||||||
let mut base_config = fern::Dispatch::new();
|
let mut base_config = fern::Dispatch::new();
|
||||||
|
|
||||||
|
@ -88,11 +89,12 @@ FLAGS:
|
||||||
std::fs::create_dir_all(&conf_dir).ok();
|
std::fs::create_dir_all(&conf_dir).ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
let config = match std::fs::read_to_string(conf_dir.join("config.toml")) {
|
let config = std::fs::read_to_string(conf_dir.join("config.toml"))
|
||||||
Ok(config) => toml::from_str(&config)?,
|
.ok()
|
||||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Config::default(),
|
.map(|s| toml::from_str(&s))
|
||||||
Err(err) => return Err(Error::new(err)),
|
.transpose()?
|
||||||
};
|
.or_else(|| Some(Config::default()))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
setup_logging(logpath, args.verbosity).context("failed to initialize logging")?;
|
setup_logging(logpath, args.verbosity).context("failed to initialize logging")?;
|
||||||
|
|
||||||
|
|
|
@ -11,13 +11,12 @@ use helix_core::{
|
||||||
syntax::{self, HighlightEvent},
|
syntax::{self, HighlightEvent},
|
||||||
Position, Range,
|
Position, Range,
|
||||||
};
|
};
|
||||||
use helix_view::input::{KeyCode, KeyEvent, KeyModifiers};
|
|
||||||
use helix_view::{document::Mode, Document, Editor, Theme, View};
|
use helix_view::{document::Mode, Document, Editor, Theme, View};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
cursor,
|
cursor,
|
||||||
event::{read, Event, EventStream},
|
event::{read, Event, EventStream, KeyCode, KeyEvent, KeyModifiers},
|
||||||
};
|
};
|
||||||
use tui::{
|
use tui::{
|
||||||
backend::CrosstermBackend,
|
backend::CrosstermBackend,
|
||||||
|
@ -608,8 +607,7 @@ impl Component for EditorView {
|
||||||
cx.editor.resize(Rect::new(0, 0, width, height - 1));
|
cx.editor.resize(Rect::new(0, 0, width, height - 1));
|
||||||
EventResult::Consumed(None)
|
EventResult::Consumed(None)
|
||||||
}
|
}
|
||||||
Event::Key(key) => {
|
Event::Key(mut key) => {
|
||||||
let mut key = KeyEvent::from(key);
|
|
||||||
canonicalize_key(&mut key);
|
canonicalize_key(&mut key);
|
||||||
// clear status
|
// clear status
|
||||||
cx.editor.status_msg = None;
|
cx.editor.status_msg = None;
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
use anyhow::{anyhow, Context, Error};
|
use anyhow::{anyhow, Context, Error};
|
||||||
use serde::de::{self, Deserialize, Deserializer};
|
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::path::{Component, Path, PathBuf};
|
use std::path::{Component, Path, PathBuf};
|
||||||
|
@ -17,6 +15,8 @@ use helix_core::{
|
||||||
|
|
||||||
use crate::{DocumentId, ViewId};
|
use crate::{DocumentId, ViewId};
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
pub enum Mode {
|
pub enum Mode {
|
||||||
Normal,
|
Normal,
|
||||||
|
@ -24,40 +24,6 @@ pub enum Mode {
|
||||||
Insert,
|
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)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
pub enum IndentStyle {
|
pub enum IndentStyle {
|
||||||
Tabs,
|
Tabs,
|
||||||
|
@ -122,6 +88,29 @@ 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)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Like std::mem::replace() except it allows the replacement value to be mapped from the
|
/// Like std::mem::replace() except it allows the replacement value to be mapped from the
|
||||||
/// original value.
|
/// original value.
|
||||||
fn take_with<T, F>(mut_ref: &mut T, closure: F)
|
fn take_with<T, F>(mut_ref: &mut T, closure: F)
|
||||||
|
|
|
@ -1,226 +0,0 @@
|
||||||
//! 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());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,16 +3,14 @@ pub mod macros;
|
||||||
|
|
||||||
pub mod document;
|
pub mod document;
|
||||||
pub mod editor;
|
pub mod editor;
|
||||||
pub mod input;
|
|
||||||
pub mod register_selection;
|
pub mod register_selection;
|
||||||
pub mod theme;
|
pub mod theme;
|
||||||
pub mod tree;
|
pub mod tree;
|
||||||
pub mod view;
|
pub mod view;
|
||||||
|
|
||||||
slotmap::new_key_type! {
|
use slotmap::new_key_type;
|
||||||
pub struct DocumentId;
|
new_key_type! { pub struct DocumentId; }
|
||||||
pub struct ViewId;
|
new_key_type! { pub struct ViewId; }
|
||||||
}
|
|
||||||
|
|
||||||
pub use document::Document;
|
pub use document::Document;
|
||||||
pub use editor::Editor;
|
pub use editor::Editor;
|
||||||
|
|
Loading…
Add table
Reference in a new issue