The OxL language server (oxl lsp)
Standard [С ч. III.3], TZ requirements И3 (formatter in the editor) and И4 (LSP, "M": basic features). Decisions:
D-2800..D-2810 in docs/decisions.md.
oxl lsp speaks the Language Server Protocol 3.17 (JSON-RPC 2.0, Content-Length framing) on stdin/stdout. It runs the
same front end as oxl check (lexer, parser with error recovery, sema), so the editor shows the compiler's own
diagnostics and types. Clients: the VS Code extension in tools/vscode (see its README), or any LSP client; editors built on tree-sitter can use the
grammar in tools/tree-sitter-oxl for highlighting without the server (JetBrains is not targeted, answer 10 of the stage 5
plan):
oxl lsp [--stdio] [--debounce-ms N] [--std-dir DIR] [--log]--stdio is accepted and ignored (stdio is the only transport). --log (or OXL_LSP_LOG=1) traces requests, timings and
analyses to stderr. Environment: OXL_STD_DIR (standard library sources), OXL_DIAG_DOCS (base of the diagnostic page
links). initializationOptions: { "debounceMs": 150, "diagnosticDocsBase": "file:///…/docs/diagnostics/" };
workspace/didChangeConfiguration accepts settings.oxl.debounceMs.
Features#
| Request / notification | What it does |
|---|---|
textDocument/publishDiagnostics | Errors and lint warnings with code, codeDescription.href (the page in docs/diagnostics), notes as relatedInformation, Unnecessary / Deprecated tags, fix-its in data. While the package has syntax errors only syntax diagnostics are sent. |
textDocument/hover | Signature (functions with effects, data / enum bodies, interfaces, aliases, variants, consts, locals with their inferred type, fields) and the /// doc comment. |
textDocument/definition, typeDefinition | Locals, parameters, functions, methods, types, fields, variants, modules and imported names, across the files of the package and into the standard library. |
textDocument/references, documentHighlight | All uses in the package (declaration optional); highlights in the current file. |
textDocument/prepareRename, rename | Symbols defined in the main package; shorthand struct fields (P{ x }) keep their meaning. |
textDocument/documentSymbol | Hierarchical outline (types with fields / variants, impls with methods, actors, services, tests, consts). Works while the file has syntax errors. |
textDocument/completion | Scope names (locals, fields / methods of the implicit self, module items, imports, prelude, keywords); members after . / ?.; variants, constants and static functions after Type.; module items after std.; type names after -> / as; field names (name: ) inside the braces of a struct literal. Triggers: . and :. |
textDocument/signatureHelp | Parameters of the function or method being called, active parameter by comma count. Triggers: ( and ,. |
textDocument/formatting, rangeFormatting, onTypeFormatting | The canonical formatter (oxl fmt) as minimal line edits; on type after } and \n. Files with syntax errors are left alone. |
textDocument/codeAction | Quick fixes from the fix-its of diagnostics (incl. "extract to constant" of magic-literal), and from the analysis: handle all errors of the chain (a !> E => panic("TODO…") link per pending error), add @internal / add a policy rule (missing-policy), generate mock for S (in a test body, on the name of a service: mock S { m(_) => panic("TODO…") }), move step to role r / run step on the node of the previous step (pipeline steps, kind refactor.rewrite). |
textDocument/semanticTokens/full, …/range | Tokens classified by what the names resolve to (namespace, type, struct, enum, interface (services too), class (actors), enumMember, function, method, parameter, variable, property, typeParameter) with the modifiers declaration, readonly, static, defaultLibrary, async (suspending); keywords, contextual keywords where they do not name anything (on, durable, within, role, serves, effects …), attributes (decorator), literals, comments. Multi-line comments and strings are split per line. |
textDocument/inlayHint | : T after let names without a type; ! E1 | E2 after the head / each link of a -> chain that brings errors; the inferred effects of a function without a written effect list; on caller / on <role> (, durable) after a pipeline step without on; call markers at calls of rpcs and sharded actors: in process, over network → <roles>, in process on a, over network from b → a, by key → <roles>, scatter-gather (from the call graph and the placement, К14). Categories (data.category) can be switched off with initializationOptions.inlayHints / settings.oxl.inlayHints (types, chainErrors, effects, pipelineRoles, callMarkers). |
workspace/symbol | Declarations (items, members of types, services, actors) of every package below the workspace folders (rootUri / workspaceFolders) and of the workspaces of the open documents; open buffers win over the disk; fuzzy match, at most 1000. |
workspace/didChangeWatchedFiles | Re-reads sources changed on disk by other tools. |
Hover over an rpc (its call S.m(…), client.m(…), the contract or the impl method) adds the rpc section: path and
kind, schema version and tag from oxl.schema.lock, callers per role (call graph), policy mode with @public /
@internal / @allow and the allow / deny rules of the service's policy, every attribute with its final value
(@timeout(T) → 2s, cluster default when none) and the constants it mentions resolved (T = BASE, BASE = 2s).
Rename also follows mock S { m(_) => … }, S.calls(.m) and names inside attributes such as @retry(max: LIMIT) (D-7793).
Rename covers, besides the package: an rpc is one symbol across its contract, the impl method, the rules of the
service's policy, every call (S.m, client.m) and the mock functions; and a pub(api) item or an rpc renamed in a
workspace member is also renamed in the other members that depend on it (each is analysed for the rename, D-6775).
Packages and files#
A document below the src/ directory of a directory that contains oxl.toml is analysed together with its package; the
unsaved text of every open document replaces the file on disk. Any other file is analysed alone (it need not exist on
disk). The standard library is resolved as the compiler does (OXL_STD_DIR, then the directory the compiler was built
with). *_test.oxl files take part in the analysis.
Positions and text#
Documents are UTF-8, line ends are normalized to LF. Positions are UTF-16 code units (the LSP default); if the client
offers utf-8 in general.positionEncodings the server selects it. Incremental text synchronization is used
(textDocumentSync.change = 2).
Responsiveness#
Analyses run on a background thread after a quiet period (150 ms by default) following the last edit. Requests are answered as follows:
- completion and signature help use the newest finished analysis plus the text just typed, so they never wait (about 1 ms for the lookup, 25-45 ms including the lexing of a 2000-line buffer on a Debug build);
- hover, definition, references, rename need an analysis of exactly the current text: if the text changed they
trigger the analysis at once and answer when it finishes (a re-analysis of a 2000-line file takes 100-200 ms on a Debug
build);
$/cancelRequestaborts the wait; after 10 s the request is answered withnull; - symbols and formatting parse the text on their own.
- semantic tokens and inlay hints need the current analysis like hover (about 50 ms / 2 ms for a 2000-line file on a Debug build once it is there); workspace symbols parse the files of the workspace (cached by modification time); code actions use the analysis only when it is current and never wait for one.
Target (TZ 3.2): completion / hover p95 < 100 ms, checked by tests/lsp/perf_test.py (D-2809); the same script checks
semantic tokens and inlay hints against 5x that budget.
Testing#
ctest --test-dir build -R lsp # test_lsp (gtest), lsp_scenarios, lsp_latency
./build/tests/unit/test_lsp # unit tests: JSON, framing, positions, sync, queries, server, stdio, fuzz
OXL_LSP_FUZZ_ITERS=3000 ./build/tests/unit/test_lsp --gtest_filter='LspFuzz*' # long mutation run
python3 tests/lsp/run_scenarios.py --oxl build/tools/oxl/oxl -v # protocol scenarios against the real binary
python3 tests/lsp/perf_test.py --oxl build/tools/oxl/oxl # latency
(cd tools/vscode && npm install && npm test) # extension + grammar
python3 tools/tree-sitter-oxl/scripts/check.py -v # tree-sitter grammar (D-6780)Protocol scenarios (tests/lsp/*.json, format in tests/lsp/README.md) start oxl lsp, replay a session
(initialize, didOpen, didChange, requests) and compare each response with a partial expectation.
Code map#
| File | What |
|---|---|
compiler/src/lsp/Json.cpp, Transport.cpp | JSON value / parser / serializer; Content-Length framing, fd reader. |
Text.cpp, TextDiff.cpp | Documents, LineIndex (UTF-8 / 16 / 32 positions), incremental sync, URIs, EditWindow (offset mapping between text versions), line diff. |
Analysis.cpp | Snapshot: one analysis of a package or file with the editor buffers laid over the disk. |
AstNodes.cpp | Child enumeration of all AST node kinds, pathAt(offset). |
Queries.cpp | Identifier resolution, references, locals in scope, hover text, completion candidates, signatures. |
ServerCore.cpp | Dispatch, document store, analysis scheduling, diagnostics, stdio loop. |
ServerFeatures.cpp | Request handlers. |
Insights.cpp | Stage 5 (D-6770..D-6779): semantic tokens, inlay hints, call markers, rpc hover section, workspace-symbol extraction, snapshot quick fixes. |
ServerTools.cpp | Their request handlers, workspace symbols, rename in the other workspace members. |
tools/oxl/cmd_lsp.cpp | The oxl lsp command. |
Driver hooks used by the server (both additive, off by default): BuildOptions::overlay and
BuildOptions::semaDespiteSyntaxErrors.
Исходник: docs/lsp.md