Skip to content

Commit f71d8dd

Browse files
authored
feat(lsp): add Kotlin Language Server support (#7)
* feat(lsp): add Kotlin Language Server support (#6) Add support for JetBrains Kotlin LSP with auto-download capabilities: - Platform detection for win-x64, linux-x64, linux-arm64, osx-x64, osx-arm64 - Auto-download JetBrains Kotlin LSP (v0.253.10629) - Auto-download bundled JRE 21 from vscode-java releases - Root detection via Gradle (build.gradle.kts, build.gradle) and Maven (pom.xml) - Support for .kt and .kts file extensions - Exported KotlinServer from index.ts - Added comprehensive tests for KotlinServer Closes #6 * docs: update README and add lsp package CLAUDE.md - Add Kotlin LSP to README supported languages table - Create CLAUDE.md for packages/lsp with architecture overview * fix(lsp): improve error handling and documentation accuracy Error handling improvements: - Replace empty .catch(() => {}) with proper error logging - Add specific ENOENT checks instead of broad catch blocks - Add try-catch around spawn() with error/exit event listeners - Add user guidance message for dependency setup failures - Add error context to verification failure messages Documentation fixes: - Fix misleading extractZip comment (system unzip, not Bun native) - Update CLAUDE.md with accurate extension lists for all servers - Add missing root detection files (go.sum, pyrightconfig.json, etc.) - Update auto-download pattern example to match implementation Test improvements: - Add .kts extension test for getServersForExtension * style: fix whitespace in CLAUDE.md
1 parent 10354c7 commit f71d8dd

8 files changed

Lines changed: 507 additions & 0 deletions

File tree

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,6 @@
77
[submodule "ref/opencode"]
88
path = ref/opencode
99
url = https://github.com/sst/opencode.git
10+
[submodule "ref/multispy"]
11+
path = ref/multispy
12+
url = https://github.com/microsoft/multilspy.git

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ Claude/MCP Client <-> StdioTransport <-> McpServer <-> Providers
124124
| Python | pyright-langserver | pyproject.toml, setup.py, requirements.txt |
125125
| Go | gopls | go.mod, go.work |
126126
| Rust | rust-analyzer | Cargo.toml |
127+
| Kotlin | JetBrains Kotlin LSP (auto-download) | build.gradle.kts, build.gradle, pom.xml |
127128

128129
### Built-in Formatters
129130

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ Create `dora.json` or `opencode.json` in your project root:
101101
| Python | pyright | pyproject.toml, requirements.txt |
102102
| Go | gopls | go.mod |
103103
| Rust | rust-analyzer | Cargo.toml |
104+
| Kotlin | JetBrains Kotlin LSP | build.gradle.kts, pom.xml |
104105

105106
### Formatters
106107

packages/lsp/CLAUDE.md

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# @pleaseai/code-lsp
2+
3+
LSP (Language Server Protocol) client implementation for AI coding tools.
4+
5+
## Overview
6+
7+
This package provides a unified interface for interacting with multiple language servers, enabling real-time diagnostics, hover information, and symbol navigation.
8+
9+
## Architecture
10+
11+
```
12+
src/
13+
├── index.ts # Public API, LSPManager class
14+
├── client.ts # LSP client implementation (JSON-RPC)
15+
├── server.ts # LSP server definitions
16+
└── language.ts # Language ID mapping
17+
```
18+
19+
## Supported Language Servers
20+
21+
| Server | ID | Extensions | Root Detection |
22+
|--------|-----|------------|----------------|
23+
| TypeScript | `typescript` | .ts, .tsx, .js, .jsx, .mjs, .cjs, .mts, .cts | package-lock.json, bun.lockb, bun.lock, yarn.lock, pnpm-lock.yaml |
24+
| Deno | `deno` | .ts, .tsx, .js, .jsx, .mjs | deno.json, deno.jsonc |
25+
| 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 |
26+
| Pyright | `pyright` | .py, .pyi | pyproject.toml, setup.py, requirements.txt, pyrightconfig.json |
27+
| Gopls | `gopls` | .go | go.work, go.mod, go.sum |
28+
| Rust Analyzer | `rust-analyzer` | .rs | Cargo.toml, Cargo.lock |
29+
| Kotlin | `kotlin` | .kt, .kts | build.gradle.kts, build.gradle, settings.gradle.kts, settings.gradle, pom.xml |
30+
31+
## Adding a New Server
32+
33+
1. Define the server in `server.ts`:
34+
```typescript
35+
export const MyServer: LSPServerInfo = {
36+
id: 'my-server',
37+
extensions: ['.ext'],
38+
root: nearestRoot(['config.json']), // or custom root function
39+
async spawn(root) {
40+
const proc = spawn('my-lsp', ['--stdio'], { cwd: root })
41+
return { process: proc }
42+
},
43+
}
44+
```
45+
46+
2. Add to `LSP_SERVERS` array in `server.ts`
47+
48+
3. Export from `index.ts`
49+
50+
4. Add tests in `__tests__/server.test.ts`
51+
52+
## Auto-Download Pattern (Kotlin Example)
53+
54+
For servers requiring runtime dependencies:
55+
56+
```typescript
57+
const KOTLIN_RUNTIME_DEPS = {
58+
kotlinLsp: { url: '...', version: '...' },
59+
java: {
60+
'win-x64': { url: '...', javaHomePath: '...', javaPath: '...' },
61+
'linux-x64': { url: '...', javaHomePath: '...', javaPath: '...' },
62+
// ... other platforms
63+
} as Record<PlatformId, { url: string, javaHomePath: string, javaPath: string }>,
64+
}
65+
66+
async function setupKotlinDependencies(platformId: PlatformId) {
67+
const cacheDir = path.join(os.homedir(), '.cache', 'dora', 'kotlin-lsp')
68+
// Check if exists, download and extract if not
69+
// Verify files exist after download
70+
return { javaHomePath, kotlinLspPath }
71+
}
72+
```
73+
74+
## Key APIs
75+
76+
### LSPManager
77+
78+
Main entry point for managing LSP clients:
79+
80+
```typescript
81+
const manager = new LSPManager(projectPath)
82+
83+
// Touch file to initialize LSP
84+
await manager.touchFile('src/index.ts', true)
85+
86+
// Get diagnostics
87+
const diags = await manager.diagnostics()
88+
89+
// Get hover info
90+
const hover = await manager.hover({ file, line, character })
91+
92+
// Search symbols
93+
const symbols = await manager.workspaceSymbol('query')
94+
95+
// Cleanup
96+
await manager.shutdown()
97+
```
98+
99+
### Server Utilities
100+
101+
```typescript
102+
import { getServerById, getServersForExtension } from '@pleaseai/code-lsp'
103+
104+
const server = getServerById('typescript')
105+
const servers = getServersForExtension('.ts')
106+
```
107+
108+
## Testing
109+
110+
```bash
111+
bun test ./src
112+
```
113+
114+
Tests cover:
115+
- Server definitions (ID, extensions, root, spawn functions)
116+
- LSP client lifecycle
117+
- Manager operations

packages/lsp/src/__tests__/server.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
getServerById,
55
getServersForExtension,
66
GoplsServer,
7+
KotlinServer,
78
LSP_SERVERS,
89
OxlintServer,
910
PyrightServer,
@@ -22,6 +23,7 @@ describe('LSP_SERVERS', () => {
2223
expect(serverIds).toContain('pyright')
2324
expect(serverIds).toContain('gopls')
2425
expect(serverIds).toContain('rust-analyzer')
26+
expect(serverIds).toContain('kotlin')
2527
})
2628
})
2729

@@ -120,6 +122,25 @@ describe('RustAnalyzerServer', () => {
120122
})
121123
})
122124

125+
describe('KotlinServer', () => {
126+
test('has correct id', () => {
127+
expect(KotlinServer.id).toBe('kotlin')
128+
})
129+
130+
test('supports Kotlin extensions', () => {
131+
expect(KotlinServer.extensions).toContain('.kt')
132+
expect(KotlinServer.extensions).toContain('.kts')
133+
})
134+
135+
test('has root function', () => {
136+
expect(typeof KotlinServer.root).toBe('function')
137+
})
138+
139+
test('has spawn function', () => {
140+
expect(typeof KotlinServer.spawn).toBe('function')
141+
})
142+
})
143+
123144
describe('getServerById', () => {
124145
test('returns typescript server', () => {
125146
const server = getServerById('typescript')
@@ -158,6 +179,22 @@ describe('getServersForExtension', () => {
158179
expect(serverIds).toContain('gopls')
159180
})
160181

182+
test('returns servers for .kt extension', () => {
183+
const servers = getServersForExtension('.kt')
184+
expect(servers.length).toBeGreaterThan(0)
185+
186+
const serverIds = servers.map(s => s.id)
187+
expect(serverIds).toContain('kotlin')
188+
})
189+
190+
test('returns servers for .kts extension', () => {
191+
const servers = getServersForExtension('.kts')
192+
expect(servers.length).toBeGreaterThan(0)
193+
194+
const serverIds = servers.map(s => s.id)
195+
expect(serverIds).toContain('kotlin')
196+
})
197+
161198
test('returns empty array for unknown extension', () => {
162199
const servers = getServersForExtension('.unknown')
163200
expect(servers).toEqual([])

packages/lsp/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,7 @@ export {
396396
getServerById,
397397
getServersForExtension,
398398
GoplsServer,
399+
KotlinServer,
399400
LSP_SERVERS,
400401
type LSPServerHandle,
401402
type LSPServerInfo,

0 commit comments

Comments
 (0)