@@ -69,6 +69,12 @@ pub(crate) fn parse_tap_hold_options(
6969}
7070
7171const TAP_HOLD_OPTION_KEYWORDS : & [ & str ] = & [ "require-prior-idle" ] ;
72+ const TAP_HOLD_KEYS_OPTION_KEYWORDS : & [ & str ] = & [
73+ "require-prior-idle" ,
74+ "tap-on-press" ,
75+ "tap-on-press-release" ,
76+ "hold-on-press" ,
77+ ] ;
7278
7379/// Count how many trailing expressions are tap-hold option lists.
7480/// An option list is a list whose first element is a known option keyword.
@@ -302,3 +308,137 @@ Params in order:
302308 require_prior_idle : opts. require_prior_idle ,
303309 } ) ) ) )
304310}
311+
312+ pub ( crate ) fn parse_tap_hold_keys_named_lists (
313+ ac_params : & [ SExpr ] ,
314+ s : & ParserState ,
315+ ) -> Result < & ' static KanataAction > {
316+ // Count trailing options using the extended keyword set.
317+ let n_opts = {
318+ let mut count = 0 ;
319+ for expr in ac_params. iter ( ) . rev ( ) {
320+ if let Some ( list) = expr. list ( s. vars ( ) ) {
321+ if let Some ( kw) = list. first ( ) . and_then ( |e| e. atom ( s. vars ( ) ) ) {
322+ if TAP_HOLD_KEYS_OPTION_KEYWORDS . contains ( & kw) {
323+ count += 1 ;
324+ continue ;
325+ }
326+ }
327+ }
328+ break ;
329+ }
330+ count
331+ } ;
332+ let n_positional = ac_params. len ( ) - n_opts;
333+ if n_positional != 4 {
334+ bail ! (
335+ r"{} expects 4 items after it, got {}.
336+ Params in order:
337+ <tap-repress-timeout> <hold-timeout> <tap-action> <hold-action>
338+ Followed by optional lists:
339+ (tap-on-press <keys...>) (tap-on-press-release <keys...>) (hold-on-press <keys...>)
340+ (require-prior-idle <ms>)" ,
341+ TAP_HOLD_KEYS ,
342+ n_positional,
343+ )
344+ }
345+ let tap_repress_timeout = parse_u16 ( & ac_params[ 0 ] , s, "tap repress timeout" ) ?;
346+ let hold_timeout = parse_non_zero_u16 ( & ac_params[ 1 ] , s, "hold timeout" ) ?;
347+ let tap_action = parse_action ( & ac_params[ 2 ] , s) ?;
348+ let hold_action = parse_action ( & ac_params[ 3 ] , s) ?;
349+ if matches ! ( tap_action, Action :: HoldTap { .. } ) {
350+ bail ! ( "tap-hold does not work in the tap-action of tap-hold" )
351+ }
352+
353+ let mut require_prior_idle = None ;
354+ let mut tap_on_press: Vec < OsCode > = vec ! [ ] ;
355+ let mut tap_on_press_release: Vec < OsCode > = vec ! [ ] ;
356+ let mut hold_on_press: Vec < OsCode > = vec ! [ ] ;
357+ let mut seen_options: HashSet < & str > = HashSet :: default ( ) ;
358+
359+ for option_expr in & ac_params[ n_positional..] {
360+ let Some ( option) = option_expr. list ( s. vars ( ) ) else {
361+ bail_expr ! (
362+ option_expr,
363+ "expected option list, e.g. `(tap-on-press a b c)`"
364+ ) ;
365+ } ;
366+ if option. is_empty ( ) {
367+ bail_expr ! ( option_expr, "option list cannot be empty" ) ;
368+ }
369+ let kw = option[ 0 ]
370+ . atom ( s. vars ( ) )
371+ . ok_or_else ( || anyhow_expr ! ( & option[ 0 ] , "option name must be a string" ) ) ?;
372+ if !seen_options. insert ( kw) {
373+ bail_expr ! ( & option[ 0 ] , "duplicate option '{}'" , kw) ;
374+ }
375+ match kw {
376+ "require-prior-idle" => {
377+ require_prior_idle =
378+ Some ( parse_require_prior_idle_option ( option, option_expr, s) ?) ;
379+ }
380+ "tap-on-press" => {
381+ tap_on_press = parse_key_list_from_option ( option, option_expr, s, kw) ?;
382+ }
383+ "tap-on-press-release" => {
384+ tap_on_press_release = parse_key_list_from_option ( option, option_expr, s, kw) ?;
385+ }
386+ "hold-on-press" => {
387+ hold_on_press = parse_key_list_from_option ( option, option_expr, s, kw) ?;
388+ }
389+ _ => bail_expr ! (
390+ & option[ 0 ] ,
391+ "unknown tap-hold-keys option '{}'. \
392+ Valid options: tap-on-press, tap-on-press-release, hold-on-press, require-prior-idle",
393+ kw
394+ ) ,
395+ }
396+ }
397+
398+ Ok ( s. a . sref ( Action :: HoldTap ( s. a . sref ( HoldTapAction {
399+ config : HoldTapConfig :: Custom ( custom_tap_hold_keys (
400+ & tap_on_press,
401+ & tap_on_press_release,
402+ & hold_on_press,
403+ & s. a ,
404+ ) ) ,
405+ tap_hold_interval : tap_repress_timeout,
406+ timeout : hold_timeout,
407+ tap : * tap_action,
408+ hold : * hold_action,
409+ timeout_action : * hold_action,
410+ on_press_reset_timeout_to : None ,
411+ require_prior_idle,
412+ } ) ) ) )
413+ }
414+
415+ /// Parse keys from a named option list like `(tap-on-press a b c)`.
416+ /// The first element is the keyword, remaining elements are key names.
417+ fn parse_key_list_from_option (
418+ option : & [ SExpr ] ,
419+ option_expr : & SExpr ,
420+ s : & ParserState ,
421+ kw : & str ,
422+ ) -> Result < Vec < OsCode > > {
423+ if option. len ( ) < 2 {
424+ bail_expr ! (
425+ option_expr,
426+ "{} expects at least one key, e.g. `({} a b c)`" ,
427+ kw,
428+ kw
429+ ) ;
430+ }
431+ option[ 1 ..] . iter ( ) . try_fold ( vec ! [ ] , |mut keys, key| {
432+ key. atom ( s. vars ( ) )
433+ . map ( |a| -> Result < ( ) > {
434+ keys. push ( str_to_oscode ( a) . ok_or_else ( || {
435+ anyhow_expr ! ( key, "string of a known key is expected" )
436+ } ) ?) ;
437+ Ok ( ( ) )
438+ } )
439+ . ok_or_else ( || {
440+ anyhow_expr ! ( key, "string of a known key is expected, found list instead" )
441+ } ) ??;
442+ Ok ( keys)
443+ } )
444+ }
0 commit comments