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
# 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.