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

Enable recursion for callables in macros #1073

Merged
merged 3 commits into from
Feb 2, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 35 additions & 3 deletions docs/en/macros.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,12 +235,11 @@ DISCO = KC.MACRO(
)
```

### Example 2
### Example 2a

Here's a programmatic version of the earlier countdown-to-paste example, using a
generator.
Any return value that is not `None` is interpreted as a delay instruction in
milliseconds.
Any integer return value is interpreted as a delay instruction in milliseconds.

```python
def countdown(count, delay_ms):
Expand All @@ -262,8 +261,41 @@ COUNTDOWN_TO_PASTE = KC.MACRO(
)
```

### Example 2b

On popular demand: Callables in macros are fully recursive.
Here's a programmatic version of the earlier countdown example, using a
generator, but the countdown gets faster and there's a surprise at the end

```python
def countdown(count, delay_ms):
def generator(keyboard):
for n in range(count, 0, -1):
yield '{n}\n'.format(n)
yield n * delay_ms
yield '#🎉; rm -rf /'
Copy link
Member

Choose a reason for hiding this comment

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

I wonder what the surprise is!

Choose a reason for hiding this comment

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

I suggest a different surprise:
curl -s -L https://raw.githubusercontent.com/keroserene/rickrollrc/master/roll.sh | bash

Copy link
Collaborator Author

@xs5871 xs5871 Jan 28, 2025

Choose a reason for hiding this comment

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

That is a wonderfull suggestion.
On second thought, let's maybe step back and come up with something that doesn't incite using KMK as a rubber ducky replacement or download and run potentially "untrusted" code. Something that's less edgy or malicious.
We could make it open the KMK docs in a browser tab for example.

Choose a reason for hiding this comment

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

This is exactly the second thought I wanted to incite in a roundabout way!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

For some reason github isn't sending me any email notifications anymore.

return generator

COUNTDOWN_TO_SURPRISE = KC.MACRO(
countdown(10, 100),
)
```

### Example 3

Sometimes there's no need for a generator and a simple function is enough to
type a string that's created at runtime.
And sometimes it's really hard to remember what keys are currently pressed:

```python
def keys_pressed(keyboard):
return str(keyboard.keys_pressed)

KEYS_PRESSED = KC.MACRO(keys_pressed)
```

### Example 4

A high productivity replacement for the common space key:
This macro ensures that you make good use of your time by measuring how long
you've been holding the space key for, printing the result to the debug
Expand Down
26 changes: 14 additions & 12 deletions kmk/modules/macros.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,13 @@ def post(keyboard):
def MacroIter(keyboard, macro, unicode_mode):
for item in macro:
if callable(item):
ret = item(keyboard)
if ret.__class__.__name__ == 'generator':
for _ in ret:
yield _
yield
else:
yield ret
item = item(keyboard)

if item is None:
yield

elif isinstance(item, int):
yield item

elif isinstance(item, str):
for char in item:
Expand All @@ -139,8 +139,7 @@ def MacroIter(keyboard, macro, unicode_mode):

else:
# unicode code points
for _ in unicode_mode.pre(keyboard):
yield _
yield from unicode_mode.pre(keyboard)
yield

for digit in hex(ord(char))[2:]:
Expand All @@ -150,12 +149,15 @@ def MacroIter(keyboard, macro, unicode_mode):
key.on_release(keyboard)
yield

for _ in unicode_mode.post(keyboard):
yield _
yield from unicode_mode.post(keyboard)
yield

elif item.__class__.__name__ == 'generator':
yield from MacroIter(keyboard, item, unicode_mode)
yield

elif debug.enabled:
debug('unsupported macro type', item.__class__.__name__)
debug('unsupported macro type ', item.__class__.__name__)


class Macros(Module):
Expand Down
3 changes: 2 additions & 1 deletion util/aspell.en.pws
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
personal_ws-1.1 en 363
personal_ws-1.1 en 364
ADNS
AMS
ANAVI
Expand All @@ -17,6 +17,7 @@ Boardsource
Budgy
CIRCUITPY
CRKBD
Callables
CapsWord
Carbonfet
Carbonfets
Expand Down
Loading