0
# Utilities and Helpers
1
2
URI handling, position encoding, exception management, and other utility functions for language server development with comprehensive support for cross-platform operations and error handling.
3
4
## Capabilities
5
6
### Position Encoding Utilities
7
8
Comprehensive position encoding system supporting different character encoding methods with backward compatibility for deprecated functions.
9
10
```python { .api }
11
class PositionCodec:
12
"""
13
Position encoding codec for character unit conversion.
14
15
Handles conversion between different character encoding methods
16
(UTF-8, UTF-16) for LSP position calculations.
17
"""
18
19
@classmethod
20
def create_encoding(cls, encoding: str = "utf-16") -> 'PositionCodec':
21
"""
22
Create codec for specific encoding.
23
24
Parameters:
25
- encoding: str - Encoding type ('utf-8', 'utf-16')
26
27
Returns:
28
PositionCodec instance
29
"""
30
31
def utf16_unit_offset(self, chars: str) -> int:
32
"""
33
Calculate UTF-16 unit offset.
34
35
Parameters:
36
- chars: str - Character string
37
38
Returns:
39
int - UTF-16 unit offset
40
"""
41
42
def client_num_units(self, chars: str) -> int:
43
"""
44
Get number of client character units.
45
46
Parameters:
47
- chars: str - Character string
48
49
Returns:
50
int - Number of client units based on encoding
51
"""
52
53
# Deprecated utility functions (maintained for backward compatibility)
54
def utf16_unit_offset(chars: str) -> int:
55
"""
56
DEPRECATED: Use PositionCodec.utf16_unit_offset instead.
57
Calculate UTF-16 unit offset for character string.
58
"""
59
60
def utf16_num_units(chars: str) -> int:
61
"""
62
DEPRECATED: Use PositionCodec.client_num_units instead.
63
Get number of UTF-16 units in character string.
64
"""
65
66
def position_from_utf16(lines: List[str], position: Position) -> Position:
67
"""
68
DEPRECATED: Use PositionCodec.position_from_client_units instead.
69
Convert position from UTF-16 units to server units.
70
"""
71
72
def position_to_utf16(lines: List[str], position: Position) -> Position:
73
"""
74
DEPRECATED: Use PositionCodec.position_to_client_units instead.
75
Convert position from server units to UTF-16 units.
76
"""
77
78
def range_from_utf16(lines: List[str], range: Range) -> Range:
79
"""
80
DEPRECATED: Use PositionCodec.range_from_client_units instead.
81
Convert range from UTF-16 units to server units.
82
"""
83
84
def range_to_utf16(lines: List[str], range: Range) -> Range:
85
"""
86
DEPRECATED: Use PositionCodec.range_to_client_units instead.
87
Convert range from server units to UTF-16 units.
88
"""
89
```
90
91
### Constants and Configuration
92
93
Configuration constants and attribute definitions for internal pygls operations and feature management.
94
95
```python { .api }
96
# Constants from pygls.constants module
97
98
# Dynamically assigned attributes for feature management
99
ATTR_EXECUTE_IN_THREAD: str = "execute_in_thread"
100
ATTR_COMMAND_TYPE: str = "command"
101
ATTR_FEATURE_TYPE: str = "feature"
102
ATTR_REGISTERED_NAME: str = "reg_name"
103
ATTR_REGISTERED_TYPE: str = "reg_type"
104
105
# Parameters for server operations
106
PARAM_LS: str = "ls"
107
```
108
109
### Platform Detection
110
111
Platform and environment detection utilities for cross-platform compatibility and runtime environment adaptation.
112
113
```python { .api }
114
# Platform detection from pygls module
115
116
IS_WIN: bool = ...
117
"""True if running on Windows platform."""
118
119
IS_PYODIDE: bool = ...
120
"""True if running in Pyodide environment (browser Python)."""
121
```
122
123
### LSP Type Utilities
124
125
Utility functions for working with LSP method types, parameter validation, and capability management.
126
127
```python { .api }
128
# Type utilities from pygls.lsp module
129
130
def get_method_params_type(
131
method_name: str,
132
lsp_methods_map: dict = METHOD_TO_TYPES
133
) -> Optional[Type]:
134
"""
135
Get parameter type for LSP method.
136
137
Parameters:
138
- method_name: str - LSP method name
139
- lsp_methods_map: dict - Method to types mapping
140
141
Returns:
142
Type for method parameters or None
143
144
Raises:
145
MethodTypeNotRegisteredError if method not found
146
"""
147
148
def get_method_return_type(
149
method_name: str,
150
lsp_methods_map: dict = METHOD_TO_TYPES
151
) -> Optional[Type]:
152
"""
153
Get return type for LSP method.
154
155
Parameters:
156
- method_name: str - LSP method name
157
- lsp_methods_map: dict - Method to types mapping
158
159
Returns:
160
Type for method return value or None
161
162
Raises:
163
MethodTypeNotRegisteredError if method not found
164
"""
165
166
def get_method_options_type(
167
method_name: str,
168
lsp_options_map: dict = METHOD_TO_OPTIONS,
169
lsp_methods_map: dict = METHOD_TO_TYPES
170
) -> Optional[Type]:
171
"""
172
Get options type for LSP method capabilities.
173
174
Parameters:
175
- method_name: str - LSP method name
176
- lsp_options_map: dict - Method to options mapping
177
- lsp_methods_map: dict - Method to types mapping
178
179
Returns:
180
Type for method options or None
181
182
Raises:
183
MethodTypeNotRegisteredError if method not found
184
"""
185
186
def get_method_registration_options_type(
187
method_name: str,
188
lsp_methods_map: dict = METHOD_TO_TYPES
189
) -> Optional[Type]:
190
"""
191
Get registration options type for LSP method.
192
193
Parameters:
194
- method_name: str - LSP method name
195
- lsp_methods_map: dict - Method to types mapping
196
197
Returns:
198
Type for method registration options or None
199
200
Raises:
201
MethodTypeNotRegisteredError if method not found
202
"""
203
204
def is_instance(cv: Converter, obj: Any, type_cls: Type) -> bool:
205
"""
206
Check if object is instance of type using cattrs converter.
207
208
Parameters:
209
- cv: Converter - cattrs converter instance
210
- obj: Any - Object to check
211
- type_cls: Type - Type to check against
212
213
Returns:
214
bool - True if object matches type
215
"""
216
```
217
218
## Usage Examples
219
220
### Position Encoding
221
222
```python
223
from pygls.workspace import PositionCodec
224
from lsprotocol.types import Position, Range
225
226
# Create position codec
227
codec = PositionCodec.create_encoding("utf-16")
228
229
# Document with Unicode characters
230
lines = [
231
"def hello():",
232
" print('Hello 世界')", # Contains non-ASCII characters
233
" return True"
234
]
235
236
# Position at the '世' character
237
client_position = Position(line=1, character=12)
238
239
# Convert from client units to server units
240
server_position = codec.position_from_client_units(lines, client_position)
241
print(f"Client pos: {client_position}")
242
print(f"Server pos: {server_position}")
243
244
# Convert back to client units
245
converted_back = codec.position_to_client_units(lines, server_position)
246
print(f"Converted back: {converted_back}")
247
248
# Handle ranges
249
client_range = Range(
250
start=Position(line=1, character=10),
251
end=Position(line=1, character=15)
252
)
253
254
server_range = codec.range_from_client_units(lines, client_range)
255
print(f"Client range: {client_range}")
256
print(f"Server range: {server_range}")
257
258
# Calculate character units
259
text = "Hello 世界"
260
utf16_units = codec.client_num_units(text)
261
print(f"UTF-16 units for '{text}': {utf16_units}")
262
```
263
264
### Exception Handling
265
266
```python
267
from pygls.exceptions import (
268
JsonRpcException,
269
JsonRpcMethodNotFound,
270
FeatureRequestError,
271
PyglsError
272
)
273
274
@server.feature(TEXT_DOCUMENT_HOVER)
275
def hover_with_error_handling(params):
276
try:
277
document = server.workspace.get_document(params.text_document.uri)
278
279
# Simulate potential errors
280
if not document.source.strip():
281
raise FeatureRequestError("Document is empty")
282
283
# Generate hover content
284
position = params.position
285
if position.line >= len(document.lines):
286
raise JsonRpcException.invalid_params()
287
288
word = document.word_at_position(position)
289
if not word:
290
return None # No hover content
291
292
return Hover(contents=f"Hover for: {word}")
293
294
except KeyError:
295
# Document not found
296
raise JsonRpcMethodNotFound()
297
298
except FeatureRequestError:
299
# Re-raise feature errors
300
raise
301
302
except Exception as e:
303
# Convert unexpected errors
304
raise JsonRpcException.internal_error(str(e))
305
306
# Custom error handling
307
class CustomServerError(PyglsError):
308
def __init__(self, message: str, error_code: int = -32000):
309
super().__init__(message)
310
self.error_code = error_code
311
312
@server.command("myServer.riskyOperation")
313
def risky_operation(params):
314
try:
315
# Risky operation here
316
result = perform_risky_operation(params)
317
return result
318
319
except CustomServerError as e:
320
# Convert to JSON-RPC error
321
raise JsonRpcServerError(str(e), e.error_code)
322
323
except FileNotFoundError:
324
raise JsonRpcException.invalid_params("Required file not found")
325
326
except PermissionError:
327
raise JsonRpcServerError("Insufficient permissions", -32001)
328
```
329
330
### Type Validation and Method Utilities
331
332
```python
333
from pygls.lsp import (
334
get_method_params_type,
335
get_method_return_type,
336
get_method_options_type
337
)
338
from lsprotocol.types import TEXT_DOCUMENT_COMPLETION
339
340
# Get type information for LSP methods
341
params_type = get_method_params_type(TEXT_DOCUMENT_COMPLETION)
342
print(f"Completion params type: {params_type}")
343
344
return_type = get_method_return_type(TEXT_DOCUMENT_COMPLETION)
345
print(f"Completion return type: {return_type}")
346
347
options_type = get_method_options_type(TEXT_DOCUMENT_COMPLETION)
348
print(f"Completion options type: {options_type}")
349
350
# Validate parameters using type information
351
def validate_completion_params(params):
352
expected_type = get_method_params_type(TEXT_DOCUMENT_COMPLETION)
353
354
# Use cattrs to validate structure
355
try:
356
converter = default_converter()
357
converter.structure(params, expected_type)
358
return True
359
except Exception as e:
360
print(f"Invalid parameters: {e}")
361
return False
362
363
# Dynamic feature registration with type checking
364
def register_feature_with_validation(server, method_name, handler):
365
try:
366
# Check if method type is registered
367
params_type = get_method_params_type(method_name)
368
return_type = get_method_return_type(method_name)
369
370
print(f"Registering {method_name}")
371
print(f" Params: {params_type}")
372
print(f" Returns: {return_type}")
373
374
# Register the feature
375
server.feature(method_name)(handler)
376
377
except MethodTypeNotRegisteredError:
378
print(f"Unknown method: {method_name}")
379
```
380
381
### Platform-Specific Operations
382
383
```python
384
from pygls import IS_WIN, IS_PYODIDE
385
import os
386
387
def get_platform_config():
388
"""Get platform-specific configuration."""
389
config = {
390
"line_ending": "\r\n" if IS_WIN else "\n",
391
"path_separator": "\\" if IS_WIN else "/",
392
"supports_symlinks": not IS_WIN,
393
"is_browser": IS_PYODIDE
394
}
395
396
if IS_PYODIDE:
397
# Browser environment limitations
398
config.update({
399
"supports_file_system": False,
400
"supports_threads": False,
401
"max_file_size": 1024 * 1024 # 1MB limit
402
})
403
404
return config
405
406
@server.command("myServer.getSystemInfo")
407
def get_system_info(params):
408
"""Return system information."""
409
config = get_platform_config()
410
411
return {
412
"platform": "windows" if IS_WIN else "unix",
413
"environment": "pyodide" if IS_PYODIDE else "native",
414
"python_version": sys.version,
415
"config": config
416
}
417
418
# Platform-specific file operations
419
def safe_file_operation(file_path: str, operation: str):
420
"""Perform file operation with platform considerations."""
421
422
if IS_PYODIDE:
423
return {"error": "File operations not supported in browser"}
424
425
try:
426
if operation == "read":
427
with open(file_path, 'r', encoding='utf-8') as f:
428
content = f.read()
429
return {"content": content}
430
431
elif operation == "exists":
432
exists = os.path.exists(file_path)
433
return {"exists": exists}
434
435
except PermissionError:
436
return {"error": "Permission denied"}
437
except FileNotFoundError:
438
return {"error": "File not found"}
439
except Exception as e:
440
return {"error": str(e)}
441
```