Skip to content

Commit

Permalink
feat: scnl and sequence start with override of defaults (#492)
Browse files Browse the repository at this point in the history
Co-authored-by: jtroo <[email protected]>
  • Loading branch information
DarkKronicle and jtroo committed Jul 20, 2023
1 parent f08aff1 commit 4b1f14f
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 88 deletions.
17 changes: 17 additions & 0 deletions cfg_samples/kanata.kbd
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,11 @@ If you need help, you are welcome to ask.
;; You can add an entry to defcfg to change the sequence timeout (default is 1000):
;; sequence-timeout <number(ms)>
;;
;; If you want multiple timeouts with different leaders, you can also activate the
;; sequence action:
;; (sequence <timeout>)
;; This acts like `sldr` but uses a different timeout.
;;
;; There is also an option to customize the key sequence input mode. Its default
;; value when not configured is `hidden-suppressed`.
;;
Expand All @@ -817,6 +822,18 @@ If you need help, you are welcome to ask.
(deffakekeys git-status (macro g i t spc s t a t u s))
(defalias rcl (tap-hold-release 200 200 sldr rctl))

(defseq
dotcom (. S-3)
dotorg (. S-4)
)
(deffakekeys
dotcom (macro . c o m)
dotorg (macro . o r g)
)
;; Enter sequence mode and input .
(defalias dot-sequence (macro (sequence 250) 10 .))
(defalias dot-sequence-inputmode (macro (sequence 250 hidden-delay-type) 10 .))

;; Input chording.
;;
;; Not to be confused with output chords (like C-S-a or the chords layer
Expand Down
34 changes: 34 additions & 0 deletions docs/config.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1824,6 +1824,15 @@ precisely, the action triggered is:
(defseq git-status (g s t))
(deffakekeys git-status (macro g i t spc s t a t u s))
(defalias rcl (tap-hold-release 200 200 sldr rctl))
(defseq
dotcom (. S-3)
dotorg (. S-4)
)
(deffakekeys
dotcom (macro . c o m)
dotorg (macro . c o m)
)
----

For more context, you can read the
Expand All @@ -1833,6 +1842,31 @@ https://github.com/jtroo/kanata/blob/main/docs/sequence-adding-chords-ideas.md[
the document describing chords is sequences]
to read about how chords in sequences behave.

==== Override the global timeout and input mode

An alternative to using `sldr` is the `sequence` action.
The syntax is `(sequence <timeout>)`.
This enters sequence mode with a sequence timeout
different from the globally configured one.

The `sequence` action can also be called with a second parameter.
The second parameter is an override for `sequence-input-mode`:

----
(sequence <timeout> <input-mode>)
----


.Example:
[source]
----
;; Enter sequence mode and input . with a timeout of 250
(defalias dot-sequence (macro (sequence 250) 10 .))
;; Enter sequence mode and input . with a timeout of 250 and using hidden-delay-type
(defalias dot-sequence (macro (sequence 250 hidden-delay-type) 10 .))
----

[[input-chords]]
=== Input chords
<<table-of-contents,Back to ToC>>
Expand Down
56 changes: 55 additions & 1 deletion parser/src/cfg/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,17 @@ fn parse_cfg_raw_string(
false
}
}),
default_sequence_timeout: cfg
.get(SEQUENCE_TIMEOUT_CFG_NAME)
.map(|s| match str::parse::<u16>(s) {
Ok(0) | Err(_) => Err(anyhow!("{SEQUENCE_TIMEOUT_ERR}")),
Ok(t) => Ok(t),
})
.unwrap_or(Ok(SEQUENCE_TIMEOUT_DEFAULT))?,
default_sequence_input_mode: cfg
.get(SEQUENCE_INPUT_MODE_CFG_NAME)
.map(|s| SequenceInputMode::try_from_str(s.as_str()))
.unwrap_or(Ok(SequenceInputMode::HiddenSuppressed))?,
..Default::default()
};

Expand Down Expand Up @@ -852,6 +863,8 @@ struct ParsedState {
defsrc_layer: [KanataAction; KEYS_IN_ROW],
is_cmd_enabled: bool,
delegate_to_first_layer: bool,
default_sequence_timeout: u16,
default_sequence_input_mode: SequenceInputMode,
vars: HashMap<String, SExpr>,
a: Arc<Allocations>,
}
Expand All @@ -862,6 +875,12 @@ impl ParsedState {
}
}

const SEQUENCE_TIMEOUT_CFG_NAME: &str = "sequence-timeout";
const SEQUENCE_INPUT_MODE_CFG_NAME: &str = "sequence-input-mode";
const SEQUENCE_TIMEOUT_ERR: &str = "sequence-timeout should be a number (1-65535)";
const SEQUENCE_TIMEOUT_DEFAULT: u16 = 1000;
const SEQUENCE_INPUT_MODE_DEFAULT: SequenceInputMode = SequenceInputMode::HiddenSuppressed;

impl Default for ParsedState {
fn default() -> Self {
Self {
Expand All @@ -875,6 +894,8 @@ impl Default for ParsedState {
is_cmd_enabled: false,
delegate_to_first_layer: false,
vars: Default::default(),
default_sequence_timeout: SEQUENCE_TIMEOUT_DEFAULT,
default_sequence_input_mode: SEQUENCE_INPUT_MODE_DEFAULT,
a: unsafe { Allocations::new() },
}
}
Expand Down Expand Up @@ -1042,8 +1063,16 @@ fn parse_action_atom(ac: &Spanned<String>, s: &ParsedState) -> Result<&'static K
)))
}
"sldr" => {
return Ok(s.a.sref(Action::Custom(s.a.sref(s.a.sref_slice(
CustomAction::SequenceLeader(
s.default_sequence_timeout,
s.default_sequence_input_mode,
),
)))))
}
"scnl" => {
return Ok(s.a.sref(Action::Custom(
s.a.sref(s.a.sref_slice(CustomAction::SequenceLeader)),
s.a.sref(s.a.sref_slice(CustomAction::SequenceCancel)),
)))
}
"mlft" | "mouseleft" => {
Expand Down Expand Up @@ -1202,6 +1231,7 @@ fn parse_action_list(ac: &[SExpr], s: &ParsedState) -> Result<&'static KanataAct
"caps-word-custom" => parse_caps_word_custom(&ac[1..], s),
"dynamic-macro-record-stop-truncate" => parse_macro_record_stop_truncate(&ac[1..], s),
"switch" => parse_switch(&ac[1..], s),
"sequence" => parse_sequence_start(&ac[1..], s),
_ => bail_expr!(&ac[0], "Unknown action type: {ac_type}"),
}
}
Expand Down Expand Up @@ -2717,6 +2747,30 @@ fn parse_macro_record_stop_truncate(
))))
}

fn parse_sequence_start(ac_params: &[SExpr], s: &ParsedState) -> Result<&'static KanataAction> {
const ERR_MSG: &str =
"sequence expects one or two params: <timeout-override> <?input-mode-override>";
if !matches!(ac_params.len(), 1 | 2) {
bail!("{ERR_MSG}\nfound {} items", ac_params.len());
}
let timeout = parse_non_zero_u16(&ac_params[0], s, "timeout-override")?;
let input_mode = if ac_params.len() > 1 {
if let Some(Ok(input_mode)) = ac_params[1]
.atom(s.vars())
.map(|config_str| SequenceInputMode::try_from_str(config_str))
{
input_mode
} else {
bail_expr!(&ac_params[1], "{ERR_MSG}\n{}", SequenceInputMode::err_msg());
}
} else {
s.default_sequence_input_mode
};
Ok(s.a.sref(Action::Custom(s.a.sref(
s.a.sref_slice(CustomAction::SequenceLeader(timeout, input_mode)),
))))
}

fn parse_switch(ac_params: &[SExpr], s: &ParsedState) -> Result<&'static KanataAction> {
const ERR_STR: &str =
"switch expects triples of params: <key match> <action> <break|fallthrough>";
Expand Down
40 changes: 39 additions & 1 deletion parser/src/custom_action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//! When adding a new custom action, the macro section of the config.adoc documentation may need to
//! be updated, to include the new action to the documented list of supported actions in macro.

use anyhow::{anyhow, Result};
use kanata_keyberon::key_code::KeyCode;

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
Expand Down Expand Up @@ -39,7 +40,8 @@ pub enum CustomAction {
min_distance: u16,
max_distance: u16,
},
SequenceLeader,
SequenceCancel,
SequenceLeader(u16, SequenceInputMode),
LiveReload,
LiveReloadNext,
LiveReloadPrev,
Expand Down Expand Up @@ -100,3 +102,39 @@ pub struct CapsWordCfg {
pub keys_nonterminal: &'static [KeyCode],
pub timeout: u16,
}

/// This controls the behaviour of kanata when sequence mode is initiated by the sequence leader
/// action.
///
/// - `HiddenSuppressed` hides the keys typed as part of the sequence and does not output the keys
/// typed when an invalid sequence is the result of an invalid sequence character or a timeout.
/// - `HiddenDelayType` hides the keys typed as part of the sequence and outputs the keys when an
/// typed when an invalid sequence is the result of an invalid sequence character or a timeout.
/// - `VisibleBackspaced` will type the keys that are typed as part of the sequence but will
/// backspace the typed sequence keys before performing the fake key tap when a valid sequence is
/// the result.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SequenceInputMode {
HiddenSuppressed,
HiddenDelayType,
VisibleBackspaced,
}

const SEQ_VISIBLE_BACKSPACED: &str = "visible-backspaced";
const SEQ_HIDDEN_SUPPRESSED: &str = "hidden-suppressed";
const SEQ_HIDDEN_DELAY_TYPE: &str = "hidden-delay-type";

impl SequenceInputMode {
pub fn try_from_str(s: &str) -> Result<Self> {
match s {
SEQ_VISIBLE_BACKSPACED => Ok(SequenceInputMode::VisibleBackspaced),
SEQ_HIDDEN_SUPPRESSED => Ok(SequenceInputMode::HiddenSuppressed),
SEQ_HIDDEN_DELAY_TYPE => Ok(SequenceInputMode::HiddenDelayType),
_ => Err(anyhow!(SequenceInputMode::err_msg())),
}
}

pub fn err_msg() -> String {
format!("sequence input mode must be one of: {SEQ_VISIBLE_BACKSPACED}, {SEQ_HIDDEN_SUPPRESSED}, {SEQ_HIDDEN_DELAY_TYPE}")
}
}
Loading

0 comments on commit 4b1f14f

Please sign in to comment.