|
|
|
@ -15,8 +15,6 @@ pub enum Error {
|
|
|
|
|
pub enum ParseError {
|
|
|
|
|
// u32 is the line number where an error occured
|
|
|
|
|
UnknownSymbol(u32),
|
|
|
|
|
MissingCommand(u32),
|
|
|
|
|
CommandWithoutWhitespace(u32),
|
|
|
|
|
InvalidModifier(u32),
|
|
|
|
|
InvalidKeysym(u32),
|
|
|
|
|
}
|
|
|
|
@ -169,6 +167,47 @@ fn parse_contents(contents: String) -> Result<Vec<Hotkey>, Error> {
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
let lines: Vec<&str> = contents.split('\n').collect();
|
|
|
|
|
|
|
|
|
|
// Go through each line, ignore comments and empty lines, mark lines starting with whitespace
|
|
|
|
|
// as commands, and mark the other lines as keysyms. Mark means storing a line's type and the
|
|
|
|
|
// line number in a vector.
|
|
|
|
|
let mut lines_with_types: Vec<(&str, u32)> = Vec::new();
|
|
|
|
|
for (line_number, line) in lines.iter().enumerate() {
|
|
|
|
|
if line.trim().starts_with('#') || line.trim().is_empty() {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if line.starts_with(' ') || line.starts_with('\t') {
|
|
|
|
|
lines_with_types.push(("command", line_number as u32));
|
|
|
|
|
} else {
|
|
|
|
|
lines_with_types.push(("keysym", line_number as u32));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Go through lines_with_types, and add the next line over and over until the current line no
|
|
|
|
|
// longer ends with backslash. (Only if the lines have the same type)
|
|
|
|
|
let mut actual_lines: Vec<(&str, u32, String)> = Vec::new();
|
|
|
|
|
let mut current_line_type = lines_with_types[0].0;
|
|
|
|
|
let mut current_line_number = lines_with_types[0].1;
|
|
|
|
|
let mut current_line_string = String::new();
|
|
|
|
|
for (line_type, line_number) in lines_with_types {
|
|
|
|
|
if line_type != current_line_type {
|
|
|
|
|
current_line_type = line_type;
|
|
|
|
|
current_line_number = line_number;
|
|
|
|
|
current_line_string = String::new();
|
|
|
|
|
}
|
|
|
|
|
current_line_string.push_str(lines[line_number as usize].trim());
|
|
|
|
|
if !current_line_string.ends_with('\\') {
|
|
|
|
|
actual_lines.push((
|
|
|
|
|
current_line_type,
|
|
|
|
|
current_line_number,
|
|
|
|
|
current_line_string.replace("\\", ""),
|
|
|
|
|
));
|
|
|
|
|
current_line_type = line_type;
|
|
|
|
|
current_line_number = line_number;
|
|
|
|
|
current_line_string = String::new();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut hotkeys: Vec<Hotkey> = Vec::new();
|
|
|
|
|
|
|
|
|
|
let mut lines_to_skip: u32 = 0;
|
|
|
|
@ -193,22 +232,6 @@ fn parse_contents(contents: String) -> Result<Vec<Hotkey>, Error> {
|
|
|
|
|
let (keysym, modifiers) =
|
|
|
|
|
parse_keybind(lines[i], real_line_no, &key_to_evdev_key, &mod_to_mod_enum)?;
|
|
|
|
|
|
|
|
|
|
// Error if keybind line is at the very last line
|
|
|
|
|
// ( It's impossible for there to be a command )
|
|
|
|
|
if i >= lines.len() - 1 {
|
|
|
|
|
return Err(Error::InvalidConfig(ParseError::MissingCommand(real_line_no)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Error if the command doesn't start with whitespace
|
|
|
|
|
// OR is not a blank command
|
|
|
|
|
if (!lines[i + 1].starts_with(' ') && !lines[i + 1].starts_with('\t'))
|
|
|
|
|
&& !lines[i + 1].trim().is_empty()
|
|
|
|
|
{
|
|
|
|
|
return Err(Error::InvalidConfig(ParseError::CommandWithoutWhitespace(
|
|
|
|
|
real_line_no + 1,
|
|
|
|
|
)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Parse the command, also handling multiline commands
|
|
|
|
|
let mut command = String::new();
|
|
|
|
|
let mut j = i + 1;
|
|
|
|
@ -326,9 +349,7 @@ mod tests {
|
|
|
|
|
assert!(result.is_err());
|
|
|
|
|
|
|
|
|
|
match result.unwrap_err() {
|
|
|
|
|
Error::ConfigNotFound => {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
Error::ConfigNotFound => {}
|
|
|
|
|
_ => {
|
|
|
|
|
panic!("Error type for nonexistent file is wrong.");
|
|
|
|
|
}
|
|
|
|
@ -534,7 +555,8 @@ pesto
|
|
|
|
|
eval_invalid_config_test(contents, ParseError::UnknownSymbol(5))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
#[ignore]
|
|
|
|
|
// if a line do not start with whitespace, it is treated as a key. So we'll expect a invalid key error here
|
|
|
|
|
fn test_command_without_whitespace() -> std::io::Result<()> {
|
|
|
|
|
let contents = "0
|
|
|
|
|
firefox
|
|
|
|
@ -543,21 +565,22 @@ pesto
|
|
|
|
|
brave
|
|
|
|
|
";
|
|
|
|
|
|
|
|
|
|
eval_invalid_config_test(contents, ParseError::CommandWithoutWhitespace(5))
|
|
|
|
|
eval_invalid_config_test(contents, ParseError::UnknownSymbol(4))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_eofed_keybinding() -> std::io::Result<()> {
|
|
|
|
|
let contents = "
|
|
|
|
|
k
|
|
|
|
|
xbacklight -inc 10 -fps 30 -time 200
|
|
|
|
|
// #[test]
|
|
|
|
|
// fn test_eofed_keybinding() -> std::io::Result<()> {
|
|
|
|
|
// let contents = "
|
|
|
|
|
// k
|
|
|
|
|
// xbacklight -inc 10 -fps 30 -time 200
|
|
|
|
|
|
|
|
|
|
c ";
|
|
|
|
|
// c ";
|
|
|
|
|
|
|
|
|
|
eval_invalid_config_test(contents, ParseError::MissingCommand(5))
|
|
|
|
|
}
|
|
|
|
|
// eval_invalid_config_test(contents, ParseError::MissingCommand(5))
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
#[ignore]
|
|
|
|
|
// keysyms not followed by command should be ignored
|
|
|
|
|
fn test_no_command() -> std::io::Result<()> {
|
|
|
|
|
let contents = "
|
|
|
|
|
k
|
|
|
|
@ -569,14 +592,11 @@ w
|
|
|
|
|
|
|
|
|
|
eval_config_test(
|
|
|
|
|
contents,
|
|
|
|
|
vec![
|
|
|
|
|
Hotkey::new(
|
|
|
|
|
evdev::Key::KEY_K,
|
|
|
|
|
vec![],
|
|
|
|
|
"xbacklight -inc 10 -fps 30 -time 200".to_string(),
|
|
|
|
|
),
|
|
|
|
|
Hotkey::new(evdev::Key::KEY_W, vec![], "".to_string()),
|
|
|
|
|
],
|
|
|
|
|
vec![Hotkey::new(
|
|
|
|
|
evdev::Key::KEY_K,
|
|
|
|
|
vec![],
|
|
|
|
|
"xbacklight -inc 10 -fps 30 -time 200".to_string(),
|
|
|
|
|
)],
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|