Language service
On this page

Language service

The language service is the layer between the compiler's front end and an editor. It takes text and positions and returns data, and it touches no DOM or Node API. The Playground reads its diagnostics, completions and hover from it today, through the Monaco editor, and the VS Code extension reads them through a TypeScript server plugin, so the two cannot drift apart. A language server for other editors is not published yet.

Layers

The front end parses a "use typeshade" file, checks its types, structs and bindings, and reports diagnostics with source positions. The language service sits on top of the front end and of the TypeScript language service, which runs over an ambient declaration of the TypeShade globals, and answers requests about documents it holds by uri. Adapters sit above it and do nothing semantic: the Playground's Monaco adapter converts coordinates and owns the editor's markers, and an LSP server would carry the same answers over JSON-RPC. A judgement about TypeShade belongs in the service; an adapter converts.

Requests

One document API answers the requests an editor makes.

  • Diagnostics TypeScript and TypeShade diagnostics in one list. Each carries a source of typeshade or typescript and a code, so an adapter can tell the two apart, and a TypeScript parse error appears once, under its TypeScript code.
  • Completions The symbols in scope, the keywords, and TypeShade items where the context calls for them: attribute names after @, builtin input names inside @builtin(", GPU type names in a type position, and snippets for vectors and entry functions.
  • Hover Quick info for a symbol, with the TypeShade type name where TypeScript would say number, and documentation for GPU types, attributes and builtin inputs.
  • Signature help The signatures of the function under the cursor and the parameter being typed.
  • Definition and references Where a symbol is declared and where it is used, across the documents the service holds.
  • Document symbols The functions, structs, fields and resources of a document as an outline, with an entry function labelled by its stage.
  • Rename The edits that rename a symbol in every document that uses it, after a check that the position can be renamed at all.
  • Semantic tokens The tokens in document order, with GPU types, entry functions, resources and the names inside @builtin(...) marked as such.
  • Compiled output The WGSL or GLSL a document compiles to, on demand for an output pane. Diagnostics produce no shader text, so a keystroke does not run a backend.

Documents and positions

Import createTypeshadeLanguageService from the typeshade/language-service subpath and open a document by uri with its text and an optional version. Update it with the whole text on each change and close it when the editor does; every other method takes the uri and, where it applies, a position. Nothing in the service is asynchronous, and a result for a stale version is the adapter's to drop.

Positions are zero-based line and character pairs, with the character counted in UTF-16 code units, and a range is half-open with its end exclusive. These are the conventions LSP uses, so a language server passes them through field for field. Monaco counts from one, so the Playground's adapter adds one on its own side and takes it off on the way back; that adapter is the only place the two coordinate systems meet.

A document can import another shader file by a relative path, or a package's by the package's name, found in node_modules the way Node finds one (Rule 3.9). The service resolves the import against the document's uri by the rule compile() follows, or through the resolveImport you pass it, and reads a file it has not opened through readDocument, which returns the file's text or undefined and is asked for each package's package.json too. compile() takes the same two options, so the editor and the compiler read one program, and an import the two cannot follow is TS8072 in both.

Packaging

The service runs on typescript, which the package lists as a required peer dependency. The main entry needs it too, because compile() reads a "use typeshade" file with the TypeScript parser, so a program that imports only the compiler installs it as well, and typeshade/language-service uses that same copy.

Example

A host opens one document, asks for its diagnostics and for the hover at a position, and compiles it for an output pane.

editor.ts
import { createTypeshadeLanguageService } from 'typeshade/language-service'
// readDocument reads a file a document imports that the editor has not opened.
const service = createTypeshadeLanguageService({ readDocument: (uri) => files.get(uri) })
const uri = 'file:///hello.shade.ts'
service.openDocument(uri, source)
const diagnostics = service.getDiagnostics(uri)
const hover = service.getHover(uri, cursor)
const output = service.getCompiledOutput(uri, 'wgsl')
service.updateDocument(uri, nextSource)
service.closeDocument(uri)

Further reading

The Playground is the service at work in a browser. The design document in the compiler's repository, at the pinned commit, records the conventions, the adapter contracts and the order of work.

Edit this page Report a problem