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
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@
[submodule "ref/opencode"]
path = ref/opencode
url = https://github.com/sst/opencode.git
[submodule "ref/multispy"]
path = ref/multispy
url = https://github.com/microsoft/multilspy.git
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ Claude/MCP Client <-> StdioTransport <-> McpServer <-> Providers
| Python | pyright-langserver | pyproject.toml, setup.py, requirements.txt |
| Go | gopls | go.mod, go.work |
| Rust | rust-analyzer | Cargo.toml |
| Kotlin | JetBrains Kotlin LSP (auto-download) | build.gradle.kts, build.gradle, pom.xml |

### Built-in Formatters

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ Create `dora.json` or `opencode.json` in your project root:
| Python | pyright | pyproject.toml, requirements.txt |
| Go | gopls | go.mod |
| Rust | rust-analyzer | Cargo.toml |
| Kotlin | JetBrains Kotlin LSP | build.gradle.kts, pom.xml |

### Formatters

Expand Down
117 changes: 117 additions & 0 deletions packages/lsp/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# @pleaseai/code-lsp

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.

## Architecture

```
src/
├── index.ts # Public API, LSPManager class
├── client.ts # LSP client implementation (JSON-RPC)
├── server.ts # LSP server definitions
└── language.ts # Language ID mapping
```

## Supported Language Servers

| Server | ID | Extensions | Root Detection |
|--------|-----|------------|----------------|
| 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 |
| 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 |

## Adding a New Server

1. Define the server in `server.ts`:
```typescript
export const MyServer: LSPServerInfo = {
id: 'my-server',
extensions: ['.ext'],
root: nearestRoot(['config.json']), // or custom root function
async spawn(root) {
const proc = spawn('my-lsp', ['--stdio'], { cwd: root })
return { process: proc }
},
}
```

2. Add to `LSP_SERVERS` array in `server.ts`

3. Export from `index.ts`

4. Add tests in `__tests__/server.test.ts`

## Auto-Download Pattern (Kotlin Example)

For servers requiring runtime dependencies:

```typescript
const KOTLIN_RUNTIME_DEPS = {
kotlinLsp: { url: '...', version: '...' },
java: {
'win-x64': { url: '...', javaHomePath: '...', javaPath: '...' },
'linux-x64': { url: '...', javaHomePath: '...', javaPath: '...' },
// ... other platforms
} as Record<PlatformId, { url: string, javaHomePath: string, javaPath: string }>,
}

async function setupKotlinDependencies(platformId: PlatformId) {
const cacheDir = path.join(os.homedir(), '.cache', 'dora', 'kotlin-lsp')
// Check if exists, download and extract if not
// Verify files exist after download
return { javaHomePath, kotlinLspPath }
}
```

## Key APIs

### LSPManager

Main entry point for managing LSP clients:

```typescript
const manager = new LSPManager(projectPath)

// Touch file to initialize LSP
await manager.touchFile('src/index.ts', true)

// Get diagnostics
const diags = await manager.diagnostics()

// Get hover info
const hover = await manager.hover({ file, line, character })

// Search symbols
const symbols = await manager.workspaceSymbol('query')

// Cleanup
await manager.shutdown()
```

### Server Utilities

```typescript
import { getServerById, getServersForExtension } from '@pleaseai/code-lsp'

const server = getServerById('typescript')
const servers = getServersForExtension('.ts')
```

## Testing

```bash
bun test ./src
```

Tests cover:
- Server definitions (ID, extensions, root, spawn functions)
- LSP client lifecycle
- Manager operations
37 changes: 37 additions & 0 deletions packages/lsp/src/__tests__/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
getServerById,
getServersForExtension,
GoplsServer,
KotlinServer,
LSP_SERVERS,
OxlintServer,
PyrightServer,
Expand All @@ -22,6 +23,7 @@ describe('LSP_SERVERS', () => {
expect(serverIds).toContain('pyright')
expect(serverIds).toContain('gopls')
expect(serverIds).toContain('rust-analyzer')
expect(serverIds).toContain('kotlin')
})
})

Expand Down Expand Up @@ -120,6 +122,25 @@ describe('RustAnalyzerServer', () => {
})
})

describe('KotlinServer', () => {
test('has correct id', () => {
expect(KotlinServer.id).toBe('kotlin')
})

test('supports Kotlin extensions', () => {
expect(KotlinServer.extensions).toContain('.kt')
expect(KotlinServer.extensions).toContain('.kts')
})

test('has root function', () => {
expect(typeof KotlinServer.root).toBe('function')
})

test('has spawn function', () => {
expect(typeof KotlinServer.spawn).toBe('function')
})
})

describe('getServerById', () => {
test('returns typescript server', () => {
const server = getServerById('typescript')
Expand Down Expand Up @@ -158,6 +179,22 @@ describe('getServersForExtension', () => {
expect(serverIds).toContain('gopls')
})

test('returns servers for .kt extension', () => {
const servers = getServersForExtension('.kt')
expect(servers.length).toBeGreaterThan(0)

const serverIds = servers.map(s => s.id)
expect(serverIds).toContain('kotlin')
})

test('returns servers for .kts extension', () => {
const servers = getServersForExtension('.kts')
expect(servers.length).toBeGreaterThan(0)

const serverIds = servers.map(s => s.id)
expect(serverIds).toContain('kotlin')
})

test('returns empty array for unknown extension', () => {
const servers = getServersForExtension('.unknown')
expect(servers).toEqual([])
Expand Down
1 change: 1 addition & 0 deletions packages/lsp/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,7 @@ export {
getServerById,
getServersForExtension,
GoplsServer,
KotlinServer,
LSP_SERVERS,
type LSPServerHandle,
type LSPServerInfo,
Expand Down
Loading