lsp

package module
v0.1.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 3, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package lsp is the Go half of the tabnas language server: the same protocol-free pipeline as the canonical TypeScript package (ts/src/core.js) — diagnostics via ParseRecover, semantic tokens via the reconciled lex trace, outline via SubRuleDone, completion via Continuations — plus a minimal stdio JSON-RPC front-end (Serve).

TS is canonical; this port mirrors it by fixture parity (test/fixtures/, run by both runtimes), never by code sharing. It exists so the generator can emit static, single-binary language servers for grammars that live in Go — or for any pure-data GrammarSpec, which is runtime-independent (design §7).

Index

Constants

View Source
const QuarantineLimit = 3
View Source
const VERSION = "0.1.4"

VERSION is this package's version.

Variables

View Source
var DefaultOutlineRules = map[string]string{
	"map":  "Object",
	"list": "Array",
}

DefaultOutlineRules maps structural rule names to symbol labels.

View Source
var DefaultTokenTypes = map[string]string{
	"#ST": "string",
	"#NR": "number",
	"#CM": "comment",
	"#VL": "keyword",
	"#TX": "string",
	"#OB": "operator",
	"#CB": "operator",
	"#OS": "operator",
	"#CS": "operator",
	"#CL": "operator",
	"#CA": "operator",
}

DefaultTokenTypes maps engine-standard token names (railroad's CANON key set) to LSP semantic token types. #ID is per-plugin and comes from entry overrides.

View Source
var Legend = []string{
	"string", "number", "comment", "keyword", "operator", "variable",
	"macro", "type", "property",
}

Legend is the fixed superset legend (design §11).

Functions

func EntryFromSpecJSON

func EntryFromSpecJSON(languageID string, extensions []string, spec []byte) (*Entry, MakeInstance)

EntryFromSpecJSON builds an Entry plus a grammar loader from a serialized GrammarSpec — the L2 lane, and the only lane a Go server needs for pure-data grammars. The spec bytes are expected to have passed the generation-time firewall (the generator refuses poisoned specs); GrammarSpecFromJSON re-applies the engine's own gates (schema-version ceiling among them) at load time.

func ExtOf

func ExtOf(uri string) string

ExtOf returns the lowercased extension of a URI or path, "" if none.

func NewInstance

func NewInstance(e *Entry) *tabnas.Tabnas

NewInstance builds a bare engine instance configured for LSP use: recovery on (multi-error diagnostics), the entry's sync groups applied when it declares any.

func Serve

func Serve(cfg Config) error

Serve runs the server until the client closes the stream or sends exit. It returns nil on an orderly shutdown.

func TokenType

func TokenType(name string, overrides map[string]string) string

TokenType resolves an engine token name to an LSP token type, "" for none.

func UTF16Len

func UTF16Len(s string) int

UTF16Len counts UTF-16 code units of a string.

Types

type Analysis

type Analysis struct {
	Value          any
	Errors         []*tabnas.TabnasError
	Failed         bool
	Diagnostics    []Diagnostic
	SemanticTokens *SemanticTokens
	Outline        []*DocumentSymbol
}

Analysis is everything one parse yields.

func Analyze

func Analyze(instances *Instances, inst *tabnas.Tabnas, entry *Entry, doc *Doc) *Analysis

Analyze runs one parse and derives every artifact (ts/src/core.js analyze()).

type CompletionItem

type CompletionItem struct {
	Label      string `json:"label"`
	Kind       int    `json:"kind"`
	Detail     string `json:"detail,omitempty"`
	InsertText string `json:"insertText,omitempty"`
}

CompletionItem is the LSP completion item.

func Complete

func Complete(instances *Instances, inst *tabnas.Tabnas, doc *Doc, pos Position) []CompletionItem

Complete answers a completion request at a position: continuations of the document prefix, labeled by fixed-token source where the token has one. Runs under the parse lock with no collector active — Continuations parses internally, and those events must not leak into any analysis.

type Config

type Config struct {
	// Entries are the served languages.
	Entries []*Entry

	// MakeInstance builds the engine instance for an entry. Generated
	// servers pass the closure from EntryFromSpecJSON or their own
	// plugin-applying loader.
	MakeInstance MakeInstance

	// In/Out default to stdin/stdout.
	In  io.Reader
	Out io.Writer

	// Logf receives server-side log lines; default: stderr.
	Logf func(format string, args ...any)
}

Config configures Serve.

type Diagnostic

type Diagnostic struct {
	Range           Range            `json:"range"`
	Severity        int              `json:"severity"`
	Code            string           `json:"code,omitempty"`
	Source          string           `json:"source"`
	Message         string           `json:"message"`
	CodeDescription *codeDescription `json:"codeDescription,omitempty"`
}

Diagnostic is an LSP diagnostic.

func Diagnostics

func Diagnostics(errs []*tabnas.TabnasError, entry *Entry, doc *Doc) []Diagnostic

Diagnostics converts engine errors to LSP diagnostics through the structured-diagnostic JSON — the exact bytes TS consumes — so the two ports cannot disagree about codes, rows, or the `len` unit.

type Doc

type Doc struct {
	URI        string
	LanguageID string
	Version    int
	Text       string
	// contains filtered or unexported fields
}

Doc is one open document.

func (*Doc) LineStarts

func (d *Doc) LineStarts() []int

LineStarts returns the byte offset of each line start.

func (*Doc) OffsetAt

func (d *Doc) OffsetAt(p Position) int

OffsetAt converts an LSP Position to a byte offset.

func (*Doc) PosFromEngine

func (d *Doc) PosFromEngine(row, col int) Position

PosFromEngine converts an engine (row, col) — 1-based, col in runes — to an LSP Position.

func (*Doc) PositionAt

func (d *Doc) PositionAt(offset int) Position

PositionAt converts a byte offset to an LSP Position.

func (*Doc) RangeFrom

func (d *Doc) RangeFrom(row, col, lenCodePoints int) Range

RangeFrom converts an engine diagnostic position (row, col) plus a token length in CODE POINTS into an LSP Range, walking the document text so astral characters convert correctly and multi-line tokens end on the right line.

func (*Doc) Update

func (d *Doc) Update(text string, version int)

type DocumentStore

type DocumentStore struct {
	// contains filtered or unexported fields
}

DocumentStore holds the open documents.

func NewDocumentStore

func NewDocumentStore() *DocumentStore

func (*DocumentStore) Close

func (s *DocumentStore) Close(uri string)

func (*DocumentStore) Get

func (s *DocumentStore) Get(uri string) *Doc

func (*DocumentStore) Open

func (s *DocumentStore) Open(uri, languageID string, version int, text string) *Doc

type DocumentSymbol

type DocumentSymbol struct {
	Name           string            `json:"name"`
	Kind           int               `json:"kind"`
	Range          Range             `json:"range"`
	SelectionRange Range             `json:"selectionRange"`
	Children       []*DocumentSymbol `json:"children"`
	// contains filtered or unexported fields
}

DocumentSymbol is the LSP hierarchical symbol. Children hold pointers so nesting never copies a subtree that later grows.

func Outline

func Outline(events []ruleEvent, entry *Entry, doc *Doc) []*DocumentSymbol

Outline derives nested document symbols from one parse's rule events.

type Entry

type Entry struct {
	Name       string
	LanguageID string
	Extensions []string

	// PluginKind: grammar | compiler | modifier. Modifiers are never
	// routing targets.
	PluginKind string

	// LexStream gates semantic tokens: only "clean" entries (no relex,
	// no rewind) serve them. Default "clean".
	LexStream string

	// SemanticTokens maps engine token names to LSP token types,
	// overriding the CANON defaults (core DefaultTokenTypes).
	SemanticTokens map[string]string

	// OutlineRules maps rule names to symbol labels, overriding the
	// defaults (map -> Object, list -> Array).
	OutlineRules map[string]string

	// SyncGroups REPLACES the engine's recovery sync-group defaults
	// when non-nil (mirrors the TS option's semantics).
	SyncGroups []string

	// Enabled follows the editor-collision policy; disabled entries
	// are not routed.
	Enabled bool
}

Entry describes one served language: the Go mirror of a registry entry (ts/src/registry.js). Generated servers construct these statically; there is no bundled fleet registry on the Go side — fleet grammars are npm modules, and only pure-data specs or Go plugin packages can serve from a Go binary.

type Instances

type Instances struct {
	// contains filtered or unexported fields
}

Instances caches engine instances per entry, serializes parses, and quarantines repeatedly failing grammars (design §6).

func NewInstances

func NewInstances(makeInst MakeInstance) *Instances

func (*Instances) Get

func (s *Instances) Get(e *Entry) (*tabnas.Tabnas, error)

Get returns the cached instance for an entry, building it (and installing the permanent mux) on first use. nil when quarantined.

func (*Instances) Invalidate

func (s *Instances) Invalidate(e *Entry)

Invalidate drops an entry's cached instance (grammar hot-reload: rebuild, never re-apply — Grammar() prepends alternates).

func (*Instances) Parse

func (s *Instances) Parse(inst *tabnas.Tabnas, src string, c *collector) (any, []*tabnas.TabnasError, error)

Parse runs one parse with the collector active. parseMu serializes parses across all instances, so the subscribers — which run synchronously inside the parsing goroutine — always see the one active collector. Continuations' internal parses (routed through Complete) hold the same lock with a nil collector, making the mux a no-op for them.

func (*Instances) Quarantined

func (s *Instances) Quarantined(e *Entry) bool

func (*Instances) RecordFailure

func (s *Instances) RecordFailure(e *Entry)

func (*Instances) WithParseLock

func (s *Instances) WithParseLock(fn func())

WithParseLock runs fn with the parse lock held and no collector active — the engine-call guard for non-analyze parses such as Continuations.

type MakeInstance

type MakeInstance func(*Entry) (*tabnas.Tabnas, error)

MakeInstance builds (or rebuilds, on reload) the engine instance for an entry. Instances installs the single mux subscriber on whatever this returns — implementations must NOT install their own subscribers for the pipeline's events.

type Position

type Position struct {
	Line      int `json:"line"`
	Character int `json:"character"`
}

Position is an LSP position (0-based line, UTF-16 character).

type Range

type Range struct {
	Start Position `json:"start"`
	End   Position `json:"end"`
}

Range is an LSP range.

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

func NewRegistry

func NewRegistry(entries []*Entry) *Registry

func (*Registry) All

func (r *Registry) All() []*Entry

func (*Registry) Get

func (r *Registry) Get(languageID string) *Entry

func (*Registry) Resolve

func (r *Registry) Resolve(languageID, uri string) Resolution

type Resolution

type Resolution struct {
	Entry     *Entry
	Via       string // "languageId" | "extension" | ""
	Ambiguous []string
}

Resolution names the entry and how it was reached; Ambiguous lists competing languageIds when an extension is claimed more than once.

type SemanticTokens

type SemanticTokens struct {
	Data   []int    `json:"data"`
	Legend []string `json:"-"`
}

SemanticTokens is the LSP result plus the legend it indexes.

func SemanticTokensOf

func SemanticTokensOf(events []tokenPoint, entry *Entry, doc *Doc) *SemanticTokens

SemanticTokensOf derives the delta-encoded token data (line and character deltas in UTF-16 units, per the negotiated encoding). Tokens spanning lines are split into line-local spans, mirroring the TS core: multiline semantic tokens are an optional client capability, and an unsplit one mis-highlights or is rejected by clients without it.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server is the running state, exported for tests.

func NewServer

func NewServer(cfg Config) *Server

NewServer builds a server without starting the loop (tests drive Handle directly).

func (*Server) Handle

func (s *Server) Handle(msg *rpcMessage)

Handle dispatches one message.

func (*Server) Run

func (s *Server) Run() error

Run is the message loop.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL