sysmlpy Language Server (LSP)¶
(v0.65.0 — Adoption Roadmap Goal 5; batch 4 enhancements v0.83.0)
sysmlpy ships a Language Server Protocol server that brings parse/analyze diagnostics and model navigation into any LSP-capable editor — VS Code, Neovim, Emacs, Sublime, …
sysmlpy-lsp --version # console script (installed with sysmlpy)
python -m sysmlpy.lsp # equivalent
sysmlpy-lsp --log /tmp/lsp.log # protocol trace for debugging
Capabilities¶
| LSP capability | Behavior |
|---|---|
publishDiagnostics |
On open/change: syntax errors with exact ANTLR line:column ranges, plus semantic issues from analyze() (errors/warnings) located in the text |
documentSymbol |
Hierarchical model outline (packages → defs → features) with kind icons (Package, Class, Field, …) and part def/: Type details |
hover |
Markdown card: element kind, name, qualified name, typed by: Type, literal value (via the grammar object's get_value()) |
definition |
Go-to-declaration: usage name → its declaration; type name (in part w : T) → the type's definition |
completion |
SysML v2 keywords + every named element; after base. the members of the resolved type of base (typed part, or a definition named directly) |
workspace/symbol |
Case-insensitive substring query across all open documents plus *.sysml files under the initialized workspace root (cached until the next document change) |
| Text sync | Incremental (didOpen / didChange with LSP ranges); range-less changes are still accepted as full-document replacements |
Position encoding is UTF-16 (LSP default).
Diagnostic fidelity (honest limits)¶
- Syntax errors carry exact positions from the ANTLR error listener.
- Semantic issues are position-tracked by source-order pairing
(v0.83.0): the symbol walk visits model elements in declaration
order, so the n-th element with a given
(kind, name)is paired with the n-th declaration occurrence of that pair in the text — an issue aboutFleet::Enginepoints at the rightpart def Engineeven when several share the name. Issues whose element cannot be located fall back to the quoted-'name'-occurrence heuristic, then to line 1. True parser-side position tracking (annotating visitor dicts with ANTLR token positions) remains future work. - While a document is transiently unparsable (e.g. a half-typed
expression), outline/hover/definition degrade to empty results, but
completionkeeps working: member resolution falls back to the last successfully parsed model, and the keyword/name fallback list is built from that model too.
Architecture¶
editor (VS Code / Neovim) stdio JSON-RPC (Content-Length framing)
│ ▲
▼ ▼
extension/nvim-lsp sysmlpy.lsp.stdio (transport, logging)
│ │ dicts
│ sysmlpy.lsp.server.SysmlLanguageServer
│ │
│ DocumentIndex: sysmlpy.parse() + sysmlpy.analyze()
│ → diagnostics / symbol tree / features
- Transport-agnostic core —
SysmlLanguageServer.handle_message()maps message dicts to message dicts;protocol.pyimplements the byte-stream framing;stdio.pyglues them. All 37 protocol tests run in-process against aBytesIOpipe plus one subprocess smoke test. - One
DocumentIndexper document version caches the parse + analysis snapshot; every feature handler reads that snapshot, so outline, hover and diagnostics always agree. The server additionally keeps each document's last successfully parsed model (surviving re-indexing on every keystroke) socompletionkeeps resolving members while the text is transiently broken. analyze()failures are swallowed (issues become the diagnostics that did parse) — the analyzer must never crash the editor session.
Editor setup¶
VS Code¶
A ready-to-package extension lives in
editors/vscode/sysmlpy-lsp/
(plain JS, no build step):
cd editors/vscode/sysmlpy-lsp && npm install && code . # F5 → dev host
# or
npx vsce package && code --install-extension sysmlpy-lsp-*.vsix
Neovim (0.11+, built-in LSP)¶
-- ~/.config/nvim/lua/sysml.lua (or init.lua)
vim.filetype.add({ extension = { sysml = "sysml" } })
vim.lsp.config("sysmlpy", {
cmd = { "sysmlpy-lsp" }, -- or { "python", "-m", "sysmlpy.lsp" }
filetypes = { "sysml" },
root_markers = { ".git", "sysml.lock" },
})
vim.lsp.enable("sysmlpy")
Open a .sysml file — :lua vim.diagnostic.setloclist() shows issues,
K hovers, gd jumps to definitions, :Pickers -> symbols outlines.
Neovim (0.8–0.10, nvim-lspconfig)¶
local lspconfig = require("lspconfig")
local configs = require("lspconfig.configs")
if not configs.sysmlpy then
configs.sysmlpy = {
default_config = {
cmd = { "sysmlpy-lsp" },
filetypes = { "sysml" },
root_dir = lspconfig.util.find_git_ancestor,
},
}
end
lspconfig.sysmlpy.setup({})
Other editors¶
Any client that can spawn a stdio LSP server works. Point it at:
Testing¶
The suite covers framing round-trips (incl. non-ASCII payloads and
malformed headers), the full lifecycle (initialize → features →
shutdown → exit), diagnostics ranges, every feature, an in-memory
stdio session, and a real subprocess run of python -m sysmlpy.lsp.
The batch-4 suite adds incremental-sync edits (UTF-16 positions,
clamping, mixed full+range changes), source-order diagnostic pairing,
workspace scans, and .-member completion over transiently broken
documents.
Follow-ups (tracked in TODO.md)¶
- ~~Incremental text sync~~ (shipped v0.83.0)
- ~~
workspace/symbol+ multi-file workspace support~~ (shipped v0.83.0 — root scan, not yet cross-file symbol resolution) - ~~Completion after
.(member completion via type resolution)~~ (shipped v0.83.0 — direct members; inherited members and library types fall back to the full list) - True parser-side position tracking for semantic diagnostics (annotating visitor dicts with ANTLR token positions — a larger visitor change; the current source-order pairing covers the common cases)
- Debounced re-parse for very large documents
- Cross-file go-to-definition across workspace roots