Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": []
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,4 @@ Thumbs.db
# Coverage
coverage/

/.claude/settings.local.json
30 changes: 29 additions & 1 deletion packages/lsp/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand Down Expand Up @@ -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
Expand Down
129 changes: 129 additions & 0 deletions packages/lsp/README.md
Original file line number Diff line number Diff line change
@@ -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
125 changes: 125 additions & 0 deletions packages/lsp/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,64 @@ export const LocationLinkSchema = z.object({
})
export type LocationLink = z.infer<typeof LocationLinkSchema>

/**
* 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<typeof CompletionItemSchema>

/**
* 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<typeof CompletionListSchema>

/**
* LSP Symbol schema
*/
Expand Down Expand Up @@ -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<CompletionItem[]> {
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
Expand Down