Skip to content

Commit dce7c7b

Browse files
committed
refactor: rename DockerServer to ContainerServer and update image registry
- Rename DockerServer to ContainerServer across all client implementations - Update container server variable names (*DockerServer -> *ContainerServer) - Change image registry from docker.io/lspcontainers/ to ghcr.io/observerw/lsp-client/ - Update imports and exports to use ContainerServer instead of DockerServer - Remove obsolete docker.py server module
1 parent c88cbd3 commit dce7c7b

36 files changed

+1347
-23
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,13 @@ if __name__ == "__main__":
6969
from pathlib import Path
7070
import anyio
7171
from lsp_client import Position
72-
from lsp_client.clients.pyright import PyrightClient, PyrightDockerServer
72+
from lsp_client.clients.pyright import PyrightClient, PyrightContainerServer
7373

7474
async def main():
7575
workspace = Path.cwd()
7676
async with PyrightClient(
7777
workspace=workspace,
78-
server=PyrightDockerServer(mounts=[workspace]),
78+
server=PyrightContainerServer(mounts=[workspace]),
7979
) as client:
8080
# Find definition of something at line 11, character 28 in a file
8181
refs = await client.request_definition_locations(
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# How to Add Support for a New LSP Server
2+
3+
To add a new LSP server to this project, follow these steps to ensure both client support and containerized deployment are correctly configured.
4+
5+
## 1. Implement the Client
6+
7+
Create a new file in `src/lsp_client/clients/` (e.g., `your_server.py`) and implement your client class by inheriting from `LSPClient` and relevant capability mixins.
8+
9+
Refer to existing implementations like `pyright.py` or `rust_analyzer.py` for inspiration.
10+
11+
Don't forget to export your new client in `src/lsp_client/clients/__init__.py`.
12+
13+
## 2. Register for Automatic Versioning
14+
15+
Add your server configuration to `registry/wiki.toml`. This allows the automation script to track the latest versions and update both the CI workflow and Docker build arguments.
16+
17+
Example entry:
18+
```toml
19+
[your-server]
20+
type = "npm" # or "pypi", "github"
21+
package = "your-server-package-name"
22+
```
23+
24+
The system will automatically:
25+
- Fetch the latest version from the provider.
26+
- Update the GitHub Actions matrix.
27+
- Update the `ARG VERSION` in your `ContainerFile`.
28+
29+
## 3. Create a Docker Environment
30+
31+
Create a directory for your server in `container/` and add a `ContainerFile`.
32+
33+
```bash
34+
mkdir -p container/your-server
35+
touch container/your-server/ContainerFile
36+
```
37+
38+
Use multi-stage builds to keep the image size minimal. Ensure you use `ARG VERSION` for the server version.
39+
40+
Example `ContainerFile`:
41+
```dockerfile
42+
ARG VERSION=0.0.1
43+
FROM node:22-slim AS builder
44+
ARG VERSION
45+
RUN npm install -g your-server-package-name@${VERSION}
46+
47+
FROM node:22-slim
48+
COPY --from=builder /usr/local/lib/node_modules /usr/local/lib/node_modules
49+
COPY --from=builder /usr/local/bin/your-server-binary /usr/local/bin/your-server-binary
50+
51+
WORKDIR /workspace
52+
ENTRYPOINT ["your-server-binary", "--stdio"]
53+
```
54+
55+
## 4. Verify
56+
57+
1. Run the version update script locally:
58+
```bash
59+
python3 scripts/update_lsp_versions.py
60+
```
61+
2. Check if `.github/workflows/lsp-servers.yml` and your `ContainerFile` are updated with the latest version.
62+
3. Commit and push your changes. The GitHub Action will build and push the new image to GHCR.

examples/pyright_docker.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,20 @@
1111
import anyio
1212

1313
from lsp_client import Position
14-
from lsp_client.clients.pyright import PyrightClient, PyrightDockerServer
14+
from lsp_client.clients.pyright import PyrightClient, PyrightContainerServer
1515

1616

1717
async def main():
1818
# Set up workspace directory and mount it in Docker
1919
workspace = Path.cwd()
2020
async with PyrightClient(
21-
# here we use `PyrightDockerServer`
22-
server=PyrightDockerServer(
21+
# here we use `PyrightContainerServer`
22+
server=PyrightContainerServer(
2323
mounts=[workspace] # Mount workspace into container
2424
),
2525
workspace=workspace,
2626
) as client:
27-
# Find definition of PyrightDockerServer at line 12, column 28
27+
# Find definition of PyrightContainerServer at line 12, column 28
2828
refs = await client.request_definition_locations(
2929
file_path=__file__,
3030
position=Position(12, 28),

packages/lsp-client-js/.gitignore

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# dependencies (bun install)
2+
node_modules
3+
4+
# output
5+
out
6+
dist
7+
*.tgz
8+
9+
# code coverage
10+
coverage
11+
*.lcov
12+
13+
# logs
14+
logs
15+
_.log
16+
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
17+
18+
# dotenv environment variable files
19+
.env
20+
.env.development.local
21+
.env.test.local
22+
.env.production.local
23+
.env.local
24+
25+
# caches
26+
.eslintcache
27+
.cache
28+
*.tsbuildinfo
29+
30+
# IntelliJ based IDEs
31+
.idea
32+
33+
# Finder (MacOS) folder config
34+
.DS_Store

packages/lsp-client-js/CLAUDE.md

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
2+
Default to using Bun instead of Node.js.
3+
4+
- Use `bun <file>` instead of `node <file>` or `ts-node <file>`
5+
- Use `bun test` instead of `jest` or `vitest`
6+
- Use `bun build <file.html|file.ts|file.css>` instead of `webpack` or `esbuild`
7+
- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install`
8+
- Use `bun run <script>` instead of `npm run <script>` or `yarn run <script>` or `pnpm run <script>`
9+
- Use `bunx <package> <command>` instead of `npx <package> <command>`
10+
- Bun automatically loads .env, so don't use dotenv.
11+
12+
## APIs
13+
14+
- `Bun.serve()` supports WebSockets, HTTPS, and routes. Don't use `express`.
15+
- `bun:sqlite` for SQLite. Don't use `better-sqlite3`.
16+
- `Bun.redis` for Redis. Don't use `ioredis`.
17+
- `Bun.sql` for Postgres. Don't use `pg` or `postgres.js`.
18+
- `WebSocket` is built-in. Don't use `ws`.
19+
- Prefer `Bun.file` over `node:fs`'s readFile/writeFile
20+
- Bun.$`ls` instead of execa.
21+
22+
## Testing
23+
24+
Use `bun test` to run tests.
25+
26+
```ts#index.test.ts
27+
import { test, expect } from "bun:test";
28+
29+
test("hello world", () => {
30+
expect(1).toBe(1);
31+
});
32+
```
33+
34+
## Frontend
35+
36+
Use HTML imports with `Bun.serve()`. Don't use `vite`. HTML imports fully support React, CSS, Tailwind.
37+
38+
Server:
39+
40+
```ts#index.ts
41+
import index from "./index.html"
42+
43+
Bun.serve({
44+
routes: {
45+
"/": index,
46+
"/api/users/:id": {
47+
GET: (req) => {
48+
return new Response(JSON.stringify({ id: req.params.id }));
49+
},
50+
},
51+
},
52+
// optional websocket support
53+
websocket: {
54+
open: (ws) => {
55+
ws.send("Hello, world!");
56+
},
57+
message: (ws, message) => {
58+
ws.send(message);
59+
},
60+
close: (ws) => {
61+
// handle close
62+
}
63+
},
64+
development: {
65+
hmr: true,
66+
console: true,
67+
}
68+
})
69+
```
70+
71+
HTML files can import .tsx, .jsx or .js files directly and Bun's bundler will transpile & bundle automatically. `<link>` tags can point to stylesheets and Bun's CSS bundler will bundle.
72+
73+
```html#index.html
74+
<html>
75+
<body>
76+
<h1>Hello, world!</h1>
77+
<script type="module" src="./frontend.tsx"></script>
78+
</body>
79+
</html>
80+
```
81+
82+
With the following `frontend.tsx`:
83+
84+
```tsx#frontend.tsx
85+
import React from "react";
86+
import { createRoot } from "react-dom/client";
87+
88+
// import .css files directly and it works
89+
import './index.css';
90+
91+
const root = createRoot(document.body);
92+
93+
export default function Frontend() {
94+
return <h1>Hello, world!</h1>;
95+
}
96+
97+
root.render(<Frontend />);
98+
```
99+
100+
Then, run index.ts
101+
102+
```sh
103+
bun --hot ./index.ts
104+
```
105+
106+
For more information, read the Bun API docs in `node_modules/bun-types/docs/**.mdx`.

packages/lsp-client-js/README.md

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# lsp-client-js
2+
3+
A lightweight LSP (Language Server Protocol) client implementation in TypeScript using Bun.
4+
5+
## Features
6+
7+
- **Decentralized Capabilities**: Uses a feature-based architecture (inspired by the Python version's Mixins) to manage LSP capabilities independently.
8+
- **Robust JSON-RPC**: Leverages `vscode-jsonrpc` for reliable communication with language servers.
9+
- **Bun Optimized**: Built with Bun for high performance and modern TypeScript support.
10+
- **Async Iterators**: Uses async generators for managing the client lifecycle.
11+
12+
## Architecture
13+
14+
The project follows the design principles outlined in `docs/DESIGN.md`:
15+
16+
1. **Feature Registry**: Instead of Python's MRO, we use an explicit registration pattern for features (capabilities).
17+
2. **JSON-RPC Layer**: Built on top of `vscode-jsonrpc` which provides robust request/response matching and stream handling.
18+
3. **Lifecycle Management**: Async generators ensure proper initialization and cleanup (shutdown/exit/kill).
19+
20+
## Development
21+
22+
To run the examples:
23+
24+
```bash
25+
# Basic hover example
26+
bun run examples/basic.ts
27+
28+
# Diagnostics example
29+
bun run examples/diagnostics.ts
30+
31+
# Pyrefly specific examples
32+
bun run examples/pyrefly.ts
33+
bun run examples/pyrefly_diagnostics.ts
34+
35+
# TypeScript `using` syntax examples
36+
bun run examples/using_syntax.ts
37+
bun run examples/using_advanced.ts
38+
bun run examples/using_async.ts
39+
40+
# Rust-analyzer example
41+
bun run examples/rust-analyzer.ts
42+
```
43+
44+
## TypeScript `using` Syntax Integration
45+
46+
This project showcases TypeScript 5.2+'s **Explicit Resource Management** feature:
47+
48+
### Key Features Implemented
49+
50+
1. **Automatic Resource Cleanup**: LSP server connections are automatically disposed
51+
2. **Exception Safety**: Resources are cleaned up even when errors occur
52+
3. **Clean Syntax**: Eliminates manual `try/finally` blocks for resource management
53+
4. **Stack-Based Order**: Disposal happens in correct LIFO order
54+
55+
### Usage Examples
56+
57+
#### Basic Resource Management
58+
```typescript
59+
{
60+
using server = new SafeLSPServer(['pyrefly', 'lsp']);
61+
const connection = await server.start();
62+
// Connection automatically disposed when scope ends
63+
}
64+
```
65+
66+
#### Error Safety
67+
```typescript
68+
try {
69+
using server = new SafeLSPServer(['pyrefly', 'lsp']);
70+
await server.start();
71+
throw new Error('Something went wrong');
72+
} catch (e) {
73+
// Server is automatically disposed even with errors
74+
}
75+
```
76+
77+
#### Async Resource Management
78+
```typescript
79+
await using connection = await server.start();
80+
// Async resources are automatically disposed
81+
```
82+
83+
#### LSP Client Integration
84+
```typescript
85+
for await (const client of lspClient.start()) {
86+
// Client lifecycle automatically managed
87+
// Server disposed when generator exits
88+
}
89+
```
90+
91+
### Implementation Details
92+
93+
The project provides:
94+
- **`SafeLSPServer`**: Implements `Disposable` for `using` compatibility
95+
- **Enhanced LSPClient**: Uses `using` for automatic connection disposal
96+
- **Resource Safety**: Ensures servers are killed and connections disposed
97+
98+
## Testing with Pyrefly
99+
100+
The project includes comprehensive tests with Pyrefly LSP server. To run the tests:
101+
102+
1. Ensure Pyrefly is installed: `pip install pyrefly` or `uv tool install pyrefly`
103+
2. Create a `pyproject.toml` file in your project root with Pyrefly configuration
104+
3. Run the Pyrefly examples to test hover, definition, and diagnostics features

packages/lsp-client-js/bun.lock

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"lockfileVersion": 1,
3+
"configVersion": 1,
4+
"workspaces": {
5+
"": {
6+
"name": "lsp-client-js",
7+
"dependencies": {
8+
"vscode-jsonrpc": "^8.2.1",
9+
"vscode-languageserver-protocol": "^3.17.5",
10+
"vscode-languageserver-types": "^3.17.5",
11+
},
12+
"devDependencies": {
13+
"@types/bun": "latest",
14+
},
15+
"peerDependencies": {
16+
"typescript": "^5",
17+
},
18+
},
19+
},
20+
"packages": {
21+
"@types/bun": ["@types/[email protected]", "https://registry.npmmirror.com/@types/bun/-/bun-1.3.5.tgz", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="],
22+
23+
"@types/node": ["@types/[email protected]", "https://registry.npmmirror.com/@types/node/-/node-25.0.3.tgz", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="],
24+
25+
"bun-types": ["[email protected]", "https://registry.npmmirror.com/bun-types/-/bun-types-1.3.5.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
26+
27+
"typescript": ["[email protected]", "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
28+
29+
"undici-types": ["[email protected]", "https://registry.npmmirror.com/undici-types/-/undici-types-7.16.0.tgz", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
30+
31+
"vscode-jsonrpc": ["[email protected]", "https://registry.npmmirror.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.1.tgz", {}, "sha512-kdjOSJ2lLIn7r1rtrMbbNCHjyMPfRnowdKjBQ+mGq6NAW5QY2bEZC/khaC5OR8svbbjvLEaIXkOq45e2X9BIbQ=="],
32+
33+
"vscode-languageserver-protocol": ["[email protected]", "https://registry.npmmirror.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", { "dependencies": { "vscode-jsonrpc": "8.2.0", "vscode-languageserver-types": "3.17.5" } }, "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg=="],
34+
35+
"vscode-languageserver-types": ["[email protected]", "https://registry.npmmirror.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", {}, "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg=="],
36+
37+
"vscode-languageserver-protocol/vscode-jsonrpc": ["[email protected]", "https://registry.npmmirror.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", {}, "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA=="],
38+
}
39+
}

0 commit comments

Comments
 (0)