Piston is a REST API service for running language servers in a Docker container.
This is alpha software. Much work remains to be done.
Piston is a language-agnostic API service that provides code intelligence capabilities through language servers. It can automatically detect the programming language of a codebase and provide features like:
- Finding symbol definitions
- Finding references to symbols
- Extracting document symbols
- Parsing and analyzing diff patches
Piston uses tree-sitter for code parsing and analysis, and multilspy for language server protocol integration.
Piston is meant to be run with Docker because its main benefit is easy packing of the needed language server binaries. You can either pull the pre-built Docker image from Docker Hub or build it locally:
- Pull from GitHub Container Registry
docker pull ghcr.io/engines-dev/piston:latest
docker tag ghcr.io/engines-dev/piston:latest piston
- Build Docker image locally (after you have checked out the repository)
docker build --tag piston .
Once the Docker image is ready, run the image with the following command:
docker run --tty --rm --publish 8000:8000 --volume /path/to/workspace:/workspace piston
where /path/to/workspace
is the path to your codebase. Without this volume mount, Piston will use
an example Python project in the container. The curl
command examples below uses the example
project.
WORKSPACE_ROOT
: Path to the workspace directory (default:/workspace
)CODE_LANGUAGE
: Explicitly set the code language (if not set, will be auto-detected)
Piston provides several REST API endpoints:
Find definitions for a symbol at a specific location
curl "localhost:8000/definitions?path=main.py&line=8&character=11"
{
"definitions": [
{
"uri": "file:///workspace/main.py",
"range": {
"start": {
"line": 5,
"character": 4
},
"end": {
"line": 5,
"character": 12
}
},
"absolutePath": "/workspace/main.py",
"relativePath": "main.py"
}
]
}
Find references to a symbol at a specific location
curl "localhost:8000/references?path=utils.py&line=0&character=4"
{
"references": [
{
"uri": "file:///workspace/main.py",
"range": {
"start": {
"line": 0,
"character": 18
},
"end": {
"line": 0,
"character": 25
}
},
"absolutePath": "/workspace/main.py",
"relativePath": "main.py"
},
{
"uri": "file:///workspace/main.py",
"range": {
"start": {
"line": 6,
"character": 7
},
"end": {
"line": 6,
"character": 14
}
},
"absolutePath": "/workspace/main.py",
"relativePath": "main.py"
},
{
"uri": "file:///workspace/utils.py",
"range": {
"start": {
"line": 0,
"character": 4
},
"end": {
"line": 0,
"character": 11
}
},
"absolutePath": "/workspace/utils.py",
"relativePath": "utils.py"
}
]
}
Get all symbols in a file
curl "localhost:8000/symbols?path=utils.py"
{
"symbols": [
{
"name": "is_even",
"kind": "Function",
"range": {
"start": {
"line": 0,
"character": 0
},
"end": {
"line": 2,
"character": 26
}
},
"selectionRange": {
"start": {
"line": 0,
"character": 4
},
"end": {
"line": 0,
"character": 11
}
},
"detail": "def is_even"
},
{
"name": "is_positive",
"kind": "Function",
"range": {
"start": {
"line": 5,
"character": 0
},
"end": {
"line": 7,
"character": 21
}
},
"selectionRange": {
"start": {
"line": 5,
"character": 4
},
"end": {
"line": 5,
"character": 15
}
},
"detail": "def is_positive"
}
]
}
Analyze a diff patch and return a digest of the changes as well as the identifiers in the changed lines.
Given a diff patch such as this one:
diff --git main.py main.py
index 3f9a1e8..dc99c56 100644
--- main.py
+++ main.py
@@ -1,4 +1,4 @@
-from utils import is_even
+from utils import is_even, is_positive
from data import Person
@@ -7,6 +7,8 @@ def greet_person(person):
greeting = f"Hello, {person.name}!"
if is_even(person.age):
greeting += " You have an even age."
+ if is_positive(person.age):
+ greeting += " And you have a positive age. How surprising!"
return greeting
curl \
-X POST \
-H "Content-Type: multipart/form-data" \
-F "patch=@example-workspace/patch.diff" \
"localhost:8000/patch-digest"
{
"digest": [
{
"old_file": "main.py",
"new_file": "main.py",
"changes": [
{
"line": 0,
"text": "from utils import is_even",
"type": "deletion",
"identifiers": [
{
"name": "utils",
"character": 5
},
{
"name": "is_even",
"character": 18
}
]
},
{
"line": 0,
"text": "from utils import is_even, is_positive",
"type": "addition",
"identifiers": [
{
"name": "utils",
"character": 5
},
{
"name": "is_even",
"character": 18
},
{
"name": "is_positive",
"character": 27
}
]
}
]
},
{
"old_file": "main.py",
"new_file": "main.py",
"changes": [
{
"line": 9,
"text": " if is_positive(person.age):",
"type": "addition",
"identifiers": [
{
"name": "is_positive",
"character": 7
},
{
"name": "person",
"character": 19
},
{
"name": "age",
"character": 26
}
]
},
{
"line": 10,
"text": " greeting += \" And you have a positive age. How surprising!\"",
"type": "addition",
"identifiers": [
{
"name": "greeting",
"character": 8
}
]
}
]
}
]
}
Currently, Piston supports:
- Python
Other languages can be added easily but Python is the only one tested so far.
- Docker
- Python 3.13+
-
Clone the repository:
git clone https://github.com/engines-dev/piston.git cd piston
-
Install dependencies:
uv venv source .venv/bin/activate uv pip install -r requirements-dev.txt
-
Run tests:
pytest
-
Run the development server:
docker run \ --interactive --tty --rm \ --publish 8000:8000 \ --volume .:/app \ --volume /path/to/workspace:/workspace \ --entrypoint /bin/bash \ piston -c "fastapi dev --host 0.0.0.0 src/app.py"