Warp-style terminal autocomplete engine that works in any terminal. Written in Rust. Wraps your shell in a PTY and intercepts Tab to show a floating completion popup with commands, descriptions, and fuzzy matching.
cargo build # Build
cargo test # Run all tests (35 tests)
cargo run # Launch melon (spawns your $SHELL inside a PTY)
cargo run -- --debug # Launch with debug logging to ~/.local/share/melon/melon.log
cargo run -- --install # Print shell integration snippetHost Terminal (raw mode)
│ │
│ stdin │ stdout
▼ ▲
┌──────────────────────────────────────┐
│ melon process │
│ │
│ stdin ─► InputProcessor ─┬─► PTY │
│ (state machine) │ master │
│ │ │
│ CompletionEngine │
│ │ │
│ PopupRenderer ──► host stdout
│ │ │
│ PTY master reader ────────┴─► host stdout
└──────────────────────────────────────┘
Key design decisions:
- PTY wrapper (not daemon+plugin) — works with any shell/terminal, no per-shell integration needed
- Fig/Withfig completion specs as data format — JSON specs, 35 commands included
nucleo-matcherfor fuzzy matching (from Helix editor, SIMD-accelerated)portable-pty(from wezterm) for cross-platform PTY handlingcrosstermfor raw terminal control and ANSI rendering- Async I/O via tokio with channels between stdin reader, PTY, and stdout writer
- Specs embedded in binary via
include_dir— no external files needed at runtime
src/
├── main.rs # CLI (clap), logging setup, entry point
├── config.rs # ~/.config/melon/config.toml loading
├── pty/
│ ├── mod.rs
│ ├── proxy.rs # Core proxy loop + state machine (Passthrough ↔ PopupActive)
│ └── signals.rs # SIGWINCH forwarding to PTY
├── input/
│ ├── mod.rs
│ ├── parser.rs # Command-line tokenizer (quotes, pipes, &&, escapes)
│ └── trigger.rs # Raw byte → InputAction classification (Tab, arrows, Esc, etc.)
├── completion/
│ ├── mod.rs
│ ├── spec.rs # Rust structs mirroring Fig spec types (serde)
│ ├── loader.rs # Load JSON specs from disk + embedded (include_dir)
│ ├── engine.rs # Spec tree walker → candidates for current input context
│ ├── matcher.rs # nucleo fuzzy matching wrapper, filter + rank
│ └── source.rs # CompletionSource trait, PathSource for filesystem
├── ui/
│ ├── mod.rs
│ ├── popup.rs # Selection state, scroll, wrap-around navigation
│ ├── render.rs # ANSI escape sequence popup drawing
│ └── theme.rs # Colors (Catppuccin dark), rounded borders, dimensions
└── shell/
├── mod.rs
├── detect.rs # $SHELL detection (zsh/bash/fish)
└── cursor.rs # Cursor position estimation for popup placement
specs/ # 35 JSON completion specs (embedded in binary at compile time)
tools/convert_specs.ts # Deno script: convert Fig TypeScript specs → JSON
tests/integration.rs # Integration test: validates all spec files deserialize
tasks/todo.md # Implementation tracking
The proxy (src/pty/proxy.rs) runs a two-state machine:
- Passthrough — all input goes directly to the PTY. Tracks
current_lineby watching for printable chars, backspace, and enter. Tab triggers completion lookup. - PopupActive — Tab/Down cycles selection, Up/Shift-Tab goes backwards, Enter accepts (backspaces the partial then inserts the completion), Esc/Ctrl-C dismisses. Any typing dismisses and passes through.
current_line → parser::split_partial()
→ engine.complete() (walks spec tree for subcommands/options/args)
→ matcher.filter(partial, candidates) (nucleo fuzzy scoring)
→ popup.set_items(scored) → renderer.render()
The engine resolves context by:
- Looking up the command name in the
SpecStore - Walking subcommand tokens to find the deepest matching spec node
- Determining whether to complete subcommands, options, or positional args
- For args with
template: "filepaths"/"folders", delegating toPathSource
Specs follow the Fig autocomplete format (camelCase JSON):
{
"name": ["git", "g"], // string or array of aliases
"description": "Version control",
"subcommands": [
{
"name": "commit",
"description": "Record changes",
"options": [
{
"name": ["-m", "--message"],
"description": "Commit message",
"args": { "name": "message" }
}
]
}
],
"options": [...],
"args": {
"template": "filepaths", // "filepaths" | "folders" for filesystem completion
"isVariadic": true,
"suggestions": ["value1", {"name": "value2", "description": "..."}]
}
}Key spec types: Spec, Subcommand, Opt, Arg, StringOrArray, ArgOrArgs, Template, SuggestionOrString — all defined in src/completion/spec.rs.
- Create
specs/<command>.jsonfollowing the format above - Rebuild —
include_dirpicks it up automatically - Or use the converter:
deno run --allow-read --allow-write tools/convert_specs.ts /path/to/fig-autocomplete/src specs/
Users can also drop custom specs into ~/.local/share/melon/specs/ (loaded at runtime, overrides builtins).
| Variable | Purpose |
|---|---|
MELON=1 |
Set inside the PTY so the shell knows it's wrapped (prevents recursive exec) |
SHELL |
Used to detect which shell to spawn |
TERM |
Passed through to the child shell |
Optional ~/.config/melon/config.toml:
max_visible = 8 # Max popup items (default: 8)
specs_dir = "/path/to/specs" # Custom specs directory- Unit tests are colocated in each module (
#[cfg(test)] mod tests) - Integration test in
tests/integration.rsvalidates all 35 spec files parse correctly - Key test coverage: tokenizer (quotes, pipes, escapes), spec deserialization, engine (subcommand/option completion), fuzzy matcher (scoring order), popup state (selection wrapping, scroll), input classifier (key mapping)
| Crate | Purpose |
|---|---|
portable-pty |
PTY creation and management |
crossterm |
Terminal raw mode, size query, ANSI rendering |
nucleo-matcher |
SIMD-accelerated fuzzy matching |
tokio |
Async runtime, channels, task spawning |
serde / serde_json |
Spec deserialization |
clap |
CLI argument parsing |
include_dir |
Embed specs/ directory in binary |
signal-hook / signal-hook-tokio |
SIGWINCH handling |
tracing / tracing-subscriber |
Debug logging |
dirs |
XDG directory resolution |
toml |
Config file parsing |
unicode-width |
Correct column width for popup layout |
macOS and Linux. Uses Unix PTY APIs via portable-pty and nix. Not Windows-compatible (no Windows PTY support in current architecture).