AutoHotkey, the scripting language built around SetWindowsHookEx
, allows defining
"hotstrings". A hotstring is a string that, when typed anywhere, triggers a certain
action. For example, you can define:
::hello::привет
Which will replace the string "hello"
with the string "привет"
, after typing
the former followed by an "ending character" (e.g. space or enter).
You can also make it so that the ending character is not required:
:*:hello::привет
Which will trigger the replacement immediately after you type "hello"
.
And you can make the hotstring be recognized anywhere:
:?:hello::привет
Which will, for instance, replace "123hello"
with "123привет"
. Without the ?
,
a hotstring will not be recognized in the middle of words.
And, these options can be combined:
:*?:hello::привет
Finally, a hotstring can be defined as calling a function:
:*?:hello::{
MsgBox "test"
}
Which will show a message box when "hello"
is typed, and delete the typed hotstring.
Now this is all fine, but what if we want to do this:
:*?:a::something
:*?:ab::something else
If we type "a"
, it's immediately replaced by "something"
. Typing "b"
then has
no effect. It makes a certain amount of sense: AutoHotkey can't know in advance that
we want to type "ab"
, so when it sees the first "a"
it immediately triggers
the shorter hotstring.
However, looking through the documentation, we find this little snippet:
... consider the following hotstring:
:b0*?:11:: { SendInput "xx" }
Since the above lacks the Z option, typing 111 (three consecutive 1's) would trigger the hotstring twice because the middle 1 is the last character of the first triggering but also the first character of the second triggering.
What this actually says, is that the b0
option does not reset
the hotstring recognizer. Which means we can rewrite our hotstrings as:
:b0*?:a::{
SendInput "{BS}something"
}
:b0*?:ab::{
SendInput "{BS} else"
}
Now, when "a"
is typed our custom function gets called. Since b0
is in effect
the "a"
is left alone, so we have to erase it ourselves (that's the {BS}
, which
sends a backspace keystroke), then send the replacement string.
If a subsequent "b"
is pressed, we again delete it and append the "else".
Okay, but what if we were to do this:
:b0*?:a::{
SendInput "{BS}something"
}
:b0*?:aa::{
SendInput "{BS} else"
}
This won't work because the second "a"
press will still trigger the first hotstring.
That's because hotstrings are processed in the order they are defined in the script.
So we just swap the order, right?
:b0*?:aa::{
SendInput "{BS} else"
}
:b0*?:a::{
SendInput "{BS}something"
}
That's better, but still not good enough. We're now running afoul of the original
issue described in the documentation. If we type "aaaa"
we'll get
"something else else else"
, when what we really want1 is
"something elsesomething else"
.
So we apply the fix from the docs — adding the Z
option:
:b0*?Z:aa::{
SendInput "{BS} else"
}
:b0*?:a::{
SendInput "{BS}something"
}
Sometimes you have to reverse-engineer the documentation to get what you want.
-
Presumably. ↩︎