raffi 0.25.0

A simple fuzzel launcher based on YAML file
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
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
---
title: Text Snippets
description: Reusable text values from inline, file, command, or directory sources
---

import { Aside, Tabs, TabItem, Card, CardGrid, Steps, Code } from '@astrojs/starlight/components';
import { Image } from 'astro:assets';
import snippetsScreenshot from '../../../assets/screenshots/text-snippets.png';
import placeholdersScreenshot from '../../../assets/screenshots/text-snippets-placeholders.png';
import bugReport from '../../../../../examples/snippets/bug-report.snippet?raw';
import reviewRequest from '../../../../../examples/snippets/review-request.snippet?raw';
import meetingNotes from '../../../../../examples/snippets/meeting-notes.snippet?raw';
import incidentHandover from '../../../../../examples/snippets/incident-handover.snippet?raw';
import followUp from '../../../../../examples/snippets/follow-up.snippet?raw';
import signOff from '../../../../../examples/snippets/sign-off.snippet?raw';

The native interface supports text snippets, which let you define reusable text values that can be searched and copied to the clipboard or typed into applications.

<Aside type="note">
Text snippets are only available in **native mode**. They are not available when using Fuzzel mode.
</Aside>

## Overview

Snippets can come from four different sources:

<div class="nav-cards">
  <CardGrid>
    <a href="#inline-snippets">
      <Card title="Inline" icon="pencil">
        Defined directly in the config file
      </Card>
    </a>
    <a href="#file-source">
      <Card title="File" icon="document">
        Loaded from an external YAML file
      </Card>
    </a>
    <a href="#command-source">
      <Card title="Command" icon="laptop">
        Generated by running a command (Alfred JSON format)
      </Card>
    </a>
    <a href="#directory-source">
      <Card title="Directory" icon="puzzle">
        Loaded from `.snippet` files in a directory
      </Card>
    </a>
  </CardGrid>
</div>

<Image src={snippetsScreenshot} alt="Text snippets list with a selected snippet and expanded preview" width={800} />

<style>
{`
  .nav-cards a {
    text-decoration: none !important;
    color: inherit;
    display: flex;
    height: 100%;
  }
  .nav-cards a article {
    flex: 1;
    transition: all 0.2s ease-in-out;
    cursor: pointer;
    display: flex;
    flex-direction: column;
    gap: 1rem;
    border: 1px solid var(--sl-color-gray-5);
    background-color: var(--sl-color-black);
  }
  .nav-cards a article .title {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 1rem;
    font-size: 1.2rem;
  }
  .nav-cards a article .title .icon {
    font-size: 2.5rem !important; /* Huge icons */
    color: var(--sl-color-purple);
    background: var(--sl-color-purple-low);
    padding: 0.5rem;
    border-radius: 0.5rem;
    border: 1px solid var(--sl-color-purple);
  }
  .nav-cards a:hover article {
    border-color: var(--sl-color-text-accent);
    background-color: var(--sl-color-gray-6);
  }
`}
</style>

## Usage

<Steps>
1. **Type Keyword**

   Type the configured keyword followed by a space (e.g., `em ` for emails).
   While you are still typing the keyword, Raffi shows a row describing what it
   does; press Enter or Tab on that row to jump straight into the source.

2. **Filter Results**

   Continue typing to filter snippets. Exact [keyword](#snippet-keywords-and-search)
   matches come first, then keyword prefixes, then fuzzy matches on the name,
   then fuzzy matches on the snippet content. Snippets you used recently come
   first when nothing is typed and win ties between equal matches

3. **Select Snippet**

   Use arrow keys to select the desired snippet

4. **Execute Action**

   - **Enter**: Default action (copy or insert)
       - **Ctrl+Enter**: Secondary action (if configured)

</Steps>

## Configuration Sources

### Inline Snippets

Define snippets directly in `raffi.yaml`:

```yaml raffi.yaml
addons:
  text_snippets:
    - name: "Emails"
      keyword: "em"
      icon: "mail"
      snippets:
        - name: "Personal Email"
          icon: "📨"
          value: "user@example.com"
        - name: "Work Email"
          value: "user@company.com"
        - name: "Support Email"
          value: "support@company.com"
```

Usage:

```text
em personal
# Filters to "Personal Email"
# Press Enter to copy user@example.com
```

### File Source

Load snippets from an external YAML file:

```yaml raffi.yaml
addons:
  text_snippets:
    - name: "Templates"
      keyword: "tpl"
      icon: "document"
      file: "~/.config/raffi/snippets.yaml"
```

The snippet file (`~/.config/raffi/snippets.yaml`):

```yaml snippets.yaml
- name: "Greeting"
  value: "Hello, world!"

- name: "Signature"
  value: "Best regards, User"

- name: "Meeting Template"
  value: |
    Meeting Notes
    Date: {date}
    Attendees: [NAMES]
    Topics:
    -
    Action Items:
    -
```

<Aside type="note">
File-based snippets are cached per session for performance.
</Aside>

### Command Source

Generate snippets dynamically from a command:

```yaml raffi.yaml
addons:
  text_snippets:
    - name: "Dynamic"
      keyword: "dyn"
      icon: "terminal"
      command: "my-snippet-gen"
      args: ["-j"]
```

The command must output JSON in Alfred Script Filter format:

```json
{
  "items": [
    {
      "title": "Current Date",
      "arg": "2024-03-15"
    },
    {
      "title": "Current Time",
      "arg": "14:30:00"
    }
  ]
}
```

<Aside type="tip">
The `title` field becomes the snippet name, and `arg` becomes the snippet value.
An optional `keyword` field sets the [snippet keyword](#snippet-keywords-and-search).
An optional `icon` object, such as `"icon": {"path": "mail-client"}`, sets the item icon.
</Aside>

### Directory Source

Load snippets from a directory of `.snippet` files:

```yaml raffi.yaml
addons:
  text_snippets:
    - name: "Snippets"
      keyword: "sn"
      icon: "snippets"
      directory: "~/.local/share/desktop-config/snippets"
```

Each `.snippet` file has this format:

```text greeting.snippet
Friendly Greeting
---
Hello! How are you doing today?
```

```text email-signature.snippet
Professional Signature
---
Best regards,
John Doe
Software Engineer
john@example.com
```

Format:

- **First line**: Snippet name (description)
- **Optional `keyword: ...` header**: [Snippet keyword](#snippet-keywords-and-search)
- **Optional `icon: ...` header**: Emoji, icon name, or image path
- **`---`**: Separator
- **After separator**: Snippet content (value)

```text signature.snippet
Professional Signature
keyword: !sig
icon: ✍️
---
Best regards,
John Doe
```

<Aside type="note">
The `keyword:` and `icon:` headers can appear in either order. Keep the name
on the first line and the content after `---`. Directory-based snippets are
read in alphabetical order, then ordered by use like any other source, and
cached per session.
</Aside>

## Dynamic Placeholders

Snippet values can contain placeholders that are filled in when you pick the
snippet. The subtitle under each snippet shows a preview of the result.

```yaml raffi.yaml
snippets:
  - name: "Quote clipboard"
    value: "> {clipboard}"
  - name: "Meeting header"
    value: "## Meeting {date:%A %d %B}, follow-up on {date +1w}"
  - name: "Branch"
    value: "{shell:git -C ~/src/project branch --show-current}"
  - name: "Function"
    value: "fn {cursor}() {}"
```

| Placeholder | Result |
|---|---|
| `{clipboard}` | Current clipboard text |
| `{clipboard:1}` | An earlier clipboard entry, up to `{clipboard:100}`. See [Clipboard history](#clipboard-history) |
| `{date}` `{time}` `{datetime}` | Now, formatted as `%Y-%m-%d`, `%H:%M` and `%Y-%m-%d %H:%M` |
| `{date:%A %d %B}` | Custom [strftime](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) format, also for `time` and `datetime` |
| `{date +1d}` `{datetime -2h +30m:%H:%M}` | Date arithmetic. Units: `s` `m` `h` `d` `w` `M` (months) `y` |
| `{random:uuid}` | A random UUID (v4) |
| `{random:1-100}` | A random whole number between both values, inclusive |
| `{env:USER}` | An environment variable, empty when unset |
| `{snippet:NAME}` | Another snippet from the same source, by name or keyword |
| `{shell:COMMAND}` | Output of `sh -c COMMAND` |
| `{cursor}` | Where the caret ends up after `insert` |

<Image src={placeholdersScreenshot} alt="Snippet preview showing an expanded dynamic value and its raw template" width={800} />

Add transforms after a `|`, chained left to right: `uppercase`, `lowercase`,
`capitals` (first letter of each word) and `trim`. For example
`{clipboard|trim|lowercase}` or `{env:USER|capitals}`. Transforms work on every
placeholder except `shell` and `cursor`.

Some details worth knowing:

- Only the names above are placeholders. Other braces, such as code or a
  `{value}` in an action, are left alone. Write `\{date}` to get a literal
  `{date}`. YAML double-quoted strings reject `\{` and `\}` as invalid
  escapes, so put values containing them in single quotes or a block scalar,
  for example `value: '\{date} is {date}'`.
- A placeholder with a mistake in it (unknown transform, bad date format,
  reversed range, missing snippet) shows an error row and leaves Raffi open
  instead of copying the wrong text.
- Month and year offsets clamp to the end of the month: January 31 `+1M` is the
  last day of February.
- Text that comes from the clipboard, environment, shell or another snippet's
  placeholders is inserted as is and not scanned for more placeholders.
  `{snippet:...}` references can nest up to 5 levels and expand at most 1000
  times in total.
- `{shell:...}` ends at the first `}`. Write `\}` for a literal `}` inside the
  command. It times out after 5 seconds and terminates the command and its
  child processes. It never runs for the preview, only
  when you select the snippet, and it is disabled for `command:` sources.
- Previews leave `{shell:...}` and `{random:...}` untouched. The clipboard
  preview is read once when the source opens, the final value reads it again.
- Only the first `{cursor}` counts, and it only moves the caret with `insert`.
  The caret moves one Left key per character, which can be off by a few for
  emoji sequences or combining accents.
- Expanded values are limited to 100 KiB. With `insert`, `{cursor}` can be at
  most 1000 characters from the end of the value.

### Clipboard History

`{clipboard:1}` is the entry copied before the current one, `{clipboard:2}` the
one before that, and so on. `{clipboard:0}` is the same as `{clipboard}`. Raffi
keeps no history of its own, so these read from the clipboard manager you
already run: [cliphist](https://github.com/sentriz/cliphist),
[CopyQ](https://hluk.github.io/CopyQ/) or
[clipman](https://github.com/yory8/clipman).

```yaml raffi.yaml
snippets:
  - name: "Previous copy"
    value: "{clipboard:1|trim}"
  - name: "Name and email"
    value: "{clipboard:1} <{clipboard}>"
```

Raffi picks the manager that is running under your user. It looks for
`cliphist store` or `clipman store` (usually run from `wl-paste --watch`) and
for the CopyQ server, and prefers them in that order when several run. A custom
`-db-path` for cliphist or `--histpath` for clipman on that command line is
used too, including paths containing spaces. Quote those paths when launching
the watcher through a shell. To pick one yourself or turn history off, set
[`clipboard_history`](../../configuration/general-settings/) in
`general:`.

With CopyQ, Raffi reads the tab CopyQ saves the clipboard to (its
`clipboard_tab` setting), even when that is not the first tab.

Numbering assumes the newest entry in the manager is the current clipboard, as
Alfred does. If the manager skipped what you just copied (an image, a password
it ignores, text below its minimum length), the numbers are off by one. Entries
that are not text, such as images, show an error instead of being inserted.

<Aside type="caution">
Placeholders are on by default, so existing snippets containing `{date}` or
`{clipboard}` now expand. Escape them with `\{` inside single-quoted or block
scalar YAML values, such as `value: '\{date} is {date}'`, or set
`placeholders: false` on the source to keep every value exactly as written.
</Aside>

## Snippet Keywords and Search

Each snippet can have its own `keyword`, a short abbreviation you type to find
it straight away, like Alfred's snippet keywords:

```yaml raffi.yaml
snippets:
  - name: "Email signature"
    keyword: "!sig"
    value: "Best regards, John"
```

Typing `sn !sig` puts that snippet first. Results are ranked as exact keyword,
then keyword prefix, then fuzzy match on the name, then fuzzy match on the raw
snippet value (before placeholders are filled in). The keyword is shown next to
the snippet name.

Raffi also remembers which snippets you pick, scored with the same
[`sort_mode`](../../configuration/general-settings/) as launchers (`hybrid` by
default). With nothing typed after the source keyword, the snippets you use
most come first; while typing, usage only decides between equally good
matches. A query starting with `!` is a keyword lookup, so usage is ignored
there and ties keep the source order. Usage is stored in
`~/.cache/raffi/snippets.cache`, per source keyword and snippet name.

To search a source without its keyword, add it to
[`general.main_view`](../../configuration/general-settings/#main-view) as
`text_snippets."<name>"`. Its best matches then show in the main list. A
command source runs once when Raffi starts there, rather than for each
search.

## Actions

Configure how snippets are used when selected:

### Default Actions

<Tabs>
  <TabItem label="Copy (Default)">
    ```yaml
    action: "copy"
    ```
    Copies snippet to clipboard using `wl-copy`, `xclip`, or `xsel`
  </TabItem>

  <TabItem label="Insert/Type">
    ```yaml
    action: "insert"
    ```
    Types snippet into focused app using `wtype` or `ydotool`
  </TabItem>
  <TabItem label="Hyprland Paste (Default on Hyprland)">
    ```yaml
    action: "hypr_paste"
    secondary_action: "copy"
    restore_clipboard: true
    ```
    Copies the expanded snippet with `wl-copy`, closes Raffi, waits for the
    original window to regain focus, and sends the paste shortcut directly to
    that window through Hyprland: Ctrl+V, or Ctrl+Shift+V in terminals. This avoids typing long snippets character by character.
  </TabItem>
</Tabs>

When Raffi runs inside Hyprland and a source has no `action`, Enter uses
`hypr_paste`. If Raffi finds no window to paste into when it opens, or
Hyprland is older than 0.55, Enter keeps the `copy` default. Set `action: "copy"` to keep copying on Hyprland.

`hypr_paste` requires Hyprland's Lua API (0.55 or later), `hyprctl`, and
`wl-clipboard`. Run Raffi inside the Hyprland session so it inherits
`HYPRLAND_INSTANCE_SIGNATURE` and `XDG_RUNTIME_DIR`. It works with either
`action` or `secondary_action` on a text snippet source. The receiving app
must accept Ctrl+V, or Ctrl+Shift+V for terminals. Raffi recognises common
terminals by their window class; add others with
[`general.terminal_classes`](../../configuration/general-settings/).

Snippet copies are marked sensitive so clipboard managers that honor this
hint, including CopyQ with its default settings, keep them out of history.
The hint needs wl-clipboard 2.3 or later; older versions paste without it.
This applies whether clipboard restoration is enabled or not. Ordinary `copy`
actions and restored clipboard text are not marked sensitive. Existing history
entries are not removed.

By default, the snippet stays on the clipboard. Set `restore_clipboard: true`
on the source to restore the previous plain text one second after sending the
paste shortcut, provided the clipboard still has Raffi's paste marker. A newer
copy skips restoration, even if it contains identical text. Saving an empty,
unavailable, non-text, invalid UTF-8, or larger-than-100-KiB selection skips
restoration and still allows pasting. Rich text is restored as plain text;
images and other formats are not preserved. Restoration is best effort: a
slow application may not finish reading the snippet before the delay expires.

`{cursor}` is removed during expansion but does not move the caret with this
action. Clipboard preparation errors keep Raffi open. If the original window
does not regain focus within two seconds, or Hyprland rejects the shortcut,
Raffi reports an error and does not paste elsewhere. Successful dispatch means
the shortcut was sent, not that the receiving app confirmed insertion.

### Custom Actions

Use a shell command as the action. The value is exported as the `RAFFI_VALUE`
environment variable:

```yaml raffi.yaml
text_snippets:
  - name: "Commands"
    keyword: "cmd"
    action: 'kitty sh -c "$RAFFI_VALUE"'
    snippets:
      - name: "System Update"
        value: "sudo pacman -Syu"
```

The command can also use a `{value}` placeholder, but `{value}` is pasted into
the command as raw text, so quotes or `$(...)` in the snippet are interpreted
by the shell. For snippets that pull in the clipboard, shell output or anything
else you do not control, quote the variable instead:

```yaml
action: 'notify-send "Snippet" "$RAFFI_VALUE"'
```

### Primary and Secondary Actions

Configure different actions for Enter and Ctrl+Enter:

```yaml raffi.yaml
text_snippets:
  - name: "Emails"
    keyword: "em"
    icon: "mail"
    action: "insert"          # Enter: Type into app
    secondary_action: "copy"  # Ctrl+Enter: Copy to clipboard
    snippets:
      - name: "Personal Email"
        value: "user@example.com"
      - name: "Work Email"
        value: "user@company.com"
```

## Configuration Fields

**`name`** `string` (required)

Display name shown during loading (for command sources)


**`keyword`** `string` (required)

Text that activates the snippet source


**`icon`** `string`

Default icon for the source: an emoji, a name from your icon cache, or an image
path. A snippet’s `icon` overrides it. Missing icons fall back to the source
icon. `no_icons` hides both. Image paths support `~` and environment variables.


**`snippets`** `array`

Inline list of snippets (each with `name`, `value`, and optional `keyword` and `icon` fields)


**`file`** `string`

Path to a YAML file containing snippets (cached per session)


**`command`** `string`

Executable that outputs Alfred Script Filter JSON


**`directory`** `string`

Path to a directory of `.snippet` files (cached per session)


**`args`** `array`

Arguments passed to the command


**`action`** `string` (default: `copy`, or `hypr_paste` on Hyprland)

Action on Enter:
  - `"copy"`: Copy to clipboard
  - `"insert"`: Type into focused app
  - `"hypr_paste"`: Paste into the original window on Hyprland
  - Command template with `{value}` placeholder


**`secondary_action`** `string` (default: `insert`)

Action on Ctrl+Enter. Accepts same values as `action`.


**`placeholders`** `boolean` (default: `true`)

Expand [dynamic placeholders](#dynamic-placeholders) in snippet values. Set to
`false` to use values exactly as written.


<Aside type="caution">
Exactly one of `snippets`, `file`, `command`, or `directory` should be specified per entry.
</Aside>

## Examples

### Work Templates

These templates turn copied context into bug reports, review requests, notes,
and emails. Browse the [snippet files on GitHub](https://github.com/chmouel/raffi/tree/main/examples/snippets),
or download all six with `curl`. This command skips files you already have:

```sh
snippet_dir="$HOME/.local/share/raffi/snippets"
base_url="https://raw.githubusercontent.com/chmouel/raffi/main/examples/snippets"
mkdir -p "$snippet_dir"

for name in bug-report review-request meeting-notes incident-handover follow-up sign-off; do
  [ -e "$snippet_dir/$name.snippet" ] && continue
  curl --fail --location --remove-on-error \
    "$base_url/$name.snippet" \
    --output "$snippet_dir/$name.snippet" || break
done
```

Keep all six files together: the review request and follow-up email reuse the
sign-off by its `!signoff` keyword. Edit that file to use your preferred name;
by default it uses your login name with an initial capital.

Add a directory source to your existing `addons.text_snippets` list:

```yaml raffi.yaml
addons:
  text_snippets:
    - name: "Work templates"
      keyword: "sn"
      directory: "~/.local/share/raffi/snippets"
      action: "hypr_paste"
      secondary_action: "copy"
      restore_clipboard: true
```

Use `action: "copy"` on other compositors. Placeholders are enabled by default.
Copy the context described below, focus a browser text field, open Raffi, and
search for the template's keyword. Dates use local time; `+2d` means two
calendar days. Replace the bracketed prompts after pasting.

| Search | Copy before opening Raffi | Result |
|---|---|---|
| `sn !bug` | Error output or a failing test log | Markdown bug report with the error and report date |
| `sn !review` | A pull request URL | Review request with testing prompts, a deadline, and your sign-off |
| `sn !meeting` | A meeting topic on one line | Notes with decisions, action owners, and next week's date |
| `sn !handover` | The latest incident summary | Handover with impact, next actions, and an update time in 30 minutes |
| `sn !followup` | A ticket, link, or earlier message | Follow-up email with a reply date and your sign-off |
| `sn !signoff` | Nothing | The shared sign-off used by the email templates |

<Tabs>
  <TabItem label="Bug report">
    <Code code={bugReport} lang="text" title="bug-report.snippet" />
  </TabItem>
  <TabItem label="PR review">
    <Code code={reviewRequest} lang="text" title="review-request.snippet" />
  </TabItem>
  <TabItem label="Meeting notes">
    <Code code={meetingNotes} lang="text" title="meeting-notes.snippet" />
  </TabItem>
  <TabItem label="Incident handover">
    <Code code={incidentHandover} lang="text" title="incident-handover.snippet" />
  </TabItem>
  <TabItem label="Follow-up email">
    <Code code={followUp} lang="text" title="follow-up.snippet" />
  </TabItem>
  <TabItem label="Sign-off">
    <Code code={signOff} lang="text" title="sign-off.snippet" />
  </TabItem>
</Tabs>

To check clipboard restoration, copy a recognizable piece of text, paste a
template with Enter, wait a second, then paste manually again. The second
paste should contain the original text. With CopyQ's default settings, the
generated template should stay out of history. Ctrl+Enter uses the ordinary
copy action, so you can keep a generated template on the clipboard and in
history when you want to reuse it.

### Email Addresses

```yaml raffi.yaml
text_snippets:
  - name: "Emails"
    keyword: "em"
    icon: "mail"
    snippets:
      - name: "Personal"
        value: "john@personal.com"
      - name: "Work"
        value: "john@company.com"
      - name: "GitHub"
        value: "john@users.noreply.github.com"
```

### Code Snippets

```yaml raffi.yaml
text_snippets:
  - name: "Code Templates"
    keyword: "code"
    icon: "code"
    action: "insert"
    snippets:
      - name: "Rust Main"
        value: |
          fn main() {
              println!("Hello, world!");
          }

      - name: "Python Main"
        value: |
          if __name__ == "__main__":
              pass
```

### Git Commands

```yaml raffi.yaml
text_snippets:
  - name: "Git Commands"
    keyword: "git"
    file: "~/.config/raffi/git-snippets.yaml"
```

```yaml git-snippets.yaml
- name: "Amend Commit"
  value: "git commit --amend --no-edit"

- name: "Push Force"
  value: "git push --force-with-lease"

- name: "Interactive Rebase"
  value: "git rebase -i HEAD~"
```

### Dynamic Dates

```yaml raffi.yaml
text_snippets:
  - name: "Dates"
    keyword: "date"
    icon: "calendar"
    snippets:
      - name: "Today"
        keyword: "td"
        value: "{date}"
      - name: "Yesterday"
        value: "{date -1d}"
      - name: "Tomorrow"
        value: "{date +1d}"
      - name: "ISO Timestamp"
        value: "{datetime:%Y-%m-%dT%H:%M:%S%:z}"
```

## Use Cases


  <details>
<summary>Contact Information</summary>

    ```yaml
    - name: "Contact"
      keyword: "contact"
      snippets:
        - name: "Email"
          value: "user@example.com"
        - name: "Phone"
          value: "+1-555-0123"
        - name: "Address"
          value: "123 Main St, City, State 12345"
    ```
  </details>

  <details>
<summary>Frequently Used Commands</summary>

    ```yaml
    - name: "Shell Commands"
      keyword: "sh"
      action: "insert"
      snippets:
        - name: "Docker Cleanup"
          value: "docker system prune -af"
        - name: "Find Large Files"
          value: "du -sh * | sort -h"
    ```
  </details>

  <details>
<summary>Text Templates</summary>

    ```yaml
    - name: "Templates"
      keyword: "tpl"
      directory: "~/.config/raffi/templates"
    ```

    With files like:
    - `meeting-notes.snippet`
    - `bug-report.snippet`
    - `email-signature.snippet`
  </details>

  <details>
<summary>Programming Snippets</summary>

    ```yaml
    - name: "Code"
      keyword: "snippet"
      file: "~/.config/raffi/code-snippets.yaml"
      action: "insert"
    ```
  </details>


## Keyboard Shortcuts

- **Enter**: Execute primary action (default: copy, or paste on Hyprland)
- **Ctrl+Enter**: Execute secondary action (default: insert)
- **Esc**: Cancel and return to normal mode
- **↑/↓**: Navigate through snippets

## Requirements

<Aside type="note">
**For copying**: `wl-copy` (wl-clipboard), `xclip`, or `xsel`
</Aside>

<Aside type="note">
**For typing/inserting**: `wtype` or `ydotool`
</Aside>

<Aside type="note">
**For `{clipboard:N}`**: cliphist, CopyQ or clipman running
</Aside>

## Tips

<Aside type="tip">
Use file sources for large snippet collections to keep your main config clean
</Aside>

<Aside type="tip">
Use placeholders for dates, the clipboard or a quick shell command, and command sources when the list of snippets itself is dynamic, such as API responses
</Aside>

<Aside type="tip">
Directory sources work well for organizing snippets by category (each category in its own directory)
</Aside>

<Aside type="tip">
Set `action: "insert"` and `secondary_action: "copy"` for maximum flexibility
</Aside>

## Complete Example

Here's a full configuration with multiple snippet sources:

```yaml raffi.yaml
addons:
  text_snippets:
    # Inline emails
    - name: "Emails"
      keyword: "em"
      icon: "mail"
      action: "copy"
      snippets:
        - name: "Personal"
          value: "user@example.com"
        - name: "Work"
          value: "user@company.com"

    # Templates from file
    - name: "Templates"
      keyword: "tpl"
      icon: "document"
      file: "~/.config/raffi/templates.yaml"
      action: "insert"

    # Snippets generated by a command
    - name: "Projects"
      keyword: "proj"
      command: "project-snippets"
      icon: "folder"

    # Code snippets from directory
    - name: "Code Snippets"
      keyword: "code"
      directory: "~/.config/raffi/snippets"
      action: "insert"
      secondary_action: "copy"
```