espanso path
Expand your productivity with espanso
Custom text snippets with espanso: a text expander written in Rust
I recently discovered a wonderfully useful little program called espanso – a “text expander” that I now use on a daily basis. Text expander programs “expand” short text “triggers” into longer / more complex strings of text, like this:
There are lots of these programs out there, such as Typinator, or the creatively-named “textexpander” (macOS even has a build-in expander). But I prefer to use open source software whenever possible (the espanso source code is on GitHub), and especially when using software that tracks the text you type (imagine how easy it would be to grab passwords with this type of software). Plus espanso is free 😄. Of course if you’d like to support the project you can donate here.
More than anything, I started (and kept) using espanso because I found it to be remarkably simple to use and easy to customize. So far, it has integrated quite nicely into my day-to-day workflow. In fact, I have become such a fan of espanso that I decided to sit down and write a whole post highlighting some of the ways I use it.
Quick overview
Note: I’m skipping a lot of details about installation, etc. because it’s all there in the espanso documentation. It’s really simple to get it up and running – check it out.
To set a text expansion, all you have to do is edit the default.yml
file. You can find it’s location with this in the terminal:
/Users/jhelvy/Library/Preferences/espanso
Once you’ve opened up the default.yml
file, you can define an expansion like this:
# Simple text replacement
- trigger: ":test"
: "Hello World!" replace
Now anytime I type :test
, the text will expand to Hello World!
. Pretty cool! 😎
What I really love about this interface is it’s simplicity. With just a few lines you can define a wide range of custom expansions. After using it for just a few weeks, I have already come up with several dozen expansions that I now regularly use.
I’ll spend the rest of this post highlighting a few use cases, but for reference I also included a copy of most of my settings at the end of this post (I took out some snippets with personal information like the link to my personal Zoom room, etc.)
Personal information
I can’t stand having to type my name, address, email, etc. into online forms. I probably have to do this once a week or so for one reason or another, so coming up with some quick expansions for common personal information is extremely satisfying.
Note: It’s probably a really bad idea to put passwords you use in your espanso settings, so don’t do that!
Here are a few I’ve set up:
Trigger | Expansion |
---|---|
":jp" |
"John Paul Helveston" |
":jh" |
"John Helveston" |
":title" |
"Assistant Professor" |
":dept" |
"Engineering Management and Systems Engineering" |
":gw" |
"George Washington University" |
"@!" |
"jph@gwu.edu" |
URLs
I used to rely on bookmarks for lots of common sites I visit, but I often need to copy-paste the actual url to those sites (e.g. if I’m sending the link to someone else). Before, I had to open up my browser, navigate to the site, then copy the url. Now I can just type a few keys 🎉! I start each of these with "//"
. For example, to get to my GitHub page, I just type //gh
:
By far my most used url expansion this year was //zoom
to expand to the url to my personal zoom room! 🚀
Code
This is where things start getting really helpful. I write lots of common code snippets for a wide variety of use cases, and the ability to automate these globally (i.e. not just in a code IDE) on my computer is an incredible help. The time savings don’t come from avoiding the extra keystrokes – they come from avoiding typos! It’s all too easy to miss a tiny symbol or letter in a chunk of code which then results in several minutes (if not longer) of searching for the bug.
As a convention, I decided to start all of my “code” triggers with a double letter of the first letter of the code language. So html expansions all start with hh
, R expansions start with rr
, python with pp
, etc. I use this rather than starting with the default :
symbol because 1) I find it easier to remember, and 2) it doesn’t require me to use the Shift key (so it’s fast).
HTML
I don’t write a lot of html, but occasionally I’ll use it to insert an image in a RMarkdown document. And usually I want that image centered. The basic template I usually start with is:
<center>
<img src="" width=100%>
</center>
I use the trigger hhimg
(which I remember as “html” + “img”) to insert this snippet. Here’s the code to define this expansion:
- trigger: "hhimg"
: "<center>\n<img src=\"$|$\" width=100%>\n</center>" replace
Notice the $|$
symbols inside the quotes following “src”. This sets the location of the cursor once the expansion is complete, allowing me to quickly type in the name of the path to the image I want to insert. Another one of those little bonus features about espanso that is just 🤌 *chef’s kiss*.
R
Since I use R more than any other language, I’ve come up with a lot of R-specific shortcuts. But before I dive into them, a quick side note:
One important modification I learned (thanks to some help on the espanso Reddit community) is that if you’re working in RStudio, the expansions don’t quite work correctly on their own (I believe it might have something to do with RStudio’s autofill feature). To fix it, I had to insert the force_clipboard: true
setting into each espanso expansion. For example, in my snippet to read in a CSV file, I inserted this option between the trigger and expansion text like this:
- trigger: "rrcsv"
: true
force_clipboard: "read_csv($|$)" replace
Okay with that in mind, I’ll highlight a few R expansions I use a lot. I’ve organized this based on my common workflow for exploring data:
- Add a setup chunk (if working in Rmd)
- Load libraries & data
- Wrangle data
- Visualize
Add a setup chunk
I usually include a “setup” chunk in most of my Rmd files, but it’s really hard to remember all the specific details for various settings, so I made a dedicated expansion just for this. With this expansion, I can insert my whole default setup chunk with rrsetup
:
- trigger: "rrsetup"
force_clipboard: true
replace: |```{r setup, include=FALSE}
knitr::opts_chunk$set(
warning = FALSE,
message = FALSE,
fig.path = "figs/",
fig.width = 7.252,
fig.height = 4,
comment = "#>",
fig.retina = 3
)
```
Load libraries & data
I use some simple shortcuts to load individual libraries, like rrtidy
to expand to library(tidyverse)
. But (as shown in the above image) I also set up an expansion to load some of my most often used libraries and settings. By typing rrstart
, I get this expansion:
library(tidyverse)
library(cowplot)
options(dplyr.width = Inf)
With my libraries loaded, I then read in my data. I use rrcsv
to expand to read_csv($|$)
, rrex
to expand to read_excel($|$)
, and rrhere
to expand to here::here($|$)
, which I use to define paths to files.
Side note: if you don’t already, you should definitely try using the {here} package to define paths.
Wrangle data
I usually wrangle my data using tidyverse commands, and I have come up with a few expansions for some common “pipelines”. For example, one common sequence is to do grouped summary operations. I use the rrgs
(“gs” for “grouped summary”) trigger to load the code block:
group_by() %>%
summarise()
Likewise, I use the rrgm
trigger to load the code block for a grouped mutate:
group_by() %>%
mutate()
There are so many of these you could put together!
Visualize
Since {ggplot2} has such a distinct syntax, I decided to start my ggplot code snippets with gg
rather than rr
. Some of my favoriate {ggplot2} expansions are the scaling shortcuts:
ggsxc
–>scale_x_continuous()
ggsyc
–>scale_y_continuous()
ggsxd
–>scale_x_discrete()
ggsyd
–>scale_y_discrete()
I also use some snippets for common geoms, like ggpoint
:
ggplot() +
geom_point(aes(x = , y = ))
and ggcol
:
ggplot() +
geom_col(aes(x = , y = , fill = ))
Paste clipboard contents as plain text
This is probably my favorite little hack. Often times, I want to copy-paste a snippet of text from one source to another, but when I go to paste it somewhere else, it retains all of the text formatting from the original source. My previous work around was to paste it first into a text editor (e.g. Sublime Text), then re-copy-paste it from there with the formatting now stripped away. But since espanso has support for working with your “clipboard”, I found I could simply paste the plain text by using the force_clipboard: true
. I use ppp
as the trigger for this:
- trigger: "ppp"
: true
force_clipboard: "{{clipboard}}"
replace:
vars- name: "clipboard"
: "clipboard" type
Open things
You can run terminal commands using an espanso trigger. I haven’t found too many ways to use this feature, but it is helpful for opening files or folders that may be buried deep down in some folder structure. For example, I use the :espanso
trigger to open the default.yml
file to edit my espanso settings:
- trigger: ":espanso"
: "{{output}}"
replace:
vars- name: output
: shell
type:
params: "open '/Users/jhelvy/gh/espanso/espanso/default.yml'" cmd
Dates
These come from the espanso documentation, but they’re super useful:
- trigger: ":date"
: true
force_clipboard: "{{today}}"
replace:
vars- name: today
: date
type:
params: "%Y-%m-%d"
format
- trigger: ":time"
: true
force_clipboard: "{{time}}"
replace:
vars- name: time
: date
type:
params: "%H:%M" format
Packages
There are already a lot of great espanso packages. My favorite so far is the emoji package. It’s super convenient, but I often forget the name of specific emojis, so I made another expansion to the url to an emoji cheat sheet site:
- trigger: "//emo"
: true
force_clipboard: "https://www.webfx.com/tools/emoji-cheat-sheet/" replace
Highlights from my default.yml
file
Thanks for reading, and I hope you check out espanso! Here are some of the expansions I have come up with in my default.yml
file (for now…again, I removed a few that had some personal information)
# espanso configuration file
# This is the default configuration file, change it as you like it
# You can refer to the official documentation:
# https://espanso.org/docs/
# Matches are the substitution rules, when you type the "trigger" string
# it gets replaced by the "replace" string.
matches:
# Sign offs
- trigger: "cjo"
force_clipboard: true
replace: "Cheers,\nJohn"
- trigger: "bjo"
force_clipboard: true
replace: "Best,\nJohn"
- trigger: "rjo"
force_clipboard: true
replace: "Regards,\nJohn"
- trigger: "cjp"
force_clipboard: true
replace: "Cheers,\nJP"
- trigger: "cpr"
force_clipboard: true
replace: "Cheers,\nProfessor Helveston"
# URLs
- trigger: "//emo"
force_clipboard: true
replace: "https://www.webfx.com/tools/emoji-cheat-sheet/"
- trigger: "//dr"
force_clipboard: true
replace: "https://drive.google.com/drive/u/0/my-drive"
- trigger: "//gh"
force_clipboard: true
replace: "https://github.com/jhelvy/"
- trigger: "//jh"
force_clipboard: true
replace: "https://jhelvy.com/"
- trigger: "//talks"
force_clipboard: true
replace: "https://jhelvy.com/talks"
- trigger: "//pubs"
force_clipboard: true
replace: "https://jhelvy.com/publications"
- trigger: "//gm"
force_clipboard: true
replace: "https://mail.google.com/mail/u/0/"
# GW
- trigger: ":title"
force_clipboard: true
replace: "Assistant Professor"
- trigger: ":gw"
force_clipboard: true
replace: "George Washington University"
- trigger: ":dept"
force_clipboard: true
replace: "Engineering Management and Systems Engineering"
# Names
- trigger: ":jp"
force_clipboard: true
replace: "John Paul Helveston"
- trigger: ":jh"
force_clipboard: true
replace: "John Helveston"
# HTML
- trigger: "hhimg"
force_clipboard: true
replace: "<center>\n<img src=\"$|$\" width=100%>\n</center>"
- trigger: "hhcss"
force_clipboard: true|$\n</style>"
replace: "<style>\n$
- trigger: "hhbr"
force_clipboard: true
replace: "<br>"
# R
- trigger: "rrrm"
force_clipboard: true
replace: "rm(list = ls())"
- trigger: "rrrs"
force_clipboard: true
replace: "rs.restartR()"
- trigger: "rrlib"
force_clipboard: true
replace: "library($|$)"
- trigger: "rrtidy"
force_clipboard: true
replace: "library(tidyverse)\n"
- trigger: "rrdply"
force_clipboard: true
replace: "library(dplyr)\n"
- trigger: "rrcow"
force_clipboard: true
replace: "library(cowplot)\n"
- trigger: "rrops"
force_clipboard: true
replace: "options(dplyr.width = Inf)\n"
- trigger: "rrstart"
force_clipboard: true
replace: "library(tidyverse)\nlibrary(cowplot)\noptions(dplyr.width = Inf)\n"
- trigger: "rrsetup"
force_clipboard: true
replace: |```{r setup, include=FALSE}
knitr::opts_chunk$set(
warning = FALSE,
message = FALSE,
fig.path = "figs/",
fig.width = 7.252,
fig.height = 4,
comment = "#>",
fig.retina = 3
)
```
- trigger: "rrcsv"
force_clipboard: true
replace: "read_csv($|$)"
- trigger: "rrex"
force_clipboard: true
replace: "read_excel($|$)"
- trigger: "rrhere"
force_clipboard: true
replace: "here::here($|$)"
- trigger: "rread"
force_clipboard: true
replace: "read_csv(here::here($|$))"
- trigger: "rrgs"
force_clipboard: true
replace: "group_by($|$) %>%\n summarise()\n"
- trigger: "rrgm"
force_clipboard: true
replace: "group_by($|$) %>%\n mutate()\n"
- trigger: "filterne"
force_clipboard: true
replace: "filter($|$ != )"
- trigger: "rrin"
force_clipboard: true
replace: " %in% "
- trigger: "ggsxc"
force_clipboard: true
replace: "scale_x_continuous($|$) +"
- trigger: "ggsyc"
force_clipboard: true
replace: "scale_y_continuous($|$) +"
- trigger: "ggsxd"
force_clipboard: true
replace: "scale_x_discrete($|$) +"
- trigger: "ggsyc"
force_clipboard: true
replace: "scale_y_discrete($|$) +"
- trigger: "ggex"
force_clipboard: true
replace: "expand = expansion(mult = c(0, 0.05))"
- trigger: "rrch"
force_clipboard: true
replace: "```{r$|$}\n```"
- trigger: "rrmd"
force_clipboard: true
replace: "rmarkdown::render($|$)"
- trigger: "rrxi"
force_clipboard: true
replace: "xaringan::inf_mr()"
- trigger: "rrgg"
force_clipboard: true
replace: "ggplot($|$) +\n"
- trigger: "ggcol"
force_clipboard: true
replace: "ggplot() +\n geom_col(aes(x = $|$, y = , fill = ))\n"
- trigger: "ggpoint"
force_clipboard: true
replace: "ggplot() +\n geom_point(aes(x = $|$, y = ))\n"
# Dates
- trigger: ":date"
force_clipboard: true
replace: "{{today}}"
vars: - name: today
type: date
params:
format: "%Y-%m-%d"
- trigger: ":time"
force_clipboard: true
replace: "{{time}}"
vars: - name: time
type: date
params:
format: "%H:%M"
# Paste clipboard contents as plain text
- trigger: "ppp"
force_clipboard: true
replace: "{{clipboard}}"
vars: - name: "clipboard"
type: "clipboard"
# Open things
- trigger: ":espanso"
replace: "{{output}}"
vars: - name: output
type: shell
params: cmd: "open '/Users/jhelvy/gh/espanso/espanso/default.yml'"