Build Your Own Snippet Manager
Way back in the day, when I used a Mac for a few years, I really liked the TextExpander tool. You hit a key, pick a snippet, and it’ll paste the snippet in the currently focused text area. It can do all kinds of fancy other things, like inserting timestamps into your snippets, too, but I only ever used a subset of its features.
I found myself missing that functionality on my Linux machine a year or two ago, so I decided to write my own! I’m sure there are plenty of existing tools that’ll do what I want, but it’s not too complicated to build my own and it’s a fun process: I’m a professional engineer,1 but I’m also a “computer hobbyist,” and I take some intrinsic pleasure in building and using my own tools.2
I just found myself explaining my setup to someone and wishing I had a post to direct them toward, so here we are!
I use three external dependencies in my manager:
- xdotool, a utility to send keyboard and mouse events to X from the command line. You can use it to programmatically move the mouse around, click, and type keys.
- rofi, a graphical fuzzy-finder and application launcher. It’s similar to dmenu, or a subset of Alfred.
- fuzz, a little gem I wrote a while back for integrating with fuzzy-finders
like
rofi
through Ruby scripts.
To install those dependencies (on Debian or Ubuntu, at least):
$ sudo apt install xdotool rofi
$ gem install fuzz
Here’s how they work together.
I’ve added a keybinding in i3, my window manager, that invokes a Ruby script
when I hit Super-s
(for “snippet”). Here’s the line from my ~/.i3/config
:
# Insert a snippet
bindsym Mod4+s exec ~/bin/snippet.rb
The snippet.rb
script defines some snippets, invokes rofi
through fuzz
to
let me choose one, and then shells out to xdotool
to type the snippet.
Here’s the script:
#!/usr/bin/env ruby
require "fuzz"
class Snippet
attr_reader :description, :text
def initialize(text, description = nil)
@text = text
@description = description || text
end
def to_s
description
end
end
SNIPPETS = [
# Personal info
["hello@harryrschwartz.com"],
["https://harryrschwartz.com"],
["https://github.com/hrs"],
# ... elided snippets ...
# Mathematical symbols and Unicode
["→", "→) right arrow"],
["∧", "∧) logical and"],
# ... elided snippets ...
["¢", "¢) cent"],
["°", "°) degree"],
["§", "§) section"],
# Malarkey
["ಠ_ಠ", "ಠ_ಠ) look of disapproval"],
].map { |pair| Snippet.new(*pair) }
snippet = Fuzz::Selector.new(
SNIPPETS,
cache: Fuzz::Cache.new("~/.cache/fuzz/snippets"),
).pick
system("xdotool type \"#{ snippet.text }\"")
A Snippet
has both text (that’s what gets typed) and a description (what the
user sees in the fuzzy finder). Distinguishing between the two makes it easy to
fuzzy-find Unicode characters that would otherwise be hard to type.
We define a list of lists, each of which includes some text and, optionally, a
description. Those are converted into Snippet
objects.
Next, we invoke fuzz
to prompt the user to pick one of those snippets. rofi
is the default picker, so we don’t need to explicitly refer to it, but we could
choose a different picker if we liked. #pick
calls #to_s
on each snippet
before displaying it to the user, then returns the associated object.
Notice that we’re referencing a cache. This argument tells fuzz
to record our
selection and, in future invocations of the script, to put that selection nearer
the top of the list. That makes our snippet manager learn which snippets we use
most often and makes it easier to choose them in the future.
Finally, once the user has chosen a snippet, we shell out to xdotool
to type
the associated text. That’ll write out the characters, just as if we’d manually
typed them, into the selected text area.
So, that’s how I define and type snippets! I’ve found it to be a really convenient way to easily fill out forms and enter Unicode characters. If you’re also of the “hobbyist” persuasion, and like building your own tools even when there might be a technically better solution out there, I hope you find this strategy useful!
-
For client work, of course, this probably isn’t the approach I’d take. ↩
-
See, for reference, Gerald Weinberg’s distinction between amateur and professional software. This is amateur, and totally fit for its thoroughly limited purpose. Especially since its real purpose is just my pleasure in creating it! ↩
You might like these textually similar articles: