Item Model Expansion - Item now caches sublabel, icon, group with accessors. Added resolve_field_path() for dotted path traversal and field_value() on Item.
Output Struct - New OutputItem with OutputAction (select/cancel) and index. Object values flatten, strings get a value field. MenuResult::Selected now carries { value, index }.
Hook Types - Replaced the old Hook trait with HookEvent (serializable, 6 variants), HookResponse (deserializable, 5 commands), HookHandler trait (sync for dyn-compatibility), and parse_hook_response() with tracing warnings.
New Actions & Menu Methods - Added ReplaceItems, RemoveItems, ProcessHookResponse, CloseMenu actions. Menu trait gained original_index(), replace_all(), remove_by_indices(), formatted_label(). Pipeline got rebuild() and rebuild_with_values(). Smart cursor preservation on replace.
Lifecycle Events - MenuRunner emits Open, Close, Hover, Select, Cancel, Filter events through the dispatcher. Cursor tracking for Hover detection.
Debounce - DebouncedDispatcher with 4 modes: None, Debounce, CancelStale, DebounceAndCancelStale. Defaults: hover=DebounceAndCancelStale(200ms), filter=Debounce(200ms).
Exec Hooks - ShellExecHandler maps --on-{open,close,hover,select,cancel,filter}-exec flags to fire-and-forget subprocesses. Event JSON piped to stdin.
Handler Hooks - ShellHandlerHook launches persistent processes per --on-{event} flag. Bidirectional JSON lines: events on stdin, responses on stdout flowing back through Action::ProcessHookResponse. CompositeHookHandler dispatches to both.
--filter-fields - --filter-fields label,sublabel,meta.tags searches multiple fields. Combined text for fuzzy, individual for exact/regex.
--format - FormatTemplate parses {field.path} placeholders. --format '{label} - {sublabel}' controls display. TUI renders formatted_text when available.
Field Filters - meta.res:3840 in query syntax matches specific fields. !meta.res:3840 for inverse. Pipeline stores item Values for field resolution. Requires dotted path (single word colons stay fuzzy).
134 lines
4.8 KiB
Markdown
134 lines
4.8 KiB
Markdown
# pikl-menu
|
|
|
|
Pipe stuff in, pick stuff out.
|
|
|
|
pikl is an interactive menu that runs in your terminal or as a desktop
|
|
overlay. You give it a list of things. It lets you filter, navigate, and
|
|
select from that list. Then it tells you what was picked.
|
|
|
|
That's it. That's the whole idea. The power comes from what you connect
|
|
it to.
|
|
|
|
## What's a menu tool for?
|
|
|
|
You've got a folder of 400 wallpapers and you want to pick one. You've
|
|
got 30 running processes and you want to kill a few. You've got a list
|
|
of SSH hosts, git branches, emoji, colour schemes, docker containers,
|
|
tmux sessions, browser bookmarks. Anything that's fundamentally
|
|
"here's a list of stuff, let me search through it and pick something."
|
|
|
|
Normally you'd write a little script for each of these. Or scroll through
|
|
terminal output. Or memorize names. A menu tool replaces all of that
|
|
with one interaction pattern: a searchable, keyboard-driven list that
|
|
feeds your choice back into whatever script or keybinding kicked it off.
|
|
|
|
Some things pikl can do:
|
|
|
|
```sh
|
|
# pick a file
|
|
ls ~/wallpapers/*.jpg | pikl
|
|
|
|
# pick a process to kill
|
|
ps aux | pikl | awk '{print $2}' | xargs kill
|
|
|
|
# browse git branches and check one out
|
|
git branch --list | pikl | xargs git checkout
|
|
|
|
# pick from structured data - understands JSON
|
|
cat bookmarks.json | pikl --format '{title} ({url})'
|
|
```
|
|
|
|
Every one of those is just a pipe. Your shell already knows how to glue
|
|
these together. pikl is the interactive step in the middle.
|
|
|
|
## Coming from rofi or wofi?
|
|
|
|
If you already use rofi, wofi, dmenu, or fzf, you know the pattern.
|
|
pikl is in the same family, with a few things those tools don't do well
|
|
or at all:
|
|
|
|
- **Structured items.** Input is JSON lines, not just flat strings. Each
|
|
item can have a label, sublabel, metadata fields, and an icon. Output
|
|
includes the full item with selection context, so downstream tools
|
|
don't have to re-parse anything. Plain text still works for simple
|
|
cases.
|
|
|
|
- **Event hooks.** Shell commands that fire on lifecycle events. When
|
|
the cursor moves to a new item, when the user confirms, when they
|
|
cancel. A wallpaper picker is just pikl with an `on-hover` hook that
|
|
sets the wallpaper live as you scroll. Hooks are debounced and can
|
|
feed data back into the menu.
|
|
|
|
- **Vim keybindings.** Not just j/k. Normal mode with `gg`, `G`,
|
|
`H/M/L`, `Ctrl+D/U`, marks, named registers, visual line select.
|
|
Insert mode for filtering. Tab to switch modes, Escape to close.
|
|
|
|
- **Streaming.** Items can arrive over time. The list populates
|
|
progressively as a slow command produces output. Useful for recursive
|
|
finds, network queries, or anything that takes a while.
|
|
|
|
- **Multi-select with registers.** Select multiple items (Space to
|
|
toggle, V for visual line mode). Group selections into named registers
|
|
like vim yanking.
|
|
|
|
- **Filtering that goes beyond fuzzy.** Fuzzy by default, but also exact
|
|
(`'term`), regex (`/pattern/`), inverse (`!term`), and field-scoped
|
|
(`meta.size:2.4MB`). Chain filters with `|`. Full PCRE2-style regex
|
|
with lookaround and capture groups.
|
|
|
|
- **Table mode.** Display items as aligned columns. Pipe in CSV or TSV
|
|
and get a sortable, filterable table. Handy for process lists, log
|
|
entries, or anything columnar.
|
|
|
|
- **Sessions.** `--session name` remembers your filter, scroll position,
|
|
and selections across invocations. Close pikl, reopen it, pick up
|
|
where you left off.
|
|
|
|
- **IPC.** While pikl is running, external scripts can push or remove
|
|
items, change the filter, or read the current selection over a Unix
|
|
socket. Makes live-updating dashboards possible.
|
|
|
|
- **Watched sources.** Point pikl at a directory and the list updates
|
|
as files are added or removed. No restart needed.
|
|
|
|
- **TUI and GUI.** Runs in your terminal (ratatui) or as a floating
|
|
overlay on Wayland (layer-shell) and X11. Auto-detects your
|
|
environment.
|
|
|
|
## Building
|
|
|
|
```sh
|
|
cargo build --workspace
|
|
cargo test --workspace
|
|
```
|
|
|
|
Requires Rust stable. The repo includes a `rust-toolchain.toml` that
|
|
pins the version and pulls in rust-analyzer + clippy.
|
|
|
|
## Current Status
|
|
|
|
Phases 1 through 3 are complete. pikl works as a TUI menu with:
|
|
- Fuzzy, exact, regex, and inverse filtering with `|` pipeline chaining
|
|
- Vim navigation (j/k, gg/G, Ctrl+D/U, Ctrl+F/B, modes)
|
|
- Structured JSON I/O with sublabel, icon, group, and arbitrary metadata
|
|
- Lifecycle hooks: exec (fire-and-forget) and handler (bidirectional)
|
|
- Debounce and cancel-stale for rapid events
|
|
- Format templates (`--format '{label} - {sublabel}'`)
|
|
- Field-scoped filtering (`--filter-fields`, `meta.res:3840` syntax)
|
|
- Headless scripting via `--action-fd`
|
|
|
|
## Platform Support
|
|
|
|
| Platform | Frontend | Status |
|
|
|---|---|---|
|
|
| Linux (Wayland) | GUI (layer-shell overlay) | Planned |
|
|
| Linux (X11) | GUI | Planned |
|
|
| Linux | TUI | Working |
|
|
| macOS | GUI | Planned |
|
|
| macOS | TUI | Working |
|
|
| Windows | GUI | Low Priority |
|
|
|
|
## License
|
|
|
|
MIT
|