# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Commands

```bash
# Build
cargo build

# Run
cargo run -- <keyword>         # e.g. cargo run -- anyhow::Error

# Install locally
cargo install --path . --locked

# Test (unit tests require docs to be generated first)
cargo doc
cargo test --bins

# Integration tests (snapshot-based via insta)
cargo test                          # uses pre-generated HTML in tests/html/
RUSTY_MAN_GENERATE=1 cargo test     # also generates docs from current toolchain

# Update insta snapshots after intentional output changes
cargo insta review                  # or: INSTA_UPDATE=always cargo test

# Format / Lint
cargo fmt
cargo clippy
```

## Architecture

manrs is a CLI viewer for rustdoc-generated HTML documentation. The lookup pipeline is:

1. **`args.rs`** — CLI arguments via `clap` derive (`Parser` trait). `ViewerName` is a newtype wrapper used for serde/clap compatibility; actual `Box<dyn Viewer>` is resolved via `args.get_viewer()` in `main.rs`.
2. **`source.rs`** — `Source` trait + `DirSource` for local doc directories; `Sources` aggregates multiple sources and searches them in reverse-priority order (last-added = highest priority, so `--source` paths win over defaults).
3. **`parser/html/`** — scrapes rustdoc HTML into `doc::Doc` structs using `scraper` (CSS selectors) + `ego-tree` for DOM traversal. `util.rs` provides `NodeRefExt` trait over scraper's `ElementRef` and `ego_tree::NodeRef<Node>`.
4. **`index.rs`** — parses `search-index.js` for partial matching; `index/v1_44.rs` and `index/v1_52.rs` handle two index format versions (changed in Rust 1.44 and 1.52). Post-1.56 rustdoc index formats are not yet supported.
5. **`doc.rs`** — core data types: `Name`/`Fqn`, `ItemType`, `Doc`, `Text`, `Code`, `Example`.
6. **`viewer/`** — `Viewer` trait with three implementations:
   - `viewer/text/` — `plain` (no formatting) and `rich` (ANSI + syntax highlighting via syntect).
   - `viewer/tui/` — interactive terminal UI using `cursive 0.21`. **Default on TTY.** Key internals:
     - `TuiManRenderer` renders sections as a `LinearLayout` of `TextView`/`LinkView`/`CodeView` children inside a `ScrollView`.
     - `LinkView` (in `views.rs`) is a focusable view that renders cyan underlined text and fires a callback on Enter. Focus is tracked via `is_focused`: set by `take_focus`, cleared by `Event::FocusLost`. The focused link renders with `PaletteColor::Highlight` (blue bg, white text) using `printer.print` on the plain text — bypassing the embedded `StyledString` colors that would otherwise override it.
     - `j`/`k` scroll one line down/up; `J`/`K` simulate Tab/Shift-Tab for link-to-link focus traversal.
     - `pick_crate()` is a separate full-screen cursive session (filter + select) that runs before the doc viewer session.
     - `extract_doc_links` + `parse_doc_url` parse `<a href>` tags in HTML text blocks and collect them in `TuiManRenderer::collected_links` (deduplicated via `seen_links`). They are inserted as a `LINKS` section at position 0 of the layout in `into_view`, not inline after each paragraph.
     - `print_text` uses `utils::highlight_html` to apply syntect syntax highlighting to `<pre>` code blocks in descriptions. Syntect output (`text_style::StyledStr`) is converted to cursive styles via `text_style_to_cursive` (manual conversion — the `text-style` cursive feature is excluded).
     - `RichDecorator::annotating()` mode: preserves `Link` annotations on all inline links (enabling cyan+underline for linked items like `axum`, `tower::Service`) without adding `[N]` brackets or footnote lines.

## Dependencies (key ones)

| Crate | Purpose |
|---|---|
| `clap 4` | CLI argument parsing (derive API) |
| `scraper` + `ego-tree` | HTML parsing and DOM traversal (replaced kuchiki) |
| `html2text 0.12` | HTML → plain/rich text rendering |
| `syntect 4` | Syntax highlighting for code blocks |
| `cursive 0.21` | TUI framework |
| `text-style 0.3` | Bridge between syntect/termion/cursive styling |
| `merge` + `serde` | Config file merging with CLI args |

## Testing

Integration tests in `tests/output.rs` use `insta` snapshots in `tests/snapshots/`. HTML fixtures in `tests/html/` cover rustdoc output for Rust 1.40–1.56. `with_rustdoc()` in `src/test_utils.rs` iterates over them with semver filtering.

`RUSTY_MAN_GENERATE=1` additionally generates docs via `cargo doc` for the running toolchain and tests against that output.

## No-keyword mode (crate picker)

When `manrs` is run with no keyword inside a Cargo project:
1. Detects `target/doc` via `cargo metadata` (workspace-aware, not just `./target`)
2. If no docs exist, auto-runs `cargo doc`
3. Opens a full-screen TUI crate picker (`viewer::tui::pick_crate`) — type to filter, Enter to select
4. The selected crate name becomes the keyword; the TUI doc viewer opens next

Key functions: `resolve_keyword`, `ensure_docs`, `get_workspace_doc_dir` in `main.rs`; `pick_crate` in `src/viewer/tui/mod.rs`.

## TUI link navigation

- Member headings in modules are rendered as `LinkView` (cyan, underlined, focusable).
- `j`/`k` scroll the page one line down/up; `J`/`K` simulate Tab/Shift-Tab to jump between `LinkView` items.
- The currently focused link is highlighted (blue background, white text) via `is_focused` state on `LinkView`. Focus gain/loss is tracked through `take_focus` / `Event::FocusLost`.
- Doc links extracted from description HTML via `extract_doc_links` + `parse_doc_url` are collected across all `print_text` calls and rendered as a `LINKS` section at the **top** of the page (above Synopsis/Description), so they are immediately reachable by pressing `j`.

## TUI description rendering

- `print_text` uses `utils::highlight_html` (same path as the rich text viewer) so that `<pre>` code blocks within descriptions get full syntect syntax highlighting.
- Inline links (`<a>` tags) retain their `Link` annotation via `RichDecorator::annotating()`, giving linked identifiers like `axum` or `tower::Service` cyan+underline styling distinct from plain inline code (light yellow).
- `text_style_to_cursive` in `tui/mod.rs` manually converts `text_style::Style` → `theme::Style` since the `text-style` cursive feature is excluded.

## Known limitations

- The search index parser only supports rustdoc formats up to Rust 1.56. Post-1.56 docs fall back to direct HTML lookup (no search index).
- `text-style 0.3` is only used for its `termion` and `syntect` features — the `cursive` feature is intentionally excluded to avoid pulling in the yanked cursive 0.16.
- Inline link navigation (clicking links embedded mid-sentence in text) is not supported; links are extracted and shown as a `LINKS` section at the top of the page instead.
