Regex triggers
Regex triggers are a powerful alternative to regular ones, with a few extra features:
- They let you define triggers using a full-blown regex syntax, enabling use-cases that would be inconvenient or even impossible with regular triggers.
- They accept named groups, which can be used to convenientely pass arguments to expansions or external scripts.
This tutorial assumes you already know the basics of Regular expressions. If you don't, these resources can be a good starting point:
Basics
Before diving into the advanced topics, let's recap some of the basics.
Let's say we want to create a match that gets expanded to Hello!
every time
we write :greet2
.
With regular triggers, you could do the following:
- trigger: ":greet2"
replace: "Hello!"
After a while, you decide to extend the previous match to
all single-digits, such as :greet1
, :greet2
, :greet3
, etc.
With regular triggers, your only option would be to list all the possible ones:
- triggers:
- ":greet1"
- ":greet2"
- ":greet3"
- ":greet4"
- ":greet5"
- ":greet6"
- ":greet7"
- ":greet8"
- ":greet9"
- ":greet0"
replace: "Hello!"
As you can see, this approach hardly scales with multiple variations.
On the other hand, Regex triggers let you define the previous match very concisely:
- regex: ":greet\\d"
replace: "Hello!"
With this match, every time you write :greet1
or :greet7
, you'll get Hello!
As you can see from the previous example, we had to write :greet\\d
instead of :greet\d
.
That's because we need to escape backslashes for it to be a valid YAML double-quoted scalar. If the quote-marks around the string are omitted (plain scalar) this isn't necessary, which can make debugging easier.
Although this example showed an interesting use-case for regex triggers, it was only the tip of the iceberg. Regex triggers really start to shine when used to create dynamic triggers, which are explained in the next section.
Arguments
The most significant difference between regular triggers and regex ones is that the latter can be dynamic.
For example, the following trigger would only match with :greet(John)
- trigger: ":greet(John)"
replace: "Hi John!"
whereas we could define a regex trigger to match all names:
- regex: ":greet\\(.*\\)"
replace: "Hi John!"
The previous match works with any name. For example, both :greet(Bob)
and :greet(Mark)
cause an expansion.
Unfortunately, the match will always output Hi John!
, even when you specify other names.
To solve the problem, you'll need to use named groups, a really powerful regex feature that integrates well with Espanso. Let's refactor the previous example to use named groups:
- regex: ":greet\\((?P<person>.*)\\)"
replace: "Hi {{person}}!"
If you now type :greet(Bob)
, you'll see Hi Bob!
appear!
The key part is the (?P<person>.*)
block in the middle, which "captures" the text
contained between the two outer parenthesis (the ones we escaped), inside the person
group.
The powerful thing is that Espanso automatically converts named groups into variables, so that you can use them in replacements and scripts.
Although regular expressions are supported by most programming languages, their syntax can slightly vary between implementations. Espanso uses the regex library, which requires you to specify named groups as follows:
(?P<name>exp)
TLDR: Make sure to specify that P
, otherwise named groups won't work!
Keep in mind that you don't have to enclose your arguments within parenthesis, as that was only a matter of style. For example, you could also rewrite the previous example as:
- regex: "greet(?P<person>.*)\\."
replace: "Hi {{person}}!"
Which would expand to Hi Mark!
every time you write greetMark.
. As you can see,
you can go crazy with it!
Advanced examples
As explained in the previous section, Espanso automatically converts named groups into variables. This makes it possible to use the value of a named group inside scripts and shell commands, among other things.
In the next example, we are going to use the expr
command line utility to create a
match that calculates and expands the sum between two numbers dynamically.
- regex: "=sum\\((?P<num1>.*?),(?P<num2>.*?)\\)"
replace: "{{result}}"
vars:
- name: result
type: shell
params:
cmd: "expr $ESPANSO_NUM1 + $ESPANSO_NUM2"
If you now type =sum(3,4)
, Espanso will expand it to 7
!
In a nutshell, the two numbers are captured inside the num1
and num2
named groups,
which in turn are converted to Espanso variables.
As with all variables, you can use them inside the shell
extension by reading
the appropriate environment variables. In this case we are passing them
to the expr
command.
If you want to know more about the variable injection logic, please read the Variables section.
The expr
command is not available by default on Windows, so the previous example might not
work on that platform. You can still apply the concepts to other commands/scripts.
When NOT to use Regex triggers
Although regex triggers could completely replace regular ones, you should only use them when necessary. In other words, if a regular trigger (or a simple combination of them) could serve your needs, you should prefer them to regex ones.
That's because regex triggers, despite being more powerful, are also less efficient than regular ones. Their performance shouldn't be a problem unless you use thousands of them, but it's important to keep this in mind when defining matches.
Limitations
The current regex implementation is subject to a few limitations:
-
The maximum length of a "regex match" is set to 30 characters, including the captured named groups. For example, if your regex trigger is
:greet\\((?P<person>.*)\\)
and you type:greet(Bob)
, you've consumed 11 out of 30 characters. This limitation has been placed to improve performance, but we are planning to make this configurable. Please open an issue on GitHub if this is limiting you :) -
As explained in the previous sections, Espanso uses a Regex implementation that doesn't support all Regex features. For more information, please see the library documentation: https://docs.rs/regex/1.5.4/regex/