Using Espanso to boost Efficiency ๐Ÿšค

Intro

Espanso is an open source, privacy-first, cross-platform text expander developed by @federico-terzi and written in Rust. In short, it detects when you type a certain keyword, and replaces it on the fly with a pre-defined string or dynamic output.

Espanso not only supports simple text replacement/ expansion, but also images, custom scripts and shell commands, app-specific configurations and more. There is also a basic form feature, enabling arguments to be passed to a block. It's under active development, so hopefully there will be even more functionality implemented in the future.

It uses a file-based configuration, written entirely in YAML (but I think there is a GUI in development), and for the most part is quick and easy to it it configured exactly to your liking. But you can also use pre-built packages, installed via Espanso Hub (or any external source).

There are many possibilities where Espanso can be really useful, but the main areas that I am using it for are:

  • Quickly typing characters that do not appear on my keyboard (such as math symbols, foreign language characters and emojis)
  • Easily inserting longer strings that would otherwise have required many keystrokes
  • Inserting dynamic content, such as the output of a script, response from an API call, or time/ date info
  • Making typing easier, with a custom spelling and grammar auto-correct system

Espanso Links: Docs, Reddit, Package Hub, Source Code, Quick Start, Author's Site

I'm still working on my config, but for reference here it is: github.com/Lissy93/espanso-config


Use Cases

Easy Emoji Inputs

The first thing I used Espanso for was being able to type emojis, without having to use wait for a popup to load or use the internet.

There is a plugin that does exactly this perfectly, called espanso-all-emojis, by @FoxxMD, using gemoji. It can be installed with:
espanso install all-emojis

Then just type the name of the emoji, surrounded by colons. For example:
:smile: --> ๐Ÿ˜„, :rocket: --> ๐Ÿš€, :milky way: --> ๐ŸŒŒ

For reference, here is the full list emojis, along with their shorthand code

The next thing I wanted to do, was be able to easily insert old-school ASCII emoticons or Lenny faces. This could be done with a similar method, but I didn't want to have to remember all the key combinations. A perfect opportunity to give Espanso's form feature a go!

With the above code, typing :lenny will open up a form with a dropdown, using the arrow keys I can now select an option, hit enter and it will be inserted

Espanso Lenny Demo

# Easily inputs ASCII emoticons from a dropdown
- trigger: :lenny
  form: "{{smileys}}"
  form_fields:
    smileys:
      type: choice
      values:
      - 'ยฏ\\_(ใƒ„)_/ยฏ'
      - '(โ•ฏยฐโ–กยฐ๏ผ‰โ•ฏ๏ธต โ”ปโ”โ”ป'
      - '( อกเฒ  ส–ฬฏ อกเฒ )'
      - 'โ˜‰ โ€ฟ โš†'
      - 'ส•โ€ขแดฅโ€ขส”'
      - 'โ‹†๏ฝกหš โ˜๏ธŽ หš๏ฝกโ‹†๏ฝกหšโ˜ฝหš๏ฝกโ‹†'
      - '(ใฅแต”โ—กแต”)ใฅ'
      - '|แต”โ€ฟแต”|'
      - 'โคœ(*๏น*)โค'
      - 'ใƒ„'

Inserting Dynamic Content

Espanso has a series of built in extensions, that are able to insert dynamic data, either from a command, script, web address or API

An example of how this can be useful, is for fetching your current public IP address, using ipify.org:

# Outputs public IP address
- trigger: ":ip"
  replace: "{{output}}"
  vars:
    - name: output
      type: shell
      params:
        cmd: "curl 'https://api.ipify.org'"

Or the current weather in your location, using wttr.in:

# Outputs the current weather for your location
- trigger: ":weather"
  replace: "{{output}}"
  vars:
    - name: output
      type: shell
      params:
        cmd: "curl 'http://wttr.in/?format=3'"

Easily insert the MIT license:

# Outputs full MIT license text, from GitHub
- trigger: :mit-long
  replace: "{{output}}"
  vars:
  - name: output
    type: shell
    params:
      cmd: "curl 'https://gist.githubusercontent.com/Lissy93/143d2ee01ccc5c052a17/raw/a8ac96cd15847a231931b561d95d2de47066fd33/LICENSE.MD'"

Generating Deterministic Passwords on the Fly

LessPass is a stateless password manager, given a set of arguments (usually site, username and master pass) the output will always be the same, omitting the need to store passwords anywhere. I use it for less important accounts, and this sounded like another great use case for Espanso.

- trigger: :pass
  replace: "{{lesspass}}"
  vars:
  - name: "params"
    type: form
    params:
      layout: |
        Less Pass Generator
        Website: {{site}}
        Login: {{login}}
        Master Password: {{pass}}
  - name: lesspass
    type: shell
    params:
      cmd: "lesspass $ESPANSO_PARAMS_SITE $ESPANSO_PARAMS_LOGIN $ESPANSO_PARAMS_PASS"

With the above block, you can type :pass, and a form will popup, prompting you for the three arguments, and on submit a password will be returned and auto-filled. This does of course require the LessPass CLI tool to be installed.


Quickly Closing Brackets

This is a small one, saving only a single key press, but over time it all adds up. In Espanso, you can specify where the cursor should be placed using $|$

So typing a colon : followed by any type of bracket, tag or formatting symbol, will result in the corresponding closing bracket will be filled, and the cursor will be moved conveniently middle of the parenthesis.
This works for (), [], {}, <>, ` `, '', "", __, -- and **

# Auto close brackets, quotes and modifiers, putting cursor in the center
- trigger: ':('
  replace: '($|$)'
- trigger: ':['
  replace: '[$|$]'
- trigger: ':{'
  replace: '{$|$}'
- trigger: ':<'
  replace: '<$|$>'
- trigger: ':`'
  replace: '`$|$`'
- trigger: ":\'"
  replace: "\'$|$\'"
- trigger: ':"'
  replace: '"$|$"'
- trigger: ':_'
  replace: '_$|$_'
- trigger: ':*'
  replace: '*$|$*'
- trigger: ':-'
  replace: '-$|$-'

Date / Time Info

Another handy feature, is the built-in date extension. For the format of the date, see the chrono::format::strftime Docs.

# Outputs todays date (dd/mm/yy)
- trigger: :date
  replace: "{{date}}"
  vars:
  - name: date
    type: date
    params:
      format: "%d/%m/%y"

# Outputs the current time (24hr)
- trigger: :time
  replace: "{{time}}"
  vars:
  - name: time
    type: date
    params:
      format: "%H:%M"

# Outputs the month and year (e.g. January 2020)
- trigger: :month
  replace: "{{date}}"
  vars:
  - name: date
    type: date
    params:
      format: "%B %Y"

Inserting Links

This is handy if you find yourself often sharing links in forums, or pasting them in documents. It makes use of Espanso's handy built-in Clipboard Extension, to get the URL that has been copied.

This works for Markdown with :md-link, HTML with :html-link and BB Code with :bb-link.

# Outputs markdown link, with clipboard contents as the URL
- trigger: ":md-link"
  replace: "[$|$]({{clipboard}})"
  vars:
  - name: "clipboard"
    type: "clipboard"

# Creates a HTML anchor element, with clipboard contents as href
- trigger: ":html-link"
  replace: "<a href=\"{{clipboard}}\" />$|$</a>"
  vars:
  - name: "clipboard"
    type: "clipboard"

# Outputs BB Code link, with clipboard contents as the URL
- trigger: ":bb-link"
  replace: "[url={{clipboard}}]$|$[/url]"
  vars:
  - name: "clipboard"
    type: "clipboard"

For example, say you's copied have http://example.com and ran :html-link is would return <a href="http://example.com" /></a>, with the cursor in the middle, ready for the title.


Auto-Correct Typos

This is certainly the task that I use Espanso for most! And I have previously written a post outlining this.

If you're interested in doing this, I prepared a list of 4,200 of the most commonly misspelled words from Wikipedia, presented in AHK format, and wrote a quick script to convert it to Espanso YAML.

I personally just use the 250 words that I most often mistype / spell. The format looks like this (below), and my full script is here

matches:
- trigger: acsent
  replace: accent
  propagate_case: true
  word: true
- trigger: advesary
  replace: adversary
  propagate_case: true
  word: true

The word will not update until a terminator character (such as space or enter) is pressed (defined by word: true). The case will be propogated, (because propagate_case: true is set), so the output will match the case of the original word (either lower-case, upper-case or capitalized)


Inserting Common HTML and Markdown Elements

A simple one, if you find yourself often typing the symbols required for DOM elements, then this can save a bit of time.

Common tags, like :hr, :br, :div, :span, :para, :h1, :h2 etc are autofilled, with the cursor placed inside the tag ready for the value. For custom web components and XML tags, use :tag, and a form will open, where you can type the name of the element

Right now, for markdown, all I have is :md-code to insert a code block, and :md-collapse will in the very annoying <details><summary>, and again place the cursor inside.

# Inserts common HTML elements
- trigger: :hr
  replace: '<hr />'
- trigger: :br
  replace: '<br />'
- trigger: :div
  replace: '<div>$|$</div>'
- trigger: :span
  replace: '<span>$|$</span>'
- trigger: :h1
  replace: '<h1>$|$</h1>'
- trigger: :h2
  replace: '<h2>$|$</h2>'
- trigger: :h3
  replace: '<h3>$|$</h3>'
- trigger: :para
  replace: '<p>$|$</p>'

# Inserts any custom HTML, XML or web component tag 
- trigger: ":tag"
  replace: "<{{html.element}}>$|$</{{html.element}}>"
  vars:
  - name: "html"
    type: form
    params:
      layout: "XML / HTML Element Inserter\nTag Name: {{element}}"
      fields: { element: { type: text }}

# Inserts a markdown code block
- trigger: :md-code
  replace: "```\n$|$\n```"

# Inserts markdown collapsable section
- trigger: :md-collapse
  replace: "\n<details>\n\t<summary>$|$</summary>\n\t<p></p>\n</details>"

Inserting Personal Info

There are several things that I find I need to type quite often, for various reasons. For example, email addresses, phone numbers, social media links, address and other important details. For some of this, I just use shortcuts (e.g. :addr outputs my address), whereas for other tasks I use dropdowns.

For example, to insert a social media profile link, without having to remember different shortcuts for different services, I just type :social. I do the same thing with email addresses and project websites

# Inserts the URL to a selected website or social media platform
- trigger: ":social"
  replace: "{{social.links}}"
  vars:
  - name: "social"
    type: form
    params:
      layout: "Social Media Profiles \n{{links}}"
      fields:
        links:
          type: choice
          values:
          - 'https://aliciasykes.com'
          - 'https://listed.to/@lissy93'
          - 'https://github.com/lissy93'
          - 'https://stackoverflow.com/users/979052/alicia'
          - 'https://keybase.io/aliciasykes'
          - 'https://www.linkedin.com/in/aliciasykes'
          - 'https://www.reddit.com/user/lissy93'
          - 'https://twitter.com/Lissy_Sykes'
          - 'https://www.instagram.com/liss.sykes'
          - 'https://www.facebook.com/liss.sykes'
          - 'https://www.youtube.com/c/AliciaSykes'
          - 'https://direct.me/liss'

Formulating Search Queries

There are browser extensions and web services that do this already (like DuckDuckGo bangs), but it's often useful to search a specific website, without having to first navigate to it. This function will formulate the URL, with the correct parameters ready for searching. You can also use Ctrl + L to focus the address bar.

For example, :srch-wiki will output https://en.wikipedia.org/w/?search=. You can also search with the contents of your clipboard (swc), where the query will be automatically filled.

# Quick search, formulates the URL params for searching a given website
- triggers: [:srch-ddg, :search-duckduckgo]
  replace: 'https://duckduckgo.com/?q='
- triggers: [:srch-wiki, :search-wikipedia]
  replace: 'https://en.wikipedia.org/w/?search='
- triggers: [:srch-gh, :search-github]
  replace: 'https://github.com/search?q='
- triggers: [:srch-so, :search-stackoverflow]
  replace: 'https://stackoverflow.com/search?q='
- triggers: [:srch-dh, :search-dockerhub]
  replace: 'https://hub.docker.com/search?q='
- triggers: [:srch-wa, :search-wolframalpha]
  replace: 'https://www.wolframalpha.com/input/?i='
- triggers: [:srch-red, :search-reddit]
  replace: 'https://www.reddit.com/search/?q='
- triggers: [:srch-bbc, :search-bbc]
  replace: 'https://www.bbc.co.uk/search?q='
- triggers: [:srch-vt, :search-virustotal]
  replace: 'https://www.virustotal.com/gui/search/'
- triggers: [:srch-amz, :search-amazon]
  replace: 'https://amazon.co.uk/s?k='
- triggers: [:srch-yt, :search-youtube]
  replace: 'https://youtube.com/results?q='
- triggers: [:srch-maps, :search-maps]
  replace: 'https://www.google.com/maps/search/'
- triggers: [:srch-goo, :search-google]
  replace: 'https://google.com/search?q='

# Similar to above, but it uses the clipboard for the search query
- trigger: ":swc-ddg"
  replace: "https://duckduckgo.com/?q={{clipboard}}"
  vars: [{ name: "clipboard", type: "clipboard"}]
- trigger: ":swc-wiki"
  replace: "https://en.wikipedia.org/w/?search='{{clipboard}}"
  vars: [{ name: "clipboard", type: "clipboard"}]
- trigger: ":swc-gh"
  replace: "https://github.com/search?q={{clipboard}}"
  vars: [{ name: "clipboard", type: "clipboard"}]
- trigger: ":swc-so"
  replace: "https://stackoverflow.com/search?q={{clipboard}}"
  vars: [{ name: "clipboard", type: "clipboard"}]
- trigger: ":swc-dh"
  replace: "https://hub.docker.com/search?q={{clipboard}}"
  vars: [{ name: "clipboard", type: "clipboard"}]
- trigger: ":swc-wa"
  replace: "https://www.wolframalpha.com/input/?i={{clipboard}}"
  vars: [{ name: "clipboard", type: "clipboard"}]
- trigger: ":swc-red"
  replace: "https://www.reddit.com/search/?q={{clipboard}}"
  vars: [{ name: "clipboard", type: "clipboard"}]
- trigger: ":swc-bbc"
  replace: "https://www.bbc.co.uk/search?q={{clipboard}}"
  vars: [{ name: "clipboard", type: "clipboard"}]
- trigger: ":swc-vt"
  replace: "https://www.virustotal.com/gui/search/{{clipboard}}"
  vars: [{ name: "clipboard", type: "clipboard"}]
- trigger: ":swc-amz"
  replace: "https://amazon.co.uk/s?k={{clipboard}}"
  vars: [{ name: "clipboard", type: "clipboard"}]
- trigger: ":swc-yt"
  replace: "https://youtube.com/results?q={{clipboard}}"
  vars: [{ name: "clipboard", type: "clipboard"}]
- trigger: ":swc-maps"
  replace: "https://www.google.com/maps/search/{{clipboard}}"
  vars: [{ name: "clipboard", type: "clipboard"}]
- trigger: ":swc-goo"
  replace: "https://google.com/search?q={{clipboard}}"
  vars: [{ name: "clipboard", type: "clipboard"}]

Additional Notes

This is just a tiny tiny selection of things you could use Espanso for, the possibilities are almost endless, and I keep finding new ways to use it to speed up my typing. I'm excited about the future of the project, as new features and improvements are being added all the time.

Huge kudos to the author, Federico Terzi, who has done the bulk of the work himself.

Security

This is worth mentioning, as I am sure others will be wondering about it. Initially I was fearful of a system that could apparently intercept all of my keystrokes, but the author has highlighted that it has been built with security in mind, there is absolutely no logging, and Espanso has a memory of just 3 characters (in order for the backspace functionality to work). There's also no network requests, and I verified this myself, both in the source, any by running Wireshark.
The code is also extremely efficient, written in Rust, it uses virtually negligible system resources, even on a low-spec PC.


You'll only receive email when they publish something new.

More from Alicia's Notes ๐Ÿš€
All posts