Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

CapabilityWhat it does
DiagnosticsSyntax errors plus warnings for duplicate names, unknown models/subcircuits, missing includes, and bad .lib sections
OutlineDocument symbols for subcircuits, models, parameters, and instances
Go to definition / find referencesJump 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 resolutionFollow .include / .inc and HSPICE .lib 'file' entry so models and subcircuits in other files participate in checks and navigation
HoverDialect reference docs (HSPICE by default) plus file-local detail for subcircuit pins and in-file models
Dialect selectionChoose HSPICE, Ngspice, or LTspice (spiceLsp.dialect) so hover and related behavior match your simulator
FormattingColumnar 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 .lib chains 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 installGetting Started
What the server implementsLSP Features
How crates and the pipeline fit togetherArchitecture
.include / .lib behaviorInclude and Library Resolution
Dialect docs and net semanticsDialect Reference and Net Semantics
Per-dialect reference pagesDialect reference catalog
Formatting rules and CLIFormatter
Building from source / CIBuild
Extension layout and publishingVS 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

ToolPurpose
pixiManages Rust, Node (for the VS Code extension), and build tasks
GitClone 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):

  1. Build the LSP binary: pixi run build
  2. Open the extension folder: editors/vscode
  3. Install JS dependencies: npm install
  4. Press F5 to launch an Extension Development Host with the SPICE extension loaded
  5. Open demo.cir and 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:

EditorConfiguration
Neovimlspconfig custom server block with cmd = { "spice-lsp" }
Helix[language-server.spice-lsp] in languages.toml
ZedExtension or lsp settings (once published)

File extensions to associate: .cir, .sp, .spf, .net, .ckt, .inc, .lib (dialect-dependent).

If you are new to the repo, follow this order:

  1. Read Principles — know what is in and out of scope
  2. Use Demo and testing — verify each layer before adding features
  3. Read Architecture — understand where new code belongs
  4. Skim Dialect reference and net semantics — hover corpus and connectivity plans
  5. Skim Include and library resolution — cross-file model/subckt resolution

Next steps

Principles

Goals, non-goals, and UX values for spice-lsp.

What good looks like

A developer editing a netlist in VS Code should get:

  1. Immediate syntax and semantic feedback — parse errors, duplicate names, unknown models
  2. Jump to definitions and a useful outline — subcircuits, models, parameters
  3. Dialect-aware documentation on hover — curated reference plus file-local pin/model detail
  4. Include-aware analysis.include / .lib participate in checks and navigation
  5. Consistent formatting (shipped) and completion (planned) — align netlists; suggest elements/directives later
  6. Connectivity warnings (planned) — dangling nodes and floating nets before simulation

Details on reference hover and connectivity: Dialect reference and net semantics.

Goals

  1. Fast feedback while editing — Diagnostics feel instant on typical netlists (< 5k lines). Tree-sitter incremental parsing is the foundation.
  2. Works offline — Single static binary; no cloud services; no simulator required for IDE features.
  3. 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.
  4. Catch connectivity mistakes before simulation — Flag dangling nodes and floating nets as warnings when analysis is confident enough.
  5. Editor-agnostic core — All language logic lives in the LSP binary. VS Code is the first client, not the only one.
  6. Testable at every layer — Parser fixtures, reference schema tests, hover snapshots, and LSP integration tests in CI.

Non-goals

Non-goalWhy
Running SPICE simulationsUse Ngspice/LTspice externally
Schematic captureNetlist text only
Auto-generating reference from PDF manualsYou author reference/ deliberately; quality over coverage
Full ERC/DRCFloating-net checks are heuristic helpers, not sign-off tools
Replacing simulator errorsWe 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 .DATA value 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

  1. pixi run test passes parser and LSP integration tests
  2. Invalid netlist in the editor shows a syntax diagnostic; fixing it clears the diagnostic without restart
  3. Go to definition reaches .model / .subckt across .include / .lib when paths resolve, and jumps from include/lib paths (and .lib entry names) into the target file or section
  4. Hover on a documented directive shows dialect reference text for the active dialect
  5. 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:

LayerResponsibilityStatus
1. ParseTree-sitter CST, syntax diagnosticsShipped
2. IndexSymbols, scopes, cross-references, include/lib graphShipped
3. AssistHover (reference + file-local); completionHover shipped; completion planned
4. Deep semanticsFormatter; net connectivityFormatter 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 / directoryRole
crates/spice-lspLSP server, JSON-RPC, document store; format CLI subcommand
crates/spice-parserParsing, symbol index, diagnostics, formatter (format_source)
crates/spice-referenceLoad 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

  1. Client connects via stdio; sends initialize with client capabilities and dialect option.
  2. Server responds with capabilities (incremental sync, diagnostics, symbols, definition, references, hover, formatting).
  3. Document open/change updates an in-memory map of open buffers.
  4. On each change (debounced ~150 ms):
    • Re-parse with Tree-sitter
    • Run diagnostic passes (syntax + semantic + include resolution)
    • Send textDocument/publishDiagnostics with the document version
  5. 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.
  6. Formatting pretty-prints the buffer via spice_parser::format_source and returns a full-document TextEdit when needed.
  7. 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

  1. Parse buffer → CST
  2. Collect ERROR / MISSING nodes and hand-written checks (e.g. unclosed .subckt)
  3. Map to LSP Diagnostic (Error)

Symbol index

Walk the CST to build:

  • Subcircuit and model definitions
  • Component instances and .param bindings

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

MetricTarget
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 editRe-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.

LSP Features

LSP methods spice-lsp implements today, plus a short note on planned work.

Capability matrix

LSP method / featureStatus
initialize / initializedShipped
shutdown / exitShipped
textDocument/didOpen / didChange / didCloseShipped
textDocument/publishDiagnosticsShipped
Syntax diagnosticsShipped
Duplicate / undefined symbol diagnosticsShipped
Include / .lib resolution diagnosticsShipped
textDocument/documentSymbolShipped
textDocument/definition / referencesShipped
textDocument/hover (dialect reference + file-local)Shipped
Dangling node / floating net diagnosticsPlanned
textDocument/formatting / rangeFormattingShipped
textDocument/completionPlanned
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

SourceExampleSeverity
Unclosed .subcktmissing .ends for subcircuit XError
Parse ERROR nodeunexpected tokenError
Missing CST childexpected node listError

Symbols and navigation

Document symbols for outline / breadcrumbs:

Symbol kindSPICE construct
Namespace.subckt block
Class.model
Variable.param
FieldInstance 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

SettingDefaultEffect
spiceLsp.analysisModeautoauto / full / extracted
spiceLsp.extractedByteThreshold16777216 (16 MiB)Size gate for auto

In extracted mode (forced, or auto when the buffer reaches the threshold):

  • Index keeps .subckt / .model / .param definitions
  • Instance symbols and outline children are omitted
  • spice/duplicate-name is 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

CodeExampleSeverity
spice/duplicate-nameduplicate component name 'R1'Warning
spice/unknown-modelmodel 'nfet' not definedWarning
spice/include-not-foundinclude file not found: 'models.inc'Warning
spice/lib-section-not-foundlibrary section 'TT' not foundWarning
spice/include-cycleinclude 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:

  1. Curated entry from reference/ for the active dialect (_shared fallback)
  2. File-local detail for .subckt / .model / .param symbols
  3. 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

CodeExampleSeverity
spice/dangling-nodenode 'bias' is connected to only one device terminalWarning
spice/floating-netnet 'internal' has no DC path to groundWarning

Published alongside other diagnostics in publishDiagnostics. Configurable via spiceLsp.diagnostics.* settings.

Client configuration

SettingTypeDefaultNotes
spiceLsp.dialectstring"hspice"Dialect switch
spiceLsp.libraryPathsstring[][]Include / .lib search path
spiceLsp.include.maxDepthnumber16Nested include / .lib depth cap
spiceLsp.diagnostics.danglingNodesbooleantruePlanned connectivity pass
spiceLsp.diagnostics.floatingNetsbooleantruePlanned connectivity pass
spiceLsp.groundNodesstring[]["0","gnd","GND"]Planned connectivity pass
spiceLsp.trace.serverstring"off"LSP trace level

Testing

Integration coverage includes:

  1. initialize returns expected capabilities
  2. Open invalid document → diagnostics notification
  3. Edit → updated diagnostics (debounced)
  4. documentSymbol returns hierarchical outline for .subckt blocks
  5. definition on subcircuit reference jumps to .subckt definition
  6. definition on .lib 'file' entry path opens the library file; on the entry name jumps to .lib entry
  7. references on subcircuit definition lists definition + usages; includeDeclaration: false omits the definition
  8. Semantic fixtures (duplicate-instance.cir, unknown-subckt.cir) produce warning codes
  9. 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

InputOutput
LSP textDocument/formattingTextEdit[] (full-document replacement when changed)
LSP textDocument/rangeFormattingSame 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 $param tokens stay intact)
  • Do not reorder or remove comment lines

Architecture

The formatter lives in crates/spice-parser (format_source):

  1. Split the buffer into physical lines and group + continuations into statements
  2. For contiguous instance blocks, compute per-column widths
  3. Pretty-print tokens (wrap at maxLineWidth)
  4. 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

OptionValuesDefault
indentWidthpositive integer2 (LSP tabSize when > 0)
keywordCaseupper, lower, preserveupper
alignColumnstrue, falsetrue
maxLineWidthnumber120 (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.

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 / .lib resolution for model and subcircuit definitions
  • Document formatting (textDocument/formatting) and spice-lsp format CLI
  • Debounced diagnostics on edit; textDocument/references honors includeDeclaration
  • Marketplace extension with bundled binaries, TextMate highlighting, and restart command
  • File associations for .cir, .sp, .spf, .net, .ckt, .inc, and .lib

Current limitations

LimitationWorkaround
Shared grammar for all dialectsPrefer common SPICE constructs; dialect-specific parse quirks grow over time; hover/docs already switch
No connectivity analysisManual 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 yetType element/directive names manually
Formatter has no dialect profiles yetShared 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 binarySet 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 .DATAPrefer + 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:

LimitationDetail
Single fileNet connectivity still ignores .include until a full cross-file net graph exists
Ground aliasesDefaults to 0, gnd, GND; exotic ground names may need config
False positivesIntentionally open probe points may warn until suppression exists
Not full ERCDoes not check layout, EM, or foundry rules
Ideal elementsVoltage sources and unusual topologies need careful graph rules

These warnings supplement — not replace — simulator and layout review.

Dialect differences

AreaNgspiceLTspiceHSPICE
Comments*, ;, $$ common*
Directives / optionsBaseline corpusOverrides 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 when spiceLsp.analysisMode is extracted): 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 didChange are debounced (~150 ms); navigation requests re-analyze on demand so the index stays current

Reporting issues

Include:

  1. Minimal netlist snippet
  2. Dialect (Ngspice / LTspice / HSPICE)
  3. 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)
.tranSyntax, parameters, units, dialect notes
.option keywordMeaning, default, valid values
M (MOSFET line)Terminal order, common parameters
{expression} in .paramAllowed 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

  1. Client sends active dialect via initializationOptions or spiceLsp.dialect setting (default hspice; command + status bar to switch — design).
  2. On textDocument/hover, the server maps the cursor CST node to a reference key (e.g. directive name, element letter, option token).
  3. Server renders Hover markdown from the entry: summary, syntax block, parameter table, examples.
  4. 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:

  1. Add or edit JSON under reference/<dialect>/ or reference/_shared/.
  2. Run pixi run reference-validate to load and exercise the embedded corpus.
  3. Run pixi run reference-docs to regenerate the Dialect reference catalog.
  4. Add or update hover snapshot tests when behavior changes.
  5. Ship with the binary (corpus is embedded at compile time via the spice-reference build script).

Prefer small, focused files over one giant manual. Link related entries with seeAlso.

Coverage status

AreaScope
ShippedInline hover from CST + curated reference/ lookup (HSPICE overlays for .data / .dc / .op and common controls; Ngspice baseline)
GrowingBroader 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

TermMeaningExample
GroundNode 0, gnd, GND, or dialect-specific ground aliasesC1 out 0 1u
Dangling nodeAppears on exactly one device terminal in the analyzed scopeNet bias only on R1 in bias with nothing on bias elsewhere
Floating netHas connections but no DC path to ground through R, L, V, I, or defined groundsUnconnected 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

CodeMessageSeverity
spice/dangling-nodenode 'bias' is connected to only one device terminalWarning
spice/floating-netnet 'internal' has no DC path to groundWarning

Attach diagnostics to the node token on the instance line when possible. Offer a single diagnostic per net, not one per terminal.

Configuration (planned)

SettingDefaultEffect
spiceLsp.diagnostics.danglingNodestrueEnable dangling-node pass
spiceLsp.diagnostics.floatingNetstrueEnable floating-net pass
spiceLsp.groundNodes["0", "gnd", "GND"]Treat as ground for path search

Limitations

  • Ignores nets from .include files 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

ScopeStatus
Duplicate instance namesShipped (separate from connectivity)
Dangling nodes and floating nets (single-file)Planned

Testing

LayerTest
ReferenceSchema validation on all reference/**/*.json
ReferenceHover snapshot: fixture + cursor → markdown
Connectivitytest-data/semantic/dangling-node.cir → one spice/dangling-node
Connectivitytest-data/semantic/floating-net.cir → one spice/floating-net
LSPIntegration test publishes warnings after open

See Demo and testing.

Include and library resolution

How spice-lsp resolves .model, .subckt, and related symbols across .include / .inc and HSPICE .lib files.

Goals

CapabilityBehavior
Follow .include / .incLoad the target file and merge its model/subcircuit definitions into resolution
Follow .lib 'file' entryLoad only the named .LIB entry.ENDL section from the library file
Unknown model/subcktspice/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 pathspice/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

FormMeaning
.include path / .inc pathInsert the whole file
.lib 'path' entryCall the named section in a library file (HSPICE-style)
.lib entry.endlSection 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

  1. Absolute path as written
  2. Relative to the directory of the file that contains the .include / .lib call
  3. Each entry in spiceLsp.libraryPaths (workspace or absolute folders)

LSP integration

RequestCross-file behavior
publishDiagnosticsUses include graph when publishing for an open document
textDocument/definitionMay return a Location in another file URI (model/subckt, include/lib path, or .lib entry section)
textDocument/referencesSame-buffer only
textDocument/documentSymbolSame-buffer only
textDocument/hoverFile-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

SettingTypeDefaultPurpose
spiceLsp.libraryPathsstring[][]Extra directories for resolving include/lib paths
spiceLsp.include.maxDepthnumber16Cap on nested include/lib depth

Diagnostics

CodeWhen
spice/include-not-foundPath does not resolve under search rules
spice/lib-section-not-foundFile loads but the requested .LIB entry is missing
spice/include-cycleInclude/lib graph would revisit a file already on the stack
spice/unknown-modelModel/subckt still missing after the include closure is merged

Limits

  • No workspace-wide workspace/symbol yet
  • No automatic PDK discovery beyond libraryPaths
  • LTspice / Ngspice .lib quirks beyond the HSPICE call/section pattern are best-effort
  • Nested .lib section 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

  1. Edit or add JSON under reference/_shared/ or reference/<dialect>/.
  2. Run pixi run reference-validate.
  3. Run pixi run reference-docs to regenerate these pages.
  4. CI runs pixi run reference-docs-check so the book stays in sync.

Design notes: Multi-dialect support.

Dialects

DialectSetting valueEntries (effective)Catalog
Shared base16Shared
HSPICEhspice25HSPICE
Ngspicengspice16Ngspice
LTspiceltspice16LTspice

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

NameKindSummary
.acdirectiveSmall-signal AC frequency analysis
.dcdirectiveDC sweep analysis
.enddirectiveEnd of netlist
.endsdirectiveEnd a subcircuit definition
.icdirectiveSet initial node voltages
.includedirectiveInclude another netlist file
.modeldirectiveDefine a device model
.opdirectiveDC operating-point analysis
.paramdirectiveDefine a named parameter
.printdirectivePrint analysis results as a table
.subcktdirectiveBegin a subcircuit definition
.tempdirectiveSet circuit temperature(s)
.trandirectiveTransient analysis
CelementCapacitor
RelementResistor
XelementSubcircuit instance

Directives

.ac

shared.directive.ac — Small-signal AC frequency analysis

.ac {LIN|DEC|OCT} np fstart fstop
ParameterDescriptionUnits
`LINDECOCT`
npNumber of points (LIN) or points per decade/octave (DEC/OCT).
fstartStart frequency.Hz
fstopStop 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
ParameterDescriptionUnits
srcnameIndependent source, parameter, or TEMP to sweep.
startSweep start value.
stopSweep stop value.
stepIncrement between points.

Examples

  • .dc V1 0 5 0.1
  • .dc TEMP 0 100 25

.end

shared.directive.end — End of netlist

.end [comment]
ParameterDescriptionUnits
commentOptional trailing comment.

Examples

  • .end
  • .end my_circuit

.ends

shared.directive.ends — End a subcircuit definition

.ends [name]
ParameterDescriptionUnits
nameOptional 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 ...]
ParameterDescriptionUnits
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'
ParameterDescriptionUnits
filenamePath 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 ...)
ParameterDescriptionUnits
mnameModel name referenced by instances.
typeModel 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 ...]
ParameterDescriptionUnits
nameParameter identifier.
valueNumeric value or expression.

Examples

  • .param rload=1k
  • .param pi=3.14159

.print

shared.directive.print — Print analysis results as a table

.print {DC|TRAN|AC} ov1 [ov2 ...]
ParameterDescriptionUnits
`DCTRANAC`
ovOutput 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 ...]
ParameterDescriptionUnits
nameSubcircuit 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 ...]
ParameterDescriptionUnits
t1Temperature 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]
ParameterDescriptionUnits
tstepSuggested printing / sampling increment.s
tstopFinal simulation time.s
tstartOptional start of printing.s
tmaxOptional maximum timestep.s
UICUse initial conditions.

Examples

  • .tran 1n 100n
  • .tran 1u 1m 0 10u UIC

Elements

C

shared.element.C — Capacitor

Cname n1 n2 value [params...]
ParameterDescriptionUnits
n1Positive node.
n2Negative node.
valueCapacitance.F

Examples

  • C1 out 0 1p

R

shared.element.R — Resistor

Rname n1 n2 value [params...]
ParameterDescriptionUnits
n1Positive node.
n2Negative node.
valueResistance.ohm

Examples

  • R1 in out 1k
  • Rload out 0 50

X

shared.element.X — Subcircuit instance

Xname n1 [n2 ...] subckt_name [params...]
ParameterDescriptionUnits
n1...Nodes connected to subcircuit ports.
subckt_nameName 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

NameKindSummarySource
.acdirectiveAC frequency analysis (HSPICE)dialect
.alterdirectiveRerun simulation with alternate parameters or analyses (HSPICE)dialect
.datadirectiveDefine a named data table for data-driven sweeps (HSPICE)dialect
.dcdirectiveDC analysis and sweeps (HSPICE)dialect
.enddirectiveTerminate the netlist (HSPICE)dialect
.endsdirectiveEnd a subcircuit definitionshared
.icdirectiveForce initial node voltages (HSPICE)dialect
.includedirectiveInclude an external file (HSPICE)dialect
.libdirectiveCall a named library section (HSPICE)dialect
.measuredirectiveMeasure delay, rise/fall, extrema, and other results (HSPICE)dialect
.modeldirectiveDefine a device modelshared
.nodesetdirectiveSuggest initial guesses for DC convergence (HSPICE)dialect
.noisedirectiveNoise analysis paired with .AC (HSPICE)dialect
.opdirectiveDC operating-point report (HSPICE)dialect
.optiondirectiveSet HSPICE simulation optionsdialect
.paramdirectiveDefine parameters and expressions (HSPICE)dialect
.printdirectivePrint tabulated analysis results (HSPICE)dialect
.probedirectiveSelect waveforms saved for post-processing (HSPICE)dialect
.subcktdirectiveBegin a subcircuit definitionshared
.tempdirectiveSet simulation temperature(s) (HSPICE)dialect
.tfdirectiveDC small-signal transfer function (HSPICE)dialect
.trandirectiveTransient analysis (HSPICE)dialect
CelementCapacitorshared
RelementResistorshared
XelementSubcircuit instanceshared

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
ParameterDescriptionUnits
typeLIN, DEC, or OCT frequency spacing.
npPoints (LIN) or points per decade/octave.
fstart / fstopFrequency range.Hz
SWEEP …Optional nested parameter/source sweep.
DATA=datanmData-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]
ParameterDescriptionUnits
title_stringOptional 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
ParameterDescriptionUnits
datanmData-block name referenced by DATA=datanm on .DC / .AC / .TRAN.
pnamParameter column name (must be declared with .PARAM).
pvalRow 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
ParameterDescriptionUnits
var1Primary sweep variable: independent source, element/model parameter, or TEMP.
start1 / stop1 / incr1Linear sweep bounds and step (positional or START=/STOP=/STEP=).
type npLIN|DEC|OCT with point count for parameterized / nested sweeps.
SWEEP var2 …Optional nested (second) sweep over another source or parameter.
DATA=datanmData-driven sweep using a .DATA block.
MONTE=valMonte 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]
ParameterDescriptionUnits
commentOptional 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]
ParameterDescriptionUnits
nameOptional 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 ...]
ParameterDescriptionUnits
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'
ParameterDescriptionUnits
filenameFile 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
ParameterDescriptionUnits
filenameLibrary file path.
entrynameSection 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 …
ParameterDescriptionUnits
`TRANDCAC`
nameMeasurement result name written to the measure output.
TRIG / TARGTrigger 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 ...)
ParameterDescriptionUnits
mnameModel name referenced by instances.
typeModel 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 ...]
ParameterDescriptionUnits
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]
ParameterDescriptionUnits
outvarOutput voltage node or pair, e.g. V(out) or V(out,ref).
srcnamIndependent source treated as the noise input reference.
intervalOptional 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 ...]]
ParameterDescriptionUnits
formatOptional report style: ALL, BRIEF, CURRENT, DEBUG, NONE, VOLTAGE.
timeOptional 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 ...]
ParameterDescriptionUnits
keywordOption 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 ...]
ParameterDescriptionUnits
nameParameter identifier.
valueNumber, ‘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 ...]
ParameterDescriptionUnits
`DCTRANAC`
ovOutput 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 ...]
ParameterDescriptionUnits
`DCTRANAC`
ovOutput 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 ...]
ParameterDescriptionUnits
nameSubcircuit 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 ...]
ParameterDescriptionUnits
t1Temperature 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
ParameterDescriptionUnits
outvarOutput variable, e.g. V(out) or I(Vload).
srcnamIndependent 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]
ParameterDescriptionUnits
tstepPrinting increment.s
tstopStop time.s
tstartOptional start of printing.s
tmaxOptional maximum timestep.s
UICUse 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...]
ParameterDescriptionUnits
n1Positive node.
n2Negative node.
valueCapacitance.F

Examples

  • C1 out 0 1p

R

shared.element.R — Resistor

Rname n1 n2 value [params...]
ParameterDescriptionUnits
n1Positive node.
n2Negative node.
valueResistance.ohm

Examples

  • R1 in out 1k
  • Rload out 0 50

X

shared.element.X — Subcircuit instance

Xname n1 [n2 ...] subckt_name [params...]
ParameterDescriptionUnits
n1...Nodes connected to subcircuit ports.
subckt_nameName 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

NameKindSummarySource
.acdirectiveSmall-signal AC frequency analysisshared
.dcdirectiveDC sweep analysisshared
.enddirectiveEnd of netlistshared
.endsdirectiveEnd a subcircuit definitionshared
.icdirectiveSet initial node voltagesshared
.includedirectiveInclude another netlist fileshared
.modeldirectiveDefine a device modelshared
.opdirectiveDC operating-point analysisshared
.paramdirectiveDefine a named parametershared
.printdirectivePrint analysis results as a tableshared
.subcktdirectiveBegin a subcircuit definitionshared
.tempdirectiveSet circuit temperature(s)shared
.trandirectiveTransient analysis (Ngspice)dialect
CelementCapacitorshared
RelementResistorshared
XelementSubcircuit instanceshared

Directives

.ac

shared.directive.ac — Small-signal AC frequency analysis

.ac {LIN|DEC|OCT} np fstart fstop
ParameterDescriptionUnits
`LINDECOCT`
npNumber of points (LIN) or points per decade/octave (DEC/OCT).
fstartStart frequency.Hz
fstopStop 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
ParameterDescriptionUnits
srcnameIndependent source, parameter, or TEMP to sweep.
startSweep start value.
stopSweep stop value.
stepIncrement between points.

Examples

  • .dc V1 0 5 0.1
  • .dc TEMP 0 100 25

.end

shared.directive.end — End of netlist

.end [comment]
ParameterDescriptionUnits
commentOptional trailing comment.

Examples

  • .end
  • .end my_circuit

.ends

shared.directive.ends — End a subcircuit definition

.ends [name]
ParameterDescriptionUnits
nameOptional 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 ...]
ParameterDescriptionUnits
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'
ParameterDescriptionUnits
filenamePath 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 ...)
ParameterDescriptionUnits
mnameModel name referenced by instances.
typeModel 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 ...]
ParameterDescriptionUnits
nameParameter identifier.
valueNumeric 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 ...]
ParameterDescriptionUnits
`DCTRANAC`
ovOutput 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 ...]
ParameterDescriptionUnits
nameSubcircuit 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 ...]
ParameterDescriptionUnits
t1Temperature 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]
ParameterDescriptionUnits
tstepSuggested printing increment.s
tstopFinal time.s
tstartOptional start of printing.s
tmaxOptional maximum timestep.s
UICUse 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...]
ParameterDescriptionUnits
n1Positive node.
n2Negative node.
valueCapacitance.F

Examples

  • C1 out 0 1p

R

shared.element.R — Resistor

Rname n1 n2 value [params...]
ParameterDescriptionUnits
n1Positive node.
n2Negative node.
valueResistance.ohm

Examples

  • R1 in out 1k
  • Rload out 0 50

X

shared.element.X — Subcircuit instance

Xname n1 [n2 ...] subckt_name [params...]
ParameterDescriptionUnits
n1...Nodes connected to subcircuit ports.
subckt_nameName 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

NameKindSummarySource
.acdirectiveSmall-signal AC frequency analysisshared
.dcdirectiveDC sweep analysisshared
.enddirectiveEnd of netlistshared
.endsdirectiveEnd a subcircuit definitionshared
.icdirectiveSet initial node voltagesshared
.includedirectiveInclude another netlist fileshared
.modeldirectiveDefine a device modelshared
.opdirectiveDC operating-point analysisshared
.paramdirectiveDefine a named parametershared
.printdirectivePrint analysis results as a tableshared
.subcktdirectiveBegin a subcircuit definitionshared
.tempdirectiveSet circuit temperature(s)shared
.trandirectiveTransient analysis (LTspice)dialect
CelementCapacitorshared
RelementResistorshared
XelementSubcircuit instanceshared

Directives

.ac

shared.directive.ac — Small-signal AC frequency analysis

.ac {LIN|DEC|OCT} np fstart fstop
ParameterDescriptionUnits
`LINDECOCT`
npNumber of points (LIN) or points per decade/octave (DEC/OCT).
fstartStart frequency.Hz
fstopStop 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
ParameterDescriptionUnits
srcnameIndependent source, parameter, or TEMP to sweep.
startSweep start value.
stopSweep stop value.
stepIncrement between points.

Examples

  • .dc V1 0 5 0.1
  • .dc TEMP 0 100 25

.end

shared.directive.end — End of netlist

.end [comment]
ParameterDescriptionUnits
commentOptional trailing comment.

Examples

  • .end
  • .end my_circuit

.ends

shared.directive.ends — End a subcircuit definition

.ends [name]
ParameterDescriptionUnits
nameOptional 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 ...]
ParameterDescriptionUnits
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'
ParameterDescriptionUnits
filenamePath 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 ...)
ParameterDescriptionUnits
mnameModel name referenced by instances.
typeModel 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 ...]
ParameterDescriptionUnits
nameParameter identifier.
valueNumeric 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 ...]
ParameterDescriptionUnits
`DCTRANAC`
ovOutput 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 ...]
ParameterDescriptionUnits
nameSubcircuit 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 ...]
ParameterDescriptionUnits
t1Temperature 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]
ParameterDescriptionUnits
TstepPrinting increment.s
TstopStop 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...]
ParameterDescriptionUnits
n1Positive node.
n2Negative node.
valueCapacitance.F

Examples

  • C1 out 0 1p

R

shared.element.R — Resistor

Rname n1 n2 value [params...]
ParameterDescriptionUnits
n1Positive node.
n2Negative node.
valueResistance.ohm

Examples

  • R1 in out 1k
  • Rload out 0 50

X

shared.element.X — Subcircuit instance

Xname n1 [n2 ...] subckt_name [params...]
ParameterDescriptionUnits
n1...Nodes connected to subcircuit ports.
subckt_nameName 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:

TaskCommandPurpose
buildcargo build --releaseProduction binary
build-devcargo buildFast debug builds
testcargo test --workspaceAll unit + integration tests
test-parsercargo test -p spice-parserParser fixtures only
test-lspcargo test -p spice-lspLSP integration tests
spice-lspcargo run -p spice-lspRun language server (stdio; also accepts --stdio)
format-spicecargo run -q -p spice-lsp -- formatFormat SPICE netlists (--write / --check)
fmtcargo fmt --allRust source formatting
clippycargo clippy --workspace -- -D warningsLint
mdbook-buildmdbook build docsStatic doc site
mdbook-servemdbook serve docs -n 127.0.0.1 -p 3000Live doc preview
ext-installnpm install in editors/vscodeExtension deps
ext-compilenpm run compileBuild extension TS
ext-package./scripts/package-vscode-extension.shBundle local binary + .vsix
reference-validatecargo test -p spice-reference --libValidate embedded corpus
reference-docsspice-reference-catalog writeRegenerate docs/reference/ from corpus
reference-docs-checkspice-reference-catalog checkFail 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)

GitHub Actions workflow stages:

  1. pixi install
  2. pixi run fmt -- --check
  3. pixi run clippy
  4. pixi run test
  5. pixi 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:

  1. Runs pixi run mdbook-build
  2. Pushes the output to the gh-pages branch

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-pages branch (/ 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

ProblemFix
cargo: command not foundRun pixi install; use pixi run cargo
Tree-sitter build.rs failsEnsure C compiler available in pixi env (pixi add gcc on Linux)
Extension can’t find binarySet spiceLsp.serverPath in VS Code settings to absolute path
LSP hangs on startNormal for stdio servers waiting for JSON-RPC input

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)

  1. Build Rust binary: pixi run build
  2. cd editors/vscode && npm install && npm run compile
  3. Open editors/vscode in VS Code
  4. Run and Debug → Launch Extension (F5)
  5. 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 on nch / inverter to jump into models.sp
    • Open test-data/invalid/unclosed-subckt.cir and confirm Problems lists diagnostics
    • Fix syntax, confirm clearing

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:

StepActionExpected
1pixi run testAll tests pass
2F5 extensionDevelopment Host opens
3Open invalid netlistRed squiggle + Problems entry
4Edit to fixDiagnostic disappears
5Open valid netlistNo errors
6SPICE LSP: Create Demo Folderspice-lsp-demo/ appears (HSPICE); F12 on nch in top.sp opens models.sp; F12 on nch_tt in top-lib.sp opens corners.lib
7SPICE 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.


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:

FieldPurpose
engines.vscodeMinimum VS Code version
activationEventsonLanguage:spice, onCommand:spiceLsp.restartServer, onCommand:spiceLsp.setDialect, onCommand:spiceLsp.createDemoFolder
main./out/extension.js (esbuild bundle)
contributes.languagesRegister spice language id and file extensions
contributes.configurationspiceLsp.serverPath, spiceLsp.trace.server, spiceLsp.dialect
contributes.commandsspiceLsp.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.

FilePurpose
same-file.spHSPICE .param / .option / .model / .subcktF12 on buffer / nch stays in-file
models.spShared models and subcircuits
top.sp.include 'models.sp'F12 on nch / inverter / buffer jumps across files
corners.libHSPICE .lib / .endl corner sections
top-lib.sp.lib 'corners.lib' TTF12 on the path opens corners.lib; on TT jumps to .lib TT; on nch_tt / pch_tt jumps to the model
README.mdShort 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 idOS / archNotes
linux-x64Linux x86_64Linked for glibc 2.31+ (Ubuntu 20.04 / Debian 11+) via Zig
linux-arm64Linux ARM64Same glibc 2.31 floor
darwin-x64macOS Intel
darwin-arm64macOS Apple Silicon
win32-x64Windows 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:

  1. spiceLsp.serverPath setting (if set)
  2. Bundled binary at bin/<platform>-<arch>/spice-lsp inside the extension
  3. Local dev paths under target/debug or target/release (F5 from this repo)
  4. spice-lsp on PATH

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):

  1. Bumps the patch version in editors/vscode/package.json and commits it to main
  2. Cross-compiles spice-lsp for all supported platform ids
  3. Assembles a single .vsix containing every platform binary
  4. Uploads the VSIX as a GitHub Actions artifact
  5. Creates a GitHub Release tagged vscode-v<version>
  6. Publishes to the VS Code Marketplace from the same package job (VSCE_PAT required)
StrategyProsCons
User PATHSimplest for local devPoor UX for end users
Setting serverPathFlexibleManual configuration
Bundle in .vsixWorks offline; Marketplace defaultLarger artifact; CI builds all platforms
Download from GitHub Releases on activateSmall VSIXRequires 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:

  1. Sign in to the Visual Studio Marketplace publisher management page with a Microsoft account.
  2. Create a publisher whose Publisher ID matches editors/vscode/package.json (AmirhosseinDavoody in this repo). The ID is permanent and must match exactly.
  3. Create a Personal Access Token in Azure DevOps (not portal.azure.com):
    1. Open https://dev.azure.com and sign in with the same Microsoft account used for the Marketplace publisher.
    2. If prompted, create a free Azure DevOps organization (any name is fine; it is only a container for the PAT).
    3. Click your profile avatar (top right) → Personal access tokens
      Direct link: https://dev.azure.com/_usersSettings/tokens
    4. + 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
    5. Create and copy the token immediately (it is shown once)
  4. In the GitHub repo: Settings → Secrets and variables → Actions → New repository secret
    • Name: VSCE_PAT
    • Value: the Azure DevOps PAT from step 3
  5. 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 (MIT matches package.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 compileout/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:

  1. Patch-bumps editors/vscode/package.json (e.g. 0.2.00.2.1)
  2. Commits chore(vscode): bump extension to <version> (via GITHUB_TOKEN, which does not re-trigger the workflow)
  3. Builds platform binaries, packages the VSIX, creates GitHub Release vscode-v<version>, and runs vsce 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 in package.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.json publisher field
  • VSCE_PAT repository secret configured
  • README.md describes bundled-binary behavior for end users
  • LICENSE aligned with repo (MIT in package.json)
  • engines.vscode set to tested minimum version

Troubleshooting

SymptomLikely causeFix
Server not startingBinary not on PATH / wrong serverPath / unsupported platformSet spiceLsp.serverPath, then SPICE LSP: Restart Server; check Output → SPICE Language Server
unexpected argument '--stdio' / server exits code 2Bundled 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 foundHost glibc older than the binaryUpdate to a Marketplace build linked for glibc 2.31+, or build locally and set spiceLsp.serverPath
spiceLsp.restartServer / spiceLsp.setDialect / spiceLsp.createDemoFolder not foundExtension 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 OutputExtension did not activateOpen a SPICE file or run SPICE LSP: Restart Server / Set Dialect…; check Developer: Show Running Extensions for activation errors
Extension activates with module errorsUnbundled VSIX missing node_modulesUse an esbuild-bundled release (vsce package --no-dependencies after npm run compile)
No diagnosticsWrong language idEnsure file extension maps to spice
Stale diagnosticsServer crashCheck Output → SPICE Language Server; restart server
Wrong binary archDownload mismatch / unsupported platformPick correct release asset or build from source

Beyond VS Code

The same spice-lsp binary enables other editors:

EditorIntegration
Neovimvim.lsp.enable or lspconfig custom server
Helixlanguage-server.spice-lsp in user config
ZedCommunity extension calling the binary

VS Code is the reference client; keep editor-specific code out of Rust.

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.
  • Navigation (Go to Definition & Find References):
    • Resolve references for subcircuits (.subckt) and models (.model), including through .include / .lib.
    • On .lib 'file' entry / .include lines, jump from the path to the file and from a .lib entry name to the section header.
    • Map parameter definitions (.param) to their usages in expressions.
  • 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), .option keywords, element types, and common expressions — authored per dialect in a reference/ 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 .subckt blocks, .model definitions, and control blocks.

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 .directive keywords.
  • 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:

FocusNotes
CompletionElement/directive suggestions; reuse reference corpus for docs
ConnectivityDangling-node and floating-net diagnostics — Dialect reference and net semantics
Large-file / extracted modeSize-gated defs-only analysis shipped (spiceLsp.analysisMode); lazy includes / incremental parse still open — Large-file / extracted mode
Deeper dialect grammarLTspice / 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

LayerMethod
GrammarTree-sitter corpus + Rust fixture tests
ParserGolden diagnostics on test-data/invalid/*
LSPJSON-RPC harness over stdio (subprocess or mock client)
VS CodeExtension Development Host (F5), Problems panel
CIpixi 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:

  1. Development: spiceLsp.serverPath points at target/debug/spice-lsp
  2. Side-load: .vsix built with vsce package
  3. 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; powers textDocument/hover and 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

GoalDetail
User-selectable dialectVS Code setting + Command Palette command
Default = HSPICEMatches issue #16; Ngspice remains fully supported
Shared knowledge systemOne corpus drives diagnostics policy, hover, and (later) completion docs
Low-friction authoringAdding a directive/element/rule is mostly data + a test, not scattered Rust strings
Keep current Ngspice behaviorExisting 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-docs projects them to Markdown for the public site.
  • Registration table + codegen: codes.rs + proc macros produce the Rule enum and metadata accessors so “forgot to register” fails loudly.
  • CI gates: generate-all --mode check and check_docs_formatted.py reject 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 MarkupContent markdown.
  • 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 on man, 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

IdLabelInitial role
hspiceHSPICEDefault
ngspiceNgspiceCurrent parser/diagnostics baseline; keep working
ltspiceLTspiceStub 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

  1. Setting: spiceLsp.dialect — enum hspice | ngspice | ltspice, default hspice.
  2. Command: SPICE LSP: Set Dialect… — QuickPick; writes the setting (workspace if a folder is open, else user) and restarts / notifies the server.
  3. Status bar (recommended in the same slice): show current dialect; click opens the QuickPick.

Optional later (not required for #16):

  • # spice-lsp dialect=hspice file header / .spice-lsp.toml
  • Infer from path heuristics (*.sp in 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:

  1. Re-analyze all open documents with the new dialect profile.
  2. Republish diagnostics.
  3. 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

  1. Load _shared/ as base for the active dialect.
  2. Overlay reference/<dialect>/ by id / (kind, name) — dialect file wins.
  3. Missing entry → no hover / no completion doc (not an error). Gaps are filled by adding JSON.

5.4 Codegen / validation tasks

TaskPurpose
pixi run reference-validateLoad embedded corpus; unit tests for merge/lookup
pixi run reference-docsWrite mdBook pages under docs/reference/ from the corpus
pixi run reference-docs-checkCI: fail if catalog markdown drifts from JSON

Authoring workflow (add a new directive):

  1. Add/edit reference/<dialect>/directives/foo.json (or _shared/ if universal).
  2. pixi run reference-validate.
  3. pixi run reference-docs (regenerate catalog chapters).
  4. Add hover snapshot fixture under test-data/hover/<dialect>/ when needed.
  5. pixi run test.
  6. 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

Concernhspicengspiceltspice
Parse grammarCurrent line-oriented grammar (shared)SameSame
Comment styles (docs / future toggle)* primary*, ;, $$ / * (document; refine later)
Semantic diagnosticsSame engines; corpus may gate “unknown directive” listsCurrent fixturesMinimal
Hoverhspice corpus (+ _shared)ngspice corpusltspice 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

  1. Classify line / token: directive name, element type letter, .option keyword, etc. (reuse / extend CST + symbol index).
  2. Build key (dialect, kind, normalized_name).
  3. 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

ItemChange
package.json settingsspiceLsp.dialect enum, default hspice
CommandspiceLsp.setDialect → QuickPick
Status barHSPICE / Ngspice / LTspice
LanguageClient initinitializationOptions: { dialect }
Middleware / config listenerOn dialect change → DidChangeConfiguration + optional restart if needed
Marketplace READMEDocument 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.
  • DialectProfile for dialect metadata; parsing still largely shared across dialects.
  • Docs: default dialect, how to switch.

Shipped — Reference crate + hover

  • reference/ schema + _shared + hspice / ngspice / stub ltspice entries.
  • HSPICE overlays for analysis/control directives (.data, multi-mode .dc, .op, plus .ac / .measure / .probe / .lib / …).
  • spice-reference crate + validate/codegen pixi tasks.
  • textDocument/hover with 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

LayerTests
SchemaEvery JSON entry validates; required sections present
MergeOverlay wins; shared fallback works
LSPinitialize with dialect; didChangeConfiguration republishes
HoverFixtures per dialect; missing entry → null
RegressionExisting Ngspice stdio tests run with explicit ngspice
ExtensionSetting default is hspice; command updates config (smoke / manual)

11. Risks and decisions

TopicDecision
Default dialectHSPICE per #16 (overrides earlier docs that said Ngspice default)
One grammar vs manyOne shared grammar for now; profile flags first
Doc authoring mediumJSON under reference/, not Rust comments
External doc servicesOut of scope; optional later, opt-in only
LTspiceEnum + stub corpus early; deep support later
Breaking changeDefault dialect change may surprise Ngspice users — document prominently; one-click switch

12. Open questions

  1. Should dialect be workspace-only or allow per-file override?
  2. Do we keep the full corpus embedded in the binary, or also support loading from an extension-relative path for faster iteration?
  3. Which remaining HSPICE / LTspice constructs matter most for authoring flows?
  4. 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 under ngspice
  • 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:

  1. Holds the full buffer as a String
  2. Fully re-parses with Tree-sitter (no incremental old_tree reuse yet)
  3. Builds an Index that records every instance plus hierarchical outline children
  4. Loads and indexes .include / .lib targets 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

PatternDetail
LSP-first incrementalityAnalysis is a Salsa query graph; edits invalidate only dependent queries down to individual definitions
Coarse then fineParse a whole file once; expensive work (type inference) is per-scope and reusable
Lazy dependency workSkip large parts of third-party code until imports / open files require it
Memory pressure controlDrop ASTs after checking and reparse on demand; planned LRU eviction for dominant caches
Diagnostic scopeDefault openFilesOnly; optional workspace diagnostics; prefer pull diagnostics over push-everything
InterningDeduplicate paths and types via the query DB

2.2 What we should not copy wholesale

ty choiceWhy not for spice-lsp (yet)
Full Salsa databaseLarge architectural bet; valuable later for multi-file schematic projects, overkill as the first fix for one huge buffer
Definition-level type inference granularityExtracted SPICE pain is open/index cost of millions of similar lines, not “recheck one function”
Workspace-scale symbol search as a goal for extracted dumpsSearching 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

GoalDetail
Open large extracted files safelySize/line gate; never build a multi‑GB instance index by default
Useful navigation on structureGo-to-definition for .subckt / .model / .param (local + lazy include/lib)
Keep schematic UX unchangedFiles under the threshold keep today’s full index, outline, and diagnostics
Bounded LSP payloadsCap or omit instance outline children; avoid flooding diagnostics
Clear mode signalingStatus / 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

ModeWhenIndexOutlineDiagnosticsIncludes
Full (default)Buffer below thresholdDefinitions + instances + refsHierarchical, including instancesCurrent syntax + semantic setEager definition graph (current behavior)
ExtractedBuffer at/above threshold, or user overrideDefinitions only (.subckt, .model, .param)Structure only (no instance children)Syntax + cheap checks; skip scans that walk every instanceLazy: 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() >= N bytes (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

FeatureBehavior
Syntax diagnosticsKeep (Tree-sitter / line classify); consider debouncing more aggressively
Duplicate instance namesOff or sampled — full scan is O(instances)
Unknown model / subcktOn for references that are checked; may require lazy include lookup of definition maps only
Document symbolsSubcircuits, models, params; no per-instance children
Go to definitionDefinitions in-file + lazy include/lib
Find referencesFile-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 / connectivityRemain 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::Instance insertion
    • Skip pushing instances into document_symbols children
    • Optionally still record model/subckt name references from instance lines into the references map without storing an instance Symbol per line (supports unknown-model + goto without GB-scale vectors)
  • Prefer interned or borrowed name keys where practical later; first slice can keep String keys if instance rows are omitted

5.2 Include graph (spice-parser / LSP)

  • In extracted mode, do not retain full text + full Index for 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 large didChange (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_blocking long 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”):

WinNotes
Deduplicate parses in analyze_with_includesRoot is parsed multiple times today
Persist Tree-sitter Tree + InputEditMatches documented incremental architecture
Line index / rope for UTF-16 ↔ byte mappingAvoid O(file) scans per symbol conversion
Cap outline sizeEven in full mode, enormous outlines are hostile to editors

6. Comparison: ty patterns → spice-lsp actions

ty patternspice-lsp action
Fine-grained incremental queriesNear term: cache parse/index per document; invalidate on change. Later: optional query-style layering if multi-file cost dominates
Skip irrelevant dependenciesLazy .include / .lib in extracted mode
Drop AST after useAlready drop Tree; extend to “don’t retain instance IR”
openFilesOnly / pull diagnosticsThin diagnostic set + eventual pull; never push millions of warnings
Coarse parse, fine semanticsLine classify always; instance indexing and connectivity optional

7. Implementation slices

SliceStatus
1. Gate + profile plumbing (AnalysisMode / AnalysisProfile, threshold)Done
2. Defs-only index + outline without instances; goto via line classifyDone
3. Diagnostic thinning (no duplicate-name; sparse unknown-model)Done
4. Settings (spiceLsp.analysisMode, extractedByteThreshold) + docsDone
5. Defs-only indexes for includes when root is extracted / include is hugeDone
6. Lazy include materialization (load on demand only)Open
7. Perf hygiene — single-parse path, incremental Tree-sitter, position indexOpen

Public docs: LSP features, Limitations, VS Code README.


8. Testing

LayerApproach
Unitbuild_index with Extracted on a small fixture asserting zero instance symbols and retained .subckt/.model
ThresholdForce profile via test API or setting without needing a 300 MB file in CI
IntegrationLSP: open “large” synthetic buffer; assert outline has no instance flood; goto def still works
Manual / benchOptional 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

  1. Default threshold — bytes vs lines vs both; different defaults for .spf/.net associations?
  2. Model refs without instance symbols — enough for unknown-model quality, or accept weaker checks in extracted mode?
  3. Editor still holds 300 MB — server-side wins don’t fix VS Code memory; document that opening such files is inherently heavy
  4. Partial / viewport analysis — analyze only visible ranges later? Powerful but much more complex; not in the first slices
  5. 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:

  1. Open without process kill / multi‑GB RSS from instance indexes
  2. Show a usable structural outline (subcircuits/models/params)
  3. Support go-to-definition for models/subcircuits with lazy includes
  4. Leave schematic-scale files behaviorally unchanged
  5. Document the mode and limits in Limitations