How many times in your day to day work or computer use at home do you find yourself typing the same text over and over again? Whether its a specific piece of information like your email address, telephone number, your company name or web address or whether its a longer text like a postal address, an email signature block or standard greeting or sign off text for emails you probably do a lot of repeat typing… And aren’t computers meant to reduce the amount of repeated work? If your job is in IT there may be long shell commands you use regularly or blocks of code that you frequently use. You may need to use text expansion with espanso.

The importance of minimising repeat typing has already been recognised by web browser designers with the option to auto complete web forms, but what if you’re not using a browser? What are your options?

There are 2 widely available choices:

  • a clipboard manager with your most frequently used phrases ready to paste in
  • an auto complete tool that replaces what you type with your chosen text

Both of these have their place and they can be used together to get the best of both worlds. We’re going to discuss the second option, the auto complete tool or text expander .

What is auto complete and text expansion?

You probably already use a form of auto complete, you might not even know it. If you have auto correction turned on in any application you use this is a form of auto complete. You type the letters t e and h preceded by and followed by a space. Your computer knows “teh” is not a real world and can automatically delete your typed characters and replace them with the word “the”. Likewise it can replace “shoudl” with “should”.

But what if we could easily customise these words and their replacements?

What if it was more than just correction of typos?

What if the solution worked across multiple applications and even multiple operating systems with a standard set of initial texts that triggered a suitable replacement?

What if we could type the letters “my_address” and that triggered the computer to replace it with “344 Clinton Street, Apartment 3B, Metropolis, USA."?

That saves us typing 40 characters every time we need that address.

What about a trigger of GDPR being replaced with General Data Protection Act?

What about a trigger of NCSC14 being replaced with “National Cyber Security Centre fourteen principles of cloud security“. If you regularly write extensively on a topic there are probably a whole list of phrases, acronyms or words that you repeatedly type that could have much shorter triggers. You might really benefit from using text expansion with espanso.

What makes a good trigger?

A good trigger needs to have the following properties

  • be easy to remember
  • not be something that you would type normally

If you cant remember your trigger its not going to save you any time. Having triggers that follow a logical pattern can help.

If your trigger is a word that you type in your normal vocabulary then it will replace when you don’t want it to. Look for patterns that don’t exist in your normal typing. For example in normal typed text some punctuation marks are nearly always followed by a space. If you do technical writing or write computer programs using code then other punctuation marks are often used for other reasons, Page.print uses a dot to separate a class from its methods and properties. $my_string uses a dollar sign to denote a variable. A semi colon “;” is normally always followed by a space or a new line. Because of this using a semicolon as a prefix to your trigger is normally a safe option to make your trigger not something you would type normally and on most keyboards you don’t need to press Shift to get it, one more keypress saved.

A shorter trigger of ;addr becomes “344 Clinton Street, Apartment 3B, Metropolis, USA.” saving even more characters.

Setting up your triggers and replacements

Some operating systems have auto correct functions built in. On the Mac in System Settings under Keyboard there is an option for “Spelling and Prediction” and a list of “Text Replacements”. Most built in systems have definitions that are not easy to transfer between computers. Most times its not possible to transfer between different operating systems. Using third party software the triggers and their replacements are often defined in plain text files with a simple structure. This means you can easily edit them using your favourite editor and they can be copied between computers just like any other file or even shared across a whole office so everyone gets the benefit.

How to chose the right tool

There are lots of tutorials, documentation and user forums you can check for how to install, configure and start using each tool. In this case we’ll be looking at Espanso and in this use case we’ll be looking at using it on Mac or Linux, although it is available for Windows and it can achieve similar functionality. This page will show you each end of the complexity spectrum of configuration so you can see how easy it can be and how capable it can be.

My First Espanso Trigger

A configuration file looks like this

- trigger: ;addr
  replace: 344 Clinton Street, Apartment 3B, Metropolis, USA.

That is about as simple as it can get. But, depending on how adventurous you are, it is a lot more capable than just these simple replacements

- trigger: ;addr
  replace: |
    344 Clinton Street, 
    Apartment 3B, 
    Metropolis, 
    USA.

This definition uses the | pipe character to let espanso know to include new lines as defined in the replacement.

What about other addresses like email addresses? When did you last see two @ characters together? Probably not that often. Let’s exploit that fact:

- trigger: @@
  replace: @myemaildomain.com

Just start typing the email address and then put two @ signs and espanso will complete the domain part of your email address. The good thing here is that your muscle memory will mean that your fingers are already reaching for the @ key, you just have to press it twice and it will complete the domain part.

How complex can it get?

Let’s skip all the intermediate steps covered in the documentation and go for something really complex. After all, when you set off on a journey its good to know what your destination is so you know you’re travelling in the right direction. When you learn a piece of software its good to know that its going to meet your needs and great if it will exceed your needs.

Here’s what you need if you want to copy text from the a web page in an Internet browser and paste it in a format called markup:

 - triggers: [";dwclip",";2dw",";todw"]
    replace: "{{md_output}}"
    vars:
      - name: md_output
        type: script
        params:
          args:
            - /venvs/python3.14/bin/python
            - -c
            - |
              import sys
              import re
              from PyQt5.QtWidgets import QApplication
              from bs4 import BeautifulSoup
              import pypandoc


              def is_html(content: str) -> bool:
                  """Check if content contains real HTML tags."""
                  try:
                      soup = BeautifulSoup(content, "html.parser")
                      return bool(soup.find())  # at least one tag
                  except Exception:
                      return False

              def clean_text(text: str) -> str:
                  """Remove non-printing characters and normalize spaces."""
                  # Remove all control chars except newline/tab/carriage return
                  text = re.sub(r"[^\x20-\x7E\n\r\t\u00A0]", "", text)
                  # Normalize multiple blank lines: 3+ → 2
                  text = re.sub(r"\n{3,}", "\n\n", text)
                  # Replace non-breaking spaces with regular spaces
                  return text.replace("\u00A0", " ").strip()

              def main():
                  app = QApplication(sys.argv)
                  clipboard = app.clipboard()
                  mime = clipboard.mimeData()

                  if mime.hasHtml():
                      html_content = mime.html()
                      if not is_html(html_content):
                          print("Clipboard contains HTML format but no valid tags.")
                          sys.exit(0)
                      try:
                          md = pypandoc.convert_text(html_content, "dokuwiki", format="html")
                          print(clean_text(md))
                      except Exception as e:
                          print(f"Conversion failed: {e}")
                  elif mime.hasText():
                      text = mime.text()
                      if text.strip():
                          print(clean_text(text))
                      else:
                          print("Clipboard is empty.")
                  else:
                      print("Clipboard does not contain HTML or text.")

                  sys.exit(0)

              if __name__ == "__main__":
                  main()

If you have copied text from a web page in your clipboard this replacement action uses Python code to strip out special characters, uses an application called pandoc to turn the web site formatting into markup and then outputs that new format. It takes a couple of seconds and can save a lot of work if document formatting is something you do often.

Maybe that was a step to far, maybe dial it back a bit…

Do you want a random passcode generator so that the passwords you generate are more random than you mashing your keyboard with fists? (Yes, we’ve all done that!). This one works best on Linux or Mac based systems because it relies on shell commands to generate random content, match against an approved list of characters and return a certain number of characters. This introduces the concept of regular expressions (like wildcards) and named groups in your trigger. You type the trigger ;randp and a 2 digit number eg ;randp20 and it will return a random string of that length.

#random pasword safe chars
  - regex: ";randp(?P<charcount>([\\d ][\\d ]))"
    replace: "{{myshell}}"
    force_mode: clipboard
    vars:
      - name: "clipboard"
        type: "clipboard"
      - name: myshell
        type: shell
        params:
          cmd: |
            LC_ALL=C tr -dc 'A-Za-z0-9!@#$%^&()][' </dev/urandom | head -c $ESPANSO_CHARCOUNT; echo

The named group called charcount is captured by matching 2 digit characters using the regular expression ([\\d ][\\d ]) from your trigger. This 2 digit number is passed to the script as the variable $ESPANSO_CHARCOUNT making the script choose that many characters to output.

Gotchas

Does it always work?

Long texts: sometimes using a long replacement text can be slow and if you continue to type too quickly after your trigger text things can get jumbled up. In this case slower typing is more effective. Its something to adapt to.

Remotely controlling another computer can cause some problems due to the way keypresses are processed and passed between computers. SSH doesn’t appear to be a problem but with RDP its best avoided. You can work around this by installing espanso on the remote computer as well and setting up a rule for your local espanso to ignore that application.

What Other Solutions are available?

Autohotkey is great, I spent a lot of time using this but as I moved more towards using a Mac and Linux I needed to find an alternative as this is a windows only application. It has some really useful features that are not available in espanso like capturing data from the current window (eg the URL in a browser or the title text of the active window).

These are some other choices too : Textexpander Typinator Phrasexpress Autokey