Spytial integration for Lean 4. Visualize Lean data structures as spatial diagrams directly in the VS Code infoview.
Add to your lakefile.lean:
require spytialLean from
git "https://github.com/sidprasad/spytial-lean" @ "v0.1.0"Then run:
lake update
lake buildPre-built artifacts are downloaded automatically from GitHub Releases. Node.js is NOT required.
- Lean 4 (v4.24.0)
- VS Code with the Lean 4 extension
If you want to build from source (e.g., for development), you will also need:
- Node.js (for building the widget JS)
git clone https://github.com/sidprasad/spytial-lean.git
cd spytial-lean
lake update
lake buildOpen Demo.lean and place your cursor on a #spytial line. The infoview panel will show the diagram.
Import SpytialLean and use #spytial with any term:
import SpytialLean
open SpytialLean
def myList : List Nat := [1, 2, 3]
#spytial myListThe relationalizer walks the expression, turning constructors into nodes and arguments into edges.
Pass a with [...] block to control how the diagram is laid out:
inductive Tree (α : Type) where
| leaf (value : α) : Tree α
| node (left : Tree α) (right : Tree α) : Tree α
def t := Tree.node (.leaf 1) (.node (.leaf 2) (.leaf 3))
#spytial t with [
.orientation (selector := "left") (directions := [.left, .below]),
.orientation (selector := "right") (directions := [.right, .below]),
.hideAtom (selector := "Nat")
]Selectors use the constructor parameter names you define. Named arguments like (left : Tree α) produce a relation called "left". Structure fields also use their declared names.
Use spytial_spec to attach a default layout to a type. Any #spytial call on a value of that type will use it automatically:
spytial_spec Tree [
.orientation (selector := "left") (directions := [.left, .below]),
.orientation (selector := "right") (directions := [.right, .below]),
.hideAtom (selector := "Nat")
]
-- Uses the attached spec automatically
#spytial tAn explicit with [...] overrides the attached spec.
The canonical Spytial example, matching the Python and Rust versions:
inductive Color where
| red | black
inductive RBNode where
| nil : RBNode
| node (color : Color) (key : Nat) (left : RBNode) (right : RBNode) : RBNode
spytial_spec RBNode [
.attribute (field := "key"),
.attribute (field := "color"),
.orientation (selector := "left") (directions := [.left, .below]),
.orientation (selector := "right") (directions := [.right, .below]),
.hideAtom (selector := "Color + Nat"),
.atomColor (selector := "{x : RBNode | @:(x.color) = red}") (value := "red"),
.atomColor (selector := "{x : RBNode | @:(x.color) = black}") (value := "black")
]
def myRBTree : RBNode :=
.node .black 10
(.node .red 5
(.node .black 3 .nil .nil)
(.node .black 7 .nil .nil))
(.node .red 15
(.node .black 12 .nil .nil)
(.node .black 20 .nil .nil))
#spytial myRBTreeUse #spytial.datum and #spytial.spec to inspect what the relationalizer and spec serializer produce:
-- See the JSON data instance (atoms + relations with their names)
#spytial.datum myTree
-- See the generated YAML spec
#spytial.spec myTree with [
.orientation (selector := "left") (directions := [.left, .below])
]Operations are constructors of SpytialOp. Pass them as a list to with [...] or spytial_spec.
| Operation | Description |
|---|---|
.orientation (selector) (directions) |
Position source above/below/left/right of target |
.align (selector) (direction) |
Align selected elements horizontally or vertically |
.cyclic (selector) (direction) |
Arrange elements in a circle (clockwise/counterclockwise) |
.group (selector) (name) |
Group selected elements with a bounding box |
.hideAtom (selector) |
Hide elements matching the selector |
.size (selector) (width) (height) |
Set node dimensions |
| Operation | Description |
|---|---|
.atomColor (selector) (value) |
Color nodes (any CSS color) |
.edgeColor (field) (value) |
Color edges for a relation |
.hideField (field) |
Hide all edges for a relation |
.attribute (field) |
Display a relation as a node label instead of an edge |
.icon (selector) (path) |
Set a custom icon on nodes |
.tag (toTag) (name) (value) |
Add computed attributes to nodes |
.inferredEdge (name) (selector) |
Add edges that don't exist in the data |
.flag (name) |
Set a boolean flag (e.g., hideDisconnected) |
Direction: .above, .below, .left, .right, .directlyAbove, .directlyBelow, .directlyLeft, .directlyRight
AlignDir: .horizontal, .vertical
RotationDir: .clockwise, .counterclockwise
EdgeStyle: .solid, .dashed, .dotted
-
Relationalizer (
SpytialLean/Relationalizer.lean) — Walks the LeanExprtree after WHNF reduction. Constructors become atoms (nodes), data arguments become relations (edges). Type and proof arguments are skipped. -
Spec (
SpytialLean/Spec.lean) — Typed Lean operations serialize to YAML for spytial-core's layout engine. -
Widget (
widget/src/spytialWidget.tsx) — A ProofWidgets4 widget module that loads spytial-core, generates a layout from the relational data + spec, and renders via thewebcola-cnd-graphweb component.
Relations are named after the constructor parameter names you define:
inductive Tree (α : Type) where
| leaf (value : α) : Tree α
| node (left : Tree α) (right : Tree α) : Tree αThis produces relations named value, left, right. Use #spytial.datum to see the exact names. Structure fields use their declared field names directly.
If constructor arguments are unnamed (positional style | node : Tree α → Tree α → Tree α), the relationalizer falls back to ctorName_index (e.g., node_0, node_1).
When constraints are unsatisfiable, the widget:
- Renders a counterfactual diagram using the Maximal Feasible Subset (MFS)
- Shows the Irreducible Infeasible Subsystem (IIS) — the minimal set of conflicting constraints
- Highlights related constraints on hover (bidirectional source/diagram cross-highlighting)
- Reports selector evaluation errors separately
This uses spytial-core's ErrorMessageModal component directly.
SpytialLean/
Types.lean -- JSON-serializable data instance types
Spec.lean -- SpytialOp, SpytialSpec, Direction, etc.
Relationalizer.lean -- Expr walker producing atoms + relations
Widget.lean -- ProofWidgets4 widget module registration
Attr.lean -- Environment extension for spytial_spec
Command.lean -- #spytial command and spytial_spec elaborators
widget/
src/spytialWidget.tsx -- React component rendering the diagram
rollup.config.js -- Bundles spytial-core into the widget
See DEVGUIDE.md for build details and development workflow.
Specs compose across Lean's structure hierarchy. If type B extends A, and A has a spytial_spec, then B inherits it automatically. If B also has its own spytial_spec, the two are composed — parent ops first, child ops appended:
structure Vehicle where
make : String
year : Nat
spytial_spec Vehicle [
.attribute (field := "make"),
.attribute (field := "year"),
.hideAtom (selector := "String + Nat")
]
structure ElectricCar extends Vehicle where
range : Nat
spytial_spec ElectricCar [
.attribute (field := "range"),
.atomColor (selector := "ElectricCar") (value := "#2196F3")
]
-- Effective spec = Vehicle's ops ++ ElectricCar's ops
#spytial myEVAn explicit with [...] still fully overrides the inherited spec.
- Better integration with Lean's tactic mode (
spytialtactic, panel widgets)