jtroo / kanata

Improve keyboard comfort and usability with advanced customization
GNU Lesser General Public License v3.0
2.92k stars 124 forks source link

Feature request: templating macros/functions #766

Closed reidprichard closed 8 months ago

reidprichard commented 8 months ago

Is your feature request related to a problem? Please describe.

This may be pretty niche, but I'm using my (shameless plug) window_tools to save and reactivate windows. Capslock, alt, and a number saves a window, then capslock and that same number later reactivates it.

This results in a lot of copy-pasted configuration, as you can see below (ignore anything below the second row of the layer):

(defvar
  window_manager ./window_manager.exe
)

(defalias wdw1 (fork (cmd $window_manager --load-window=0) (cmd $window_manager --save-window=0) (lalt ralt)))
(defalias wdw2 (fork (cmd $window_manager --load-window=1) (cmd $window_manager --save-window=1) (lalt ralt)))
(defalias wdw3 (fork (cmd $window_manager --load-window=2) (cmd $window_manager --save-window=2) (lalt ralt)))
(defalias wdw4 (fork (cmd $window_manager --load-window=3) (cmd $window_manager --save-window=3) (lalt ralt)))
(defalias wdw5 (fork (cmd $window_manager --load-window=4) (cmd $window_manager --save-window=4) (lalt ralt)))
(defalias wdw6 (fork (cmd $window_manager --load-window=3) (cmd $window_manager --save-window=5) (lalt ralt)))

(deflayer capslock
  _         _     _     _     _     _     _     _    _    _    _    _    _          _    _    _
  _         @wdw1 @wdw2 @wdw3 @wdw4 @wdw5 @wdw6 _    _    _    _    _    _    del   _    _    _
  _         _     _     _     _     _     _     _    _    _    @prv _    _    _     _    _    _
  @default  home  _     end   _     _     left  down up   rght esc  _    lrld
  _         _     _     _     _     _     @nxt  _    _    _    _    _               _
  _         _     _               esc                _    _    _    _          _    _    _
)

Describe the solution you'd like.

I was thinking it could be cool to have some simple macro expansion. Since this would be evaluated only once, when the config was read, it shouldn't add overhead or much complexity. It would have the form of:

(deffun
  function_name arg_1 arg_2 ... function_text
)

where function_text could be any string with args referenced as $arg_1, $arg_2, etc. I think it would probably be easiest to use lisp syntax to expand a macro, so the above function would be called using:

(function_name value_1 value_2 ...)

This would make my config collapse to:

(deffun
  wdw window_num (fork (cmd $window_manager --load-window=$window_num) (cmd $window_manager --save-window=$window_num) (lalt ralt))
)
(deflayer capslock
  _         _     _     _     _     _     _     _    _    _    _    _    _          _    _    _
  _         (wdw 1) (wdw 2) (wdw 3) (wdw 4) (wdw 5) (wdw 6) _    _    _    _    _    _    del   _    _    _
  _         _     _     _     _     _     _     _    _    _    @prv _    _    _     _    _    _
  @default  home  _     end   _     _     left  down up   rght esc  _    lrld
  _         _     _     _     _     _     @nxt  _    _    _    _    _               _
  _         _     _               esc                _    _    _    _          _    _    _
)

Though, since alias names can't contain parentheses, it seems possible to use a different syntax, like @wdw(1).

Describe alternatives you've considered.

I considered doing some sort of templating with aliases or variables, but since neither can be redefined I had no luck there. It seems like concat could somehow be used here, but I don't think that would work well. The only other option I've considered is making a separate preprocess script that does the expansion for me, but I'm not sure that's easier than just copy-pasting.

Additional context

No response

jtroo commented 8 months ago

This was recently added: https://github.com/jtroo/kanata/blob/main/docs/config.adoc#templates

It hasn't yet made it into an official release so syntax is still up for changes.

jtroo commented 8 months ago

Unfortunately --load-window=0 can't be transformed into --load-window=$var currently, but maybe that can be worked around at the cmd/other-binary level by using+accepting --load-window $var. Or maybe some fancy concat stuff can be done within the template...

reidprichard commented 8 months ago

Thanks for the suggestions.

The new template actions look interesting and definitely a powerful addition. I think if variables could be inserted at any arbitrary position (like (cmd comand_name --argument-name=$value) as above) it would do exactly what I need. Other than that, I think the only real difference is that my idea used the template name as the function name whereas yours uses template-expand. While I would like the succinctness of just using the template name (e.g. (wdw 0) instead of (template-expand wdw 0)) in my initial post's example, I do see how that could make the config file less readable.

It took me quite awhile to figure out what was going in in example 1 of the template docs, but I think that's just due to not understanding chords.. I'm guessing the syntactical gymnastics there are due to relying on Keyberon? Off-topic, but is there a hope to ever handle the state logic natively? My feeling is that cleaning up some of the syntax quirks could make this much more accessible to non-programming-minded folks if that's your aim for the project.

I think it would help to have a simpler example of the template function in the docs. I can submit a PR if you're open.

jtroo commented 8 months ago

I think if variables could be inserted at any arbitrary position (like (cmd comand_name --argument-name=$value) as above) it would do exactly what I need.

To avoid adding code for variable interpolation, and to hopefully reuse some existing functionality, my first thought would be to use concat inside of templates like how it can be used in defvar today.

I think it would help to have a simpler example of the template function in the docs. I can submit a PR if you're open.

Sure, adding a simpler example before the other two existing ones would be welcome.

jtroo commented 8 months ago

I'm guessing the syntactical gymnastics there are due to relying on Keyberon? Off-topic, but is there a hope to ever handle the state logic natively? My feeling is that cleaning up some of the syntax quirks could make this much more accessible to non-programming-minded folks if that's your aim for the project.

Indeed, with the goals of making parsing simpler and removing ambiguity of the transformation from parsed text to actual behaviour, the syntax for features such as chording and fake keys closely matches the underlying data model. I would not move kanata away from using keyberon though. That is work for a different project. Contributions that make syntax easier are certainly welcome though.

Accessibility to non-programmers is a not an explicit goal. The motivation of kanata was to be able to customize keyboard behaviour similar to how I was writing QMK keymaps in C before.