Skip to content

Commit 6b436d6

Browse files
Add minimal param LSP server example
1 parent 3c0fce6 commit 6b436d6

8 files changed

Lines changed: 1066 additions & 0 deletions

File tree

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,3 +387,9 @@ $RECYCLE.BIN/
387387
.github/skills/hvplot.md
388388
.github/skills/holoviews.md
389389
.github/skills/panel-material-ui.md
390+
*node_modules/
391+
392+
# param-lsp example extension
393+
examples/param-lsp/vscode-client/node_modules/
394+
examples/param-lsp/vscode-client/package-lock.json
395+
examples/param-lsp/vscode-client/*.vsix

.pre-commit-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ repos:
5858
rev: v9.13.0
5959
hooks:
6060
- id: eslint
61+
files: ^holoviz_mcp/.*\.(js|ts)$
6162
args: ['-c', 'holoviz_mcp/.eslintrc.js', 'holoviz_mcp/*.ts', 'holoviz_mcp/models/**/*.ts', '--fix']
6263
additional_dependencies:
6364
- 'eslint@8.57.0'

examples/param-lsp/README.md

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
# Minimal LSP Server for param
2+
3+
A minimal Language Server Protocol (LSP) implementation that provides IDE features for `param.Parameterized` classes.
4+
5+
## Current Status
6+
7+
This is a **proof-of-concept** LSP server demonstrating what's possible for param tooling. It uses simple regex-based parsing (not a full Python AST parser) and only analyzes the current file.
8+
9+
### Features
10+
11+
| Feature | Status | Description |
12+
|---------|--------|-------------|
13+
| **Hover (declarations)** || Hover over `param.Integer(...)` to see type info |
14+
| **Hover (attribute access)** || Hover over `person.name` to see param info (same file only) |
15+
| **Completion (param types)** || Type `param.` to see all param types |
16+
| **Completion (kwargs)** || Inside `param.Number(...)`, get suggestions for `bounds`, `doc`, etc. |
17+
| **Completion (instantiation)** || Type `Person(` to see parameter names |
18+
| **Diagnostics** || Warnings for unknown types, missing `objects` in Selectors |
19+
| **Cross-file resolution** || Only works within the current file |
20+
| **Type checking** || Does not replace Pylance/Pyright |
21+
22+
### Limitations
23+
24+
- **Regex-based parsing**: May fail on complex multi-line declarations
25+
- **Same-file only**: Cannot resolve classes or variables from imports
26+
- **No inheritance tracking**: Doesn't resolve inherited parameters from parent classes in other files
27+
- **Runs alongside Pylance**: Pylance may still show type errors (see [Pylance Coexistence](#pylance-coexistence))
28+
29+
## Installation
30+
31+
### Prerequisites
32+
33+
```bash
34+
pip install pygls lsprotocol param
35+
```
36+
37+
### Quick Test
38+
39+
Run the tests to verify the server works:
40+
41+
```bash
42+
cd examples/param-lsp
43+
python test_param_lsp.py
44+
```
45+
46+
Expected output:
47+
```
48+
✓ test_parse_param_args_with_kwargs
49+
✓ test_parse_param_args_with_positional
50+
...
51+
PASSED: All 11 tests passed!
52+
```
53+
54+
## Building the VS Code Extension
55+
56+
The `vscode-client/` folder contains a minimal VS Code extension that launches the LSP server.
57+
58+
### Build Steps
59+
60+
```bash
61+
cd examples/param-lsp/vscode-client
62+
63+
# Install dependencies
64+
npm install
65+
66+
# Package as VSIX
67+
npx vsce package --allow-missing-repository -o param-lsp.vsix
68+
```
69+
70+
This creates `param-lsp.vsix` (~32 MB).
71+
72+
> **Note**: The extension uses an absolute path to `param_lsp.py`. If you move the files, update the path in `extension.js`.
73+
74+
## Installing the Extension
75+
76+
### VS Code (Desktop)
77+
78+
```bash
79+
# Install
80+
code --install-extension examples/param-lsp/vscode-client/param-lsp.vsix
81+
82+
# Or for development (hot reload)
83+
code --extensionDevelopmentPath=/full/path/to/examples/param-lsp/vscode-client
84+
```
85+
86+
### code-server (Web)
87+
88+
```bash
89+
# Install
90+
code-server --install-extension examples/param-lsp/vscode-client/param-lsp.vsix
91+
92+
# Force reinstall (after updates)
93+
code-server --install-extension examples/param-lsp/vscode-client/param-lsp.vsix --force
94+
```
95+
96+
After installation, **reload the window**:
97+
- Press `Ctrl+Shift+P`
98+
- Type "Reload Window"
99+
- Press Enter
100+
101+
### Verify Installation
102+
103+
1. Open a Python file with `param.Parameterized` classes
104+
2. Open the Output panel: `Ctrl+Shift+U` (or View → Output)
105+
3. Select "Param LSP" from the dropdown
106+
4. You should see: `Starting Param LSP server...`
107+
108+
## Uninstalling the Extension
109+
110+
### VS Code (Desktop)
111+
112+
```bash
113+
code --uninstall-extension undefined_publisher.param-lsp-client
114+
```
115+
116+
### code-server (Web)
117+
118+
```bash
119+
code-server --uninstall-extension undefined_publisher.param-lsp-client
120+
```
121+
122+
### Via UI
123+
124+
1. Open Extensions panel: `Ctrl+Shift+X`
125+
2. Search for "Param LSP"
126+
3. Click the gear icon → Uninstall
127+
128+
## Usage
129+
130+
Open `example_parameterized.py` and try:
131+
132+
1. **Hover** over `param.Integer` → see type info and kwargs
133+
2. **Hover** over `person.name` → see the parameter's type and metadata
134+
3. **Type** `param.` → see completion for all param types
135+
4. **Inside** `param.Number(`, press `Ctrl+Space` → see kwargs like `bounds`, `doc`
136+
5. **Type** `Person(` → see completion for `name`, `age`, `height`, etc.
137+
138+
## Pylance Coexistence
139+
140+
This LSP runs **alongside** Pylance (VS Code's Python language server). They don't share information:
141+
142+
```
143+
┌─────────────────┐ ┌─────────────────┐
144+
│ Pylance │ │ param-lsp │
145+
│ "name unknown" │ │ "name: str" │
146+
└────────┬────────┘ └────────┬────────┘
147+
│ │
148+
└───────────┬───────────┘
149+
150+
Both show in VS Code
151+
```
152+
153+
**Pylance may still show errors** like "Property 'name' not defined" because it doesn't understand param descriptors.
154+
155+
### Workarounds
156+
157+
1. **Add type annotations** (recommended):
158+
```python
159+
name: str = param.String(default="Alice")
160+
```
161+
162+
2. **Disable Pylance for specific files**:
163+
```json
164+
// .vscode/settings.json
165+
{
166+
"python.analysis.ignore": ["**/my_parameterized_file.py"]
167+
}
168+
```
169+
170+
3. **Create type stubs** (`param.pyi`) - more complex but fully fixes the issue
171+
172+
## Architecture
173+
174+
```
175+
examples/param-lsp/
176+
├── param_lsp.py # LSP server (Python)
177+
│ ├── PARAM_TYPE_INFO # Map: param.Type → Python type
178+
│ ├── hover() # textDocument/hover handler
179+
│ ├── completion() # textDocument/completion handler
180+
│ └── analyze_document() # Diagnostics
181+
├── test_param_lsp.py # Unit tests
182+
├── example_parameterized.py # Example file to test with
183+
├── README.md # This file
184+
└── vscode-client/ # VS Code extension
185+
├── package.json # Extension manifest
186+
├── extension.js # Extension entry point
187+
└── node_modules/ # Dependencies
188+
```
189+
190+
## Development
191+
192+
### Running the Server Standalone
193+
194+
For debugging, run the server directly:
195+
196+
```bash
197+
python param_lsp.py
198+
```
199+
200+
The server communicates via stdio using JSON-RPC. You can test with:
201+
202+
```json
203+
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"capabilities":{}}}
204+
```
205+
206+
### Modifying the Server
207+
208+
1. Edit `param_lsp.py`
209+
2. No need to rebuild the extension - it reads the Python file directly
210+
3. Reload VS Code window to pick up changes
211+
212+
### Adding New Param Types
213+
214+
Edit `PARAM_TYPE_INFO` in `param_lsp.py`:
215+
216+
```python
217+
PARAM_TYPE_INFO = {
218+
"param.MyNewType": ("my_python_type", "Description of the type"),
219+
# ...
220+
}
221+
```
222+
223+
## Future Improvements
224+
225+
To make this production-ready:
226+
227+
1. **Use AST parsing** instead of regex for robust detection
228+
2. **Cross-file resolution** via workspace indexing
229+
3. **Runtime introspection** to get actual Parameter metadata
230+
4. **Mypy/Pyright plugin** to provide types to static checkers
231+
5. **Type stubs generation** for param library
232+
233+
## Related Resources
234+
235+
- [pygls](https://pygls.readthedocs.io/) - Python Language Server library
236+
- [param](https://param.holoviz.org/) - Parameter library for Python
237+
- [LSP Specification](https://microsoft.github.io/language-server-protocol/) - Protocol documentation
238+
- [vscode-languageclient](https://github.com/microsoft/vscode-languageserver-node) - VS Code LSP client
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
"""
2+
Example Parameterized classes for testing the param-lsp server.
3+
4+
Open this file in VS Code with the param-lsp extension active to see:
5+
- Hover documentation for param declarations
6+
- Autocompletion for param types after "param."
7+
- Autocompletion for kwargs inside param.Type(...)
8+
- Autocompletion for parameter names when instantiating classes
9+
- Diagnostics for unknown types and missing required kwargs
10+
"""
11+
12+
import param
13+
14+
15+
class Person(param.Parameterized):
16+
"""A person with various attributes."""
17+
18+
name = param.String(default="Anonymous", doc="The person's name")
19+
age = param.Integer(default=0, bounds=(0, 150), doc="Age in years")
20+
height = param.Number(default=1.7, bounds=(0.0, 3.0), doc="Height in meters")
21+
is_active = param.Boolean(default=True, doc="Whether the person is active")
22+
email = param.String(default="", regex=r"^[\w\.-]+@[\w\.-]+\.\w+$", doc="Email address")
23+
24+
25+
class Employee(Person):
26+
"""An employee extending Person."""
27+
28+
employee_id = param.String(doc="Unique employee identifier")
29+
department = param.Selector(
30+
objects=["Engineering", "Sales", "Marketing", "HR"],
31+
default="Engineering",
32+
doc="Department the employee belongs to",
33+
)
34+
salary = param.Number(default=50000, bounds=(0, None), doc="Annual salary")
35+
skills = param.List(default=[], item_type=str, doc="List of skills")
36+
37+
38+
class Project(param.Parameterized):
39+
"""A project with team members."""
40+
41+
name = param.String(default="Untitled", doc="Project name")
42+
lead = param.ClassSelector(class_=Employee, doc="Project lead")
43+
team = param.List(default=[], item_type=Employee, doc="Team members")
44+
budget = param.Number(default=0, bounds=(0, None), doc="Project budget")
45+
status = param.Selector(
46+
objects=["Planning", "In Progress", "Review", "Complete"],
47+
default="Planning",
48+
doc="Current project status",
49+
)
50+
start_date = param.Date(doc="Project start date")
51+
config = param.Dict(default={}, doc="Additional configuration")
52+
53+
54+
class DataProcessor(param.Parameterized):
55+
"""A data processing pipeline."""
56+
57+
input_path = param.Path(doc="Input file or folder path")
58+
output_path = param.Filename(doc="Output filename")
59+
batch_size = param.Integer(default=100, bounds=(1, 10000), doc="Processing batch size")
60+
threshold = param.Number(default=0.5, bounds=(0.0, 1.0), doc="Decision threshold")
61+
on_complete = param.Callable(doc="Callback when processing is complete")
62+
verbose = param.Boolean(default=False, doc="Enable verbose logging")
63+
64+
65+
# Example instantiation - the LSP should provide completion for parameter names
66+
if __name__ == "__main__":
67+
# Try typing inside the parentheses to see completions
68+
person = Person(
69+
name="Alice",
70+
age=30,
71+
)
72+
73+
employee = Employee(
74+
name="Bob",
75+
department="Engineering",
76+
skills=["Python", "Data Science"],
77+
)
78+
79+
print(f"Person: {person.name}, {person.age} years old", person.email)
80+
print(f"Employee: {employee.name}, {employee.department}")
81+
82+
Person()

0 commit comments

Comments
 (0)