Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Read chords from simple text file #984

Merged
merged 7 commits into from
May 18, 2024
Merged

Conversation

nightscape
Copy link
Contributor

Describe your changes. Use imperative present tense.

This PR is a first shot at implementing a more scalable chording approach using a simple text file for configuration (see #979)

Kanata.Chording.mp4

Checklist

  • Add documentation to docs/config.adoc
    • Yes or N/A
  • Add example and basic docs to cfg_samples/kanata.kbd
    • Yes or N/A
  • Update error messages
    • Yes or N/A
  • Added tests, or did manual testing
    • Yes

Copy link
Contributor

@rszyma rszyma left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of introducting another file with it's own format for defining chords, maybe a config item could be added to .kbd configurtion e.g.:

(def-chords-v2-dictionary
an	and 
as	as 
or	or 
bu	but 
if	if 
so	so 
dn	then 
bc	because 
)

src/transform_chords.rs Outdated Show resolved Hide resolved
src/transform_chords.rs Outdated Show resolved Hide resolved
src/transform_chords.rs Outdated Show resolved Hide resolved
@nightscape nightscape force-pushed the simpler-chords branch 7 times, most recently from c05de89 to 20a903d Compare May 8, 2024 17:23
@nightscape
Copy link
Contributor Author

nightscape commented May 8, 2024

@jtroo @rszyma @SequentialDesign would you mind giving this a try?
I've been using it for the past days, and it really feels like magic!
I've set up my quick launcher (Raycast) with a simple script that appends new words directly to my chords.tsv and added a lrld to my layout, so adding a new word is a matter of ~5 seconds, which is fast enough to pay off rather quickly.

Approach

  • I'm reading a chords tab-separated values file as close as possible to the ZipChord format and transforming it into defchordsv2-experimental syntax, which then gets parsed as usual.
  • In order to handle different keyboard layouts, I build a target_map by parsing the first deflayer and mapping it back to the defsrc layout. This is quite unelegant and probably full of bugs, but it mostly works (see below).

Caveats

  • Chords trigger accidentally when I'm typing fast. This is something that @psoukie already described, along with possible solutions. This is currently the most annoying part because otherwise you would have no downsides to just adding a few chords and incrementally ramping up the chording.
  • Despite having a deflocalkeys-macos for my German keyboard layout in my config, the trigger keys still operate as though I was on an English keyboard (e.g. I have to use a trigger d a z if I want to match the chord day).
  • Sequences seem to compete with each other, so if I only have
    (t! seq ion (spc nop0 O-(. ,)) (macro i o n spc nop0)) ;; extend words with the suffix `ion`.
    it works (e.g. I can chord on ., and get onion 😄), but if I add a competing sequence
    (t! seq DelSpace_. (spc nop0 .) (macro . spc))
    (t! seq ion (spc nop0 O-(. ,)) (macro i o n spc nop0)) ;; extend words with the suffix `ion`.
    the shorter one wins (I get on. ,).
  • I generally have issues with sequences with a O-(...) trigger when the keyboard layout remaps something. Are these handled differently than the non-O-(...) triggers?
  • I'm a Rust noob (coming from Scala) and am probably doing things in rather inelegant ways.

@nightscape nightscape force-pushed the simpler-chords branch 7 times, most recently from 79d8dc4 to 1fe9546 Compare May 8, 2024 22:53
@nightscape nightscape marked this pull request as ready for review May 8, 2024 22:57
@jtroo
Copy link
Owner

jtroo commented May 9, 2024

I generally have issues with sequences with a O-(...)

I consider overlapped key sequences a failed experiment 😆. They might work in simpler use cases but not under heavy usage.

Despite having a deflocalkeys-macos for my German keyboard layout in my config

Hm that's surprising, sounds like a bug somewhere... though no specific pointers off the top of my head.

Copy link
Owner

@jtroo jtroo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice! I haven't tried it out yet, but hopefully soon :)

cfg_samples/kanata.kbd Outdated Show resolved Hide resolved
parser/src/cfg/chord.rs Outdated Show resolved Hide resolved
parser/src/cfg/chord.rs Outdated Show resolved Hide resolved
parser/src/cfg/chord.rs Outdated Show resolved Hide resolved
parser/src/cfg/chord.rs Outdated Show resolved Hide resolved
parser/src/cfg/chord.rs Outdated Show resolved Hide resolved
.map(|c| self.post_process(&c.to_string()))
.collect_vec();
// Wait 50ms for one-shot Shift to release
// TODO: This would be better handled by a (multi (release-key lsft)(release-key rsft))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would rely on a tap-vkey. For a "worse-is-better" solution, using this functionality could mandate a virtual key with a specific name that does the specific function 😅. I haven't thought too hard about it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This relates to a general concern I've been thinking about:
Mapping all the functionality from a ZipChord chord definition requires more than just entries in the defchordsv2-experimental section. E.g. the suffix stuff is currently handled via sequences which are top-level in the config. The tap-vkey is another indicator that the chording might have to live in the top-level config, so that it can instantiate everything required for a seamless experience, and require the user to add magic stuff elsewhere in his config.

I'd be very thankful for your ideas and opinion here @jtroo.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To address this point specifically, I would be fine with to have the documentation+error message mandate an item like below to use include:

(defvirtualkeys release-shift (multi (release-key lsft) (release-key rsft)))

The code could even validate that the action matches exactly too. The information required to do these validations is found in ParserState.virtual_keys. The generated macro that gets created would then use a on-press tap-vkey release-shift.


At a higher level, my overall impression is that this PR is a great step in improving the convenience of using today's chords. I would merge it when it's ready, if we want to keep it as-is. However, to get even closer to ZipChord-like or Plover-like functionality, it would IMO be best done with a dedicated feature. As discovered in our experimentation, stitching together Kanata's existing features - chordsv2+sequences - to emulate ZipChord doesn't work perfectly.

My first thought would be to port https://github.com/psoukie/zipchord/blob/main/source/io.ahk as highlighted by psoukie into the output feed of kanata at https://github.com/jtroo/kanata/blob/main/src/kanata/output_logic.rs . At least from my initial impression ZipChord seems easier to port than Plover, so seems like a good starting point. On the point of where the implementation would be, my feeling is that adapting/extending the ZMK-inspired chording, which is done at the Keyberon state machine level, is probably not preferable.

@nightscape
Copy link
Contributor Author

@rszyma sorry I had not responded to your comments yet. I was busy experimenting 😉

I see a few complications with inlining the definition into a .kbd file:

  1. We can't share the format with ZipChord or any other tools (I own a CharaChorder as well, so sharing config between the two would be cool).
  2. Adding to it via an external tool (like I do with RayCast) gets more complicated because one cannot just append, but needs to find the right location. One could assume that the closing ) for the chords is on the last line, but that would require extra safety measures to not accidentally mess up the config.
  3. Chord triggers and actions can contain a space (and possibly other whitespace) characters. Those might get messed up by parsing.

@nightscape
Copy link
Contributor Author

@jtroo from my side it would be great to merge the PR (once it's good to go from your side) and do a follow-up with improvements.
I've been using it daily and I think it's a reasonable Minimum Viable Product for getting started with chording in Kanata.
Maybe this helps in gathering broader interest 😃

@eugenesvk
Copy link
Contributor

Great idea, hopefully the most annoying part can eventually get resolved!

Just a few thoughts on the lesser aspect:

I see a few complications with inlining the definition into a .kbd file:

You could "inline" it in a separate file that will be imported into the main config file, so the only difference from your txt file would be (def-chords-v2-dictionary), so that would solve the sharing part?

Whitespace is better dealt with via quotes? For example, what if you want to add a space before an expansion, that might visually be a challenge to differentiate from the tab before. Or what if you want more whitespace between chord,expansion for alignment? Or what if you want to expand into a tab-separated pair?

Also you could eventually add more complicated instructions to individual chords (or all of the chording block) if you use the more flexible/extensible .kbd config

one cannot just append, but ... closing )

Could this maybe be one special exception to the kbd format: assume the outer () if they're not present (but only in imported files)?

@jtroo jtroo merged commit 520ab00 into jtroo:main May 18, 2024
4 checks passed
eugenesvk pushed a commit to eugenesvk/kanata that referenced this pull request May 19, 2024
This PR is a first shot at implementing a more scalable chording
approach using a simple text file for configuration.

See:
- jtroo#979
- https://github.com/jtroo/kanata/assets/35170/d51a4669-ce8c-4355-9124-53c47a1cdd47
@nightscape nightscape deleted the simpler-chords branch May 19, 2024 20:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants