Language services
VS Code programmatic language features often run through a Language Server. Lapis uses a custom LanguageServiceProvider contract consumed by app.languageServices. The API is LSP-shaped—positions, ranges, diagnostics, completions—but it does not use JSON-RPC or VS Code’s language server libraries.
Capabilities
Section titled “Capabilities”Providers declare what they implement through metadata.capabilities:
| Capability | Editor support today |
|---|---|
diagnostics | Yes — lint gutter, inline markers, Lapis-owned lint tooltip |
codeActions | Yes — quick fixes from diagnostics |
completion | Yes — TypeScript in code file views and notebook cells |
hover | Yes — TypeScript |
definition | Provider API only — no go-to-definition editor UI yet |
Declare a provider in the manifest
Section titled “Declare a provider in the manifest”Manifest-only language services are the preferred path when Lapis can bind a known provider at boot. The bundled Markdown Lint plugin demonstrates the pattern:
{ "contributes": { "configuration": [ { "id": "markdown-lint", "title": "Markdown Lint", "properties": { "disabledRules": { "type": "array", "title": "Disabled rules", "items": { "type": "string" }, "default": [] } } } ], "services": [ { "id": "markdown-lint", "service": "language-service", "languages": ["markdown"], "priority": 100, "capabilities": { "diagnostics": true, "codeActions": true } } ] }}System extensions connect the manifest declaration to a concrete provider implementation. Community plugins with custom analysis logic should register imperatively.
Register a provider from plugin code
Section titled “Register a provider from plugin code”Hybrid plugins call Plugin.registerLapisServiceProvider() during onload():
this.registerLapisServiceProvider({ id: "example-plugin:analyzer", service: "language-service", languages: ["markdown"], priority: 50, capabilities: { diagnostics: true, }, provider: myProvider,});The plugin manager scopes the provider ID to your plugin and registers it with app.languageServices. Cleanup runs automatically on unload.
Provider contract
Section titled “Provider contract”A LanguageServiceProvider includes metadata and optional methods:
interface LanguageServiceProvider { metadata: { id: string; languages: string[]; runtime: "worker" | "native" | "in-process" | "lsp"; priority?: number; capabilities: { diagnostics?: boolean; completion?: boolean; hover?: boolean; definition?: boolean; codeActions?: boolean; }; }; updateDocument?(update): void | Promise<void>; provideDiagnostics?(context): Promise<Diagnostic[]>; provideCompletions?(context, position): Promise<CompletionList | null>; provideHover?(context, position): Promise<Hover | null>; provideDefinition?(context, position): Promise<Location[]>; provideCodeActions?(context, range): Promise<CodeAction[]>; dispose?(): void | Promise<void>;}Request context includes the virtual document (uri, languageId, text, version) and optional global declarations for cross-file analysis.
Runtime hosts
Section titled “Runtime hosts”| Runtime | Where it runs | When to use |
|---|---|---|
worker | Browser Web Worker | PWA-safe analysis (markdownlint, TypeScript) |
native | Desktop Electron sidecar | Higher-priority desktop providers |
in-process | Renderer thread | Lightweight providers and tests |
lsp | Reserved | Not implemented; do not rely on it |
On desktop, native providers typically register with higher priority than worker providers for the same language. The manager merges diagnostics from all matching providers, preferring higher priority when combining results.
Wire providers into the editor
Section titled “Wire providers into the editor”File-backed editors attach language-service UI through languageServiceExtensions() from @lapis-notes/api/editor/language-service:
import { languageServiceExtensions } from "@lapis-notes/api/editor/language-service";
this.registerEditorExtension( markupEditor( javascript({ typescript: true }), languageServiceExtensions({ languageId: "typescript" }), ), "typescript",);Options let you enable subsets of features per view:
languageServiceExtensions({ languageId: "markdown", completion: false, hover: false,});The markdown plugin enables diagnostics only. TypeScript views enable diagnostics, completions, and hover.
What the editor renders
Section titled “What the editor renders”Shared adapters in the API package connect app.languageServices to CodeMirror:
- Diagnostics — gutter markers, inline squiggles, and a Lapis-owned lint tooltip (not CodeMirror’s default lint tooltip)
- Completions —
@codemirror/autocompleteoverride - Hover —
hoverTooltip()integration - Code actions — exposed through the lint UI
Notebook cell editors can supply a virtual document through a CodeMirror facet so TypeScript sees the full notebook while diagnostics map back to the focused cell.
Language services vs CodeMirror autocomplete
Section titled “Language services vs CodeMirror autocomplete”Not every suggestion belongs in a language service.
| Use language services when | Use CodeMirror extensions when |
|---|---|
| Analysis needs document or project context | Suggestions come from vault structure or local syntax |
| Results should merge with other providers | Behavior is markdown- or view-specific |
| You want consistent diagnostics and quick fixes | You need custom trigger rules or widget UI |
Examples:
- TypeScript completions — language service (TypeScript compiler API in a worker)
- Markdown wiki link autocomplete — CodeMirror completion in the markdown plugin
- Search query suggestions — custom autocomplete in the search query editor
- Markdownlint diagnostics — language service
Shipped providers
Section titled “Shipped providers”Lapis ships these language-service providers today:
| Provider | Languages | Capabilities |
|---|---|---|
| Markdownlint | markdown | diagnostics, code actions |
| TypeScript | typescript, javascript | diagnostics, completions, hover, definition (API only) |