Skip to content

Commit 362f7a1

Browse files
committed
Merge branch 'main' into feat/deno-client
2 parents 79453f7 + 50f950c commit 362f7a1

File tree

37 files changed

+1256
-132
lines changed

37 files changed

+1256
-132
lines changed

.github/workflows/docs.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ name: Deploy Documentation
33
on:
44
push:
55
branches: ["main"]
6+
paths:
7+
- "src/**"
8+
- "pyproject.toml"
69
workflow_dispatch:
710

811
permissions:
@@ -12,7 +15,7 @@ permissions:
1215

1316
concurrency:
1417
group: "pages"
15-
cancel-in-progress: false
18+
cancel-in-progress: true
1619

1720
jobs:
1821
build:
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
name: Update Servers
2+
3+
on:
4+
schedule:
5+
- cron: '0 0 * * 0' # Weekly on Sunday
6+
workflow_dispatch:
7+
inputs:
8+
force:
9+
description: 'Force build all images'
10+
type: boolean
11+
default: false
12+
13+
permissions:
14+
contents: write
15+
packages: write
16+
17+
jobs:
18+
update:
19+
runs-on: ubuntu-latest
20+
outputs:
21+
has_updates: ${{ steps.update.outputs.has_updates }}
22+
updated_servers: ${{ steps.update.outputs.updated_servers }}
23+
steps:
24+
- uses: actions/checkout@v4
25+
26+
- name: Install uv
27+
uses: astral-sh/setup-uv@v7
28+
with:
29+
python-version: '3.12'
30+
31+
- name: Run update script
32+
id: update
33+
run: |
34+
FORCE_FLAG=""
35+
if [[ "${{ github.event.inputs.force }}" == "true" ]]; then
36+
FORCE_FLAG="--force"
37+
fi
38+
uv run scripts/update_containers.py $FORCE_FLAG
39+
env:
40+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
41+
42+
- name: Commit and push changes
43+
if: steps.update.outputs.has_updates == 'true'
44+
run: |
45+
git config --global user.name "github-actions[bot]"
46+
git config --global user.email "github-actions[bot]@users.noreply.github.com"
47+
git add container/
48+
if ! git diff --cached --quiet; then
49+
git commit -m "chore: update server versions"
50+
git push
51+
else
52+
echo "No changes to commit"
53+
fi
54+
55+
build:
56+
needs: update
57+
if: needs.update.outputs.has_updates == 'true'
58+
runs-on: ubuntu-latest
59+
strategy:
60+
matrix:
61+
server: ${{ fromJson(needs.update.outputs.updated_servers) }}
62+
steps:
63+
- uses: actions/checkout@v4
64+
65+
- name: Log in to GHCR
66+
uses: docker/login-action@v3
67+
with:
68+
registry: ghcr.io
69+
username: ${{ github.actor }}
70+
password: ${{ secrets.GITHUB_TOKEN }}
71+
72+
- name: Get version
73+
id: get_version
74+
run: |
75+
VERSION=$(grep "ARG VERSION=" container/${{ matrix.server }}/ContainerFile | cut -d'=' -f2)
76+
echo "version=$VERSION" >> $GITHUB_OUTPUT
77+
78+
- name: Build and push
79+
uses: docker/build-push-action@v6
80+
with:
81+
context: container/${{ matrix.server }}
82+
file: container/${{ matrix.server }}/ContainerFile
83+
push: true
84+
tags: |
85+
ghcr.io/${{ github.repository }}/${{ matrix.server }}:${{ steps.get_version.outputs.version }}
86+
ghcr.io/${{ github.repository }}/${{ matrix.server }}:latest

README.md

Lines changed: 70 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -2,105 +2,117 @@
22

33
[![Python](https://img.shields.io/badge/Python-3.12+-blue.svg)](https://python.org)
44
[![License](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
5+
[![Documentation](https://img.shields.io/badge/docs-pdoc-blue)](https://observerw.github.io/lsp-client/)
56
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/observerw/lsp-client)
67

7-
A full-featured, well-typed, and easy-to-use Python client for the Language Server Protocol (LSP). This library provides a clean, async-first interface for interacting with language servers, supporting both local and Docker-based runtimes.
8+
A production-ready, async-first Python client for the Language Server Protocol (LSP). Built for developers who need fine-grained control, container isolation, and extensibility when integrating language intelligence into their tools.
89

9-
## Features
10+
## Why lsp-client?
1011

11-
- **🚀 Async-first Design**: Built for high-performance concurrent operations
12-
- **🔧 Full LSP Support**: Comprehensive implementation of LSP 3.17 specification
13-
- **🐳 Docker Support**: Run language servers in isolated containers
14-
- **📝 Type Safety**: Full type annotations with Pydantic validation
15-
- **🧩 Modular Architecture**: Mixin-based capability system for easy extension
16-
- **🎯 Production Ready**: Robust error handling with tenacity retries
17-
- **📚 Well Documented**: Extensive documentation and examples
12+
`lsp-client` is engineered for developers building **production-grade tooling** that requires precise control over language server environments:
13+
14+
- **🐳 Container-First Architecture**: Containers as first-class citizens with workspace mounting, path translation, and lifecycle management. Pre-built images available, seamless switching between local and container environments.
15+
- **🧩 Intelligent Capability Management**: Zero-overhead mixin system with automatic registration, negotiation, and availability checks. Only access methods for registered capabilities.
16+
- **🎯 Complete LSP 3.17 Support**: Full specification implementation with pre-configured clients for Pyright, Rust-Analyzer, Deno, TypeScript, and Pyrefly.
17+
- **⚡ Production-Ready & Modern**: Explicit environment control with no auto-downloads. Built with async patterns, comprehensive error handling, retries, and full type safety.
1818

1919
## Quick Start
2020

2121
### Installation
2222

2323
```bash
24+
# Recommended: Use uv for modern Python dependency management
2425
uv add lsp-client
26+
27+
# Or with pip
28+
pip install lsp-client
2529
```
2630

27-
### Basic Usage
31+
### Local Language Server
32+
33+
The following code snippet can be run as-is, try it out:
2834

2935
```python
30-
from pathlib import Path
36+
# install pyrefly with `uv tool install pyrefly` first
3137
import anyio
3238
from lsp_client import Position
33-
from lsp_client.clients.pyright import PyrightClient, PyrightLocalServer
39+
from lsp_client.clients.pyrefly import PyreflyClient
3440

3541
async def main():
36-
workspace = Path.cwd()
37-
async with PyrightClient(
38-
workspace=workspace,
39-
server=PyrightLocalServer(),
40-
) as client:
41-
# Find definition of something at line 11, character 28 in a file
42-
refs = await client.request_definition_locations(
43-
file_path="src/main.py",
44-
position=Position(11, 28) # line 12, character 29 (1-indexed)
42+
async with PyreflyClient() as client:
43+
refs = await client.request_references(
44+
file_path="example.py",
45+
position=Position(10, 5)
4546
)
46-
47-
if not refs:
48-
print("No definition found.")
49-
return
50-
5147
for ref in refs:
52-
print(f"Found definition at: {ref.uri} - Range: {ref.range}")
48+
print(f"Reference at {ref.uri}: {ref.range}")
5349

54-
if __name__ == "__main__":
55-
anyio.run(main)
50+
anyio.run(main)
5651
```
5752

58-
### Docker-based Language Server
53+
### Container-based Language Server
5954

6055
```python
61-
from pathlib import Path
62-
import anyio
63-
from lsp_client import Position
64-
from lsp_client.clients.pyright import PyrightClient, PyrightDockerServer
65-
6656
async def main():
6757
workspace = Path.cwd()
6858
async with PyrightClient(
69-
workspace=workspace,
70-
server=PyrightDockerServer(mounts=[workspace]),
59+
server=PyrightContainerServer(mounts=[workspace]),
60+
workspace=workspace
7161
) as client:
72-
# Find definition of something at line 11, character 28 in a file
73-
refs = await client.request_definition_locations(
74-
file_path="src/main.py",
75-
position=Position(11, 28)
62+
# Find definition of a symbol
63+
definitions = await client.request_definition_locations(
64+
file_path="example.py",
65+
position=Position(10, 5)
7666
)
67+
for def_loc in definitions:
68+
print(f"Definition at {def_loc.uri}: {def_loc.range}")
7769

78-
if not refs:
79-
print("No definition found.")
80-
return
70+
anyio.run(main)
71+
```
8172

82-
for ref in refs:
83-
print(f"Found definition at: {ref.uri} - Range: {ref.range}")
73+
### More Examples
74+
75+
The `examples/` directory contains comprehensive usage examples:
76+
77+
- `pyright_docker.py` - Using Pyright in Docker for Python analysis
78+
- `rust_analyzer.py` - Rust code intelligence with Rust-Analyzer
79+
- `pyrefly.py` - Python linting and analysis with Pyrefly
80+
- `protocol.py` - Direct LSP protocol usage
8481

85-
if __name__ == "__main__":
86-
anyio.run(main)
82+
Run examples with:
83+
84+
```bash
85+
uv run examples/pyright_docker.py
8786
```
8887

8988
## Supported Language Servers
9089

91-
The library includes pre-configured clients for popular language servers:
90+
| Language Server | Module Path | Language | Container Image |
91+
| -------------------------- | ---------------------------------- | --------------------- | -------------------------------------------- |
92+
| Pyright | `lsp_client.clients.pyright` | Python | `ghcr.io/observerw/lsp-client/pyright` |
93+
| Pyrefly | `lsp_client.clients.pyrefly` | Python | `ghcr.io/observerw/lsp-client/pyrefly` |
94+
| Rust Analyzer | `lsp_client.clients.rust_analyzer` | Rust | `ghcr.io/observerw/lsp-client/rust-analyzer` |
95+
| Deno | `lsp_client.clients.deno` | TypeScript/JavaScript | `ghcr.io/observerw/lsp-client/deno` |
96+
| TypeScript Language Server | `lsp_client.clients.typescript` | TypeScript/JavaScript | `ghcr.io/observerw/lsp-client/typescript` |
97+
98+
Container images are automatically updated weekly to ensure access to the latest language server versions.
9299

93-
| Language Server | Module Path | Language |
94-
| ---------------------------- | ------------------------------------ | --------------------- |
95-
| Pyright | `lsp_client.clients.pyright` | Python |
96-
| Pyrefly | `lsp_client.clients.pyrefly` | Python |
97-
| Rust Analyzer | `lsp_client.clients.rust_analyzer` | Rust |
98-
| Deno | `lsp_client.clients.deno` | TypeScript/JavaScript |
99-
| TypeScript Language Server | `lsp_client.clients.typescript` | TypeScript/JavaScript |
100+
### Key Benefits
101+
102+
1. **Method Safety**: You can only call methods for capabilities you've registered. No runtime surprises from unavailable capabilities.
103+
2. **Automatic Registration**: The mixin system automatically handles client registration, capability negotiation, and availability checks behind the scenes.
104+
3. **Zero Boilerplate**: No manual capability checking, no complex initialization logic, no error handling for missing capabilities.
105+
4. **Type Safety**: Full type annotations ensure you get compile-time guarantees about available methods.
106+
5. **Composability**: Mix and match exactly the capabilities you need, creating perfectly tailored clients.
100107

101108
## Contributing
102109

103-
We welcome contributions! Please see our [Contributing Guide](docs/contribution/) for details.
110+
We welcome contributions! Please see our [Contributing Guide](docs/contribution/) for details on:
111+
112+
- Adding new language server support
113+
- Extending protocol capabilities
114+
- Container image updates
115+
- Development workflow
104116

105117
## License
106118

@@ -110,4 +122,4 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
110122

111123
- Built on the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) specification
112124
- Uses [lsprotocol](https://github.com/microsoft/lsprotocol) for LSP type definitions
113-
- Inspired by [multilspy](https://github.com/microsoft/multilspy) and other LSP clients
125+
- Architecture inspired by [multilspy](https://github.com/microsoft/multilspy) and other LSP clients

0 commit comments

Comments
 (0)