Status: accepted baseline (living language services and LSP contract)
Scope: editor-neutral language services (wright-language) and the thin LSP
adapter (wright-lsp)
document/workspace model (Document, DocumentStore)
→ LanguageService (editor-neutral, no LSP types)
├─ diagnostics (parse errors + semantic findings)
├─ hover / definition / references
├─ completion (symbols + builtins + keywords)
├─ rename (identifier-exact, pipeline-validated)
└─ semantic tokens (native lexer classification)
↓
wright-lsp (thin protocol adapter, Content-Length stdio framing)
All semantic logic lives in wright-language; wright-lsp only maps LSP
DTOs, so adding or removing an editor protocol never changes the compiler or
analyzer contracts.
Document: URI identity, current text, monotonic internalversion, project root (include base).DocumentStore: open/change/close lifecycle with version bumping.- Positions and ranges are 0-based editor conventions; the service converts to
the compiler's 1-based spans at the boundary. UTF-16 ↔ character conversion
is centralized in
wright_language::document(utf16_offset_to_char,char_offset_to_utf16,span_to_range,full_document_range) and is the only path that consumes or emits editor positions; no UTF-16 offset is ever used as a byte index. - File URI ↔ filesystem path conversion is centralized in
wright_language::document(uri_to_path,path_to_uri) using the standard URL parser, covering percent-encoding, spaces, Unicode filenames, and platform drive paths. - Every result carries
document_version; stale results are detectable and replaceable.
Reanalysis is a deterministic full recomputation over the changed document.
The committed language-service perf harness (wright-language/tests/perf.rs)
measures the heaviest corpus fixture: analyze ≈1.0 ms, diagnostics ≈0.9 ms,
hover ≈0.9 ms, peak RSS ≈7 MB (target/language-service-perf.json). Each
workflow is bounded far below interactive latency.
The responsiveness requirement is stale/current-state correctness, not in-flight request cancellation. Synchronous deterministic full recomputation is the implemented contract: results are version-tagged, stale/out-of-order client versions are rejected and cannot overwrite newer state, obsolete results are never surfaced as current, and every query re-reads the current document state. True in-flight request cancellation is explicitly deferred.
Re-evaluation trigger (measured, testable): re-open the true-cancellation or incremental-analysis decision when any of the following holds on the current machine baseline:
- the committed perf harness mean for
analyzeordiagnosticsexceeds the harness regression bound (200 ms per workflow); or - peak RSS on the perf workload exceeds 1 GB; or
- a representative project-scale measurement (a multi-file corpus of at least 20 source files, or the declared representative project) shows any single interactive request exceeding 100 ms.
Until a trigger fires, bounded full recomputation with stale-result suppression is the authoritative contract.
- Diagnostics: source-aware
SourceDiagnostics for parse errors and analyzer findings, carrying source identity, source-local range, severity, code, message, and source/requesting-document versions; included-file spans are resolved against their own source text, not the requesting document. - Hover: symbol name/kind and usage summary (reads/writes/calls/rules).
- Definition / References: via the semantic index over source spans.
- Completion: declared symbols, manifest-declared builtins and receiver members, keywords (the OPY semantic manifest is the authoritative builtin surface, #109).
- Rename: project-wide identifier-exact rename: resolves the symbol
through the semantic index, unions its exact declaration/definition/reference
identifier spans across every open root whose project includes the
requesting document, and returns source-aware full-document edits for all
affected sources (open overlays take precedence over filesystem content).
Collisions, unresolvable identity, a missing exact identifier span, stale
source identity, and failed validation refuse explicitly. Edits are
Wright-owned (
RenameEdit/TargetSpaninwright-language) and carry the SHA-256 source identity computed throughwright_driver::input_identity. Rename delegates target resolution, edit generation, and validation to the shared driver refactoring contract (wright_driver::edit::semantic_rename, #129): every affected root resolves through its original owner-backed source implementation, the unioned exact-range transaction is validated through the shared #128 transaction boundary (wright_driver::edit::validate_transaction), and no duplicate edit-validation or span-collection semantics live here. - Semantic tokens: classified by the native lexer/parser identity (keywords, variables, identifiers, strings, numbers, operators, macros, attributes), not textual heuristics.
.ostw/.del documents route through the owner-backed adapter. del-rs
loads the project closure, resolves semantics, lowers directly to canonical
WIR, and preserves owner diagnostics and file identity. Shared analysis runs
on successful owner WIR; owner capabilities that do not produce WIR are
reported as structured source errors.
Operations that stay unsupported for OSTW are explicitly refused or documented, never emulated through upstream calls:
- Semantic rename and overlays: refused explicitly while the owner-backed adapter lacks a source-edit/overlay project contract. The service never invokes an upstream compiler or the removed Wright implementation as a fallback. Rename edits modify original OSTW source with exact identifier ranges, never reconstructed text.
- Whole-source pretty-printing / comment-preserving regeneration: not implemented; diagnostics and navigation are source-preserving.
- Upstream OSTW LSP parity, classes/generics/lambdas/pattern matching beyond
the accepted corpus boundary, and whole-source regeneration remain out of
scope for the language service (the corpus boundary defines what resolves).
Workshop → OPY/OSTW semantic reconstruction is available through the CLI
and driver conversion command (
wright convert --target opy|ostw, #126), not through the language-service surface.
wright-lsp (stdio, Content-Length framing) implements: initialize
(capability negotiation: hover/definition/references/completion/rename/full
semantic tokens), didOpen/didChange/didSave/didClose (didSave is an explicit
no-op for full-sync documents; didClose retires diagnostics), publishDiagnostics
(versioned and grouped by source identity, with didClose cleanup), per-root
publication ownership: a source that disappears from a root analysis is
retired with an empty publishDiagnostics unless another open root still owns
it, so no diagnostic stays stale solely because its source left the analysis,
dependency-refresh of affected documents on include/overlay changes, hover,
definition, references, completion, rename (multi-document workspace edit),
semanticTokens/full, shutdown/exit.
Rename is a pure adapter (#131): the LSP layer maps the shared #129
transaction (exact-occurrence editor edits from wright-language) directly
to WorkspaceEdit documentChanges/TextDocumentEdit, creating one TextEdit per
semantic occurrence, grouped by document, with each open document identified
at its current version and filesystem-backed sources in the unversioned
null form. No symbol resolution, collision, or stale-state logic exists in
the protocol layer; unsupported rename targets surface the shared refusal as
an explicit LSP error, never a textual fallback. The end-to-end
harness (wright-lsp/tests/lsp.rs) drives the real binary and verifies
capability negotiation, lifecycle, navigation, completion, rename (OPY
multi-document and supported OSTW rename, UTF-16/non-BMP ranges, version
preconditions), semantic tokens, and stale-version suppression.
VS Code/browser extensions, incremental diff-based reanalysis (full recomputation is deterministic and fast enough), and client-side behavior scenarios remain future work.