Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: rust
on:
push:
pull_request:
jobs:
build-test:
runs-on: ubuntu-latest
defaults:
run:
working-directory: rust
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy, rustfmt
- run: cargo fmt -- --check
- run: cargo clippy -- -D warnings
- run: cargo test
23 changes: 0 additions & 23 deletions .github/workflows/test.yml

This file was deleted.

72 changes: 40 additions & 32 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,45 +1,53 @@
# AGENTS.md

Shell functions for a unified fd + rg + fzf + bat file search (`srchr <term>`).
No build system. Local smoke tests live in `tests/smoke.sh`; GitHub Actions runs the same script.
Rust implementation for a unified live-grep file search (`srchr`). The binary
embeds file walking, content search, fuzzy-style TUI selection, syntax preview,
and editor launch behavior. Runtime external dependency: `$EDITOR` only.

## Structure

- `srchr.fish` — fish implementation
- `srchr.sh` — bash **and** zsh implementation (one sourceable file; syntax is kept POSIX-compatible for both)
- `rust/` — Cargo crate for the `srchr` binary
- `rust/src/search.rs` — gitignore-aware name/content search and aggregation
- `rust/src/preview.rs` — preview windowing, binary/unreadable placeholders, syntect styling
- `rust/src/editor.rs` — `$EDITOR` resolution, path safety, launch args
- `rust/src/app.rs` — TUI state machine
- `rust/src/ui.rs` — ratatui rendering
- `rust/src/main.rs` — terminal setup, event loop, debounce, editor handoff
- `rust/tests/` — integration tests
- `docs/superpowers/specs/` — design docs; update when behavior changes
- `docs/superpowers/plans/` — implementation plans

## Behavior Invariants

- The tool must not require `fd`, `rg`, `fzf`, or `bat` at runtime.
- `$EDITOR` is required. If unset or empty, exit with a clear error instead of
guessing an editor.
- Content and filename queries use smart-case regex semantics.
- Results are file-level rows: content hits show a match count; name-only hits
show `[name]`.
- Selected paths may begin with `+` or `-`. Keep the guard that rewrites those
relative paths to `./...` before calling `$EDITOR`; otherwise vim/nvim can
treat `+...` as editor commands or `-...` as options.
- The interactive TUI needs a TTY. Agents cannot fully test it; ask the user for
a manual smoke test after changes that affect interaction.

## Critical invariant: keep the ports in sync

The two files implement the same function. The `sh -c` preview/enter snippets
passed to fzf must stay **byte-identical** between `srchr.fish` and `srchr.sh`.
Any behavior change goes into both files.
## Verification

Quoting differs by necessity, not choice:
- fish: snippets are inline, using `\'` to escape single quotes (valid in fish only)
- bash/zsh: snippets are assembled from local vars (`locate`/`preview`/`open`)
because those shells cannot escape `'` inside single quotes
Run the Rust checks from the workspace root:

The search term is never interpolated into the fzf command strings (injection
safety). It reaches the snippets via the `SRCHR_TERM` env var: `set -lx` in
fish, env prefix on the fzf call only (`| SRCHR_TERM=$term fzf`) in sh —
do not `export` it into the session.
```sh
cargo fmt --manifest-path rust/Cargo.toml -- --check
cargo clippy --manifest-path rust/Cargo.toml -- -D warnings
cargo test --manifest-path rust/Cargo.toml
```

Snippets run via `sh -c '...' sh {}` (file arrives as `$1`) so they work no
matter which shell fzf's `$SHELL -c` uses.
GitHub Actions runs the same fmt/clippy/test gates in `.github/workflows/rust.yml`.

Selected paths may begin with `+` or `-` (raw output from `fd`/`rg`). Keep the
snippet guard that rewrites those relative paths to `./...` before calling
`rg`, `bat`, or `$EDITOR`; otherwise nvim/vim can treat `+...` as editor
commands and tools can treat `-...` as options.
Manual TUI smoke test:

## Verification
```sh
cargo run --manifest-path rust/Cargo.toml -- .
```

- Run the automated smoke suite: `tests/smoke.sh`
- The script covers syntax checks, fzf preview/bind parity across shell ports,
direct snippet behavior, and security smoke checks for leading-option terms
and selected paths beginning with `+` or `-`.
- zsh is optional locally: the script uses direct `zsh` when installed, falls
back to Docker when available, and skips only when neither exists.
- The interactive fzf flow needs a TTY; the agent cannot test it — ask the
user for a manual smoke test.
Check live typing, result counts, `[name]` rows, preview highlight, arrow-key
selection, `Enter` opening `$EDITOR`, and `Esc` restoring the terminal.
83 changes: 41 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,69 +1,68 @@
# srchr

Unified file search for your shell: one command that matches **file names**
(via [fd](https://github.com/sharkdp/fd)) and **file contents** (via
[ripgrep](https://github.com/BurntSushi/ripgrep)), merges the results into
[fzf](https://github.com/junegunn/fzf), previews with
[bat](https://github.com/sharkdp/bat), and opens your selection in `$EDITOR`.
Self-contained live-grep file search in Rust. `srchr` searches file names and
file contents, merges the results into an interactive terminal UI, previews the
selected file with syntax highlighting, and opens your selection in `$EDITOR`.

```
srchr <search_term>
Runtime external dependency: `$EDITOR` only.

```sh
srchr
```

- Files whose **name** matches the term and files whose **contents** match
are combined and deduplicated into a single fzf picker.
- **Smart preview:** if the selected file contains the term, the bat preview
jumps to the first matching line and highlights it; otherwise it shows the
file from the top.
- **Smart open:** pressing enter opens `$EDITOR +<line> <file>` when the file
contains the term (vim/nvim/helix-style line jump), or `$EDITOR <file>`
otherwise.
- Content matching is smart-case (`rg -S`), mirroring fd's default.
- Type to run a live smart-case regex search over file names and file contents.
- Results are deduplicated file rows.
- Content hits show a match count, sorted before name-only hits.
- Name-only hits show `[name]`.
- The preview highlights the first content match with context above it, or shows
the file from the top for name-only hits.
- Pressing `Enter` opens `$EDITOR +<line> <file>` for content hits, or
`$EDITOR <file>` for name-only hits.

## Requirements

[fd](https://github.com/sharkdp/fd),
[ripgrep](https://github.com/BurntSushi/ripgrep),
[fzf](https://github.com/junegunn/fzf), and
[bat](https://github.com/sharkdp/bat) on your `PATH`, plus an `$EDITOR` that
understands `+<line>` (vim, nvim, helix, kakoune, nano, ...).
- Rust toolchain to build from source.
- `$EDITOR` set to an editor that understands `+<line>` for line jumps (vim,
nvim, helix, kakoune, nano, ...).

## Test
If `$EDITOR` is unset or empty, `srchr` exits with a clear error.

Run the local smoke checks:
## Build

```sh
tests/smoke.sh
cargo build --manifest-path rust/Cargo.toml --release
```

This requires `fish` on your `PATH`.

The interactive fzf flow still needs a manual TTY smoke test.
The binary is at `rust/target/release/srchr`.

## Install
## Test

### fish
```sh
cargo fmt --manifest-path rust/Cargo.toml -- --check
cargo clippy --manifest-path rust/Cargo.toml -- -D warnings
cargo test --manifest-path rust/Cargo.toml
```

Copy (or symlink) `srchr.fish` into your functions directory:
The interactive TUI still needs a manual TTY smoke test:

```fish
ln -s (pwd)/srchr.fish ~/.config/fish/functions/srchr.fish
```sh
cargo run --manifest-path rust/Cargo.toml -- .
```

### bash / zsh
## Install

Source `srchr.sh` from your `.bashrc` or `.zshrc`:
Build the release binary and place it somewhere on your `PATH`, for example:

```sh
source /path/to/srchr/srchr.sh
cargo build --manifest-path rust/Cargo.toml --release
install -Dm755 rust/target/release/srchr ~/.local/bin/srchr
```

## Notes

- The search term is passed to the fzf preview/enter commands via the
`SRCHR_TERM` environment variable rather than string interpolation, so
terms containing quotes or shell metacharacters are safe.
- Search terms are passed to `fd`/`rg` after `--`, and selected relative
paths starting with `+` or `-` are normalized before invoking `bat` or
`$EDITOR`, so option/command-looking inputs are treated as data.
- `srchr.sh` is a single file that works in both bash and zsh.
- Search respects gitignore rules.
- Search terms are smart-case regexes for both content and filename matches.
- Selected relative paths starting with `+` or `-` are normalized before
invoking `$EDITOR`, so option/command-looking paths are treated as data.
- Preview uses an embedded default syntect theme; it does not read `bat` config
or require `bat` to be installed.
Loading
Loading