diff --git a/.claude/settings.json b/.claude/settings.json index 33f37b5..c78b41f 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -5,7 +5,12 @@ "Bash(bun run test)", "Bash(bun run lint)", "Bash(bun run lint:fix:*)", - "mcp__github__issue_write" + "mcp__github__issue_write", + "Bash(gh label list:*)", + "Bash(gh issue:*)", + "Bash(bun run tsc:*)", + "Bash(bun test:*)", + "Bash(bun install:*)" ], "deny": [], "ask": [] diff --git a/.gitignore b/.gitignore index 45ce1cc..7e998f2 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,4 @@ Thumbs.db # Coverage coverage/ +/.claude/settings.local.json diff --git a/packages/lsp/CLAUDE.md b/packages/lsp/CLAUDE.md index 5a46dd0..7526a49 100644 --- a/packages/lsp/CLAUDE.md +++ b/packages/lsp/CLAUDE.md @@ -4,7 +4,7 @@ LSP (Language Server Protocol) client implementation for AI coding tools. ## Overview -This package provides a unified interface for interacting with multiple language servers, enabling real-time diagnostics, hover information, and symbol navigation. +This package provides a unified interface for interacting with multiple language servers, enabling real-time diagnostics, code navigation, completions, and symbol navigation. ## Architecture @@ -22,11 +22,13 @@ src/ |--------|-----|------------|----------------| | TypeScript | `typescript` | .ts, .tsx, .js, .jsx, .mjs, .cjs, .mts, .cts | package-lock.json, bun.lockb, bun.lock, yarn.lock, pnpm-lock.yaml | | Deno | `deno` | .ts, .tsx, .js, .jsx, .mjs | deno.json, deno.jsonc | +| Vue | `vue` | .vue | package.json, package-lock.json, bun.lockb, bun.lock, pnpm-lock.yaml, yarn.lock | | Oxlint | `oxlint` | .ts, .tsx, .js, .jsx, .mjs, .cjs, .mts, .cts, .vue, .astro, .svelte | .oxlintrc.json, package-lock.json, bun.lockb, bun.lock, pnpm-lock.yaml, yarn.lock, package.json | | Pyright | `pyright` | .py, .pyi | pyproject.toml, setup.py, requirements.txt, pyrightconfig.json | | Gopls | `gopls` | .go | go.work, go.mod, go.sum | | Rust Analyzer | `rust-analyzer` | .rs | Cargo.toml, Cargo.lock | | Kotlin | `kotlin` | .kt, .kts | build.gradle.kts, build.gradle, settings.gradle.kts, settings.gradle, pom.xml | +| Dart | `dart` | .dart | pubspec.yaml, pubspec.lock | ## Adding a New Server @@ -89,13 +91,39 @@ const diags = await manager.diagnostics() // Get hover info const hover = await manager.hover({ file, line, character }) +// Go to definition +const defs = await manager.definition({ file, line, character }) + +// Find all references +const refs = await manager.references({ file, line, character, includeDeclaration: true }) + +// Get code completions +const completions = await manager.completion({ file, line, character }) + // Search symbols const symbols = await manager.workspaceSymbol('query') +// Get document symbols +const docSymbols = await manager.documentSymbol(uri) + // Cleanup await manager.shutdown() ``` +### LSPManager Methods + +| Method | LSP Request | Description | +|--------|-------------|-------------| +| `touchFile()` | `textDocument/didOpen` | Open file in LSP servers | +| `diagnostics()` | `textDocument/publishDiagnostics` | Get all diagnostics | +| `hover()` | `textDocument/hover` | Get hover information | +| `definition()` | `textDocument/definition` | Go to definition | +| `references()` | `textDocument/references` | Find all references | +| `completion()` | `textDocument/completion` | Get code completions | +| `workspaceSymbol()` | `workspace/symbol` | Search workspace symbols | +| `documentSymbol()` | `textDocument/documentSymbol` | Get document symbols | +| `shutdown()` | `shutdown` | Close all clients | + ### Server Utilities ```typescript diff --git a/packages/lsp/README.md b/packages/lsp/README.md new file mode 100644 index 0000000..e5d6150 --- /dev/null +++ b/packages/lsp/README.md @@ -0,0 +1,129 @@ +# @pleaseai/code-lsp + +LSP (Language Server Protocol) client implementation for AI coding tools. + +## Installation + +```bash +bun add @pleaseai/code-lsp +``` + +## Features + +- Multi-server support with automatic lifecycle management +- Code navigation (definition, references) +- Code completions +- Diagnostics and hover information +- Symbol search (workspace and document) +- Auto-download for Kotlin, Dart, and Vue language servers + +## Quick Start + +```typescript +import { LSPManager } from '@pleaseai/code-lsp' + +const manager = new LSPManager('/path/to/project') + +// Open a file to initialize LSP +await manager.touchFile('src/index.ts', true) + +// Get diagnostics +const diagnostics = await manager.diagnostics() + +// Go to definition +const definitions = await manager.definition({ + file: 'src/index.ts', + line: 10, + character: 5, +}) + +// Find all references +const references = await manager.references({ + file: 'src/index.ts', + line: 10, + character: 5, + includeDeclaration: true, +}) + +// Get completions +const completions = await manager.completion({ + file: 'src/index.ts', + line: 10, + character: 5, +}) + +// Cleanup +await manager.shutdown() +``` + +## Supported Language Servers + +| Language | Server | Auto-download | +|----------|--------|---------------| +| TypeScript/JavaScript | typescript-language-server | No | +| Deno | deno lsp | No | +| Vue | @vue/language-server | Yes | +| Python | pyright-langserver | No | +| Go | gopls | No | +| Rust | rust-analyzer | No | +| Kotlin | JetBrains Kotlin LSP | Yes | +| Dart | dart language-server | Yes | +| Linting | oxlint | No | + +## API Reference + +### LSPManager + +| Method | Description | +|--------|-------------| +| `touchFile(file, waitForDiagnostics?)` | Open file in LSP servers | +| `diagnostics()` | Get all diagnostics | +| `hover({ file, line, character })` | Get hover information | +| `definition({ file, line, character })` | Go to definition | +| `references({ file, line, character, includeDeclaration? })` | Find all references | +| `completion({ file, line, character })` | Get code completions | +| `workspaceSymbol(query)` | Search workspace symbols | +| `documentSymbol(uri)` | Get document symbols | +| `status()` | Get connected server status | +| `shutdown()` | Close all clients | + +### Types + +```typescript +import { + // Completions + CompletionItem, + CompletionItemKind, + CompletionList, + // Diagnostics + Diagnostic, + + DocumentSymbol, + Location, + LocationLink, + + // Position and Range + Position, + Range, + // Symbols + Symbol, + + SymbolKind, +} from '@pleaseai/code-lsp' +``` + +## Server Utilities + +```typescript +import { getServerById, getServersForExtension } from '@pleaseai/code-lsp' + +// Get server by ID +const tsServer = getServerById('typescript') + +// Get servers for file extension +const servers = getServersForExtension('.ts') +``` + +## License + +MIT diff --git a/packages/lsp/src/index.ts b/packages/lsp/src/index.ts index 0942571..effdf1a 100644 --- a/packages/lsp/src/index.ts +++ b/packages/lsp/src/index.ts @@ -59,6 +59,64 @@ export const LocationLinkSchema = z.object({ }) export type LocationLink = z.infer +/** + * Completion item kind mapping + * @see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#completionItemKind + */ +export enum CompletionItemKind { + Text = 1, + Method = 2, + Function = 3, + Constructor = 4, + Field = 5, + Variable = 6, + Class = 7, + Interface = 8, + Module = 9, + Property = 10, + Unit = 11, + Value = 12, + Enum = 13, + Keyword = 14, + Snippet = 15, + Color = 16, + File = 17, + Reference = 18, + Folder = 19, + EnumMember = 20, + Constant = 21, + Struct = 22, + Event = 23, + Operator = 24, + TypeParameter = 25, +} + +/** + * LSP CompletionItem schema + * A completion item represents a text snippet that is proposed to complete text being typed. + */ +export const CompletionItemSchema = z.object({ + label: z.string(), + kind: z.number().optional(), + detail: z.string().optional(), + documentation: z.union([z.string(), z.object({ kind: z.string(), value: z.string() })]).optional(), + sortText: z.string().optional(), + filterText: z.string().optional(), + insertText: z.string().optional(), + insertTextFormat: z.number().optional(), +}) +export type CompletionItem = z.infer + +/** + * LSP CompletionList schema + * Represents a collection of completion items to be presented in the editor. + */ +export const CompletionListSchema = z.object({ + isIncomplete: z.boolean(), + items: z.array(CompletionItemSchema), +}) +export type CompletionList = z.infer + /** * LSP Symbol schema */ @@ -459,6 +517,73 @@ export class LSPManager { return results.flat() } + /** + * Get code completion suggestions at the given position + * + * @see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_completion + */ + async completion(input: { + file: string + line: number + character: number + }): Promise { + const clients = await this.getClients(input.file) + + const results = await Promise.all( + clients.map(client => + client.connection + .sendRequest('textDocument/completion', { + textDocument: { + uri: pathToFileURL(input.file).href, + }, + position: { + line: input.line, + character: input.character, + }, + }) + .then((result: unknown) => this.normalizeCompletions(result)) + .catch(() => []), + ), + ) + + return results.flat() + } + + /** + * Normalize completion response to CompletionItem[] + * Handles CompletionList and CompletionItem[] responses + */ + private normalizeCompletions(result: unknown): CompletionItem[] { + if (!result) + return [] + + // CompletionList format + if (this.isCompletionList(result)) { + return result.items + } + + // Direct CompletionItem[] format + if (Array.isArray(result)) { + return result.filter((item): item is CompletionItem => + typeof item === 'object' && item !== null && 'label' in item, + ) + } + + return [] + } + + /** + * Type guard for CompletionList + */ + private isCompletionList(obj: unknown): obj is CompletionList { + return ( + typeof obj === 'object' + && obj !== null + && 'items' in obj + && Array.isArray((obj as CompletionList).items) + ) + } + /** * Normalize definition response to Location[] * Handles Location, Location[], and LocationLink[] responses