Introduction
Last verified against: LSP CLI accepts --stdio (vscode-languageclient); size-gated extracted analysis mode (spiceLsp.analysisMode / extractedByteThreshold); document formatting (textDocument/formatting + spice-lsp format); go-to-definition on .lib / .include; include/lib resolution; multi-dialect hover (default HSPICE); completion and connectivity still planned
spice-lsp is a language server and VS Code extension for SPICE circuit netlists. It gives you editor feedback while you write .cir, .sp, .spf, .inc, .lib, and related files — without running a simulator.
Use it when you want syntax and semantic checks, navigation, and dialect-aware help in the same place you edit the netlist.
What you get
| Capability | What it does |
|---|---|
| Diagnostics | Syntax errors plus warnings for duplicate names, unknown models/subcircuits, missing includes, and bad .lib sections |
| Outline | Document symbols for subcircuits, models, parameters, and instances |
| Go to definition / find references | Jump between .subckt / .model / .param definitions and their uses; also jump from .include / .lib paths (and .lib entry names) into the target file or section |
| Include and library resolution | Follow .include / .inc and HSPICE .lib 'file' entry so models and subcircuits in other files participate in checks and navigation |
| Hover | Dialect reference docs (HSPICE by default) plus file-local detail for subcircuit pins and in-file models |
| Dialect selection | Choose HSPICE, Ngspice, or LTspice (spiceLsp.dialect) so hover and related behavior match your simulator |
| Formatting | Columnar instance alignment, + wrap, directive keyword casing via Format Document or spice-lsp format |
The VS Code extension starts the spice-lsp binary over stdio. The same binary works with any LSP-capable editor.
Install and try it
VS Code: install SPICE Language Support from the Marketplace, open a netlist, and edit — diagnostics and navigation should appear without extra setup.
From source: see Getting Started for the pixi workflow (pixi install, pixi run build, pixi run spice-lsp).
Configure search paths for shared model libraries with spiceLsp.libraryPaths. Details: Include and library resolution.
Why spice-lsp
SPICE netlists are text, but most editors treat them as plain files. spice-lsp is built so that:
- Feedback stays in the editor — no simulator round-trip for common mistakes
- The core is editor-agnostic (LSP); VS Code is the first client
- Dialect differences are first-class — hover and settings follow HSPICE, Ngspice, or LTspice
- Include and
.libchains are part of analysis, matching how real decks are structured
Goals and non-goals: Principles. Known gaps: Limitations.
Documentation map
| If you want… | Read |
|---|---|
| Setup, first run, editor install | Getting Started |
| What the server implements | LSP Features |
| How crates and the pipeline fit together | Architecture |
.include / .lib behavior | Include and Library Resolution |
| Dialect docs and net semantics | Dialect Reference and Net Semantics |
| Per-dialect reference pages | Dialect reference catalog |
| Formatting rules and CLI | Formatter |
| Building from source / CI | Build |
| Extension layout and publishing | VS Code Integration |
Repository quick start: README.md.
Getting Started
This chapter covers environment setup and the shortest path from clone to a running language server.
Prerequisites
| Tool | Purpose |
|---|---|
| pixi | Manages Rust, Node (for the VS Code extension), and build tasks |
| Git | Clone and contribute |
You do not need a system-wide Rust install. Pixi provides the toolchain pinned in pixi.toml.
Clone and install
git clone https://github.com/amirhosseindavoody/spice-lsp.git
cd spice-lsp
pixi install
pixi install creates a reproducible environment with the Rust compiler, Node.js, and other dev tools.
Verify the environment
pixi run rustc --version
pixi run cargo --version
Both commands should succeed and report Rust ≥ 1.96.
Build and run
pixi run build
pixi run test
Format a netlist from the CLI:
pixi run format-spice -- test-data/valid/simple-rc.cir
pixi run format-spice -- --check test-data/valid/simple-rc.cir
Run the language server directly (it communicates over stdio — it will appear to hang; that is normal):
pixi run spice-lsp
# Equivalent; accepted because vscode-languageclient passes --stdio:
pixi run cargo run -p spice-lsp -- --stdio
Press Ctrl+C to stop. In an editor, use Format Document once the LSP is connected.
Open sample netlists
In VS Code: run SPICE LSP: Create Demo Folder from the Command Palette. It creates spice-lsp-demo/ in your opened workspace with HSPICE .sp / .lib files for same-file and cross-file go-to-definition (and sets the dialect to HSPICE).
By hand: create or copy a minimal netlist for manual testing:
* demo.cir — Ngspice-style
.title Simple RC
R1 in out 1k
C1 out 0 1u
V1 in 0 DC 1
.tran 1u 1m
.end
Save as demo.cir in the repo root or under test-data/.
Editor integration
VS Code (primary target)
From the Marketplace: install SPICE Language Support, open a .cir (or related) file, and edit.
From source (Extension Development Host):
- Build the LSP binary:
pixi run build - Open the extension folder:
editors/vscode - Install JS dependencies:
npm install - Press F5 to launch an Extension Development Host with the SPICE extension loaded
- Open
demo.cirand confirm diagnostics appear
Full extension setup: VS Code integration.
Other editors
Any editor with generic LSP client support can point at the spice-lsp binary:
| Editor | Configuration |
|---|---|
| Neovim | lspconfig custom server block with cmd = { "spice-lsp" } |
| Helix | [language-server.spice-lsp] in languages.toml |
| Zed | Extension or lsp settings (once published) |
File extensions to associate: .cir, .sp, .spf, .net, .ckt, .inc, .lib (dialect-dependent).
Recommended first contribution path
If you are new to the repo, follow this order:
- Read Principles — know what is in and out of scope
- Use Demo and testing — verify each layer before adding features
- Read Architecture — understand where new code belongs
- Skim Dialect reference and net semantics — hover corpus and connectivity plans
- Skim Include and library resolution — cross-file model/subckt resolution
Next steps
- Architecture — crate layout and data flow
- Build — pixi tasks and CI
- Demo and testing — smoke and integration checks
Principles
Goals, non-goals, and UX values for spice-lsp.
What good looks like
A developer editing a netlist in VS Code should get:
- Immediate syntax and semantic feedback — parse errors, duplicate names, unknown models
- Jump to definitions and a useful outline — subcircuits, models, parameters
- Dialect-aware documentation on hover — curated reference plus file-local pin/model detail
- Include-aware analysis —
.include/.libparticipate in checks and navigation - Consistent formatting (shipped) and completion (planned) — align netlists; suggest elements/directives later
- Connectivity warnings (planned) — dangling nodes and floating nets before simulation
Details on reference hover and connectivity: Dialect reference and net semantics.
Goals
- Fast feedback while editing — Diagnostics feel instant on typical netlists (< 5k lines). Tree-sitter incremental parsing is the foundation.
- Works offline — Single static binary; no cloud services; no simulator required for IDE features.
- Dialect-aware, corpus-driven docs — Ngspice, LTspice, and HSPICE differ. Hover (and later completion) documentation come from a curated reference library maintained per dialect, not hard-coded strings scattered in Rust.
- Catch connectivity mistakes before simulation — Flag dangling nodes and floating nets as warnings when analysis is confident enough.
- Editor-agnostic core — All language logic lives in the LSP binary. VS Code is the first client, not the only one.
- Testable at every layer — Parser fixtures, reference schema tests, hover snapshots, and LSP integration tests in CI.
Non-goals
| Non-goal | Why |
|---|---|
| Running SPICE simulations | Use Ngspice/LTspice externally |
| Schematic capture | Netlist text only |
| Auto-generating reference from PDF manuals | You author reference/ deliberately; quality over coverage |
| Full ERC/DRC | Floating-net checks are heuristic helpers, not sign-off tools |
| Replacing simulator errors | We front-load syntax and common semantic mistakes |
UX values
- Actionable squiggles — Clear message, stable range, stable diagnostic code (e.g.
spice/floating-net). - Graceful partial files — Incomplete subcircuits during editing must not block analysis of the rest of the buffer.
- Respect line continuations — The
+character is first-class in the grammar; HSPICE.DATAvalue rows may also continue without+. - Documentation you trust — Reference hover reads like a concise manual entry: syntax, units, examples. Missing entries show nothing rather than wrong text.
- Warn, don’t nag — Connectivity warnings are severity
Warning, configurable, and scoped to reduce false positives on intentional open nodes. - Low configuration — Sensible defaults; dialect and diagnostics toggles via settings when needed.
Success criteria
pixi run testpasses parser and LSP integration tests- Invalid netlist in the editor shows a syntax diagnostic; fixing it clears the diagnostic without restart
- Go to definition reaches
.model/.subcktacross.include/.libwhen paths resolve, and jumps from include/lib paths (and.libentry names) into the target file or section - Hover on a documented directive shows dialect reference text for the active dialect
- A contributor can follow Demo and testing and reproduce the smoke demo
Architecture
System layout for spice-lsp: crates, data flow, and how analysis layers build on each other.
Story in four layers
Every feature belongs to one of these layers:
| Layer | Responsibility | Status |
|---|---|---|
| 1. Parse | Tree-sitter CST, syntax diagnostics | Shipped |
| 2. Index | Symbols, scopes, cross-references, include/lib graph | Shipped |
| 3. Assist | Hover (reference + file-local); completion | Hover shipped; completion planned |
| 4. Deep semantics | Formatter; net connectivity | Formatter shipped; connectivity planned |
Layer 4 and the reference corpus are documented in Dialect reference and net semantics.
High-level overview
┌─────────────────────────────────────────────────────────────────┐
│ Editor clients │
│ VS Code extension │ Neovim │ Helix │ other LSP clients │
└────────────┬────────────────────────────────────────────────────┘
│ JSON-RPC 2.0 over stdio (LSP)
▼
┌─────────────────────────────────────────────────────────────────┐
│ crates/spice-lsp (binary: spice-lsp) │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ tower-lsp Backend │ │
│ │ • text sync, publishDiagnostics │ │
│ │ • symbols, definition, references │ │
│ │ • hover (reference corpus + file-local) │ │
│ │ • formatting (`format_source` → TextEdit) │ │
│ │ • (planned) completion │ │
│ └────────────────────────┬─────────────────────────────────┘ │
└───────────────────────────┼─────────────────────────────────────┘
│
┌─────────────────┼─────────────────┐
▼ ▼ ▼
┌─────────────────┐ ┌────────────────┐ ┌──────────────────┐
│ spice-parser │ │ spice-reference│ │ tree-sitter-spice│
│ parse, index, │ │ dialect docs │ │ grammar, queries │
│ diagnose, format│ │ │ │ │
└─────────────────┘ └────────────────┘ └──────────────────┘
Crate responsibilities
| Crate / directory | Role |
|---|---|
crates/spice-lsp | LSP server, JSON-RPC, document store; format CLI subcommand |
crates/spice-parser | Parsing, symbol index, diagnostics, formatter (format_source) |
crates/spice-reference | Load and query dialect reference entries |
tree-sitter-spice/ | Grammar and query files |
reference/ | Curated JSON per dialect — authored over time |
editors/vscode/ | VS Code extension client |
test-data/ | Fixtures for syntax, semantics, hover snapshots |
LSP server lifecycle
- Client connects via stdio; sends
initializewith client capabilities and dialect option. - Server responds with capabilities (incremental sync, diagnostics, symbols, definition, references, hover, formatting).
- Document open/change updates an in-memory map of open buffers.
- On each change (debounced ~150 ms):
- Re-parse with Tree-sitter
- Run diagnostic passes (syntax + semantic + include resolution)
- Send
textDocument/publishDiagnosticswith the document version
- Hover resolves against the CST and
spice-reference. Navigation requests re-analyze on demand so the symbol index stays current even when diagnostics are still debouncing. - Formatting pretty-prints the buffer via
spice_parser::format_sourceand returns a full-documentTextEditwhen needed. - Shutdown exits cleanly.
Document model
#![allow(unused)]
fn main() {
struct Document {
uri: Url,
text: String,
tree: tree_sitter::Tree,
version: i32,
symbols: SymbolTable,
// planned
net_graph: Option<NetGraph>,
}
}
Parser and analysis pipeline
Syntax
- Parse buffer → CST
- Collect ERROR / MISSING nodes and hand-written checks (e.g. unclosed
.subckt) - Map to LSP
Diagnostic(Error)
Symbol index
Walk the CST to build:
- Subcircuit and model definitions
- Component instances and
.parambindings
Enables navigation, duplicate-name warnings, and undefined reference checks.
Include / library graph
Follow .include / .inc and HSPICE .lib 'file' entry (section-filtered) to merge external model and subcircuit definitions. Used by unknown-model diagnostics and go-to-definition. Details: Include and library resolution.
Assist
Use the symbol index and reference corpus for hover (subcircuit pin lists, in-file .model parameters, curated directive/element docs). Completion will reuse the same index and corpus.
Format and dialect
Dialect setting selects reference namespace and (later) grammar quirks / formatter profiles. The formatter pretty-prints line tokens (column alignment, + wrap, directive casing) and returns a full-document TextEdit — see Formatter.
Reference docs and connectivity
Reference lookup (shipped): Map cursor token → reference/<dialect>/… entry → markdown hover.
Net graph (planned): Build terminal graph per scope → warn on dangling nodes and floating nets.
Instance lines ──► NetGraph ──► dangling / floating diagnostics
Cursor token ──► ReferenceIndex ──► rich hover markdown
See Dialect reference and net semantics.
VS Code extension
Thin Node client: spawns spice-lsp, forwards LSP traffic, exposes dialect and diagnostic settings. No parsing in TypeScript.
See VS Code integration.
Performance targets
| Metric | Target |
|---|---|
| Parse + syntax diagnose (5k lines) | < 50 ms |
| Full semantic pass + net graph (50k lines) | < 100 ms |
| Reference hover lookup | < 1 ms (in-memory index) |
| Incremental edit | Re-parse changed regions only |
Buffers at or above spiceLsp.extractedByteThreshold (default 16 MiB) use extracted analysis: definitions-only indexing without per-instance symbols. See LSP features and Large-file / extracted mode.
Related reading
- Dialect reference and net semantics
- LSP features — method-by-method status
- Demo and testing — verification
- Design (internal) — full requirements
LSP Features
LSP methods spice-lsp implements today, plus a short note on planned work.
Capability matrix
| LSP method / feature | Status |
|---|---|
initialize / initialized | Shipped |
shutdown / exit | Shipped |
textDocument/didOpen / didChange / didClose | Shipped |
textDocument/publishDiagnostics | Shipped |
| Syntax diagnostics | Shipped |
| Duplicate / undefined symbol diagnostics | Shipped |
Include / .lib resolution diagnostics | Shipped |
textDocument/documentSymbol | Shipped |
textDocument/definition / references | Shipped |
textDocument/hover (dialect reference + file-local) | Shipped |
| Dangling node / floating net diagnostics | Planned |
textDocument/formatting / rangeFormatting | Shipped |
textDocument/completion | Planned |
textDocument/didSave (re-lint) | Planned |
Full specification of reference hover and net diagnostics: Dialect reference and net semantics.
Server capabilities
{
"capabilities": {
"textDocumentSync": {
"openClose": true,
"change": 2,
"save": false
},
"documentSymbolProvider": true,
"definitionProvider": true,
"referencesProvider": true,
"hoverProvider": true,
"documentFormattingProvider": true,
"documentRangeFormattingProvider": true
},
"serverInfo": { "name": "spice-lsp", "version": "0.1.0" }
}
change: 2 is Incremental sync — the client sends edit ranges, not the full buffer every keystroke.
Diagnostics arrive via server-initiated textDocument/publishDiagnostics.
Syntax diagnostics
| Source | Example | Severity |
|---|---|---|
Unclosed .subckt | missing .ends for subcircuit X | Error |
| Parse ERROR node | unexpected token | Error |
| Missing CST child | expected node list | Error |
Symbols and navigation
Document symbols for outline / breadcrumbs:
| Symbol kind | SPICE construct |
|---|---|
Namespace | .subckt block |
Class | .model |
Variable | .param |
Field | Instance line (full analysis only) |
Navigation: go to definition and find references for subcircuits, models, and parameters. textDocument/references honors context.includeDeclaration (omit definition sites when the client passes false).
Include / library resolution: .include / .inc and HSPICE .lib 'file' entry are followed so model and subcircuit definitions in those files participate in unknown-model checks and go-to-definition. On a .lib 'file' entry (or .include path) line, go to definition on the path opens that file; on the entry name it jumps to the matching .lib entry section header. See Include and library resolution.
Large-file / extracted analysis
| Setting | Default | Effect |
|---|---|---|
spiceLsp.analysisMode | auto | auto / full / extracted |
spiceLsp.extractedByteThreshold | 16777216 (16 MiB) | Size gate for auto |
In extracted mode (forced, or auto when the buffer reaches the threshold):
- Index keeps
.subckt/.model/.paramdefinitions - Instance symbols and outline children are omitted
spice/duplicate-nameis not emitted- Unknown-model still reports unique missing model/subckt names (sparse refs)
- Go to definition on an instance’s model/subckt token still works via line classification
Design detail: Large-file / extracted mode.
Semantic diagnostics
| Code | Example | Severity |
|---|---|---|
spice/duplicate-name | duplicate component name 'R1' | Warning |
spice/unknown-model | model 'nfet' not defined | Warning |
spice/include-not-found | include file not found: 'models.inc' | Warning |
spice/lib-section-not-found | library section 'TT' not found | Warning |
spice/include-cycle | include cycle involving '…' | Warning |
Diagnostics from didChange are debounced (~150 ms) so rapid typing does not re-analyze on every keystroke. didOpen publishes immediately. Navigation handlers refresh the in-memory index on demand.
Dialect selection
spiceLsp.dialect is hspice | ngspice | ltspice (default hspice). The VS Code command SPICE LSP: Set Dialect… and a status-bar item change it. The same dialect selects the reference corpus for hover and (later) completion docs.
Design: Multi-dialect support.
Hover
textDocument/hover resolves in order:
- Curated entry from
reference/for the active dialect (_sharedfallback) - File-local detail for
.subckt/.model/.paramsymbols - No hover
When the cursor is on a directive, option, element keyword, or documented expression form, the server loads the matching entry and returns markdown (summary, syntax, parameter table, examples). You maintain this corpus over time; the LSP indexes and renders it. Authoring guide: Dialect reference and net semantics.
Formatting
textDocument/formatting and textDocument/rangeFormatting return a full-document TextEdit when the buffer would change. Range formatting uses the same full-document pass so instance alignment groups stay consistent. LSP tabSize maps to continuation indentWidth; output always uses spaces.
Rules, CLI (spice-lsp format), and options: Formatter.
Planned
Completion
Element letters, directive names, in-scope model and subcircuit names, snippet templates for .tran / .subckt. Documentation can attach the same reference entries used for hover.
Connectivity diagnostics
| Code | Example | Severity |
|---|---|---|
spice/dangling-node | node 'bias' is connected to only one device terminal | Warning |
spice/floating-net | net 'internal' has no DC path to ground | Warning |
Published alongside other diagnostics in publishDiagnostics. Configurable via spiceLsp.diagnostics.* settings.
Client configuration
| Setting | Type | Default | Notes |
|---|---|---|---|
spiceLsp.dialect | string | "hspice" | Dialect switch |
spiceLsp.libraryPaths | string[] | [] | Include / .lib search path |
spiceLsp.include.maxDepth | number | 16 | Nested include / .lib depth cap |
spiceLsp.diagnostics.danglingNodes | boolean | true | Planned connectivity pass |
spiceLsp.diagnostics.floatingNets | boolean | true | Planned connectivity pass |
spiceLsp.groundNodes | string[] | ["0","gnd","GND"] | Planned connectivity pass |
spiceLsp.trace.server | string | "off" | LSP trace level |
Testing
Integration coverage includes:
initializereturns expected capabilities- Open invalid document → diagnostics notification
- Edit → updated diagnostics (debounced)
documentSymbolreturns hierarchical outline for.subcktblocksdefinitionon subcircuit reference jumps to.subcktdefinitiondefinitionon.lib 'file' entrypath opens the library file; on the entry name jumps to.lib entryreferenceson subcircuit definition lists definition + usages;includeDeclaration: falseomits the definition- Semantic fixtures (
duplicate-instance.cir,unknown-subckt.cir) produce warning codes - Hover snapshots match reference entries for the active dialect
Planned: semantic fixtures for dangling / floating warnings once connectivity lands.
See Demo and testing.
Formatter
SPICE netlist formatter: columnar instance alignment, + continuation wrapping, and directive keyword casing.
Goals
- Columnar alignment of instance name, nodes, model/value, and parameters
- Consistent handling of
+continuation lines - Normalized directive keyword casing (configurable)
- Idempotent output:
format(format(x)) == format(x)
Input / output
| Input | Output |
|---|---|
LSP textDocument/formatting | TextEdit[] (full-document replacement when changed) |
LSP textDocument/rangeFormatting | Same as full-document formatting (alignment stays consistent) |
CLI spice-lsp format [--check|--write] file.cir… | stdout / in-place write / exit 1 when --check would change a file |
Formatting rules
Instance lines
Align columns within a contiguous block of instance lines:
* before
R1 in out 1k
C1 out 0 1u
X1 a b mycell
* after (aligned)
R1 in out 1k
C1 out 0 1u
X1 a b mycell
Column widths come from the widest field in each column. Blank lines, comments, and directives break an alignment block.
Continuation lines
Logical statements fold + continuations into one token stream, then soft-wrap at maxLineWidth with a fixed indent after +:
* before
M1 d g s b nfet W=10u L=0.18u AS=1e-12 AD=1e-12 PS=1u PD=1u
* after (maxLineWidth=40)
M1 d g s b nfet W=10u L=0.18u AS=1e-12
+ AD=1e-12 PS=1u PD=1u
Directives
Dot-directives stay on their own lines. The leading keyword is cased per keywordCase (default upper); other tokens keep their spelling:
.TRAN 1u 1m
.SUBCKT buffer in out
.MODEL nfet nmos ( LEVEL=1 )
Comments
- Preserve
*,;, and$full-line comments (trim trailing whitespace only) - Normalize spacing before inline
;comments (; …) - Do not treat
$as an inline comment delimiter (HSPICE$paramtokens stay intact) - Do not reorder or remove comment lines
Architecture
The formatter lives in crates/spice-parser (format_source):
- Split the buffer into physical lines and group
+continuations into statements - For contiguous instance blocks, compute per-column widths
- Pretty-print tokens (wrap at
maxLineWidth) - LSP maps old vs new text to a full-document
TextEdit
Formatting does not mutate the parse tree; it is a pure text pretty-printer. Tree-sitter line kinds inform classification but field columns are tokenized from the line text.
Configuration
| Option | Values | Default |
|---|---|---|
indentWidth | positive integer | 2 (LSP tabSize when > 0) |
keywordCase | upper, lower, preserve | upper |
alignColumns | true, false | true |
maxLineWidth | number | 120 (soft wrap with +) |
CLI and LSP use these defaults today. Dialect-specific formatter profiles are not shipped yet.
CLI
pixi run format-spice -- file.cir # print formatted text
pixi run format-spice -- --write file.cir # rewrite in place
pixi run format-spice -- --check file.cir # exit 1 if not formatted
Equivalent direct invocation: cargo run -p spice-lsp -- format ….
Testing
Golden-file tests in crates/spice-parser/tests/format/:
input.cir → format → compare to expected.cir
Cases cover instance alignment, directives, comments, and wrap. Each case also asserts idempotence. LSP stdio tests advertise documentFormattingProvider and check a formatting round-trip.
Related
- Architecture — FormatterEngine placement
- LSP features —
textDocument/formattingcapability
Limitations
Known constraints and unsupported behavior. Updated as the parser and LSP mature.
Shipped today
- Rust crates (
spice-parser,spice-lsp,spice-reference), Tree-sitter grammar, and VS Code extension - Syntax diagnostics plus semantic warnings (
spice/duplicate-name,spice/unknown-model, include/lib path issues) - Document outline, go to definition, and find references
- Dialect-aware hover from the curated
reference/corpus (default HSPICE) plus file-local pin/model detail .include/.libresolution for model and subcircuit definitions- Document formatting (
textDocument/formatting) andspice-lsp formatCLI - Debounced diagnostics on edit;
textDocument/referenceshonorsincludeDeclaration - Marketplace extension with bundled binaries, TextMate highlighting, and restart command
- File associations for
.cir,.sp,.spf,.net,.ckt,.inc, and.lib
Current limitations
| Limitation | Workaround |
|---|---|
| Shared grammar for all dialects | Prefer common SPICE constructs; dialect-specific parse quirks grow over time; hover/docs already switch |
| No connectivity analysis | Manual review until dangling/floating checks land |
| Include graph is definition-focused | .include / .lib resolve models and subcircuits for diagnostics and go-to-definition; outline and find-references stay file-local — see Include and library resolution |
| No completion yet | Type element/directive names manually |
| Formatter has no dialect profiles yet | Shared alignment/casing rules for all dialects; see Formatter |
Comment toggle uses * only | ; and $ are highlighted as comments; VS Code allows one lineComment |
| No Windows arm64 bundled binary | Set spiceLsp.serverPath or put spice-lsp on PATH |
| Linux bundled binary needs glibc 2.31+ | Upgrade the host OS, or build spice-lsp locally and set spiceLsp.serverPath |
Bare numeric lines outside .DATA | Prefer + continuations or keep value rows inside .DATA … .ENDDATA |
Dialect reference coverage
The reference library under reference/ grows incrementally:
- Shared baseline covers common directives (
.subckt,.tran,.dc,.op,.ac, …) and elements (R,C,X) - HSPICE overlays expand analysis/control docs (
.data, multi-mode.dc,.op,.measure,.probe,.lib, …) — see Dialect reference catalog - LTspice remains a stub corpus; missing entry → no hover (not an error)
- Reference describes language constructs, not simulator version release notes
See Dialect reference and net semantics.
Connectivity analysis (planned)
Dangling-node and floating-net diagnostics will be heuristic:
| Limitation | Detail |
|---|---|
| Single file | Net connectivity still ignores .include until a full cross-file net graph exists |
| Ground aliases | Defaults to 0, gnd, GND; exotic ground names may need config |
| False positives | Intentionally open probe points may warn until suppression exists |
| Not full ERC | Does not check layout, EM, or foundry rules |
| Ideal elements | Voltage sources and unusual topologies need careful graph rules |
These warnings supplement — not replace — simulator and layout review.
Dialect differences
| Area | Ngspice | LTspice | HSPICE |
|---|---|---|---|
| Comments | *, ;, $ | $ common | * |
| Directives / options | Baseline corpus | Overrides in reference/ltspice/ | Overrides in reference/hspice/ |
Parsing is still largely Ngspice-oriented; reference namespaces already switch with spiceLsp.dialect.
Parser robustness
- Error recovery may leave incomplete indexes until syntax is fixed
- Very large files still re-parse the full buffer after the debounce window (Tree-sitter incremental reuse is not wired yet)
- Large / extracted netlists use extracted analysis mode above
spiceLsp.extractedByteThreshold(default 16 MiB, or whenspiceLsp.analysisModeisextracted): definitions-only indexing, no instance outline/symbols, no duplicate-name scan — see Large-file / extracted mode - LSP assumes UTF-8 source
Editor / LSP
- UTF-16 positions per LSP spec
- Stdio transport only
- No workspace-wide symbol search yet (include graph is used for definitions, not
workspace/symbol) - Diagnostics on
didChangeare debounced (~150 ms); navigation requests re-analyze on demand so the index stays current
Reporting issues
Include:
- Minimal netlist snippet
- Dialect (Ngspice / LTspice / HSPICE)
- Expected vs actual diagnostic or hover text
Add a fixture under test-data/ when fixing.
Dialect Reference and Net Semantics
Two related capabilities: a curated dialect reference the LSP consults for documentation on hover, and net connectivity analysis that flags floating nets and dangling nodes. This chapter is the single source of truth for both; other pages link here rather than repeating detail.
Reference-powered hover is shipped. Connectivity analysis is planned.
Part 1 — Dialect reference library
Purpose
SPICE dialects differ in directives (.tran, .option), device syntax, and parameter names. Generic hover text is not enough. spice-lsp ships with — and grows — a reference corpus you maintain: structured descriptions of commands, options, element types, and common expressions per dialect.
The LSP does not scrape simulator manuals at runtime. It looks up entries from checked-in reference data selected by the active dialect.
What users see
| Cursor on… | Hover shows (from reference) |
|---|---|
.tran | Syntax, parameters, units, dialect notes |
.option keyword | Meaning, default, valid values |
M (MOSFET line) | Terminal order, common parameters |
{expression} in .param | Allowed functions, unit conventions |
Completion (when implemented) can attach the same entries as documentation on completion items.
Repository layout
reference/
├── schema.json # JSON Schema for reference entries
├── ngspice/
│ ├── directives/
│ │ ├── tran.json
│ │ ├── ac.json
│ │ └── option.json
│ ├── elements/
│ │ ├── R.json
│ │ └── M.json
│ └── expressions.json # shared {…} expression helpers
├── ltspice/
│ └── … # LTspice-specific overrides and additions
└── hspice/
└── …
Author HSPICE and Ngspice first (HSPICE is the extension default — see Multi-dialect design); add LTspice as the corpus grows. Entries can override or extend _shared/ where dialects agree.
Entry format (draft)
Each file describes one construct. Example reference/ngspice/directives/tran.json:
{
"id": "ngspice.directive.tran",
"kind": "directive",
"name": ".tran",
"summary": "Transient analysis",
"syntax": ".tran Tstep Tstop [Tstart [Tmax]] [UIC]",
"parameters": [
{ "name": "Tstep", "description": "Suggested printing increment.", "units": "seconds" },
{ "name": "Tstop", "description": "Final time.", "units": "seconds" }
],
"examples": [".tran 1n 100n", ".tran 1u 1m 0 10u UIC"],
"seeAlso": ["ngspice.directive.options"],
"dialect": "ngspice"
}
The Rust crate spice-reference loads and indexes entries by (dialect, kind, name).
LSP integration
- Client sends active dialect via
initializationOptionsorspiceLsp.dialectsetting (defaulthspice; command + status bar to switch — design). - On
textDocument/hover, the server maps the cursor CST node to a reference key (e.g. directive name, element letter, option token). - Server renders
Hovermarkdown from the entry: summary, syntax block, parameter table, examples. - Missing entry → no hover (or a one-line fallback from the parse tree). Gaps are filled by adding reference files, not hard-coding strings in Rust.
Authoring workflow
Reference content is your ongoing work, independent of parser releases:
- Add or edit JSON under
reference/<dialect>/orreference/_shared/. - Run
pixi run reference-validateto load and exercise the embedded corpus. - Run
pixi run reference-docsto regenerate the Dialect reference catalog. - Add or update hover snapshot tests when behavior changes.
- Ship with the binary (corpus is embedded at compile time via the
spice-referencebuild script).
Prefer small, focused files over one giant manual. Link related entries with seeAlso.
Coverage status
| Area | Scope |
|---|---|
| Shipped | Inline hover from CST + curated reference/ lookup (HSPICE overlays for .data / .dc / .op and common controls; Ngspice baseline) |
| Growing | Broader dialect coverage; LTspice / remaining HSPICE constructs added incrementally |
Part 2 — Net connectivity analysis
Purpose
Syntax-correct netlists can still fail simulation because a node is dangling (only one connection) or floating (no DC path to ground). spice-lsp will analyze connectivity from parsed instance lines and surface these as semantic diagnostics (typically warnings).
This is not ERC/DRC and does not replace the simulator — it catches common mistakes early.
Definitions
| Term | Meaning | Example |
|---|---|---|
| Ground | Node 0, gnd, GND, or dialect-specific ground aliases | C1 out 0 1u |
| Dangling node | Appears on exactly one device terminal in the analyzed scope | Net bias only on R1 in bias with nothing on bias elsewhere |
| Floating net | Has connections but no DC path to ground through R, L, V, I, or defined grounds | Unconnected island of R–C with no voltage source or ground tie |
Exact rules are dialect-aware (e.g. which nodes count as ground, whether V with both nodes internal creates a path). Document rules per dialect in the analyzer and test with fixtures.
Architecture
CST instance lines
│
▼
┌──────────────────┐
│ NetGraph builder │ nodes ↔ terminals, per .subckt scope
└────────┬─────────┘
│
▼
┌──────────────────┐
│ Connectivity │ dangling: degree == 1 (exclude intentional probes)
│ passes │ floating: no path to ground node set
└────────┬─────────┘
│
▼
Vec<Diagnostic> → publishDiagnostics (Warning)
Build the graph per scope: top level and inside each .subckt separately. Subcircuit ports are connections to the parent scope, not isolated graphs.
Diagnostic examples
| Code | Message | Severity |
|---|---|---|
spice/dangling-node | node 'bias' is connected to only one device terminal | Warning |
spice/floating-net | net 'internal' has no DC path to ground | Warning |
Attach diagnostics to the node token on the instance line when possible. Offer a single diagnostic per net, not one per terminal.
Configuration (planned)
| Setting | Default | Effect |
|---|---|---|
spiceLsp.diagnostics.danglingNodes | true | Enable dangling-node pass |
spiceLsp.diagnostics.floatingNets | true | Enable floating-net pass |
spiceLsp.groundNodes | ["0", "gnd", "GND"] | Treat as ground for path search |
Limitations
- Ignores nets from
.includefiles until a cross-file net graph exists (model/subckt include resolution is separate — see Include and library resolution) - Ideal voltage sources and shorted nodes need special handling
- Intentionally open probes may false-positive — allow suppress comments or config later
Status
| Scope | Status |
|---|---|
| Duplicate instance names | Shipped (separate from connectivity) |
| Dangling nodes and floating nets (single-file) | Planned |
Testing
| Layer | Test |
|---|---|
| Reference | Schema validation on all reference/**/*.json |
| Reference | Hover snapshot: fixture + cursor → markdown |
| Connectivity | test-data/semantic/dangling-node.cir → one spice/dangling-node |
| Connectivity | test-data/semantic/floating-net.cir → one spice/floating-net |
| LSP | Integration test publishes warnings after open |
See Demo and testing.
Related
- Architecture — crate layout and analysis layers
- LSP features — capability matrix
- Limitations — what analysis does not cover
- Design (internal) — requirements spec
Include and library resolution
How spice-lsp resolves .model, .subckt, and related symbols across .include / .inc and HSPICE .lib files.
Goals
| Capability | Behavior |
|---|---|
Follow .include / .inc | Load the target file and merge its model/subcircuit definitions into resolution |
Follow .lib 'file' entry | Load only the named .LIB entry … .ENDL section from the library file |
| Unknown model/subckt | spice/unknown-model is suppressed when the name is defined in a reachable include or lib section |
| Go to definition (symbol) | Jumps to the defining .model / .subckt in the included or library file |
| Go to definition (path / entry) | Cursor on an include/lib path opens that file; cursor on a .lib entry name jumps to the .lib entry section header |
| Missing path | spice/include-not-found (or spice/lib-section-not-found) on the include/lib directive |
Outline (documentSymbol) stays file-local. Find references stays in the open buffer today (cross-file references may expand later).
Directive shapes
| Form | Meaning |
|---|---|
.include path / .inc path | Insert the whole file |
.lib 'path' entry | Call the named section in a library file (HSPICE-style) |
.lib entry … .endl | Section delimiters inside a library file (not a file call) |
Paths may be single-quoted, double-quoted, or bare. Relative paths resolve against the including file’s directory, then against spiceLsp.libraryPaths.
Resolution algorithm
analyze(root)
→ parse + local Index + IncludeRef list
→ for each IncludeRef (depth-limited, cycle-safe):
resolve path (relative → libraryPaths → fail)
load text (open buffer if present, else disk)
if LibCall: keep only lines inside matching .LIB entry … .ENDL
else: use full file
build Index for that slice
recurse into nested includes
→ merge external definitions
→ drop spice/unknown-model when name exists in merge
→ emit spice/include-not-found / spice/lib-section-not-found
Default max nesting depth is 16 (aligned with common HSPICE nested-.LIB limits).
Search path
- Absolute path as written
- Relative to the directory of the file that contains the
.include/.libcall - Each entry in
spiceLsp.libraryPaths(workspace or absolute folders)
LSP integration
| Request | Cross-file behavior |
|---|---|
publishDiagnostics | Uses include graph when publishing for an open document |
textDocument/definition | May return a Location in another file URI (model/subckt, include/lib path, or .lib entry section) |
textDocument/references | Same-buffer only |
textDocument/documentSymbol | Same-buffer only |
textDocument/hover | File-local symbols still prefer the open buffer; dialect corpus unchanged |
Open buffers win over disk content when the resolved path matches an open document (so edits to an included file are visible before save).
Configuration
| Setting | Type | Default | Purpose |
|---|---|---|---|
spiceLsp.libraryPaths | string[] | [] | Extra directories for resolving include/lib paths |
spiceLsp.include.maxDepth | number | 16 | Cap on nested include/lib depth |
Diagnostics
| Code | When |
|---|---|
spice/include-not-found | Path does not resolve under search rules |
spice/lib-section-not-found | File loads but the requested .LIB entry is missing |
spice/include-cycle | Include/lib graph would revisit a file already on the stack |
spice/unknown-model | Model/subckt still missing after the include closure is merged |
Limits
- No workspace-wide
workspace/symbolyet - No automatic PDK discovery beyond
libraryPaths - LTspice / Ngspice
.libquirks beyond the HSPICE call/section pattern are best-effort - Nested
.libsection selection inside an already-filtered section follows nested includes normally
Dialect reference catalog
This section is generated from the curated JSON corpus under reference/.
The language server embeds the same data for hover (and later completion docs).
How to update
- Edit or add JSON under
reference/_shared/orreference/<dialect>/. - Run
pixi run reference-validate. - Run
pixi run reference-docsto regenerate these pages. - CI runs
pixi run reference-docs-checkso the book stays in sync.
Design notes: Multi-dialect support.
Dialects
| Dialect | Setting value | Entries (effective) | Catalog |
|---|---|---|---|
| Shared base | — | 16 | Shared |
| HSPICE | hspice | 25 | HSPICE |
| Ngspice | ngspice | 16 | Ngspice |
| LTspice | ltspice | 16 | LTspice |
Embedded corpus size: 37 raw JSON entries (shared + overlays).
Shared reference catalog
Constructs common to all dialects. Dialect pages overlay these when a matching (kind, name) exists.
Source of truth: reference/_shared/.
Index
| Name | Kind | Summary |
|---|---|---|
.ac | directive | Small-signal AC frequency analysis |
.dc | directive | DC sweep analysis |
.end | directive | End of netlist |
.ends | directive | End a subcircuit definition |
.ic | directive | Set initial node voltages |
.include | directive | Include another netlist file |
.model | directive | Define a device model |
.op | directive | DC operating-point analysis |
.param | directive | Define a named parameter |
.print | directive | Print analysis results as a table |
.subckt | directive | Begin a subcircuit definition |
.temp | directive | Set circuit temperature(s) |
.tran | directive | Transient analysis |
C | element | Capacitor |
R | element | Resistor |
X | element | Subcircuit instance |
Directives
.ac
shared.directive.ac — Small-signal AC frequency analysis
.ac {LIN|DEC|OCT} np fstart fstop
| Parameter | Description | Units |
|---|---|---|
| `LIN | DEC | OCT` |
np | Number of points (LIN) or points per decade/octave (DEC/OCT). | |
fstart | Start frequency. | Hz |
fstop | Stop frequency. | Hz |
Examples
.ac DEC 10 1k 1G.ac LIN 100 1 1Meg
.dc
shared.directive.dc — DC sweep analysis
.dc srcname start stop step
| Parameter | Description | Units |
|---|---|---|
srcname | Independent source, parameter, or TEMP to sweep. | |
start | Sweep start value. | |
stop | Sweep stop value. | |
step | Increment between points. |
Examples
.dc V1 0 5 0.1.dc TEMP 0 100 25
.end
shared.directive.end — End of netlist
.end [comment]
| Parameter | Description | Units |
|---|---|---|
comment | Optional trailing comment. |
Examples
.end.end my_circuit
.ends
shared.directive.ends — End a subcircuit definition
.ends [name]
| Parameter | Description | Units |
|---|---|---|
name | Optional subcircuit name; should match .subckt. |
Examples
.ends.ends buffer
See also: shared.directive.subckt
.ic
shared.directive.ic — Set initial node voltages
.ic V(node)=value [V(node2)=value2 ...]
| Parameter | Description | Units |
|---|---|---|
V(node) | Node voltage to initialize. | V |
Examples
.ic V(out)=0.ic V(in)=1.8 V(out)=0
.include
shared.directive.include — Include another netlist file
.include 'filename'
| Parameter | Description | Units |
|---|---|---|
filename | Path to the file to include (quotes recommended). |
Examples
.include 'models.inc'.inc 'corners/tt.sp'
See also: shared.directive.model
.model
shared.directive.model — Define a device model
.model mname type (param=value ...)
| Parameter | Description | Units |
|---|---|---|
mname | Model name referenced by instances. | |
type | Model type (e.g. NMOS, PMOS, D, NPN). |
Examples
.model nmos NMOS (VTO=0.7)
.op
shared.directive.op — DC operating-point analysis
.op
Examples
.op
Computes node voltages and bias points. Many simulators also print an operating point before .tran / .ac unless UIC is used.
.param
shared.directive.param — Define a named parameter
.param name=value [name2=value2 ...]
| Parameter | Description | Units |
|---|---|---|
name | Parameter identifier. | |
value | Numeric value or expression. |
Examples
.param rload=1k.param pi=3.14159
shared.directive.print — Print analysis results as a table
.print {DC|TRAN|AC} ov1 [ov2 ...]
| Parameter | Description | Units |
|---|---|---|
| `DC | TRAN | AC` |
ov | Output variable, e.g. V(node), I(Vsrc), VM(node). |
Examples
.print TRAN V(out) I(Vdd).print DC V(2) V(3)
.subckt
shared.directive.subckt — Begin a subcircuit definition
.subckt name n1 [n2 ...] [params: p=val ...]
| Parameter | Description | Units |
|---|---|---|
name | Subcircuit name. | |
n1... | External port nodes in order. |
Examples
.subckt buffer in out.subckt inv in out
See also: shared.directive.ends
.temp
shared.directive.temp — Set circuit temperature(s)
.temp t1 [t2 ...]
| Parameter | Description | Units |
|---|---|---|
t1 | Temperature in Celsius (additional values run multi-temp analysis). | °C |
Examples
.temp 25.temp 0 25 85
.tran
shared.directive.tran — Transient analysis
.tran tstep tstop [tstart [tmax]] [UIC]
| Parameter | Description | Units |
|---|---|---|
tstep | Suggested printing / sampling increment. | s |
tstop | Final simulation time. | s |
tstart | Optional start of printing. | s |
tmax | Optional maximum timestep. | s |
UIC | Use initial conditions. |
Examples
.tran 1n 100n.tran 1u 1m 0 10u UIC
Elements
C
shared.element.C — Capacitor
Cname n1 n2 value [params...]
| Parameter | Description | Units |
|---|---|---|
n1 | Positive node. | |
n2 | Negative node. | |
value | Capacitance. | F |
Examples
C1 out 0 1p
R
shared.element.R — Resistor
Rname n1 n2 value [params...]
| Parameter | Description | Units |
|---|---|---|
n1 | Positive node. | |
n2 | Negative node. | |
value | Resistance. | ohm |
Examples
R1 in out 1kRload out 0 50
X
shared.element.X — Subcircuit instance
Xname n1 [n2 ...] subckt_name [params...]
| Parameter | Description | Units |
|---|---|---|
n1... | Nodes connected to subcircuit ports. | |
subckt_name | Name of a .subckt definition. |
Examples
X1 a b buffer
HSPICE reference catalog
Effective documentation for the HSPICE dialect (spiceLsp.dialect = "hspice"): shared entries plus dialect overlays.
Source of truth: JSON under reference/. Hover in the editor uses the same corpus.
Index
| Name | Kind | Summary | Source |
|---|---|---|---|
.ac | directive | AC frequency analysis (HSPICE) | dialect |
.alter | directive | Rerun simulation with alternate parameters or analyses (HSPICE) | dialect |
.data | directive | Define a named data table for data-driven sweeps (HSPICE) | dialect |
.dc | directive | DC analysis and sweeps (HSPICE) | dialect |
.end | directive | Terminate the netlist (HSPICE) | dialect |
.ends | directive | End a subcircuit definition | shared |
.ic | directive | Force initial node voltages (HSPICE) | dialect |
.include | directive | Include an external file (HSPICE) | dialect |
.lib | directive | Call a named library section (HSPICE) | dialect |
.measure | directive | Measure delay, rise/fall, extrema, and other results (HSPICE) | dialect |
.model | directive | Define a device model | shared |
.nodeset | directive | Suggest initial guesses for DC convergence (HSPICE) | dialect |
.noise | directive | Noise analysis paired with .AC (HSPICE) | dialect |
.op | directive | DC operating-point report (HSPICE) | dialect |
.option | directive | Set HSPICE simulation options | dialect |
.param | directive | Define parameters and expressions (HSPICE) | dialect |
.print | directive | Print tabulated analysis results (HSPICE) | dialect |
.probe | directive | Select waveforms saved for post-processing (HSPICE) | dialect |
.subckt | directive | Begin a subcircuit definition | shared |
.temp | directive | Set simulation temperature(s) (HSPICE) | dialect |
.tf | directive | DC small-signal transfer function (HSPICE) | dialect |
.tran | directive | Transient analysis (HSPICE) | dialect |
C | element | Capacitor | shared |
R | element | Resistor | shared |
X | element | Subcircuit instance | shared |
Directives
.ac
hspice.directive.ac — AC frequency analysis (HSPICE)
Dialect overlay (replaces shared entry with the same name).
.AC type np fstart fstop
.AC type np fstart fstop SWEEP var type2 np2 start stop
.AC DATA=datanm
| Parameter | Description | Units |
|---|---|---|
type | LIN, DEC, or OCT frequency spacing. | |
np | Points (LIN) or points per decade/octave. | |
fstart / fstop | Frequency range. | Hz |
SWEEP … | Optional nested parameter/source sweep. | |
DATA=datanm | Data-driven AC analysis. |
Examples
.AC DEC 10 1 1G.AC LIN 50 1k 100Meg.AC DATA=ac_corners
HSPICE adds nested SWEEP and DATA= forms on top of classic LIN/DEC/OCT.
See also: hspice.directive.data, hspice.directive.noise
.alter
hspice.directive.alter — Rerun simulation with alternate parameters or analyses (HSPICE)
Dialect overlay (replaces shared entry with the same name).
.ALTER [title_string]
| Parameter | Description | Units |
|---|---|---|
title_string | Optional title for this alter case. |
Examples
.ALTER slow_corner .TEMP 125 .LIB 'models.lib' SS
An .ALTER block may contain elements and many control statements (.PARAM, .LIB, .DATA, .DC, .TRAN, .OP, …). Analysis types already used in the main deck have restrictions—see the HSPICE user guide.
See also: hspice.directive.data, hspice.directive.lib, hspice.directive.param
.data
hspice.directive.data — Define a named data table for data-driven sweeps (HSPICE)
Dialect overlay (replaces shared entry with the same name).
.DATA datanm pnam1 [pnam2 ...]
pval1 [pval2 ...]
...
.ENDDATA
| Parameter | Description | Units |
|---|---|---|
datanm | Data-block name referenced by DATA=datanm on .DC / .AC / .TRAN. | |
pnam | Parameter column name (must be declared with .PARAM). | |
pval | Row of values; column count matches the parameter list. |
Examples
.DATA load_sweep rload 1k 10k 100k .ENDDATA- `.DATA load_sweep rload
- 1k
- 10k .ENDDATA`
.DC DATA=load_sweep
HSPICE supports inline .DATA … .ENDDATA blocks and external/MER file forms. Value rows may be bare (no leading +) or classic + continuations. Analysis statements select a block with DATA=datanm (optionally DATA=datanm(Nums)).
See also: hspice.directive.dc, shared.directive.param, hspice.directive.tran
.dc
hspice.directive.dc — DC analysis and sweeps (HSPICE)
Dialect overlay (replaces shared entry with the same name).
.DC var1 start1 stop1 incr1
.DC var1 START=start1 STOP=stop1 STEP=incr1
.DC var1 start1 stop1 incr1 [SWEEP] var2 start2 stop2 incr2
.DC var1 type np start1 stop1
.DC DATA=datanm
.DC MONTE=val
| Parameter | Description | Units |
|---|---|---|
var1 | Primary sweep variable: independent source, element/model parameter, or TEMP. | |
start1 / stop1 / incr1 | Linear sweep bounds and step (positional or START=/STOP=/STEP=). | |
type np | LIN|DEC|OCT with point count for parameterized / nested sweeps. | |
SWEEP var2 … | Optional nested (second) sweep over another source or parameter. | |
DATA=datanm | Data-driven sweep using a .DATA block. | |
MONTE=val | Monte Carlo DC analysis. |
Examples
.DC Vgs 0 1.8 0.1.DC Vds 0 1.8 0.05 SWEEP Vgs 0 1.8 0.3.DC TEMP START=0 STOP=100 STEP=25.DC DATA=corner_table.DC MONTE=100
HSPICE extends classic SPICE .DC with keyword START/STOP/STEP, nested SWEEP, DATA= tables, and MONTE=. Prefer DATA= for multi-parameter corner tables.
See also: hspice.directive.data, shared.directive.op, hspice.directive.print
.end
hspice.directive.end — Terminate the netlist (HSPICE)
Dialect overlay (replaces shared entry with the same name).
.END [comment]
| Parameter | Description | Units |
|---|---|---|
comment | Optional comment, often the deck name. |
Examples
.END.END inverter_tb
Also closes any open .ALTER sequences. Statements after .END are ignored.
.ends
shared.directive.ends — End a subcircuit definition
.ends [name]
| Parameter | Description | Units |
|---|---|---|
name | Optional subcircuit name; should match .subckt. |
Examples
.ends.ends buffer
See also: shared.directive.subckt
.ic
hspice.directive.ic — Force initial node voltages (HSPICE)
Dialect overlay (replaces shared entry with the same name).
.IC V(node)=value [V(node2)=value2 ...]
| Parameter | Description | Units |
|---|---|---|
V(node) | Node (or hierarchical path) to initialize. | V |
Examples
.IC V(out)=0.IC V(clk)=0 V(data)=1.8
Also accepted as .DCVOLT. Used with .TRAN … UIC to skip the quiescent OP and start from these voltages.
See also: hspice.directive.nodeset, hspice.directive.tran
.include
hspice.directive.include — Include an external file (HSPICE)
Dialect overlay (replaces shared entry with the same name).
.INCLUDE 'filename'
| Parameter | Description | Units |
|---|---|---|
filename | File path to insert (quotes recommended). |
Examples
.INCLUDE 'models/nmos.inc'.INC '../stimuli/clock.sp'
Also accepted as .INC. Prefer .LIB when selecting a named section from a library file.
See also: hspice.directive.lib
.lib
hspice.directive.lib — Call a named library section (HSPICE)
Dialect overlay (replaces shared entry with the same name).
.LIB 'filename' entryname
| Parameter | Description | Units |
|---|---|---|
filename | Library file path. | |
entryname | Section name inside the library (.LIB entry … .ENDL). |
Examples
.LIB 'models.lib' TT.LIB '/pdk/hspice/models' FF
Library files define sections with .LIB entryname … .ENDL entryname. Nested .LIB calls are supported up to a limited depth.
See also: hspice.directive.include, shared.directive.model
.measure
hspice.directive.measure — Measure delay, rise/fall, extrema, and other results (HSPICE)
Dialect overlay (replaces shared entry with the same name).
.MEASURE {TRAN|DC|AC} name TRIG … TARG …
.MEASURE {TRAN|DC|AC} name MAX|MIN|PP|AVG|RMS|INTEG …
| Parameter | Description | Units |
|---|---|---|
| `TRAN | DC | AC` |
name | Measurement result name written to the measure output. | |
TRIG / TARG | Trigger and target conditions (VAL=, RISE=/FALL=/CROSS=). |
Examples
.MEASURE TRAN trise TRIG V(out) VAL=0.1*vdd RISE=1 TARG V(out) VAL=0.9*vdd RISE=1.MEASURE TRAN tpd TRIG V(in) VAL=0.5 RISE=1 TARG V(out) VAL=0.5 RISE=1.MEASURE TRAN vmax MAX V(out)
Also accepted as .MEAS. Many measure forms exist (FIND/WHEN, PARAM, DERIV, etc.); TRIG/TARG and MAX/MIN cover the most common timing and peak checks.
See also: hspice.directive.tran, hspice.directive.print, hspice.directive.probe
.model
shared.directive.model — Define a device model
.model mname type (param=value ...)
| Parameter | Description | Units |
|---|---|---|
mname | Model name referenced by instances. | |
type | Model type (e.g. NMOS, PMOS, D, NPN). |
Examples
.model nmos NMOS (VTO=0.7)
.nodeset
hspice.directive.nodeset — Suggest initial guesses for DC convergence (HSPICE)
Dialect overlay (replaces shared entry with the same name).
.NODESET V(node)=value [V(node2)=value2 ...]
| Parameter | Description | Units |
|---|---|---|
V(node) | Node voltage guess for the DC solution. | V |
Examples
.NODESET V(out)=0.9.NODESET V(n1)=0 V(n2)=1.8
Unlike .IC, .NODESET is a soft hint for finding the operating point, not a hard initial condition for transient UIC.
See also: hspice.directive.ic, hspice.directive.op
.noise
hspice.directive.noise — Noise analysis paired with .AC (HSPICE)
Dialect overlay (replaces shared entry with the same name).
.NOISE outvar srcnam [interval]
| Parameter | Description | Units |
|---|---|---|
outvar | Output voltage node or pair, e.g. V(out) or V(out,ref). | |
srcnam | Independent source treated as the noise input reference. | |
interval | Optional print interval for intermediate noise summaries. |
Examples
.NOISE V(out) Vin.NOISE V(out,0) Vin 10
Requires a companion .AC statement. Reports total output noise and equivalent input noise.
See also: hspice.directive.ac
.op
hspice.directive.op — DC operating-point report (HSPICE)
Dialect overlay (replaces shared entry with the same name).
.OP [format [time ...]]
| Parameter | Description | Units |
|---|---|---|
format | Optional report style: ALL, BRIEF, CURRENT, DEBUG, NONE, VOLTAGE. | |
time | Optional time(s) for reporting during transient (with ALL/VOLTAGE/CURRENT/DEBUG). | s |
Examples
.OP.OP BRIEF.OP ALL 10n
HSPICE often prints bias automatically with other analyses; use .OP when you only need the operating point, or to request a formatted report at specific times.
See also: hspice.directive.dc, hspice.directive.ic, hspice.directive.nodeset
.option
hspice.directive.option — Set HSPICE simulation options
Dialect overlay (replaces shared entry with the same name).
.OPTION keyword=value [keyword=value ...]
| Parameter | Description | Units |
|---|---|---|
keyword | Option name (e.g. POST, RUNLVL). |
Examples
.OPTION POST=2.OPTION RUNLVL=5
Option keywords differ across simulators; this entry covers the .option form.
.param
hspice.directive.param — Define parameters and expressions (HSPICE)
Dialect overlay (replaces shared entry with the same name).
.PARAM name=value [name2=value2 ...]
| Parameter | Description | Units |
|---|---|---|
name | Parameter identifier. | |
value | Number, ‘algebraic expression’, distribution function, or str(‘…’). |
Examples
.PARAM vdd=1.8 rload=10k.PARAM cload='2*cunit'.PARAM tox=agauss(3n,0.1n,3)
HSPICE allows quoted expressions and statistical distributions (GAUSS/AGAUSS/…) used with Monte Carlo. Parameters referenced by .DATA columns must be declared here.
See also: hspice.directive.data, hspice.directive.alter
hspice.directive.print — Print tabulated analysis results (HSPICE)
Dialect overlay (replaces shared entry with the same name).
.PRINT {DC|TRAN|AC} ov1 [ov2 ...]
| Parameter | Description | Units |
|---|---|---|
| `DC | TRAN | AC` |
ov | Output variable; AC forms include VM/VP/VR/VI/VDB. |
Examples
.PRINT TRAN V(out) I(Vdd).PRINT AC VM(out) VDB(out) VP(out)
See also: hspice.directive.probe, hspice.directive.measure
.probe
hspice.directive.probe — Select waveforms saved for post-processing (HSPICE)
Dialect overlay (replaces shared entry with the same name).
.PROBE {DC|TRAN|AC} ov1 [ov2 ...]
| Parameter | Description | Units |
|---|---|---|
| `DC | TRAN | AC` |
ov | Output variable to save (node voltage, branch current, etc.). |
Examples
.PROBE TRAN V(out) V(in) I(Vdd).PROBE AC VM(out) VP(out)
Pair with .OPTION POST=… . With .OPTION PROBE, only .PROBE/.PRINT/.PLOT variables are written, shrinking waveform files.
See also: hspice.directive.print, hspice.directive.option
.subckt
shared.directive.subckt — Begin a subcircuit definition
.subckt name n1 [n2 ...] [params: p=val ...]
| Parameter | Description | Units |
|---|---|---|
name | Subcircuit name. | |
n1... | External port nodes in order. |
Examples
.subckt buffer in out.subckt inv in out
See also: shared.directive.ends
.temp
hspice.directive.temp — Set simulation temperature(s) (HSPICE)
Dialect overlay (replaces shared entry with the same name).
.TEMP t1 [t2 ...]
| Parameter | Description | Units |
|---|---|---|
t1 | Temperature in Celsius; multiple values run multi-temperature analysis. | °C |
Examples
.TEMP 25.TEMP -40 25 125
TEMP can also be the sweep variable on .DC / nested SWEEP.
See also: hspice.directive.dc, hspice.directive.option
.tf
hspice.directive.tf — DC small-signal transfer function (HSPICE)
Dialect overlay (replaces shared entry with the same name).
.TF outvar srcnam
| Parameter | Description | Units |
|---|---|---|
outvar | Output variable, e.g. V(out) or I(Vload). | |
srcnam | Independent input source name. |
Examples
.TF V(out) Vin.TF I(Vmeas) Iin
Reports transfer gain plus small-signal input and output resistances.
See also: hspice.directive.op, hspice.directive.dc
.tran
hspice.directive.tran — Transient analysis (HSPICE)
Dialect overlay (replaces shared entry with the same name).
.TRAN tstep tstop [tstart [tmax]] [UIC]
| Parameter | Description | Units |
|---|---|---|
tstep | Printing increment. | s |
tstop | Stop time. | s |
tstart | Optional start of printing. | s |
tmax | Optional maximum timestep. | s |
UIC | Use initial conditions. |
Examples
.TRAN 1p 10n
HSPICE commonly uses uppercase directives; spice-lsp matches case-insensitively.
Elements
C
shared.element.C — Capacitor
Cname n1 n2 value [params...]
| Parameter | Description | Units |
|---|---|---|
n1 | Positive node. | |
n2 | Negative node. | |
value | Capacitance. | F |
Examples
C1 out 0 1p
R
shared.element.R — Resistor
Rname n1 n2 value [params...]
| Parameter | Description | Units |
|---|---|---|
n1 | Positive node. | |
n2 | Negative node. | |
value | Resistance. | ohm |
Examples
R1 in out 1kRload out 0 50
X
shared.element.X — Subcircuit instance
Xname n1 [n2 ...] subckt_name [params...]
| Parameter | Description | Units |
|---|---|---|
n1... | Nodes connected to subcircuit ports. | |
subckt_name | Name of a .subckt definition. |
Examples
X1 a b buffer
Ngspice reference catalog
Effective documentation for the Ngspice dialect (spiceLsp.dialect = "ngspice"): shared entries plus dialect overlays.
Source of truth: JSON under reference/. Hover in the editor uses the same corpus.
Index
| Name | Kind | Summary | Source |
|---|---|---|---|
.ac | directive | Small-signal AC frequency analysis | shared |
.dc | directive | DC sweep analysis | shared |
.end | directive | End of netlist | shared |
.ends | directive | End a subcircuit definition | shared |
.ic | directive | Set initial node voltages | shared |
.include | directive | Include another netlist file | shared |
.model | directive | Define a device model | shared |
.op | directive | DC operating-point analysis | shared |
.param | directive | Define a named parameter | shared |
.print | directive | Print analysis results as a table | shared |
.subckt | directive | Begin a subcircuit definition | shared |
.temp | directive | Set circuit temperature(s) | shared |
.tran | directive | Transient analysis (Ngspice) | dialect |
C | element | Capacitor | shared |
R | element | Resistor | shared |
X | element | Subcircuit instance | shared |
Directives
.ac
shared.directive.ac — Small-signal AC frequency analysis
.ac {LIN|DEC|OCT} np fstart fstop
| Parameter | Description | Units |
|---|---|---|
| `LIN | DEC | OCT` |
np | Number of points (LIN) or points per decade/octave (DEC/OCT). | |
fstart | Start frequency. | Hz |
fstop | Stop frequency. | Hz |
Examples
.ac DEC 10 1k 1G.ac LIN 100 1 1Meg
.dc
shared.directive.dc — DC sweep analysis
.dc srcname start stop step
| Parameter | Description | Units |
|---|---|---|
srcname | Independent source, parameter, or TEMP to sweep. | |
start | Sweep start value. | |
stop | Sweep stop value. | |
step | Increment between points. |
Examples
.dc V1 0 5 0.1.dc TEMP 0 100 25
.end
shared.directive.end — End of netlist
.end [comment]
| Parameter | Description | Units |
|---|---|---|
comment | Optional trailing comment. |
Examples
.end.end my_circuit
.ends
shared.directive.ends — End a subcircuit definition
.ends [name]
| Parameter | Description | Units |
|---|---|---|
name | Optional subcircuit name; should match .subckt. |
Examples
.ends.ends buffer
See also: shared.directive.subckt
.ic
shared.directive.ic — Set initial node voltages
.ic V(node)=value [V(node2)=value2 ...]
| Parameter | Description | Units |
|---|---|---|
V(node) | Node voltage to initialize. | V |
Examples
.ic V(out)=0.ic V(in)=1.8 V(out)=0
.include
shared.directive.include — Include another netlist file
.include 'filename'
| Parameter | Description | Units |
|---|---|---|
filename | Path to the file to include (quotes recommended). |
Examples
.include 'models.inc'.inc 'corners/tt.sp'
See also: shared.directive.model
.model
shared.directive.model — Define a device model
.model mname type (param=value ...)
| Parameter | Description | Units |
|---|---|---|
mname | Model name referenced by instances. | |
type | Model type (e.g. NMOS, PMOS, D, NPN). |
Examples
.model nmos NMOS (VTO=0.7)
.op
shared.directive.op — DC operating-point analysis
.op
Examples
.op
Computes node voltages and bias points. Many simulators also print an operating point before .tran / .ac unless UIC is used.
.param
shared.directive.param — Define a named parameter
.param name=value [name2=value2 ...]
| Parameter | Description | Units |
|---|---|---|
name | Parameter identifier. | |
value | Numeric value or expression. |
Examples
.param rload=1k.param pi=3.14159
shared.directive.print — Print analysis results as a table
.print {DC|TRAN|AC} ov1 [ov2 ...]
| Parameter | Description | Units |
|---|---|---|
| `DC | TRAN | AC` |
ov | Output variable, e.g. V(node), I(Vsrc), VM(node). |
Examples
.print TRAN V(out) I(Vdd).print DC V(2) V(3)
.subckt
shared.directive.subckt — Begin a subcircuit definition
.subckt name n1 [n2 ...] [params: p=val ...]
| Parameter | Description | Units |
|---|---|---|
name | Subcircuit name. | |
n1... | External port nodes in order. |
Examples
.subckt buffer in out.subckt inv in out
See also: shared.directive.ends
.temp
shared.directive.temp — Set circuit temperature(s)
.temp t1 [t2 ...]
| Parameter | Description | Units |
|---|---|---|
t1 | Temperature in Celsius (additional values run multi-temp analysis). | °C |
Examples
.temp 25.temp 0 25 85
.tran
ngspice.directive.tran — Transient analysis (Ngspice)
Dialect overlay (replaces shared entry with the same name).
.tran tstep tstop [tstart [tmax]] [UIC]
| Parameter | Description | Units |
|---|---|---|
tstep | Suggested printing increment. | s |
tstop | Final time. | s |
tstart | Optional start of printing. | s |
tmax | Optional maximum timestep. | s |
UIC | Use initial conditions. |
Examples
.tran 1n 100n.tran 1u 1m 0 10u UIC
Ngspice also accepts ; and $ comments in addition to *.
Elements
C
shared.element.C — Capacitor
Cname n1 n2 value [params...]
| Parameter | Description | Units |
|---|---|---|
n1 | Positive node. | |
n2 | Negative node. | |
value | Capacitance. | F |
Examples
C1 out 0 1p
R
shared.element.R — Resistor
Rname n1 n2 value [params...]
| Parameter | Description | Units |
|---|---|---|
n1 | Positive node. | |
n2 | Negative node. | |
value | Resistance. | ohm |
Examples
R1 in out 1kRload out 0 50
X
shared.element.X — Subcircuit instance
Xname n1 [n2 ...] subckt_name [params...]
| Parameter | Description | Units |
|---|---|---|
n1... | Nodes connected to subcircuit ports. | |
subckt_name | Name of a .subckt definition. |
Examples
X1 a b buffer
LTspice reference catalog
Effective documentation for the LTspice dialect (spiceLsp.dialect = "ltspice"): shared entries plus dialect overlays.
Source of truth: JSON under reference/. Hover in the editor uses the same corpus.
Index
| Name | Kind | Summary | Source |
|---|---|---|---|
.ac | directive | Small-signal AC frequency analysis | shared |
.dc | directive | DC sweep analysis | shared |
.end | directive | End of netlist | shared |
.ends | directive | End a subcircuit definition | shared |
.ic | directive | Set initial node voltages | shared |
.include | directive | Include another netlist file | shared |
.model | directive | Define a device model | shared |
.op | directive | DC operating-point analysis | shared |
.param | directive | Define a named parameter | shared |
.print | directive | Print analysis results as a table | shared |
.subckt | directive | Begin a subcircuit definition | shared |
.temp | directive | Set circuit temperature(s) | shared |
.tran | directive | Transient analysis (LTspice) | dialect |
C | element | Capacitor | shared |
R | element | Resistor | shared |
X | element | Subcircuit instance | shared |
Directives
.ac
shared.directive.ac — Small-signal AC frequency analysis
.ac {LIN|DEC|OCT} np fstart fstop
| Parameter | Description | Units |
|---|---|---|
| `LIN | DEC | OCT` |
np | Number of points (LIN) or points per decade/octave (DEC/OCT). | |
fstart | Start frequency. | Hz |
fstop | Stop frequency. | Hz |
Examples
.ac DEC 10 1k 1G.ac LIN 100 1 1Meg
.dc
shared.directive.dc — DC sweep analysis
.dc srcname start stop step
| Parameter | Description | Units |
|---|---|---|
srcname | Independent source, parameter, or TEMP to sweep. | |
start | Sweep start value. | |
stop | Sweep stop value. | |
step | Increment between points. |
Examples
.dc V1 0 5 0.1.dc TEMP 0 100 25
.end
shared.directive.end — End of netlist
.end [comment]
| Parameter | Description | Units |
|---|---|---|
comment | Optional trailing comment. |
Examples
.end.end my_circuit
.ends
shared.directive.ends — End a subcircuit definition
.ends [name]
| Parameter | Description | Units |
|---|---|---|
name | Optional subcircuit name; should match .subckt. |
Examples
.ends.ends buffer
See also: shared.directive.subckt
.ic
shared.directive.ic — Set initial node voltages
.ic V(node)=value [V(node2)=value2 ...]
| Parameter | Description | Units |
|---|---|---|
V(node) | Node voltage to initialize. | V |
Examples
.ic V(out)=0.ic V(in)=1.8 V(out)=0
.include
shared.directive.include — Include another netlist file
.include 'filename'
| Parameter | Description | Units |
|---|---|---|
filename | Path to the file to include (quotes recommended). |
Examples
.include 'models.inc'.inc 'corners/tt.sp'
See also: shared.directive.model
.model
shared.directive.model — Define a device model
.model mname type (param=value ...)
| Parameter | Description | Units |
|---|---|---|
mname | Model name referenced by instances. | |
type | Model type (e.g. NMOS, PMOS, D, NPN). |
Examples
.model nmos NMOS (VTO=0.7)
.op
shared.directive.op — DC operating-point analysis
.op
Examples
.op
Computes node voltages and bias points. Many simulators also print an operating point before .tran / .ac unless UIC is used.
.param
shared.directive.param — Define a named parameter
.param name=value [name2=value2 ...]
| Parameter | Description | Units |
|---|---|---|
name | Parameter identifier. | |
value | Numeric value or expression. |
Examples
.param rload=1k.param pi=3.14159
shared.directive.print — Print analysis results as a table
.print {DC|TRAN|AC} ov1 [ov2 ...]
| Parameter | Description | Units |
|---|---|---|
| `DC | TRAN | AC` |
ov | Output variable, e.g. V(node), I(Vsrc), VM(node). |
Examples
.print TRAN V(out) I(Vdd).print DC V(2) V(3)
.subckt
shared.directive.subckt — Begin a subcircuit definition
.subckt name n1 [n2 ...] [params: p=val ...]
| Parameter | Description | Units |
|---|---|---|
name | Subcircuit name. | |
n1... | External port nodes in order. |
Examples
.subckt buffer in out.subckt inv in out
See also: shared.directive.ends
.temp
shared.directive.temp — Set circuit temperature(s)
.temp t1 [t2 ...]
| Parameter | Description | Units |
|---|---|---|
t1 | Temperature in Celsius (additional values run multi-temp analysis). | °C |
Examples
.temp 25.temp 0 25 85
.tran
ltspice.directive.tran — Transient analysis (LTspice)
Dialect overlay (replaces shared entry with the same name).
.tran <Tstep> <Tstop> [Tstart [dTmax]] [modifiers]
| Parameter | Description | Units |
|---|---|---|
Tstep | Printing increment. | s |
Tstop | Stop time. | s |
Examples
.tran 1n 100n
LTspice support is a stub corpus; grammar remains shared.
Elements
C
shared.element.C — Capacitor
Cname n1 n2 value [params...]
| Parameter | Description | Units |
|---|---|---|
n1 | Positive node. | |
n2 | Negative node. | |
value | Capacitance. | F |
Examples
C1 out 0 1p
R
shared.element.R — Resistor
Rname n1 n2 value [params...]
| Parameter | Description | Units |
|---|---|---|
n1 | Positive node. | |
n2 | Negative node. | |
value | Resistance. | ohm |
Examples
R1 in out 1kRload out 0 50
X
shared.element.X — Subcircuit instance
Xname n1 [n2 ...] subckt_name [params...]
| Parameter | Description | Units |
|---|---|---|
n1... | Nodes connected to subcircuit ports. | |
subckt_name | Name of a .subckt definition. |
Examples
X1 a b buffer
Build
Pixi-managed build environment, tasks, CI, and release workflow.
Environment
All tooling flows through pixi:
pixi install # sync environment from pixi.lock
pixi run <task> # run a defined task
pixi run cargo build # ad-hoc command in the env
The workspace supports linux-64, osx-arm64, osx-64, and win-64 so release CI can install the same environment on every runner.
Add dependencies with the CLI (do not hand-edit versions):
pixi add rust=1.96
pixi add nodejs=22 # when VS Code extension work starts
pixi add mdbook # already in pixi.toml
pixi add --pypi pytest # example PyPI package
Planned pixi tasks
Add these to [tasks] in pixi.toml as the workspace grows:
| Task | Command | Purpose |
|---|---|---|
build | cargo build --release | Production binary |
build-dev | cargo build | Fast debug builds |
test | cargo test --workspace | All unit + integration tests |
test-parser | cargo test -p spice-parser | Parser fixtures only |
test-lsp | cargo test -p spice-lsp | LSP integration tests |
spice-lsp | cargo run -p spice-lsp | Run language server (stdio; also accepts --stdio) |
format-spice | cargo run -q -p spice-lsp -- format | Format SPICE netlists (--write / --check) |
fmt | cargo fmt --all | Rust source formatting |
clippy | cargo clippy --workspace -- -D warnings | Lint |
mdbook-build | mdbook build docs | Static doc site |
mdbook-serve | mdbook serve docs -n 127.0.0.1 -p 3000 | Live doc preview |
ext-install | npm install in editors/vscode | Extension deps |
ext-compile | npm run compile | Build extension TS |
ext-package | ./scripts/package-vscode-extension.sh | Bundle local binary + .vsix |
reference-validate | cargo test -p spice-reference --lib | Validate embedded corpus |
reference-docs | spice-reference-catalog write | Regenerate docs/reference/ from corpus |
reference-docs-check | spice-reference-catalog check | Fail if catalog docs drift |
Example pixi.toml fragment:
[tasks]
build = "cargo build --release"
test = "cargo test --workspace"
spice-lsp = "cargo run -p spice-lsp"
mdbook-serve = "mdbook serve docs -n 127.0.0.1 -p 3000"
Rust workspace layout
Root Cargo.toml:
[workspace]
members = ["crates/spice-parser", "crates/spice-lsp"]
resolver = "2"
Release profile (recommended once benchmarks exist):
[profile.release]
lto = true
codegen-units = 1
Building the Tree-sitter grammar
Grammar crate under tree-sitter-spice/ is built via build.rs in spice-parser:
pixi run cargo build -p spice-parser
After grammar edits, run Tree-sitter’s test harness (once added):
pixi run cargo test -p tree-sitter-spice
# or: tree-sitter test (if CLI added via pixi)
CI (recommended)
GitHub Actions workflow stages:
pixi installpixi run fmt -- --checkpixi run clippypixi run testpixi run mdbook-build(optional, docs PRs)
Cache ~/.pixi and target/ between runs.
Release builds
Ship a single static binary per platform:
pixi run cargo build --release -p spice-lsp
# artifact: target/release/spice-lsp
Cross-compile with cross or platform matrix in CI. The Release VS Code extension workflow runs on every push to main: it patch-bumps the extension version, builds all platform binaries, packages a bundled .vsix, creates a GitHub Release, and publishes to the VS Code Marketplace (requires VSCE_PAT). See VS Code integration.
Documentation site
Build locally:
pixi run reference-docs # regenerate dialect catalog from reference/
pixi run mdbook-build
# output: docs/book/
Preview:
pixi run mdbook-serve
After editing JSON under reference/, run pixi run reference-docs so the Dialect reference catalog stays in sync. CI runs pixi run reference-docs-check.
CI and GitHub Pages
The Deploy docs workflow runs on pushes to main when docs/, reference/, crates/spice-reference/, pixi.toml, or pixi.lock change. It:
- Runs
pixi run mdbook-build - Pushes the output to the
gh-pagesbranch
GITHUB_TOKEN cannot enable GitHub Pages or set the repository Website field on this repo (API returns 403). Run the one-time setup script locally as a repository admin after the first successful deploy:
./scripts/setup-github-pages.sh
That script uses the gh CLI to:
- Point Pages at the
gh-pagesbranch (/root) - Set the repository Website field to the published URL
Published URL: https://amirhosseindavoody.github.io/spice-lsp/
Trigger a manual deploy from the Actions tab via workflow_dispatch if needed.
Troubleshooting
| Problem | Fix |
|---|---|
cargo: command not found | Run pixi install; use pixi run cargo |
Tree-sitter build.rs fails | Ensure C compiler available in pixi env (pixi add gcc on Linux) |
| Extension can’t find binary | Set spiceLsp.serverPath in VS Code settings to absolute path |
| LSP hangs on start | Normal for stdio servers waiting for JSON-RPC input |
Related
Demo and Testing
How to manually demo spice-lsp and automate tests at each layer.
Testing pyramid
┌─────────────┐
│ Manual VS │ F5 extension, eyeball squiggles
│ Code demo │
└──────┬──────┘
┌───────────┴───────────┐
│ LSP integration tests │ JSON-RPC over stdio
└───────────┬───────────┘
┌────────────────┴────────────────┐
│ Parser / grammar tests │ Fixtures in test-data/
└─────────────────────────────────┘
Lower layers run faster and should carry most coverage.
Parser tests
Location: crates/spice-parser/tests/ or inline #[cfg(test)] modules.
Pattern — golden diagnostics:
#![allow(unused)]
fn main() {
#[test]
fn unclosed_subckt_reports_error() {
let source = std::fs::read_to_string("test-data/invalid/unclosed-subckt.cir").unwrap();
let result = spice_parser::analyze(&source);
assert!(!result.diagnostics.is_empty());
assert!(result.diagnostics[0].message.contains("ends"));
}
}
Grammar tests (Tree-sitter): Corpus files under tree-sitter-spice/test/corpus/:
==========
simple RC
==========
R1 in out 1k
---
(source_file (instance_line ...))
Run: pixi run cargo test -p spice-parser
LSP integration tests
Test the binary without an editor by driving stdio.
Option A — Custom test harness
Spawn spice-lsp as a child process, write JSON-RPC messages with Content-Length headers, read responses:
#![allow(unused)]
fn main() {
// Pseudocode — pass `--stdio` like vscode-languageclient does
let mut child = Command::new("target/debug/spice-lsp")
.arg("--stdio")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()?;
write_message(&mut child, initialize_request());
let init_resp = read_message(&mut child);
assert_eq!(init_resp["result"]["capabilities"]["textDocumentSync"]["change"], 2);
write_message(&mut child, did_open("file:///test.cir", INVALID_SOURCE));
let diag = read_until_method(&mut child, "textDocument/publishDiagnostics");
assert!(!diag["params"]["diagnostics"].as_array().unwrap().is_empty());
}
Option B — tower-lsp in-process tests
Test Backend methods directly with a mock Client that records publish_diagnostics calls — faster, no subprocess.
Use both: in-process for logic, one subprocess smoke test for the full binary.
Run: pixi run cargo test -p spice-lsp
Manual LSP smoke test (no VS Code)
Use a generic LSP inspector or minimal script.
With languageclient CLI (if installed)
Some ecosystems ship an inspector; alternatively use the VS Code Output → SPICE LSP trace.
Raw JSON-RPC with Python (example)
#!/usr/bin/env python3
"""Send initialize to spice-lsp over stdio. Requires built binary on PATH."""
import json, subprocess, struct, sys
proc = subprocess.Popen(
["spice-lsp", "--stdio"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
)
def send(msg):
body = json.dumps(msg).encode()
header = f"Content-Length: {len(body)}\r\n\r\n".encode()
proc.stdin.write(header + body)
proc.stdin.flush()
def read():
headers = {}
while True:
line = proc.stdout.readline().decode()
if line in ("\r\n", "\n", ""):
break
k, v = line.split(":", 1)
headers[k.strip()] = int(v.strip())
body = proc.stdout.read(headers["Content-Length"])
return json.loads(body)
send({"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {
"processId": None,
"rootUri": None,
"capabilities": {},
}})
resp = read()
print(json.dumps(resp, indent=2))
assert "capabilities" in resp.get("result", {}), resp
print("OK: initialize succeeded", file=sys.stderr)
Run after build:
pixi run build
export PATH="$PWD/target/debug:$PATH"
python3 scripts/lsp_smoke.py
Add scripts/lsp_smoke.py to the repo when the server exists.
VS Code extension demo
Development host (primary demo path)
- Build Rust binary:
pixi run build cd editors/vscode && npm install && npm run compile- Open
editors/vscodein VS Code - Run and Debug → Launch Extension (F5)
- In the new [Extension Development Host] window:
- Run SPICE LSP: Create Demo Folder (or open
test-data/invalid/unclosed-subckt.cir) - In
spice-lsp-demo/top.sp, press F12 onnch/inverterto jump intomodels.sp - Open
test-data/invalid/unclosed-subckt.cirand confirm Problems lists diagnostics - Fix syntax, confirm clearing
- Run SPICE LSP: Create Demo Folder (or open
Launch configuration
.vscode/launch.json in the extension folder:
{
"version": "0.2.0",
"configurations": [{
"name": "Launch Extension",
"type": "extensionHost",
"request": "launch",
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
"env": {},
"preLaunchTask": "npm: compile"
}]
}
Set user/workspace setting in Development Host:
{
"spiceLsp.serverPath": "/absolute/path/to/spice-lsp/target/debug/spice-lsp"
}
Trace LSP traffic
Enable verbose logging during demo debugging:
{
"spiceLsp.trace.server": "verbose"
}
View Output panel → channel SPICE LSP (or Language Client name).
Side-load packaged extension
cd editors/vscode
npx vsce package
code --install-extension spice-lsp-0.1.0.vsix
Demo checklist for stakeholders
Use this script in reviews:
| Step | Action | Expected |
|---|---|---|
| 1 | pixi run test | All tests pass |
| 2 | F5 extension | Development Host opens |
| 3 | Open invalid netlist | Red squiggle + Problems entry |
| 4 | Edit to fix | Diagnostic disappears |
| 5 | Open valid netlist | No errors |
| 6 | SPICE LSP: Create Demo Folder | spice-lsp-demo/ appears (HSPICE); F12 on nch in top.sp opens models.sp; F12 on nch_tt in top-lib.sp opens corners.lib |
| 7 | SPICE LSP: Restart Server (if needed) | Server reconnects, diagnostics return |
CI expectations
Every push should run:
pixi install
pixi run test
Optional nightly or pre-release:
pixi run build --release
pixi run cargo test --release
Extension CI (when added):
cd editors/vscode && npm ci && npm run compile && npm test
Benchmarks
Add criterion benches for parse + analyze on large fixtures:
pixi run cargo bench -p spice-parser
Track regressions against targets in Architecture.
Related
VS Code Integration
The VS Code extension launches the Rust language server and provides a first-class editing experience for SPICE netlists.
This chapter covers extension layout, development workflow, configuration, and publishing.
Architecture
┌──────────────────────────────────────────────────────────────┐
│ VS Code Extension Host (Node.js) │
│ │
│ package.json ── contributes languages, config, commands │
│ extension.ts ── activates LanguageClient │
│ language-configuration.json ── comments, brackets, auto-close│
└───────────────────────────┬──────────────────────────────────┘
│ spawns process
▼
┌──────────────────────────────────────────────────────────────┐
│ spice-lsp binary (Rust, stdio JSON-RPC) │
│ initialize → didOpen/didChange → publishDiagnostics │
└──────────────────────────────────────────────────────────────┘
The extension is intentionally thin: no parsing in TypeScript. All language intelligence stays in Rust so Neovim and other clients can share the same binary.
Repository layout
editors/vscode/
├── .vscode/
│ ├── launch.json # F5 Extension Development Host
│ └── tasks.json # compile before launch
├── package.json
├── tsconfig.json
├── demo/ # HSPICE samples copied by Create Demo Folder
├── src/
│ ├── extension.ts
│ └── demoContent.ts
├── language-configuration.json
└── README.md # Marketplace-facing extension readme
package.json
Key fields:
| Field | Purpose |
|---|---|
engines.vscode | Minimum VS Code version |
activationEvents | onLanguage:spice, onCommand:spiceLsp.restartServer, onCommand:spiceLsp.setDialect, onCommand:spiceLsp.createDemoFolder |
main | ./out/extension.js (esbuild bundle) |
contributes.languages | Register spice language id and file extensions |
contributes.configuration | spiceLsp.serverPath, spiceLsp.trace.server, spiceLsp.dialect |
contributes.commands | spiceLsp.restartServer, spiceLsp.setDialect, spiceLsp.createDemoFolder — register first in activate; do not await LSP start before returning |
Example language contribution:
{
"languages": [{
"id": "spice",
"aliases": ["SPICE", "spice"],
"extensions": [".cir", ".sp", ".spf", ".net", ".ckt"],
"configuration": "./language-configuration.json"
}]
}
language-configuration.json
Teach VS Code comment syntax and line continuation behavior:
{
"comments": {
"lineComment": "*"
},
"brackets": [["(", ")"]],
"autoClosingPairs": [
{ "open": "(", "close": ")" }
]
}
Comment toggle uses * (language-configuration.json allows only one lineComment). ; and $ comments are highlighted by the TextMate grammar (syntaxes/spice.tmLanguage.json). Tree-sitter highlights.scm can back semantic tokens later.
extension.ts
Minimal Language Client setup. Register palette commands before any await, then start the client in the background. If activate awaits a slow/hung client.start(), VS Code times out onCommand activation and reports command 'spiceLsp.setDialect' not found (same for Restart Server / Create Demo Folder):
import * as vscode from "vscode";
import {
LanguageClient,
LanguageClientOptions,
ServerOptions,
TransportKind,
} from "vscode-languageclient/node";
let client: LanguageClient | undefined;
async function startClient(serverPath: string) {
// TransportKind.stdio makes the client append `--stdio` to the process args.
// spice-lsp accepts that flag (stdio is the only transport).
const serverOptions: ServerOptions = {
command: serverPath,
args: [],
transport: TransportKind.stdio,
};
const clientOptions: LanguageClientOptions = {
documentSelector: [{ scheme: "file", language: "spice" }],
synchronize: {
fileEvents: vscode.workspace.createFileSystemWatcher("**/*.{cir,sp,spf,net,ckt}"),
},
};
client = new LanguageClient("spiceLsp", "SPICE Language Server", serverOptions, clientOptions);
await client.start();
}
export async function activate(context: vscode.ExtensionContext) {
const config = vscode.workspace.getConfiguration("spiceLsp");
const serverPath = config.get<string>("serverPath") || "spice-lsp";
context.subscriptions.push(
vscode.commands.registerCommand("spiceLsp.restartServer", async () => {
await client?.stop();
await startClient(serverPath);
}),
vscode.commands.registerCommand("spiceLsp.setDialect", async () => {
/* QuickPick → update spiceLsp.dialect → restart client */
}),
vscode.commands.registerCommand("spiceLsp.createDemoFolder", async () => {
/* Write spice-lsp-demo/ with sample .sp netlists under the workspace folder */
}),
);
void startClient(serverPath).catch((error) => {
const message = error instanceof Error ? error.message : String(error);
void vscode.window.showErrorMessage(`Failed to start SPICE LSP: ${message}`);
});
}
export async function deactivate() {
await client?.stop();
}
Create Demo Folder
SPICE LSP: Create Demo Folder copies the templates from editors/vscode/demo/ into a spice-lsp-demo/ directory under the opened workspace folder (or a folder you pick if none is open). It also sets spiceLsp.dialect to hspice.
| File | Purpose |
|---|---|
same-file.sp | HSPICE .param / .option / .model / .subckt — F12 on buffer / nch stays in-file |
models.sp | Shared models and subcircuits |
top.sp | .include 'models.sp' — F12 on nch / inverter / buffer jumps across files |
corners.lib | HSPICE .lib / .endl corner sections |
top-lib.sp | .lib 'corners.lib' TT — F12 on the path opens corners.lib; on TT jumps to .lib TT; on nch_tt / pch_tt jumps to the model |
README.md | Short walkthrough |
If the folder already exists, the command offers Overwrite or Open Existing. Templates live in the extension package under demo/ so Marketplace installs ship the same samples.
Development workflow
One-time setup
pixi add nodejs=22 # if not already in pixi.toml
cd editors/vscode
npm install
Add devDependencies in package.json:
{
"devDependencies": {
"@types/vscode": "^1.90.0",
"@types/node": "^20.0.0",
"typescript": "^5.0.0",
"@vscode/vsce": "^3.0.0"
},
"dependencies": {
"vscode-languageclient": "^9.0.0"
}
}
Daily loop
# terminal 1 — Rust server
pixi run build
# terminal 2 — extension
cd editors/vscode
npm run watch # esbuild --watch
# VS Code: F5 to launch Extension Development Host
Set absolute path to debug binary in Development Host settings:
{
"spiceLsp.serverPath": "/path/to/spice-lsp/target/debug/spice-lsp"
}
Or use launch.json env / preLaunchTask to build Rust first.
Verify integration
Follow Demo and testing VS Code section.
Bundling the server binary
The Marketplace extension ships a platform-specific binary inside the .vsix under bin/<platform>-<arch>/:
| Platform id | OS / arch | Notes |
|---|---|---|
linux-x64 | Linux x86_64 | Linked for glibc 2.31+ (Ubuntu 20.04 / Debian 11+) via Zig |
linux-arm64 | Linux ARM64 | Same glibc 2.31 floor |
darwin-x64 | macOS Intel | |
darwin-arm64 | macOS Apple Silicon | |
win32-x64 | Windows x64 |
There is no win32-arm64 bundle today. Unsupported platforms must set spiceLsp.serverPath or put spice-lsp on PATH.
Linux CI builds use scripts/zig-cc-*.sh so binaries from ubuntu-latest (glibc 2.39) still load on hosts with glibc 2.31. A plain cargo build on a newer distro may require a newer glibc — use the Zig wrappers for release artifacts.
At activation, the extension resolves the binary in this order:
spiceLsp.serverPathsetting (if set)- Bundled binary at
bin/<platform>-<arch>/spice-lspinside the extension - Local dev paths under
target/debugortarget/release(F5 from this repo) spice-lsponPATH
Package locally
Build a release binary for the current platform and create a .vsix:
pixi run build
pixi run ext-package
# output: editors/vscode/spice-lsp-0.2.0.vsix
Install side-loaded:
code --install-extension editors/vscode/spice-lsp-0.2.0.vsix
CI release workflow
The Release VS Code extension workflow runs on every push to main (and on manual workflow_dispatch / vscode-v* tags):
- Bumps the patch version in
editors/vscode/package.jsonand commits it tomain - Cross-compiles
spice-lspfor all supported platform ids - Assembles a single
.vsixcontaining every platform binary - Uploads the VSIX as a GitHub Actions artifact
- Creates a GitHub Release tagged
vscode-v<version> - Publishes to the VS Code Marketplace from the same package job (
VSCE_PATrequired)
| Strategy | Pros | Cons |
|---|---|---|
| User PATH | Simplest for local dev | Poor UX for end users |
Setting serverPath | Flexible | Manual configuration |
Bundle in .vsix | Works offline; Marketplace default | Larger artifact; CI builds all platforms |
| Download from GitHub Releases on activate | Small VSIX | Requires network on first run |
The Marketplace release uses bundle in .vsix.
TextMate grammar
Syntax highlighting ships as:
editors/vscode/syntaxes/spice.tmLanguage.json
Registered in package.json:
"grammars": [{
"language": "spice",
"scopeName": "source.spice",
"path": "./syntaxes/spice.tmLanguage.json"
}]
The grammar colors * / ; / $ comments, . directives, instance lines, and numeric literals. Tree-sitter-based highlighting via nvim-treesitter is separate; VS Code can adopt semantic tokens when the LSP advertises semanticTokensProvider (future).
Marketplace listing icon: editors/vscode/media/icon.png (package.json "icon" field).
Publishing
One-time Marketplace setup
Do this once before the first CI publish succeeds:
- Sign in to the Visual Studio Marketplace publisher management page with a Microsoft account.
- Create a publisher whose Publisher ID matches
editors/vscode/package.json(AmirhosseinDavoodyin this repo). The ID is permanent and must match exactly. - Create a Personal Access Token in Azure DevOps (not portal.azure.com):
- Open https://dev.azure.com and sign in with the same Microsoft account used for the Marketplace publisher.
- If prompted, create a free Azure DevOps organization (any name is fine; it is only a container for the PAT).
- Click your profile avatar (top right) → Personal access tokens
Direct link: https://dev.azure.com/_usersSettings/tokens - + New Token:
- Name: e.g.
vscode-marketplace - Organization: All accessible organizations
- Expiration: choose a duration you are willing to rotate
- Scopes: Custom defined → enable Marketplace → Manage
- Name: e.g.
- Create and copy the token immediately (it is shown once)
- In the GitHub repo: Settings → Secrets and variables → Actions → New repository secret
- Name:
VSCE_PAT - Value: the Azure DevOps PAT from step 3
- Name:
- Confirm Marketplace listing metadata is ready in
editors/vscode/:README.md(Marketplace landing page — include a Quick start so users know what to do after install)LICENSE(MITmatchespackage.json)publisher,displayName,description,engines.vscode
Optional local dry-run before relying on CI:
pixi run build
pixi run ext-package
# output: editors/vscode/spice-lsp-<version>.vsix
# packaging fails if the esbuild bundle is missing LanguageClient or terminateProcess.sh
The extension is esbuild-bundled (npm run compile → out/extension.js with vscode-languageclient inlined). Package and publish with vsce … --no-dependencies so the VSIX does not ship node_modules.
Release from CI (automatic)
Every push to main runs Release VS Code extension:
- Patch-bumps
editors/vscode/package.json(e.g.0.2.0→0.2.1) - Commits
chore(vscode): bump extension to <version>(viaGITHUB_TOKEN, which does not re-trigger the workflow) - Builds platform binaries, packages the VSIX, creates GitHub Release
vscode-v<version>, and runsvsce publish
Manual options:
- Actions tab → Release VS Code extension → Run workflow — optional bump + publish flags
- Push a tag
vscode-v*from your machine to publish the version already inpackage.json(no auto-bump)
The workflow always uploads the .vsix as an Actions artifact.
Release manually
pixi run build
pixi run ext-package
cd editors/vscode
# bump version first if this version was already published
npm version patch --no-git-tag-version
npx vsce publish --no-dependencies --packagePath "$(ls -t *.vsix | head -1)" # requires VSCE_PAT
Pre-publish checklist:
- Publisher ID matches
package.jsonpublisherfield -
VSCE_PATrepository secret configured -
README.mddescribes bundled-binary behavior for end users -
LICENSEaligned with repo (MITinpackage.json) -
engines.vscodeset to tested minimum version
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Server not starting | Binary not on PATH / wrong serverPath / unsupported platform | Set spiceLsp.serverPath, then SPICE LSP: Restart Server; check Output → SPICE Language Server |
unexpected argument '--stdio' / server exits code 2 | Bundled binary predates the --stdio CLI flag (needed by TransportKind.stdio) | Update the Marketplace extension, or build from source and set spiceLsp.serverPath |
version 'GLIBC_2.3x' not found | Host glibc older than the binary | Update to a Marketplace build linked for glibc 2.31+, or build locally and set spiceLsp.serverPath |
spiceLsp.restartServer / spiceLsp.setDialect / spiceLsp.createDemoFolder not found | Extension never activated, activate hung on LSP start, or Marketplace build predates the command (setDialect needs ≥ 0.2.10; Create Demo Folder is newer) | Update the extension; reload the window; open a .cir/.sp file or run the command (auto-activates). Prefer builds that register commands before awaiting client.start() |
| No SPICE Language Server in Output | Extension did not activate | Open a SPICE file or run SPICE LSP: Restart Server / Set Dialect…; check Developer: Show Running Extensions for activation errors |
| Extension activates with module errors | Unbundled VSIX missing node_modules | Use an esbuild-bundled release (vsce package --no-dependencies after npm run compile) |
| No diagnostics | Wrong language id | Ensure file extension maps to spice |
| Stale diagnostics | Server crash | Check Output → SPICE Language Server; restart server |
| Wrong binary arch | Download mismatch / unsupported platform | Pick correct release asset or build from source |
Beyond VS Code
The same spice-lsp binary enables other editors:
| Editor | Integration |
|---|---|
| Neovim | vim.lsp.enable or lspconfig custom server |
| Helix | language-server.spice-lsp in user config |
| Zed | Community extension calling the binary |
VS Code is the reference client; keep editor-specific code out of Rust.
Related
SPICE Netlist LSP and Formatter Design Document
1. Executive Summary
This document defines the system design, capabilities, and requirements for a Language Server Protocol (LSP) and Formatter tailored for SPICE (Simulation Program with Integrated Circuit Emphasis) netlists. The goal is to improve developer velocity, reduce syntax errors, and enforce stylistic consistency across analog and mixed-signal simulation workflows.
2. System Capabilities
2.1 Language Server Protocol (LSP) Capabilities
The LSP server implements the following capabilities to provide real-time IDE feedback:
- Syntax and Semantic Diagnostics:
- Syntax: Missing
.ends, bad line continuations, parse errors. - Symbols: Duplicate component identifiers, undefined model/subcircuit references; include/lib path issues.
- Connectivity (planned): Dangling nodes (single terminal connection) and floating nets (no DC path to ground). Severity warning; configurable. See Dialect reference and net semantics.
- Syntax: Missing
- Navigation (Go to Definition & Find References):
- Resolve references for subcircuits (
.subckt) and models (.model), including through.include/.lib. - On
.lib 'file' entry/.includelines, jump from the path to the file and from a.libentry name to the section header. - Map parameter definitions (
.param) to their usages in expressions.
- Resolve references for subcircuits (
- Autocomplete and Snippets (planned):
- Offer context-aware suggestions for basic elements (R, C, L, diodes, transistors).
- Provide templates for simulation directives (e.g.,
.tran,.ac,.dc,.temp).
- Hover Documentation:
- File-local: Subcircuit pin order, in-file model parameters.
- Dialect reference: Curated documentation for directives (
.tran,.ac),.optionkeywords, element types, and common expressions — authored per dialect in areference/corpus the LSP loads at runtime, not hard-coded in server logic. Coverage grows over time as you add entries for Ngspice, LTspice, and HSPICE.
- Document Outline (Symbols):
- Index hierarchical structures, isolating
.subcktblocks,.modeldefinitions, and control blocks.
- Index hierarchical structures, isolating
2.2 Formatter Capabilities
The formatting engine processes netlist files to enforce consistent layouts:
- Columnar Alignment: Align component names, nodes, model references, values, and parameters in tabular columns.
- Case Normalization: Enforce uppercase, lowercase, or preserve for leading
.directivekeywords. - Continuation-Line Standardization: Fold and re-wrap multi-line statements with the
+character using predictable indentation. - Comment Preservation: Keep line-start comments (
*,;,$); normalize spacing before inline;comments.
3. System Requirements
3.1 Functional Requirements
- Dialect Support: The parser must support standard SPICE variants, specifically LTspice, Ngspice, and HSPICE syntax.
- Performance: Code diagnostics must execute in under 100ms on files up to 50,000 lines.
- Robustness: The parser must gracefully recover from syntax errors to continue indexing subsequent parts of the file.
3.2 Technical & Architectural Requirements
- Parser Technology: Implement the parser using a formal grammar parser-generator like Tree-sitter. This ensures incremental parsing capability for low-latency editing.
- Communication Protocol: Conform strictly to the official LSP specification (JSON-RPC 2.0).
- Distribution: Package the LSP as a standalone executable (compiled Rust) with no external runtimes required.
4. Development plans
Capabilities that are not yet shipped, in rough priority:
| Focus | Notes |
|---|---|
| Completion | Element/directive suggestions; reuse reference corpus for docs |
| Connectivity | Dangling-node and floating-net diagnostics — Dialect reference and net semantics |
| Large-file / extracted mode | Size-gated defs-only analysis shipped (spiceLsp.analysisMode); lazy includes / incremental parse still open — Large-file / extracted mode |
| Deeper dialect grammar | LTspice / HSPICE parse quirks beyond the shared grammar |
Shipped behavior is documented in Architecture and LSP features. Multi-dialect selection and corpus authoring: Multi-dialect support.
Demo and test strategy
| Layer | Method |
|---|---|
| Grammar | Tree-sitter corpus + Rust fixture tests |
| Parser | Golden diagnostics on test-data/invalid/* |
| LSP | JSON-RPC harness over stdio (subprocess or mock client) |
| VS Code | Extension Development Host (F5), Problems panel |
| CI | pixi install && pixi run test on every push |
Manual smoke: open test-data/invalid/unclosed-subckt.cir, fix .ends, confirm diagnostic clears. See Demo and testing.
VS Code as primary client
Distribution path:
- Development:
spiceLsp.serverPathpoints attarget/debug/spice-lsp - Side-load:
.vsixbuilt withvsce package - General availability: Marketplace publish with platform-specific bundled binaries
Extension architecture (thin Node client, Rust server): VS Code integration.
5. System Architecture & Implementation
5.1 Implementation Language
The LSP and formatter are implemented in Rust to satisfy the low-latency and performance requirements (<100ms on 50k lines) while guaranteeing thread safety and memory efficiency without a garbage collector.
5.2 Architecture & Design
The system uses a classic compiler frontend architecture integrated into an event-driven JSON-RPC server:
[IDE Client]
│ (LSP over JSON-RPC 2.0 via StdIO)
▼
┌────────────────────────────────────────────────────────┐
│ LSP Server (tower-lsp) │
│ │ │
│ ├─► [Parser Engine] ─────────────────────────────┐ │
│ │ Incrementally parses buffer into Tree-sitter │ │
│ │ Concrete Syntax Tree (CST) │ │
│ │ │ │
│ ├─► [Diagnostics Analyzer] ◄─────────────────────┘ │
│ │ Syntax, symbols, include graph; connectivity │
│ │ planned │
│ │ │
│ ├─► [Reference Index] ◄── reference/<dialect>/ │
│ │ Hover docs for directives, options, elements │
│ │ │
│ └─► [Formatter Engine] ◄─────────────────────────┘ │
│ Columnar alignment, continuations, keyword case │
└────────────────────────────────────────────────────────┘
- LSP Layer: Handles connection lifecycle, text document synchronization, and capability routing.
- Incremental Parsing: Tree-sitter maintains an active syntax tree; edits re-parse only changed ranges.
- Reference Index: Loads structured JSON entries from
reference/per active dialect; powerstextDocument/hoverand will enrich completion documentation. Maintained manually over time — see Dialect reference and net semantics. - Net Graph (planned): Builds terminal connectivity from instance lines; emits dangling-node and floating-net warnings.
- Formatting Pipeline: Line/token pretty-print → column rules →
TextEdit(full document).
5.3 Key Dependencies
tower-lsp: High-level LSP implementation framework for Rust built on Tokio.tree-sitter: Rust bindings to the incremental parsing library.tree-sitter-spice: Custom grammar for parsing SPICE dialects.serde/serde_json: Serialization and deserialization of LSP messages.clap: Robust command-line argument parser for standalone formatter CLI execution.
Multi-dialect support design
Design for issue #16: selectable SPICE dialects (default HSPICE), retained Ngspice support, a maintainable system for growing syntax/reference knowledge, and reuse of that data for hover (and later completion).
Status: Dialect switch and reference corpus + hover are implemented. Remaining work: dialect-sensitive diagnostics/grammar and deeper LTspice coverage.
Related: Dialect reference and net semantics, LSP features, Architecture.
1. Goals and non-goals
Goals
| Goal | Detail |
|---|---|
| User-selectable dialect | VS Code setting + Command Palette command |
| Default = HSPICE | Matches issue #16; Ngspice remains fully supported |
| Shared knowledge system | One corpus drives diagnostics policy, hover, and (later) completion docs |
| Low-friction authoring | Adding a directive/element/rule is mostly data + a test, not scattered Rust strings |
| Keep current Ngspice behavior | Existing fixtures and diagnostics stay green under ngspice |
Non-goals (this design / first implementation slices)
- Full HSPICE / LTspice grammar parity on day one
- Scraping simulator manuals at runtime (bash-lsp
man/ explainshell style) - Per-file dialect auto-detection from content (may come later as a hint)
- Formatter dialect profiles (planned)
- Connectivity analysis (planned; dialect-agnostic graph with dialect-specific ground aliases later)
2. Lessons from reference systems
2.1 Ruff (astral-sh/ruff, rules docs)
What they do well
- Single source of truth next to behavior: rule docs live as structured
///sections on the violation type;cargo dev generate-docsprojects them to Markdown for the public site. - Registration table + codegen:
codes.rs+ proc macros produce theRuleenum and metadata accessors so “forgot to register” fails loudly. - CI gates:
generate-all --mode checkandcheck_docs_formatted.pyreject missing sections / stale generated output. - Stable IDs + human names: codes and kebab-case names with redirects.
What we should not copy wholesale
- Embedding long simulator-manual prose in Rust doc comments (wrong medium for SPICE).
- A giant proc-macro registry for hundreds of rules before we need it.
- MkDocs Material as a second doc site unless we later publish a public “reference catalog.”
Takeaway for spice-lsp: treat checked-in structured data as the SSOT (like Ruff treats rule metadata), generate indexes / book pages / Rust embeds from it, and CI-check that generated artifacts match.
2.2 bash-language-server (bash-lsp/bash-language-server)
What they do well
- Layered hover: optional rich external docs (explainshell) → shell
help/man→ file-local symbol comments. - Markdown LSP contract: hover is always
MarkupContentmarkdown. - Memoization of expensive doc lookups.
- Opt-in external services (explainshell off by default).
What we should not copy
- Runtime
man/ network scrape as the primary SPICE reference (manuals are not onman, dialects diverge). - Detecting a “dialect” (shebang) without switching documentation corpora.
- Letting external docs replace file-local hover instead of stacking with it.
Takeaway for spice-lsp: keep a priority chain for hover (reference corpus → file-local CST → nothing), cache the corpus at startup, never require an external service for basic tips.
3. Product behavior
3.1 Dialects
| Id | Label | Initial role |
|---|---|---|
hspice | HSPICE | Default |
ngspice | Ngspice | Current parser/diagnostics baseline; keep working |
ltspice | LTspice | Stub corpus + setting value; grammar/rules grow later |
Unknown dialect values → error diagnostic on initialize / config change, fall back to hspice with a logged warning.
3.2 How the user chooses
- Setting:
spiceLsp.dialect— enumhspice|ngspice|ltspice, defaulthspice. - Command:
SPICE LSP: Set Dialect…— QuickPick; writes the setting (workspace if a folder is open, else user) and restarts / notifies the server. - Status bar (recommended in the same slice): show current dialect; click opens the QuickPick.
Optional later (not required for #16):
# spice-lsp dialect=hspicefile header /.spice-lsp.toml- Infer from path heuristics (
*.spin an HSPICE tree) as a suggestion only
3.3 Client ↔ server contract
initialize.initializationOptions.dialect → "hspice" | "ngspice" | "ltspice"
workspace/didChangeConfiguration → spiceLsp.dialect
Extension always sends the resolved dialect on start and on change. Server stores it per-session (workspace-wide for v1; per-document overrides later).
Changing dialect:
- Re-analyze all open documents with the new dialect profile.
- Republish diagnostics.
- Clear hover/completion caches keyed by dialect.
4. Architecture: one corpus, many consumers
┌─────────────────────────────────────┐
│ reference/ (SSOT, authored data) │
│ schema + per-dialect entries │
└──────────────┬──────────────────────┘
│
pixi run reference-codegen / validate
│
┌───────────────────────┼───────────────────────┐
▼ ▼ ▼
spice-reference crate docs book pages CI snapshots
(embedded index) (optional catalog) (hover / schema)
│
▼
spice-parser ←── DialectProfile (syntax flags, comment rules, …)
│
▼
spice-lsp (diagnostics, hover, later completion)
│
▼
VS Code extension (setting, command, status bar)
Principle: Rust implements mechanisms (parse, index, lookup, render). Humans author knowledge as data under reference/. Parser dialect quirks that cannot be expressed as data yet live in a small DialectProfile table in Rust, keyed by the same dialect ids.
5. Reference corpus (Ruff-inspired authoring)
5.1 Layout
Evolve the plan in § Dialect reference with an explicit shared + override model:
reference/
├── schema.json # JSON Schema for entries
├── _shared/ # constructs common across dialects
│ ├── directives/
│ │ └── subckt.json
│ └── elements/
│ └── R.json
├── hspice/
│ ├── dialect.toml # metadata: display name, aliases, comment styles
│ ├── directives/
│ │ └── option.json # HSPICE-specific or override
│ └── elements/
├── ngspice/
│ ├── dialect.toml
│ └── …
└── ltspice/
├── dialect.toml
└── …
5.2 Entry shape (v1)
{
"id": "hspice.directive.tran",
"kind": "directive",
"name": ".tran",
"summary": "Transient analysis",
"syntax": ".TRAN tstep tstop [tstart [tmax]] [UIC]",
"parameters": [
{ "name": "tstep", "description": "Printing / sampling step.", "units": "s" }
],
"examples": [".TRAN 1p 10n"],
"seeAlso": ["hspice.directive.option"],
"diagnostics": ["spice/unknown-directive"],
"since": "0.3.0",
"dialectNotes": "HSPICE accepts …"
}
Required sections (CI-enforced, Ruff-style): id, kind, name, summary, syntax.
Optional: parameters, examples, seeAlso, diagnostics, deprecated, dialectNotes.
5.3 Merge rules
- Load
_shared/as base for the active dialect. - Overlay
reference/<dialect>/byid/(kind, name)— dialect file wins. - Missing entry → no hover / no completion doc (not an error). Gaps are filled by adding JSON.
5.4 Codegen / validation tasks
| Task | Purpose |
|---|---|
pixi run reference-validate | Load embedded corpus; unit tests for merge/lookup |
pixi run reference-docs | Write mdBook pages under docs/reference/ from the corpus |
pixi run reference-docs-check | CI: fail if catalog markdown drifts from JSON |
Authoring workflow (add a new directive):
- Add/edit
reference/<dialect>/directives/foo.json(or_shared/if universal). pixi run reference-validate.pixi run reference-docs(regenerate catalog chapters).- Add hover snapshot fixture under
test-data/hover/<dialect>/when needed. pixi run test.- No Rust change unless a new kind or lookup path is needed.
This is the spice-lsp analogue of Ruff’s “add rule → docs fall out of metadata,” with JSON as the authoring surface instead of /// comments.
6. DialectProfile (syntax / semantics knobs)
Until grammars fully diverge, keep a Rust profile beside the corpus:
#![allow(unused)]
fn main() {
struct DialectProfile {
id: DialectId,
// Comments recognized for toggle / highlighting hints
line_comment_prefixes: &'static [&'static str], // e.g. hspice: ["*"], ngspice: ["*", ";", "$"]
// Directives treated as unknown → warning vs ignore
unknown_directive_severity: Severity,
// Element letter sets, continuation rules, case sensitivity, …
}
}
v1 behavior
| Concern | hspice | ngspice | ltspice |
|---|---|---|---|
| Parse grammar | Current line-oriented grammar (shared) | Same | Same |
| Comment styles (docs / future toggle) | * primary | *, ;, $ | $ / * (document; refine later) |
| Semantic diagnostics | Same engines; corpus may gate “unknown directive” lists | Current fixtures | Minimal |
| Hover | hspice corpus (+ _shared) | ngspice corpus | ltspice stub |
Later: dialect-specific Tree-sitter grammars or grammar injections only when shared tokens are insufficient (do not fork three full grammars prematurely).
7. Hover design (bash-lsp layering + corpus)
7.1 Resolution order
cursor token
1. dialect reference lookup (kind + name + active dialect)
2. file-local hover (subckt pins, in-file .model / .param)
3. null
Never call out to the network. Render markdown:
### `.tran` — Transient analysis
**Dialect:** HSPICE
.TRAN tstep tstop [tstart [tmax]] [UIC]
| Parameter | Description | Units |
|-----------|-------------|-------|
| tstep | … | s |
**Examples**
- `.TRAN 1p 10n`
7.2 Mapping cursor → entry
- Classify line / token: directive name, element type letter,
.optionkeyword, etc. (reuse / extend CST + symbol index). - Build key
(dialect, kind, normalized_name). - Lookup in embedded index; try dialect overlay then
_shared.
7.3 Same data for completion (follow-on)
Completion items attach documentation from the same entry. No parallel doc strings in Rust.
8. VS Code extension changes
| Item | Change |
|---|---|
package.json settings | spiceLsp.dialect enum, default hspice |
| Command | spiceLsp.setDialect → QuickPick |
| Status bar | HSPICE / Ngspice / LTspice |
LanguageClient init | initializationOptions: { dialect } |
| Middleware / config listener | On dialect change → DidChangeConfiguration + optional restart if needed |
| Marketplace README | Document default HSPICE; how to switch to Ngspice |
TextMate grammar stays shared initially; dialect-specific highlighting can wait.
9. Delivery status
Shipped — Dialect switch
- Setting + command + status bar; default hspice.
- Server accepts dialect; re-analyzes on change.
DialectProfilefor dialect metadata; parsing still largely shared across dialects.- Docs: default dialect, how to switch.
Shipped — Reference crate + hover
reference/schema +_shared+hspice/ngspice/ stubltspiceentries.- HSPICE overlays for analysis/control directives (
.data, multi-mode.dc,.op, plus.ac/.measure/.probe/.lib/ …). spice-referencecrate + validate/codegen pixi tasks.textDocument/hoverwith layered resolution and snapshot tests per dialect.- Catalog pages under
docs/reference/generated from the corpus (reference-docs/reference-docs-check).
Planned — Dialect-sensitive diagnostics / grammar
- Unknown-directive / option lists from corpus.
- Comment / continuation profile differences.
- Split grammar only where needed; grow LTspice.
10. Testing strategy
| Layer | Tests |
|---|---|
| Schema | Every JSON entry validates; required sections present |
| Merge | Overlay wins; shared fallback works |
| LSP | initialize with dialect; didChangeConfiguration republishes |
| Hover | Fixtures per dialect; missing entry → null |
| Regression | Existing Ngspice stdio tests run with explicit ngspice |
| Extension | Setting default is hspice; command updates config (smoke / manual) |
11. Risks and decisions
| Topic | Decision |
|---|---|
| Default dialect | HSPICE per #16 (overrides earlier docs that said Ngspice default) |
| One grammar vs many | One shared grammar for now; profile flags first |
| Doc authoring medium | JSON under reference/, not Rust comments |
| External doc services | Out of scope; optional later, opt-in only |
| LTspice | Enum + stub corpus early; deep support later |
| Breaking change | Default dialect change may surprise Ngspice users — document prominently; one-click switch |
12. Open questions
- Should dialect be workspace-only or allow per-file override?
- Do we keep the full corpus embedded in the binary, or also support loading from an extension-relative path for faster iteration?
- Which remaining HSPICE / LTspice constructs matter most for authoring flows?
- Any UX beyond status bar + Command Palette for dialect switching?
13. Implementation checklist
-
spiceLsp.dialect+spiceLsp.setDialect+ status bar - Server session dialect + config update path
-
DialectProfile+ Ngspice parity tests underngspice - Update LSP features, limitations, Marketplace README for default HSPICE
- Scaffold
reference/schema.json,_shared/,hspice/,ngspice/ - Expand HSPICE corpus:
.data,.dc(sweep modes),.op, plus common controls (.ac,.measure,.probe,.lib, …) -
spice-reference+ validate/codegen tasks - Hover provider + snapshots
- Close #16
- Catalog docs from JSON (
docs/reference/,reference-docs/reference-docs-check) - Dialect-sensitive diagnostics / grammar splits
14. References
- Issue: https://github.com/amirhosseindavoody/spice-lsp/issues/16
- Ruff rules: https://docs.astral.sh/ruff/rules/
- Ruff repo (docs codegen):
crates/ruff_dev/src/generate_docs.rs,scripts/generate_mkdocs.py,CONTRIBUTING.md(“Adding a new rule”) - bash-language-server hover:
server/src/server.ts,server/src/util/sh.ts,server/src/analyser.ts(explainshell) - Existing spice-lsp plan: Dialect reference and net semantics
Large-file / extracted-netlist mode
Design for opening post-layout and other extracted netlists (tens to hundreds of MB) in the editor without OOM or multi-second hangs, while keeping full analysis for normal schematic-scale decks.
Status: Implemented (size gate, defs-only index, thinned diagnostics, settings). Lazy include materialization and Tree-sitter incremental reuse remain follow-ups.
Related: Limitations, Architecture, Principles, Include and library resolution, LSP features, Design.
1. Problem
spice-lsp today eagerly:
- Holds the full buffer as a
String - Fully re-parses with Tree-sitter (no incremental
old_treereuse yet) - Builds an
Indexthat records every instance plus hierarchical outline children - Loads and indexes
.include/.libtargets for definition resolution
That matches interactive schematic netlists (thousands to tens of thousands of lines). It does not match extracted dumps (DSPF/SPEF-style or flattened SPICE) where a single open file can be ~300 MB and almost entirely instance lines.
Full per-instance symbol resolution at that scale is not feasible with the current model: source + dense owned symbol/outline tables amplify memory to multiple times the file size, and LSP payloads (outline, diagnostics) would overwhelm the client even if the server survived.
Performance targets in Design (~50k lines / <100 ms semantic) and Principles (typical <5k lines) already describe a different operating region.
2. Lessons from Astral’s ty
ty (Astral’s Python type checker / LSP; engine in astral-sh/ruff) is built for large projects: millions of lines across many modules, with millisecond incremental updates after edits. Public summary: language server docs, announcement.
2.1 What they do well
| Pattern | Detail |
|---|---|
| LSP-first incrementality | Analysis is a Salsa query graph; edits invalidate only dependent queries down to individual definitions |
| Coarse then fine | Parse a whole file once; expensive work (type inference) is per-scope and reusable |
| Lazy dependency work | Skip large parts of third-party code until imports / open files require it |
| Memory pressure control | Drop ASTs after checking and reparse on demand; planned LRU eviction for dominant caches |
| Diagnostic scope | Default openFilesOnly; optional workspace diagnostics; prefer pull diagnostics over push-everything |
| Interning | Deduplicate paths and types via the query DB |
2.2 What we should not copy wholesale
| ty choice | Why not for spice-lsp (yet) |
|---|---|
| Full Salsa database | Large architectural bet; valuable later for multi-file schematic projects, overkill as the first fix for one huge buffer |
| Definition-level type inference granularity | Extracted SPICE pain is open/index cost of millions of similar lines, not “recheck one function” |
| Workspace-scale symbol search as a goal for extracted dumps | Searching millions of X/M/R instance names is rarely useful |
2.3 Problem-shape caveat
ty’s “large” usually means many moderate files with fine-grained edits. A single ~300 MB netlist is a different stressor (they already see pain on ~28 MB dense stubs). Steal policy (lazy, layered, scoped diagnostics, drop heavy IR); do not assume Salsa alone makes full instance indexing viable.
3. Goals and non-goals
Goals
| Goal | Detail |
|---|---|
| Open large extracted files safely | Size/line gate; never build a multi‑GB instance index by default |
| Useful navigation on structure | Go-to-definition for .subckt / .model / .param (local + lazy include/lib) |
| Keep schematic UX unchanged | Files under the threshold keep today’s full index, outline, and diagnostics |
| Bounded LSP payloads | Cap or omit instance outline children; avoid flooding diagnostics |
| Clear mode signaling | Status / log / optional diagnostic so users know analysis is thinned |
Non-goals (this design)
- Full per-device symbol tables for extracted dumps
- Find-all-references across millions of instances
- Connectivity / net-graph analysis on extracted top-levels
- Formatter rewrites of 100+ MB buffers
- Adopting Salsa in the first implementation slice
4. Proposed modes
| Mode | When | Index | Outline | Diagnostics | Includes |
|---|---|---|---|---|---|
| Full (default) | Buffer below threshold | Definitions + instances + refs | Hierarchical, including instances | Current syntax + semantic set | Eager definition graph (current behavior) |
| Extracted | Buffer at/above threshold, or user override | Definitions only (.subckt, .model, .param) | Structure only (no instance children) | Syntax + cheap checks; skip scans that walk every instance | Lazy: resolve on navigation / unknown-model as needed; prefer defs-only indexes for closed files |
Optional third lever later: spiceLsp.analysisMode = "auto" | "full" | "extracted" so users can force either side of the gate.
4.1 Threshold
Start with a simple gate, e.g.:
text.len() >= Nbytes (suggested starting point: 16–32 MiB), and/or- line count ≥ 200k
Exact numbers should be tuned with a fixture and RSS measurements; document the defaults in settings and Limitations when implemented.
4.2 Feature matrix in extracted mode
| Feature | Behavior |
|---|---|
| Syntax diagnostics | Keep (Tree-sitter / line classify); consider debouncing more aggressively |
| Duplicate instance names | Off or sampled — full scan is O(instances) |
| Unknown model / subckt | On for references that are checked; may require lazy include lookup of definition maps only |
| Document symbols | Subcircuits, models, params; no per-instance children |
| Go to definition | Definitions in-file + lazy include/lib |
| Find references | File-local refs among indexed symbols only (defs / sparse refs); do not promise all instance hits |
| Hover (reference corpus) | Unchanged (line-local token + corpus) |
| Hover (file-local instance) | Best-effort from the current line without a global instance table |
| Completion / formatter / connectivity | Remain out of scope or explicitly disabled on huge buffers |
5. Architecture sketch
Keep the existing crate split; add a policy layer in analysis rather than a second parser.
didOpen / didChange
→ choose AnalysisProfile { Full, Extracted }
→ parse / classify lines (shared)
→ build_index(profile)
Full: today’s symbols + outline
Extracted: definitions (+ optional sparse model refs), thin outline
→ includes
Full: eager resolve (current)
Extracted: stub graph; materialize IncludedFile defs on demand
→ publish diagnostics / serve navigation
5.1 Index changes (spice-parser)
- Extend
build_index(or wrap it) with a profile flag:- Skip
SymbolKind::Instanceinsertion - Skip pushing instances into
document_symbolschildren - Optionally still record model/subckt name references from instance lines into the references map without storing an instance
Symbolper line (supports unknown-model + goto without GB-scale vectors)
- Skip
- Prefer interned or borrowed name keys where practical later; first slice can keep
Stringkeys if instance rows are omitted
5.2 Include graph (spice-parser / LSP)
- In extracted mode, do not retain full text + full
Indexfor every include by default - Materialize definition maps when resolving go-to-definition or unknown-model
- Cap concurrent materialized includes; depth cap already exists (
DEFAULT_MAX_INCLUDE_DEPTH)
5.3 LSP backend (spice-lsp)
- Gate on
didOpen/ after largedidChange(rare for extracted files) - Avoid cloning entire workspace buffers into analyze snapshots when possible
- Prefer pull diagnostics later; until then, publish a small diagnostic set in extracted mode
- Log once:
spice-lsp: extracted analysis mode (N bytes) — instance indexing disabled - Cancel or
spawn_blockinglong analyzes so the server stays responsive
5.4 Easy wins independent of mode
Worth doing even for schematic files (aligned with ty’s “don’t redo work”):
| Win | Notes |
|---|---|
Deduplicate parses in analyze_with_includes | Root is parsed multiple times today |
Persist Tree-sitter Tree + InputEdit | Matches documented incremental architecture |
| Line index / rope for UTF-16 ↔ byte mapping | Avoid O(file) scans per symbol conversion |
| Cap outline size | Even in full mode, enormous outlines are hostile to editors |
6. Comparison: ty patterns → spice-lsp actions
| ty pattern | spice-lsp action |
|---|---|
| Fine-grained incremental queries | Near term: cache parse/index per document; invalidate on change. Later: optional query-style layering if multi-file cost dominates |
| Skip irrelevant dependencies | Lazy .include / .lib in extracted mode |
| Drop AST after use | Already drop Tree; extend to “don’t retain instance IR” |
openFilesOnly / pull diagnostics | Thin diagnostic set + eventual pull; never push millions of warnings |
| Coarse parse, fine semantics | Line classify always; instance indexing and connectivity optional |
7. Implementation slices
| Slice | Status |
|---|---|
1. Gate + profile plumbing (AnalysisMode / AnalysisProfile, threshold) | Done |
| 2. Defs-only index + outline without instances; goto via line classify | Done |
| 3. Diagnostic thinning (no duplicate-name; sparse unknown-model) | Done |
4. Settings (spiceLsp.analysisMode, extractedByteThreshold) + docs | Done |
| 5. Defs-only indexes for includes when root is extracted / include is huge | Done |
| 6. Lazy include materialization (load on demand only) | Open |
| 7. Perf hygiene — single-parse path, incremental Tree-sitter, position index | Open |
Public docs: LSP features, Limitations, VS Code README.
8. Testing
| Layer | Approach |
|---|---|
| Unit | build_index with Extracted on a small fixture asserting zero instance symbols and retained .subckt/.model |
| Threshold | Force profile via test API or setting without needing a 300 MB file in CI |
| Integration | LSP: open “large” synthetic buffer; assert outline has no instance flood; goto def still works |
| Manual / bench | Optional local 100–300 MB extracted file: RSS + time-to-first-navigation; not required in CI |
Do not check multi‑hundred‑MB binaries into the repo.
9. Open questions
- Default threshold — bytes vs lines vs both; different defaults for
.spf/.netassociations? - Model refs without instance symbols — enough for unknown-model quality, or accept weaker checks in extracted mode?
- Editor still holds 300 MB — server-side wins don’t fix VS Code memory; document that opening such files is inherently heavy
- Partial / viewport analysis — analyze only visible ranges later? Powerful but much more complex; not in the first slices
- Salsa later? — revisit if multi-file schematic workspaces (many includes, frequent edits) become the bottleneck after extracted mode lands
10. Success criteria
When implemented, an extracted ~100–300 MB netlist should:
- Open without process kill / multi‑GB RSS from instance indexes
- Show a usable structural outline (subcircuits/models/params)
- Support go-to-definition for models/subcircuits with lazy includes
- Leave schematic-scale files behaviorally unchanged
- Document the mode and limits in Limitations