abbrs 0.2.8

Fast and safe abbreviation expansion for zsh
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
# abbrs

Fast and safe abbreviation expansion for zsh.

abbrs pre-validates your abbreviations at compile time — catching conflicts with existing commands before they cause problems — then uses a binary cache for instant expansion at runtime.

## What's abbrs

Traditional zsh abbreviation tools expand keywords at runtime without checking whether they collide with real commands in your `$PATH` or zsh builtins — you only discover the conflict when something breaks. They also rely on shell-script lookups that slow down linearly as your abbreviation list grows.

abbrs takes a different approach: **compile, then expand**. Running `abbrs compile` scans your PATH and builtins, rejects dangerous conflicts up front, and writes a binary cache. At expansion time, abbrs reads that cache for O(1) HashMap lookup — or runs as a persistent daemon (`abbrs serve`) communicating via Unix domain socket for sub-100 µs latency, regardless of how many abbreviations you have.

## Features

- **Compile-time safety** — Detects conflicts with PATH commands and zsh builtins before they cause problems
- **Sub-100 µs expansion** — Persistent daemon mode via Unix domain socket + binary cache (bitcode) for imperceptible latency
- **Layered expansion priority** — Contextual > Command-scoped > Regular > Global > Regex keywords > Prefix candidates
- **Multiple expansion modes** — Replace, Evaluate (shell command output), Function call, and Placeholder (cursor positioning)
- **Auto-recompilation** — Detects config changes automatically; no manual recompile needed
- **Prefix candidates** — Partial keyword input shows matching abbreviations as you type
- **Abbreviation reminders** — Notifies you when a shorter form was available for what you typed
- **Zero-friction migration** — Import from zsh aliases, fish abbreviations, and git aliases

## Installation

### From crates.io

```bash
cargo install abbrs
```

### From GitHub Releases (via mise)

```bash
mise install github:ushironoko/abbrs
```

### Build from source

```bash
cargo install --path .
```

### Setup

1. Generate a config file:

```bash
abbrs init config
```

This creates `~/.config/abbrs/abbrs.toml`.

2. Add the zsh integration to your `.zshrc`:

```bash
eval "$(abbrs init zsh)"
```

3. Compile your config:

```bash
abbrs compile
```

## Migrating from Aliases

### From zsh aliases

Pipe the output of `alias` into `abbrs import aliases`:

```bash
alias | abbrs import aliases
```

This parses each `alias name='expansion'` line and appends it to your `abbrs.toml`. Aliases that conflict with PATH commands are automatically marked with `allow_conflict = true`.

### From fish abbreviations

Pipe the output of `abbr` into `abbrs import fish`, or pass a file path:

```bash
fish -c "abbr" | abbrs import fish
# or
abbrs import fish /path/to/abbreviations.txt
```

### From git aliases

```bash
abbrs import git-aliases
```

## Configuration

Edit `~/.config/abbrs/abbrs.toml` to define your abbreviations.

### Regular Abbreviations

Expand only at command position (the beginning of a command):

```toml
[[abbr]]
keyword = "g"
expansion = "git"

[[abbr]]
keyword = "gc"
expansion = "git commit"
```

Typing `g` then pressing Space expands to `git `. But `echo g` does not expand, because `g` is not in command position.

### Global Abbreviations

Expand anywhere in the line:

```toml
[[abbr]]
keyword = "NE"
expansion = "2>/dev/null"
global = true
```

`curl example.com NE` expands to `curl example.com 2>/dev/null`.

### Contextual Abbreviations

Expand only when surrounding text matches regex patterns:

```toml
[[abbr]]
keyword = "main"
expansion = "main --branch"
context.lbuffer = "^git (checkout|switch) "
```

`main` expands to `main --branch` only after `git checkout` or `git switch`.

### Placeholders

Use `{{name}}` to mark positions where you want to type after expansion:

```toml
[[abbr]]
keyword = "gc"
expansion = "git commit -m '{{message}}'"
```

`gc` expands to `git commit -m ''` with the cursor placed between the quotes. Press Tab to jump to the next placeholder if there are multiple.

### Evaluate Mode

Execute a shell command and insert its output:

```toml
[[abbr]]
keyword = "TODAY"
expansion = "date +%Y-%m-%d"
evaluate = true
global = true
```

`TODAY` expands to the current date, e.g. `2026-03-08`.

### Command-Scoped Abbreviations

Expand only after a specific command:

```toml
[[abbr]]
keyword = "co"
expansion = "checkout"
command = "git"
```

`git co` expands to `git checkout`, but `co` alone does not expand.

### Function Mode

Run expansion as a shell function:

```toml
[[abbr]]
keyword = "mf"
expansion = "my_func"
function = true
```

### Regex Keywords

Use a regex pattern as the keyword:

```toml
[[abbr]]
keyword = "^g[0-9]$"
expansion = "git"
regex = true
```

### Settings

```toml
[settings]
# serve = true      # enable daemon mode for sub-millisecond latency (default: true)
# prefixes = ["sudo", "doas"]  # commands that preserve command position
# remind = false     # remind when abbreviation could have been used
# page_size = 10     # paginate candidate display (0 or omit = show all)
```

> **Migration note:** If you upgrade from a version before `serve` / `page_size` support, re-run `eval "$(abbrs init zsh)"` (or re-source your `.zshrc`) so the init script picks up the new protocol fields.

## Conflict Detection

When you run `abbrs compile`, abbrs scans your `$PATH` and checks zsh builtins to detect abbreviations that shadow existing commands.

| Conflict Type | Behavior |
|---|---|
| Exact match with a command in `$PATH` | Error |
| zsh builtin (e.g. `cd`, `echo`) | Error |

To allow a specific conflict:

```toml
[[abbr]]
keyword = "gs"
expansion = "git status --short"
allow_conflict = true
```

## Key Bindings

The zsh integration sets up the following key bindings:

| Key | Action |
|---|---|
| Space | Expand abbreviation, then insert space. While prefix candidates are shown, confirm current selection and expand |
| Enter | Expand abbreviation, then execute. While prefix candidates are shown, confirm current selection and execute |
| Tab | Cycle through prefix candidates when shown; otherwise jump to next `{{placeholder}}` (falls back to normal completion) |
| Ctrl+Space | Insert a literal space (no expansion). While prefix candidates are shown, cancel and restore original input |
| accept-line | Check for abbreviation reminders (when `remind = true`) |

## Prefix Candidates

When you type a partial keyword and press Space, abbrs shows matching abbreviations as candidates if no exact match is found.

For example, with these abbreviations defined:

```toml
[[abbr]]
keyword = "gc"
expansion = "git commit"

[[abbr]]
keyword = "gp"
expansion = "git push"

[[abbr]]
keyword = "gd"
expansion = "git diff"
```

Typing `g` then pressing Space displays:

```
  gc → git commit
  gp → git push
  gd → git diff
```

Space is **not** inserted — you continue typing to narrow down the candidates. Typing `gc` then pressing Space expands to `git commit` as usual.

Candidates respect abbreviation scope:

- At **command position**: regular, global, and command-scoped abbreviations are shown
- At **argument position**: only global and matching command-scoped abbreviations are shown

The prefix index is built automatically during `abbrs compile` — no extra configuration needed. Candidates are shown only when 2 or more matches exist.

## Adding Abbreviations from the CLI

Instead of editing `abbrs.toml` by hand, you can use `abbrs add`:

### Non-interactive

```bash
abbrs add g "git"
abbrs add gc "git commit -m '{{message}}'" --global
abbrs add main "main --branch" --context-lbuffer "^git (checkout|switch) "
abbrs add TODAY "date +%Y-%m-%d" --evaluate --global
abbrs add gs "git status --short" --allow-conflict
```

| Flag | Description |
|---|---|
| `--global` | Register as a global abbreviation |
| `--evaluate` | Run expansion as a shell command |
| `--function` | Run expansion as a shell function |
| `--regex` | Keyword is a regex pattern |
| `--command <CMD>` | Only expand as argument of this command |
| `--allow-conflict` | Allow conflicts with PATH commands |
| `--context-lbuffer <REGEX>` | Left-buffer regex for context matching |
| `--context-rbuffer <REGEX>` | Right-buffer regex for context matching |
| `--config <PATH>` | Use a custom config file path |

### Interactive

Run `abbrs add` without arguments to enter interactive mode:

```bash
abbrs add
```

You will be prompted for the keyword, expansion, type (regular / global / context), and other options.

## Commands

| Command | Description |
|---|---|
| `abbrs init config` | Generate a config template at `~/.config/abbrs/abbrs.toml` |
| `abbrs init zsh` | Output zsh integration script (usage: `eval "$(abbrs init zsh)"`) |
| `abbrs add` | Add an abbreviation interactively |
| `abbrs add <keyword> <expansion>` | Add an abbreviation with options |
| `abbrs erase <keyword>` | Erase an abbreviation from config (`--command`, `--global` to target specific entries) |
| `abbrs rename <old> <new>` | Rename an abbreviation keyword (`--command`, `--global` to target specific entries) |
| `abbrs query <keyword>` | Check if an abbreviation exists (`--command`, `--global` to target specific entries) |
| `abbrs show [keyword]` | Show abbreviations in re-importable `abbrs add` format |
| `abbrs compile` | Validate config, detect conflicts, and generate binary cache |
| `abbrs check` | Validate config syntax without compiling |
| `abbrs list` | Show all registered abbreviations |
| `abbrs import aliases` | Import from zsh aliases (stdin) |
| `abbrs import fish [file]` | Import from fish abbreviations |
| `abbrs import git-aliases` | Import from git aliases |
| `abbrs export` | Export abbreviations in `abbrs add` format |
| `abbrs remind` | Check for abbreviation reminders (called by ZLE) |
| `abbrs expand` | Expand an abbreviation (called by the zsh widget) |
| `abbrs next-placeholder` | Jump to next placeholder (called by the zsh widget) |
| `abbrs serve` | Start persistent daemon mode (Unix domain socket) for sub-100µs expansion latency |

## Auto-Recompilation

When you edit `abbrs.toml`, the next expansion automatically detects the stale cache and recompiles. No manual `abbrs compile` needed after config changes.

## Performance

abbrs is designed for imperceptible expansion latency. Below are benchmark results comparing abbrs with [zsh-abbr](https://github.com/olets/zsh-abbr).

### Architecture comparison

| | abbrs | zsh-abbr |
|---|---|---|
| Language | Rust (compiled binary) | Zsh (shell script) |
| Data structure | `FxHashMap` (O(1) lookup) | Zsh associative array |
| Invocation | External process / daemon via Unix domain socket (`abbrs serve`) | In-process function call |
| Cache format | bitcode (binary) | Plain text files |

### Expansion lookup (in-process, criterion)

The core HashMap lookup scales O(1) regardless of abbreviation count:

| Abbreviation count | Lookup time |
|---|---|
| 10 | 75 ns |
| 100 | 75 ns |
| 500 | 77 ns |
| 1,000 | 77 ns |

### End-to-end expansion latency

Measured with the comparison benchmark (`benchmarks/comparison/bench.zsh`, 1000 iterations per measurement):

| Abbreviation count | abbrs expand | abbrs serve (socket) | zsh-abbr |
|---|---|---|---|
| 10 | ~1.0 ms | ~0.05 ms | ~0.07 ms |
| 50 | ~1.0 ms | ~0.05 ms | ~0.12 ms |
| 100 | ~1.0 ms | ~0.05 ms | ~0.18 ms |
| 500 | ~1.1 ms | ~0.06 ms | ~0.70 ms |

> **Note:** `abbrs expand` includes fork+exec overhead (~1 ms), which dominates the actual lookup time. `abbrs serve` eliminates this by running as a persistent daemon, communicating via Unix domain socket — achieving **sub-100µs** latency that is faster than zsh-abbr at any scale.

### Other operations (criterion)

| Operation | Time |
|---|---|
| Global expansion (100 abbrs) | 81 ns |
| Placeholder expansion | 123 ns |
| Contextual expansion (50 regex patterns) | 27 µs |
| Cache read (100 abbrs, bitcode) | 62 µs |
| Cache read (500 abbrs, bitcode) | 297 µs |
| Config parse (100 abbrs, TOML) | 150 µs |

### Run benchmarks yourself

```bash
# Criterion microbenchmarks (Rust)
cargo bench

# End-to-end comparison with zsh-abbr (requires zsh + zsh-abbr installed)
zsh benchmarks/comparison/bench.zsh [iterations]
```

## License

MIT