Commit Graph

52 Commits

Author SHA1 Message Date
gffranco 6efe154f1a phase 7: semantic tokens for syntax highlighting
CI / cargo fmt --check (push) Successful in 30s
CI / cargo clippy (push) Successful in 1m16s
CI / cargo test (push) Successful in 1m20s
`textDocument/semanticTokens/full` + `/range` per SPEC §6.9. Token
type scheme settled in P7: custom `vimwiki*` types
(`vimwikiHeading`, `vimwikiBold`, `vimwikiCodeBlock`, `vimwikiKeyword`,
`vimwikiWikilink`, `vimwikiTransclusion`, …) plus `level1`..`level6`
and `centered` modifiers. Default highlight groups for these arrive
in Phase 9.

Pipeline (`semantic_tokens.rs`):

- `legend()` builds the `SemanticTokensLegend` advertised in
  `initialize` capabilities.
- `LineIndex` precomputes byte offsets per line so multi-line span
  splitting and UTF-16 conversion stay linear.
- `collect_tokens` walks the AST. Blocks that are visually distinct
  (heading, preformatted, math-block, comment) emit one token over
  their span; multi-line ones get split per line because the LSP wire
  format can't represent a token that crosses a newline. Container
  blocks (paragraph, list item, table cell, blockquote) recurse into
  inlines instead of wrapping them, so we end up with a non-overlapping
  tile of inline tokens. Headings shadow nested inlines (one heading
  token covers the line, no Bold-inside-Heading).
- `encode` produces the LSP delta-quintuple stream
  `(deltaLine, deltaStart, length, type, mods)`. UTF-16 mode converts
  byte offsets/lengths to code-unit counts on the fly (BMP + surrogate
  pairs).
- `build_data` is the entry point used by both handlers; the `range`
  variant filters tokens by overlap in the same encoding the client
  requested.

Capability declared as `SemanticTokensOptions { full = true,
range = true, legend = … }`. Both handlers reuse the document store.

P7 resolved in SPEC §11 — custom vimwiki types with rationale and a
note that Phase 9 will ship default highlight-group definitions in
`syntax/nuwiki.vim` and the Lua glue.

Tests (21 new): legend integrity, per-construct emission for every
inline + block kind, heading shadowing of inner inlines, multi-line
code/math block splitting, sort order, delta encoding (same line and
crossing lines), UTF-16 length conversion for é, range filter,
empty-doc edge case, end-to-end `build_data` parity.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 21:14:01 +00:00
gffranco c44db37bab phase 6: LSP foundation — didOpen/didChange, diagnostics, outline
CI / cargo fmt --check (push) Successful in 48s
CI / cargo clippy (push) Successful in 1m19s
CI / cargo test (push) Successful in 1m19s
`tower-lsp` server (`nuwiki-lsp::run` + `run_stdio`) wired up by the
`nuwiki-ls` binary. Backend holds an `Arc<DashMap<Url, DocumentState>>`
per SPEC §6.10 plus an `Arc<SyntaxRegistry>` pre-loaded with
`VimwikiSyntax`. Re-parse is full per change; incremental parsing
deferred post-v1 per spec.

Capabilities declared:
- `text_document_sync = FULL`
- `document_symbol_provider = true`
- `position_encoding = UTF-8` when the client opts into LSP 3.17+
  encodings (SPEC §6.11). Otherwise we keep the legacy UTF-16 default
  and translate spans through a per-line UTF-16 code-unit walk that
  handles BMP and surrogate pairs alike.

Handlers: `initialize` (negotiate encoding), `initialized` (log it),
`shutdown`, `did_open` / `did_change` (full re-parse +
`publishDiagnostics`), `did_close` (drop state, clear diagnostics),
`document_symbol` (nested outline).

Diagnostics: every `BlockNode::Error` (from §6.7's resilient parser)
emits one `DiagnosticSeverity::ERROR` with `source = "nuwiki"`. The
walker descends into Blockquotes and ListItems so errors nested inside
those don't slip through.

Document outline: nested `DocumentSymbol` tree built recursively from
heading levels (`= H1 =` → root, `== H2 ==` → child of preceding H1,
etc.). Titles flatten inline markers via `inline_to_text` so
`*world*` shows up as `world` in the outline.

Pure helpers (`to_lsp_position`, `to_lsp_range`, `ast_diagnostics`,
`headings_to_symbols`) are `pub` so the integration tests can verify
conversion correctness without spinning up the async server. End-to-
end LSP message exercises will land in Phase 8 alongside navigation.

Tests (11): UTF-8 passthrough, UTF-16 conversion for é + 🦀, single
and multi-error diagnostic emission, blockquote descent, flat outline,
nested outline by levels, inline-marker stripping in titles, empty-
heading fallback, registry-dispatch parity.

Dep pin: `idna_adapter` held at 1.2.0 — 1.2.2 needs Cargo's
edition2024 feature (Rust 1.85), our MSRV is 1.83.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 19:44:44 +00:00