Compare commits
2 Commits
21adbec2ce
...
d1fcf0ac89
Author | SHA1 | Date |
---|---|---|
emilis | d1fcf0ac89 | |
emilis | 7498338b9e |
|
@ -257,6 +257,15 @@ version = "0.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
|
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clipboard-win"
|
||||||
|
version = "5.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "12f9a0700e0127ba15d1d52dd742097f821cd9c65939303a44d970465040a297"
|
||||||
|
dependencies = [
|
||||||
|
"error-code",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cnx"
|
name = "cnx"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
|
@ -309,6 +318,12 @@ version = "1.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
|
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "endian-type"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "env_logger"
|
name = "env_logger"
|
||||||
version = "0.10.2"
|
version = "0.10.2"
|
||||||
|
@ -338,6 +353,23 @@ dependencies = [
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "error-code"
|
||||||
|
version = "3.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a0474425d51df81997e2f90a21591180b38eccf27292d755f3e30750225c175b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fd-lock"
|
||||||
|
version = "4.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7e5768da2206272c81ef0b5e951a41862938a6070da63bcea197899942d3b947"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"rustix",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures"
|
name = "futures"
|
||||||
version = "0.3.30"
|
version = "0.3.30"
|
||||||
|
@ -553,6 +585,7 @@ dependencies = [
|
||||||
"paste",
|
"paste",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
"pretty_env_logger",
|
"pretty_env_logger",
|
||||||
|
"rustyline",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"strum",
|
"strum",
|
||||||
|
@ -685,6 +718,26 @@ dependencies = [
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nibble_vec"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43"
|
||||||
|
dependencies = [
|
||||||
|
"smallvec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nix"
|
||||||
|
version = "0.27.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.4.2",
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.18"
|
version = "0.2.18"
|
||||||
|
@ -877,6 +930,16 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "radix_trie"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd"
|
||||||
|
dependencies = [
|
||||||
|
"endian-type",
|
||||||
|
"nibble_vec",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.10.3"
|
version = "1.10.3"
|
||||||
|
@ -931,6 +994,28 @@ version = "1.0.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
|
checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustyline"
|
||||||
|
version = "13.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "02a2d683a4ac90aeef5b1013933f6d977bd37d51ff3f4dad829d4931a7e6be86"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.4.2",
|
||||||
|
"cfg-if",
|
||||||
|
"clipboard-win",
|
||||||
|
"fd-lock",
|
||||||
|
"home",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"memchr",
|
||||||
|
"nix",
|
||||||
|
"radix_trie",
|
||||||
|
"unicode-segmentation",
|
||||||
|
"unicode-width",
|
||||||
|
"utf8parse",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.17"
|
version = "1.0.17"
|
||||||
|
@ -1189,6 +1274,18 @@ version = "1.0.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-segmentation"
|
||||||
|
version = "1.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.1.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf8parse"
|
name = "utf8parse"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
|
|
@ -18,6 +18,7 @@ which = "6.0.0"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
pretty_env_logger = "0.5"
|
pretty_env_logger = "0.5"
|
||||||
cnx = { git = "https://github.com/mjkillough/cnx.git", rev = "7845d99baa296901171c083db61588a62f9a8b34" }
|
cnx = { git = "https://github.com/mjkillough/cnx.git", rev = "7845d99baa296901171c083db61588a62f9a8b34" }
|
||||||
|
rustyline = "13.0.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pretty_assertions = "1.4.0"
|
pretty_assertions = "1.4.0"
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
use clap::Subcommand;
|
||||||
|
use rustyline::{error::ReadlineError, DefaultEditor};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use crate::hlwm::{
|
||||||
|
command::{CommandParseError, HlwmCommand},
|
||||||
|
key::{Key, KeyParseError},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Subcommand, Debug, Clone)]
|
||||||
|
pub enum Add {
|
||||||
|
/// Add a new keybind to the existing hlctl config
|
||||||
|
#[clap(arg_required_else_help = true)]
|
||||||
|
Keybind {
|
||||||
|
#[arg(short, long)]
|
||||||
|
interactive: bool,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Error)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("readline error: [{0}]")]
|
||||||
|
ReadlineError(String),
|
||||||
|
#[error("key parse error: [{0}]")]
|
||||||
|
KeyParseError(#[from] KeyParseError),
|
||||||
|
#[error("could not parse command: [{0}]")]
|
||||||
|
CommandParseError(#[from] CommandParseError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ReadlineError> for Error {
|
||||||
|
fn from(value: ReadlineError) -> Self {
|
||||||
|
Self::ReadlineError(value.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add {
|
||||||
|
pub fn run(self) -> Result<(), Error> {
|
||||||
|
match self {
|
||||||
|
Add::Keybind { interactive } => {
|
||||||
|
if interactive {
|
||||||
|
keybind_interactive()
|
||||||
|
} else {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn keybind_interactive() -> Result<(), Error> {
|
||||||
|
let mut read = DefaultEditor::new()?;
|
||||||
|
let keys = read
|
||||||
|
.readline("Keys (Separated by spaces, tabs, +, or -): ")?
|
||||||
|
.trim()
|
||||||
|
.split([' ', '\t', '+', '-'])
|
||||||
|
.map(|l| l.trim())
|
||||||
|
.filter(|l| !l.is_empty())
|
||||||
|
.map(|k| Key::from_str_case_insensitive(k))
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
|
let command = {
|
||||||
|
let read = read.readline("Command (Arguments separated by tabs or spaces): ")?;
|
||||||
|
let mut args = read
|
||||||
|
.trim()
|
||||||
|
.split([' ', '\t'])
|
||||||
|
.map(|a| a.trim())
|
||||||
|
.filter(|a| !a.is_empty())
|
||||||
|
.map(|a| a.to_string());
|
||||||
|
match args.next() {
|
||||||
|
Some(cmd) => HlwmCommand::from_raw_parts(&cmd, args.collect())?,
|
||||||
|
None => {
|
||||||
|
println!("No command provided");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("{keys:?} -> {command:?}");
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -960,6 +960,9 @@ mod test {
|
||||||
value: Attribute::Color(Color::X11(X11Color::NavajoWhite3)),
|
value: Attribute::Color(Color::X11(X11Color::NavajoWhite3)),
|
||||||
});
|
});
|
||||||
let cfg_serialized = cfg.serialize().unwrap();
|
let cfg_serialized = cfg.serialize().unwrap();
|
||||||
|
println!("--> Config <--");
|
||||||
|
println!("{cfg_serialized}");
|
||||||
|
println!("--> Config <--");
|
||||||
let parsed_cfg = Config::deserialize(&cfg_serialized).unwrap();
|
let parsed_cfg = Config::deserialize(&cfg_serialized).unwrap();
|
||||||
|
|
||||||
assert_eq!(cfg, parsed_cfg,)
|
assert_eq!(cfg, parsed_cfg,)
|
||||||
|
|
|
@ -272,7 +272,7 @@ impl Default for HlwmCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Clone, Error)]
|
||||||
pub enum CommandParseError {
|
pub enum CommandParseError {
|
||||||
#[error("unknown command [{0}]")]
|
#[error("unknown command [{0}]")]
|
||||||
UnknownCommand(String),
|
UnknownCommand(String),
|
||||||
|
@ -287,11 +287,17 @@ pub enum CommandParseError {
|
||||||
#[error("command execution error: [{0}]")]
|
#[error("command execution error: [{0}]")]
|
||||||
CommandError(#[from] CommandError),
|
CommandError(#[from] CommandError),
|
||||||
#[error("string utf8 error")]
|
#[error("string utf8 error")]
|
||||||
StringUtf8Error(#[from] FromUtf8Error),
|
StringUtf8Error(String),
|
||||||
#[error("parsing string value error: [{0}]")]
|
#[error("parsing string value error: [{0}]")]
|
||||||
StringParseError(#[from] StringParseError),
|
StringParseError(#[from] StringParseError),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<FromUtf8Error> for CommandParseError {
|
||||||
|
fn from(value: FromUtf8Error) -> Self {
|
||||||
|
Self::StringUtf8Error(value.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl serde::de::Expected for CommandParseError {
|
impl serde::de::Expected for CommandParseError {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
write!(f, "expected valid command string. Got error: {self}")
|
write!(f, "expected valid command string. Got error: {self}")
|
||||||
|
|
216
src/hlwm/key.rs
216
src/hlwm/key.rs
|
@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::split;
|
use crate::{logerr::UnwrapLog, split};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
command::{CommandParseError, HlwmCommand},
|
command::{CommandParseError, HlwmCommand},
|
||||||
|
@ -43,6 +43,21 @@ pub enum Key {
|
||||||
Mouse(MouseButton),
|
Mouse(MouseButton),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn title_case(s: &str) -> String {
|
||||||
|
let mut chars = s.chars();
|
||||||
|
let mut out_chars = Vec::with_capacity(s.len());
|
||||||
|
|
||||||
|
if let Some(first) = chars.next() {
|
||||||
|
out_chars.push(first.to_ascii_uppercase());
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some(c) = chars.next() {
|
||||||
|
out_chars.push(c.to_ascii_lowercase());
|
||||||
|
}
|
||||||
|
|
||||||
|
out_chars.into_iter().collect()
|
||||||
|
}
|
||||||
|
|
||||||
impl Key {
|
impl Key {
|
||||||
pub fn is_standard(&self) -> bool {
|
pub fn is_standard(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
|
@ -62,6 +77,88 @@ impl Key {
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_str_case_insensitive(s: &str) -> Result<Self, KeyParseError> {
|
||||||
|
Key::from_str(&title_case(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the name for this key as it should appear in the config
|
||||||
|
pub fn config_name(&self) -> String {
|
||||||
|
self.aliases()
|
||||||
|
.first()
|
||||||
|
.expect_log("all keys should have at least one alias")
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a list of aliases for the key
|
||||||
|
pub fn aliases(&self) -> Vec<String> {
|
||||||
|
match self {
|
||||||
|
Key::Mod1Alt => vec!["Alt".into(), "Mod1".into()],
|
||||||
|
Key::Mod4Super => vec!["Super".into(), "Mod4".into()],
|
||||||
|
Key::Return => vec!["Return".into(), "Enter".into()],
|
||||||
|
Key::Shift => vec!["Shift".into()],
|
||||||
|
Key::Tab => vec!["Tab".into()],
|
||||||
|
Key::Left => vec!["Left".into()],
|
||||||
|
Key::Right => vec!["Right".into()],
|
||||||
|
Key::Up => vec!["Up".into()],
|
||||||
|
Key::Down => vec!["Down".into()],
|
||||||
|
Key::Space => vec!["Space".into()],
|
||||||
|
Key::Control => vec!["Ctrl".into(), "Ctl".into(), "Control".into()],
|
||||||
|
Key::Backtick => vec!["Backtick".into(), "Grave".into()],
|
||||||
|
Key::F1 => vec!["F1".into()],
|
||||||
|
Key::F2 => vec!["F2".into()],
|
||||||
|
Key::F3 => vec!["F3".into()],
|
||||||
|
Key::F4 => vec!["F4".into()],
|
||||||
|
Key::F5 => vec!["F5".into()],
|
||||||
|
Key::F6 => vec!["F6".into()],
|
||||||
|
Key::F7 => vec!["F7".into()],
|
||||||
|
Key::F8 => vec!["F8".into()],
|
||||||
|
Key::F9 => vec!["F9".into()],
|
||||||
|
Key::F10 => vec!["F10".into()],
|
||||||
|
Key::F11 => vec!["F11".into()],
|
||||||
|
Key::F12 => vec!["F12".into()],
|
||||||
|
Key::Home => vec!["Home".into()],
|
||||||
|
Key::Delete => vec!["Delete".into(), "Del".into()],
|
||||||
|
Key::Char(c) => vec![c.to_ascii_lowercase().to_string()],
|
||||||
|
Key::Mouse(m) => vec![m.to_string()],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Key {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let data = match self {
|
||||||
|
Key::Return => "Return".to_string(),
|
||||||
|
Key::Shift => "Shift".to_string(),
|
||||||
|
Key::Tab => "Tab".to_string(),
|
||||||
|
Key::Left => "Left".to_string(),
|
||||||
|
Key::Right => "Right".to_string(),
|
||||||
|
Key::Up => "Up".to_string(),
|
||||||
|
Key::Down => "Down".to_string(),
|
||||||
|
Key::Space => "space".to_string(),
|
||||||
|
Key::Control => "Control".to_string(),
|
||||||
|
Key::Backtick => "grave".to_string(),
|
||||||
|
Key::Char(c) => c.to_string(),
|
||||||
|
Key::Mouse(m) => m.to_string(),
|
||||||
|
Key::F1 => "F1".to_string(),
|
||||||
|
Key::F2 => "F2".to_string(),
|
||||||
|
Key::F3 => "F3".to_string(),
|
||||||
|
Key::F4 => "F4".to_string(),
|
||||||
|
Key::F5 => "F5".to_string(),
|
||||||
|
Key::F6 => "F6".to_string(),
|
||||||
|
Key::F7 => "F7".to_string(),
|
||||||
|
Key::F8 => "F8".to_string(),
|
||||||
|
Key::F9 => "F9".to_string(),
|
||||||
|
Key::F10 => "F10".to_string(),
|
||||||
|
Key::F11 => "F11".to_string(),
|
||||||
|
Key::F12 => "F12".to_string(),
|
||||||
|
Key::Home => "Home".to_string(),
|
||||||
|
Key::Delete => "Delete".to_string(),
|
||||||
|
Key::Mod1Alt => "Mod1".to_string(),
|
||||||
|
Key::Mod4Super => "Mod4".to_string(),
|
||||||
|
};
|
||||||
|
f.write_str(&data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<MouseButton> for Key {
|
impl From<MouseButton> for Key {
|
||||||
|
@ -81,7 +178,7 @@ impl Serialize for Key {
|
||||||
where
|
where
|
||||||
S: serde::Serializer,
|
S: serde::Serializer,
|
||||||
{
|
{
|
||||||
serializer.serialize_str(&self.to_string())
|
serializer.serialize_str(&self.config_name())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,7 +194,7 @@ impl<'de> Deserialize<'de> for Key {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let str_val: String = Deserialize::deserialize(deserializer)?;
|
let str_val: String = Deserialize::deserialize(deserializer)?;
|
||||||
Ok(Self::from_str(&str_val).map_err(|_| {
|
Ok(Self::from_str_case_insensitive(&str_val).map_err(|_| {
|
||||||
serde::de::Error::invalid_value(serde::de::Unexpected::Str(&str_val), &ExpectedKey)
|
serde::de::Error::invalid_value(serde::de::Unexpected::Str(&str_val), &ExpectedKey)
|
||||||
})?)
|
})?)
|
||||||
}
|
}
|
||||||
|
@ -112,14 +209,16 @@ impl FromStr for Key {
|
||||||
Ok(Self::Mouse(MouseButton::from_str(s)?))
|
Ok(Self::Mouse(MouseButton::from_str(s)?))
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
if let Some(key) = Self::iter().into_iter().find(|key| key.to_string() == s) {
|
if let Some(key) = Self::iter().into_iter().find(|key| match key {
|
||||||
match key {
|
Key::Char(_) | Key::Mouse(_) => false,
|
||||||
Key::Char(_) | Key::Mouse(_) => (),
|
_ => key.aliases().contains(&s.to_string()),
|
||||||
_ => return Ok(key),
|
}) {
|
||||||
}
|
return Ok(key);
|
||||||
}
|
}
|
||||||
if s.len() == 1 {
|
if s.len() == 1 {
|
||||||
Ok(Self::Char(s.chars().next().unwrap()))
|
Ok(Self::Char(
|
||||||
|
s.chars().next().unwrap_log().to_ascii_lowercase(),
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
Err(KeyParseError::ExpectedCharKey(s.to_string()))
|
Err(KeyParseError::ExpectedCharKey(s.to_string()))
|
||||||
}
|
}
|
||||||
|
@ -170,42 +269,6 @@ impl Display for MouseButton {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Key {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
let data = match self {
|
|
||||||
Key::Return => "Return".to_string(),
|
|
||||||
Key::Shift => "Shift".to_string(),
|
|
||||||
Key::Tab => "Tab".to_string(),
|
|
||||||
Key::Left => "Left".to_string(),
|
|
||||||
Key::Right => "Right".to_string(),
|
|
||||||
Key::Up => "Up".to_string(),
|
|
||||||
Key::Down => "Down".to_string(),
|
|
||||||
Key::Space => "space".to_string(),
|
|
||||||
Key::Control => "Control".to_string(),
|
|
||||||
Key::Backtick => "grave".to_string(),
|
|
||||||
Key::Char(c) => c.to_string(),
|
|
||||||
Key::Mouse(m) => m.to_string(),
|
|
||||||
Key::F1 => "F1".to_string(),
|
|
||||||
Key::F2 => "F2".to_string(),
|
|
||||||
Key::F3 => "F3".to_string(),
|
|
||||||
Key::F4 => "F4".to_string(),
|
|
||||||
Key::F5 => "F5".to_string(),
|
|
||||||
Key::F6 => "F6".to_string(),
|
|
||||||
Key::F7 => "F7".to_string(),
|
|
||||||
Key::F8 => "F8".to_string(),
|
|
||||||
Key::F9 => "F9".to_string(),
|
|
||||||
Key::F10 => "F10".to_string(),
|
|
||||||
Key::F11 => "F11".to_string(),
|
|
||||||
Key::F12 => "F12".to_string(),
|
|
||||||
Key::Home => "Home".to_string(),
|
|
||||||
Key::Delete => "Delete".to_string(),
|
|
||||||
Key::Mod1Alt => "Mod1".to_string(),
|
|
||||||
Key::Mod4Super => "Mod4".to_string(),
|
|
||||||
};
|
|
||||||
f.write_str(&data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, strum::Display, strum::EnumIter, PartialEq)]
|
#[derive(Debug, Clone, strum::Display, strum::EnumIter, PartialEq)]
|
||||||
#[strum(serialize_all = "snake_case")]
|
#[strum(serialize_all = "snake_case")]
|
||||||
pub enum MousebindAction {
|
pub enum MousebindAction {
|
||||||
|
@ -489,9 +552,10 @@ mod test {
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
use crate::hlwm::command::HlwmCommand;
|
use crate::{hlwm::command::HlwmCommand, logerr::UnwrapLog};
|
||||||
|
|
||||||
use super::{Key, MousebindAction};
|
use super::{Key, MousebindAction};
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct KeyWrapper {
|
pub struct KeyWrapper {
|
||||||
|
@ -501,8 +565,8 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn key_serialize_deserialize() {
|
fn key_serialize_deserialize() {
|
||||||
for (original_key, wrapper) in Key::iter().map(|key| (key, KeyWrapper { key })) {
|
for (original_key, wrapper) in Key::iter().map(|key| (key, KeyWrapper { key })) {
|
||||||
let wrapper_str = toml::to_string_pretty(&wrapper).unwrap();
|
let wrapper_str = toml::to_string_pretty(&wrapper).expect_log("serialize");
|
||||||
let parsed: KeyWrapper = toml::from_str(&wrapper_str).unwrap();
|
let parsed: KeyWrapper = toml::from_str(&wrapper_str).expect_log("deserialize");
|
||||||
assert_eq!(original_key, parsed.key);
|
assert_eq!(original_key, parsed.key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -527,4 +591,60 @@ mod test {
|
||||||
assert_eq!(original_key, parsed.action);
|
assert_eq!(original_key, parsed.action);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn case_random<S: Into<String>>(s: S) -> Vec<String> {
|
||||||
|
let s = s.into();
|
||||||
|
let counts: usize = s.chars().map(|c| c.is_ascii_alphabetic() as usize).sum();
|
||||||
|
if counts < 2 {
|
||||||
|
return vec![s.to_string()];
|
||||||
|
}
|
||||||
|
let mut variants: Vec<String> = Vec::with_capacity(counts + 2);
|
||||||
|
let mut working = Vec::new();
|
||||||
|
|
||||||
|
for idx in 0..counts {
|
||||||
|
let mut c_idx = 0;
|
||||||
|
for c in s.chars() {
|
||||||
|
if !c.is_ascii_alphabetic() {
|
||||||
|
working.push(c);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if c_idx == idx {
|
||||||
|
working.push(c.to_ascii_uppercase());
|
||||||
|
} else {
|
||||||
|
working.push(c.to_ascii_lowercase());
|
||||||
|
}
|
||||||
|
c_idx += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
variants.push(working.into_iter().collect());
|
||||||
|
working = Vec::new();
|
||||||
|
}
|
||||||
|
variants.push(s.to_ascii_lowercase());
|
||||||
|
variants.push(s.to_ascii_uppercase());
|
||||||
|
|
||||||
|
variants
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn key_from_case_insensitive() {
|
||||||
|
let test_cases = Key::iter().map(|key| {
|
||||||
|
let strings = key
|
||||||
|
.aliases()
|
||||||
|
.into_iter()
|
||||||
|
.map(|a| case_random(a))
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
(strings, key)
|
||||||
|
});
|
||||||
|
|
||||||
|
for (strings, key) in test_cases {
|
||||||
|
for case in strings {
|
||||||
|
println!("comparing str: [{case}] with key: [{key}]");
|
||||||
|
let parsed = Key::from_str_case_insensitive(&case).expect(&format!(
|
||||||
|
"key [{key}] parse from insensitive string [{case}] failed"
|
||||||
|
));
|
||||||
|
assert_eq!(key, parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,20 @@ use std::{error::Error, fmt::Debug};
|
||||||
|
|
||||||
use log::{debug, error};
|
use log::{debug, error};
|
||||||
|
|
||||||
|
// So we still get a useful panic when logs are filtered
|
||||||
|
macro_rules! error_and_panic {
|
||||||
|
($($arg:tt)*) => {
|
||||||
|
log::error!($($arg)*);
|
||||||
|
panic!($($arg)*);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub trait UnwrapLog {
|
pub trait UnwrapLog {
|
||||||
type Target;
|
type Target;
|
||||||
|
|
||||||
fn unwrap_or_log(self, default: Self::Target) -> Self::Target;
|
fn unwrap_or_log(self, default: Self::Target) -> Self::Target;
|
||||||
|
fn unwrap_log(self) -> Self::Target;
|
||||||
|
fn expect_log(self, expect: &str) -> Self::Target;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, E> UnwrapLog for Result<T, E>
|
impl<T, E> UnwrapLog for Result<T, E>
|
||||||
|
@ -28,6 +38,26 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn unwrap_log(self) -> Self::Target {
|
||||||
|
match self {
|
||||||
|
Ok(target) => target,
|
||||||
|
Err(err) => {
|
||||||
|
error_and_panic!("called `Result::unwrap_log()` on an `Err` value: {err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_log(self, expect: &str) -> Self::Target {
|
||||||
|
match self {
|
||||||
|
Ok(target) => target,
|
||||||
|
Err(err) => {
|
||||||
|
error_and_panic!(
|
||||||
|
"[{expect}] called `Result::expect_log()` on an `Err` value: {err}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> UnwrapLog for Option<T>
|
impl<T> UnwrapLog for Option<T>
|
||||||
|
@ -49,4 +79,22 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn unwrap_log(self) -> Self::Target {
|
||||||
|
match self {
|
||||||
|
Some(target) => target,
|
||||||
|
None => {
|
||||||
|
error_and_panic!("called `Option::unwrap_log()` on a `None` value");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_log(self, expect: &str) -> Self::Target {
|
||||||
|
match self {
|
||||||
|
Some(target) => target,
|
||||||
|
None => {
|
||||||
|
error_and_panic!("[{expect}] called `Option::expect_log()` on an `None` value");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
17
src/main.rs
17
src/main.rs
|
@ -1,10 +1,12 @@
|
||||||
#![feature(macro_metavar_expr)]
|
#![feature(macro_metavar_expr)]
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use add::Add;
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use config::Config;
|
use config::Config;
|
||||||
use log::{error, info};
|
use log::{error, info, LevelFilter};
|
||||||
|
|
||||||
|
mod add;
|
||||||
pub mod cmd;
|
pub mod cmd;
|
||||||
mod config;
|
mod config;
|
||||||
pub mod environ;
|
pub mod environ;
|
||||||
|
@ -41,10 +43,16 @@ enum HlctlCommand {
|
||||||
},
|
},
|
||||||
/// Start the top panel
|
/// Start the top panel
|
||||||
Panel,
|
Panel,
|
||||||
|
/// Add an element to the existing hlctl config
|
||||||
|
#[command(subcommand)]
|
||||||
|
Add(Add),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
pretty_env_logger::init();
|
pretty_env_logger::formatted_builder()
|
||||||
|
.filter_module("rustyline", LevelFilter::Error)
|
||||||
|
.parse_default_env()
|
||||||
|
.init();
|
||||||
|
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
match args.command {
|
match args.command {
|
||||||
|
@ -56,6 +64,11 @@ fn main() {
|
||||||
error!("panel: {err}");
|
error!("panel: {err}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
HlctlCommand::Add(add) => {
|
||||||
|
if let Err(err) = add.run() {
|
||||||
|
error!("add: {err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue