Language servers
A language server extension declares which files it serves and tells Terminay how to start the server for a project. Terminay owns the process and the protocol, and provides diagnostics, completion, hover, and go-to-definition in the editor.
Manifest contribution
This is the contribution of the bundled TypeScript extension. Available since API 2.1; no permission is required.
"permissions": [],
"contributes": {
"languageServers": [{
"id": "typescript",
"displayName": "TypeScript",
"description": "Runs typescript-language-server for TypeScript and JavaScript files.",
"languageIds": ["typescript", "typescriptreact", "javascript", "javascriptreact"],
"fileExtensions": [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"],
"runtimeNotes": "Uses the project's TypeScript when it is installed, and a bundled TypeScript otherwise."
}]
}
A file is served by the language server whose fileExtensions match it. Unlike
other contributions, a language server id is extension-local and not namespaced.
Field rules are in the manifest reference.
Runtime contract
Call context.registerLanguageServerProvider({ id, runtime }) during activation.
The runtime has one method, launch(request, signal).
import { defineExtension } from '@terminay/extension-api';
import type {
LanguageServerLaunch,
LanguageServerLaunchRequest,
LanguageServerProviderRuntime,
} from '@terminay/extension-api';
import { languageServerCliPath, resolveTypeScript } from './resolve.js';
export const typescriptLanguageServer: LanguageServerProviderRuntime = {
async launch(
request: LanguageServerLaunchRequest,
signal: AbortSignal,
): Promise<LanguageServerLaunch> {
if (signal.aborted) throw new Error('language server launch was aborted');
const typescript = resolveTypeScript(request.projectRoot);
return {
command: process.execPath,
args: [languageServerCliPath(), '--stdio'],
initializationOptions: {
tsserver: { path: typescript.tsserverPath },
},
description: typescript.description,
};
},
};
export default defineExtension({
activate(context) {
context.registerLanguageServerProvider({
id: 'typescript',
runtime: typescriptLanguageServer,
});
},
});| Request member | Meaning |
|---|---|
languageServerId | The declared ID being launched. |
projectRoot | Absolute path of the project on the server. The extension may read it. |
| Launch field | Required | Meaning and bound |
|---|---|---|
command | Yes | An absolute path, or an executable resolvable on PATH. Single line. |
args | Yes | At most 64 non-empty strings. |
env | No | At most 32 entries, overlaid on a minimal host environment. The server's environment is not inherited wholesale. |
initializationOptions | No | JSON passed to the server in LSP initialize. |
description | No | Safe detail for Settings, such as project typescript 5.6.2. At most 200 characters. |
The launch is a closed object, validated by validateLanguageServerLaunch before
anything is spawned. The server must speak LSP over stdio.
What Terminay owns
- Spawning the server as a child of the extension process, with the project root as its working directory.
- The base environment:
PATH,HOME,LANG,LC_ALL,LC_CTYPE,TMPDIR, andTZ, beforelaunch.envis applied. - Stdio framing, LSP
initialize, document synchronisation, deadlines, and shutdown. - Translation of results into bounded Terminay data. No LSP JSON-RPC reaches a client.
A language server extension contributes no UI and registers no protocol operations.
Session lifecycle
- One session exists per project and language server, shared by every client of that project.
- A session starts on the first language request for a file it serves.
- A session with no open documents and no requests is stopped after an idle period.
- Each server caps concurrent language sessions. A request beyond the cap returns an unavailable outcome instead of starting a session.
- A language server that exits unexpectedly counts once against the extension's crash window, and the editor degrades to highlighting and editing.
- Disabling or quarantining the extension ends its sessions.
Test the launch
createLanguageServerExtensionHarness and openLanguageServerSession
activate the extension, start the launch it returns, and speak LSP to it over stdio. See
Testing and conformance.