diff --git a/Cargo.lock b/Cargo.lock index 44038590f..7dc4ce03b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -657,6 +657,7 @@ dependencies = [ "parking_lot", "patricia_tree", "rustc-hash", + "simplelog", "thiserror", ] diff --git a/parser/Cargo.toml b/parser/Cargo.toml index f3b249867..90f0da739 100644 --- a/parser/Cargo.toml +++ b/parser/Cargo.toml @@ -29,8 +29,11 @@ kanata-keyberon = { path = "../keyberon" } bytemuck = "1.15.0" bitflags = "2.5.0" +[dev-dependencies] +simplelog = "0.12.0" + [features] cmd = [] interception_driver = [] gui = [] -lsp = [] \ No newline at end of file +lsp = [] diff --git a/parser/src/cfg/mod.rs b/parser/src/cfg/mod.rs index 6416d1d82..f158dc87f 100755 --- a/parser/src/cfg/mod.rs +++ b/parser/src/cfg/mod.rs @@ -2083,7 +2083,8 @@ fn parse_macro_item_impl<'a>( Ok((events, &acs[1..])) } Ok(Action::Custom(custom)) => Ok((vec![SequenceEvent::Custom(custom)], &acs[1..])), - _ => { + Ok(_) => bail_expr!(&acs[0], "{MACRO_ERR}"), + Err(e) => { if let Some(submacro) = acs[0].list(s.vars()) { // If it's just a list that's not parsable as a usable action, try parsing the // content. @@ -2091,13 +2092,20 @@ fn parse_macro_item_impl<'a>( let mut all_events = vec![]; while !submacro_remainder.is_empty() { let mut events; - (events, submacro_remainder) = parse_macro_item(submacro_remainder, s)?; + (events, submacro_remainder) = + parse_macro_item(submacro_remainder, s).map_err(|_e| e.clone())?; all_events.append(&mut events); } return Ok((all_events, &acs[1..])); } - let (held_mods, unparsed_str) = parse_mods_held_for_submacro(&acs[0], s)?; + let (held_mods, unparsed_str) = + parse_mods_held_for_submacro(&acs[0], s).map_err(|mut err| { + if err.msg == MACRO_ERR { + err.msg = format!("{}\n{MACRO_ERR}", &e.msg); + } + err + })?; let mut all_events = vec![]; // First, press all of the modifiers @@ -2113,12 +2121,12 @@ fn parse_macro_item_impl<'a>( // Ensure that the unparsed text is empty since otherwise it means there is // invalid text there if !unparsed_str.is_empty() { - bail_expr!(&acs[0], "{MACRO_ERR}") + bail_expr!(&acs[0], "{}\n{MACRO_ERR}", &e.msg) } // Check for a follow-up list rem_start = 2; if acs.len() < 2 { - bail_expr!(&acs[0], "{MACRO_ERR}") + bail_expr!(&acs[0], "{}\n{MACRO_ERR}", &e.msg) } acs[1] .list(s.vars()) diff --git a/parser/src/cfg/tests.rs b/parser/src/cfg/tests.rs index 9381d63d9..2651c442d 100644 --- a/parser/src/cfg/tests.rs +++ b/parser/src/cfg/tests.rs @@ -7,9 +7,32 @@ use std::sync::{Mutex, MutexGuard}; mod ambiguous; mod environment; +mod macros; static CFG_PARSE_LOCK: Mutex<()> = Mutex::new(()); +fn init_log() { + use simplelog::*; + use std::sync::OnceLock; + static LOG_INIT: OnceLock<()> = OnceLock::new(); + LOG_INIT.get_or_init(|| { + let mut log_cfg = ConfigBuilder::new(); + if let Err(e) = log_cfg.set_time_offset_to_local() { + eprintln!("WARNING: could not set log TZ to local: {e:?}"); + }; + log_cfg.set_time_format_rfc3339(); + CombinedLogger::init(vec![TermLogger::new( + // Note: set to a different level to see logs in tests. + // Also, not all tests call init_log so you might have to add the call there too. + LevelFilter::Error, + log_cfg.build(), + TerminalMode::Stderr, + ColorChoice::AlwaysAnsi, + )]) + .expect("logger can init"); + }); +} + fn lock(lk: &Mutex) -> MutexGuard { match lk.lock() { Ok(guard) => guard, @@ -18,6 +41,7 @@ fn lock(lk: &Mutex) -> MutexGuard { } fn parse_cfg(cfg: &str) -> Result { + init_log(); let _lk = lock(&CFG_PARSE_LOCK); let mut s = ParserState::default(); parse_cfg_raw_string( diff --git a/parser/src/cfg/tests/macros.rs b/parser/src/cfg/tests/macros.rs new file mode 100644 index 000000000..b2619bb3e --- /dev/null +++ b/parser/src/cfg/tests/macros.rs @@ -0,0 +1,30 @@ +use super::*; + +#[test] +fn unsupported_action_in_macro_triggers_error() { + let source = r#" +(defsrc) +(deflayer base) +(defalias a (macro (multi a b c))) "#; + parse_cfg(source) + .map(|_| ()) + .map_err(|e| log::info!("{:?}", miette::Error::from(e))) + .expect_err("errors"); +} + +#[test] +fn incorrectly_configured_supported_action_in_macro_triggers_useful_error() { + let source = r#" +(defsrc) +(deflayer base) +(defalias a (macro (on-press press-vkey does-not-exist))) "#; + parse_cfg(source) + .map(|_| ()) + .map_err(|e| { + let e = miette::Error::from(e); + let msg = format!("{:?}", e); + log::info!("{msg}"); + assert!(msg.contains("unknown virtual key name: does-not-exist")); + }) + .expect_err("errors"); +}