LSP UML
LSP UML draws UML class, sequence, activity and use case diagrams from the language server VS Code already has running.
VS Code's outline, "Go to Implementation" and call hierarchy are fed by a language server that parsed the project. LSP UML asks that server the same questions through provider commands, maps answers into a language-neutral model, and draws it. A harvest is one pass of that questioning over a file or folder.
- No external runtimes: needs no per-language parser, Java runtime or PlantUML server, and makes no network call. Offline PlantUML renders in the webview; nothing leaves the machine.
- One code path across languages: a language server does language-specific work, so nothing per-language in this extension falls behind.
- Live and interactive: diagrams re-harvest on save, clicking nodes jumps to declarations, and a
+ button grows the diagram outward one file at a time.

One harvest of a TypeScript lending library test corpus, drawn on the interactive canvas.
Installation and Quick Start
- Install the
.vsix using Install from VSIX... in the Extensions view ... menu, or run code --install-extension followed by the file path.
- Open the project and give its language server a moment to finish indexing.
- Put the cursor in a source file and run LSP UML: Class Diagram from the command palette, or right-click to pick it from the LSP-UML submenu.
- Right-clicking a folder in the Explorer diagrams everything inside it, under a cancellable progress notification, stopping at 500 files.

At the shipped default, a class diagram opens in this panel with the source pane open beside the render.
[!TIP]
If a diagram comes back empty or thin, open the LSP UML output channel (View, then Output, then pick LSP UML from the dropdown). It carries the full text of every run: the scope, the entity and relationship counts, and every note the harvest recorded. The usual cause is a language server that had not finished indexing.
VS Code ^1.90.0 is required. Providers come from the installed language extension:
| Provider |
Required for |
| Document symbol provider |
Every diagram |
| Implementation provider |
Inheritance and implementation edges |
| Call hierarchy provider |
Sequence, activity and use case diagrams |
TypeScript and JavaScript work with no extra extension. Everything else needs its usual language extension installed and warmed up.
Class diagrams on the canvas and in PlantUML
A harvest finds:
- Types, with the kind the language server reported, and stereotypes such as «enumeration» and «interface».
- Members with full signatures, parameters and return types included.
- Inheritance edges.
- Implementation edges.
The canvas is a Svelte and svelte-flow webview laid out by elkjs in a web worker. Click headers or member rows to navigate, fold compartments with ▸/▾, or click + to grow the diagram. LSP UML: Open in Canvas opens the canvas directly.

It is the same lending library domain written in Java and harvested through jdtls, on the same canvas and the same code path as the TypeScript picture at the top of the page.
LSP UML: Class Diagram renders offline PlantUML via bundled @plantuml/core inside the webview. Member rows look identical on screen whether jdtls supplies checkout(Member, String, LocalDate) : Optional<Loan> and Repository<T> directly, or tsserver queries the hover provider.

It is a single file of the test corpus rendered through the PlantUML target.
Sequence and activity diagrams
Put the cursor inside a function or method and run LSP UML: Sequence Diagram. Outgoing calls are walked three levels deep by default (lsp-uml.sequence.maxDepth, default 3, range 1 to 10). The diagram marks depth limits, recursion, and omitted external calls (lsp-uml.sequence.includeExternal).
| UML construct |
Walk mapping |
| Participant, or lifeline |
The owning type of a call target, declared in first-use order rather than sorted. |
| Message |
One outgoing call reported by the call hierarchy provider, numbered in source order. |
| Activation bar |
A call that is still running while the calls it makes run. |
| Action, and partition box |
On an activity diagram, one call per action, and a box named after the entity whose code is running inside it. |

One walk from the cursor, rendered as PlantUML.
Set lsp-uml.defaultRenderer to canvas for a live diagram. Click a message for the call site, or a participant header for its type declaration. Where a walk is cut short, + expand resumes from that call and merges in place.

It is the same walk as the previous picture, opened on the canvas rather than as PlantUML.
LSP UML: Activity Diagram runs from the same cursor position, drawing actions in source order with nested partition boxes. Actions name callees and owners, such as Catalog.find() and Member.addLoan(). It renders to the PlantUML preview only, sharing lsp-uml.sequence.maxDepth and lsp-uml.sequence.includeExternal.

The same walk again, laid out as a flow.
Use case diagrams and the 3D system view
Right-click a folder in the Explorer and pick LSP UML: Use Case Diagram (derived). Derived in the command title is literal, drawing code facts in use case notation rather than a requirements diagram. Multi-call links show counts like x3, while lsp-uml.useCase.maxCallerLookups (default 200) bounds the callables asked about.

One folder of the test corpus, with the callables in it that something outside it calls.
| UML construct |
Derived from |
| System boundary |
The folder you pointed at, with nothing inferred about where a module begins. |
| Use case |
A callable inside the folder that receives a call from outside it, found by asking the language server's incoming-call provider about each one. |
| Actor |
A calling module, meaning the directory the caller's file lives in, named relative to your workspace root, such as src/services. Callers outside the workspace collapse into one external actor. |
LSP UML: 3D View (experimental) harvests the class diagram scope, makes a bounded call-hierarchy pass, and draws the result in space. Click a sphere to open its declaration. It requires WebGL, does not live-update or export, and lsp-uml.defaultRenderer does not steer it.

The same eleven type corpus as a system, rather than as a class diagram.
- A sphere is a type, sized by member count and coloured by kind.
- A wireframe sphere is a type whose harvest was incomplete, or referenced but never harvested.
- A line is a relationship.
- A glowing dot is caller-to-callee call flow, denser where more places call across an edge.
Language support
LSP UML has no parsers, so this table lists language servers rather than languages. Each row was measured against one eleven-type fixture domain, implemented six times, on VS Code 1.135.0. It is not a list of languages that are supported; it is a list of servers that were measured, and of what they gave up.
Anything else with a document-symbol provider draws something. A language with no server at all produces an empty but well-formed model, plus a note that points at the server, rather than an error dialog or a stack trace.
| Language |
Server |
Result |
| TypeScript |
tsserver (built in) |
VERIFIED. Eleven of eleven entities with exact kinds, all three edges, and complete members, while JavaScript rides the same server. Member signatures come from the hover provider, because tsserver reports an empty detail for every member. |
| Java |
jdtls (redhat.java) |
VERIFIED. The richest row: fields, methods, constructors and enum constants are all distinguished, and return types arrive in the symbol answer itself. |
| Python |
Pylance |
VERIFIED. Everything answers, but Python has no interface and no enum declaration. A Protocol comes back as a class and a realization edge is drawn as inheritance. |
| Rust |
rust-analyzer |
VERIFIED. Methods live in impl blocks, which the harvest folds back onto the type they implement. All three edges read as implementation, because MediaItem and Notifier are traits. |
| Go |
gopls |
VERIFIED, in a container. Twelve entities rather than eleven, because ItemDetails is the embedded struct standing in for a base class. LoanStatus comes back as a class, since a named int is not an enum to gopls. |
| C# |
Roslyn LSP (ms-dotnettools.csharp) |
UNVERIFIED. Not measured, as installing without a .NET SDK on the test machine would report "no symbols" as though the server were at fault. The row's expectations are written down and light up on a machine that has the toolchain. |
[!WARNING]
redhat.java defaults to a hybrid launch mode. A syntax-only server answers document symbols in about 1.3 seconds, while the real jdtls imports the project in the background, which takes minutes. Generate a diagram inside that window and you get every type and no inheritance edges.
The harvest re-probes for a few seconds before giving up, and if nothing has answered by then the note says the server may still be indexing rather than making a claim about your code. Wait for the import to finish and run LSP UML: Refresh Diagram, or set "java.server.launchMode": "Standard" in your settings so the server answers nothing until it can answer everything.
What these diagrams cannot show
These boundaries are properties of drawing UML from a language server rather than from a parser. Every one of them also shows up on the diagram itself, not only here.
| Boundary |
What you get |
Why |
| Association and dependency edges |
None, at all. A real harvest produces exactly two kinds of edge, inheritance and implementation, and every type reports its associations as unresolved. |
Resolving a member label such as item : MediaItem into an arrow between two boxes is an unbuilt later harvest phase. Both renderers can already draw associations and dependencies, and the 3D legend lists them because the model has room for them, but nothing fills that room yet. |
| Member signatures |
Display text on a row, for example item : MediaItem. |
Nothing has resolved MediaItem to a type, so there is no edge to it and no association claim. On TypeScript the text comes from the hover provider, costing about 1.7 ms per member, capped at 500 lookups per harvest, and configurable via lsp-uml.signatures, because tsserver reports an empty detail. Other servers fill the detail themselves and keep their own wording. |
Visibility, static and abstract |
No +, - or # glyph on any member, on any language, and visibility recorded as unknown. |
No provider command reports modifiers, so the harvest does not guess them from a naming convention and both renderers draw no glyph rather than a wrong one. A UML tool that shows you + and - either has a parser or is guessing. |
| Inheritance edges |
Partial by scope. Diagram a subclass on its own and you get the class with no arrow. |
An edge appears only when the supertype is inside the scope you harvested, because the harvest discovers edges by asking a supertype who implements it. A subtype named from outside that scope is dropped rather than drawn as a stub, leaving every type stamped as partially harvested and the canvas header reading N of N. |
| Control flow |
No decision diamonds, no fork or join on fan-out, and no loop arrow on recursion. An activity diagram is the calls in source order. |
There is no request that returns a method's conditionals, loops or early returns, and there is no parser here to find them with, so a decision diamond would be a claim with no evidence behind either half of it. The activity legend prints the gap instead. |
| 3D view sampling |
Call flow walked from the first 50 callable members in scope, one level deep, and at most 150 types drawn. |
One full harvest plus an unbounded call pass would cost hundreds of round trips. Both bounds are reported in the view's own caveat list when they bite, so a sparse picture reads as "the walk stopped here" rather than as "this system makes no calls". |
When a diagram is incomplete the extension says so in three places: a partial data note inside the diagram, caveat badges on the canvas, and the full text of every run in the LSP UML output channel. A notification is reserved for the one case where the harvest found no types at all, because then there is no diagram to carry the explanation, and your previous diagram is left on screen.
Printed in the activity diagram legend:
Conditional logic is not visible to the language server:
this shows calls in source order, not branches.
Printed in the use case view:
6 scanned callable(s) had no caller outside the boundary and are not drawn.
That is not the same as unused.
Live diagrams and export
An open diagram tracks contributing files, going stale as you type and re-harvesting on save while preserving canvas layout. Failed re-harvests leave the last good diagram on screen with the reason attached.
Panel titles show (stale) or (pinned) states. Set lsp-uml.refreshOnSave to false to wait for LSP UML: Refresh Diagram, or freeze a view with LSP UML: Pin / Unpin Diagram. The status bar displays four states:
live: up to date with the code it was generated from.
refreshing: a re-harvest is in flight.
stale: the code has changed since this diagram was generated.
pinned: saves will not refresh this diagram.
LSP UML: Export Diagram writes the active tab as SVG, PNG at lsp-uml.export.pngScale, or PlantUML source, preserving canvas layout and partial badges without editor controls like the + button. A sequence diagram on the canvas cannot be exported yet; render it as PlantUML if you need a file.
Commands
The same entries appear under an LSP-UML submenu on the editor and Explorer context menus.
| Command |
Id |
| LSP UML: Class Diagram |
lsp-uml.generateClassDiagram |
| LSP UML: Sequence Diagram |
lsp-uml.generateSequenceDiagram |
| LSP UML: Activity Diagram |
lsp-uml.generateActivityDiagram |
| LSP UML: Use Case Diagram (derived) |
lsp-uml.generateUseCaseDiagram |
| LSP UML: Open in Canvas |
lsp-uml.openInCanvas |
| LSP UML: 3D View (experimental) |
lsp-uml.open3dView |
| LSP UML: Refresh Diagram |
lsp-uml.refreshDiagram |
| LSP UML: Pin / Unpin Diagram |
lsp-uml.togglePinDiagram |
| LSP UML: Export Diagram |
lsp-uml.exportDiagram |
| LSP UML: Save PlantUML Source (.puml) |
lsp-uml.savePlantumlSource |
Settings
| Setting |
Default |
What it does |
lsp-uml.refreshOnSave |
true |
Re-harvests and redraws an open diagram when a file it was generated from is saved. Setting this to false marks the diagram (stale) instead until refreshed with LSP UML: Refresh Diagram. |
lsp-uml.defaultRenderer |
plantuml |
Selects the render target opened by LSP UML: Class Diagram and LSP UML: Sequence Diagram, choosing between plantuml and canvas. LSP UML: Open in Canvas always opens the canvas regardless of this setting. |
lsp-uml.signatures |
on |
Fills in member signatures from language server hover text when the symbol provider reports none, which occurs for every member in TypeScript. Each bare member costs one hover request, capped at 500 per diagram; set off on slow servers or large scopes. |
lsp-uml.useCase.maxCallerLookups |
200 |
Sets how many callables a derived use case view queries for callers before stopping, from 10 to 2000. Each callable costs up to two call hierarchy requests, and the diagram legend reports the scanned count. |
lsp-uml.export.pngScale |
2 |
Sets the resolution multiplier for a PNG export, from 1 to 8. An image exported at 2 is legible on high-DPI displays; export SVG instead for resolution independence. |
lsp-uml.sequence.maxDepth |
3 |
Sets how many levels of outgoing calls a sequence or activity diagram expands, from 1 to 10. Calls at the limit are drawn and marked as truncated rather than dropped. |
lsp-uml.sequence.includeExternal |
false |
Shows calls into code outside the workspace as <<external>> participants on a sequence diagram and as actions on an activity diagram. Off by default because calls such as Date.setDate and Array.push bury the requested collaboration. |
Support
File issues at https://github.com/flying-dice/lsp-uml/issues, the public mirror of the repository. The LSP UML output channel holds the full run text and is the most useful attachment to an issue.
Licence
MIT. The full text is in the LICENSE file in the repository.