0
# Utilities and Helpers
1
2
Comprehensive utility functions for LSP operations, text manipulation, URI handling, Python-specific operations, and code formatting. Provides essential helper functions for plugin development and server operations.
3
4
## Capabilities
5
6
### Decorators and Function Control
7
8
Utility decorators for controlling function execution timing and frequency.
9
10
```python { .api }
11
def debounce(interval_s, keyed_by=None):
12
"""
13
Debounce function calls until interval_s seconds have passed.
14
15
Parameters:
16
- interval_s: float, debounce interval in seconds
17
- keyed_by: str, parameter name to key debouncing by
18
19
Returns:
20
decorator: Function decorator
21
"""
22
23
def throttle(seconds=1):
24
"""
25
Throttle function calls to at most once per interval.
26
27
Parameters:
28
- seconds: float, minimum interval between calls
29
30
Returns:
31
decorator: Function decorator
32
"""
33
```
34
35
### File and Path Utilities
36
37
Functions for finding and manipulating file paths.
38
39
```python { .api }
40
def find_parents(root, path, names):
41
"""
42
Find files matching names in parent directories.
43
44
Parameters:
45
- root: str, root directory to stop searching at
46
- path: str, starting path for search
47
- names: list, file/directory names to find
48
49
Returns:
50
list: Found file paths
51
"""
52
53
def path_to_dot_name(path):
54
"""
55
Convert filesystem path to Python dot notation.
56
57
Parameters:
58
- path: str, filesystem path to Python file
59
60
Returns:
61
str: Python module path in dot notation
62
"""
63
64
def match_uri_to_workspace(uri, workspaces):
65
"""
66
Match URI to appropriate workspace.
67
68
Parameters:
69
- uri: str, document URI
70
- workspaces: list, available Workspace instances
71
72
Returns:
73
Workspace: Best matching workspace
74
"""
75
```
76
77
### Data Manipulation Utilities
78
79
Functions for working with data structures and strings.
80
81
```python { .api }
82
def list_to_string(value):
83
"""
84
Convert list to comma-separated string.
85
86
Parameters:
87
- value: list or str, value to convert
88
89
Returns:
90
str: Comma-separated string
91
"""
92
93
def merge_dicts(dict_a, dict_b):
94
"""
95
Recursively merge dictionaries.
96
97
Parameters:
98
- dict_a: dict, base dictionary
99
- dict_b: dict, dictionary to merge in
100
101
Returns:
102
dict: Merged dictionary
103
"""
104
```
105
106
### Text and Content Formatting
107
108
Functions for formatting text content for LSP clients.
109
110
```python { .api }
111
def escape_plain_text(contents):
112
"""
113
Escape text for plain text display.
114
115
Parameters:
116
- contents: str, text to escape
117
118
Returns:
119
str: Escaped text
120
"""
121
122
def escape_markdown(contents):
123
"""
124
Escape text for Markdown display.
125
126
Parameters:
127
- contents: str, text to escape
128
129
Returns:
130
str: Escaped Markdown text
131
"""
132
133
def wrap_signature(signature):
134
"""
135
Wrap function signature in code block.
136
137
Parameters:
138
- signature: str, function signature
139
140
Returns:
141
str: Wrapped signature
142
"""
143
144
def choose_markup_kind(client_supported_markup_kinds):
145
"""
146
Choose best supported markup kind for client.
147
148
Parameters:
149
- client_supported_markup_kinds: list, client-supported markup kinds
150
151
Returns:
152
str: Best markup kind to use
153
"""
154
155
def format_docstring(contents, markup_kind, signatures=None, signature_config=None):
156
"""
157
Format docstring as LSP MarkupContent.
158
159
Parameters:
160
- contents: str, docstring content
161
- markup_kind: str, markup kind ("plaintext" or "markdown")
162
- signatures: list, optional function signatures
163
- signature_config: dict, signature formatting options
164
165
Returns:
166
dict: LSP MarkupContent with 'kind' and 'value'
167
"""
168
```
169
170
### Position and Text Manipulation
171
172
Functions for working with LSP positions and text coordinates.
173
174
```python { .api }
175
def clip_column(column, lines, line_number):
176
"""
177
Normalize column position within line bounds.
178
179
Parameters:
180
- column: int, column position
181
- lines: list, document lines
182
- line_number: int, line number (0-based)
183
184
Returns:
185
int: Clipped column position
186
"""
187
188
def position_to_jedi_linecolumn(document, position):
189
"""
190
Convert LSP position to Jedi line/column format.
191
192
Parameters:
193
- document: Document, document instance
194
- position: dict, LSP position with 'line' and 'character'
195
196
Returns:
197
tuple: (line, column) for Jedi (1-based line, 0-based column)
198
"""
199
200
def get_eol_chars(text):
201
"""
202
Get end-of-line characters used in text.
203
204
Parameters:
205
- text: str, text to analyze
206
207
Returns:
208
str: EOL characters ("\r\n", "\r", or "\n") or None if none found
209
"""
210
211
def format_signature(signature, config, signature_formatter):
212
"""
213
Format function signature using ruff or black formatter.
214
215
Parameters:
216
- signature: str, function signature to format
217
- config: dict, formatting configuration with line_length
218
- signature_formatter: str, formatter name ("ruff" or "black")
219
220
Returns:
221
str: Formatted signature
222
"""
223
224
def convert_signatures_to_markdown(signatures, config):
225
"""
226
Convert list of signatures to markdown code block.
227
228
Parameters:
229
- signatures: list, function signatures
230
- config: dict, formatting configuration
231
232
Returns:
233
str: Markdown-formatted signatures
234
"""
235
```
236
237
### Process Utilities
238
239
Functions for process management and monitoring.
240
241
```python { .api }
242
def is_process_alive(pid):
243
"""
244
Check if process is still alive.
245
246
Parameters:
247
- pid: int, process ID
248
249
Returns:
250
bool: True if process is alive
251
"""
252
```
253
254
### URI Utilities
255
256
Functions for working with URIs and filesystem paths.
257
258
```python { .api }
259
def urlparse(uri):
260
"""
261
Parse and decode URI parts.
262
263
Parameters:
264
- uri: str, URI to parse
265
266
Returns:
267
tuple: Parsed URI components (scheme, netloc, path, params, query, fragment)
268
"""
269
270
def urlunparse(parts):
271
"""
272
Unparse and encode URI parts.
273
274
Parameters:
275
- parts: tuple, URI components
276
277
Returns:
278
str: Assembled URI
279
"""
280
281
def to_fs_path(uri):
282
"""
283
Convert URI to filesystem path.
284
285
Parameters:
286
- uri: str, file URI
287
288
Returns:
289
str: Filesystem path
290
"""
291
292
def from_fs_path(path):
293
"""
294
Convert filesystem path to URI.
295
296
Parameters:
297
- path: str, filesystem path
298
299
Returns:
300
str: File URI
301
"""
302
303
def uri_with(uri, **parts):
304
"""
305
Return URI with specified parts replaced.
306
307
Parameters:
308
- uri: str, base URI
309
- **parts: URI components to replace
310
311
Returns:
312
str: Modified URI
313
"""
314
```
315
316
### Text Edit Utilities
317
318
Functions for manipulating and applying text edits.
319
320
```python { .api }
321
def get_well_formatted_range(lsp_range):
322
"""
323
Normalize LSP range format.
324
325
Parameters:
326
- lsp_range: dict, LSP range with 'start' and 'end'
327
328
Returns:
329
dict: Normalized range
330
"""
331
332
def get_well_formatted_edit(text_edit):
333
"""
334
Normalize LSP text edit format.
335
336
Parameters:
337
- text_edit: dict, LSP TextEdit
338
339
Returns:
340
dict: Normalized text edit
341
"""
342
343
def compare_text_edits(a, b):
344
"""
345
Compare text edits for sorting.
346
347
Parameters:
348
- a: dict, first text edit
349
- b: dict, second text edit
350
351
Returns:
352
int: Comparison result (-1, 0, 1)
353
"""
354
355
def merge_sort_text_edits(text_edits):
356
"""
357
Sort text edits by position.
358
359
Parameters:
360
- text_edits: list, text edits to sort
361
362
Returns:
363
list: Sorted text edits
364
"""
365
366
def apply_text_edits(doc, text_edits):
367
"""
368
Apply text edits to document.
369
370
Parameters:
371
- doc: Document, document to edit
372
- text_edits: list, text edits to apply
373
374
Returns:
375
str: Document text after applying edits
376
"""
377
```
378
379
### Code Formatters
380
381
Classes for code formatting with different tools.
382
383
```python { .api }
384
class Formatter:
385
"""Base class for code formatters."""
386
387
@property
388
def is_installed(self):
389
"""
390
Check if formatter is available.
391
392
Returns:
393
bool: True if formatter is installed
394
"""
395
396
def format(self, code, line_length=79):
397
"""
398
Format code.
399
400
Parameters:
401
- code: str, code to format
402
- line_length: int, maximum line length
403
404
Returns:
405
str: Formatted code
406
"""
407
408
class RuffFormatter(Formatter):
409
"""Ruff code formatter."""
410
411
class BlackFormatter(Formatter):
412
"""Black code formatter."""
413
```
414
415
### Constants
416
417
Utility constants used throughout the server.
418
419
```python { .api }
420
JEDI_VERSION = "0.19.1" # Jedi library version
421
422
# End-of-line character sequences in preference order
423
EOL_CHARS = ["\r\n", "\r", "\n"]
424
425
# Regex for matching EOL characters
426
EOL_REGEX = re.compile(r"(\r\n|\r|\n)")
427
428
# Markup kinds supported by server
429
SERVER_SUPPORTED_MARKUP_KINDS = {"plaintext", "markdown"}
430
431
# Available formatters
432
formatters = {
433
"ruff": RuffFormatter(),
434
"black": BlackFormatter()
435
}
436
```
437
438
### Exceptions
439
440
Custom exceptions for text editing operations.
441
442
```python { .api }
443
class OverLappingTextEditException(Exception):
444
"""Raised when text edits overlap and cannot be applied."""
445
```
446
447
## Usage Examples
448
449
### Debouncing and Throttling
450
451
```python
452
from pylsp._utils import debounce, throttle
453
454
# Debounce linting to avoid excessive calls
455
@debounce(0.5, keyed_by='document')
456
def run_linting(document):
457
# Linting logic here
458
pass
459
460
# Throttle progress updates
461
@throttle(seconds=1)
462
def update_progress(message):
463
# Progress update logic
464
pass
465
```
466
467
### Path and File Operations
468
469
```python
470
from pylsp._utils import find_parents, path_to_dot_name
471
472
# Find configuration files
473
config_files = find_parents(
474
"/project",
475
"/project/src/module.py",
476
["setup.cfg", "pyproject.toml"]
477
)
478
479
# Convert path to module name
480
module_name = path_to_dot_name("/project/src/utils/helper.py")
481
print(module_name) # "src.utils.helper"
482
```
483
484
### Text Formatting
485
486
```python
487
from pylsp._utils import (
488
format_docstring, escape_markdown, wrap_signature,
489
format_signature, convert_signatures_to_markdown
490
)
491
492
# Format docstring for hover
493
docstring = format_docstring(
494
"Function description\n\nArgs:\n x: parameter",
495
"markdown",
496
signatures=["def func(x: int) -> str"]
497
)
498
499
# Escape markdown content
500
escaped = escape_markdown("Text with *special* chars")
501
502
# Wrap signature
503
wrapped = wrap_signature("def example(param: str) -> int")
504
505
# Format signature with black or ruff
506
config = {"line_length": 88}
507
formatted_sig = format_signature("def func(a,b,c):", config, "black")
508
509
# Convert multiple signatures to markdown
510
sigs = ["def func1(x: int) -> str", "def func2(y: float) -> bool"]
511
markdown_sigs = convert_signatures_to_markdown(sigs, config)
512
```
513
514
### Position Conversion
515
516
```python
517
from pylsp._utils import position_to_jedi_linecolumn, clip_column
518
519
# Convert LSP position to Jedi format
520
lsp_pos = {"line": 5, "character": 10}
521
jedi_line, jedi_col = position_to_jedi_linecolumn(document, lsp_pos)
522
523
# Clip column to line bounds
524
clipped_col = clip_column(100, document.lines, 5) # Won't exceed line length
525
```
526
527
### URI Handling
528
529
```python
530
from pylsp.uris import to_fs_path, from_fs_path, uri_with
531
532
# Convert between URIs and paths
533
path = to_fs_path("file:///project/src/main.py") # "/project/src/main.py"
534
uri = from_fs_path("/project/src/main.py") # "file:///project/src/main.py"
535
536
# Modify URI components
537
new_uri = uri_with("file:///project/old.py", path="/project/new.py")
538
```
539
540
### Text Edits
541
542
```python
543
from pylsp.text_edit import apply_text_edits, merge_sort_text_edits
544
545
# Apply text edits to document
546
edits = [
547
{
548
"range": {
549
"start": {"line": 0, "character": 0},
550
"end": {"line": 0, "character": 5}
551
},
552
"newText": "print"
553
}
554
]
555
556
# Sort edits and apply
557
sorted_edits = merge_sort_text_edits(edits)
558
new_text = apply_text_edits(document, sorted_edits)
559
```
560
561
### Code Formatting
562
563
```python
564
from pylsp._utils import formatters
565
566
# Use available formatters
567
black_formatter = formatters["black"]
568
if black_formatter.is_installed:
569
formatted_code = black_formatter.format("def f():pass", line_length=88)
570
571
ruff_formatter = formatters["ruff"]
572
if ruff_formatter.is_installed:
573
formatted_code = ruff_formatter.format(code, line_length=100)
574
```
575
576
### Data Manipulation
577
578
```python
579
from pylsp._utils import merge_dicts, list_to_string
580
581
# Merge configuration dictionaries
582
base_config = {"plugins": {"pyflakes": {"enabled": True}}}
583
user_config = {"plugins": {"pyflakes": {"ignore": ["E203"]}}}
584
merged = merge_dicts(base_config, user_config)
585
586
# Convert list to string
587
error_codes = ["E203", "W503", "E501"]
588
ignore_string = list_to_string(error_codes) # "E203,W503,E501"
589
```