use serde::{Deserialize, Serialize};
use crate::msg::Msg;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum KeyCode {
Char(char),
Enter,
Backspace,
Delete,
Left,
Right,
Up,
Down,
Home,
End,
PageUp,
PageDown,
Tab,
BackTab,
Esc,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Mods {
#[serde(default)]
pub shift: bool,
#[serde(default)]
pub ctrl: bool,
#[serde(default)]
pub alt: bool,
#[serde(default)]
pub cmd: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Key {
pub code: KeyCode,
#[serde(default)]
pub mods: Mods,
}
impl Key {
pub fn plain(code: KeyCode) -> Key {
Key {
code,
mods: Mods::default(),
}
}
}
pub fn keymap(key: &Key) -> Option<Msg> {
crate::commands::lookup(false, key)
}
pub fn outline_keymap(key: &Key) -> Option<Msg> {
crate::commands::lookup(true, key)
}
pub fn keymap_for(outline: bool, key: &Key) -> Option<Msg> {
crate::commands::lookup(outline, key)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ScriptItem {
Key(Key),
Wait(u64),
}
pub fn parse_keys(script: &str) -> Result<Vec<ScriptItem>, String> {
let chars: Vec<char> = script.chars().collect();
let mut items = Vec::new();
let mut i = 0;
while i < chars.len() {
let c = chars[i];
if c == '<' {
if let Some(len) = chars[i + 1..].iter().position(|&c| c == '>') {
let token: String = chars[i + 1..i + 1 + len].iter().collect();
if !token.is_empty() {
items.push(parse_token(&token)?);
i += len + 2;
continue;
}
}
}
let code = match c {
'\n' | '\r' => KeyCode::Enter,
'\t' => KeyCode::Tab,
c => KeyCode::Char(c),
};
items.push(ScriptItem::Key(Key::plain(code)));
i += 1;
}
Ok(items)
}
fn parse_token(token: &str) -> Result<ScriptItem, String> {
if let Some(ms) = token.strip_prefix("wait:") {
return ms
.parse()
.map(ScriptItem::Wait)
.map_err(|_| format!("bad wait in <{token}>"));
}
let mut mods = Mods::default();
let mut rest = token;
loop {
let lower = rest.to_ascii_lowercase();
if rest.len() > 2 && rest.as_bytes()[1] == b'-' {
match &lower[..1] {
"s" => mods.shift = true,
"c" => mods.ctrl = true,
"a" | "m" => mods.alt = true,
"d" => mods.cmd = true,
_ => return Err(format!("unknown modifier in <{token}>")),
}
rest = &rest[2..];
continue;
}
break;
}
let name = rest.to_ascii_lowercase();
let code = match name.as_str() {
"cr" | "enter" | "ret" | "return" => KeyCode::Enter,
"bs" | "backspace" => KeyCode::Backspace,
"del" | "delete" => KeyCode::Delete,
"tab" if mods.shift => {
mods.shift = false;
KeyCode::BackTab
}
"tab" => KeyCode::Tab,
"esc" => KeyCode::Esc,
"space" => KeyCode::Char(' '),
"left" => KeyCode::Left,
"right" => KeyCode::Right,
"up" => KeyCode::Up,
"down" => KeyCode::Down,
"home" => KeyCode::Home,
"end" => KeyCode::End,
"pgup" | "pageup" => KeyCode::PageUp,
"pgdn" | "pagedown" => KeyCode::PageDown,
"lt" => KeyCode::Char('<'),
"gt" => KeyCode::Char('>'),
_ => {
let mut cs = rest.chars();
match (cs.next(), cs.next()) {
(Some(c), None) => KeyCode::Char(c),
_ => return Err(format!("unknown key <{token}>")),
}
}
};
Ok(ScriptItem::Key(Key { code, mods }))
}
pub fn script_to_msgs(script: &str, now_ms: u64) -> Result<Vec<Msg>, String> {
script_to_msgs_for(script, now_ms, false)
}
pub fn script_to_msgs_for(script: &str, now_ms: u64, outline: bool) -> Result<Vec<Msg>, String> {
let mut now = now_ms;
let mut msgs = Vec::new();
for item in parse_keys(script)? {
match item {
ScriptItem::Key(key) => msgs.extend(keymap_for(outline, &key)),
ScriptItem::Wait(ms) => {
now += ms;
msgs.push(Msg::Tick { now_ms: now });
}
}
}
Ok(msgs)
}