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(behaviors): Smart-layers (e.g., num-word) and caps-word enhancements #1451

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

urob
Copy link
Contributor

@urob urob commented Sep 10, 2022

EDIT: The PR is somewhat outdated. An updated version focusing on the auto-layer functions is available as ZMK module


This PR contains a number of enhancements for the caps-word behavior. Most importantly it adds a smart-layer option that activates a layer until a key not in continue-list is pressed. The PR supersedes #1422

New/updated backend-properties:

The backend behavior zmk,behavior-caps-word (not to be confused with the frontend &caps_word) now supports the following config options:

  • [updated] mods: Specifies the mods that get activated when smart-word is active. New default: 0 (none).
  • [new] int layers: Specifies the layer that gets activated when smart-word is active. Default: -1 (none)
  • [new] bool ignore-alphas: If true, alphas do not cancel the smart-word behavior
  • [new] bool ignore-numbers: If true, numbers do not cancel the smart-word behavior
  • [new] bool ignore-modifiers: If true, modifiers do not cancel the smart-word behavior

The new layers property is used to configure the new smart-layers capabilities. The new ignore-* flags can be used to configure whether smart-word continues on alphas, numbers or modifiers (the original caps-word would always continue on all of these regardless of the value of continue-list)

Smart-layer example: Num-word

For instance, to set up a "numword", one can define a new behavior as follows:

\ {
    behaviors {
		        num_word: behavior_num_word {
			compatible = "zmk,behavior-caps-word";
			label = "NUM_WORD";
			#binding-cells = <0>;
                        layers = <NUM>;                                // insert location of numbers layer here
			continue-list = <BACKSPACE DELETE DOT COMMA>;  // adjust as desired
                        ignore-numbers;                                // numbers don't deactivate the layer
		};
	};
};

Pressing &num_word will activate the number-layer and keep it active for as long as only numbers, backspace, space, dot or comma are pressed. After any other key is pressed, say, "space", the layer is automatically deactivated. This works well if the number-layer is mostly transparent, so that one can just continue typing normally after finishing with the numbers without having to actively release the number-layer.

For convenience, the PR also adds a pre-defined &num_word behavior. To use it, one only needs to specify the correct layer specification in the user config:

&num_word {
    layers = <NUM>;  // replace NUM by the location of numbers layer
};

Caps-word

The pre-defined caps-word definition has been adjusted to yield the old behavior using the new configuration-flags:

/ {
	behaviors {
		/omit-if-no-ref/ caps_word: behavior_caps_word {
			compatible = "zmk,behavior-caps-word";
			label = "CAPS_WORD";
			#binding-cells = <0>;
                        mods = <MOD_LSFT>;
			continue-list = <UNDERSCORE BACKSPACE DELETE>;
                        ignore-alphas;
                        ignore-numbers;
                        ignore-modifiers;
		};
	};
};

On the user-side nothing changes when using &caps_word.

Using ignore-flags to tweak caps-word

For backwards-compatibility, the frontend implementation of &caps_word continues on all alphas, numbers and modifiers. This can be overwritten using /delete-property/. For instance, to cancel caps-word when numbers and modifiers are pressed, one can add the following to the user config:

&caps_word {
    /delete-property/ ignore-numbers;
    /delete-property/ ignore-modifiers;
};

This is useful, for example, for people who activate caps-word with somewhat complex key combos or tap dances and who wish to be able to deactivate it by simply pressing LEFT_SHIFT (of course, it still deactivates automatically with any other key not specified in continue-list). This fixes #1410.

If one wants more fine-grained control over which modifiers or numbers cancel caps-word, one can use continue-list to do so. For example, if one wants to continue caps-word on shift but cancel it with all other modifiers, one can do so by adding the following to the user-config:

&caps_word {
    /delete-property/ ignore-modifiers;
    continue-list = <UNDERSCORE BACKSPACE DELETE LSHFT RSHFT>;
};

Discussion

Would it make sense to rename the backend-behavior from zmk,behavior-caps-word to something like zmk,behavior-smart-word? It might be a bit confusing that the zmk,behavior-caps-word backend (which can be used to implement any smart-modifier or smart-layer) has almost exactly the same name as the &caps_word frontend that sets mods to MOD_LSFT and layers to -1 (none).

@urob urob changed the title feat(behaviors): Smart-word/layers (an enhanced caps-word with smart-layers) feat(behaviors): Smart-layers (e.g., num-word) and smart-word enhancements Sep 10, 2022
@urob urob changed the title feat(behaviors): Smart-layers (e.g., num-word) and smart-word enhancements feat(behaviors): Smart-layers (e.g., num-word) and caps-word enhancements Sep 10, 2022
@snoyer snoyer mentioned this pull request Oct 7, 2022
johnm added a commit to johnm/zmk that referenced this pull request Nov 6, 2022
@instance-id
Copy link

instance-id commented Feb 2, 2023

Unless I did something incorrectly, this seems to only work on keys N1-N6, it does not work properly for KP_N0-KP_N9, nor KP_DOT. I don't believe I did something incorrectly, otherwise it would not work for the N1-6 keys, I expect.

If I tap &num_word, KP_N0-9 will insert a single number and then the keypad layer will deactivate. If I hit plain N1-6 on the number row across the top, it will remain in the numeric layer. Do I have to do something specific/different for this to stay on the keypad layer when using KP_N numbers as opposed to using N numbers? I have the ignore-numbers bool enabled as per the example listed above.

Behavior implementation:
https://github.com/instance-id/Adv360-Pro-ZMK/blob/9f241c1671b44a36fd6b92064022a3180f056b6e/config/adv360.keymap#L21

Key implementation: line 38/col 65
https://github.com/instance-id/Adv360-Pro-ZMK/blob/9f241c1671b44a36fd6b92064022a3180f056b6e/config/adv360.keymap#L38

@urob
Copy link
Contributor Author

urob commented Feb 2, 2023

Unless I did something incorrectly, this seems to only work on keys N1-N6, it does not work properly for KP_N0-KP_N9, nor KP_DOT. I don't believe I did something incorrectly, otherwise it would not work for the N1-6 keys, I expect.

If I tap &num_word, KP_N0-9 will insert a single number and then the keypad layer will deactivate. If I hit plain N1-6 on the number row across the top, it will remain in the numeric layer. Do I have to do something specific/different for this to stay on the keypad layer when using KP_N numbers as opposed to using N numbers? I have the ignore-numbers bool enabled as per the example listed above.

Behavior implementation: https://github.com/instance-id/Adv360-Pro-ZMK/blob/9f241c1671b44a36fd6b92064022a3180f056b6e/config/adv360.keymap#L21

Key implementation: line 38/col 65 https://github.com/instance-id/Adv360-Pro-ZMK/blob/9f241c1671b44a36fd6b92064022a3180f056b6e/config/adv360.keymap#L38

The ignore-numbers boolean is only for the regular numbers. But you should be able to manually add the keypad numbers to continue-list:

\ {
    behaviors {
		        num_word: behavior_num_word {
			compatible = "zmk,behavior-caps-word";
			label = "NUM_WORD";
			#binding-cells = <0>;
                        layers = <NUM>;                                // insert location of numbers layer here
			continue-list = <BACKSPACE DELETE DOT COMMA KP_N0 KP_N1 KP_N2 etc etc>; 
                        ignore-numbers; 
		};
	};
};

@instance-id
Copy link

instance-id commented Feb 2, 2023

Good call, that did the trick. I do appreciate it. 👍

continue-list = <BACKSPACE DELETE DOT COMMA KP_N0 KP_N1 KP_N2 KP_N3 KP_N4 KP_N5 KP_N6 KP_N7 KP_N8 KP_N9 KP_DOT KP_EQUAL KP_DIVIDE KP_MULTIPLY KP_MINUS KP_ENTER LEFT RIGHT>;

tospens added a commit to tospens/zmk that referenced this pull request Feb 21, 2023
replicaJunction added a commit to replicaJunction/zmk that referenced this pull request Feb 24, 2023
@yanshay
Copy link

yanshay commented Mar 10, 2023

I'm using num_word through urob/zmk and I noticed pressing !@#$% (EXLAMATION, AT, HASH, ...) through unshifted keymaps don't toggle off the layer, but -+~ etc. do.
Is there a way to get all punctuations to toggle off the layer?

@urob
Copy link
Contributor Author

urob commented Mar 11, 2023

I'm using num_word through urob/zmk and I noticed pressing !@#$% (EXLAMATION, AT, HASH, ...) through unshifted keymaps don't toggle off the layer, but -+~ etc. do. Is there a way to get all punctuations to toggle off the layer?

Yes, the current implementation does not distinguish between shifted/unshifted keycodes. So all symbols that under the hood are implemented via shift + number won't trigger the exit condition.

For now, you could use tri-state as an alternative, allowing you to define exit conditions purely based on key positions.

urob added 6 commits April 6, 2023 07:40
Squashed commit of the following:

commit ba38925
Author: urob <978080+urob@users.noreply.github.com>
Date:   Fri Aug 5 16:05:58 2022 -0400

    Ignore-modifiers property for capsword
urob added a commit to urob/zmk that referenced this pull request Apr 10, 2023
urob added a commit to urob/zmk that referenced this pull request Apr 12, 2023
urob added a commit to urob/zmk that referenced this pull request Apr 12, 2023
urob added a commit to urob/zmk that referenced this pull request Apr 30, 2023
yuanbin pushed a commit to yuanbin/zmk that referenced this pull request Jun 6, 2023
yuanbin pushed a commit to yuanbin/zmk that referenced this pull request Jun 6, 2023
yuanbin pushed a commit to yuanbin/zmk that referenced this pull request Jun 6, 2023
yuanbin added a commit to yuanbin/zmk that referenced this pull request Jun 13, 2023
@minusfive
Copy link
Contributor

Just wanted to drop by and thank you @urob! This made an incredible difference in comfort, cognitive ease and speed on my keymap. Feels so intuitive now! https://github.com/minusfive/zmk-config

Wondering whether there are any plans of merging this to main? Feels like a no-brainer for a native feature, and I know there are already a ton of users, right?

@snprajwal
Copy link

snprajwal commented Sep 25, 2023

@maintainers could we please get this merged for the next release? It's an excellent feature that doesn't break anything (as far as I can see), and a lot of people would love to see it on mainline ZMK

@infused-kim
Copy link
Contributor

I also love this feature and it’s working well.

It hasn’t received any feedback from the maintainers, but I imagine squeezing these features into the caps word implementation isn’t the way to go.

I think either defining a new behavior or adding it to the layer-toggle behavior would probably be more appropriate.

@tilliwilli
Copy link

tilliwilli commented Oct 18, 2023

First off, I really enjoy all the work @urob has been doing and I'm using his ZMK repo for my stuff as well.

But regarding this functionality here I came upon one issue with my numbers layer that is definitely related to the layer addition (which capsword does not use).

That is that an &mo layer does not overlay the numbers layer. If I activate my &numword and then try to use my navigation &mo layer by holding down a thumb key (which is transparent on the num layer), the numbers still take preference and I can't move the cursor around (I added RIGHT and LEFT to my continue list for numword).

This should actually be reproducable with urobs own keymap directly (e.g. try the swapper or any F## key with numword active).

@urob
Copy link
Contributor Author

urob commented May 7, 2024

I have been pondering about the future of this PR. The current proposal generalizes -- or perhaps more fittings: piggybacks on -- caps-word. Implementation-wise this seemed to make sense to me at the time, given that 95% of the code is shared.

But design-wise I am wondering whether it would make sense to split off the backend behavior for "smart layers" from the one for "caps word" (especially in light of #1742 which adds some more customization options for the latter)?

Specifically, I am thinking of a more focused smart-layer behavior that takes one argument (the layer index) and activates the layer until a key not in the ignore list is pressed. Relative to the current implementation it would drop support for locking mods and would move the layer spec from a property to an argument. The latter change would allow us to pre-define a fully functional num_word which could be added to the keymap with &num_word LAYER instead of needing to overwrite the behavior node.

@zoriya
Copy link

zoriya commented May 7, 2024

@urob with your new implementation idea, how would you make a caps-word that ignore modifiers? Keep it in caps-word in a separate PR?

@urob
Copy link
Contributor Author

urob commented May 7, 2024

@urob with your new implementation idea, how would you make a caps-word that ignore modifiers? Keep it in caps-word in a separate PR?

At some point @joelspadin seemed open to add this to #1742. I am not sure what the current verdict is but IMHO this would be the best solution. Otherwise, I could also open a new PR for just that.

@infused-kim
Copy link
Contributor

@urob how about a behavior that doesn’t define an allowed list at all? Instead it could just be “smart layer toggle”, which turns on a layer and if a none or trans behavior on it is hit, the layer is deactivated.

People have been using your PR to implement smart nav and symbol layers.

This would allow to create a generalized implementation that doesn’t need any customization for those.

@joelspadin
Copy link
Collaborator

I just want to get #1742 merged first, and then I can look into more enhancements like allowing it to cancel when modifiers are pressed (assuming I understand what you're wanting to do).

@urob
Copy link
Contributor Author

urob commented May 8, 2024

@urob how about a behavior that doesn’t define an allowed list at all? Instead it could just be “smart layer toggle”, which turns on a layer and if a none or trans behavior on it is hit, the layer is deactivated.

That makes a lot of sense. Instead of a continue-list one could have a cancel-list. It would default to &trans &none but allows adding other conditions like &kp G on a "vim-num" layer.

What I am not sure atm is how hard it would be to switch to a condition model that's behavior-based and is able to detect &trans and &none. But I definitely see the merit and think it's worth exploring.

@infused-kim
Copy link
Contributor

@urob

What I am not sure atm is how hard it would be to switch to a condition model that's behavior-based and is able to detect &trans and &none. But I definitely see the merit and think it's worth exploring.

I will soon start working on a feature that needs the same functionality and recently asked about it in the #development discord channel. Currently there is only a "pretty gross" way to accomplish it without adding new trans and none behaviors.

But if a major feature like your num layer implementation is using it and I also need it, then maybe it would make sense to turn the "dirty way" into a proper API or notification event.

@zxku
Copy link

zxku commented May 9, 2024

That makes a lot of sense. Instead of a continue-list one could have a cancel-list. It would default to &trans &none but allows adding other conditions like &kp G on a "vim-num" layer.

I would agree that the ability to cancel using &trans and &none would be incredibly useful. Though I can appreciate the complexity of implementing that functionality.

I was initially going to suggest having different "flavors" of the smart layer behavior, like the hold tap behavior has to specify a 'cancel-list' flavor, or a 'continue-list' flavor. But, I cannot think of an instance when you would need the continue-list if it is switching to a different layer and you cancel out with &trans and &none. The 'continue-list' is simply the key mappings you include on that smart layer. And if I'm understanding it correctly, it would also allow more complex behaviors to be executed on that smart layer without cancelling it out.

@urob
Copy link
Contributor Author

urob commented Aug 2, 2024

Specifically, I am thinking of a more focused smart-layer behavior that takes one argument (the layer index) and activates the layer until a key not in the ignore list is pressed. [It] would allow us to pre-define a fully functional num_word which could be added to the keymap with &num_word LAYER instead of needing to overwrite the behavior node.

I implemented this a new module: https://github.com/urob/zmk-auto-layer

To keep the new behavior focused, I dropped all of the original caps-word functionality. I won't be updating this PR for now so that anyone using the old implementation can still access it. If there is sufficient demand for updating this PR with the new implementation or opening a new one, I might do so in the future.

@zoriya
Copy link

zoriya commented Aug 23, 2024

I used this PR to just for the /delete-property/ ignore-modifiers; (solution from #1410). Should just this feature be cherry-picked in a new PR?

Maybe re-open/update #1422

@urob
Copy link
Contributor Author

urob commented Aug 26, 2024

I used this PR to just for the /delete-property/ ignore-modifiers; (solution from #1410). Should just this feature be cherry-picked in a new PR?

Maybe re-open/update #1422

For now this PR should continue to work. In the long-run, I prefer to re-create something new on top of #1742. Note that #1742 includes a no-default-keys option, which can be used replicate the ignore-alphas and ignore-numbers flags.

The only thing currently missing is a way to terminate caps-word on mods (or more generally, configure which mods terminate caps-word). Maybe @joelspadin has some ideas to add that. Otherwise, I can look into it again once #1742 is merged.

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.

Feature request: continue-mod-list for caps-word