diff --git a/examples/lsp-typescript/README.md b/examples/lsp-typescript/README.md new file mode 100644 index 0000000..9be9bf9 --- /dev/null +++ b/examples/lsp-typescript/README.md @@ -0,0 +1,148 @@ +# LSP TypeScript Example + +This example demonstrates how to use `@pleaseai/code-lsp` to interact with TypeScript language servers. + +## Features Demonstrated + +- **LSPManager initialization** - Set up the LSP client for a project +- **File opening & diagnostics** - Get type errors and warnings +- **Hover information** - Get type info and documentation at cursor position +- **Go to definition** - Navigate to where symbols are defined +- **Find references** - Find all usages of a symbol +- **Code completion** - Get intelligent code suggestions +- **Document symbols** - List all symbols in a file +- **Workspace symbol search** - Search for symbols across the project + +## Project Structure + +``` +examples/lsp-typescript/ +├── README.md # This file +├── package.json # Project dependencies +├── tsconfig.json # TypeScript configuration +├── demo.ts # LSP demo script +└── src/ + ├── index.ts # Entry point with classes and interfaces + └── utils/ + └── math.ts # Utility functions with JSDoc +``` + +## Quick Start + +### 1. Install Dependencies + +From the project root: + +```bash +cd examples/lsp-typescript +bun install +``` + +### 2. Run the Demo + +```bash +bun run demo +# or directly: +bun run demo.ts +``` + +### 3. Expected Output + +The demo will show: +- Connected LSP servers +- Diagnostics (type errors/warnings) +- Hover information for imports +- Definition locations +- Reference locations +- Code completions +- Document symbols +- Workspace symbols + +## Using LSPManager in Your Code + +```typescript +import { LSPManager, formatDiagnostic } from '@pleaseai/code-lsp' + +// Initialize manager with project path +const manager = new LSPManager('/path/to/project') + +// Open a file and wait for diagnostics +await manager.touchFile('src/index.ts', true) + +// Get diagnostics +const diagnostics = await manager.diagnostics() +for (const [file, diags] of Object.entries(diagnostics)) { + for (const diag of diags) { + console.log(formatDiagnostic(diag)) + } +} + +// Get hover info +const hovers = await manager.hover({ + file: 'src/index.ts', + line: 10, // 0-indexed + character: 5, // 0-indexed +}) + +// Go to definition +const definitions = await manager.definition({ + file: 'src/index.ts', + line: 10, + character: 5, +}) + +// Find references +const references = await manager.references({ + file: 'src/index.ts', + line: 10, + character: 5, + includeDeclaration: true, +}) + +// Code completion +const completions = await manager.completion({ + file: 'src/index.ts', + line: 10, + character: 5, +}) + +// Document symbols +const symbols = await manager.documentSymbol('file:///path/to/src/index.ts') + +// Workspace symbol search +const wsSymbols = await manager.workspaceSymbol('User') + +// Always cleanup +await manager.shutdown() +``` + +## Testing with Intentional Errors + +To test diagnostic detection, uncomment the error example in `src/utils/math.ts`: + +```typescript +// Uncomment this line to test LSP diagnostics: +export const errorExample: string = 42 +``` + +Then run the demo again to see the type error reported. + +## API Reference + +| Method | Description | +|--------|-------------| +| `touchFile(file, waitForDiagnostics?)` | Open file in LSP server | +| `diagnostics()` | Get all diagnostics | +| `hover({ file, line, character })` | Get hover info | +| `definition({ file, line, character })` | Go to definition | +| `references({ file, line, character, includeDeclaration? })` | Find references | +| `completion({ file, line, character })` | Get completions | +| `documentSymbol(uri)` | Get document symbols | +| `workspaceSymbol(query)` | Search workspace symbols | +| `status()` | Get connected server status | +| `shutdown()` | Close all LSP connections | + +## Related + +- [packages/lsp/README.md](../../packages/lsp/README.md) - Full LSP package documentation +- [packages/lsp/CLAUDE.md](../../packages/lsp/CLAUDE.md) - Detailed API reference diff --git a/examples/lsp-typescript/demo.ts b/examples/lsp-typescript/demo.ts new file mode 100644 index 0000000..c041d5a --- /dev/null +++ b/examples/lsp-typescript/demo.ts @@ -0,0 +1,216 @@ +#!/usr/bin/env bun +/** + * LSP TypeScript Demo + * + * This script demonstrates how to use @pleaseai/code-lsp to interact with + * TypeScript language servers. Run it with: bun run demo.ts + * + * Features demonstrated: + * - LSPManager initialization + * - File opening and diagnostics + * - Hover information + * - Go to definition + * - Find references + * - Code completion + * - Document symbols + * - Workspace symbol search + */ + +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { CompletionItemKind, formatDiagnostic, LSPManager, SymbolKind } from '@pleaseai/code-lsp' + +// Get project path +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const PROJECT_PATH = __dirname + +// File paths +const INDEX_FILE = path.join(PROJECT_PATH, 'src/index.ts') +const MATH_FILE = path.join(PROJECT_PATH, 'src/utils/math.ts') + +// Helper to print section headers +function printSection(title: string): void { + console.log(`\n${'='.repeat(60)}`) + console.log(` ${title}`) + console.log('='.repeat(60)) +} + +// Helper to print JSON with truncation +function printJson(obj: unknown, maxLength = 500): void { + const json = JSON.stringify(obj, null, 2) + if (json.length > maxLength) { + console.log(`${json.substring(0, maxLength)}\n... (truncated)`) + } + else { + console.log(json) + } +} + +// Symbol kind name mapping +const SYMBOL_KIND_NAMES: Record = { + [SymbolKind.File]: 'File', + [SymbolKind.Module]: 'Module', + [SymbolKind.Class]: 'Class', + [SymbolKind.Method]: 'Method', + [SymbolKind.Property]: 'Property', + [SymbolKind.Function]: 'Function', + [SymbolKind.Variable]: 'Variable', + [SymbolKind.Interface]: 'Interface', + [SymbolKind.Enum]: 'Enum', +} + +// Completion item kind name mapping +const COMPLETION_KIND_NAMES: Record = { + [CompletionItemKind.Function]: 'Function', + [CompletionItemKind.Variable]: 'Variable', + [CompletionItemKind.Class]: 'Class', + [CompletionItemKind.Interface]: 'Interface', + [CompletionItemKind.Method]: 'Method', + [CompletionItemKind.Property]: 'Property', + [CompletionItemKind.Keyword]: 'Keyword', +} + +async function main(): Promise { + console.log('🚀 LSP TypeScript Demo') + console.log(`Project: ${PROJECT_PATH}`) + + // Initialize LSP Manager + printSection('1. Initializing LSP Manager') + const manager = new LSPManager(PROJECT_PATH) + console.log('✅ LSPManager created') + + try { + // Touch file to initialize LSP server + printSection('2. Opening Files & Getting Diagnostics') + console.log(`Opening: ${path.relative(PROJECT_PATH, INDEX_FILE)}`) + await manager.touchFile(INDEX_FILE, true) + console.log('✅ File opened, waiting for diagnostics...') + + // Also touch the math file + console.log(`Opening: ${path.relative(PROJECT_PATH, MATH_FILE)}`) + await manager.touchFile(MATH_FILE, true) + + // Check server status + const status = await manager.status() + console.log('\n📊 Connected LSP Servers:') + for (const s of status) { + console.log(` - ${s.id}: ${s.status} (root: ${s.root})`) + } + + // Get diagnostics + const diagnostics = await manager.diagnostics() + const indexDiags = diagnostics[INDEX_FILE] || [] + const mathDiags = diagnostics[MATH_FILE] || [] + + console.log(`\n📋 Diagnostics for index.ts: ${indexDiags.length} issues`) + for (const diag of indexDiags.slice(0, 5)) { + console.log(` ${formatDiagnostic(diag)}`) + } + + console.log(`\n📋 Diagnostics for math.ts: ${mathDiags.length} issues`) + for (const diag of mathDiags.slice(0, 5)) { + console.log(` ${formatDiagnostic(diag)}`) + } + + // Hover information + printSection('3. Hover Information') + // Hover over 'add' function in index.ts (line 9, position of 'add') + const hovers = await manager.hover({ + file: INDEX_FILE, + line: 9, // import { add, ... } + character: 9, // position of 'add' + }) + console.log('Hover over "add" import:') + if (hovers.length > 0 && hovers[0]) { + printJson(hovers[0]) + } + else { + console.log(' (No hover info available)') + } + + // Go to definition + printSection('4. Go to Definition') + // Go to definition of 'add' from index.ts + const definitions = await manager.definition({ + file: INDEX_FILE, + line: 9, + character: 9, + }) + console.log('Definition of "add":') + for (const def of definitions) { + const relativePath = def.uri.replace('file://', '').replace(PROJECT_PATH, '.') + console.log(` 📍 ${relativePath}:${def.range.start.line + 1}:${def.range.start.character + 1}`) + } + + // Find references + printSection('5. Find References') + // Find all references to 'add' function + const refs = await manager.references({ + file: MATH_FILE, + line: 24, // export function add(...) + character: 16, // 'add' function name + includeDeclaration: true, + }) + console.log(`References to "add" function: ${refs.length} locations`) + for (const ref of refs.slice(0, 5)) { + const relativePath = ref.uri.replace('file://', '').replace(PROJECT_PATH, '.') + console.log(` 📍 ${relativePath}:${ref.range.start.line + 1}:${ref.range.start.character + 1}`) + } + + // Code completion + printSection('6. Code Completion') + // Get completions after 'Math.' in math.ts (line 80 in 1-based = line 79 in 0-based) + const completions = await manager.completion({ + file: MATH_FILE, + line: 79, // return Math.abs(value) line (0-based) + character: 14, // after 'Math.' (2 indent + 7 'return ' + 5 'Math.') + }) + console.log(`Completions at Math.: ${completions.length} items`) + console.log('First 10 completions:') + for (const item of completions.slice(0, 10)) { + const kindName = item.kind ? COMPLETION_KIND_NAMES[item.kind] || `Kind(${item.kind})` : 'Unknown' + console.log(` ${kindName.padEnd(12)} ${item.label}`) + } + + // Document symbols + printSection('7. Document Symbols') + const docSymbols = await manager.documentSymbol(`file://${MATH_FILE}`) + console.log(`Symbols in math.ts: ${docSymbols.length}`) + for (const sym of docSymbols) { + const kindName = SYMBOL_KIND_NAMES[sym.kind] || `Kind(${sym.kind})` + console.log(` ${kindName.padEnd(12)} ${sym.name}`) + } + + // Workspace symbol search + printSection('8. Workspace Symbol Search') + const wsSymbols = await manager.workspaceSymbol('User') + console.log(`Workspace symbols matching "User": ${wsSymbols.length}`) + for (const sym of wsSymbols) { + const kindName = SYMBOL_KIND_NAMES[sym.kind] || `Kind(${sym.kind})` + const relativePath = sym.location.uri.replace('file://', '').replace(PROJECT_PATH, '.') + console.log(` ${kindName.padEnd(12)} ${sym.name.padEnd(20)} ${relativePath}`) + } + + // Summary + printSection('✅ Demo Complete!') + console.log('\nThis demo showed:') + console.log(' 1. LSPManager initialization') + console.log(' 2. File opening and diagnostics') + console.log(' 3. Hover information') + console.log(' 4. Go to definition') + console.log(' 5. Find references') + console.log(' 6. Code completion') + console.log(' 7. Document symbols') + console.log(' 8. Workspace symbol search') + console.log('\nFor more advanced usage, see the README.md') + } + finally { + // Cleanup + printSection('Cleanup') + await manager.shutdown() + console.log('✅ LSP servers shut down') + } +} + +// Run the demo +main().catch(console.error) diff --git a/examples/lsp-typescript/package.json b/examples/lsp-typescript/package.json new file mode 100644 index 0000000..df8803b --- /dev/null +++ b/examples/lsp-typescript/package.json @@ -0,0 +1,15 @@ +{ + "name": "lsp-typescript-example", + "type": "module", + "version": "1.0.0", + "private": true, + "description": "Example TypeScript project for testing @pleaseai/code-lsp functionality", + "scripts": { + "demo": "bun run demo.ts", + "typecheck": "tsc --noEmit" + }, + "devDependencies": { + "eslint": "^9.18.0", + "typescript": "^5.7.0" + } +} diff --git a/examples/lsp-typescript/src/index.ts b/examples/lsp-typescript/src/index.ts new file mode 100644 index 0000000..39bf7ca --- /dev/null +++ b/examples/lsp-typescript/src/index.ts @@ -0,0 +1,53 @@ +/** + * Example TypeScript Entry Point + * + * This file demonstrates various TypeScript patterns that the LSP server can analyze: + * - Type definitions and interfaces + * - Function signatures + * - Module imports + */ + +import { add, divide, multiply, subtract } from './utils/math' + +// Example interface +interface User { + id: number + name: string + email: string +} + +// Example class +class UserService { + private users: User[] = [] + + addUser(user: User): void { + this.users.push(user) + } + + findUser(id: number): User | undefined { + return this.users.find(u => u.id === id) + } + + getAllUsers(): User[] { + return [...this.users] + } +} + +// Example function using math utilities +function calculate(a: number, b: number): void { + console.log('Addition:', add(a, b)) + console.log('Subtraction:', subtract(a, b)) + console.log('Multiplication:', multiply(a, b)) + console.log('Division:', divide(a, b)) +} + +// Example with intentional type usage for LSP testing +const service = new UserService() +service.addUser({ id: 1, name: 'Alice', email: 'alice@example.com' }) +service.addUser({ id: 2, name: 'Bob', email: 'bob@example.com' }) + +// Test calculations +calculate(10, 5) + +// Export for testing +export { calculate, User, UserService } diff --git a/examples/lsp-typescript/src/utils/math.ts b/examples/lsp-typescript/src/utils/math.ts new file mode 100644 index 0000000..e262f78 --- /dev/null +++ b/examples/lsp-typescript/src/utils/math.ts @@ -0,0 +1,90 @@ +/** + * Math Utilities + * + * This module provides basic math operations and demonstrates: + * - Type exports + * - Function signatures with JSDoc + * - Error handling patterns + */ + +/** + * Calculator interface for complex operations + */ +export interface Calculator { + add: (a: number, b: number) => number + subtract: (a: number, b: number) => number + multiply: (a: number, b: number) => number + divide: (a: number, b: number) => number +} + +/** + * Adds two numbers + * @param a - First number + * @param b - Second number + * @returns Sum of a and b + */ +export function add(a: number, b: number): number { + return a + b +} + +/** + * Subtracts second number from first + * @param a - First number + * @param b - Second number + * @returns Difference of a and b + */ +export function subtract(a: number, b: number): number { + return a - b +} + +/** + * Multiplies two numbers + * @param a - First number + * @param b - Second number + * @returns Product of a and b + */ +export function multiply(a: number, b: number): number { + return a * b +} + +/** + * Divides first number by second + * @param a - Dividend + * @param b - Divisor + * @returns Quotient of a divided by b + * @throws Error if divisor is zero + */ +export function divide(a: number, b: number): number { + if (b === 0) { + throw new Error('Cannot divide by zero') + } + return a / b +} + +/** + * Calculates the power of a number + * @param base - Base number + * @param exponent - Exponent + * @returns base raised to the power of exponent + */ +export function power(base: number, exponent: number): number { + return base ** exponent +} + +/** + * Returns the absolute value of a number + * @param value - Input number + * @returns Absolute value + */ +export function abs(value: number): number { + return Math.abs(value) +} + +// ======================================== +// INTENTIONAL TYPE ERROR FOR LSP TESTING +// ======================================== +// Uncomment the following line to test LSP diagnostics: +// export const errorExample: string = 42 + +// Example of unused variable (LSP should report this) +// const unusedVariable = 'this is unused' diff --git a/examples/lsp-typescript/tsconfig.json b/examples/lsp-typescript/tsconfig.json new file mode 100644 index 0000000..4e9c7d3 --- /dev/null +++ b/examples/lsp-typescript/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022"], + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "noEmit": true, + "skipLibCheck": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*.ts", "demo.ts"] +} diff --git a/packages/code/src/cli.ts b/packages/code/src/cli.ts index 8c2aa96..057b7c6 100755 --- a/packages/code/src/cli.ts +++ b/packages/code/src/cli.ts @@ -113,7 +113,9 @@ async function lspServerCommand(serverId: string, projectDir: string): Promise