VSCode Language Server Protocol client implementation for extension integration with language servers
—
Base interfaces and classes for implementing static and dynamic language server features.
Base interfaces defining the feature system architecture.
/**
* Base interface for all language client features
*/
interface Feature {
/** Get the current state of the feature */
getState(): FeatureState;
/** Clear the feature state and cleanup resources */
clear(): void;
}
/**
* Feature state enumeration
*/
enum FeatureState {
/** Feature is not initialized */
Initial,
/** Feature is starting */
Starting,
/** Feature is active and running */
Started,
/** Feature has been stopped */
Stopped,
/** Feature is disposing */
Disposing,
/** Feature has been disposed */
Disposed
}Features that are registered once during client initialization.
/**
* Interface for static language server features
* Static features are registered once during initialization and don't support dynamic registration
*/
interface StaticFeature extends Feature {
/**
* Fill initialization parameters sent to the server
* Called before client initialization
*/
fillInitializeParams?(params: InitializeParams): void;
/**
* Fill client capabilities advertised to the server
* Called during client initialization
*/
fillClientCapabilities(capabilities: ClientCapabilities): void;
/**
* Pre-initialization hook called before the feature is initialized
* Used for setup that requires server capabilities
*/
preInitialize?(capabilities: ServerCapabilities, documentSelector: DocumentSelector | undefined): void;
/**
* Initialize the feature with server capabilities
* Called after successful server initialization
*/
initialize(capabilities: ServerCapabilities, documentSelector: DocumentSelector | undefined): void;
}Usage Examples:
Implementing a static feature:
class CustomStaticFeature implements StaticFeature {
private _state: FeatureState = FeatureState.Initial;
fillClientCapabilities(capabilities: ClientCapabilities): void {
// Add custom capabilities
capabilities.experimental = {
...capabilities.experimental,
customFeature: true
};
}
initialize(capabilities: ServerCapabilities, documentSelector: DocumentSelector | undefined): void {
if (capabilities.experimental?.customFeature) {
this._state = FeatureState.Started;
// Initialize feature logic
}
}
getState(): FeatureState {
return this._state;
}
clear(): void {
this._state = FeatureState.Disposed;
// Cleanup resources
}
}Features that support dynamic registration and unregistration.
/**
* Interface for dynamic language server features
* Dynamic features can be registered and unregistered at runtime
*/
interface DynamicFeature<RO> extends StaticFeature {
/** Registration type that identifies this feature */
readonly registrationType: RegistrationType<RO>;
/**
* Register the feature with specific options
* Called when server requests feature registration
*/
register(data: RegistrationData<RO>): void;
/**
* Unregister the feature by ID
* Called when server requests feature unregistration
*/
unregister(id: string): void;
}
/**
* Registration data for dynamic features
*/
interface RegistrationData<T> {
/** Unique registration identifier */
id: string;
/** Registration method name */
method: string;
/** Registration options specific to the feature */
registerOptions?: T;
}
/**
* Registration type interface
*/
interface RegistrationType<RO> {
/** LSP method name for this registration type */
readonly method: string;
}Usage Examples:
Implementing a dynamic feature:
class CustomDynamicFeature implements DynamicFeature<CustomRegistrationOptions> {
private _state: FeatureState = FeatureState.Initial;
private _registrations = new Map<string, CustomRegistration>();
readonly registrationType: RegistrationType<CustomRegistrationOptions> = {
method: 'textDocument/customFeature'
};
fillClientCapabilities(capabilities: ClientCapabilities): void {
capabilities.textDocument = capabilities.textDocument || {};
capabilities.textDocument.customFeature = {
dynamicRegistration: true
};
}
initialize(capabilities: ServerCapabilities, documentSelector: DocumentSelector | undefined): void {
this._state = FeatureState.Started;
}
register(data: RegistrationData<CustomRegistrationOptions>): void {
const registration = new CustomRegistration(data.id, data.registerOptions);
this._registrations.set(data.id, registration);
}
unregister(id: string): void {
const registration = this._registrations.get(id);
if (registration) {
registration.dispose();
this._registrations.delete(id);
}
}
getState(): FeatureState {
return this._state;
}
clear(): void {
for (const registration of this._registrations.values()) {
registration.dispose();
}
this._registrations.clear();
this._state = FeatureState.Disposed;
}
}Specialized interfaces for features that provide VS Code providers.
/**
* Mixin interface for text document provider features
* Features implementing this can provide providers per text document
*/
interface TextDocumentProviderFeature<T> {
/**
* Get the provider for a specific text document
* @param textDocument - The text document to get provider for
* @returns Provider instance or undefined if not available
*/
getProvider(textDocument: TextDocument): T | undefined;
}
/**
* Interface for workspace provider features
* Features implementing this provide workspace-wide functionality
*/
interface WorkspaceProviderFeature<PR> {
/**
* Get all providers registered for workspace operations
* @returns Array of provider instances or undefined
*/
getProviders(): PR[] | undefined;
}
/**
* Interface for features that can be client-specific
*/
interface FeatureClient<M, PO> {
/** Get the method name for this feature */
getRegistrationType(): RegistrationType<PO>;
/** Fill client capabilities for this feature */
fillClientCapabilities(capabilities: ClientCapabilities): void;
/** Initialize the feature */
initialize(capabilities: M, documentSelector: DocumentSelector | undefined): void;
/** Register options for dynamic registration */
register(data: RegistrationData<PO>): void;
/** Unregister by ID */
unregister(id: string): void;
/** Dispose the feature */
dispose(): void;
}Base classes for text document-based features.
/**
* Base class for text document synchronization features
*/
abstract class TextDocumentSendFeature<T> implements DynamicFeature<T> {
protected _client: BaseLanguageClient;
protected _registrationType: RegistrationType<T>;
constructor(client: BaseLanguageClient, registrationType: RegistrationType<T>);
abstract fillClientCapabilities(capabilities: ClientCapabilities): void;
abstract initialize(capabilities: ServerCapabilities, documentSelector: DocumentSelector | undefined): void;
register(data: RegistrationData<T>): void;
unregister(id: string): void;
getState(): FeatureState;
clear(): void;
get registrationType(): RegistrationType<T>;
}
/**
* Interface for features that send text document notifications
*/
interface DidOpenTextDocumentFeatureShape {
/** Handle document open event */
didOpen(textDocument: TextDocument): void;
}
/**
* Interface for features that send text document close notifications
*/
interface DidCloseTextDocumentFeatureShape {
/** Handle document close event */
didClose(textDocument: TextDocument): void;
}
/**
* Interface for features that send text document change notifications
*/
interface DidChangeTextDocumentFeatureShape {
/** Handle document change event */
didChange(event: TextDocumentChangeEvent): void;
}
/**
* Interface for features that send text document save notifications
*/
interface DidSaveTextDocumentFeatureShape {
/** Handle document save event */
didSave(textDocument: TextDocument): void;
}Standard shapes for VS Code language providers.
/**
* Shape interface for code lens providers
*/
interface CodeLensProviderShape {
/** Provide code lenses for a document */
provideCodeLenses(document: TextDocument, token: CancellationToken): ProviderResult<CodeLens[]>;
/** Resolve a code lens */
resolveCodeLens?(codeLens: CodeLens, token: CancellationToken): ProviderResult<CodeLens>;
/** Event fired when code lenses should be refreshed */
onDidChangeCodeLenses?: Event<void>;
}
/**
* Shape interface for diagnostic providers
*/
interface DiagnosticProviderShape {
/** Event fired when diagnostics change */
onDidChangeDiagnostics: Event<void>;
/** Provide diagnostics for a document */
provideDiagnostics(
document: TextDocument | Uri,
previousResultId: string | undefined,
token: CancellationToken
): ProviderResult<DocumentDiagnosticReport>;
/** Provide workspace diagnostics */
provideWorkspaceDiagnostics?(
resultIds: PreviousResultId[],
token: CancellationToken,
resultReporter: ResultReporter
): ProviderResult<WorkspaceDiagnosticReport>;
}
/**
* Shape interface for semantic tokens providers
*/
interface SemanticTokensProviderShape {
/** Provide semantic tokens for entire document */
provideDocumentSemanticTokens(document: TextDocument, token: CancellationToken): ProviderResult<SemanticTokens>;
/** Provide semantic token edits */
provideDocumentSemanticTokensEdits?(
document: TextDocument,
previousResultId: string,
token: CancellationToken
): ProviderResult<SemanticTokensEdits | SemanticTokens>;
/** Provide semantic tokens for a range */
provideDocumentRangeSemanticTokens?(
document: TextDocument,
range: Range,
token: CancellationToken
): ProviderResult<SemanticTokens>;
}
/**
* Shape interface for inlay hints providers
*/
interface InlayHintsProviderShape {
/** Provide inlay hints for a document range */
provideInlayHints(document: TextDocument, range: Range, token: CancellationToken): ProviderResult<InlayHint[]>;
/** Resolve an inlay hint */
resolveInlayHint?(item: InlayHint, token: CancellationToken): ProviderResult<InlayHint>;
/** Event fired when inlay hints should be refreshed */
onDidChangeInlayHints?: Event<void>;
}
/**
* Shape interface for inline value providers
*/
interface InlineValueProviderShape {
/** Provide inline values for a document range */
provideInlineValues(
document: TextDocument,
viewPort: Range,
context: InlineValueContext,
token: CancellationToken
): ProviderResult<InlineValue[]>;
/** Event fired when inline values should be refreshed */
onDidChangeInlineValues?: Event<void>;
}Utilities for feature registration and management.
/**
* Ensure a registration data object is properly formatted
*/
function ensure<T>(target: T, key: keyof T): T[typeof key];
/**
* Language Server Protocol cancellation error
*/
class LSPCancellationError extends Error {
/** LSP error data */
readonly data: any;
constructor(data: any);
}
/**
* Create all proposed features for experimental LSP capabilities
*/
function createAll(): (StaticFeature | DynamicFeature<any>)[];Usage Examples:
Registering features with client:
import { LanguageClient } from "vscode-languageclient/node";
const client = new LanguageClient(/* ... */);
// Register a static feature
const staticFeature = new CustomStaticFeature();
client.registerFeature(staticFeature);
// Register a dynamic feature
const dynamicFeature = new CustomDynamicFeature();
client.registerFeature(dynamicFeature);
// Register proposed features
const proposedFeatures = createAll();
proposedFeatures.forEach(feature => client.registerFeature(feature));Feature with provider interface:
class CustomProviderFeature implements DynamicFeature<CustomOptions>, TextDocumentProviderFeature<CustomProvider> {
private _providers = new Map<string, CustomProvider>();
getProvider(textDocument: TextDocument): CustomProvider | undefined {
// Find appropriate provider for document
for (const provider of this._providers.values()) {
if (provider.canHandle(textDocument)) {
return provider;
}
}
return undefined;
}
register(data: RegistrationData<CustomOptions>): void {
const provider = new CustomProvider(data.registerOptions);
this._providers.set(data.id, provider);
}
// ... implement other DynamicFeature methods
}Install with Tessl CLI
npx tessl i tessl/npm-vscode-languageclient