lsp-implementation
Use when implementing Language Server Protocol features - covers completions, hover, diagnostics, and navigation
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install mcclowes-lea-lsp-implementation
Repository
Skill path: .claude/skills/lsp-implementation
Use when implementing Language Server Protocol features - covers completions, hover, diagnostics, and navigation
Open repositoryBest for
Primary workflow: Ship Full Stack.
Technical facets: Full Stack, Backend.
Target audience: everyone.
License: Unknown.
Original source
Catalog source: SkillHub Club.
Repository owner: mcclowes.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install lsp-implementation into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/mcclowes/lea before adding lsp-implementation to shared team environments
- Use lsp-implementation for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: lsp-implementation
# prettier-ignore
description: Use when implementing Language Server Protocol features - covers completions, hover, diagnostics, and navigation
---
# Language Server Protocol Implementation
## Quick Start
```typescript
import {
createConnection,
TextDocuments,
ProposedFeatures,
} from "vscode-languageserver/node";
import { TextDocument } from "vscode-languageserver-textdocument";
const connection = createConnection(ProposedFeatures.all);
const documents = new TextDocuments(TextDocument);
connection.onInitialize(() => ({
capabilities: {
textDocumentSync: TextDocumentSyncKind.Incremental,
completionProvider: { triggerCharacters: [".", "/"] },
hoverProvider: true,
definitionProvider: true,
}
}));
documents.listen(connection);
connection.listen();
```
## Core Features
### Completions
```typescript
connection.onCompletion((params): CompletionItem[] => {
const document = documents.get(params.textDocument.uri);
const position = params.position;
// Get context at cursor
const line = document?.getText({
start: { line: position.line, character: 0 },
end: position
});
// Return builtins
return [
{ label: "map", kind: CompletionItemKind.Function, detail: "(fn) -> List" },
{ label: "filter", kind: CompletionItemKind.Function, detail: "(fn) -> List" },
{ label: "reduce", kind: CompletionItemKind.Function, detail: "(init, fn) -> Value" },
];
});
```
### Hover Information
```typescript
connection.onHover((params): Hover | null => {
const document = documents.get(params.textDocument.uri);
const word = getWordAtPosition(document, params.position);
const builtin = BUILTINS[word];
if (builtin) {
return {
contents: {
kind: "markdown",
value: `**${word}**\n\n${builtin.description}\n\n\`\`\`lea\n${builtin.signature}\n\`\`\``
}
};
}
return null;
});
```
### Diagnostics
```typescript
documents.onDidChangeContent((change) => {
const document = change.document;
const diagnostics: Diagnostic[] = [];
try {
parse(document.getText());
} catch (error) {
if (error instanceof ParseError) {
diagnostics.push({
severity: DiagnosticSeverity.Error,
range: error.range,
message: error.message,
source: "lea"
});
}
}
connection.sendDiagnostics({ uri: document.uri, diagnostics });
});
```
### Go to Definition
```typescript
connection.onDefinition((params): Definition | null => {
const document = documents.get(params.textDocument.uri);
const word = getWordAtPosition(document, params.position);
// Find definition in AST
const definition = findDefinition(document.getText(), word);
if (definition) {
return {
uri: params.textDocument.uri,
range: definition.range
};
}
return null;
});
```
## Protocol Messages
- `textDocument/completion` - Code completions
- `textDocument/hover` - Hover information
- `textDocument/definition` - Go to definition
- `textDocument/references` - Find all references
- `textDocument/documentSymbol` - Document outline
- `textDocument/formatting` - Code formatting
## Reference Files
- [references/capabilities.md](references/capabilities.md) - Server capabilities
- [references/diagnostics.md](references/diagnostics.md) - Error reporting
---
## Referenced Files
> The following files are referenced in this skill and included for context.
### references/capabilities.md
```markdown
# Server Capabilities
## Initialization Response
```typescript
connection.onInitialize((params: InitializeParams): InitializeResult => {
return {
capabilities: {
// Document sync
textDocumentSync: TextDocumentSyncKind.Incremental,
// Completions
completionProvider: {
triggerCharacters: [".", "/", "#"],
resolveProvider: true,
},
// Hover
hoverProvider: true,
// Signature help
signatureHelpProvider: {
triggerCharacters: ["(", ","],
},
// Navigation
definitionProvider: true,
referencesProvider: true,
documentHighlightProvider: true,
// Symbols
documentSymbolProvider: true,
workspaceSymbolProvider: true,
// Formatting
documentFormattingProvider: true,
documentRangeFormattingProvider: true,
documentOnTypeFormattingProvider: {
firstTriggerCharacter: "}",
moreTriggerCharacter: [";", "\n"],
},
// Code actions
codeActionProvider: {
codeActionKinds: [
CodeActionKind.QuickFix,
CodeActionKind.Refactor,
],
},
// Rename
renameProvider: {
prepareProvider: true,
},
// Folding
foldingRangeProvider: true,
// Semantic tokens
semanticTokensProvider: {
legend: {
tokenTypes: ["keyword", "variable", "function"],
tokenModifiers: ["declaration", "definition"],
},
full: true,
delta: true,
},
},
};
});
```
## Capability Details
### Text Document Sync
```typescript
// Full sync - send entire document on change
textDocumentSync: TextDocumentSyncKind.Full
// Incremental - send only changes
textDocumentSync: TextDocumentSyncKind.Incremental
// Detailed options
textDocumentSync: {
openClose: true,
change: TextDocumentSyncKind.Incremental,
save: { includeText: true },
}
```
### Completion Provider
```typescript
completionProvider: {
// Characters that trigger completion
triggerCharacters: [".", "#"],
// Support resolving additional info
resolveProvider: true,
// All commit characters
allCommitCharacters: ["\t", "\n"],
}
```
### Code Action Provider
```typescript
codeActionProvider: {
codeActionKinds: [
CodeActionKind.QuickFix, // Fix errors
CodeActionKind.Refactor, // Refactorings
CodeActionKind.RefactorExtract, // Extract method/variable
CodeActionKind.Source, // Source actions
CodeActionKind.SourceOrganizeImports,
],
resolveProvider: true,
}
```
## Dynamic Registration
```typescript
// Register capability dynamically
connection.client.register(CompletionRequest.type, {
documentSelector: [{ language: "lea" }],
triggerCharacters: ["."],
});
```
```
### references/diagnostics.md
```markdown
# Diagnostic Reporting
## Basic Diagnostics
```typescript
import {
Diagnostic,
DiagnosticSeverity,
Position,
Range,
} from "vscode-languageserver/node";
function createDiagnostic(
message: string,
line: number,
startChar: number,
endChar: number,
severity: DiagnosticSeverity = DiagnosticSeverity.Error
): Diagnostic {
return {
severity,
range: {
start: { line, character: startChar },
end: { line, character: endChar },
},
message,
source: "lea",
};
}
```
## Severity Levels
```typescript
DiagnosticSeverity.Error // 1 - Red squiggle
DiagnosticSeverity.Warning // 2 - Yellow squiggle
DiagnosticSeverity.Information // 3 - Blue squiggle
DiagnosticSeverity.Hint // 4 - Faded text (3 dots)
```
## Publishing Diagnostics
```typescript
documents.onDidChangeContent((change) => {
validateDocument(change.document);
});
async function validateDocument(document: TextDocument): Promise<void> {
const diagnostics: Diagnostic[] = [];
const text = document.getText();
try {
parse(text);
} catch (error) {
if (error instanceof ParseError) {
diagnostics.push({
severity: DiagnosticSeverity.Error,
range: {
start: { line: error.line - 1, character: error.column - 1 },
end: { line: error.line - 1, character: error.column + 10 },
},
message: error.message,
source: "lea",
code: error.code,
});
}
}
// Send diagnostics to client
connection.sendDiagnostics({
uri: document.uri,
diagnostics,
});
}
```
## Diagnostic Tags
```typescript
{
severity: DiagnosticSeverity.Hint,
range: unusedVarRange,
message: "Variable is declared but never used",
tags: [DiagnosticTag.Unnecessary], // Faded text
}
{
severity: DiagnosticSeverity.Warning,
range: deprecatedFnRange,
message: "Function is deprecated",
tags: [DiagnosticTag.Deprecated], // Strikethrough
}
```
## Related Information
```typescript
{
severity: DiagnosticSeverity.Error,
range: errorRange,
message: "Type mismatch",
relatedInformation: [
{
location: {
uri: document.uri,
range: declarationRange,
},
message: "Variable declared as Number here",
},
],
}
```
## Code Actions for Diagnostics
```typescript
connection.onCodeAction((params) => {
const diagnostics = params.context.diagnostics;
const actions: CodeAction[] = [];
for (const diagnostic of diagnostics) {
if (diagnostic.code === "undeclared-variable") {
actions.push({
title: `Declare variable '${diagnostic.data?.name}'`,
kind: CodeActionKind.QuickFix,
diagnostics: [diagnostic],
edit: {
changes: {
[params.textDocument.uri]: [
TextEdit.insert(
{ line: 0, character: 0 },
`let ${diagnostic.data?.name} = undefined\n`
),
],
},
},
});
}
}
return actions;
});
```
## Clearing Diagnostics
```typescript
// Clear all diagnostics for a document
connection.sendDiagnostics({
uri: document.uri,
diagnostics: [],
});
```
```