# axon-lsp

Language Server Protocol implementation for the
[Axon](https://github.com/Bemarking/axon-lang) programming language.
A single Rust binary that speaks LSP and, by construction, works in
**VSCode, Claude Code, Cursor, Antigravity, Zed, Neovim** —
or anything else that speaks LSP — with no per-IDE work.

> **Status: v0.3.0 released** — published on crates.io with
> `axon-frontend = "0.3"` as a registry dep. v0.3.0 lifts the
> capability surface past the rust-analyzer / pyright "core"
> baseline with eight new LSP-spec features (signature help,
> document highlight, folding, smart-select, code lens, call
> hierarchy, on-type formatting, pull diagnostics) plus
> cancel-aware workspace fan-outs and two new code actions.
> Roadmap and Resultado blocks for every sub-fase live in
> [`docs/plan_v0.3.0.md`](docs/plan_v0.3.0.md); historical plans
> sit in [`docs/plan_v0.2.0.md`](docs/plan_v0.2.0.md) and
> [`docs/plan_v0.1.0.md`](docs/plan_v0.1.0.md). Install via
> `cargo install axon-lsp`, download from
> [GitHub Releases](https://github.com/Bemarking/axon-lsp/releases),
> install the [VSCode extension](editors/vscode/), or build from
> source — see [docs/integrations/install.md](docs/integrations/install.md).

## What it does

| Capability | Behavior |
|---|---|
| `publishDiagnostics` | Lex / parse / type errors with `axon-{lex,parse,type}` source tag and precise ranges (whole identifier for type errors, single char for punctuation) |
| `hover` | Markdown — local declarations show kind + signature; outer doc comments (`///`, `/** */`) attached to channel decls render as a Markdown paragraph above the signature; built-in types (`String`, `Channel`, `Trusted`, …) and syntax keywords pull rich docs from the embedded corpus |
| `definition` | Zero-width Location at the declaration's keyword |
| `documentSymbol` | Outline tree with sensible `SymbolKind` per declaration kind |
| `completion` | Context-aware: inside a type annotation surfaces only types, in declaration name slots stays out of the way, default context offers keyword snippets + user decls + built-in types with `lsp-docs` documentation attached |
| `references` | File-local + workspace-wide. Walks every TypeExpr-bearing site (type field types, flow params, return types, intent output types) with `Loc`-precise ranges; cross-file scan reads in-memory snapshots first, falls back to disk parses |
| `prepareRename` + `rename` | Workspace-wide atomic `WorkspaceEdit`. D8 rejections pinned in code: built-in types, declaration keywords, stdlib symbols, and identifiers absent from the workspace index reject with stable user-facing messages |
| `documentSymbol` + `workspace/symbol` | The latter is a substring-match query against an index of every `name → DeclLocation` the server has seen in any `.axon` file under workspace folders |
| `semanticTokens/full` | LSP standard registry mapping — types, functions, variables, parameters, properties, decl keywords, with `declaration` / `readonly` modifiers. The TextMate grammar still paints lexical concerns (strings, numbers, regular comments) so the two layers don't double-paint |
| `inlayHint` | Inferred type after `let` literal RHS — `: String`, `: Integer`, `: Float`, `: Bool`. Range filter respected so off-screen lines don't ship hints |
| `codeAction` | Quick fixes (kind = `quickfix`): **Rename to typo candidate** (Levenshtein-≤2 against the workspace decl name set on `Undefined <kind> 'NAME' …`, up to 3 candidates), **Insert missing `}`** (zero-width insertion on `Expected RBrace, found …`), **Add missing field** (matcher ready for `axon-frontend 0.4` — fires on `… field 'F' … type 'T' …` shape and inserts `  F: Any` after the type's last existing field). Refactors (kind = `refactor.rewrite`): **Make `T` optional** — cursor-driven, appends `?` to a non-optional type expression, idempotent on already-optional types |
| `formatting` | Token-level passthrough through the [`axon-fmt`](crates/axon-fmt/) crate. Right-trims trailing whitespace per line + ensures exactly one trailing `\n`. Comments survive verbatim across all six kinds (`//`, `/* */`, `///`, `/** */`, `//!`, `/*! */`) thanks to the Fase 14 trivia channel. Mirrors `axon fmt` in `axon-lang` v1.9.0+ for byte-identical output |
| `onTypeFormatting` | Trigger: `}`. Surfaces only the line edits within `[pos.line, pos.line + 2]` so the user's keystroke flow isn't fought by full-document rewrites. Same idempotence + comment-round-trip guarantees as `formatting` |
| `signatureHelp` | Pops the flow's signature label + active-parameter highlight when the cursor sits inside `run FlowName(arg1, arg2|...)`. Forward-walk state machine over the rope handles strings, six comment kinds, and nested calls — works on partial / unparseable source. Workspace fan-out via the index when the flow lives in a different file |
| `documentHighlight` | File-local — every occurrence of the symbol under the cursor with `WRITE` for the declaration and `READ` for each use. Reuses the `references` AST walker via a `RangeKind`-aware variant |
| `foldingRange` | Three families: top-level decl bodies (`region`), multi-line block comments (`comment`), and runs of three or more consecutive line comments (`comment`). Walker skips strings + every comment shape so `{` / `}` inside `"…"` or `// …` don't open frames |
| `selectionRange` | AST-driven smart-select expansion with strict-nesting filter: program → decl → field/parameter → type-expr (full extent including generic param + `?`) → word. Multi-cursor supported (LSP allows `Vec<Position>` per request) |
| `codeLens` | One `N references` lens per top-level declaration. Click invokes `editor.action.showReferences` pre-populated with the workspace-wide use locations. `0 references` lenses are intentionally omitted (D3 — no `0 references` noise). `resolve_provider: false` so each lens ships complete from the first pull |
| `prepareCallHierarchy` + `incomingCalls` + `outgoingCalls` | Limited to call-able kinds (flow, persona, tool — D6 conservative scope). Top-level `run` statements surface as synthesised callers with kind `EVENT`; flow bodies with `step.persona_ref` / `use_tool.tool_name` references surface as flow callers. Outgoing only fires for flows |
| `diagnostic` + `workspace/diagnostic` (LSP 3.17 pull) | Advertised alongside push. When the client advertises `textDocument.diagnostic`, the server flips into pull mode for **every** URI — `did_open` and the debounced reparse stop publishing push notifications. `workspace/diagnostic` walks the union `workspace_index.all_uris ∪ doc_store.open_uris` so parse-error files (which never reach the index) still surface their diagnostics |
| `window/workDoneProgress` (workspace scan) | Wraps the recursive `.axon` discovery + parse pass on `initialized`. Lifecycle: `workDoneProgress/create` → `Begin` → throttled `Report` (~20 reports per scan, message `"42 / 200 — file.axon"`) → `End`. Older clients that don't ack `create` get the scan without the lifecycle — graceful degradation, never blocks |
| Cancel-aware workspace fan-outs | Five long-running handlers (`references`, `rename`, `code_lens`, `incoming_calls`, `workspace_diagnostic`) yield via `tokio::task::yield_now()` between iterations so tower-lsp's drop-on-cancel semantics actually preempt stale requests. The user navigating away mid-scan no longer pays for the rest of the work |
| `axon/askAdvisor` (opt-in) | Custom LSP method backed by Anthropic's Messages API. Two-axis gate: build with `--features llm` AND set `AXON_LSP_LLM_ENABLED=1` + `ANTHROPIC_API_KEY=…`. Default builds reply with `MethodNotFound`; the deterministic stack is unaffected by either gate. |

## Editor integrations

Single binary, every editor. Pick yours and follow the doc:

- **VSCode** — [docs/integrations/vscode.md](docs/integrations/vscode.md)
  ([extension source](editors/vscode/))
- **Cursor** — [docs/integrations/cursor.md](docs/integrations/cursor.md)
- **Antigravity** — [docs/integrations/antigravity.md](docs/integrations/antigravity.md)
- **Zed** — [docs/integrations/zed.md](docs/integrations/zed.md)
- **Neovim** — [docs/integrations/neovim.md](docs/integrations/neovim.md)
- **Claude Code** — [docs/integrations/claude-code.md](docs/integrations/claude-code.md)

[`docs/integrations/README.md`](docs/integrations/README.md) carries
the index plus the canonical 4-check smoke-test program every editor
must light up.

## Architecture

```
crates/
  axon-lsp/   # binary, tower-lsp bootstrap + every LSP handler
  lsp-core/   # logic — pure, sync, no I/O
              #   document.rs (rope), diagnostics.rs (range scanner),
              #   hover.rs, definition.rs, completion.rs (context heuristic),
              #   symbols.rs (DeclMeta + outline), text.rs (shared helpers),
              #   frontend.rs (axon-frontend shim),
              #   workspace.rs (cross-file decl index), references.rs,
              #   rename.rs, semantic_tokens.rs, inlay_hints.rs,
              #   code_actions.rs, formatting.rs (axon-fmt shim),
              #   signature_help.rs, document_highlight.rs,
              #   folding_range.rs, selection_range.rs, code_lens.rs,
              #   call_hierarchy.rs (v0.3.0 capability completeness)
  axon-fmt/   # token-level lossless formatter, consumed by `lsp-core`
              # via workspace dep + invoked by `textDocument/formatting`.
              # Mirrors `axon fmt` in `axon-lang` v1.9.0+
  lsp-docs/   # 18+ Markdown entries embedded at compile time via build.rs;
              # zero runtime deps, lookups hit `&'static str` slices
  lsp-llm/    # opt-in advisor, feature = "llm";
              # default builds keep `reqwest` out of the binary entirely
              # — pinned by an executable smoke test that scans the
              # release binary for forbidden symbol bytes
editors/
  vscode/     # TypeScript extension; spawns the binary, attaches
              # the editor's selection as advisor context, ships
              # commands + snippets
docs/
  plan_v0.2.0.md       # the live roadmap + Resultado blocks (current)
  plan_v0.1.0.md       # historical, kept for traceability
  fase_14_lossless_lexing.md  # upstream reference doc for the
                               # trivia channel `axon-fmt` consumes
  style.md             # canonical Axon style — what the formatter
                       # changes (and what it deliberately doesn't)
  integrations/        # one self-contained doc per editor
```

`lsp-core` and `lsp-docs` keep `cargo tree --edges normal` empty of
`tokio / axum / sqlx / aws-* / reqwest / hyper / jsonwebtoken` —
verified in CI on every commit. The Fase 12.c contract (frontend with
zero runtime deps) holds end-to-end into the LSP binary.

## Build

Rust **1.95.0 stable** (pinned by `rust-toolchain.toml`).

```sh
cargo build --workspace --release
cargo test --workspace
```

The advisor crate is excluded from the default build:

```sh
cargo build --workspace --release --features axon-lsp/llm
```

## Releases

[Releases page](https://github.com/Bemarking/axon-lsp/releases)
publishes:

- 3 platform archives (`linux-x86_64`, `macos-arm64`,
  `windows-x86_64`) — one binary each.
- `SHA256SUMS` — combined integrity-verification list.

macOS Intel (`x86_64-apple-darwin`) builds from source
(`cargo install axon-lsp`) until a cross-compiled / self-hosted
target lands in a later release.

Auto-generated release notes summarise the commits since the
previous tag. The full `v0.1.0` story lives in
[`CHANGELOG.md`](CHANGELOG.md) and the per-sub-fase Resultado blocks
in [`docs/plan_v0.1.0.md`](docs/plan_v0.1.0.md).

## Contributing

`axon-lsp` is **adopter-agnostic** — it serves the Axon language the
same way `rust-analyzer` serves Rust. No customer or tenant names
appear in code, tests, fixtures, or docs. Details in
[`CONTRIBUTING.md`](CONTRIBUTING.md).

## License

MIT — see [`LICENSE`](LICENSE).
