diff --git a/cfg_samples/kanata.kbd b/cfg_samples/kanata.kbd index a96a533b0..1fb4ecef5 100644 --- a/cfg_samples/kanata.kbd +++ b/cfg_samples/kanata.kbd @@ -931,12 +931,16 @@ If you need help, please feel welcome to ask in the GitHub discussions. ;; output a chord like `ctrl+c` if the previous key pressed was `C-c` - it ;; will only output `c`. There is a variant `rpt-any` which will repeat the ;; previous action and would work for that use case. +;; +;; The `arpt` action functions the same as `rpt` takes a list of substitutions +;; as argument. Here, the keys most keys are repeated upon pressing this key, +;; except `d` and `u`, who will send `e` and `n` respectively. (deflayer misc _ _ _ _ _ _ _ _ _ @é @è _ ì #|random custom key for testing|# _ _ _ @ab1 _ _ _ ins @{ @} [ ] _ _ + @cw _ _ _ C-u _ del bspc esc ret _ _ _ @cwc C-z C-x C-c C-v _ _ _ @td @os1 @os2 @os3 - rpt rpt-any _ _ _ _ _ + rpt rpt-any _ _ _ _ (arpt (d e u n)) ) diff --git a/docs/config.adoc b/docs/config.adoc index 203bd19e8..06b303a70 100644 --- a/docs/config.adoc +++ b/docs/config.adoc @@ -1139,6 +1139,10 @@ The output chord prefix strings are: | `rpt-any` | String action that outputs the most-recently outputted action. + +| `arpt` +| String action that outputs the single most-recently typed key unless a +aubstitution is specified. |=== **Description** @@ -1171,6 +1175,19 @@ and would output `ctrl+c` in the example case. ) ---- +the `arpt` (or `alt-repeat`) action functions the same way as `rpt` but takes a +list of keycode pairs as argument. If the last keycode correspond with the +first keycode in a pair, then the associated keycode will be sent instead. + +In the example below, `s` and `f` are repeated as usual, but pressing `arpt` +after `a` or `d` will send `q` and `e` respectively + +---- +(deflayer has-alt-repeat + (arpt (a q d e)) a s d f +) +---- + [[release-a-key-or-layer]] === Release a key or layer diff --git a/parser/src/cfg/list_actions.rs b/parser/src/cfg/list_actions.rs index 05b34ba12..973237fa1 100644 --- a/parser/src/cfg/list_actions.rs +++ b/parser/src/cfg/list_actions.rs @@ -118,6 +118,8 @@ pub const ON_RELEASE: &str = "on-release"; pub const ON_RELEASE_A: &str = "on↑"; pub const ON_IDLE: &str = "on-idle"; pub const HOLD_FOR_DURATION: &str = "hold-for-duration"; +pub const ALT_REPEAT: &str = "alt-repeat"; +pub const ALT_REPEAT_A: &str = "arpt"; pub fn is_list_action(ac: &str) -> bool { const LIST_ACTIONS: &[&str] = &[ @@ -235,6 +237,8 @@ pub fn is_list_action(ac: &str) -> bool { MACRO_CANCEL_ON_NEXT_PRESS_CANCEL_ON_RELEASE, MACRO_REPEAT_CANCEL_ON_NEXT_PRESS_CANCEL_ON_RELEASE, ONE_SHOT_PAUSE_PROCESSING, + ALT_REPEAT, + ALT_REPEAT_A, ]; LIST_ACTIONS.contains(&ac) } diff --git a/parser/src/cfg/mod.rs b/parser/src/cfg/mod.rs index e23283d43..537737287 100755 --- a/parser/src/cfg/mod.rs +++ b/parser/src/cfg/mod.rs @@ -1805,6 +1805,7 @@ fn parse_action_list(ac: &[SExpr], s: &ParserState) -> Result<&'static KanataAct ON_RELEASE | ON_RELEASE_A => parse_on_release(&ac[1..], s), ON_IDLE => parse_on_idle(&ac[1..], s), HOLD_FOR_DURATION => parse_hold_for_duration(&ac[1..], s), + ALT_REPEAT | ALT_REPEAT_A => parse_alt_repeat(&ac[1..], s), MWHEEL_UP | MWHEEL_UP_A => parse_mwheel(&ac[1..], MWheelDirection::Up, s), MWHEEL_DOWN | MWHEEL_DOWN_A => parse_mwheel(&ac[1..], MWheelDirection::Down, s), MWHEEL_LEFT | MWHEEL_LEFT_A => parse_mwheel(&ac[1..], MWheelDirection::Left, s), @@ -3892,3 +3893,26 @@ fn parse_unmod( _ => panic!("Unknown unmod type {unmod_type}"), } } + +fn parse_alt_repeat(ac_params: &[SExpr], s: &ParserState) -> Result<&'static KanataAction> { + let Some(list) = ac_params.get(0).and_then(|s| s.list(None)) else { + bail!("alt-repeat needs a list of previous-next keycodes") + }; + if list.len() % 2 != 0 { + bail!( + "alt-repeat needs a list of previous-next keycodes, found {} items", + ac_params.len() + ) + } + let mut base_key_set = HashSet::default(); + let key_list = parse_key_list(&ac_params[0], s, "alt-repeat parse key list")?; + for key in key_list.iter().step_by(2) { + if !base_key_set.insert(key) { + bail!("duplicated base keycode in alt-repeat: {}", key) + } + } + use itertools::Itertools; + let substitutions = key_list.iter().tuples().map(|(a, b)| (a.into(), b.into())).collect(); + custom(CustomAction::AltRepeat { substitutions }, &s.a) +} + diff --git a/parser/src/custom_action.rs b/parser/src/custom_action.rs index 600a36b41..02d8ccc51 100644 --- a/parser/src/custom_action.rs +++ b/parser/src/custom_action.rs @@ -64,6 +64,9 @@ pub enum CustomAction { LiveReloadNum(u16), LiveReloadFile(String), Repeat, + AltRepeat { + substitutions: Vec<(KeyCode, KeyCode)>, + }, CancelMacroOnRelease, CancelMacroOnNextPress(u32), DynamicMacroRecord(u16), diff --git a/src/kanata/mod.rs b/src/kanata/mod.rs index ff0a2bc59..9872c20f0 100755 --- a/src/kanata/mod.rs +++ b/src/kanata/mod.rs @@ -1533,6 +1533,35 @@ impl Kanata { self.kbd_out.release_key(OsCode::KEY_LEFTSHIFT)?; } } + CustomAction::AltRepeat { substitutions } => { + let base_keycode = self.last_pressed_key; + let next_keycode = match substitutions.iter().find(|(x, _)| base_keycode == *x) { + Some(&(_, new_keycode)) => new_keycode, + None => base_keycode, + }; + let base_osc: OsCode = base_keycode.into(); + let next_osc: OsCode = next_keycode.into(); + log::debug!("alt-repeating a keypress {base_osc:?} -> {next_osc:?}"); + let mut do_caps_word = false; + if !cur_keys.contains(&KeyCode::LShift) { + if let Some(ref mut cw) = self.caps_word { + cur_keys.push(next_keycode); + let prev_len = cur_keys.len(); + cw.maybe_add_lsft(cur_keys); + if cur_keys.len() > prev_len { + do_caps_word = true; + press_key(&mut self.kbd_out, OsCode::KEY_LEFTSHIFT)?; + } + } + } + // Release key in case the most recently pressed key is still pressed. + release_key(&mut self.kbd_out, base_osc)?; + press_key(&mut self.kbd_out, next_osc)?; + release_key(&mut self.kbd_out, next_osc)?; + if do_caps_word { + self.kbd_out.release_key(OsCode::KEY_LEFTSHIFT)?; + } + }, CustomAction::DynamicMacroRecord(macro_id) => { if let Some((macro_id, prev_recorded_macro)) = begin_record_macro(*macro_id, &mut self.dynamic_macro_record_state)