# Vscode Languageserver Node > NPM Package:`vscode-languageclient` --- # vscode-languageclient Source: https://github.com/microsoft/vscode-languageserver-node/tree/main/client NPM Package: `vscode-languageclient` The client library that allows VSCode extensions to easily integrate language servers adhering to the Language Server Protocol. ## Overview This npm module provides the client-side implementation of the Language Server Protocol for VSCode extensions. It handles the communication between the VSCode editor and the language server running either locally or on a remote machine. ## Installation ```bash npm install vscode-languageclient ``` ## Basic Usage ### Creating a Language Client ```typescript import * as vscode from 'vscode'; import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from 'vscode-languageclient/node'; let client: LanguageClient; export function activate(context: vscode.ExtensionContext) { // Location of the server module that was shipped with the extension const serverModule = context.asAbsolutePath( join('server', 'out', 'server.js') ); // If the extension is launched in debug mode then the debug server options are used // Otherwise the run options are used const serverOptions: ServerOptions = { run: { module: serverModule, transport: TransportKind.ipc }, debug: { module: serverModule, transport: TransportKind.ipc, options: { execArgv: ['--nolazy', '--inspect=6009'] } } }; // Options to control the language client const clientOptions: LanguageClientOptions = { // Register the server for specific document types documentSelector: [{ scheme: 'file', language: 'mylanguage' }], synchronize: { // Notify the server about file changes to '.clientrc files' fileEvents: vscode.workspace.createFileSystemWatcher('**/.clientrc') } }; // Create the language client and start the client. client = new LanguageClient( 'languageServerExample', 'Language Server Example', serverOptions, clientOptions ); // Start the client. This will also launch the server const disposable = client.start(); // Push the disposable to the context's subscriptions so that the // client can be deactivated on extension deactivation context.subscriptions.push(disposable); } export function deactivate(): Thenable | undefined { if (!client) { return undefined; } return client.stop(); } ``` ## Server Options The `ServerOptions` interface defines how to start the language server: ```typescript interface ServerOptions { run?: Executable | { command: string; args?: string[] }; debug?: Executable | { command: string; args?: string[] }; } interface Executable { command: string; args?: string[]; options?: ExecOptions; } ``` ### Common Transport Types - **ipc** - Node.js inter-process communication (default for local servers) - **stdio** - Standard input/output stream - **socket** - TCP socket connection - **pipe** - Named pipe ### Server Startup Options ```typescript const serverOptions: ServerOptions = { // Run locally via IPC run: { module: serverModule, transport: TransportKind.ipc }, // Run with debugging debug: { module: serverModule, transport: TransportKind.ipc, options: { execArgv: ['--nolazy', '--inspect=6009'], cwd: process.cwd() } } }; ``` ## Client Options The `LanguageClientOptions` interface configures client behavior: ```typescript interface LanguageClientOptions { documentSelector?: DocumentSelector; synchronize?: SynchronizeOptions; diagnosticCollectionName?: string; outputChannelName?: string; revealOutputChannelOn?: RevealOutputChannelOn; stdioEncoding?: string; initializationOptions?: any; initializationFailureHandler?: ( error: ResponseError | Error, client: LanguageClient, clientOptions: LanguageClientOptions ) => boolean; errorHandler?: ErrorHandler; middleware?: ProvideCompletionItemsSignature; uriConverters?: URIConverter; workspaceFolder?: WorkspaceFolder | null; progressOnInitialization?: boolean; } ``` ### Document Selector Specifies which files the language server should handle: ```typescript const clientOptions: LanguageClientOptions = { documentSelector: [ // Handle all TypeScript and JavaScript files { scheme: 'file', language: 'typescript' }, { scheme: 'file', language: 'javascript' }, // Handle untitled/unsaved files { scheme: 'untitled', language: 'typescript' }, // Handle custom URI schemes { scheme: 'custom', language: 'mylanguage' } ] }; ``` ### Synchronize Options Control what file changes are synchronized to the server: ```typescript const clientOptions: LanguageClientOptions = { synchronize: { // Synchronize setting sections to the server configurationSection: ['myLanguage', 'myLanguage.diagnostics'], // Notify the server about file changes to matching patterns fileEvents: [ vscode.workspace.createFileSystemWatcher('**/*.ml'), vscode.workspace.createFileSystemWatcher('**/*.mli'), vscode.workspace.createFileSystemWatcher('**/.ocamlformat') ] } }; ``` ## Client Lifecycle ### Starting the Client ```typescript // Start the client and wait for it to be ready const disposable = client.start(); // Or wait for initialization to complete await client.onReady(); ``` ### Stopping the Client ```typescript // Stop the client (async operation) await client.stop(); ``` ### Deactivation ```typescript export async function deactivate(): Promise { if (client) { await client.stop(); } } ``` ## Middleware Middleware allows you to intercept and modify messages between client and server: ```typescript const clientOptions: LanguageClientOptions = { middleware: { // Intercept completion requests provideCompletionItem: ( document, position, context, token, next ) => { // Pre-processing console.log('Requesting completions at', position); // Call the next handler (could be another middleware or the server) const result = next(document, position, context, token); // Post-processing return result; }, // Intercept hover requests provideHover: (document, position, token, next) => { return next(document, position, token); }, // Intercept diagnostics publishDiagnostics: (uri, diagnostics, next) => { // Filter or modify diagnostics const filtered = diagnostics.filter(d => d.severity === DiagnosticSeverity.Error); return next(uri, filtered); } } }; ``` ### Common Middleware Hooks - `provideCompletionItem` - `provideHover` - `provideSignatureHelp` - `provideDefinition` - `provideReferences` - `provideDocumentHighlights` - `provideCodeActions` - `provideCodeLenses` - `provideDocumentSymbols` - `provideWorkspaceSymbols` - `provideDocumentFormattingEdits` - `provideDocumentRangeFormattingEdits` - `provideOnTypeFormattingEdits` - `provideRenameEdits` - `publishDiagnostics` ## Error Handling Implement custom error handling: ```typescript const errorHandler: ErrorHandler = { error: (error, message, count) => { if (count && count <= 3) { return ErrorAction.Continue; } return ErrorAction.Shutdown; }, closed: () => { return CloseAction.DoNotRestart; } }; const clientOptions: LanguageClientOptions = { errorHandler: errorHandler }; ``` ### Error Action ```typescript enum ErrorAction { Continue = 1, Shutdown = 2 } enum CloseAction { DoNotRestart = 1, Restart = 2 } ``` ## Feature Support The client automatically provides LSP features once the server advertises support: ### Dynamic Feature Registration ```typescript // The server can dynamically register handlers client.onNotification('custom/notification', (params) => { // Handle custom notification }); // The client will unregister handlers when the server requests client.onRequest('custom/request', async (params) => { return { /* response */ }; }); ``` ## Output Channel Access the client's output channel for logging: ```typescript // Get the output channel const outputChannel = client.outputChannel; // Messages are automatically logged here // You can also append your own messages outputChannel.appendLine('Custom message'); outputChannel.show(); ``` ## Configuration Access workspace configuration from your extension: ```typescript // Get configuration const config = vscode.workspace.getConfiguration('myLanguage'); const value = config.get('setting'); // Listen to configuration changes vscode.workspace.onDidChangeConfiguration((event) => { if (event.affectsConfiguration('myLanguage')) { // Reload configuration } }); ``` ## File Operations Watch for file system events: ```typescript const clientOptions: LanguageClientOptions = { synchronize: { fileEvents: vscode.workspace.createFileSystemWatcher('**/*.ml') } }; // Also handle didCreate, didChange, didDelete const watcher = vscode.workspace.createFileSystemWatcher('**/*.json'); watcher.onDidCreate(uri => { // File created }); watcher.onDidChange(uri => { // File changed }); watcher.onDidDelete(uri => { // File deleted }); ``` ## URI Conversion Convert between VSCode URIs and server URIs: ```typescript const clientOptions: LanguageClientOptions = { uriConverters: { code2Protocol: (uri: vscode.Uri): string => uri.toString(true), protocol2Code: (value: string): vscode.Uri => vscode.Uri.parse(value) } }; ``` ## Advanced Features ### Progress Reporting ```typescript // Create a progress indicator vscode.window.withProgress( { location: vscode.ProgressLocation.Notification, title: 'Working...' }, async () => { // Long-running operation await client.sendRequest('custom/longOperation', {}); } ); ``` ### Workspace Folders Work with multiple workspace folders: ```typescript const workspaceFolders = vscode.workspace.workspaceFolders; // Access workspace folder for a document const workspaceFolder = vscode.workspace.getWorkspaceFolder(document.uri); ``` ### Extension Activation Control when the extension activates: ```typescript // package.json { "activationEvents": [ "onLanguage:typescript", "onLanguage:javascript", "onCommand:extension.activate" ] } ``` ## Best Practices 1. **Always dispose of resources** - Push disposables to context.subscriptions 2. **Handle async operations** - Use `await` with client methods 3. **Implement proper error handling** - Don't let errors go unhandled 4. **Monitor server health** - Implement error and close handlers 5. **Use diagnostics collection** - Manage diagnostic collections properly 6. **Synchronize configuration** - Keep server and client config in sync 7. **Test with debugging** - Use the debug configuration to test server startup ## Version History For detailed version history and breaking changes, see the main repository README. The client module is actively maintained and receives regular updates aligned with LSP specification versions. --- # VSCode Language Server - Node Source: https://github.com/microsoft/vscode-languageserver-node Microsoft's Node.js-based Language Server Protocol (LSP) implementation framework, containing the core npm modules for building language servers and clients for Visual Studio Code. ## Overview This repository contains the code for the following npm modules: - **vscode-languageclient**: npm module to talk to a VSCode language server from a VSCode extension - **vscode-languageserver**: npm module to implement a VSCode language server using Node.js as a runtime - **vscode-languageserver-textdocument**: npm module to implement text documents usable in a LSP server using Node.js - **vscode-languageserver-protocol**: the actual language server protocol definition in TypeScript - **vscode-languageserver-types**: data types used by the language server client and server - **vscode-jsonrpc**: the underlying message protocol to communicate between a client and a server All npm modules are built using one Azure Pipeline and are available on npm registry. ## Getting Started For a detailed document on how to use these npm modules to implement language servers for VSCode, see the official documentation at https://code.visualstudio.com/docs/extensions/example-language-server ### Setting Up the Repository After cloning the repository, run the following commands to install dependencies and link packages: ```bash npm install npm run symlink ``` The `symlink` command points packages in this repository to each other, allowing for local development and testing across multiple packages. ## Core Packages ### vscode-languageclient The client-side library that allows VSCode extensions to communicate with language servers adhering to the Language Server Protocol. **NPM:** `vscode-languageclient` **Key Features:** - Easy integration of language servers into VSCode extensions - Support for request/response and notification messaging patterns - Middleware system for intercepting and modifying messages - Error handling and server restart mechanisms - Support for multiple transport types ### vscode-languageserver The server-side library that implements the Language Server Protocol for Node.js runtimes. **NPM:** `vscode-languageserver` **Key Features:** - Full LSP 3.x support - Request and notification handlers - Document synchronization (incremental and full) - Configuration management - Workspace symbols and document symbols - Code actions, diagnostics, and formatting support ### vscode-languageserver-protocol Tool-independent implementation of the LSP definition in TypeScript. **NPM:** `vscode-languageserver-protocol` **Key Features:** - Protocol definitions aligned with LSP specification - Type-safe interfaces for all protocol messages - Support for proposed protocol features - Can be used in any Node.js application ### vscode-languageserver-types Shared data types used by both client and server implementations. **NPM:** `vscode-languageserver-types` **Key Types:** - `Position`, `Range`, `Location` - `TextDocument`, `TextDocumentIdentifier` - `Diagnostic`, `DiagnosticSeverity` - `CompletionItem`, `CompletionList` - `Hover`, `SymbolInformation` - `CodeLens`, `CodeAction` - `DocumentSymbol`, `SymbolKind` ### vscode-languageserver-textdocument Provides a standard text document implementation with incremental update capabilities. **NPM:** `vscode-languageserver-textdocument` **Features:** - Incremental text document synchronization - Line and character position tracking - Full document access with offset calculations - Used as prerequisite for modern LSP servers ### vscode-jsonrpc Base messaging protocol for client-server communication using JSON-RPC. **NPM:** `vscode-jsonrpc` **Key Features:** - JSON-RPC 2.0 protocol implementation - Support for stdio, node IPC, and socket transports - Request/response with correlation handling - Notification messaging - Connection lifecycle management ## Transport Types The LSP implementation supports multiple transport mechanisms: - **stdio** (standard input/output) - **Node IPC** (inter-process communication) - **Socket files** (named pipes, Unix domain sockets) - **TCP connections** - **Web sockets** (for browser-based clients) ## Architecture The library is structured with three layers: 1. **Common Layer** - Protocol definitions and shared types 2. **Node Layer** - Node.js specific implementations (stdio, IPC, sockets) 3. **Browser Layer** - Browser compatible implementations (web sockets) This three-tier architecture allows the same code to run in multiple environments: ```typescript // Common code (protocol definitions) import { RequestType } from 'vscode-languageserver-protocol'; // Node-specific code import { createMessageConnection } from 'vscode-jsonrpc/node'; // Browser-specific code import { createWebSocketMessageReader } from 'vscode-languageserver/browser'; ``` ## LSP Server Example Basic structure of a language server: ```typescript import { createConnection, TextDocuments, ProposedFeatures, TextDocumentSyncKind, InitializeParams, InitializeResult, Diagnostic, DiagnosticSeverity } from 'vscode-languageserver/node'; import { TextDocument } from 'vscode-languageserver-textdocument'; // Create connection via stdio const connection = createConnection(ProposedFeatures.all); // Create documents manager const documents = new TextDocuments(TextDocument); connection.onInitialize((params: InitializeParams) => { const result: InitializeResult = { capabilities: { textDocumentSync: TextDocumentSyncKind.Incremental, completionProvider: { resolveProvider: false } } }; return result; }); // Handle document changes documents.onDidChangeContent(change => { validateDocument(change.document); }); function validateDocument(textDocument: TextDocument): void { // Implementation } documents.listen(connection); connection.listen(); ``` ## LSP Client Example Basic structure of a language client extension: ```typescript import * as vscode from 'vscode'; import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from 'vscode-languageclient/node'; let client: LanguageClient; export async function activate(context: vscode.ExtensionContext) { const serverModule = context.asAbsolutePath( join('server', 'out', 'server.js') ); const serverOptions: ServerOptions = { run: { module: serverModule, transport: TransportKind.ipc }, debug: { module: serverModule, transport: TransportKind.ipc, options: { execArgv: ['--nolazy', '--inspect=6009'] } } }; const clientOptions: LanguageClientOptions = { documentSelector: [{ scheme: 'file', language: 'myLanguage' }] }; client = new LanguageClient( 'myLanguageServer', 'My Language Server', serverOptions, clientOptions ); await client.start(); } export async function deactivate() { if (client) { await client.stop(); } } ``` ## Middleware System Both client and server support middleware for intercepting messages: ```typescript // Client middleware example const clientOptions: LanguageClientOptions = { middleware: { provideCompletionItem: async ( document, position, context, token, next ) => { // Pre-processing const items = await next(document, position, context, token); // Post-processing return items; } } }; ``` ## Message Exchange Patterns ### Requests (Request/Response) ```typescript // Define custom request export const CustomRequest = new RequestType< CustomParams, CustomResult, void >('custom/request'); // Server-side handler connection.onRequest(CustomRequest, (params) => { return { result: 'value' }; }); // Client-side usage const result = await client.sendRequest(CustomRequest, params); ``` ### Notifications (One-way) ```typescript // Define custom notification export const CustomNotification = new NotificationType( 'custom/notification' ); // Server-side handler connection.onNotification(CustomNotification, (params) => { // Handle notification }); // Client-side usage client.sendNotification(CustomNotification, params); ``` ## Document Synchronization The library supports multiple document synchronization modes: - **None** - No synchronization - **Full** - Full document is sent on changes - **Incremental** - Only changed parts are sent (more efficient) ```typescript // Server capability declaration capabilities: { textDocumentSync: { openClose: true, change: TextDocumentSyncKind.Incremental, save: { includeText: true } } } ``` ## Configuration Management Servers can request configuration from the client: ```typescript // Server requests configuration const config = await connection.sendRequest( ConfigurationRequest.type, { items: [{ section: 'myLanguage' }] } ); // Handle document did change notification connection.onDidChangeConfiguration((change) => { // Configuration changed }); ``` ## Workspace Features ### Workspace Symbols ```typescript connection.onWorkspaceSymbol((params) => { return [ { name: 'symbolName', kind: SymbolKind.Class, location: { uri: documentUri, range: range } } ]; }); ``` ### Workspace Edits ```typescript const workspaceEdit: WorkspaceEdit = { changes: { [documentUri]: [ TextEdit.insert(position, 'inserted text') ] } }; ``` ## Diagnostics Publish diagnostics to the client: ```typescript connection.sendDiagnostics({ uri: documentUri, diagnostics: [ { severity: DiagnosticSeverity.Error, range: range, message: 'Error message' } ] }); ``` ## Code Actions Implement code actions for fixing diagnostics: ```typescript connection.onCodeAction((params) => { const diagnostics = params.context.diagnostics; return diagnostics.map(diagnostic => ({ title: 'Fix', kind: CodeActionKind.QuickFix, edit: { changes: { [params.textDocument.uri]: [ TextEdit.replace(diagnostic.range, 'fixed text') ] } } })); }); ``` ## Completion Implement code completion: ```typescript connection.onCompletion((params) => { return [ { label: 'keyword', kind: CompletionItemKind.Keyword, detail: 'Description', insertText: 'keyword' } ]; }); connection.onCompletionResolve((item) => { item.documentation = 'Full documentation'; return item; }); ``` ## Recent Changes and Versions ### Next (10.0.0-next.* Client, 10.0.0-next.* Server, 9.0.0-next.* jsonrpc) - Upgraded to newer libraries, compilers and package.json exports rules - Compiler upgraded to TypeScript 5.9.x - Libraries now depend on NodeJS 22.13.14 and ES2022 target - Uses `exports` property instead of `main` and `typings` in package.json - Added proposed `CodeActionKind.RefactorMove` - Snippet support in Workspace edits - Parallelism control for dispatch requests and notifications - Browser implementation consistency with Node implementation ### 3.17.5 Protocol, 9.0.1 Client and 9.0.1 Server - Fixed ESM bundling ### 3.17.4 Protocol, 8.2.0 JSON-RPC, 9.0.0 Client and 9.0.0 Server - Added proposed inline completion request - Added proposed formatting ranges request - Proposed refresh request for folding ranges - Custom message handlers support ### 3.17.3 Protocol, 8.1.0 JSON-RPC, 8.1.0 Client and 8.1.0 Server - Support for custom message handlers - Bug fixes around request ordering with full document sync ### 3.17.0 Protocol, 8.0.0 JSON-RPC, 8.0.0 Client and 8.0.0 Server - Breaking change: `client.start()` and `client.stop()` now return promises - Notification and request handler registration before client start - All `sendNotification` methods return promises - Handler registrations return `Disposable` for unregistration - Support for inline values, inlay hints, type hierarchies, notebook documents ## Feature Support by Version The library supports a comprehensive set of LSP features: - **Diagnostics** - Report code issues and errors - **Hover** - Hover information for symbols - **Completion** - Code completion with snippets - **Signature Help** - Function signature assistance - **Go to Definition** - Symbol definition navigation - **Find References** - Find all symbol usages - **Document Highlights** - Highlight matching symbols - **Code Actions** - Quick fixes and refactorings - **Code Lens** - Inline code metrics and actions - **Document Symbols** - Outline of document structure - **Workspace Symbols** - Global symbol search - **Format Document** - Document and range formatting - **Format on Type** - Format as user types - **Rename** - Symbol rename with workspace refactoring - **Semantic Tokens** - Syntax highlighting with semantic information - **Folding Ranges** - Collapsible code regions - **Selection Ranges** - Smart selection expansion - **Call Hierarchies** - Call chain navigation - **Inlay Hints** - Inline type and parameter hints - **Inline Values** - Runtime variable values display - **Type Hierarchies** - Class inheritance visualization - **Linked Editing Range** - Multi-cursor for related identifiers - **Document Links** - Clickable links in documents - **Moniker** - Cross-language symbol identification - **Notebook Document** - Jupyter and similar notebook support ## Browser Support The library now has browser implementations for client and server, enabling: - Web-based language servers - Shared TypeScript instances across browser tabs - Web-based IDE implementations ## Error Handling Servers can implement custom error handlers: ```typescript interface ErrorHandler { error(error: Error, message: Message, count: number): ErrorAction; closed(): CloseAction; } enum ErrorAction { Continue = 1, Shutdown = 2 } enum CloseAction { DoNotRestart = 1, Restart = 2 } ``` ## Development ### Running Tests The repository includes comprehensive tests for all packages: ```bash npm test ``` ### Building ```bash npm run build ``` ### Publishing All packages are published to npm registry from this monorepo. ## License MIT - See LICENSE.txt in the repository ## Related Resources - [Language Server Protocol Specification](https://microsoft.github.io/language-server-protocol/) - [VSCode Extension Development](https://code.visualstudio.com/api) - [VSCode Language Server Example](https://code.visualstudio.com/docs/extensions/example-language-server) - [NPM Package: vscode-languageclient](https://www.npmjs.com/package/vscode-languageclient) - [NPM Package: vscode-languageserver](https://www.npmjs.com/package/vscode-languageserver) - [NPM Package: vscode-languageserver-protocol](https://www.npmjs.com/package/vscode-languageserver-protocol) - [NPM Package: vscode-jsonrpc](https://www.npmjs.com/package/vscode-jsonrpc) --- # vscode-languageserver-protocol Source: https://github.com/microsoft/vscode-languageserver-node/tree/main/protocol NPM Package: `vscode-languageserver-protocol` Tool-independent implementation of the Language Server Protocol definition in TypeScript. This module can be used in any type of Node.js application. ## Overview This npm module provides type-safe definitions for all LSP protocol messages, making it suitable for building language server protocol implementations in various contexts beyond just VSCode. ## Installation ```bash npm install vscode-languageserver-protocol ``` ## Protocol Definition Structure The protocol module defines three categories of messages: 1. **Requests** - Messages expecting a response 2. **Notifications** - One-way messages without response 3. **Server Capabilities** - What features the server supports ## Request Types ### Defining Custom Requests ```typescript import { RequestType, RequestType0, RequestType1, RequestType2 } from 'vscode-languageserver-protocol'; // No parameters export const MyRequest0 = new RequestType0('my/request0'); // One parameter export const MyRequest1 = new RequestType1('my/request1'); // Two parameters export const MyRequest2 = new RequestType2( 'my/request2' ); // Multiple parameters (as single object) export interface MyParams { param1: string; param2: number; } export interface MyResult { result: string; } export const MyRequest = new RequestType( 'my/customRequest' ); ``` ### Using Request Types On the server: ```typescript connection.onRequest(MyRequest, async (params: MyParams) => { // Process request return { result: 'response value' }; }); ``` On the client: ```typescript const result = await client.sendRequest(MyRequest, { param1: 'value', param2: 42 }); ``` ## Notification Types ### Defining Custom Notifications ```typescript import { NotificationType, NotificationType0 } from 'vscode-languageserver-protocol'; // No parameters export const MyNotification0 = new NotificationType0('my/notification0'); // With parameters export interface MyNotificationParams { message: string; } export const MyNotification = new NotificationType( 'my/notification' ); ``` ### Using Notification Types On the server: ```typescript connection.onNotification(MyNotification, (params: MyNotificationParams) => { console.log(params.message); }); ``` On the client: ```typescript client.sendNotification(MyNotification, { message: 'Notification message' }); ``` ## Core Protocol Messages ### Text Synchronization ```typescript import { DidOpenTextDocumentNotification, DidChangeTextDocumentNotification, DidCloseTextDocumentNotification, DidSaveTextDocumentNotification, TextDocumentSyncKind, TextDocumentSyncOptions } from 'vscode-languageserver-protocol'; // Server capability capabilities: { textDocumentSync: TextDocumentSyncKind.Incremental, // or with options textDocumentSync: { openClose: true, change: TextDocumentSyncKind.Incremental, save: { includeText: true } } } ``` ### Hover ```typescript import { HoverRequest, HoverParams, Hover, MarkupKind } from 'vscode-languageserver-protocol'; // Server capability capabilities: { hoverProvider: true } // Handler connection.onHover((params: HoverParams) => { return { contents: { kind: MarkupKind.Markdown, value: 'Hover information' } } as Hover; }); ``` ### Completion ```typescript import { CompletionRequest, CompletionParams, CompletionList, CompletionItem, CompletionItemKind, InsertTextFormat } from 'vscode-languageserver-protocol'; // Server capability capabilities: { completionProvider: { resolveProvider: true, triggerCharacters: ['.', ':'], allCommitCharacters: [';', ','] } } // Handler connection.onCompletion((params: CompletionParams) => { return { isIncomplete: false, items: [ { label: 'keyword', kind: CompletionItemKind.Keyword, detail: 'A keyword', documentation: 'Keyword documentation', insertText: 'keyword', insertTextFormat: InsertTextFormat.PlainText, sortText: '1' } ] } as CompletionList; }); // Resolve completion connection.onCompletionResolve((item: CompletionItem) => { item.data = { /* custom data */ }; return item; }); ``` ### Go to Definition ```typescript import { DefinitionRequest, DefinitionParams, Location, LocationLink } from 'vscode-languageserver-protocol'; // Server capability capabilities: { definitionProvider: true } // Handler connection.onDefinition((params: DefinitionParams) => { return Location.create(uri, range); // or return [ LocationLink.create(originUri, originRange, targetUri, targetRange) ]; }); ``` ### Find References ```typescript import { ReferencesRequest, ReferencesParams } from 'vscode-languageserver-protocol'; // Server capability capabilities: { referencesProvider: true } // Handler connection.onReferences((params: ReferencesParams) => { return [ Location.create(uri1, range1), Location.create(uri2, range2) ]; }); ``` ### Document Symbols ```typescript import { DocumentSymbolRequest, DocumentSymbolParams, SymbolInformation, DocumentSymbol, SymbolKind } from 'vscode-languageserver-protocol'; // Server capability capabilities: { documentSymbolProvider: true } // Handler (returning SymbolInformation) connection.onDocumentSymbol((params: DocumentSymbolParams) => { return [ SymbolInformation.create( 'className', SymbolKind.Class, range, params.textDocument.uri ) ]; }); // Or using hierarchical DocumentSymbol connection.onDocumentSymbol((params: DocumentSymbolParams) => { return [ DocumentSymbol.create( 'className', 'class details', SymbolKind.Class, range, range, [ DocumentSymbol.create( 'methodName', undefined, SymbolKind.Method, methodRange, methodRange ) ] ) ]; }); ``` ### Workspace Symbols ```typescript import { WorkspaceSymbolRequest, WorkspaceSymbolParams } from 'vscode-languageserver-protocol'; // Server capability capabilities: { workspaceSymbolProvider: true } // Handler connection.onWorkspaceSymbol((params: WorkspaceSymbolParams) => { return [ SymbolInformation.create( 'globalSymbol', SymbolKind.Function, range, uri ) ]; }); ``` ### Code Actions ```typescript import { CodeActionRequest, CodeActionParams, CodeAction, CodeActionKind, Command } from 'vscode-languageserver-protocol'; // Server capability capabilities: { codeActionProvider: true // or with options codeActionProvider: { codeActionKinds: [CodeActionKind.QuickFix], resolveProvider: true } } // Handler connection.onCodeAction((params: CodeActionParams) => { const fixes: CodeAction[] = []; for (const diagnostic of params.context.diagnostics) { fixes.push( CodeAction.create( 'Fix issue', { changes: { [params.textDocument.uri]: [ TextEdit.replace(diagnostic.range, 'fixed text') ] } }, CodeActionKind.QuickFix ) ); } return fixes; }); ``` ### Code Lens ```typescript import { CodeLensRequest, CodeLensParams, CodeLens, Command } from 'vscode-languageserver-protocol'; // Server capability capabilities: { codeLensProvider: { resolveProvider: true } } // Handler connection.onCodeLens((params: CodeLensParams) => { return [ CodeLens.create(range, { title: 'Run Test', command: 'extension.runTest', arguments: [testName] } as Command) ]; }); // Resolve code lens connection.onCodeLensResolve((lens: CodeLens) => { return lens; }); ``` ### Document Formatting ```typescript import { DocumentFormattingRequest, DocumentFormattingParams, DocumentRangeFormattingRequest, DocumentRangeFormattingParams, DocumentOnTypeFormattingRequest, DocumentOnTypeFormattingParams, TextEdit, FormattingOptions } from 'vscode-languageserver-protocol'; // Server capability capabilities: { documentFormattingProvider: true, documentRangeFormattingProvider: true, documentOnTypeFormattingProvider: { firstTriggerCharacter: '}', moreTriggerCharacter: [';', ','] } } // Document formatting handler connection.onDocumentFormatting((params: DocumentFormattingParams) => { return [ TextEdit.replace(wholeDocumentRange, formattedText) ]; }); // Range formatting handler connection.onDocumentRangeFormatting( (params: DocumentRangeFormattingParams) => { return [ TextEdit.replace(params.range, formattedRangeText) ]; } ); // On type formatting handler connection.onDocumentOnTypeFormatting( (params: DocumentOnTypeFormattingParams) => { return [ TextEdit.insert(params.position, additionalText) ]; } ); ``` ### Rename ```typescript import { RenameRequest, RenameParams, WorkspaceEdit } from 'vscode-languageserver-protocol'; // Server capability capabilities: { renameProvider: true // or with options renameProvider: { prepareProvider: true } } // Handler connection.onRename((params: RenameParams) => { return WorkspaceEdit.create({ changes: { [uri]: [ TextEdit.replace(range1, params.newName), TextEdit.replace(range2, params.newName) ] } }); }); // Prepare rename connection.onPrepareRename((params) => { return range; }); ``` ### Diagnostics ```typescript import { PublishDiagnosticsNotification, Diagnostic, DiagnosticSeverity, DiagnosticTag, CodeDescription } from 'vscode-languageserver-protocol'; // Send diagnostics connection.sendDiagnostics({ uri: documentUri, diagnostics: [ { severity: DiagnosticSeverity.Error, range: { start: { line: 0, character: 0 }, end: { line: 0, character: 5 } }, message: 'Error message', code: 'ERR001', source: 'myLanguage', tags: [DiagnosticTag.Unnecessary], codeDescription: { href: 'https://example.com/error-docs' }, relatedInformation: [ { location: { uri: otherUri, range: otherRange }, message: 'Related issue' } ] } ] }); ``` ### Signature Help ```typescript import { SignatureHelpRequest, SignatureHelpParams, SignatureInformation, ParameterInformation, MarkupContent, MarkupKind } from 'vscode-languageserver-protocol'; // Server capability capabilities: { signatureHelpProvider: { triggerCharacters: ['(', ','], retriggerCharacters: [','] } } // Handler connection.onSignatureHelp((params: SignatureHelpParams) => { return { signatures: [ SignatureInformation.create( 'function(arg1: string, arg2: number): void', { kind: MarkupKind.Markdown, value: 'Function documentation' } as MarkupContent, ParameterInformation.create( 'arg1', 'Argument 1' ), ParameterInformation.create( 'arg2', 'Argument 2' ) ) ], activeSignature: 0, activeParameter: 0 }; }); ``` ## Configuration Management ```typescript import { DidChangeConfigurationNotification, ConfigurationRequest, ConfigurationParams } from 'vscode-languageserver-protocol'; // Register for configuration change notifications connection.client.register( DidChangeConfigurationNotification.type, undefined ); // Handle configuration change connection.onDidChangeConfiguration((change) => { // Configuration changed }); // Request configuration from client const config = await connection.sendRequest( ConfigurationRequest.type, { items: [ { section: 'myLanguage' }, { section: 'myLanguage.diagnostics', scopeUri: documentUri } ] } as ConfigurationParams ); ``` ## Workspace Features ```typescript import { DidChangeWorkspaceFoldersNotification, WorkspaceFoldersRequest } from 'vscode-languageserver-protocol'; // Register for workspace folder changes connection.client.register( DidChangeWorkspaceFoldersNotification.type, undefined ); // Handle workspace folder changes connection.onDidChangeWorkspaceFolders((event) => { // Event contains added and removed folders }); // Request workspace folders const folders = await connection.sendRequest( WorkspaceFoldersRequest.type ); ``` ## File Events ```typescript import { DidChangeWatchedFilesNotification, FileChangeType } from 'vscode-languageserver-protocol'; // Handle watched file changes connection.onDidChangeWatchedFiles((change) => { for (const event of change.changes) { if (event.type === FileChangeType.Created) { // File created } else if (event.type === FileChangeType.Changed) { // File changed } else if (event.type === FileChangeType.Deleted) { // File deleted } } }); ``` ## Parameter Structures Control how parameters are sent in requests/notifications: ```typescript import { ParameterStructures } from 'vscode-languageserver-protocol'; // Auto-detect best structure (default) const request = new RequestType('my/request'); // Force by-position parameters const request = new RequestType( 'my/request', ParameterStructures.byPosition ); // Force by-name parameters const request = new RequestType( 'my/request', ParameterStructures.byName ); ``` ## Error Codes Standard LSP error codes: ```typescript import { ErrorCodes, LSPErrorCodes } from 'vscode-languageserver-protocol'; // JSON-RPC error codes ErrorCodes.ParseError = -32700; ErrorCodes.InvalidRequest = -32600; ErrorCodes.MethodNotFound = -32601; ErrorCodes.InvalidParams = -32602; ErrorCodes.InternalError = -32603; // LSP-specific error codes LSPErrorCodes.ServerNotInitialized = -32002; LSPErrorCodes.UnknownErrorCode = -32001; LSPErrorCodes.RequestCancelled = -32800; LSPErrorCodes.ContentModified = -32801; LSPErrorCodes.ServerCancelled = -32802; ``` ## Implementation Notes - This module is version-aligned with the LSP specification version number - It provides a tool-independent implementation suitable for various environments - Type-safe definitions prevent runtime errors - Can be used alongside other protocol implementations - Supports proposed protocol features for early adoption ## Usage Scenarios 1. **Building custom protocol implementations** - Use type definitions as reference 2. **Creating protocol adapters** - Transform messages between different protocols 3. **Protocol validation tools** - Validate messages against LSP definitions 4. **Cross-platform language servers** - Use in Node.js, Deno, or other runtimes 5. **Language protocol research** - Study the protocol specification through code ## Related Resources - [Language Server Protocol Specification](https://microsoft.github.io/language-server-protocol/) - [vscode-languageserver-types](../types.md) - Data types - [vscode-languageserver](../server.md) - Server implementation - [vscode-languageclient](../client.md) - Client implementation --- # vscode-languageserver Source: https://github.com/microsoft/vscode-languageserver-node/tree/main/server NPM Package: `vscode-languageserver` Npm module to implement a VSCode language server using Node.js as a runtime. ## Overview This module provides the server-side implementation of the Language Server Protocol for Node.js. It handles incoming LSP requests and notifications from a VSCode client extension. ## Installation ```bash npm install vscode-languageserver ``` ## Quick Start ### Basic Language Server ```typescript import { createConnection, TextDocuments, Diagnostic, DiagnosticSeverity, ProposedFeatures, TextDocumentSyncKind, InitializeParams, DidChangeConfigurationNotification, CompletionItem, CompletionItemKind, TextDocumentPositionParams, TextDocumentItem, Connection } from 'vscode-languageserver/node'; import { TextDocument } from 'vscode-languageserver-textdocument'; // Create a connection for the server. The connection uses Node's IPC as a transport. // Also include all preview / proposed LSP features. const connection = createConnection(ProposedFeatures.all); // Create a simple text document manager. const documents: TextDocuments = new TextDocuments(TextDocument); let hasConfigurationCapability: boolean = false; let hasWorkspaceFolderCapability: boolean = false; let hasDiagnosticRelatedInformationCapability: boolean = false; connection.onInitialize((params: InitializeParams) => { const capabilities = params.capabilities; // Does the client support the `workspace/configuration` request? hasConfigurationCapability = !!( capabilities.workspace && !!capabilities.workspace.configuration ); hasWorkspaceFolderCapability = !!( capabilities.workspace && !!capabilities.workspace.workspaceFolders ); hasDiagnosticRelatedInformationCapability = !!( capabilities.textDocument && capabilities.textDocument.publishDiagnostics && capabilities.textDocument.publishDiagnostics.relatedInformation ); const result: InitializeResult = { capabilities: { textDocumentSync: TextDocumentSyncKind.Incremental, completionProvider: { resolveProvider: true } } }; if (hasWorkspaceFolderCapability) { result.capabilities.workspace = { workspaceFolders: { supported: true, changeNotifications: true } }; } return result; }); connection.onInitialized(() => { if (hasConfigurationCapability) { // Register for all configuration changes. connection.client.register( DidChangeConfigurationNotification.type, undefined ); } if (hasWorkspaceFolderCapability) { connection.workspace.onDidChangeWorkspaceFolders(_event => { connection.console.log('Workspace folder change event received.'); }); } }); // The example settings interface ExampleSettings { maxNumberOfProblems: number; } // The global settings, used when the `workspace/configuration` request is not supported by the client. const defaultSettings: ExampleSettings = { maxNumberOfProblems: 1000 }; let globalSettings: ExampleSettings = defaultSettings; // Cache the settings of all open documents const documentSettings: Map> = new Map(); connection.onDidChangeConfiguration(change => { if (hasConfigurationCapability) { // Reset all cached document settings documentSettings.clear(); } else { globalSettings = ( (change.settings.languageServerExample || defaultSettings) ); } // Revalidate all open text documents documents.all().forEach(validateTextDocument); }); function getDocumentSettings(resource: string): Thenable { if (!hasConfigurationCapability) { return Promise.resolve(globalSettings); } let result = documentSettings.get(resource); if (!result) { result = connection.workspace.getConfiguration( { scopeUri: resource, section: 'languageServerExample' } ); documentSettings.set(resource, result); } return result; } // Only keep settings for open documents documents.onDidClose(e => { documentSettings.delete(e.document.uri); }); // The content of a text document has changed. This event is emitted // when the text document first opened or when its content has changed. documents.onDidChangeContent(change => { validateTextDocument(change.document); }); async function validateTextDocument(textDocument: TextDocument): Promise { // In this simple example we get the settings for every validate run. const settings = await getDocumentSettings(textDocument.uri); // The validator creates diagnostics for all uppercase words length 2 and more const text = textDocument.getText(); const pattern = /\b[A-Z]{2,}\b/g; let m: RegExpExecArray | null; let problems = 0; const diagnostics: Diagnostic[] = []; while ((m = pattern.exec(text)) && problems < settings.maxNumberOfProblems) { problems++; const diagnostic: Diagnostic = { severity: DiagnosticSeverity.Warning, range: { start: textDocument.positionAt(m.index), end: textDocument.positionAt(m.index + m[0].length) }, message: `${m[0]} is all uppercase.`, source: 'ex' }; if (hasDiagnosticRelatedInformationCapability) { diagnostic.relatedInformation = [ { location: { uri: textDocument.uri, range: Object.assign({}, diagnostic.range) }, message: 'Spelling matters' }, { location: { uri: textDocument.uri, range: Object.assign({}, diagnostic.range) }, message: 'Especially for function/class names' } ]; } diagnostics.push(diagnostic); } // Send the computed diagnostics to the client connection.sendDiagnostics({ uri: textDocument.uri, diagnostics }); } connection.onDidChangeWatchedFiles(_change => { // Monitored files have change in VSCode connection.console.log('We received a file change event'); }); // This handler provides the initial list of the completion items. connection.onCompletion( (_textDocumentPosition: TextDocumentPositionParams): CompletionItem[] => { // The pass parameter contains the position of the text document in // which code complete got requested. For the example we ignore this // information and always provide the same completion items. return [ { label: 'TypeScript', kind: CompletionItemKind.Text, data: 1 }, { label: 'JavaScript', kind: CompletionItemKind.Text, data: 2 } ]; } ); // This handler resolves additional information for the item selected in // the completion list. connection.onCompletionResolve( (item: CompletionItem): CompletionItem => { if (item.data === 1) { item.detail = 'TypeScript details'; item.documentation = 'TypeScript documentation'; } else if (item.data === 2) { item.detail = 'JavaScript details'; item.documentation = 'JavaScript documentation'; } return item; } ); // Make the text document manager listen on the connection // for open, change and close text document synchronization events documents.listen(connection); // Listen on the connection connection.listen(); ``` ## Server Creation ### Creating a Connection The `createConnection()` function establishes the server-client connection: ```typescript import { createConnection, ProposedFeatures } from 'vscode-languageserver/node'; // Create connection over stdin/stdout const connection = createConnection(); // Or with specific features const connection = createConnection(ProposedFeatures.all); ``` ### Transport Options ```typescript // Default: stdio transport const connection = createConnection(); // IPC transport const connection = createConnection( new IPCMessageReader(process.stdin), new IPCMessageWriter(process.stdout) ); // Socket transport const connection = createConnection( new SocketMessageReader(socket), new SocketMessageWriter(socket) ); ``` ## Document Management ### TextDocuments Manage open text documents and their changes: ```typescript import { TextDocuments } from 'vscode-languageserver/node'; import { TextDocument } from 'vscode-languageserver-textdocument'; // Create a documents manager const documents = new TextDocuments(TextDocument); // Listen to document events documents.onDidOpen(event => { console.log('Document opened:', event.document.uri); }); documents.onDidChange(event => { console.log('Document changed:', event.document.uri); }); documents.onDidClose(event => { console.log('Document closed:', event.document.uri); }); documents.onDidSave(event => { console.log('Document saved:', event.document.uri); }); // Get a document const document = documents.get(uri); // Access document content const text = document.getText(); const line = document.getText({ start: { line: 0, character: 0 }, end: { line: 1, character: 0 } }); // Calculate positions const offset = document.offsetAt({ line: 5, character: 10 }); const position = document.positionAt(100); // Make changes to documents documents.listen(connection); ``` ## Initialization ### onInitialize Handler ```typescript import { InitializeParams, InitializeResult } from 'vscode-languageserver/node'; connection.onInitialize((params: InitializeParams) => { // Get client capabilities const capabilities = params.capabilities; // Check for specific capabilities const hasHover = capabilities.textDocument?.hoverProvider; const hasCompletion = capabilities.textDocument?.completionProvider; // Return server capabilities const result: InitializeResult = { capabilities: { textDocumentSync: TextDocumentSyncKind.Incremental, hoverProvider: true, completionProvider: { resolveProvider: true, triggerCharacters: ['.', ':', '<'] }, definitionProvider: true, referencesProvider: true, documentSymbolProvider: true, workspaceSymbolProvider: true, codeActionProvider: true, codeLensProvider: { resolveProvider: true }, documentFormattingProvider: true, documentRangeFormattingProvider: true, renameProvider: true } }; return result; }); connection.onInitialized(() => { // Server is fully initialized // Can now send requests to client }); ``` ## Request Handlers ### Generic Request Handler ```typescript connection.onRequest(method, async (params) => { // Handle custom request return result; }); ``` ### Standard LSP Requests ```typescript // Hover connection.onHover((params: HoverParams) => { return { contents: 'Hover information' }; }); // Completion connection.onCompletion((params: CompletionParams) => { return [ { label: 'item1', kind: CompletionItemKind.Text }, { label: 'item2', kind: CompletionItemKind.Function } ]; }); connection.onCompletionResolve((item: CompletionItem) => { item.detail = 'Detailed information'; return item; }); // Go to Definition connection.onDefinition((params: DefinitionParams) => { return Location.create(uri, range); }); // Find References connection.onReferences((params: ReferencesParams) => { return [ Location.create(uri1, range1), Location.create(uri2, range2) ]; }); // Document Symbols connection.onDocumentSymbol((params: DocumentSymbolParams) => { return [ DocumentSymbol.create('symbolName', SymbolKind.Class, range) ]; }); // Workspace Symbols connection.onWorkspaceSymbol((params: WorkspaceSymbolParams) => { return [ SymbolInformation.create('symbolName', SymbolKind.Class, range, uri) ]; }); // Code Actions connection.onCodeAction((params: CodeActionParams) => { return [ CodeAction.create('Fix issue', { changes: { [uri]: [TextEdit.replace(range, 'replacement')] } }) ]; }); // Code Lens connection.onCodeLens((params: CodeLensParams) => { return [ CodeLens.create(range, { title: 'Lens', command: 'command' }) ]; }); connection.onCodeLensResolve((lens: CodeLens) => { return lens; }); // Document Formatting connection.onDocumentFormatting((params: DocumentFormattingParams) => { return [ TextEdit.replace(wholeDocRange, formattedText) ]; }); // Document Range Formatting connection.onDocumentRangeFormatting((params: DocumentRangeFormattingParams) => { return [ TextEdit.replace(params.range, formattedText) ]; }); // Format on Type connection.onDocumentOnTypeFormatting((params: DocumentOnTypeFormattingParams) => { return [ TextEdit.insert(params.position, ' ') ]; }); // Rename connection.onRename((params: RenameParams) => { return WorkspaceEdit.create({ changes: { [uri]: [TextEdit.replace(range1, newName), TextEdit.replace(range2, newName)] } }); }); // Signature Help connection.onSignatureHelp((params: SignatureHelpParams) => { return { signatures: [ SignatureInformation.create( 'function(arg1: string, arg2: number): void', 'Function documentation', [ ParameterInformation.create('arg1', 'Argument 1'), ParameterInformation.create('arg2', 'Argument 2') ] ) ], activeSignature: 0, activeParameter: 0 }; }); ``` ## Notification Handlers ### Generic Notification Handler ```typescript connection.onNotification(method, (params) => { // Handle custom notification }); ``` ### Standard LSP Notifications ```typescript // Text Document Changes documents.onDidChange(event => { // Document content changed }); // Text Document Opened documents.onDidOpen(event => { // Document opened }); // Text Document Closed documents.onDidClose(event => { // Document closed }); // Configuration Changed connection.onDidChangeConfiguration((change) => { // Configuration changed on client side }); // Watched Files Changed connection.onDidChangeWatchedFiles((change) => { for (const event of change.changes) { console.log(event.uri, event.type); } }); // Workspace Folders Changed connection.onDidChangeWorkspaceFolders((event) => { for (const folder of event.added) { console.log('Added:', folder.uri); } for (const folder of event.removed) { console.log('Removed:', folder.uri); } }); ``` ## Sending Messages to Client ### Sending Diagnostics ```typescript connection.sendDiagnostics({ uri: documentUri, diagnostics: [ { severity: DiagnosticSeverity.Error, range: range, message: 'Error message', code: 'ERROR001' } ] }); ``` ### Sending Notifications ```typescript // Show message to user connection.sendNotification('window/showMessage', { type: MessageType.Warning, message: 'Warning message' }); // Log to output connection.console.log('Log message'); connection.console.error('Error message'); ``` ### Sending Requests ```typescript // Request configuration from client const config = await connection.sendRequest('workspace/configuration', { items: [{ section: 'myLanguage' }] }); // Request workspace folders const folders = await connection.sendRequest('workspace/workspaceFolders', null); // Show input dialog const result = await connection.sendRequest('window/showInputBox', { prompt: 'Enter value:', value: 'default' }); ``` ## Configuration Management ### Getting Configuration ```typescript connection.onDidChangeConfiguration((change) => { if (hasConfigurationCapability) { // Reset cached configuration documentSettings.clear(); } else { // Use global settings globalSettings = change.settings.myLanguage || defaultSettings; } }); async function getSettings(resource: string) { if (!hasConfigurationCapability) { return globalSettings; } const settings = await connection.workspace.getConfiguration({ scopeUri: resource, section: 'myLanguage' }); return settings; } ``` ## Logging ### Console Logging ```typescript connection.console.log('Log message'); connection.console.warn('Warning message'); connection.console.error('Error message'); ``` ### Trace Logging ```typescript connection.tracer.log('Trace message'); ``` ## Error Handling ### Request Error Responses ```typescript connection.onRequest('myRequest', async (params) => { try { // Implementation return result; } catch (error) { // Return error response return new ResponseError( -32603, 'Internal error: ' + error.message ); } }); ``` ## Workspace Features ### Workspace Folders ```typescript connection.onInitialize((params) => { const workspaceFolders = params.workspaceFolders; for (const folder of workspaceFolders || []) { console.log('Workspace folder:', folder.uri); } return { capabilities: { workspace: { workspaceFolders: { supported: true, changeNotifications: true } } } }; }); connection.onDidChangeWorkspaceFolders((event) => { // Handle workspace folder changes }); // Get workspace folders at runtime const folders = await connection.workspace.getWorkspaceFolders(); ``` ## Advanced Features ### Dynamic Capability Registration ```typescript connection.onInitialize((params) => { // Return capabilities with version info for dynamic updates return { capabilities: { textDocumentSync: TextDocumentSyncKind.Incremental } }; }); connection.onInitialized(() => { // Dynamically register a capability connection.client.register( DidChangeConfigurationNotification.type, undefined ); }); ``` ## Lifecycle ```typescript // Startup connection.listen(); // While running // ... handle requests and notifications // Shutdown connection.onShutdown(() => { // Perform cleanup connection.dispose(); }); connection.onExit(() => { // Server is exiting process.exit(0); }); ``` ## Best Practices 1. **Always validate input** - Check parameters before processing 2. **Handle errors gracefully** - Use try-catch and return proper errors 3. **Cancel long operations** - Respect cancellation tokens 4. **Cache appropriately** - Cache settings and computed results 5. **Use typed parameters** - Use LSP type definitions from the protocol module 6. **Log adequately** - Use connection.console for debugging 7. **Performance matters** - Optimize for incremental updates and partial results ## Testing Test your language server with sample files and validate: 1. Proper initialization with capability reporting 2. Correct response to all implemented requests 3. Proper error handling for invalid input 4. Memory usage under load 5. CPU usage during large file processing ## Deployment Package your server as a Node.js module: ```bash npm install vscode-languageserver npm run build npm publish ``` The server can be deployed as: - Built into a VSCode extension - Shipped separately as an npm module - Deployed on a remote server