The mx compiler includes a built-in LSP server, activated with mx --lsp. It communicates via JSON-RPC over stdio with no external dependencies.
mx --lsp [--m2plus] [-I path]...Typically launched by a client (VS Code, Neovim, etc.) rather than manually.
| Feature | Method | Description |
|---|---|---|
| Diagnostics | textDocument/publishDiagnostics |
Real-time error/warning reporting (debounced) |
| Hover | textDocument/hover |
Type signatures and symbol information |
| Go to definition | textDocument/definition |
Same-file and cross-file navigation |
| Find references | textDocument/references |
Identity-based cross-file reference search |
| Rename | textDocument/rename |
Multi-file rename with prepare support |
| Completion | textDocument/completion |
Scope-aware suggestions; triggers on . |
| Completion resolve | completionItem/resolve |
Full type signatures for completion items |
| Signature help | textDocument/signatureHelp |
Parameter hints; triggers on ( and , |
| Document symbols | textDocument/documentSymbol |
File outline (procedures, types, variables) |
| Workspace symbols | workspace/symbol |
Cross-file symbol search (case-insensitive) |
| Document highlight | textDocument/documentHighlight |
Highlight all uses of symbol under cursor |
| Semantic tokens | textDocument/semanticTokens/full |
Rich syntax highlighting |
| Code actions | textDocument/codeAction |
Quick fixes (e.g., add missing import) |
| Call hierarchy | callHierarchy/* |
Incoming/outgoing calls (workspace-wide) |
| Cancellation | $/cancelRequest |
Cancel in-flight requests |
| Reindex | m2/reindexWorkspace |
Force full workspace reindex (custom method) |
The server uses a best-effort, eventually consistent indexing model. See lsp-invariants.md for formal guarantees.
Key properties:
- Open documents are always analyzed from in-memory text (never stale).
- Workspace index may lag for closed files; converges after save or reindex.
- Interactive requests (hover, completion, etc.) never block on the workspace index.
- Cross-file features (references, rename, call hierarchy) use identity-based inverted indexes for O(1) lookup, with fallback to single-file results if the index is stale.
The LSP server provides structured language documentation for built-in types, procedures, keywords, and standard library modules. This documentation is served through:
- Hover: hovering over
INTEGER,NEW,MODULE,WHILE,InOut, etc. shows a markdown panel with the signature, a one-line summary, and optional details. - Completion resolve: built-in symbols include documentation in the completion detail panel.
- Signature help: built-in procedures (INC, DEC, etc.) include documentation in the signature help popup.
Documentation is centralized in src/lsp/lang_docs.rs and is never duplicated across handlers. User-defined symbols with real source locations always take precedence -- language docs only appear for builtins (symbols with no source file) and keywords.
Coverage includes:
- Built-in types: INTEGER, CARDINAL, REAL, LONGREAL, BOOLEAN, CHAR, BITSET, etc.
- Built-in procedures: NEW, DISPOSE, INC, DEC, ABS, ODD, CAP, ORD, CHR, VAL, HIGH, SIZE, etc.
- Built-in constants: TRUE, FALSE, NIL
- Standard library modules: InOut, RealInOut, MathLib0, Strings, Terminal, Storage, SYSTEM, etc.
- Keywords and constructs: MODULE, PROCEDURE, RECORD, ARRAY, IF, WHILE, FOR, CASE, etc.
- Modula-2+ extensions: TRY/EXCEPT/FINALLY, REF, REFANY, OBJECT, LOCK, TYPECASE, etc.
| Variable | Default | Description |
|---|---|---|
MX_LSP_DEBOUNCE_MS |
250 |
Delay (ms) before publishing diagnostics after a change |
MX_LSP_INDEX_DEBOUNCE_MS |
250 |
Delay (ms) before updating workspace index after a change |
MX_LSP_TICK_MS |
50 |
Timer thread interval (ms) for debounce flush |
Clients can pass these in the initialize request:
{
"diagnostics": {
"debounce_ms": 250,
"index_debounce_ms": 250
}
}initializationOptions take precedence over environment variables.
Set debounce to 0 for immediate diagnostics/indexing on every keystroke (higher CPU usage).
The server auto-detects projects by walking up from the file's directory to find m2.toml. When found:
- Include paths are resolved from the manifest and lockfile
m2plusmode is read from the manifest- Saving
m2.tomlorm2.locktriggers automatic reindexing
The server supports workspaceFolders from the initialize request. Each folder is checked for an m2.toml manifest. Cross-file features are scoped to the workspace root containing the file -- results from other roots are excluded.
If no workspace folders are provided, the server falls back to rootUri.
Send a custom request to force the workspace index to reflect current state:
{"jsonrpc": "2.0", "id": 1, "method": "m2/reindexWorkspace", "params": {}}Response:
{"jsonrpc": "2.0", "id": 1, "result": {"files": 15, "symbols": 87}}In VS Code: Command Palette > "Modula-2+: Reindex Workspace".
| Index | Token type |
|---|---|
| 0 | keyword |
| 1 | type |
| 2 | function |
| 3 | variable |
| 4 | parameter |
| 5 | property |
| 6 | namespace |
| 7 | enumMember |
| 8 | number |
| 9 | string |
- The LSP analyzes each file independently with transitive
.defimports. There is no whole-program type checking. - Analysis uses the pure lex/parse/sema path (
analyze.rs); no C code is generated.
- The workspace index rebuilds entirely when any file changes (no incremental symbol updates). This is fast for typical project sizes but may be noticeable with hundreds of files.
workspace/symbolreturns at most 200 results.- Name-based fallback for references cannot distinguish overloads or same-named procedures in different modules.
- Cross-file rename skips files outside the workspace root (dependency directories are read-only).
- If the workspace index is stale, cross-file features return partial (same-file only) results.
- The server uses full document sync (TextDocumentSyncKind 1). Incremental sync is not supported.
- Cancellation is checked at handler entry and after index rebuilds, but not within individual handler logic. Long-running handlers cannot be interrupted mid-execution.
Verify mx is accessible:
mx --version-jsonIf not on PATH, set the full path in your editor's configuration.
- Ensure the file has a
.modor.defextension. - Check the editor's output panel for LSP errors (in VS Code: Output > "Modula-2+ Language Server").
- Try setting
MX_LSP_DEBOUNCE_MS=0in your environment for immediate diagnostics.
Cross-file features use the workspace index, which updates on save. If results seem wrong:
- Save all open files.
- Run "Reindex Workspace" from the command palette.
The server resolves dependencies from the m2.lock lockfile. If definitions from a dependency are not found:
- Ensure
m2.lockexists and is current: runmxpkg resolve. - Save
m2.lockto trigger the LSP to reload project context. - Run "Reindex Workspace".
Each workspace root is treated as a separate project. Cross-file features do not span roots. If you expect cross-root references, consolidate into a single root with appropriate include paths.
- Default debounce (250ms) provides good responsiveness. Lower values increase CPU usage.
- Initial indexing of large workspaces may take a few seconds. The server sends
workDoneProgressnotifications if the client supports them. - The tick timer (default 50ms) controls how frequently debounced items are flushed. Increasing
MX_LSP_TICK_MSreduces CPU usage at the cost of latency.