Introduction
Prior to a friend of mine mentioning espanso to me, I was unaware of such a thing as a text expander. I was familiar with the idea of expanding text, and until I adopted espanso, I was utilizing Neovim's configuration file to set custom text expansion. For example within my init.vim (which I used instead of lua at the time), I had set up a shortcut to write out "console.log()" whenever I inputted "ccl" while in insert mode.
I had many such shortcuts, mainly meant for use with various console calls in JavaScript, as well as some generic coding shortcuts. At one point my friend had pointed me in the direction of a new text expander written in rust known as espanso, and today it has become a regular part of my workflow. Whether it be a part of the command line, Neovim, or my browser, I utilize espanso in nearly every aspect of my workflow now, and I felt it was worthwhile to cover in detail.
What Is A Text Expander?
Most standard workflows by "Power Users" involve the use of keyboard shortcuts. Mainly Ctrl+C, Ctrl+V, Ctrl+A, and Ctrl+Z. If one is particularly resourceful, they are aware of various keyboard shortcuts involving the additional use of ALT and SHIFT keys, but I digress. While these shortcuts are still very useful and a necessary tool in the belt of any serious Computer user, these methods still only hold a candle to the ultra capable workflows one can achieve with a tiling window manager, a modal text editor, and an ortholinear keyboard (as well as the Vimium extension installed on the browser). Each of these subjects I have covered in detail in previous articles, and each of them continually improves upon the user's implementation of the keyboard-centric workflow. There is, however, one other tool that greatly adds to the keyboard-centric workflow, and that is the text-expander.
Let's say you have a series of commands or parts of a document that you repeatedly find yourself typing over and over again. Commonly known as boilerplate, these repeated pieces of text can become cumbersome and are the bane of any serious computer user, as this information is usually a formality, and has little to do with the meat of the content of the document itself. Of course, if there is a lot of text, one may find oneself simply copying and pasting the text, or making a multitude of copies via some script. Let's say there is some subtle difference between the documents though? Well using standard UNIX utilities like grep, sed, and awk can accomplish what one wants in an automated fashion. So where does that leave the text expander? With so many other tools available to the modern power user like autocomplete in the shell and text editor, as well as keyboard shortcuts like Ctrl+C/Ctrl+V in pretty much every application, what more could you need?
Indeed, when I first discovered that there even were applications like text expanders, I found myself wondering why they even existed when one could write aliases for your shell, and write custom shortcuts for NeoVim. After a short period of time, however, it dawned on me the power of text expanders, and that power was the ability to write quick aliases/text shortcuts that could be used in any application. And when used in conjunction with shell scripts and aliases, the power of text expansion becomes even more obvious.
Espanso Basics
While I could go into the details of the espanso program itself, I'll simply leave this link to the official website for you to peruse. There are many other text expanders out there, but espanso is open source and OS-agnostic. Additionally it is written in rust, which is known to provide fast applications when utilized by a capable programmer. It is the only text expander I have used, so I cannot speak to how it compares to others, but considering the aforementioned features, along with my own positive experiences utilizing the program, is enough reason for me to not investigate the alternatives at the time of this writing. Should you wish to install espanso and try it for yourself, please see their official installation documentation.
Using espanso is very simple. For example once you have the daemon running (see the documentation), you can check your ip address simply by typing in any application:
#bash shell
[~]$ :ip
This will expand out to display your ip address! Of course, one could easily see this using command line. Prior to using espanso, I had created a bash alias to do just that (and admittedly it gives more information than espanso does by default):
#bash shell
[~]$ alias ipaddr="curl ifconfig.me/all.json | jq"
Now, if this was all espanso ever did, I'd say the program was trivial and this article wouldn't even exist. The power of espanso is not in its out of the box settings, but rather in its simple config file. On Linux, this configuration file can be found in:
#espanso config path
~/.config/.config/espanso/match/base.yml
Here, you will find the default text expansions under the section "match":
#base.yml
matches:
# simple text replacement
- trigger: ":espanso"
replace: "Hi there!"
And indeed if we type out the trigger ":espanso", we'll see our text is replaced with "Hi there!":
There are various default examples showing espanso's utility. If you look towards the bottom of the example text expansions, you'll see the ":ip" example, which includes a very similar command to my ip alias:
#base.yml
#Returns public ip address
- trigger: ":ip"
replace: "{{output}}"
vars:
- name: output
type: shell
params:
cmd: "curl ifconfig.me ; echo ''"
Writing Espanso Aliases
Now, all this is well and good, but what sort of things can one accomplish with this tool? Well one of my most constantly repeated series of code that I need on the fly is "console.log()" whenever debugging a JavaScript file. While this isn't a lot of text to write, it can indeed get cumbersome to rewrite it many times over, and so one my first espanso aliases was:
#base.yml
- trigger: ";cll"
replace: "console.log()"
Note that instead of using the colon, I opted to use the semicolon for my custom espanso aliases as the likelihood of typing out anything directly after a semicolon is even less likely than after a colon (especially if using Vim in command mode). While this might seem trivial at first, if anyone has ever debugged a JavaScript file using this most basic of tools, one can see how useful this could be.
Within JavaScript (or any programming language), there are many times one can find themselves writing various words or series of code blocks over and over again, here a few examples of what I have written in my espanso config to occasionally speed this up:
#base.yml
- trigger: ";for
replace: "for (;;) {}"
- trigger: ";while"
replace: "while () {}"
- trigger: ";if"
replace: "if () {}"
- trigger: ";elif"
replace: "else if () {}"
- trigger: ";else"
replace: "else {}"
- trigger: ";$"
replace: "`${}`"
- trigger: ";do"
replace: "do {} while{}"
- trigger: ";func"
replace: "function () {}"
- trigger: ";=>"
replace: "() => {}"
- trigger: ";try"
replace: "try {} catch(error) {}"
Okay, so what about use outside of the terminal? Well in writing any website, any good web developer will be typing either "127.0.0.1:" or "localhost:" a lot, right? Well why not use this espanso alias instead?:
#base.yml
- trigger: ";loc"
replace: "localhost:"
Again, many of these seem trivial on the surface, and you might even be rolling your eyes reading this thinking "Really? You can't be bothered to type out nine characters vs four?" I'll admit time savings rarely come up to typing out only a few less characters, but one of the powers of Vim is its ability to accomplish more with less keystrokes, and when used in its entirety, I'd argue that no one using the classic keybindings available in the majority of non-modal text editors can accomplish the same speed of workflow as those who use some form of modal text editing. Espanso moves these cumulative time savings into any application where typing is a part of its interface!
Let's move onto another use for espanso. I'm a big fan of using DuckDuckgo's bangs. If you're unaware of bangs, check out the website. Essentially, if one has DuckDuckgo as their default search engine (or searx, which has this feature too), one can simply enter into your search/url bar: !yt and any other words typed after that will be directly searched in YouTube. There are a multitude of these bangs available and I'd encourage you to take a look at what is available. There are, however, a few websites that DuckDuckgo hasn't integrated into bangs, so what to do? While I'm sure there are other ways to accomplish this, I have utilized espanso to do this for certain sites, like Odysee, a Youtube alternative:
#base.yml
- trigger: "!ody"
replace: "https://odysee.com/$/search?q="
Note my decision to use the exclamation point instead of the semicolon here, in order to keep with the consistencies of DuckDuckgo's bang syntax. When I type "!ody" into my url, this immediately expands to "https://odysee.com/$/search?q=" and I can now type in my search query. This is even more efficient on the utilized network than DuckDuckgo's bangs as it never even queries DuckDuckgo before re-routing to the requested page. I also use this same technique for other sites, like Quetre, a non-Javascript front-end for Quora:
#base.yml
- trigger: "!que"
replace: "https://qr.vern.cc/search?q="
An Interesting Use Case
Now, any other power user of Linux and Vim would note, "yeah, but I can easily accomplish alot of these same things in the shell or vim itself, I don't need a daemon listening in the background taking up (a little bit) of RAM for this." Which is a fair point, but I'd argue that what one can accomplish with espanso integrated into your workflow with the shell, vim, and the browser, is just a little more streamlined than without. Now, this last and final example on the uses of the espanso text expander will involve use of the shell, a shell script, and NeoVim. I am positive there are other ways of accomplishing what I have done here, but nevertheless I am happy with the result as it stands right now.
For a while, I had been writing and rewriting a simple shell script that would save my current working directory. Additionally the script would take an optional argument of a file which would be utilized by a separate script or alias that would return me immediately to the saved directory and open the specified file when invoked. So, for example, if I wanted to save my current directory with a file I was working on, I could call the script like so:
#bash shell
[~]$ sdir myfile.txt
The script very simply saves an environment variable called $sdir, and an optional environment variable for the file called $sdoc into a dotfile. A corresponding alias, "rdir" could then return to said directory like so:
#bash shell
[~]$ rdir
Originally, rdir was a shell script that looked for the existence of the $sdoc, and if it existed, would call nvim on it, and if it didn't, would simply navigate to the saved directory $sdir. This worked for some time, but I wanted to be able to save a different directory to this dotfile and seamlessly return to it, and the built-in command "cd", doesn't work in shell scripts. I thought "No problem, cd works in an alias, I'll just source the file in the alias and then use cd to move into that new directory." Only to find out that source didn't work inside an alias...
Like I said, there are probably other, less hacked together, ways to accomplish my goal here. Nevertheless, it dawned on me that I could remove my rdir script and forego using an alias by simply combining the sdir shell script with an espanso alias like so:
#base.yml
- trigger: ";rr"
replace: "source ~/.config/.sdrc && cd $sdir && nvim $sdoc"
- trigger: ";rd"
replace: "source ~/.config/.sdrc && cd $sdir && ls"
Although the above commands solved my issues with both cd and source in the shell, typing this entire command out each time I wanted to start working again would have been cumbersome. Using the espanso text-expander daemon takes care of that for me! This solution is definitely not as elegant as some might like, but it serves my simple purposes (save my current directory and a desired file so I can easily navigate back to it, and also easily save a different directory and file at any time).
Conclusion
Admittedly, there are probably many other use cases for espanso I have not explored in this article. If one looks over the documentation in more detail, one will find a myriad of other useful tips more in-depth than the ones I have presented here, so please take the time to check out their great documentation. Now that I utilize espanso, I can't really imagine working without it. It's like the last puzzle piece to my keyboard-centric work flow was filled in when I discovered this amazing tool.
To cap things off, I'll share with you the simple sdir shell script below (as well as this link on my GitHub). Additionally, I'll provide you with some lines of Lua code that when put into your NeoVim configuration files, will return you to wherever you last saved in your files (this works incredibly well with the aforementioned sdir script and espanso aliases, as I simply return to the exact directory, file, and place in that file I left off on simply by typing ";rr" into the command line). Enjoy, and please do check out espanso, it's a great project!
#!/usr/bin/env bash
# sdir.sh
# used with espanso ;rr and ;rd command
# A simple bookmarking script used in conjunction with rdir
# to save a directory for easy navigation later
# save our current working directory
sdir=$(pwd)$#10sdoc="${1:-''}"
# allow overwriting of sdrc
set +o noclobber
# define and create .sdrc file
sdrc="$HOME/.config/sdir".sdrc
if [[ ! -f "$sdrc" ]]; then
/usr/bin/touch "$sdrc"
fi
if [[ $# -gt 0 ]]; then
sdoc="$1"
else
sdoc=""
fi
echo 'export sdir='"$sdir" > "$sdrc"'
echo 'export sdoc='"$sdoc" >> "$sdrc"'
-- init.lua
-- jump to last place visited in file
vim.api.nvim_create_autocmd('BufReadPost', {
callback = function()
local mark = vim.api.nvim_buf_get_mark(0, '"')
local lcount = vim.api.nvim_buf_line_count(0)
if mark[1] > 0 and mark[1] <= lcount then
pcall(vim.api.nvim_win_set_cursor, 0, mark)
end
end,
})