Model and (de-)serialize key bindings
This commit is contained in:
parent
abedc5f194
commit
3fbb9127a6
7 changed files with 259 additions and 3 deletions
44
Cargo.lock
generated
44
Cargo.lock
generated
|
|
@ -283,6 +283,12 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cove-input"
|
name = "cove-input"
|
||||||
version = "0.6.1"
|
version = "0.6.1"
|
||||||
|
dependencies = [
|
||||||
|
"crossterm",
|
||||||
|
"serde",
|
||||||
|
"serde_either",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cove-macro"
|
name = "cove-macro"
|
||||||
|
|
@ -705,6 +711,15 @@ dependencies = [
|
||||||
"windows-sys 0.45.0",
|
"windows-sys 0.45.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num_cpus"
|
name = "num_cpus"
|
||||||
version = "1.15.0"
|
version = "1.15.0"
|
||||||
|
|
@ -736,6 +751,15 @@ version = "0.1.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ordered-float"
|
||||||
|
version = "2.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
|
|
@ -1029,6 +1053,16 @@ dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde-value"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c"
|
||||||
|
dependencies = [
|
||||||
|
"ordered-float",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.159"
|
version = "1.0.159"
|
||||||
|
|
@ -1040,6 +1074,16 @@ dependencies = [
|
||||||
"syn 2.0.15",
|
"syn 2.0.15",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_either"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "689643f4e7826ffcd227d2cc166bfdf5869750191ffe9fd593531e6ba351f2fb"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"serde-value",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.95"
|
version = "1.0.95"
|
||||||
|
|
|
||||||
|
|
@ -6,5 +6,11 @@ members = ["cove", "cove-*"]
|
||||||
version = "0.6.1"
|
version = "0.6.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
[workspace.dependencies]
|
||||||
|
crossterm = "0.26.1"
|
||||||
|
serde = { version = "1.0.159", features = ["derive"] }
|
||||||
|
serde_either = "0.2.1"
|
||||||
|
thiserror = "1.0.40"
|
||||||
|
|
||||||
[profile.dev.package."*"]
|
[profile.dev.package."*"]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
|
|
|
||||||
|
|
@ -6,5 +6,6 @@ edition = { workspace = true }
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cove-macro = { path = "../cove-macro" }
|
cove-macro = { path = "../cove-macro" }
|
||||||
|
|
||||||
serde = { version = "1.0.159", features = ["derive"] }
|
serde = { workspace = true }
|
||||||
|
|
||||||
toml = "0.7.3"
|
toml = "0.7.3"
|
||||||
|
|
|
||||||
|
|
@ -4,3 +4,7 @@ version = { workspace = true }
|
||||||
edition = { workspace = true }
|
edition = { workspace = true }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
crossterm = { workspace = true }
|
||||||
|
serde = { workspace = true }
|
||||||
|
serde_either = { workspace = true }
|
||||||
|
thiserror = { workspace = true }
|
||||||
|
|
|
||||||
198
cove-input/src/keys.rs
Normal file
198
cove-input/src/keys.rs
Normal file
|
|
@ -0,0 +1,198 @@
|
||||||
|
use std::fmt;
|
||||||
|
use std::num::ParseIntError;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||||
|
use serde::{de::Error, Deserialize, Deserializer};
|
||||||
|
use serde::{Serialize, Serializer};
|
||||||
|
use serde_either::SingleOrVec;
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum ParseKeysError {
|
||||||
|
#[error("no key code specified")]
|
||||||
|
NoKeyCode,
|
||||||
|
#[error("unknown key code: {0:?}")]
|
||||||
|
UnknownKeyCode(String),
|
||||||
|
#[error("invalid function key number: {0}")]
|
||||||
|
InvalidFNumber(#[from] ParseIntError),
|
||||||
|
#[error("unknown modifier: {0:?}")]
|
||||||
|
UnknownModifier(String),
|
||||||
|
#[error("modifier {0} conflicts with previous modifier")]
|
||||||
|
ConflictingModifier(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct KeyPress {
|
||||||
|
pub code: KeyCode,
|
||||||
|
pub shift: bool,
|
||||||
|
pub ctrl: bool,
|
||||||
|
pub alt: bool,
|
||||||
|
pub any: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyPress {
|
||||||
|
fn parse_key_code(code: &str) -> Result<Self, ParseKeysError> {
|
||||||
|
let code = match code {
|
||||||
|
"backspace" => KeyCode::Backspace,
|
||||||
|
"enter" => KeyCode::Enter,
|
||||||
|
"left" => KeyCode::Left,
|
||||||
|
"right" => KeyCode::Right,
|
||||||
|
"up" => KeyCode::Up,
|
||||||
|
"down" => KeyCode::Down,
|
||||||
|
"home" => KeyCode::Home,
|
||||||
|
"end" => KeyCode::End,
|
||||||
|
"pageup" => KeyCode::PageUp,
|
||||||
|
"pagedown" => KeyCode::PageDown,
|
||||||
|
"tab" => KeyCode::Tab,
|
||||||
|
"backtab" => KeyCode::BackTab,
|
||||||
|
"delete" => KeyCode::Delete,
|
||||||
|
"insert" => KeyCode::Insert,
|
||||||
|
"esc" => KeyCode::Esc,
|
||||||
|
c if c.starts_with('F') => KeyCode::F(c.strip_prefix('F').unwrap().parse()?),
|
||||||
|
c if c.chars().count() == 1 => KeyCode::Char(c.chars().next().unwrap()),
|
||||||
|
"" => return Err(ParseKeysError::NoKeyCode),
|
||||||
|
c => return Err(ParseKeysError::UnknownKeyCode(c.to_string())),
|
||||||
|
};
|
||||||
|
Ok(Self {
|
||||||
|
code,
|
||||||
|
shift: false,
|
||||||
|
ctrl: false,
|
||||||
|
alt: false,
|
||||||
|
any: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_key_code(code: KeyCode) -> String {
|
||||||
|
match code {
|
||||||
|
KeyCode::Backspace => "backspace".to_string(),
|
||||||
|
KeyCode::Enter => "enter".to_string(),
|
||||||
|
KeyCode::Left => "left".to_string(),
|
||||||
|
KeyCode::Right => "right".to_string(),
|
||||||
|
KeyCode::Up => "up".to_string(),
|
||||||
|
KeyCode::Down => "down".to_string(),
|
||||||
|
KeyCode::Home => "home".to_string(),
|
||||||
|
KeyCode::End => "end".to_string(),
|
||||||
|
KeyCode::PageUp => "pageup".to_string(),
|
||||||
|
KeyCode::PageDown => "pagedown".to_string(),
|
||||||
|
KeyCode::Tab => "tab".to_string(),
|
||||||
|
KeyCode::BackTab => "backtab".to_string(),
|
||||||
|
KeyCode::Delete => "delete".to_string(),
|
||||||
|
KeyCode::Insert => "insert".to_string(),
|
||||||
|
KeyCode::Esc => "esc".to_string(),
|
||||||
|
KeyCode::F(n) => format!("F{n}"),
|
||||||
|
KeyCode::Char(c) => c.to_string(),
|
||||||
|
_ => "unknown".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_modifier(&mut self, modifier: &str) -> Result<(), ParseKeysError> {
|
||||||
|
match modifier {
|
||||||
|
m if self.any => return Err(ParseKeysError::ConflictingModifier(m.to_string())),
|
||||||
|
"shift" if !self.shift => self.shift = true,
|
||||||
|
"ctrl" if !self.ctrl => self.ctrl = true,
|
||||||
|
"alt" if !self.alt => self.alt = true,
|
||||||
|
"any" if !self.shift && !self.ctrl && !self.alt => self.any = true,
|
||||||
|
m @ ("shift" | "ctrl" | "alt" | "any") => {
|
||||||
|
return Err(ParseKeysError::ConflictingModifier(m.to_string()))
|
||||||
|
}
|
||||||
|
m => return Err(ParseKeysError::UnknownModifier(m.to_string())),
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn matches(&self, event: KeyEvent) -> bool {
|
||||||
|
if event.code != self.code {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.any {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let shift = event.modifiers.contains(KeyModifiers::SHIFT);
|
||||||
|
let ctrl = event.modifiers.contains(KeyModifiers::CONTROL);
|
||||||
|
let alt = event.modifiers.contains(KeyModifiers::ALT);
|
||||||
|
self.shift == shift && self.ctrl == ctrl && self.alt == alt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for KeyPress {
|
||||||
|
type Err = ParseKeysError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let mut parts = s.split('+');
|
||||||
|
let code = parts.next_back().ok_or(ParseKeysError::NoKeyCode)?;
|
||||||
|
|
||||||
|
let mut keys = KeyPress::parse_key_code(code)?;
|
||||||
|
for modifier in parts {
|
||||||
|
keys.parse_modifier(modifier)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(keys)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for KeyPress {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let code = Self::display_key_code(self.code);
|
||||||
|
|
||||||
|
let mut segments = vec![];
|
||||||
|
if self.shift {
|
||||||
|
segments.push("Shift");
|
||||||
|
}
|
||||||
|
if self.ctrl {
|
||||||
|
segments.push("Ctrl");
|
||||||
|
}
|
||||||
|
if self.alt {
|
||||||
|
segments.push("Alt");
|
||||||
|
}
|
||||||
|
if self.any {
|
||||||
|
segments.push("Any");
|
||||||
|
}
|
||||||
|
segments.push(&code);
|
||||||
|
|
||||||
|
segments.join("+").fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for KeyPress {
|
||||||
|
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||||
|
format!("{self}").serialize(serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for KeyPress {
|
||||||
|
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||||
|
String::deserialize(deserializer)?
|
||||||
|
.parse()
|
||||||
|
.map_err(|e| D::Error::custom(format!("{e}")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct KeyBinding(Vec<KeyPress>);
|
||||||
|
|
||||||
|
impl KeyBinding {
|
||||||
|
pub fn matches(&self, event: KeyEvent) -> bool {
|
||||||
|
self.0.iter().any(|kp| kp.matches(event))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for KeyBinding {
|
||||||
|
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||||
|
if self.0.len() == 1 {
|
||||||
|
self.0[0].serialize(serializer)
|
||||||
|
} else {
|
||||||
|
self.0.serialize(serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for KeyBinding {
|
||||||
|
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||||
|
Ok(match SingleOrVec::<KeyPress>::deserialize(deserializer)? {
|
||||||
|
SingleOrVec::Single(key) => Self(vec![key]),
|
||||||
|
SingleOrVec::Vec(keys) => Self(keys),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1 +1,3 @@
|
||||||
|
mod keys;
|
||||||
|
|
||||||
|
pub use keys::*;
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,13 @@ edition = { workspace = true }
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cove-config = { path = "../cove-config" }
|
cove-config = { path = "../cove-config" }
|
||||||
|
|
||||||
|
crossterm = { workspace = true }
|
||||||
|
thiserror = { workspace = true }
|
||||||
|
|
||||||
anyhow = "1.0.70"
|
anyhow = "1.0.70"
|
||||||
async-trait = "0.1.68"
|
async-trait = "0.1.68"
|
||||||
clap = { version = "4.2.1", features = ["derive", "deprecated"] }
|
clap = { version = "4.2.1", features = ["derive", "deprecated"] }
|
||||||
cookie = "0.17.0"
|
cookie = "0.17.0"
|
||||||
crossterm = "0.26.1"
|
|
||||||
directories = "5.0.0"
|
directories = "5.0.0"
|
||||||
edit = "0.1.4"
|
edit = "0.1.4"
|
||||||
linkify = "0.9.0"
|
linkify = "0.9.0"
|
||||||
|
|
@ -20,7 +22,6 @@ open = "4.0.1"
|
||||||
parking_lot = "0.12.1"
|
parking_lot = "0.12.1"
|
||||||
rusqlite = { version = "0.29.0", features = ["bundled", "time"] }
|
rusqlite = { version = "0.29.0", features = ["bundled", "time"] }
|
||||||
serde_json = "1.0.95"
|
serde_json = "1.0.95"
|
||||||
thiserror = "1.0.40"
|
|
||||||
tokio = { version = "1.27.0", features = ["full"] }
|
tokio = { version = "1.27.0", features = ["full"] }
|
||||||
unicode-segmentation = "1.10.1"
|
unicode-segmentation = "1.10.1"
|
||||||
unicode-width = "0.1.10"
|
unicode-width = "0.1.10"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue