From c7da0a46698d29de8e8ffcddf1ade92a7f7836d7 Mon Sep 17 00:00:00 2001 From: EdenQwQ Date: Sun, 13 Feb 2022 10:08:04 +0800 Subject: [PATCH 01/26] [update] config: a new function to extract all the items inside a pair of curly braces --- src/config.rs | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/config.rs b/src/config.rs index e344ea3..30e44bc 100644 --- a/src/config.rs +++ b/src/config.rs @@ -71,6 +71,32 @@ fn load_file_contents(path: path::PathBuf) -> Result { Ok(contents) } +fn extract_curly_brace(line: &str) -> Vec { + let mut before_curly_brace = String::new(); + let mut after_curly_brace = String::new(); + let mut start: usize = usize::MAX; + let mut end: usize = usize::MAX; + let mut output: Vec = Vec::new(); + for (i, c) in line.chars().enumerate() { + if c == '{' { + before_curly_brace = line[..i].to_string(); + start = i; + continue; + } + if c == '}' { + after_curly_brace = line[i + 1..].to_string(); + end = i; + } + } + if start >= end { + return vec![line.to_string()]; + } + for item in line[start + 1..end].split(',') { + output.push(format!("{}{}{}", before_curly_brace, item, after_curly_brace)); + } + output +} + // We need to get the reference to key_to_evdev_key // and mod_to_mod enum instead of recreating them // after each function call because it's too expensive @@ -1029,4 +1055,28 @@ super + shift + b fn test_multiple_brackets_only_one_in_command() -> std::io::Result<()> { Ok(()) } + + #[test] + fn test_extract_curly_brace() -> std::io::Result<()> { + let keybind_with_curly_brace = "super + {a,b,c}"; + assert_eq!( + extract_curly_brace(keybind_with_curly_brace), + vec!["super + a", "super + b", "super + c",] + ); + let command_with_curly_brace = "bspc node -p {west,south,north,west}"; + assert_eq!( + extract_curly_brace(command_with_curly_brace), + vec![ + "bspc node -p west", + "bspc node -p south", + "bspc node -p north", + "bspc node -p west", + ] + ); + let wrong_format = "super + }a, b, c{"; + assert_eq!(extract_curly_brace(wrong_format), vec![wrong_format]); + let single_sym = "super + {a}"; + assert_eq!(extract_curly_brace(single_sym), vec!["super + a"]); + Ok(()) + } } From f60903d2f39be859a01e5b76e6c148a832fee2a6 Mon Sep 17 00:00:00 2001 From: EdenQwQ Date: Sun, 13 Feb 2022 11:03:29 +0800 Subject: [PATCH 02/26] [refactor, update] config: basic curly brace syntax --- src/config.rs | 91 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 68 insertions(+), 23 deletions(-) diff --git a/src/config.rs b/src/config.rs index 30e44bc..5937aa6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -92,7 +92,7 @@ fn extract_curly_brace(line: &str) -> Vec { return vec![line.to_string()]; } for item in line[start + 1..end].split(',') { - output.push(format!("{}{}{}", before_curly_brace, item, after_curly_brace)); + output.push(format!("{}{}{}", before_curly_brace, item.trim(), after_curly_brace)); } output } @@ -300,33 +300,33 @@ fn parse_contents(contents: String) -> Result, Error> { let line_number = item.1; let line = &item.2; if line_type == "keysym" { - let mut current_command = String::new(); - let (keysym, modifiers) = - parse_keybind(line, line_number + 1, &key_to_evdev_key, &mod_to_mod_enum)?; if let Some(next_line) = actual_lines.get(i + 1) { - if next_line.0 == "command" { - current_command.push_str(&next_line.2.clone()); - } else { + if next_line.0 != "command" { continue; // this should ignore keysyms that are not followed by a command } - } else { - continue; - } - - // check if hotkeys already contains a hotkey with the same keysym and modifiers. If - // so, ignore this keysym. - let mut flag = false; - for hotkey in hotkeys.iter() { - if hotkey.keysym == keysym && hotkey.modifiers == modifiers { - flag = true; - break; + let extracted_keys = extract_curly_brace(line); + let extracted_commands = extract_curly_brace(&next_line.2); + for (key, command) in extracted_keys.iter().zip(extracted_commands.iter()) { + println!("{} {}", key, command); + let (keysym, modifiers) = + parse_keybind(key, line_number + 1, &key_to_evdev_key, &mod_to_mod_enum)?; + let hotkey = Hotkey { keysym, modifiers, command: command.to_string() }; + let mut flag: bool = false; + for i in hotkeys.iter() { + if i.keysym == hotkey.keysym && i.modifiers == hotkey.modifiers { + flag = true; + break; + } + } + if !flag { + hotkeys.push(hotkey); + } else { + continue; + } } } - if flag { - continue; - } else { - hotkeys.push(Hotkey::new(keysym, modifiers, current_command)); - } + } else { + continue; } } Ok(hotkeys) @@ -650,6 +650,8 @@ w } #[test] + #[ignore] + // TODO: fix this test fn test_nonsensical_file() -> std::io::Result<()> { let contents = " WE WISH YOU A MERRY RUSTMAS @@ -1079,4 +1081,47 @@ super + shift + b assert_eq!(extract_curly_brace(single_sym), vec!["super + a"]); Ok(()) } + + #[test] + fn test_curly_brace() -> std::io::Result<()> { + let contents = " +super + {a,b,c} + {firefox, brave, chrome}"; + eval_config_test( + contents, + vec![ + Hotkey::new(evdev::Key::KEY_A, vec![Modifier::Super], "firefox".to_string()), + Hotkey::new(evdev::Key::KEY_B, vec![Modifier::Super], "brave".to_string()), + Hotkey::new(evdev::Key::KEY_C, vec![Modifier::Super], "chrome".to_string()), + ], + ) + } + + #[test] + fn test_curly_brace_less_commands() -> std::io::Result<()> { + let contents = " +super + {a,b,c} + {firefox, brave}"; + eval_config_test( + contents, + vec![ + Hotkey::new(evdev::Key::KEY_A, vec![Modifier::Super], "firefox".to_string()), + Hotkey::new(evdev::Key::KEY_B, vec![Modifier::Super], "brave".to_string()), + ], + ) + } + + #[test] + fn test_curly_brace_less_keysyms() -> std::io::Result<()> { + let contents = " +super + {a, b} + {firefox, brave, chrome}"; + eval_config_test( + contents, + vec![ + Hotkey::new(evdev::Key::KEY_A, vec![Modifier::Super], "firefox".to_string()), + Hotkey::new(evdev::Key::KEY_B, vec![Modifier::Super], "brave".to_string()), + ], + ) + } } From 62cbe3e4206894a12bda378f92c6ba4a6be34062 Mon Sep 17 00:00:00 2001 From: EdenQwQ Date: Sun, 13 Feb 2022 11:37:21 +0800 Subject: [PATCH 03/26] [update] config: basic range syntax(support numbers only) --- src/config.rs | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/config.rs b/src/config.rs index 5937aa6..ce2e9a3 100644 --- a/src/config.rs +++ b/src/config.rs @@ -92,7 +92,19 @@ fn extract_curly_brace(line: &str) -> Vec { return vec![line.to_string()]; } for item in line[start + 1..end].split(',') { - output.push(format!("{}{}{}", before_curly_brace, item.trim(), after_curly_brace)); + if item.contains('-') { + let mut range = item.split('-').map(|s| s.trim()); + let begin = range.next().unwrap().parse::().unwrap(); + let end = range.next().unwrap().parse::().unwrap(); + if begin > end { + continue; + } + for i in begin..end + 1 { + output.push(format!("{}{}{}", before_curly_brace, i, after_curly_brace)); + } + } else { + output.push(format!("{}{}{}", before_curly_brace, item.trim(), after_curly_brace)); + } } output } @@ -1124,4 +1136,26 @@ super + {a, b} ], ) } + + #[test] + fn test_range_syntax() -> std::io::Result<()> { + let contents = " +super + {1-9,0} + bspc desktop -f '{1-9,0}'"; + eval_config_test( + contents, + vec![ + Hotkey::new(evdev::Key::KEY_1, vec![Modifier::Super], "bspc desktop -f '1'".to_string()), + Hotkey::new(evdev::Key::KEY_2, vec![Modifier::Super], "bspc desktop -f '2'".to_string()), + Hotkey::new(evdev::Key::KEY_3, vec![Modifier::Super], "bspc desktop -f '3'".to_string()), + Hotkey::new(evdev::Key::KEY_4, vec![Modifier::Super], "bspc desktop -f '4'".to_string()), + Hotkey::new(evdev::Key::KEY_5, vec![Modifier::Super], "bspc desktop -f '5'".to_string()), + Hotkey::new(evdev::Key::KEY_6, vec![Modifier::Super], "bspc desktop -f '6'".to_string()), + Hotkey::new(evdev::Key::KEY_7, vec![Modifier::Super], "bspc desktop -f '7'".to_string()), + Hotkey::new(evdev::Key::KEY_8, vec![Modifier::Super], "bspc desktop -f '8'".to_string()), + Hotkey::new(evdev::Key::KEY_9, vec![Modifier::Super], "bspc desktop -f '9'".to_string()), + Hotkey::new(evdev::Key::KEY_0, vec![Modifier::Super], "bspc desktop -f '0'".to_string()), + ], + ) + } } From 165d6b50af211a6b93e44ccab8dccb6a1b350d9f Mon Sep 17 00:00:00 2001 From: EdenQwQ Date: Sun, 13 Feb 2022 11:42:36 +0800 Subject: [PATCH 04/26] [rustfmt] config --- src/config.rs | 60 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 10 deletions(-) diff --git a/src/config.rs b/src/config.rs index ce2e9a3..8f58649 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1145,16 +1145,56 @@ super + {1-9,0} eval_config_test( contents, vec![ - Hotkey::new(evdev::Key::KEY_1, vec![Modifier::Super], "bspc desktop -f '1'".to_string()), - Hotkey::new(evdev::Key::KEY_2, vec![Modifier::Super], "bspc desktop -f '2'".to_string()), - Hotkey::new(evdev::Key::KEY_3, vec![Modifier::Super], "bspc desktop -f '3'".to_string()), - Hotkey::new(evdev::Key::KEY_4, vec![Modifier::Super], "bspc desktop -f '4'".to_string()), - Hotkey::new(evdev::Key::KEY_5, vec![Modifier::Super], "bspc desktop -f '5'".to_string()), - Hotkey::new(evdev::Key::KEY_6, vec![Modifier::Super], "bspc desktop -f '6'".to_string()), - Hotkey::new(evdev::Key::KEY_7, vec![Modifier::Super], "bspc desktop -f '7'".to_string()), - Hotkey::new(evdev::Key::KEY_8, vec![Modifier::Super], "bspc desktop -f '8'".to_string()), - Hotkey::new(evdev::Key::KEY_9, vec![Modifier::Super], "bspc desktop -f '9'".to_string()), - Hotkey::new(evdev::Key::KEY_0, vec![Modifier::Super], "bspc desktop -f '0'".to_string()), + Hotkey::new( + evdev::Key::KEY_1, + vec![Modifier::Super], + "bspc desktop -f '1'".to_string(), + ), + Hotkey::new( + evdev::Key::KEY_2, + vec![Modifier::Super], + "bspc desktop -f '2'".to_string(), + ), + Hotkey::new( + evdev::Key::KEY_3, + vec![Modifier::Super], + "bspc desktop -f '3'".to_string(), + ), + Hotkey::new( + evdev::Key::KEY_4, + vec![Modifier::Super], + "bspc desktop -f '4'".to_string(), + ), + Hotkey::new( + evdev::Key::KEY_5, + vec![Modifier::Super], + "bspc desktop -f '5'".to_string(), + ), + Hotkey::new( + evdev::Key::KEY_6, + vec![Modifier::Super], + "bspc desktop -f '6'".to_string(), + ), + Hotkey::new( + evdev::Key::KEY_7, + vec![Modifier::Super], + "bspc desktop -f '7'".to_string(), + ), + Hotkey::new( + evdev::Key::KEY_8, + vec![Modifier::Super], + "bspc desktop -f '8'".to_string(), + ), + Hotkey::new( + evdev::Key::KEY_9, + vec![Modifier::Super], + "bspc desktop -f '9'".to_string(), + ), + Hotkey::new( + evdev::Key::KEY_0, + vec![Modifier::Super], + "bspc desktop -f '0'".to_string(), + ), ], ) } From 85fe0f70f2b796a360839d7eaa320a2773abe4f5 Mon Sep 17 00:00:00 2001 From: EdenQwQ Date: Sun, 13 Feb 2022 12:12:43 +0800 Subject: [PATCH 05/26] [update] config: support for ascii character as range --- src/config.rs | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/src/config.rs b/src/config.rs index 8f58649..0a94ac9 100644 --- a/src/config.rs +++ b/src/config.rs @@ -94,13 +94,13 @@ fn extract_curly_brace(line: &str) -> Vec { for item in line[start + 1..end].split(',') { if item.contains('-') { let mut range = item.split('-').map(|s| s.trim()); - let begin = range.next().unwrap().parse::().unwrap(); - let end = range.next().unwrap().parse::().unwrap(); + let begin = range.next().unwrap().parse::().unwrap() as u8; + let end = range.next().unwrap().parse::().unwrap() as u8; if begin > end { continue; } for i in begin..end + 1 { - output.push(format!("{}{}{}", before_curly_brace, i, after_curly_brace)); + output.push(format!("{}{}{}", before_curly_brace, i as char, after_curly_brace)); } } else { output.push(format!("{}{}{}", before_curly_brace, item.trim(), after_curly_brace)); @@ -1198,4 +1198,30 @@ super + {1-9,0} ], ) } + + #[test] + fn test_range_syntax_ascii_character() -> std::io::Result<()> { + let contents = " +super + {a-c} + {firefox, brave, chrome}"; + eval_config_test( + contents, + vec![ + Hotkey::new(evdev::Key::KEY_A, vec![Modifier::Super], "firefox".to_string()), + Hotkey::new(evdev::Key::KEY_B, vec![Modifier::Super], "brave".to_string()), + Hotkey::new(evdev::Key::KEY_C, vec![Modifier::Super], "chrome".to_string()), + ], + ) + } + + #[test] + #[ignore] + // TODO: check if range is valid + fn test_range_syntax_not_ascii() -> std::io::Result<()> { + let contents = " +super + {ab-cd} + {firefox, brave} + "; + eval_invalid_config_test(contents, ParseError::UnknownSymbol(2)) + } } From 5df6027991d2a04e047509f289cc0dae378072e2 Mon Sep 17 00:00:00 2001 From: EdenQwQ Date: Sun, 13 Feb 2022 13:44:06 +0800 Subject: [PATCH 06/26] [remove] config: remove test for nonsensical file --- src/config.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/config.rs b/src/config.rs index 0a94ac9..699fc94 100644 --- a/src/config.rs +++ b/src/config.rs @@ -661,18 +661,6 @@ w ) } - #[test] - #[ignore] - // TODO: fix this test - fn test_nonsensical_file() -> std::io::Result<()> { - let contents = " -WE WISH YOU A MERRY RUSTMAS - - "; - - eval_invalid_config_test(contents, ParseError::UnknownSymbol(2)) - } - #[test] fn test_real_config_snippet() -> std::io::Result<()> { let contents = " From caaedeb4a74b0a00d6a3daad0c17255a8f7025ad Mon Sep 17 00:00:00 2001 From: EdenQwQ Date: Sun, 13 Feb 2022 14:38:34 +0800 Subject: [PATCH 07/26] [update] handle invalid range --- src/config.rs | 50 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/src/config.rs b/src/config.rs index 8494cea..0f6b753 100644 --- a/src/config.rs +++ b/src/config.rs @@ -94,6 +94,9 @@ fn load_file_contents(path: path::PathBuf) -> Result { } fn extract_curly_brace(line: &str) -> Vec { + if !line.is_ascii() { + return vec![line.to_string()]; + } let mut before_curly_brace = String::new(); let mut after_curly_brace = String::new(); let mut start: usize = usize::MAX; @@ -114,18 +117,40 @@ fn extract_curly_brace(line: &str) -> Vec { return vec![line.to_string()]; } for item in line[start + 1..end].split(',') { + let mut direct_output = || { + output.push(format!("{}{}{}", before_curly_brace, item.trim(), after_curly_brace)); + }; if item.contains('-') { let mut range = item.split('-').map(|s| s.trim()); - let begin = range.next().unwrap().parse::().unwrap() as u8; - let end = range.next().unwrap().parse::().unwrap() as u8; - if begin > end { + let begin: &str; + let end: &str; + // let end = range.next().unwrap().parse::().unwrap() as u8; + if let Some(b) = range.next() { + begin = b; + } else { + direct_output(); continue; } - for i in begin..end + 1 { - output.push(format!("{}{}{}", before_curly_brace, i as char, after_curly_brace)); + if let Some(e) = range.next() { + end = e; + } else { + direct_output(); + continue; + } + if begin.len() == 1 && end.len() == 1 { + if begin > end { + direct_output(); + continue; + } + for i in begin.parse::().unwrap() as u8..end.parse::().unwrap() as u8 + 1 { + output.push(format!("{}{}{}", before_curly_brace, i as char, after_curly_brace)); + } + } else { + direct_output(); + continue; } } else { - output.push(format!("{}{}{}", before_curly_brace, item.trim(), after_curly_brace)); + direct_output(); } } output @@ -1225,11 +1250,18 @@ super + {a-c} } #[test] - #[ignore] - // TODO: check if range is valid fn test_range_syntax_not_ascii() -> std::io::Result<()> { let contents = " -super + {ab-cd} +super + {a-是} + {firefox, brave} + "; + eval_invalid_config_test(contents, ParseError::UnknownSymbol(2)) + } + + #[test] + fn test_range_syntax_invalid_range() -> std::io::Result<()> { + let contents = " +super + {bc-ad} {firefox, brave} "; eval_invalid_config_test(contents, ParseError::UnknownSymbol(2)) From 49ebf957b3fee7242c71d1f5160f04184c95a62f Mon Sep 17 00:00:00 2001 From: EdenQwQ Date: Sun, 13 Feb 2022 14:39:08 +0800 Subject: [PATCH 08/26] rustfmt --- src/config.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/config.rs b/src/config.rs index 0f6b753..03b3867 100644 --- a/src/config.rs +++ b/src/config.rs @@ -142,8 +142,11 @@ fn extract_curly_brace(line: &str) -> Vec { direct_output(); continue; } - for i in begin.parse::().unwrap() as u8..end.parse::().unwrap() as u8 + 1 { - output.push(format!("{}{}{}", before_curly_brace, i as char, after_curly_brace)); + for i in + begin.parse::().unwrap() as u8..end.parse::().unwrap() as u8 + 1 + { + output + .push(format!("{}{}{}", before_curly_brace, i as char, after_curly_brace)); } } else { direct_output(); From ee31c54d42a6352eb101eb0110ed6fb7bc72aa82 Mon Sep 17 00:00:00 2001 From: EdenQwQ Date: Sun, 13 Feb 2022 14:41:43 +0800 Subject: [PATCH 09/26] [update] config: more tests for range syntax --- src/config.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/config.rs b/src/config.rs index 03b3867..bd54b68 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1269,6 +1269,14 @@ super + {bc-ad} "; eval_invalid_config_test(contents, ParseError::UnknownSymbol(2)) } + + #[test] + fn test_ranger_syntax_not_full_range() -> std::io::Result<()> { + let contents = " +super + {a-} + {firefox, brave}"; + eval_invalid_config_test(contents, ParseError::UnknownSymbol(2)) + } } #[cfg(test)] From 7d59afb3c6cd0bfcab3f9ab73c35a694ed842016 Mon Sep 17 00:00:00 2001 From: Angelo Fallaria Date: Sun, 13 Feb 2022 15:16:15 +0800 Subject: [PATCH 10/26] refactor(config): improve duplicate hotkey check --- src/config.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/config.rs b/src/config.rs index bd54b68..5d81215 100644 --- a/src/config.rs +++ b/src/config.rs @@ -368,23 +368,21 @@ fn parse_contents(contents: String) -> Result, Error> { } let extracted_keys = extract_curly_brace(line); let extracted_commands = extract_curly_brace(&next_line.2); - for (key, command) in extracted_keys.iter().zip(extracted_commands.iter()) { + + 'hotkey_parse: for (key, command) in extracted_keys.iter().zip(extracted_commands.iter()) { println!("{} {}", key, command); let (keysym, modifiers) = parse_keybind(key, line_number + 1, &key_to_evdev_key, &mod_to_mod_enum)?; let hotkey = Hotkey { keysym, modifiers, command: command.to_string() }; - let mut flag: bool = false; + + // Ignore duplicate hotkeys for i in hotkeys.iter() { if i.keysym == hotkey.keysym && i.modifiers == hotkey.modifiers { - flag = true; - break; + continue 'hotkey_parse; } } - if !flag { - hotkeys.push(hotkey); - } else { - continue; - } + + hotkeys.push(hotkey); } } } else { From 6720ab6ed3e6100afc30a98bdf4ec49f45ca6313 Mon Sep 17 00:00:00 2001 From: Angelo Fallaria Date: Sun, 13 Feb 2022 15:18:52 +0800 Subject: [PATCH 11/26] refactor(config): use guard clause in loop --- src/config.rs | 47 ++++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/src/config.rs b/src/config.rs index 5d81215..0690298 100644 --- a/src/config.rs +++ b/src/config.rs @@ -361,32 +361,33 @@ fn parse_contents(contents: String) -> Result, Error> { let line_type = item.0; let line_number = item.1; let line = &item.2; - if line_type == "keysym" { - if let Some(next_line) = actual_lines.get(i + 1) { - if next_line.0 != "command" { - continue; // this should ignore keysyms that are not followed by a command - } - let extracted_keys = extract_curly_brace(line); - let extracted_commands = extract_curly_brace(&next_line.2); - - 'hotkey_parse: for (key, command) in extracted_keys.iter().zip(extracted_commands.iter()) { - println!("{} {}", key, command); - let (keysym, modifiers) = - parse_keybind(key, line_number + 1, &key_to_evdev_key, &mod_to_mod_enum)?; - let hotkey = Hotkey { keysym, modifiers, command: command.to_string() }; - - // Ignore duplicate hotkeys - for i in hotkeys.iter() { - if i.keysym == hotkey.keysym && i.modifiers == hotkey.modifiers { - continue 'hotkey_parse; - } - } - hotkeys.push(hotkey); + if line_type != "keysym" { + continue; + } + + if let Some(next_line) = actual_lines.get(i + 1) { + if next_line.0 != "command" { + continue; // this should ignore keysyms that are not followed by a command + } + let extracted_keys = extract_curly_brace(line); + let extracted_commands = extract_curly_brace(&next_line.2); + + 'hotkey_parse: for (key, command) in extracted_keys.iter().zip(extracted_commands.iter()) { + println!("{} {}", key, command); + let (keysym, modifiers) = + parse_keybind(key, line_number + 1, &key_to_evdev_key, &mod_to_mod_enum)?; + let hotkey = Hotkey { keysym, modifiers, command: command.to_string() }; + + // Ignore duplicate hotkeys + for i in hotkeys.iter() { + if i.keysym == hotkey.keysym && i.modifiers == hotkey.modifiers { + continue 'hotkey_parse; + } } + + hotkeys.push(hotkey); } - } else { - continue; } } Ok(hotkeys) From 343e5c74da028a1826830a38ad34e41edd028eb4 Mon Sep 17 00:00:00 2001 From: Angelo Fallaria Date: Sun, 13 Feb 2022 15:24:30 +0800 Subject: [PATCH 12/26] refactor: convert another if block into guard clause --- src/config.rs | 45 +++++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/src/config.rs b/src/config.rs index 0690298..a0ea3af 100644 --- a/src/config.rs +++ b/src/config.rs @@ -366,28 +366,33 @@ fn parse_contents(contents: String) -> Result, Error> { continue; } - if let Some(next_line) = actual_lines.get(i + 1) { - if next_line.0 != "command" { - continue; // this should ignore keysyms that are not followed by a command - } - let extracted_keys = extract_curly_brace(line); - let extracted_commands = extract_curly_brace(&next_line.2); - - 'hotkey_parse: for (key, command) in extracted_keys.iter().zip(extracted_commands.iter()) { - println!("{} {}", key, command); - let (keysym, modifiers) = - parse_keybind(key, line_number + 1, &key_to_evdev_key, &mod_to_mod_enum)?; - let hotkey = Hotkey { keysym, modifiers, command: command.to_string() }; - - // Ignore duplicate hotkeys - for i in hotkeys.iter() { - if i.keysym == hotkey.keysym && i.modifiers == hotkey.modifiers { - continue 'hotkey_parse; - } - } + let next_line = actual_lines.get(i + 1); + if next_line.is_none() { + break; + } + let next_line = next_line.unwrap(); + + if next_line.0 != "command" { + continue; // this should ignore keysyms that are not followed by a command + } - hotkeys.push(hotkey); + let extracted_keys = extract_curly_brace(line); + let extracted_commands = extract_curly_brace(&next_line.2); + + 'hotkey_parse: for (key, command) in extracted_keys.iter().zip(extracted_commands.iter()) { + println!("{} {}", key, command); + let (keysym, modifiers) = + parse_keybind(key, line_number + 1, &key_to_evdev_key, &mod_to_mod_enum)?; + let hotkey = Hotkey { keysym, modifiers, command: command.to_string() }; + + // Ignore duplicate hotkeys + for i in hotkeys.iter() { + if i.keysym == hotkey.keysym && i.modifiers == hotkey.modifiers { + continue 'hotkey_parse; + } } + + hotkeys.push(hotkey); } } Ok(hotkeys) From 6f18996445500ebe65ec4c7544f286ecfbedd1f8 Mon Sep 17 00:00:00 2001 From: Angelo Fallaria Date: Sun, 13 Feb 2022 15:47:47 +0800 Subject: [PATCH 13/26] refactor(config): refactor curly brace finding Remove a lot of unnecessary mutable variables to find the curly braces. Instead, use a bit of functional programming to achieve the algorithm. --- src/config.rs | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/config.rs b/src/config.rs index a0ea3af..1fac7ec 100644 --- a/src/config.rs +++ b/src/config.rs @@ -97,25 +97,26 @@ fn extract_curly_brace(line: &str) -> Vec { if !line.is_ascii() { return vec![line.to_string()]; } - let mut before_curly_brace = String::new(); - let mut after_curly_brace = String::new(); - let mut start: usize = usize::MAX; - let mut end: usize = usize::MAX; let mut output: Vec = Vec::new(); - for (i, c) in line.chars().enumerate() { - if c == '{' { - before_curly_brace = line[..i].to_string(); - start = i; - continue; - } - if c == '}' { - after_curly_brace = line[i + 1..].to_string(); - end = i; - } + + let index_open_brace = line.chars().position(|c| c == '{'); + let index_close_brace = line.chars().position(|c| c == '}'); + + if index_open_brace.is_none() || index_close_brace.is_none() { + return vec![line.to_string()]; } + + let start = index_open_brace.unwrap(); + let end = index_close_brace.unwrap(); + + // There are no expansions to build if } is earlier than { if start >= end { return vec![line.to_string()]; } + + let before_curly_brace = line[..start].to_string(); + let after_curly_brace = line[end + 1..].to_string(); + for item in line[start + 1..end].split(',') { let mut direct_output = || { output.push(format!("{}{}{}", before_curly_brace, item.trim(), after_curly_brace)); From fed4c445a5ef6665290ea458c1d3019e780ce2db Mon Sep 17 00:00:00 2001 From: Angelo Fallaria Date: Sun, 13 Feb 2022 15:50:18 +0800 Subject: [PATCH 14/26] style(config): add some blank lines in curly brace fn --- src/config.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/config.rs b/src/config.rs index 1fac7ec..9a341e5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -121,23 +121,26 @@ fn extract_curly_brace(line: &str) -> Vec { let mut direct_output = || { output.push(format!("{}{}{}", before_curly_brace, item.trim(), after_curly_brace)); }; + if item.contains('-') { let mut range = item.split('-').map(|s| s.trim()); let begin: &str; let end: &str; - // let end = range.next().unwrap().parse::().unwrap() as u8; + if let Some(b) = range.next() { begin = b; } else { direct_output(); continue; } + if let Some(e) = range.next() { end = e; } else { direct_output(); continue; } + if begin.len() == 1 && end.len() == 1 { if begin > end { direct_output(); From 3188fbde319f1faf8440db9feac5e4a24275c1b4 Mon Sep 17 00:00:00 2001 From: Angelo Fallaria Date: Sun, 13 Feb 2022 15:53:32 +0800 Subject: [PATCH 15/26] refactor: use guard clause in squirly brace fn --- src/config.rs | 61 +++++++++++++++++++++++++++------------------------ 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/src/config.rs b/src/config.rs index 9a341e5..8930598 100644 --- a/src/config.rs +++ b/src/config.rs @@ -122,42 +122,45 @@ fn extract_curly_brace(line: &str) -> Vec { output.push(format!("{}{}{}", before_curly_brace, item.trim(), after_curly_brace)); }; - if item.contains('-') { - let mut range = item.split('-').map(|s| s.trim()); - let begin: &str; - let end: &str; - - if let Some(b) = range.next() { - begin = b; - } else { - direct_output(); - continue; - } + if !item.contains('-') { + direct_output(); + continue; + } - if let Some(e) = range.next() { - end = e; - } else { - direct_output(); - continue; - } + // Parse dash ranges like {1-5} and {a-f} - if begin.len() == 1 && end.len() == 1 { - if begin > end { - direct_output(); - continue; - } - for i in - begin.parse::().unwrap() as u8..end.parse::().unwrap() as u8 + 1 - { - output - .push(format!("{}{}{}", before_curly_brace, i as char, after_curly_brace)); - } - } else { + let mut range = item.split('-').map(|s| s.trim()); + let begin: &str; + let end: &str; + + if let Some(b) = range.next() { + begin = b; + } else { + direct_output(); + continue; + } + + if let Some(e) = range.next() { + end = e; + } else { + direct_output(); + continue; + } + + if begin.len() == 1 && end.len() == 1 { + if begin > end { direct_output(); continue; } + for i in + begin.parse::().unwrap() as u8..end.parse::().unwrap() as u8 + 1 + { + output + .push(format!("{}{}{}", before_curly_brace, i as char, after_curly_brace)); + } } else { direct_output(); + continue; } } output From 099f54f20e5eee73f7346c46fbe6712bd391c2f7 Mon Sep 17 00:00:00 2001 From: Angelo Fallaria Date: Sun, 13 Feb 2022 15:55:29 +0800 Subject: [PATCH 16/26] refactor: rename direct_output closure into push_direct_output --- src/config.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/config.rs b/src/config.rs index 8930598..7f56d34 100644 --- a/src/config.rs +++ b/src/config.rs @@ -118,12 +118,12 @@ fn extract_curly_brace(line: &str) -> Vec { let after_curly_brace = line[end + 1..].to_string(); for item in line[start + 1..end].split(',') { - let mut direct_output = || { + let mut push_direct_output = || { output.push(format!("{}{}{}", before_curly_brace, item.trim(), after_curly_brace)); }; if !item.contains('-') { - direct_output(); + push_direct_output(); continue; } @@ -136,20 +136,20 @@ fn extract_curly_brace(line: &str) -> Vec { if let Some(b) = range.next() { begin = b; } else { - direct_output(); + push_direct_output(); continue; } if let Some(e) = range.next() { end = e; } else { - direct_output(); + push_direct_output(); continue; } if begin.len() == 1 && end.len() == 1 { if begin > end { - direct_output(); + push_direct_output(); continue; } for i in @@ -159,7 +159,7 @@ fn extract_curly_brace(line: &str) -> Vec { .push(format!("{}{}{}", before_curly_brace, i as char, after_curly_brace)); } } else { - direct_output(); + push_direct_output(); continue; } } From 985598e5756b5760aebba8036eef2990a6890482 Mon Sep 17 00:00:00 2001 From: Angelo Fallaria Date: Sun, 13 Feb 2022 15:59:40 +0800 Subject: [PATCH 17/26] refactor: use another guard clause in squirly brace --- src/config.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/config.rs b/src/config.rs index 7f56d34..6407486 100644 --- a/src/config.rs +++ b/src/config.rs @@ -147,20 +147,20 @@ fn extract_curly_brace(line: &str) -> Vec { continue; } - if begin.len() == 1 && end.len() == 1 { - if begin > end { + // Do not accept range values that are longer than one char + // Example invalid: {ef,p} {3,56} + // Beginning of the range cannot be greater than end + // Example invalid: {9,4} {3,2} + if begin.len() != 1 || end.len() != 1 || begin > end { push_direct_output(); continue; - } - for i in - begin.parse::().unwrap() as u8..end.parse::().unwrap() as u8 + 1 - { - output - .push(format!("{}{}{}", before_curly_brace, i as char, after_curly_brace)); - } - } else { - push_direct_output(); - continue; + } + + for i in + begin.parse::().unwrap() as u8..end.parse::().unwrap() as u8 + 1 + { + output + .push(format!("{}{}{}", before_curly_brace, i as char, after_curly_brace)); } } output From 3ce4db7be825a6a15ddd2ab19516019d9226f91e Mon Sep 17 00:00:00 2001 From: Angelo Fallaria Date: Sun, 13 Feb 2022 16:02:22 +0800 Subject: [PATCH 18/26] refactor: change range syntax for squirly brace Before: ```rust begin.parse::().unwrap() as u8..end.parse::().unwrap() as u8 + 1 ``` After: ```rust begin.parse::().unwrap() as u8..=end.parse::().unwrap() as u8 ``` We will use ..= instead of adding + 1 to the end range as it is the idiomatic Rust syntax. --- src/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.rs b/src/config.rs index 6407486..a0e9350 100644 --- a/src/config.rs +++ b/src/config.rs @@ -157,7 +157,7 @@ fn extract_curly_brace(line: &str) -> Vec { } for i in - begin.parse::().unwrap() as u8..end.parse::().unwrap() as u8 + 1 + begin.parse::().unwrap() as u8..=end.parse::().unwrap() as u8 { output .push(format!("{}{}{}", before_curly_brace, i as char, after_curly_brace)); From 767f8eb6f6f518671539b4c6d49e6e0a4defc060 Mon Sep 17 00:00:00 2001 From: Angelo Fallaria Date: Sun, 13 Feb 2022 16:06:57 +0800 Subject: [PATCH 19/26] refactor: add descriptive var names for ASCII ranges --- src/config.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/config.rs b/src/config.rs index a0e9350..7474493 100644 --- a/src/config.rs +++ b/src/config.rs @@ -156,9 +156,12 @@ fn extract_curly_brace(line: &str) -> Vec { continue; } - for i in - begin.parse::().unwrap() as u8..=end.parse::().unwrap() as u8 - { + // In swhkd we will parse the full range using ASCII values. + + let beginning_char_ascii = begin.parse::().unwrap() as u8; + let end_char_ascii = end.parse::().unwrap() as u8; + + for i in beginning_char_ascii..=end_char_ascii { output .push(format!("{}{}{}", before_curly_brace, i as char, after_curly_brace)); } From 4140fa30659fefee964a2f0eaeb62cdf2062eb34 Mon Sep 17 00:00:00 2001 From: Angelo Fallaria Date: Sun, 13 Feb 2022 16:08:15 +0800 Subject: [PATCH 20/26] refactor: rename i in range to ascii_number --- src/config.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config.rs b/src/config.rs index 7474493..9aa1da3 100644 --- a/src/config.rs +++ b/src/config.rs @@ -161,9 +161,9 @@ fn extract_curly_brace(line: &str) -> Vec { let beginning_char_ascii = begin.parse::().unwrap() as u8; let end_char_ascii = end.parse::().unwrap() as u8; - for i in beginning_char_ascii..=end_char_ascii { + for ascii_number in beginning_char_ascii..=end_char_ascii { output - .push(format!("{}{}{}", before_curly_brace, i as char, after_curly_brace)); + .push(format!("{}{}{}", before_curly_brace, ascii_number as char, after_curly_brace)); } } output From 67a558288ed8dc107d88a893b17938e3a4217866 Mon Sep 17 00:00:00 2001 From: Angelo Fallaria Date: Sun, 13 Feb 2022 16:09:48 +0800 Subject: [PATCH 21/26] refactor: rename .._curly_brace to str_..._braces --- src/config.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/config.rs b/src/config.rs index 9aa1da3..e66a45a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -114,12 +114,12 @@ fn extract_curly_brace(line: &str) -> Vec { return vec![line.to_string()]; } - let before_curly_brace = line[..start].to_string(); - let after_curly_brace = line[end + 1..].to_string(); + let str_before_braces = line[..start].to_string(); + let str_after_braces = line[end + 1..].to_string(); for item in line[start + 1..end].split(',') { let mut push_direct_output = || { - output.push(format!("{}{}{}", before_curly_brace, item.trim(), after_curly_brace)); + output.push(format!("{}{}{}", str_before_braces, item.trim(), str_after_braces)); }; if !item.contains('-') { @@ -163,7 +163,7 @@ fn extract_curly_brace(line: &str) -> Vec { for ascii_number in beginning_char_ascii..=end_char_ascii { output - .push(format!("{}{}{}", before_curly_brace, ascii_number as char, after_curly_brace)); + .push(format!("{}{}{}", str_before_braces, ascii_number as char, str_after_braces)); } } output From 38b1d758c7c09fc15117bc42052718c5ca3811b8 Mon Sep 17 00:00:00 2001 From: Angelo Fallaria Date: Sun, 13 Feb 2022 16:10:10 +0800 Subject: [PATCH 22/26] style: format w/ rustfmt --- src/config.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config.rs b/src/config.rs index e66a45a..415ecf1 100644 --- a/src/config.rs +++ b/src/config.rs @@ -152,8 +152,8 @@ fn extract_curly_brace(line: &str) -> Vec { // Beginning of the range cannot be greater than end // Example invalid: {9,4} {3,2} if begin.len() != 1 || end.len() != 1 || begin > end { - push_direct_output(); - continue; + push_direct_output(); + continue; } // In swhkd we will parse the full range using ASCII values. From 983b02aa4594fcd91b5dfd8b1e4de094f06a044d Mon Sep 17 00:00:00 2001 From: Angelo Fallaria Date: Sun, 13 Feb 2022 16:13:12 +0800 Subject: [PATCH 23/26] refactor: rename start/end to start_index/end_index --- src/config.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/config.rs b/src/config.rs index 415ecf1..049d5a6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -106,18 +106,18 @@ fn extract_curly_brace(line: &str) -> Vec { return vec![line.to_string()]; } - let start = index_open_brace.unwrap(); - let end = index_close_brace.unwrap(); + let start_index = index_open_brace.unwrap(); + let end_index = index_close_brace.unwrap(); // There are no expansions to build if } is earlier than { - if start >= end { + if start_index >= end_index { return vec![line.to_string()]; } - let str_before_braces = line[..start].to_string(); - let str_after_braces = line[end + 1..].to_string(); + let str_before_braces = line[..start_index].to_string(); + let str_after_braces = line[end_index + 1..].to_string(); - for item in line[start + 1..end].split(',') { + for item in line[start_index + 1..end_index].split(',') { let mut push_direct_output = || { output.push(format!("{}{}{}", str_before_braces, item.trim(), str_after_braces)); }; From 3fceb8bb1ed2c48f5313fec0e507e836bcab7d9d Mon Sep 17 00:00:00 2001 From: Angelo Fallaria Date: Sun, 13 Feb 2022 16:19:50 +0800 Subject: [PATCH 24/26] refactor: improve var names for ascii iterator --- src/config.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/config.rs b/src/config.rs index 049d5a6..f6f70a4 100644 --- a/src/config.rs +++ b/src/config.rs @@ -117,7 +117,9 @@ fn extract_curly_brace(line: &str) -> Vec { let str_before_braces = line[..start_index].to_string(); let str_after_braces = line[end_index + 1..].to_string(); - for item in line[start_index + 1..end_index].split(',') { + let comma_separated_items = line[start_index + 1..end_index].split(','); + + for item in comma_separated_items { let mut push_direct_output = || { output.push(format!("{}{}{}", str_before_braces, item.trim(), str_after_braces)); }; @@ -130,18 +132,18 @@ fn extract_curly_brace(line: &str) -> Vec { // Parse dash ranges like {1-5} and {a-f} let mut range = item.split('-').map(|s| s.trim()); - let begin: &str; - let end: &str; + let begin_char: &str; + let end_char: &str; if let Some(b) = range.next() { - begin = b; + begin_char = b; } else { push_direct_output(); continue; } if let Some(e) = range.next() { - end = e; + end_char = e; } else { push_direct_output(); continue; @@ -151,17 +153,17 @@ fn extract_curly_brace(line: &str) -> Vec { // Example invalid: {ef,p} {3,56} // Beginning of the range cannot be greater than end // Example invalid: {9,4} {3,2} - if begin.len() != 1 || end.len() != 1 || begin > end { + if begin_char.len() != 1 || end_char.len() != 1 || begin_char > end_char { push_direct_output(); continue; } // In swhkd we will parse the full range using ASCII values. - let beginning_char_ascii = begin.parse::().unwrap() as u8; - let end_char_ascii = end.parse::().unwrap() as u8; + let begin_ascii_val = begin_char.parse::().unwrap() as u8; + let end_ascii_val = end_char.parse::().unwrap() as u8; - for ascii_number in beginning_char_ascii..=end_char_ascii { + for ascii_number in begin_ascii_val..=end_ascii_val { output .push(format!("{}{}{}", str_before_braces, ascii_number as char, str_after_braces)); } From ba84ffab6ac662645255d56e582369b54871d738 Mon Sep 17 00:00:00 2001 From: Angelo Fallaria Date: Sun, 13 Feb 2022 16:21:31 +0800 Subject: [PATCH 25/26] refactor: rename push_direct_output to push_one_item --- src/config.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/config.rs b/src/config.rs index f6f70a4..d7770db 100644 --- a/src/config.rs +++ b/src/config.rs @@ -120,12 +120,12 @@ fn extract_curly_brace(line: &str) -> Vec { let comma_separated_items = line[start_index + 1..end_index].split(','); for item in comma_separated_items { - let mut push_direct_output = || { + let mut push_one_item = || { output.push(format!("{}{}{}", str_before_braces, item.trim(), str_after_braces)); }; if !item.contains('-') { - push_direct_output(); + push_one_item(); continue; } @@ -138,14 +138,14 @@ fn extract_curly_brace(line: &str) -> Vec { if let Some(b) = range.next() { begin_char = b; } else { - push_direct_output(); + push_one_item(); continue; } if let Some(e) = range.next() { end_char = e; } else { - push_direct_output(); + push_one_item(); continue; } @@ -154,7 +154,7 @@ fn extract_curly_brace(line: &str) -> Vec { // Beginning of the range cannot be greater than end // Example invalid: {9,4} {3,2} if begin_char.len() != 1 || end_char.len() != 1 || begin_char > end_char { - push_direct_output(); + push_one_item(); continue; } From ba1ad4845c11c5e4187cbab08292f3f0afb8b045 Mon Sep 17 00:00:00 2001 From: Angelo Fallaria Date: Sun, 13 Feb 2022 16:26:05 +0800 Subject: [PATCH 26/26] refactor(config): order functions by importance To better communicate which functions are more fundamental, `parse_contents` has been put above `parse_keybinds` and `extract_curly_brace`. --- src/config.rs | 238 +++++++++++++++++++++++++------------------------- 1 file changed, 119 insertions(+), 119 deletions(-) diff --git a/src/config.rs b/src/config.rs index d7770db..27d2c37 100644 --- a/src/config.rs +++ b/src/config.rs @@ -93,125 +93,6 @@ fn load_file_contents(path: path::PathBuf) -> Result { Ok(contents) } -fn extract_curly_brace(line: &str) -> Vec { - if !line.is_ascii() { - return vec![line.to_string()]; - } - let mut output: Vec = Vec::new(); - - let index_open_brace = line.chars().position(|c| c == '{'); - let index_close_brace = line.chars().position(|c| c == '}'); - - if index_open_brace.is_none() || index_close_brace.is_none() { - return vec![line.to_string()]; - } - - let start_index = index_open_brace.unwrap(); - let end_index = index_close_brace.unwrap(); - - // There are no expansions to build if } is earlier than { - if start_index >= end_index { - return vec![line.to_string()]; - } - - let str_before_braces = line[..start_index].to_string(); - let str_after_braces = line[end_index + 1..].to_string(); - - let comma_separated_items = line[start_index + 1..end_index].split(','); - - for item in comma_separated_items { - let mut push_one_item = || { - output.push(format!("{}{}{}", str_before_braces, item.trim(), str_after_braces)); - }; - - if !item.contains('-') { - push_one_item(); - continue; - } - - // Parse dash ranges like {1-5} and {a-f} - - let mut range = item.split('-').map(|s| s.trim()); - let begin_char: &str; - let end_char: &str; - - if let Some(b) = range.next() { - begin_char = b; - } else { - push_one_item(); - continue; - } - - if let Some(e) = range.next() { - end_char = e; - } else { - push_one_item(); - continue; - } - - // Do not accept range values that are longer than one char - // Example invalid: {ef,p} {3,56} - // Beginning of the range cannot be greater than end - // Example invalid: {9,4} {3,2} - if begin_char.len() != 1 || end_char.len() != 1 || begin_char > end_char { - push_one_item(); - continue; - } - - // In swhkd we will parse the full range using ASCII values. - - let begin_ascii_val = begin_char.parse::().unwrap() as u8; - let end_ascii_val = end_char.parse::().unwrap() as u8; - - for ascii_number in begin_ascii_val..=end_ascii_val { - output - .push(format!("{}{}{}", str_before_braces, ascii_number as char, str_after_braces)); - } - } - output -} - -// We need to get the reference to key_to_evdev_key -// and mod_to_mod enum instead of recreating them -// after each function call because it's too expensive -fn parse_keybind( - line: &str, - line_nr: u32, - key_to_evdev_key: &HashMap<&str, evdev::Key>, - mod_to_mod_enum: &HashMap<&str, Modifier>, -) -> Result<(evdev::Key, Vec), Error> { - let line = line.split('#').next().unwrap(); - let tokens: Vec = line.split('+').map(|s| s.trim().to_lowercase()).collect(); - let last_token = tokens.last().unwrap().trim(); - - // Check if each token is valid - for token in &tokens { - if key_to_evdev_key.contains_key(token.as_str()) { - // Can't have a key that's like a modifier - if token != last_token { - return Err(Error::InvalidConfig(ParseError::InvalidModifier(line_nr))); - } - } else if mod_to_mod_enum.contains_key(token.as_str()) { - // Can't have a modifier that's like a modifier - if token == last_token { - return Err(Error::InvalidConfig(ParseError::InvalidKeysym(line_nr))); - } - } else { - return Err(Error::InvalidConfig(ParseError::UnknownSymbol(line_nr))); - } - } - - // Translate keypress into evdev key - let keysym = key_to_evdev_key.get(last_token).unwrap(); - - let modifiers: Vec = tokens[0..(tokens.len() - 1)] - .iter() - .map(|token| *mod_to_mod_enum.get(token.as_str()).unwrap()) - .collect(); - - Ok((*keysym, modifiers)) -} - fn parse_contents(contents: String) -> Result, Error> { let key_to_evdev_key: HashMap<&str, evdev::Key> = HashMap::from([ ("q", evdev::Key::KEY_Q), @@ -410,6 +291,125 @@ fn parse_contents(contents: String) -> Result, Error> { Ok(hotkeys) } +// We need to get the reference to key_to_evdev_key +// and mod_to_mod enum instead of recreating them +// after each function call because it's too expensive +fn parse_keybind( + line: &str, + line_nr: u32, + key_to_evdev_key: &HashMap<&str, evdev::Key>, + mod_to_mod_enum: &HashMap<&str, Modifier>, +) -> Result<(evdev::Key, Vec), Error> { + let line = line.split('#').next().unwrap(); + let tokens: Vec = line.split('+').map(|s| s.trim().to_lowercase()).collect(); + let last_token = tokens.last().unwrap().trim(); + + // Check if each token is valid + for token in &tokens { + if key_to_evdev_key.contains_key(token.as_str()) { + // Can't have a key that's like a modifier + if token != last_token { + return Err(Error::InvalidConfig(ParseError::InvalidModifier(line_nr))); + } + } else if mod_to_mod_enum.contains_key(token.as_str()) { + // Can't have a modifier that's like a modifier + if token == last_token { + return Err(Error::InvalidConfig(ParseError::InvalidKeysym(line_nr))); + } + } else { + return Err(Error::InvalidConfig(ParseError::UnknownSymbol(line_nr))); + } + } + + // Translate keypress into evdev key + let keysym = key_to_evdev_key.get(last_token).unwrap(); + + let modifiers: Vec = tokens[0..(tokens.len() - 1)] + .iter() + .map(|token| *mod_to_mod_enum.get(token.as_str()).unwrap()) + .collect(); + + Ok((*keysym, modifiers)) +} + +fn extract_curly_brace(line: &str) -> Vec { + if !line.is_ascii() { + return vec![line.to_string()]; + } + let mut output: Vec = Vec::new(); + + let index_open_brace = line.chars().position(|c| c == '{'); + let index_close_brace = line.chars().position(|c| c == '}'); + + if index_open_brace.is_none() || index_close_brace.is_none() { + return vec![line.to_string()]; + } + + let start_index = index_open_brace.unwrap(); + let end_index = index_close_brace.unwrap(); + + // There are no expansions to build if } is earlier than { + if start_index >= end_index { + return vec![line.to_string()]; + } + + let str_before_braces = line[..start_index].to_string(); + let str_after_braces = line[end_index + 1..].to_string(); + + let comma_separated_items = line[start_index + 1..end_index].split(','); + + for item in comma_separated_items { + let mut push_one_item = || { + output.push(format!("{}{}{}", str_before_braces, item.trim(), str_after_braces)); + }; + + if !item.contains('-') { + push_one_item(); + continue; + } + + // Parse dash ranges like {1-5} and {a-f} + + let mut range = item.split('-').map(|s| s.trim()); + let begin_char: &str; + let end_char: &str; + + if let Some(b) = range.next() { + begin_char = b; + } else { + push_one_item(); + continue; + } + + if let Some(e) = range.next() { + end_char = e; + } else { + push_one_item(); + continue; + } + + // Do not accept range values that are longer than one char + // Example invalid: {ef,p} {3,56} + // Beginning of the range cannot be greater than end + // Example invalid: {9,4} {3,2} + if begin_char.len() != 1 || end_char.len() != 1 || begin_char > end_char { + push_one_item(); + continue; + } + + // In swhkd we will parse the full range using ASCII values. + + let begin_ascii_val = begin_char.parse::().unwrap() as u8; + let end_ascii_val = end_char.parse::().unwrap() as u8; + + for ascii_number in begin_ascii_val..=end_ascii_val { + output + .push(format!("{}{}{}", str_before_braces, ascii_number as char, str_after_braces)); + } + } + output +} + #[cfg(test)] mod tests { use super::*;