The extension ships as two artefacts that talk over LSP (JSON-RPC over stdio):
- Zed extension (WASM) —
extension/— compiled towasm32-wasip1and loaded by Zed. - LSP server (native binary) —
lsp-server/— compiled for the host platform (macOS, Linux, Windows).
Zed Editor
│
├── loads extension/<…>.wasm
│ └── implements zed_extension_api::Extension
│ └── language_server_command() spawns zed-mermaid-lsp
│
└── speaks LSP with zed-mermaid-lsp
└── code actions, edits, …
Zed extensions run inside a WASM sandbox, which forbids:
- direct host filesystem access (needed to write
.mermaid/<id>.mmd) - spawning native threads or pulling in crates with system dependencies
- crates that don't compile for
wasm32-wasip1
A native LSP has none of these constraints. This is the standard pattern in
Zed (see rust-analyzer, pyright, …).
sequenceDiagram
participant U as User
participant Z as Zed
participant E as Extension WASM
participant L as Native LSP
Z->>E: load extension
E->>L: spawn binary (stdio)
Z->>L: initialize
L-->>Z: capabilities (codeAction)
U->>Z: opens file.md, cursor in ```mermaid```
Z->>L: textDocument/didOpen
Z->>L: textDocument/codeAction(range)
L-->>Z: [{ title: "Render Mermaid Diagram", … }]
U->>Z: Cmd+. → picks "Render"
Z->>L: applies WorkspaceEdit
L->>L: render SVG (pure-Rust crate)
L->>L: save source → .mermaid/<id>.mmd
Z-->>U: file modified
```mermaid
flowchart LR
A --> B
```
<!-- mermaid-render id=a1b2c3d4 -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 100">
…
</svg>
<!-- /mermaid-render -->
The id is an FNV-1a hash of the source, truncated to 8 hex characters. This
makes rendering idempotent: rendering the same source twice produces the same
id, so re-rendering is harmless.
<workspace_root>/.mermaid/<id>.mmd holds the original mermaid source.
Commit this directory to git so collaborators can re-edit the diagrams.
| Module | Responsibility |
|---|---|
main.rs |
Logger init, starts tower-lsp-server on stdio |
server.rs |
Implements LanguageServer, routes requests |
document.rs |
Markdown parsing, locates raw / rendered blocks |
code_actions.rs |
Builds CodeActions based on cursor position |
renderer.rs |
Wraps the pure-Rust mermaid renderer crate |
source_store.rs |
Reads/writes .mermaid/<id>.mmd and resolves workspace |
Picked: mermaid-rs-renderer
v0.2 (1jehuang).
- Pure Rust, no runtime dependency (no Node, no headless Chromium).
- Minimal API:
mermaid_rs_renderer::render(source) -> Result<String, _>. - 23 diagram types claimed; 100-1400× faster than
mermaid-cli.
Alternatives considered:
merman(Latias94) — claims 1:1 parity with mermaid 11.12.3, but needs arenderfeature flag and exposes a more verbose API. Kept as a fallback ifmermaid-rs-rendererever falls short.mermaid-to-svg(warpdotdev on GitHub) — not on crates.io at the time of the choice.
The renderer is isolated in lsp-server/src/renderer.rs. Switching crates is
a one-file change.
Advertised in initialize:
text_document_sync = Full— clients re-send the whole document on change.code_action_provider = { kinds: [refactor.rewrite], resolve: false }.
Future expansions (e.g. executeCommand-based actions, codeAction/resolve
to defer rendering until the user actually accepts) are scoped out of the
current version intentionally — see docs/usage.md for the trade-off.