Skip to content

Commit 9df0bd4

Browse files
feat(examples): Add LSP TypeScript example project (#74)
* feat(examples): add LSP TypeScript example project Add a complete example project demonstrating @pleaseai/code-lsp functionality with TypeScript language server. Features demonstrated: - LSPManager initialization and lifecycle - File opening and diagnostics retrieval - Hover information - Go to definition - Find references - Code completion - Document and workspace symbols Closes #73 * chore(examples): add eslint to lsp-typescript example Add eslint as a devDependency for testing LSP diagnostics. * fix(code): fix root detection for LSP server command Fix bug where LSP servers would incorrectly attempt to start on projects without the appropriate config files. The issue was that `server.root(projectDir, projectDir)` passed the directory path as the first argument, but the function expects a file path. When `path.dirname(directory)` was called, it would return the parent directory, breaking the search boundary. Solution: Use a dummy file path within projectDir so that `path.dirname()` returns projectDir itself, keeping the search within the correct bounds. This ensures LSP servers like Deno only start when their config files (e.g., deno.json) actually exist in the project. * fix(examples): correct line number for completion demo Fix the LSP completion demo line number from 76 to 79 (0-based). The actual `return Math.abs(value)` line is at line 80 (1-based), which is line 79 in 0-based indexing used by LSP. The character position (14) was already correct: 2 (indent) + 7 ('return ') + 5 ('Math.') = 14 Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent 7bce41f commit 9df0bd4

7 files changed

Lines changed: 540 additions & 1 deletion

File tree

examples/lsp-typescript/README.md

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# LSP TypeScript Example
2+
3+
This example demonstrates how to use `@pleaseai/code-lsp` to interact with TypeScript language servers.
4+
5+
## Features Demonstrated
6+
7+
- **LSPManager initialization** - Set up the LSP client for a project
8+
- **File opening & diagnostics** - Get type errors and warnings
9+
- **Hover information** - Get type info and documentation at cursor position
10+
- **Go to definition** - Navigate to where symbols are defined
11+
- **Find references** - Find all usages of a symbol
12+
- **Code completion** - Get intelligent code suggestions
13+
- **Document symbols** - List all symbols in a file
14+
- **Workspace symbol search** - Search for symbols across the project
15+
16+
## Project Structure
17+
18+
```
19+
examples/lsp-typescript/
20+
├── README.md # This file
21+
├── package.json # Project dependencies
22+
├── tsconfig.json # TypeScript configuration
23+
├── demo.ts # LSP demo script
24+
└── src/
25+
├── index.ts # Entry point with classes and interfaces
26+
└── utils/
27+
└── math.ts # Utility functions with JSDoc
28+
```
29+
30+
## Quick Start
31+
32+
### 1. Install Dependencies
33+
34+
From the project root:
35+
36+
```bash
37+
cd examples/lsp-typescript
38+
bun install
39+
```
40+
41+
### 2. Run the Demo
42+
43+
```bash
44+
bun run demo
45+
# or directly:
46+
bun run demo.ts
47+
```
48+
49+
### 3. Expected Output
50+
51+
The demo will show:
52+
- Connected LSP servers
53+
- Diagnostics (type errors/warnings)
54+
- Hover information for imports
55+
- Definition locations
56+
- Reference locations
57+
- Code completions
58+
- Document symbols
59+
- Workspace symbols
60+
61+
## Using LSPManager in Your Code
62+
63+
```typescript
64+
import { LSPManager, formatDiagnostic } from '@pleaseai/code-lsp'
65+
66+
// Initialize manager with project path
67+
const manager = new LSPManager('/path/to/project')
68+
69+
// Open a file and wait for diagnostics
70+
await manager.touchFile('src/index.ts', true)
71+
72+
// Get diagnostics
73+
const diagnostics = await manager.diagnostics()
74+
for (const [file, diags] of Object.entries(diagnostics)) {
75+
for (const diag of diags) {
76+
console.log(formatDiagnostic(diag))
77+
}
78+
}
79+
80+
// Get hover info
81+
const hovers = await manager.hover({
82+
file: 'src/index.ts',
83+
line: 10, // 0-indexed
84+
character: 5, // 0-indexed
85+
})
86+
87+
// Go to definition
88+
const definitions = await manager.definition({
89+
file: 'src/index.ts',
90+
line: 10,
91+
character: 5,
92+
})
93+
94+
// Find references
95+
const references = await manager.references({
96+
file: 'src/index.ts',
97+
line: 10,
98+
character: 5,
99+
includeDeclaration: true,
100+
})
101+
102+
// Code completion
103+
const completions = await manager.completion({
104+
file: 'src/index.ts',
105+
line: 10,
106+
character: 5,
107+
})
108+
109+
// Document symbols
110+
const symbols = await manager.documentSymbol('file:///path/to/src/index.ts')
111+
112+
// Workspace symbol search
113+
const wsSymbols = await manager.workspaceSymbol('User')
114+
115+
// Always cleanup
116+
await manager.shutdown()
117+
```
118+
119+
## Testing with Intentional Errors
120+
121+
To test diagnostic detection, uncomment the error example in `src/utils/math.ts`:
122+
123+
```typescript
124+
// Uncomment this line to test LSP diagnostics:
125+
export const errorExample: string = 42
126+
```
127+
128+
Then run the demo again to see the type error reported.
129+
130+
## API Reference
131+
132+
| Method | Description |
133+
|--------|-------------|
134+
| `touchFile(file, waitForDiagnostics?)` | Open file in LSP server |
135+
| `diagnostics()` | Get all diagnostics |
136+
| `hover({ file, line, character })` | Get hover info |
137+
| `definition({ file, line, character })` | Go to definition |
138+
| `references({ file, line, character, includeDeclaration? })` | Find references |
139+
| `completion({ file, line, character })` | Get completions |
140+
| `documentSymbol(uri)` | Get document symbols |
141+
| `workspaceSymbol(query)` | Search workspace symbols |
142+
| `status()` | Get connected server status |
143+
| `shutdown()` | Close all LSP connections |
144+
145+
## Related
146+
147+
- [packages/lsp/README.md](../../packages/lsp/README.md) - Full LSP package documentation
148+
- [packages/lsp/CLAUDE.md](../../packages/lsp/CLAUDE.md) - Detailed API reference

examples/lsp-typescript/demo.ts

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
#!/usr/bin/env bun
2+
/**
3+
* LSP TypeScript Demo
4+
*
5+
* This script demonstrates how to use @pleaseai/code-lsp to interact with
6+
* TypeScript language servers. Run it with: bun run demo.ts
7+
*
8+
* Features demonstrated:
9+
* - LSPManager initialization
10+
* - File opening and diagnostics
11+
* - Hover information
12+
* - Go to definition
13+
* - Find references
14+
* - Code completion
15+
* - Document symbols
16+
* - Workspace symbol search
17+
*/
18+
19+
import path from 'node:path'
20+
import { fileURLToPath } from 'node:url'
21+
import { CompletionItemKind, formatDiagnostic, LSPManager, SymbolKind } from '@pleaseai/code-lsp'
22+
23+
// Get project path
24+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
25+
const PROJECT_PATH = __dirname
26+
27+
// File paths
28+
const INDEX_FILE = path.join(PROJECT_PATH, 'src/index.ts')
29+
const MATH_FILE = path.join(PROJECT_PATH, 'src/utils/math.ts')
30+
31+
// Helper to print section headers
32+
function printSection(title: string): void {
33+
console.log(`\n${'='.repeat(60)}`)
34+
console.log(` ${title}`)
35+
console.log('='.repeat(60))
36+
}
37+
38+
// Helper to print JSON with truncation
39+
function printJson(obj: unknown, maxLength = 500): void {
40+
const json = JSON.stringify(obj, null, 2)
41+
if (json.length > maxLength) {
42+
console.log(`${json.substring(0, maxLength)}\n... (truncated)`)
43+
}
44+
else {
45+
console.log(json)
46+
}
47+
}
48+
49+
// Symbol kind name mapping
50+
const SYMBOL_KIND_NAMES: Record<number, string> = {
51+
[SymbolKind.File]: 'File',
52+
[SymbolKind.Module]: 'Module',
53+
[SymbolKind.Class]: 'Class',
54+
[SymbolKind.Method]: 'Method',
55+
[SymbolKind.Property]: 'Property',
56+
[SymbolKind.Function]: 'Function',
57+
[SymbolKind.Variable]: 'Variable',
58+
[SymbolKind.Interface]: 'Interface',
59+
[SymbolKind.Enum]: 'Enum',
60+
}
61+
62+
// Completion item kind name mapping
63+
const COMPLETION_KIND_NAMES: Record<number, string> = {
64+
[CompletionItemKind.Function]: 'Function',
65+
[CompletionItemKind.Variable]: 'Variable',
66+
[CompletionItemKind.Class]: 'Class',
67+
[CompletionItemKind.Interface]: 'Interface',
68+
[CompletionItemKind.Method]: 'Method',
69+
[CompletionItemKind.Property]: 'Property',
70+
[CompletionItemKind.Keyword]: 'Keyword',
71+
}
72+
73+
async function main(): Promise<void> {
74+
console.log('🚀 LSP TypeScript Demo')
75+
console.log(`Project: ${PROJECT_PATH}`)
76+
77+
// Initialize LSP Manager
78+
printSection('1. Initializing LSP Manager')
79+
const manager = new LSPManager(PROJECT_PATH)
80+
console.log('✅ LSPManager created')
81+
82+
try {
83+
// Touch file to initialize LSP server
84+
printSection('2. Opening Files & Getting Diagnostics')
85+
console.log(`Opening: ${path.relative(PROJECT_PATH, INDEX_FILE)}`)
86+
await manager.touchFile(INDEX_FILE, true)
87+
console.log('✅ File opened, waiting for diagnostics...')
88+
89+
// Also touch the math file
90+
console.log(`Opening: ${path.relative(PROJECT_PATH, MATH_FILE)}`)
91+
await manager.touchFile(MATH_FILE, true)
92+
93+
// Check server status
94+
const status = await manager.status()
95+
console.log('\n📊 Connected LSP Servers:')
96+
for (const s of status) {
97+
console.log(` - ${s.id}: ${s.status} (root: ${s.root})`)
98+
}
99+
100+
// Get diagnostics
101+
const diagnostics = await manager.diagnostics()
102+
const indexDiags = diagnostics[INDEX_FILE] || []
103+
const mathDiags = diagnostics[MATH_FILE] || []
104+
105+
console.log(`\n📋 Diagnostics for index.ts: ${indexDiags.length} issues`)
106+
for (const diag of indexDiags.slice(0, 5)) {
107+
console.log(` ${formatDiagnostic(diag)}`)
108+
}
109+
110+
console.log(`\n📋 Diagnostics for math.ts: ${mathDiags.length} issues`)
111+
for (const diag of mathDiags.slice(0, 5)) {
112+
console.log(` ${formatDiagnostic(diag)}`)
113+
}
114+
115+
// Hover information
116+
printSection('3. Hover Information')
117+
// Hover over 'add' function in index.ts (line 9, position of 'add')
118+
const hovers = await manager.hover({
119+
file: INDEX_FILE,
120+
line: 9, // import { add, ... }
121+
character: 9, // position of 'add'
122+
})
123+
console.log('Hover over "add" import:')
124+
if (hovers.length > 0 && hovers[0]) {
125+
printJson(hovers[0])
126+
}
127+
else {
128+
console.log(' (No hover info available)')
129+
}
130+
131+
// Go to definition
132+
printSection('4. Go to Definition')
133+
// Go to definition of 'add' from index.ts
134+
const definitions = await manager.definition({
135+
file: INDEX_FILE,
136+
line: 9,
137+
character: 9,
138+
})
139+
console.log('Definition of "add":')
140+
for (const def of definitions) {
141+
const relativePath = def.uri.replace('file://', '').replace(PROJECT_PATH, '.')
142+
console.log(` 📍 ${relativePath}:${def.range.start.line + 1}:${def.range.start.character + 1}`)
143+
}
144+
145+
// Find references
146+
printSection('5. Find References')
147+
// Find all references to 'add' function
148+
const refs = await manager.references({
149+
file: MATH_FILE,
150+
line: 24, // export function add(...)
151+
character: 16, // 'add' function name
152+
includeDeclaration: true,
153+
})
154+
console.log(`References to "add" function: ${refs.length} locations`)
155+
for (const ref of refs.slice(0, 5)) {
156+
const relativePath = ref.uri.replace('file://', '').replace(PROJECT_PATH, '.')
157+
console.log(` 📍 ${relativePath}:${ref.range.start.line + 1}:${ref.range.start.character + 1}`)
158+
}
159+
160+
// Code completion
161+
printSection('6. Code Completion')
162+
// Get completions after 'Math.' in math.ts (line 80 in 1-based = line 79 in 0-based)
163+
const completions = await manager.completion({
164+
file: MATH_FILE,
165+
line: 79, // return Math.abs(value) line (0-based)
166+
character: 14, // after 'Math.' (2 indent + 7 'return ' + 5 'Math.')
167+
})
168+
console.log(`Completions at Math.: ${completions.length} items`)
169+
console.log('First 10 completions:')
170+
for (const item of completions.slice(0, 10)) {
171+
const kindName = item.kind ? COMPLETION_KIND_NAMES[item.kind] || `Kind(${item.kind})` : 'Unknown'
172+
console.log(` ${kindName.padEnd(12)} ${item.label}`)
173+
}
174+
175+
// Document symbols
176+
printSection('7. Document Symbols')
177+
const docSymbols = await manager.documentSymbol(`file://${MATH_FILE}`)
178+
console.log(`Symbols in math.ts: ${docSymbols.length}`)
179+
for (const sym of docSymbols) {
180+
const kindName = SYMBOL_KIND_NAMES[sym.kind] || `Kind(${sym.kind})`
181+
console.log(` ${kindName.padEnd(12)} ${sym.name}`)
182+
}
183+
184+
// Workspace symbol search
185+
printSection('8. Workspace Symbol Search')
186+
const wsSymbols = await manager.workspaceSymbol('User')
187+
console.log(`Workspace symbols matching "User": ${wsSymbols.length}`)
188+
for (const sym of wsSymbols) {
189+
const kindName = SYMBOL_KIND_NAMES[sym.kind] || `Kind(${sym.kind})`
190+
const relativePath = sym.location.uri.replace('file://', '').replace(PROJECT_PATH, '.')
191+
console.log(` ${kindName.padEnd(12)} ${sym.name.padEnd(20)} ${relativePath}`)
192+
}
193+
194+
// Summary
195+
printSection('✅ Demo Complete!')
196+
console.log('\nThis demo showed:')
197+
console.log(' 1. LSPManager initialization')
198+
console.log(' 2. File opening and diagnostics')
199+
console.log(' 3. Hover information')
200+
console.log(' 4. Go to definition')
201+
console.log(' 5. Find references')
202+
console.log(' 6. Code completion')
203+
console.log(' 7. Document symbols')
204+
console.log(' 8. Workspace symbol search')
205+
console.log('\nFor more advanced usage, see the README.md')
206+
}
207+
finally {
208+
// Cleanup
209+
printSection('Cleanup')
210+
await manager.shutdown()
211+
console.log('✅ LSP servers shut down')
212+
}
213+
}
214+
215+
// Run the demo
216+
main().catch(console.error)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "lsp-typescript-example",
3+
"type": "module",
4+
"version": "1.0.0",
5+
"private": true,
6+
"description": "Example TypeScript project for testing @pleaseai/code-lsp functionality",
7+
"scripts": {
8+
"demo": "bun run demo.ts",
9+
"typecheck": "tsc --noEmit"
10+
},
11+
"devDependencies": {
12+
"eslint": "^9.18.0",
13+
"typescript": "^5.7.0"
14+
}
15+
}

0 commit comments

Comments
 (0)