0
# Progress Reporting
1
2
Built-in progress reporting system for long-running operations with client-side progress bar integration, cancellation support, and comprehensive progress lifecycle management.
3
4
## Capabilities
5
6
### Progress Class
7
8
Central progress management system that handles work-done progress reporting with client integration and cancellation support.
9
10
```python { .api }
11
class Progress:
12
"""
13
Progress reporting system for long-running operations.
14
15
Manages client-side progress bars, handles cancellation requests,
16
and provides comprehensive progress lifecycle management for
17
language server operations.
18
"""
19
20
def __init__(self, lsp: LanguageServerProtocol) -> None:
21
"""
22
Initialize progress manager.
23
24
Parameters:
25
- lsp: LanguageServerProtocol - Protocol instance for client communication
26
"""
27
28
def create_task(
29
self,
30
token: ProgressToken,
31
title: str,
32
cancellable: bool = True,
33
message: str = None,
34
percentage: int = None
35
) -> Future:
36
"""
37
Create progress task with client progress bar.
38
39
Parameters:
40
- token: ProgressToken - Unique token for progress tracking
41
- title: str - Progress task title shown to user
42
- cancellable: bool - Whether task can be cancelled (default: True)
43
- message: str - Optional initial message
44
- percentage: int - Optional initial percentage (0-100)
45
46
Returns:
47
Future that resolves when progress is created or fails
48
"""
49
50
def update(
51
self,
52
token: ProgressToken,
53
message: str = None,
54
percentage: int = None
55
) -> None:
56
"""
57
Update progress with new message or percentage.
58
59
Parameters:
60
- token: ProgressToken - Progress token to update
61
- message: str - Optional progress message
62
- percentage: int - Optional progress percentage (0-100)
63
"""
64
65
def end(self, token: ProgressToken, message: str = None) -> None:
66
"""
67
End progress reporting.
68
69
Parameters:
70
- token: ProgressToken - Progress token to end
71
- message: str - Optional final message
72
"""
73
74
@property
75
def tokens(self) -> Dict[ProgressToken, Future]:
76
"""Access to active progress tokens and their futures."""
77
```
78
79
### Progress Message Types
80
81
Core progress message types for LSP work-done progress protocol implementation.
82
83
```python { .api }
84
# Progress message types from lsprotocol.types
85
86
class WorkDoneProgressBegin:
87
"""
88
Progress begin notification structure.
89
90
Attributes:
91
- title: str - Progress title
92
- cancellable: bool - Whether progress can be cancelled
93
- message: str - Optional message
94
- percentage: int - Optional percentage
95
"""
96
97
class WorkDoneProgressReport:
98
"""
99
Progress update notification structure.
100
101
Attributes:
102
- message: str - Optional progress message
103
- percentage: int - Optional percentage (0-100)
104
- cancellable: bool - Whether progress can be cancelled
105
"""
106
107
class WorkDoneProgressEnd:
108
"""
109
Progress end notification structure.
110
111
Attributes:
112
- message: str - Optional final message
113
"""
114
115
class WorkDoneProgressCreateParams:
116
"""
117
Parameters for creating work-done progress.
118
119
Attributes:
120
- token: ProgressToken - Progress token
121
"""
122
123
class ProgressParams:
124
"""
125
Progress notification parameters.
126
127
Attributes:
128
- token: ProgressToken - Progress token
129
- value: Union[WorkDoneProgressBegin, WorkDoneProgressReport, WorkDoneProgressEnd]
130
"""
131
```
132
133
## Usage Examples
134
135
### Basic Progress Reporting
136
137
```python
138
import asyncio
139
import uuid
140
from pygls.server import LanguageServer
141
from pygls.progress import Progress
142
143
server = LanguageServer("progress-example", "1.0.0")
144
145
@server.command("myServer.longOperation")
146
async def long_operation(params):
147
# Create unique progress token
148
token = str(uuid.uuid4())
149
150
# Start progress
151
await server.lsp.progress.create_task(
152
token=token,
153
title="Processing Files",
154
cancellable=True,
155
message="Starting analysis..."
156
)
157
158
try:
159
# Simulate long-running work with progress updates
160
total_files = 100
161
162
for i in range(total_files):
163
# Check if operation was cancelled
164
if token in server.lsp.progress.tokens:
165
future = server.lsp.progress.tokens[token]
166
if future.cancelled():
167
return {"cancelled": True}
168
169
# Simulate file processing
170
await asyncio.sleep(0.1)
171
172
# Update progress
173
percentage = int((i + 1) / total_files * 100)
174
server.lsp.progress.update(
175
token=token,
176
message=f"Processing file {i + 1}/{total_files}",
177
percentage=percentage
178
)
179
180
# End progress
181
server.lsp.progress.end(
182
token=token,
183
message="Analysis completed successfully"
184
)
185
186
return {"result": "Operation completed", "files_processed": total_files}
187
188
except Exception as e:
189
# End progress with error
190
server.lsp.progress.end(
191
token=token,
192
message=f"Operation failed: {str(e)}"
193
)
194
raise
195
```
196
197
### Progress with Cancellation Handling
198
199
```python
200
import time
201
from concurrent.futures import ThreadPoolExecutor
202
203
@server.command("myServer.heavyComputation")
204
@server.thread()
205
def heavy_computation(params):
206
"""Long-running computation with cancellation support."""
207
token = str(uuid.uuid4())
208
209
# Start progress on main thread
210
asyncio.run_coroutine_threadsafe(
211
server.lsp.progress.create_task(
212
token=token,
213
title="Heavy Computation",
214
cancellable=True,
215
message="Initializing computation..."
216
),
217
server.loop
218
)
219
220
try:
221
total_iterations = 1000
222
223
for i in range(total_iterations):
224
# Check cancellation status
225
if token in server.lsp.progress.tokens:
226
future = server.lsp.progress.tokens[token]
227
if future.cancelled():
228
return {"cancelled": True, "iterations_completed": i}
229
230
# Simulate heavy computation
231
time.sleep(0.01) # Blocking operation
232
233
# Update progress every 10 iterations
234
if i % 10 == 0:
235
percentage = int(i / total_iterations * 100)
236
237
# Update from thread
238
def update_progress():
239
server.lsp.progress.update(
240
token=token,
241
message=f"Computing iteration {i}/{total_iterations}",
242
percentage=percentage
243
)
244
245
asyncio.run_coroutine_threadsafe(
246
asyncio.create_task(update_progress()),
247
server.loop
248
)
249
250
# Complete progress
251
def end_progress():
252
server.lsp.progress.end(
253
token=token,
254
message="Computation completed"
255
)
256
257
asyncio.run_coroutine_threadsafe(
258
asyncio.create_task(end_progress()),
259
server.loop
260
)
261
262
return {"result": "Computation completed", "iterations": total_iterations}
263
264
except Exception as e:
265
# Handle error in progress
266
def end_with_error():
267
server.lsp.progress.end(
268
token=token,
269
message=f"Computation failed: {str(e)}"
270
)
271
272
asyncio.run_coroutine_threadsafe(
273
asyncio.create_task(end_with_error()),
274
server.loop
275
)
276
raise
277
```
278
279
### Multiple Progress Tasks
280
281
```python
282
@server.command("myServer.multiStepProcess")
283
async def multi_step_process(params):
284
"""Process with multiple progress bars for different steps."""
285
286
# Step 1: File scanning
287
scan_token = str(uuid.uuid4())
288
await server.lsp.progress.create_task(
289
token=scan_token,
290
title="Scanning Files",
291
cancellable=False,
292
message="Scanning project directory..."
293
)
294
295
files = []
296
for i in range(50):
297
await asyncio.sleep(0.02)
298
files.append(f"file_{i}.py")
299
300
server.lsp.progress.update(
301
token=scan_token,
302
message=f"Found {len(files)} files",
303
percentage=int(i / 50 * 100)
304
)
305
306
server.lsp.progress.end(scan_token, "File scanning completed")
307
308
# Step 2: Analysis
309
analysis_token = str(uuid.uuid4())
310
await server.lsp.progress.create_task(
311
token=analysis_token,
312
title="Analyzing Files",
313
cancellable=True,
314
message="Starting analysis..."
315
)
316
317
analysis_results = {}
318
for i, file in enumerate(files):
319
# Check cancellation
320
if analysis_token in server.lsp.progress.tokens:
321
future = server.lsp.progress.tokens[analysis_token]
322
if future.cancelled():
323
return {"cancelled": True, "partial_results": analysis_results}
324
325
await asyncio.sleep(0.05)
326
analysis_results[file] = {"lines": i * 10, "functions": i * 2}
327
328
percentage = int((i + 1) / len(files) * 100)
329
server.lsp.progress.update(
330
token=analysis_token,
331
message=f"Analyzed {i + 1}/{len(files)} files",
332
percentage=percentage
333
)
334
335
server.lsp.progress.end(analysis_token, "Analysis completed")
336
337
return {
338
"files_scanned": len(files),
339
"analysis_results": analysis_results
340
}
341
```
342
343
### Progress Context Manager
344
345
```python
346
from contextlib import asynccontextmanager
347
348
class ProgressManager:
349
def __init__(self, server):
350
self.server = server
351
352
@asynccontextmanager
353
async def progress_context(
354
self,
355
title: str,
356
cancellable: bool = True,
357
initial_message: str = None
358
):
359
"""Context manager for automatic progress lifecycle management."""
360
token = str(uuid.uuid4())
361
362
try:
363
# Start progress
364
await self.server.lsp.progress.create_task(
365
token=token,
366
title=title,
367
cancellable=cancellable,
368
message=initial_message or "Starting..."
369
)
370
371
# Yield progress controller
372
controller = ProgressController(self.server, token)
373
yield controller
374
375
finally:
376
# Always end progress
377
self.server.lsp.progress.end(
378
token=token,
379
message="Operation completed"
380
)
381
382
class ProgressController:
383
def __init__(self, server, token):
384
self.server = server
385
self.token = token
386
387
def update(self, message: str = None, percentage: int = None):
388
"""Update progress."""
389
self.server.lsp.progress.update(
390
token=self.token,
391
message=message,
392
percentage=percentage
393
)
394
395
def is_cancelled(self) -> bool:
396
"""Check if progress was cancelled."""
397
if self.token in self.server.lsp.progress.tokens:
398
future = self.server.lsp.progress.tokens[self.token]
399
return future.cancelled()
400
return False
401
402
# Usage with context manager
403
progress_manager = ProgressManager(server)
404
405
@server.command("myServer.contextManagedOperation")
406
async def context_managed_operation(params):
407
async with progress_manager.progress_context(
408
title="Context Managed Operation",
409
cancellable=True,
410
initial_message="Initializing..."
411
) as progress:
412
413
for i in range(100):
414
if progress.is_cancelled():
415
return {"cancelled": True}
416
417
await asyncio.sleep(0.01)
418
419
progress.update(
420
message=f"Step {i + 1}/100",
421
percentage=i + 1
422
)
423
424
return {"result": "Operation completed successfully"}
425
```
426
427
### Custom Progress Notifications
428
429
```python
430
from lsprotocol.types import (
431
PROGRESS,
432
WINDOW_WORK_DONE_PROGRESS_CREATE,
433
WorkDoneProgressBegin,
434
WorkDoneProgressReport,
435
WorkDoneProgressEnd
436
)
437
438
class CustomProgress:
439
def __init__(self, server):
440
self.server = server
441
self.active_progress = {}
442
443
async def start_custom_progress(
444
self,
445
token: str,
446
title: str,
447
custom_data: dict = None
448
):
449
"""Start progress with custom data."""
450
451
# Create progress
452
await self.server.lsp.send_request(
453
WINDOW_WORK_DONE_PROGRESS_CREATE,
454
{"token": token}
455
)
456
457
# Send begin with custom data
458
begin_data = WorkDoneProgressBegin(
459
title=title,
460
cancellable=True,
461
message="Custom progress started"
462
)
463
464
# Add custom fields
465
if custom_data:
466
begin_data.__dict__.update(custom_data)
467
468
self.server.lsp.send_notification(
469
PROGRESS,
470
{"token": token, "value": begin_data}
471
)
472
473
self.active_progress[token] = {
474
"title": title,
475
"custom_data": custom_data
476
}
477
478
def update_custom_progress(
479
self,
480
token: str,
481
percentage: int,
482
custom_fields: dict = None
483
):
484
"""Update progress with custom fields."""
485
486
report_data = WorkDoneProgressReport(
487
percentage=percentage,
488
message=f"Progress: {percentage}%"
489
)
490
491
# Add custom fields
492
if custom_fields:
493
report_data.__dict__.update(custom_fields)
494
495
self.server.lsp.send_notification(
496
PROGRESS,
497
{"token": token, "value": report_data}
498
)
499
500
def end_custom_progress(self, token: str, final_data: dict = None):
501
"""End progress with custom final data."""
502
503
end_data = WorkDoneProgressEnd(
504
message="Custom progress completed"
505
)
506
507
if final_data:
508
end_data.__dict__.update(final_data)
509
510
self.server.lsp.send_notification(
511
PROGRESS,
512
{"token": token, "value": end_data}
513
)
514
515
self.active_progress.pop(token, None)
516
517
# Usage
518
custom_progress = CustomProgress(server)
519
520
@server.command("myServer.customProgressOperation")
521
async def custom_progress_operation(params):
522
token = str(uuid.uuid4())
523
524
await custom_progress.start_custom_progress(
525
token=token,
526
title="Custom Operation",
527
custom_data={"operation_type": "analysis", "version": "2.0"}
528
)
529
530
for i in range(10):
531
await asyncio.sleep(0.1)
532
533
custom_progress.update_custom_progress(
534
token=token,
535
percentage=(i + 1) * 10,
536
custom_fields={"current_step": f"step_{i}", "details": f"Processing item {i}"}
537
)
538
539
custom_progress.end_custom_progress(
540
token=token,
541
final_data={"total_processed": 10, "success": True}
542
)
543
544
return {"completed": True}
545
```