From 25e23ae0b3d0981730073a3ecf2cdd310bb76e83 Mon Sep 17 00:00:00 2001 From: jtroo Date: Sun, 3 Nov 2024 13:47:25 -0800 Subject: [PATCH] feat(wasm): simulate files --- parser/src/cfg/mod.rs | 8 +- src/kanata/mod.rs | 2 +- src/kanata/output_logic/zippychord.rs | 12 +- src/tests/sim_tests/mod.rs | 13 +- src/tests/sim_tests/zippychord_sim_tests.rs | 144 ++++++++++---------- wasm/Cargo.toml | 3 +- wasm/src/lib.rs | 49 ++++++- 7 files changed, 144 insertions(+), 87 deletions(-) diff --git a/parser/src/cfg/mod.rs b/parser/src/cfg/mod.rs index ce4dc1236..342283bc1 100755 --- a/parser/src/cfg/mod.rs +++ b/parser/src/cfg/mod.rs @@ -283,16 +283,18 @@ pub fn new_from_file(p: &Path) -> MResult { parse_cfg(p) } -pub fn new_from_str(cfg_text: &str, file_content: Option) -> MResult { +pub fn new_from_str(cfg_text: &str, file_content: HashMap) -> MResult { let mut s = ParserState::default(); let icfg = parse_cfg_raw_string( cfg_text, &mut s, &PathBuf::from("configuration"), &mut FileContentProvider { - get_file_content_fn: &mut move |_| match &file_content { + get_file_content_fn: &mut move |fname| match file_content + .get(fname.to_string_lossy().as_ref()) + { Some(s) => Ok(s.clone()), - None => Err("include is not supported".into()), + None => Err("File is not known".into()), }, }, DEF_LOCAL_KEYS, diff --git a/src/kanata/mod.rs b/src/kanata/mod.rs index fc31e9367..ed3c0b7aa 100755 --- a/src/kanata/mod.rs +++ b/src/kanata/mod.rs @@ -430,7 +430,7 @@ impl Kanata { Ok(Arc::new(Mutex::new(Self::new(args)?))) } - pub fn new_from_str(cfg: &str, file_content: Option) -> Result { + pub fn new_from_str(cfg: &str, file_content: HashMap) -> Result { let cfg = match cfg::new_from_str(cfg, file_content) { Ok(c) => c, Err(e) => { diff --git a/src/kanata/output_logic/zippychord.rs b/src/kanata/output_logic/zippychord.rs index 40cea1c0a..86c174e60 100644 --- a/src/kanata/output_logic/zippychord.rs +++ b/src/kanata/output_logic/zippychord.rs @@ -358,7 +358,7 @@ impl ZchState { kb.release_key(OsCode::KEY_RIGHTALT)?; } - for key_to_send in &a.zch_output { + for key_to_send in a.zch_output.iter().copied() { #[cfg(feature = "interception_driver")] { // Note: every 5 keys on Windows Interception, do a sleep because @@ -372,12 +372,12 @@ impl ZchState { let typed_osc = match key_to_send { ZchOutput::Lowercase(osc) => { - type_osc(*osc, kb, &self.zchd)?; + type_osc(osc, kb, &self.zchd)?; osc } ZchOutput::Uppercase(osc) => { maybe_press_sft_during_activation(released_sft, kb, &self.zchd)?; - type_osc(*osc, kb, &self.zchd)?; + type_osc(osc, kb, &self.zchd)?; maybe_release_sft_during_activation(released_sft, kb, &self.zchd)?; osc } @@ -392,21 +392,21 @@ impl ZchState { // always released at the beginning and pressed at the end if it was // previously being held. kb.press_key(OsCode::KEY_RIGHTALT)?; - type_osc(*osc, kb, &self.zchd)?; + type_osc(osc, kb, &self.zchd)?; kb.release_key(OsCode::KEY_RIGHTALT)?; osc } ZchOutput::ShiftAltGr(osc) => { kb.press_key(OsCode::KEY_RIGHTALT)?; maybe_press_sft_during_activation(released_sft, kb, &self.zchd)?; - type_osc(*osc, kb, &self.zchd)?; + type_osc(osc, kb, &self.zchd)?; maybe_release_sft_during_activation(released_sft, kb, &self.zchd)?; kb.release_key(OsCode::KEY_RIGHTALT)?; osc } }; - if *typed_osc == OsCode::KEY_BACKSPACE { + if typed_osc == OsCode::KEY_BACKSPACE { self.zchd.zchd_characters_to_delete_on_next_activation -= 1; // Improvement: there are many other keycodes that might be sent that // aren't printable. But for now, just include backspace. diff --git a/src/tests/sim_tests/mod.rs b/src/tests/sim_tests/mod.rs index 6c5522843..84509720a 100644 --- a/src/tests/sim_tests/mod.rs +++ b/src/tests/sim_tests/mod.rs @@ -10,6 +10,8 @@ use crate::{ str_to_oscode, Kanata, }; +use rustc_hash::FxHashMap; + mod block_keys_tests; mod capsword_sim_tests; mod chord_sim_tests; @@ -24,17 +26,20 @@ mod unmod_sim_tests; mod zippychord_sim_tests; fn simulate>(cfg: S, sim: S) -> String { - simulate_with_file_content(cfg, sim, None) + simulate_with_file_content(cfg, sim, Default::default()) } -fn simulate_with_file_content>(cfg: S, sim: S, file_content: Option) -> String { +fn simulate_with_file_content>( + cfg: S, + sim: S, + file_content: FxHashMap, +) -> String { init_log(); let _lk = match CFG_PARSE_LOCK.lock() { Ok(guard) => guard, Err(poisoned) => poisoned.into_inner(), }; - let mut k = Kanata::new_from_str(cfg.as_ref(), file_content.map(|s| s.as_ref().to_owned())) - .expect("failed to parse cfg"); + let mut k = Kanata::new_from_str(cfg.as_ref(), file_content).expect("failed to parse cfg"); for pair in sim.as_ref().split_whitespace() { match pair.split_once(':') { Some((kind, val)) => match kind { diff --git a/src/tests/sim_tests/zippychord_sim_tests.rs b/src/tests/sim_tests/zippychord_sim_tests.rs index 181e13989..abbfed4da 100644 --- a/src/tests/sim_tests/zippychord_sim_tests.rs +++ b/src/tests/sim_tests/zippychord_sim_tests.rs @@ -20,13 +20,19 @@ rqa request␣assistance 1234 bye "; +fn simulate_with_zippy_file_content(cfg: &str, input: &str, content: &str) -> String { + let mut fcontent = FxHashMap::default(); + fcontent.insert("file".into(), content.into()); + simulate_with_file_content(cfg, input, fcontent) +} + #[test] fn sim_zippychord_capitalize() { - let result = simulate_with_file_content( + let result = simulate_with_zippy_file_content( ZIPPY_CFG, "d:a t:10 d:b t:10 d:spc t:10 d:c u:a u:b u:c u:spc t:300 \ d:a t:10 d:b t:10 d:spc t:10 d:c t:300", - Some(ZIPPY_FILE_CONTENT), + ZIPPY_FILE_CONTENT, ) .to_ascii(); assert_eq!( @@ -45,10 +51,10 @@ fn sim_zippychord_capitalize() { #[test] fn sim_zippychord_followup_with_prev() { - let result = simulate_with_file_content( + let result = simulate_with_zippy_file_content( ZIPPY_CFG, "d:d t:10 d:y t:10 u:d u:y t:10 d:1 t:300", - Some(ZIPPY_FILE_CONTENT), + ZIPPY_FILE_CONTENT, ) .to_ascii(); assert_eq!( @@ -63,10 +69,10 @@ fn sim_zippychord_followup_with_prev() { #[test] fn sim_zippychord_followup_no_prev() { - let result = simulate_with_file_content( + let result = simulate_with_zippy_file_content( ZIPPY_CFG, "d:r t:10 u:r t:10 d:d d:f t:10 t:300", - Some(ZIPPY_FILE_CONTENT), + ZIPPY_FILE_CONTENT, ) .to_ascii(); assert_eq!( @@ -79,13 +85,13 @@ fn sim_zippychord_followup_no_prev() { #[test] fn sim_zippychord_washington() { - let result = simulate_with_file_content( + let result = simulate_with_zippy_file_content( ZIPPY_CFG, "d:w d:spc t:10 u:w u:spc t:10 d:a d:spc t:10 u:a u:spc t:300", - Some(ZIPPY_FILE_CONTENT), + ZIPPY_FILE_CONTENT, ) .to_ascii(); assert_eq!( @@ -100,10 +106,10 @@ fn sim_zippychord_washington() { #[test] fn sim_zippychord_overlap() { - let result = simulate_with_file_content( + let result = simulate_with_zippy_file_content( ZIPPY_CFG, "d:r t:10 d:q t:10 d:a t:10", - Some(ZIPPY_FILE_CONTENT), + ZIPPY_FILE_CONTENT, ) .to_ascii(); assert_eq!( @@ -117,7 +123,7 @@ fn sim_zippychord_overlap() { result ); let result = - simulate_with_file_content(ZIPPY_CFG, "d:1 d:2 d:3 d:4 t:20", Some(ZIPPY_FILE_CONTENT)) + simulate_with_zippy_file_content(ZIPPY_CFG, "d:1 d:2 d:3 d:4 t:20", ZIPPY_FILE_CONTENT) .to_ascii(); assert_eq!( "dn:Kb1 t:1ms dn:BSpace up:BSpace dn:H up:H dn:I up:I t:1ms dn:Kb3 t:1ms \ @@ -130,20 +136,20 @@ fn sim_zippychord_overlap() { #[test] fn sim_zippychord_lsft() { // test lsft behaviour while pressed - let result = simulate_with_file_content( + let result = simulate_with_zippy_file_content( ZIPPY_CFG, "d:lsft t:10 d:d t:10 d:y t:10", - Some(ZIPPY_FILE_CONTENT), + ZIPPY_FILE_CONTENT, ) .to_ascii(); assert_eq!( "dn:LShift t:10ms dn:D t:10ms dn:BSpace up:BSpace up:D dn:D up:LShift dn:A up:A up:Y dn:Y dn:LShift", result ); - let result = simulate_with_file_content( + let result = simulate_with_zippy_file_content( ZIPPY_CFG, "d:lsft t:10 d:x t:10 d:y t:10", - Some(ZIPPY_FILE_CONTENT), + ZIPPY_FILE_CONTENT, ) .to_ascii(); assert_eq!( @@ -153,20 +159,20 @@ fn sim_zippychord_lsft() { ); // ensure lsft-held behaviour goes away when released - let result = simulate_with_file_content( + let result = simulate_with_zippy_file_content( ZIPPY_CFG, "d:lsft t:10 d:d u:lsft t:10 d:y t:10", - Some(ZIPPY_FILE_CONTENT), + ZIPPY_FILE_CONTENT, ) .to_ascii(); assert_eq!( "dn:LShift t:10ms dn:D t:1ms up:LShift t:9ms dn:BSpace up:BSpace up:D dn:D dn:A up:A up:Y dn:Y", result ); - let result = simulate_with_file_content( + let result = simulate_with_zippy_file_content( ZIPPY_CFG, "d:lsft t:10 d:x u:lsft t:10 d:y t:10", - Some(ZIPPY_FILE_CONTENT), + ZIPPY_FILE_CONTENT, ) .to_ascii(); assert_eq!( @@ -179,20 +185,20 @@ fn sim_zippychord_lsft() { #[test] fn sim_zippychord_rsft() { // test rsft behaviour while pressed - let result = simulate_with_file_content( + let result = simulate_with_zippy_file_content( ZIPPY_CFG, "d:rsft t:10 d:d t:10 d:y t:10", - Some(ZIPPY_FILE_CONTENT), + ZIPPY_FILE_CONTENT, ) .to_ascii(); assert_eq!( "dn:RShift t:10ms dn:D t:10ms dn:BSpace up:BSpace up:D dn:D up:RShift dn:A up:A up:Y dn:Y dn:RShift", result ); - let result = simulate_with_file_content( + let result = simulate_with_zippy_file_content( ZIPPY_CFG, "d:rsft t:10 d:x t:10 d:y t:10", - Some(ZIPPY_FILE_CONTENT), + ZIPPY_FILE_CONTENT, ) .to_ascii(); assert_eq!( @@ -202,20 +208,20 @@ fn sim_zippychord_rsft() { ); // ensure rsft-held behaviour goes away when released - let result = simulate_with_file_content( + let result = simulate_with_zippy_file_content( ZIPPY_CFG, "d:rsft t:10 d:d u:rsft t:10 d:y t:10", - Some(ZIPPY_FILE_CONTENT), + ZIPPY_FILE_CONTENT, ) .to_ascii(); assert_eq!( "dn:RShift t:10ms dn:D t:1ms up:RShift t:9ms dn:BSpace up:BSpace up:D dn:D dn:A up:A up:Y dn:Y", result ); - let result = simulate_with_file_content( + let result = simulate_with_zippy_file_content( ZIPPY_CFG, "d:rsft t:10 d:x u:rsft t:10 d:y t:10", - Some(ZIPPY_FILE_CONTENT), + ZIPPY_FILE_CONTENT, ) .to_ascii(); assert_eq!( @@ -227,10 +233,10 @@ fn sim_zippychord_rsft() { #[test] fn sim_zippychord_caps_word() { - let result = simulate_with_file_content( + let result = simulate_with_zippy_file_content( ZIPPY_CFG, "d:lalt u:lalt t:10 d:d t:10 d:y t:10 u:d u:y t:10 d:spc u:spc t:2000 d:d d:y t:10", - Some(ZIPPY_FILE_CONTENT), + ZIPPY_FILE_CONTENT, ) .to_ascii(); assert_eq!( @@ -239,10 +245,10 @@ fn sim_zippychord_caps_word() { t:1999ms dn:D t:1ms dn:BSpace up:BSpace up:D dn:D dn:A up:A up:Y dn:Y", result ); - let result = simulate_with_file_content( + let result = simulate_with_zippy_file_content( ZIPPY_CFG, "d:lalt t:10 d:y t:10 d:x t:10 u:x u:y t:10 d:spc u:spc t:1000 d:y d:x t:10", - Some(ZIPPY_FILE_CONTENT), + ZIPPY_FILE_CONTENT, ) .to_ascii(); assert_eq!( @@ -257,10 +263,10 @@ fn sim_zippychord_caps_word() { #[test] fn sim_zippychord_triple_combo() { - let result = simulate_with_file_content( + let result = simulate_with_zippy_file_content( ZIPPY_CFG, "d:. d:g t:10 u:. u:g d:f t:10 u:f d:p t:10", - Some(ZIPPY_FILE_CONTENT), + ZIPPY_FILE_CONTENT, ) .to_ascii(); assert_eq!( @@ -276,10 +282,10 @@ fn sim_zippychord_triple_combo() { #[test] fn sim_zippychord_disabled_by_typing() { - let result = simulate_with_file_content( + let result = simulate_with_zippy_file_content( ZIPPY_CFG, "d:v u:v t:10 d:d d:y t:100", - Some(ZIPPY_FILE_CONTENT), + ZIPPY_FILE_CONTENT, ) .to_ascii(); assert_eq!("dn:V t:1ms up:V t:9ms dn:D t:1ms dn:Y", result); @@ -287,10 +293,10 @@ fn sim_zippychord_disabled_by_typing() { #[test] fn sim_zippychord_prefix() { - let result = simulate_with_file_content( + let result = simulate_with_zippy_file_content( ZIPPY_CFG, "d:p d:r u:p u:r t:10 d:q u:q t:10", - Some(ZIPPY_FILE_CONTENT), + ZIPPY_FILE_CONTENT, ) .to_ascii(); assert_eq!( @@ -301,10 +307,10 @@ fn sim_zippychord_prefix() { dn:R up:R dn:E up:E up:Q dn:Q dn:U up:U dn:E up:E dn:S up:S dn:T up:T t:1ms up:Q", result ); - let result = simulate_with_file_content( + let result = simulate_with_zippy_file_content( ZIPPY_CFG, "d:p d:r d:a t:10 u:d u:r u:a", - Some(ZIPPY_FILE_CONTENT), + ZIPPY_FILE_CONTENT, ) .to_ascii() .no_time() @@ -319,11 +325,11 @@ fn sim_zippychord_prefix() { #[test] fn sim_zippychord_smartspace_full() { - let result = simulate_with_file_content( + let result = simulate_with_zippy_file_content( "(defsrc)(deflayer base)(defzippy-experimental file smart-space full)", "d:d d:y t:10 u:d u:y t:100 d:. t:10 u:. t:10", - Some(ZIPPY_FILE_CONTENT), + ZIPPY_FILE_CONTENT, ) .to_ascii(); assert_eq!( @@ -333,11 +339,11 @@ fn sim_zippychord_smartspace_full() { ); // Test that prefix works as intended. - let result = simulate_with_file_content( + let result = simulate_with_zippy_file_content( "(defsrc)(deflayer base)(defzippy-experimental file smart-space add-space-only)", "d:p d:r t:10 u:p u:r t:100 d:. t:10 u:. t:10", - Some(ZIPPY_FILE_CONTENT), + ZIPPY_FILE_CONTENT, ) .to_ascii(); assert_eq!( @@ -350,11 +356,11 @@ fn sim_zippychord_smartspace_full() { #[test] fn sim_zippychord_smartspace_spaceonly() { - let result = simulate_with_file_content( + let result = simulate_with_zippy_file_content( "(defsrc)(deflayer base)(defzippy-experimental file smart-space add-space-only)", "d:d d:y t:10 u:d u:y t:100 d:. t:10 u:. t:10", - Some(ZIPPY_FILE_CONTENT), + ZIPPY_FILE_CONTENT, ) .to_ascii(); assert_eq!( @@ -364,11 +370,11 @@ fn sim_zippychord_smartspace_spaceonly() { ); // Test that prefix works as intended. - let result = simulate_with_file_content( + let result = simulate_with_zippy_file_content( "(defsrc)(deflayer base)(defzippy-experimental file smart-space add-space-only)", "d:p d:r t:10 u:p u:r t:100 d:. t:10 u:. t:10", - Some(ZIPPY_FILE_CONTENT), + ZIPPY_FILE_CONTENT, ) .to_ascii(); assert_eq!( @@ -381,11 +387,11 @@ fn sim_zippychord_smartspace_spaceonly() { #[test] fn sim_zippychord_smartspace_none() { - let result = simulate_with_file_content( + let result = simulate_with_zippy_file_content( "(defsrc)(deflayer base)(defzippy-experimental file smart-space none)", "d:d d:y t:10 u:d u:y t:100 d:. t:10 u:. t:10", - Some(ZIPPY_FILE_CONTENT), + ZIPPY_FILE_CONTENT, ) .to_ascii(); assert_eq!( @@ -395,11 +401,11 @@ fn sim_zippychord_smartspace_none() { ); // Test that prefix works as intended. - let result = simulate_with_file_content( + let result = simulate_with_zippy_file_content( "(defsrc)(deflayer base)(defzippy-experimental file smart-space add-space-only)", "d:p d:r t:10 u:p u:r t:100 d:. t:10 u:. t:10", - Some(ZIPPY_FILE_CONTENT), + ZIPPY_FILE_CONTENT, ) .to_ascii(); assert_eq!( @@ -412,11 +418,11 @@ fn sim_zippychord_smartspace_none() { #[test] fn sim_zippychord_smartspace_overlap() { - let result = simulate_with_file_content( + let result = simulate_with_zippy_file_content( "(defsrc)(deflayer base)(defzippy-experimental file smart-space full)", "d:r t:10 d:q t:10 d:a t:10", - Some(ZIPPY_FILE_CONTENT), + ZIPPY_FILE_CONTENT, ) .to_ascii(); assert_eq!( @@ -430,11 +436,11 @@ fn sim_zippychord_smartspace_overlap() { dn:Space up:Space", result ); - let result = simulate_with_file_content( + let result = simulate_with_zippy_file_content( "(defsrc)(deflayer base)(defzippy-experimental file smart-space full)", "d:1 d:2 d:3 d:4 t:20", - Some(ZIPPY_FILE_CONTENT), + ZIPPY_FILE_CONTENT, ) .to_ascii(); assert_eq!( @@ -448,11 +454,11 @@ fn sim_zippychord_smartspace_overlap() { #[test] fn sim_zippychord_smartspace_followup() { - let result = simulate_with_file_content( + let result = simulate_with_zippy_file_content( "(defsrc)(deflayer base)(defzippy-experimental file smart-space full)", "d:d t:10 d:y t:10 u:d u:y t:10 d:1 t:300", - Some(ZIPPY_FILE_CONTENT), + ZIPPY_FILE_CONTENT, ) .to_ascii(); assert_eq!( @@ -479,10 +485,10 @@ const CUSTOM_PUNC_CFG: &str = "\ #[test] fn sim_zippychord_smartspace_custom_punc() { // 1 without lsft: no smart-space-erase - let result = simulate_with_file_content( + let result = simulate_with_zippy_file_content( CUSTOM_PUNC_CFG, "d:d t:10 d:y t:10 u:d u:y t:10 d:1 t:300", - Some(ZIPPY_FILE_CONTENT), + ZIPPY_FILE_CONTENT, ) .to_ascii(); assert_eq!( @@ -495,10 +501,10 @@ fn sim_zippychord_smartspace_custom_punc() { ); // S-1 = !: smart-space-erase - let result = simulate_with_file_content( + let result = simulate_with_zippy_file_content( CUSTOM_PUNC_CFG, "d:1 d:2 t:10 u:1 u:2 t:10 d:lsft d:1 u:1 u:lsft t:300", - Some(ZIPPY_FILE_CONTENT), + ZIPPY_FILE_CONTENT, ) .to_ascii(); assert_eq!( @@ -510,10 +516,10 @@ fn sim_zippychord_smartspace_custom_punc() { ); // z: smart-space-erase - let result = simulate_with_file_content( + let result = simulate_with_zippy_file_content( CUSTOM_PUNC_CFG, "d:1 d:2 t:10 u:1 u:2 t:10 d:z u:z t:300", - Some(ZIPPY_FILE_CONTENT), + ZIPPY_FILE_CONTENT, ) .to_ascii(); assert_eq!( @@ -525,10 +531,10 @@ fn sim_zippychord_smartspace_custom_punc() { ); // r no altgr: no smart-space-erase - let result = simulate_with_file_content( + let result = simulate_with_zippy_file_content( CUSTOM_PUNC_CFG, "d:1 d:2 t:10 u:1 u:2 t:10 d:r u:r t:300", - Some(ZIPPY_FILE_CONTENT), + ZIPPY_FILE_CONTENT, ) .to_ascii(); assert_eq!( @@ -540,10 +546,10 @@ fn sim_zippychord_smartspace_custom_punc() { ); // r with altgr: smart-space-erase - let result = simulate_with_file_content( + let result = simulate_with_zippy_file_content( CUSTOM_PUNC_CFG, "d:1 d:2 t:10 u:1 u:2 t:10 d:ralt d:r u:r u:ralt t:300", - Some(ZIPPY_FILE_CONTENT), + ZIPPY_FILE_CONTENT, ) .to_ascii(); assert_eq!( @@ -555,10 +561,10 @@ fn sim_zippychord_smartspace_custom_punc() { ); // v with altgr+lsft: smart-space-erase - let result = simulate_with_file_content( + let result = simulate_with_zippy_file_content( CUSTOM_PUNC_CFG, "d:1 d:2 t:10 u:1 u:2 t:10 d:ralt d:lsft d:v u:v u:ralt u:lsft t:300", - Some(ZIPPY_FILE_CONTENT), + ZIPPY_FILE_CONTENT, ) .to_ascii(); assert_eq!( diff --git a/wasm/Cargo.toml b/wasm/Cargo.toml index 5d3f570cc..bcf9d6535 100644 --- a/wasm/Cargo.toml +++ b/wasm/Cargo.toml @@ -11,7 +11,8 @@ crate-type = [ "cdylib", "rlib" ] [dependencies] wasm-bindgen = "0.2.95" -kanata = { path = ".." , default-features = false, features = [ "simulated_output", "wasm" ] } +kanata = { path = ".." , default-features = false, features = [ "simulated_output", "wasm", "zippychord" ] } anyhow = "1.0.81" log = "0.4.21" console_error_panic_hook = "0.1.7" +rustc-hash = "1.1.0" diff --git a/wasm/src/lib.rs b/wasm/src/lib.rs index 630b70377..9e7fe1cbe 100644 --- a/wasm/src/lib.rs +++ b/wasm/src/lib.rs @@ -1,5 +1,6 @@ use anyhow::{anyhow, bail, Result}; use kanata_state_machine::{oskbd::*, *}; +use rustc_hash::FxHashMap; use wasm_bindgen::prelude::*; use std::sync::Once; @@ -15,7 +16,8 @@ pub fn init() { #[wasm_bindgen] pub fn check_config(cfg: &str) -> JsValue { - let res = Kanata::new_from_str(cfg, None); + let (cfg, files) = split_cfg_and_sim_files(cfg); + let res = Kanata::new_from_str(&cfg, files); JsValue::from_str(&match res { Ok(_) => "Config is good!".to_owned(), Err(e) => format!("{e:?}"), @@ -30,8 +32,49 @@ pub fn simulate(cfg: &str, sim: &str) -> JsValue { }) } -pub fn simulate_impl(cfg: &str, sim: &str) -> Result { - let mut k = Kanata::new_from_str(cfg, None)?; +fn split_cfg_and_sim_files(original_cfg: &str) -> (String, FxHashMap) { + let mut cfg = String::new(); + let mut file_name = None; + let mut file = String::new(); + let mut sim_files = Default::default(); + + let mut original_lines = original_cfg.lines(); + const FILE_PREFIX: &str = "=== file:"; + + // Parse main configuration. + // Must not consume whole iterator here. + #[allow(clippy::while_let_on_iterator)] + while let Some(line) = original_lines.next() { + if line.starts_with(FILE_PREFIX) { + file_name = line.strip_prefix(FILE_PREFIX); + break; + } + cfg.push_str(line); + cfg.push('\n'); + } + if file_name.is_none() { + return (cfg, sim_files); + } + + // Parse simulated sim_files. + for line in original_lines { + if line.starts_with(FILE_PREFIX) { + sim_files.insert(file_name.unwrap().to_string(), file.clone()); + file_name = line.strip_prefix(FILE_PREFIX); + file.clear(); + continue; + } + file.push_str(line); + file.push('\n'); + } + // Save the last file + sim_files.insert(file_name.unwrap().to_string(), file.clone()); + (cfg, sim_files) +} + +fn simulate_impl(cfg: &str, sim: &str) -> Result { + let (cfg, files) = split_cfg_and_sim_files(cfg); + let mut k = Kanata::new_from_str(&cfg, files)?; let mut accumulated_ticks = 0; for l in sim.lines() { for pair in l.split_whitespace() {