0
# Protocol and Message Handling
1
2
Low-level protocol handling for JSON-RPC communication, LSP message processing, custom protocol extensions, and message routing with built-in lifecycle management.
3
4
## Capabilities
5
6
### Language Server Protocol
7
8
Core LSP protocol implementation with built-in handlers for standard LSP methods and extensibility for custom protocols.
9
10
```python { .api }
11
class LanguageServerProtocol(JsonRPCProtocol):
12
"""
13
Language Server Protocol implementation with standard LSP handlers.
14
15
Provides built-in handlers for initialize, shutdown, document lifecycle,
16
and workspace operations with extensible architecture for custom features.
17
"""
18
19
def get_message_handler(self) -> Callable: ...
20
21
def lsp_initialize(self, params: InitializeParams) -> InitializeResult:
22
"""Handle LSP initialize request."""
23
24
def lsp_shutdown(self, params: Any) -> None:
25
"""Handle LSP shutdown request."""
26
27
def lsp_exit(self, params: Any) -> None:
28
"""Handle LSP exit notification."""
29
30
def lsp_text_document_did_open(self, params: DidOpenTextDocumentParams) -> None:
31
"""Handle document open notification."""
32
33
def lsp_text_document_did_change(self, params: DidChangeTextDocumentParams) -> None:
34
"""Handle document change notification."""
35
36
def lsp_text_document_did_close(self, params: DidCloseTextDocumentParams) -> None:
37
"""Handle document close notification."""
38
39
def lsp_workspace_did_change_workspace_folders(self, params: DidChangeWorkspaceFoldersParams) -> None:
40
"""Handle workspace folder changes."""
41
42
def lsp_workspace_execute_command(self, params: ExecuteCommandParams) -> Any:
43
"""Handle command execution requests."""
44
```
45
46
### JSON-RPC Protocol
47
48
Base JSON-RPC protocol implementation for message transport, request/response handling, and connection management.
49
50
```python { .api }
51
class JsonRPCProtocol(asyncio.Protocol):
52
"""
53
Base JSON-RPC protocol for message transport and communication.
54
55
Handles connection lifecycle, message parsing, request routing,
56
and response management for JSON-RPC communication.
57
"""
58
59
def connection_made(self, transport: asyncio.Transport) -> None:
60
"""Called when connection is established."""
61
62
def connection_lost(self, exc: Exception) -> None:
63
"""Called when connection is lost."""
64
65
def data_received(self, data: bytes) -> None:
66
"""Process incoming data and parse JSON-RPC messages."""
67
68
def send_request(self, method: str, params: Any = None) -> Future:
69
"""
70
Send JSON-RPC request and return future for response.
71
72
Parameters:
73
- method: str - RPC method name
74
- params: Any - Method parameters
75
76
Returns:
77
Future that resolves to the response
78
"""
79
80
def send_notification(self, method: str, params: Any = None) -> None:
81
"""
82
Send JSON-RPC notification (no response expected).
83
84
Parameters:
85
- method: str - RPC method name
86
- params: Any - Method parameters
87
"""
88
```
89
90
### Protocol Message Types
91
92
Core message type definitions for JSON-RPC communication with structured request/response handling.
93
94
```python { .api }
95
@attrs.define
96
class JsonRPCRequestMessage:
97
"""JSON-RPC request message structure."""
98
id: Union[int, str]
99
method: str
100
params: Any = None
101
jsonrpc: str = "2.0"
102
103
@attrs.define
104
class JsonRPCResponseMessage:
105
"""JSON-RPC response message structure."""
106
id: Union[int, str]
107
result: Any = None
108
error: Any = None
109
jsonrpc: str = "2.0"
110
111
@attrs.define
112
class JsonRPCNotification:
113
"""JSON-RPC notification message structure."""
114
method: str
115
params: Any = None
116
jsonrpc: str = "2.0"
117
```
118
119
### Protocol Utilities
120
121
Utility functions for protocol configuration, message conversion, and type handling.
122
123
```python { .api }
124
def default_converter() -> Converter:
125
"""
126
Create default cattrs converter with LSP-specific hooks.
127
128
Returns:
129
Configured converter for LSP message serialization/deserialization
130
"""
131
132
def _dict_to_object(d: Any) -> Any:
133
"""Convert dictionary to nested object structure."""
134
135
def _params_field_structure_hook(obj: Dict, cls: Type) -> Any:
136
"""Structure hook for handling params field in messages."""
137
138
def _result_field_structure_hook(obj: Dict, cls: Type) -> Any:
139
"""Structure hook for handling result field in messages."""
140
```
141
142
### Server Capabilities
143
144
System for building and managing server capability declarations for LSP initialization.
145
146
```python { .api }
147
class ServerCapabilitiesBuilder:
148
"""
149
Builder for constructing server capabilities during initialization.
150
151
Automatically configures capabilities based on registered features
152
and provides manual capability configuration for advanced use cases.
153
"""
154
155
# Capability configuration methods for various LSP features
156
# (specific methods depend on LSP specification)
157
```
158
159
## Usage Examples
160
161
### Custom Protocol Extension
162
163
```python
164
from pygls.protocol import LanguageServerProtocol
165
from pygls.server import LanguageServer
166
167
class CustomProtocol(LanguageServerProtocol):
168
def __init__(self, server, converter):
169
super().__init__(server, converter)
170
self.custom_state = {}
171
172
def lsp_initialize(self, params):
173
# Call parent initialization
174
result = super().lsp_initialize(params)
175
176
# Add custom initialization logic
177
self.custom_state['client_name'] = params.client_info.name if params.client_info else "Unknown"
178
179
# Extend server capabilities
180
result.capabilities.experimental = {
181
"customFeature": True,
182
"version": "1.0.0"
183
}
184
185
return result
186
187
# Add custom message handler
188
@lsp_method("custom/specialRequest")
189
def handle_special_request(self, params):
190
return {
191
"result": "Custom protocol handled",
192
"client": self.custom_state.get('client_name')
193
}
194
195
# Use custom protocol
196
server = LanguageServer(
197
"custom-server",
198
"1.0.0",
199
protocol_cls=CustomProtocol
200
)
201
```
202
203
### Manual Message Sending
204
205
```python
206
from pygls.server import LanguageServer
207
from lsprotocol.types import MessageType
208
209
server = LanguageServer("message-sender", "1.0.0")
210
211
@server.feature(TEXT_DOCUMENT_DID_OPEN)
212
def on_open(params):
213
# Send notification to client
214
server.lsp.send_notification(
215
"window/logMessage",
216
{
217
"type": MessageType.Info,
218
"message": f"Opened document: {params.text_document.uri}"
219
}
220
)
221
222
@server.command("myServer.requestConfiguration")
223
async def request_config(params):
224
# Send request and wait for response
225
try:
226
config_response = await server.lsp.send_request(
227
"workspace/configuration",
228
{
229
"items": [
230
{"section": "myServer.formatting"},
231
{"section": "myServer.linting"}
232
]
233
}
234
)
235
236
return {"configuration": config_response}
237
238
except Exception as e:
239
return {"error": str(e)}
240
```
241
242
### Error Handling and Exceptions
243
244
```python
245
from pygls.exceptions import (
246
JsonRpcException,
247
JsonRpcInternalError,
248
FeatureRequestError
249
)
250
251
class RobustProtocol(LanguageServerProtocol):
252
def lsp_text_document_did_change(self, params):
253
try:
254
# Call parent handler
255
super().lsp_text_document_did_change(params)
256
257
# Custom change processing
258
document = self.workspace.get_document(params.text_document.uri)
259
self.validate_document(document)
260
261
except Exception as e:
262
# Log error but don't propagate to avoid breaking protocol
263
self.server.logger.error(f"Error processing document change: {e}")
264
265
def validate_document(self, document):
266
# Custom validation that might raise exceptions
267
if len(document.source) > 1000000:
268
raise JsonRpcInternalError("Document too large")
269
270
@server.feature(TEXT_DOCUMENT_HOVER)
271
def safe_hover(params):
272
try:
273
# Hover implementation
274
result = generate_hover_content(params)
275
return result
276
277
except FileNotFoundError:
278
# Return None for no hover content
279
return None
280
281
except Exception as e:
282
# Convert to LSP error
283
raise FeatureRequestError(f"Hover failed: {str(e)}")
284
```
285
286
### Connection Monitoring
287
288
```python
289
class MonitoredProtocol(LanguageServerProtocol):
290
def connection_made(self, transport):
291
super().connection_made(transport)
292
self.server.logger.info("Client connected")
293
294
# Setup connection monitoring
295
self.connection_start_time = time.time()
296
self.message_count = 0
297
298
def connection_lost(self, exc):
299
duration = time.time() - self.connection_start_time
300
self.server.logger.info(
301
f"Client disconnected after {duration:.2f}s, "
302
f"processed {self.message_count} messages"
303
)
304
305
super().connection_lost(exc)
306
307
def data_received(self, data):
308
self.message_count += 1
309
super().data_received(data)
310
```
311
312
### Custom Message Converter
313
314
```python
315
from pygls.protocol import default_converter
316
import cattrs
317
318
def create_custom_converter():
319
converter = default_converter()
320
321
# Add custom type conversion
322
converter.register_structure_hook(
323
MyCustomType,
324
lambda obj, cls: MyCustomType(**obj)
325
)
326
327
converter.register_unstructure_hook(
328
MyCustomType,
329
lambda obj: {"custom_field": obj.value}
330
)
331
332
return converter
333
334
# Use custom converter
335
server = LanguageServer(
336
"custom-converter-server",
337
"1.0.0",
338
converter_factory=create_custom_converter
339
)
340
```