Typing
Abbreviations
A short run of characters that turns into a longer one as you type it, wherever you are typing. The third way of setting something off, beside a copy and a chord: the keyboard itself.
An abbreviation is a short run of characters that turns into a longer one as it is typed. Type !email and it becomes test@example.com, wherever the typing is happening — a mail window, a terminal, somebody else's app.
Each one is two things: what is typed and what it expands to. The expansion is either
- text, typed as it is written; or
- a pipeline, run when the abbreviation fires, with what it produces typed in its place.
Which of the two it is, is chosen rather than guessed at: an expansion meant literally may perfectly well hold a $ or a |, and running one nobody asked to have run is a worse mistake than typing one out.
Pipeline expansions
A pipeline expansion is the same language the search bar speaks and reaches the same things:
$signature a variable, on its own
$1 | trim the newest copy, tidied
$1 | jira-link the newest copy, turned into a link
"Dear $name," ++ $1 text written out, with pieces filled in
| now | strftime %F today's date
Unlike a pipeline action, one here needs no filters to be worth running: what it reads has somewhere to go, since it gets typed, so $signature alone is a whole abbreviation.
A > $name inside one is honoured — and since a redirect sends the result somewhere instead of to the clipboard, such an abbreviation stores its result and types nothing. A forget means what it always means: the entry the pipeline read from is dropped.
A template that asks makes a form letter out of a few keystrokes. With an expansion of $letter | fill --ask %contact, typing the abbreviation puts up a small form for whatever %contact does not fill — a reference number, a date — and the finished letter is typed where the abbreviation was once you press ↩. esc leaves the abbreviation on screen, as a failed expansion does. See Asking for the rest.
How one fires
The app keeps the run of characters typed since the last thing that broke the run, and when the end of that run is an abbreviation, it fires.
Nothing about this is a word boundary. !email fires at the end of write to me at !email, which is what makes a leading ! worth having — an abbreviation of letters alone fires inside ordinary words too, and the pane says so where one is written.
The run ends, and starts over, on anything that moves the caret somewhere the app has not been watching: ↩, ⇥, ⎋, an arrow key, ⌦, a jump around the document, a click, a chord with ⌘ or ⌃ on it, or another app coming to the front.
⌫ takes a character back off the run rather than ending it, so a typo can be corrected and the abbreviation still fires; ⌫ past the start of the run leaves it empty, since what is to the left of where the run began was never seen. ⌥ does not break a run — on many layouts it is how a character is typed. At most 64 characters are remembered, or as many as the longest abbreviation defined.
Where two abbreviations both finish on the same keystroke, the longest wins: with !em and !email both defined, typing the second means the second, since the first would have fired three keystrokes ago if it were what was wanted. Between two of the same length the topmost wins, and the pane flags the duplicate — the lower of the two can never fire.
How the expansion arrives
The characters you typed are taken back out with as many ⌫s, and the expansion is typed in their place — as the characters themselves rather than as the keys that would produce them, so an em dash arrives on a keyboard that has no way to type one.
A tab in an expansion is typed as the Tab key, so in a form it moves to the next field, the way TextExpander's Tab key does.
It types rather than pastes on purpose: nothing goes on the clipboard, so what you had copied is still there afterwards, no history entry is made, and no trigger fires on an expansion.
The order matters too. The expansion is worked out before anything is deleted, so a pipeline that fails leaves the keyword sitting on screen rather than a hole where it used to be; what it had to say goes to the HUD.
Typing is slower than pasting for long text, at about a millisecond and a half per key press.
A picture or formatted text is pasted instead, since characters can carry neither. An abbreviation that expands to $logo pastes the picture, and one that expands to $sig — a signature kept from Mail with its bold and its link — pastes it formatted. So does a template with a formatted variable in it, such as "Thanks,\n${sig}": the signature keeps its look and the words around it paste plain. Anything that leaves plain characters behind is still typed: $sig | unfmt, or a filter that rewrites the text too much for its formatting to be kept.
For the paste, what you had copied is set aside, the expansion goes on the clipboard just long enough to be pasted, and then what you had copied comes back. The expansion is marked as passing through, so it makes no history entry, sets off no trigger, and other clipboard histories that respect the marker skip it. If you copy something in that moment, your copy is kept rather than overwritten.
Putting the caret where you want it
After an expansion, the caret is at its end. Write {{|}} in the expansion to put it somewhere else, or write it twice to select what lies between:
Dear {{|}}, see you soon the caret lands after "Dear "
To: {{|}}name{{|}}, hi "name" is selected, ready to type over
It works in text and pipeline expansions alike, and in one that is pasted formatted. The marks are taken out, and the caret is moved back with the ← key, as TextExpander does it — so an app that adds characters of its own as you type, such as a code editor indenting a new line, can leave it a little off.
In formatted text, what you type takes the look of the character before the caret. So <b>Name: {{|}}</b> keeps typing bold, but <b>{{|}}</b> with nothing else inside is not: once the mark is taken out, there is no bold left to continue.
Only abbreviations act on {{|}}. In the search bar or an action it is left as written, and fill never asks about it.
What it costs
Watching what is typed means a listen-only event tap, which reads the key stream without touching it: nothing typed is delayed, swallowed or rewritten on the way to the app it was meant for. It needs the same Accessibility permission automatic pasting needs, and the pane says so, with a link, while there is something to watch for and no permission to watch with.
The tap only exists while there is something to watch for. With no abbreviations defined — which is every install until somebody writes one — there is no abbreviation tap. Disabling the last one takes it back down. (The app does keep a separate tap for counting pastes, which hears ⌘V and no other key.)
Two things are never read:
- anything typed while a password field has secure input on, which macOS suppresses event taps for entirely;
- anything typed while an excluded app is in front — an app PasteDaemon has been told to keep out of is one it keeps out of.
The app's own synthesised keys are marked as its own and ignored, so an expansion holding !email cannot set that abbreviation off again.
When abbreviations stop for no reason
Secure input is the one thing that stops abbreviations without leaving anything to see. Any app can switch it on — it is how a password field keeps what you type out of every other app on the Mac — and some apps forget to switch it back off, at which point nothing expands anywhere and PasteDaemon looks perfectly well.
So the app watches for it. When secure input has been on for a while, held by an app that is not the one you are typing into, the Abbreviations pane names it. There is nothing to press: only the app holding it can let go, and quitting it is the surest way. The warning clears itself as soon as it does.
Nothing is said while the app holding it is the one in front, because then nothing is wrong — that is a password field doing its job, and you did not want an abbreviation expanding into it.
The Abbreviations pane
A table of what is typed and what it turns into, with a panel below for the selected one — an expansion can run to a paragraph or to a pipeline, neither of which fits in a row one line high, so the row shows the expansion and the panel edits it.
Each row has a switch, the characters typed, a Kind of Text or Pipeline, and what it expands to. Adding one starts it at !abbr1, which nothing else answers to and which carries the leading ! the pane recommends. The count at the bottom says how many are actually watched for when that is fewer than there are rows — a row with no expansion yet is not one.
Click a column's heading to sort the table by it, and again to reverse it. A keyword you are editing stays where it is until you click away from it. Sorting only changes how the table looks: when two abbreviations share a keyword, the one added first is still the one that fires.
To switch many at once, select them — ⇧-click extends the selection to a row and ⌘-click adds or removes one — then tick or untick the switch on any of the selected rows: they all follow it. The minus button, or ⌘⌫, removes every selected row, and the panel below shows while just one is selected.
⌘I in the expansion opens the overlay inside it, to find a copy or a variable to write in — as quoted text or $name for a pipeline, as plain text for text. See the overlay.
The panel says three things beyond the field:
- For a pipeline, what it will do in words — where it reads from, whether it ends in a redirect and so types nothing, and any step naming a filter that does not exist. Run It Now runs it against the clipboard as it stands and shows what would be typed, without typing it anywhere and without storing anything a
> $namewould store. - For text with line breaks in it, that they are typed as
↩, which some fields read as "send". - For an abbreviation of letters and digits alone, that it will fire inside ordinary words as well as on its own.
When something is standing between your abbreviations and the keyboard, a warning at the top of the pane says what. A missing Accessibility permission comes with a link to the setting and a Recheck button that claims the keyboard as soon as it has been granted; a keyboard macOS refused comes with Try Again; and an app holding secure input is named.
Bringing snippets over from Alfred
If Alfred is on your Mac, the first launch offers your snippets alongside your clipboard history, and the Abbreviations pane has an Import from Alfred… button. It reads every snippet collection in Alfred's preferences, wherever you have synced them, and opens the Import Items window on them as abbreviations. Tick what you want and choose Import. A collection you exported from Alfred (an .alfredsnippets file) comes in the same way through Import… in Preferences → General.
Each snippet becomes an abbreviation answering to its keyword, with the collection's prefix and suffix added, just as Alfred answers to it. Nothing of Alfred's is changed.
- A snippet with no placeholders arrives as text, typed exactly as written.
- A snippet with placeholders arrives as a pipeline that does the same job:
| In Alfred | Here |
|---|---|
{clipboard}, {clipboard:1} | ${1}, ${2}. Alfred counts the newest copy as 0, this app as 1 |
{clipboard.trim.uppercase} | $(${1} | trim | upper). lowercase and capitals work too |
{date}, {time:short}, {datetime:full} | $(now | strftime …), written in US English |
{date +1D:EEEE} | $(now | dateadd +1d | strftime "%A") |
{isodate}, {isotime}, {isodatetime} | the same, in ISO 8601 |
{random:UUID} | a fresh UUID, in capitals as Alfred writes it |
{random:a,b,c}, {random:1..5} | one drawn at random each time |
{snippet:!sig} | the other snippet's text, written in place |
{cursor} | {{|}}, where the caret goes |
Some things have no equivalent here, and the row in the import window says so:
{var:…}and{query}name things only Alfred has, so they are left in the text as written.- Date patterns and modifiers this app cannot write — a quarter, say, or
.stripdiacritics— are also left as written, or skipped for a modifier. - A snippet Alfred only pasted from its search, with automatic expansion off, arrives switched off.
A snippet with no keyword cannot become an abbreviation, because there would be nothing to type, so it is left out. So is a second snippet answering to a keyword an earlier one already uses. The window counts both under the list.
Bringing snippets over from TextExpander
If TextExpander is on your Mac, the first launch asks for this file. Otherwise, or later, export the group from TextExpander and bring the file in:
- On textexpander.com, open Import/Export, go to the Export tab and download the group as CSV.
- In PasteDaemon, open Preferences → Abbreviations, click Import Snippets… and choose the file. (Import… in Preferences → General takes it too.)
- Tick what you want and choose Import.
Each snippet becomes an abbreviation answering to its abbreviation. A snippet with no macros arrives as text, typed exactly as written. One with macros arrives as a pipeline that does the same job:
| In TextExpander | Here |
|---|---|
| Clipboard | ${1}, the newest copy |
Dates and times: %Y-%m-%d, %A, %e, %1m, %date:MMMM d% | $(now | strftime …) |
| Date math, such as 7 days from now | dateadd +7d before the date |
| Insert Snippet | the other snippet's text, written in place |
| Tab and Return keys | a tab, a line break |
| Fill-ins: single line, multiple lines, popup menu, optional section | a form that asks as the abbreviation fires |
Fill-ins put up a form, as they do in TextExpander. A field's default and a popup's choices are offered there, starting on the default, and you can still type something else. An optional section is a checkbox, ticked to start if it was in TextExpander (see optional sections).
Some things don't carry over, and the row in the import window says so:
- Arrow keys, and keys other than Tab and Return, are not pressed. The cursor position does carry over, as
{{|}}. - Rich text keeps its formatting: the export writes it as HTML, and the abbreviation pastes it through
html-rich. A Tab key inside rich text is not pressed. - Shell script and AppleScript snippets arrive as their script, typed as written. The export doesn't say which snippets are scripts.
A code you typed into a snippet by hand, rather than inserting it from TextExpander's menus, is just text to TextExpander, and arrives as text here too; its row says so. A snippet that would come to nothing here, such as date math with no date after it, is shown greyed with the reason rather than imported empty.
Bringing snippets over from Raycast
Raycast exports snippets two ways, and PasteDaemon reads both:
- Export Snippets, in Raycast's snippet commands, saves a file of just the snippets.
- Export Settings & Data saves a
.rayconfigholding the snippets along with your clipboard history and the rest of Raycast's data. It has a password, which you'll be asked for.
In Preferences → Abbreviations, click Import Snippets… and choose either file, then tick what you want and choose Import. A .rayconfig also comes in through the history import, where its row offers the snippets with a tick of their own.
Each snippet becomes an abbreviation answering to its keyword. One with no placeholders arrives as text, typed exactly as written; one with placeholders arrives as a pipeline that does the same job:
| In Raycast | Here |
|---|---|
{clipboard}, {clipboard offset=1} | ${1}, ${2}: the newest copy, the one before |
{date}, {time}, {datetime}, {day} | $(now | strftime …) |
offset="+3d", format="EEEE d MMMM" | dateadd +3d, the same format |
{argument}, with a name, a default or options | a form that asks as the abbreviation fires |
{cursor} | {{|}} |
{snippet name="…"} | the other snippet's text, written in place |
{uuid} | $(| uuid) |
uppercase, lowercase, trim, percent-encode, json-stringify | upper, lower, trim, url-encode, json-escape |
Arguments put up a form, as they put up fields in Raycast. A default is where the field starts, options are its choices, and two arguments with the same name are asked once.
Some things don't carry over, and the row in the import window says so:
{calculator}has no equivalent and is typed as written.- A
localeon a date is not used: dates are written as US English writes them. - Formatting, such as a link or bold in a snippet, is not kept; the text is.
{selection}and{browser-tab}only work in Raycast's AI commands, and are typed as written.
A snippet with no keyword is left out — Raycast reaches those from its search.